From f9193f4ce7efbc0af25d4dcdfe642c7c566d18e3 Mon Sep 17 00:00:00 2001 From: Daniel Verkamp Date: Mon, 9 May 2016 13:16:32 -0700 Subject: [PATCH] json: add JSON parser and encoder libraries Change-Id: Id73fb7e300d66d31a7c3986c0334b6f56e284905 Signed-off-by: Daniel Verkamp --- autotest.sh | 1 + include/spdk/json.h | 208 +++++++ lib/Makefile | 2 +- lib/json/Makefile | 39 ++ lib/json/json_internal.h | 290 ++++++++++ lib/json/json_parse.c | 616 ++++++++++++++++++++ lib/json/json_util.c | 284 ++++++++++ lib/json/json_write.c | 365 ++++++++++++ mk/json.unittest.mk | 60 ++ test/lib/Makefile | 2 +- test/lib/json/Makefile | 44 ++ test/lib/json/json.sh | 15 + test/lib/json/parse/.gitignore | 1 + test/lib/json/parse/Makefile | 38 ++ test/lib/json/parse/json_parse_ut.c | 838 ++++++++++++++++++++++++++++ test/lib/json/util/.gitignore | 1 + test/lib/json/util/Makefile | 38 ++ test/lib/json/util/json_util_ut.c | 137 +++++ test/lib/json/write/.gitignore | 1 + test/lib/json/write/Makefile | 38 ++ test/lib/json/write/json_write_ut.c | 619 ++++++++++++++++++++ unittest.sh | 6 + 22 files changed, 3641 insertions(+), 2 deletions(-) create mode 100644 include/spdk/json.h create mode 100644 lib/json/Makefile create mode 100644 lib/json/json_internal.h create mode 100644 lib/json/json_parse.c create mode 100644 lib/json/json_util.c create mode 100644 lib/json/json_write.c create mode 100644 mk/json.unittest.mk create mode 100644 test/lib/json/Makefile create mode 100755 test/lib/json/json.sh create mode 100644 test/lib/json/parse/.gitignore create mode 100644 test/lib/json/parse/Makefile create mode 100644 test/lib/json/parse/json_parse_ut.c create mode 100644 test/lib/json/util/.gitignore create mode 100644 test/lib/json/util/Makefile create mode 100644 test/lib/json/util/json_util_ut.c create mode 100644 test/lib/json/write/.gitignore create mode 100644 test/lib/json/write/Makefile create mode 100644 test/lib/json/write/json_write_ut.c diff --git a/autotest.sh b/autotest.sh index f12bb8afe..077b8d2b5 100755 --- a/autotest.sh +++ b/autotest.sh @@ -47,6 +47,7 @@ timing_enter lib time test/lib/nvme/nvme.sh time test/lib/memory/memory.sh time test/lib/ioat/ioat.sh +time test/lib/json/json.sh timing_exit lib diff --git a/include/spdk/json.h b/include/spdk/json.h new file mode 100644 index 000000000..dacc4a9f5 --- /dev/null +++ b/include/spdk/json.h @@ -0,0 +1,208 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * JSON parsing and encoding + */ + +#ifndef SPDK_JSON_H_ +#define SPDK_JSON_H_ + +#include +#include +#include +#include +#include + +enum spdk_json_val_type { + SPDK_JSON_VAL_INVALID, + SPDK_JSON_VAL_NULL, + SPDK_JSON_VAL_TRUE, + SPDK_JSON_VAL_FALSE, + SPDK_JSON_VAL_NUMBER, + SPDK_JSON_VAL_STRING, + SPDK_JSON_VAL_ARRAY_BEGIN, + SPDK_JSON_VAL_ARRAY_END, + SPDK_JSON_VAL_OBJECT_BEGIN, + SPDK_JSON_VAL_OBJECT_END, + SPDK_JSON_VAL_NAME, +}; + +struct spdk_json_val { + /** + * Pointer to the location of the value within the parsed JSON input. + * + * For SPDK_JSON_VAL_STRING and SPDK_JSON_VAL_NAME, + * this points to the beginning of the decoded UTF-8 string without quotes. + * + * For SPDK_JSON_VAL_NUMBER, this points to the beginning of the number as represented in + * the original JSON (text representation, not converted to a numeric value). + */ + void *start; + + /** + * Length of value. + * + * For SPDK_JSON_VAL_STRING, SPDK_JSON_VAL_NUMBER, and SPDK_JSON_VAL_NAME, + * this is the length in bytes of the value starting at \ref start. + * + * For SPDK_JSON_VAL_ARRAY_BEGIN and SPDK_JSON_VAL_OBJECT_BEGIN, + * this is the number of values contained within the array or object (including + * nested objects and arrays, but not including the _END value). The array or object _END + * value can be found by advancing len values from the _BEGIN value. + */ + uint32_t len; + + /** + * Type of value. + */ + enum spdk_json_val_type type; +}; + +/** + * Invalid JSON syntax. + */ +#define SPDK_JSON_PARSE_INVALID -1 + +/** + * JSON was valid up to the end of the current buffer, but did not represent a complete JSON value. + */ +#define SPDK_JSON_PARSE_INCOMPLETE -2 + +#define SPDK_JSON_PARSE_MAX_DEPTH_EXCEEDED -3 + +/** + * Decode JSON strings and names in place (modify the input buffer). + */ +#define SPDK_JSON_PARSE_FLAG_DECODE_IN_PLACE 0x000000001 + +/* + * Parse JSON data. + * + * \param data Raw JSON data; must be encoded in UTF-8. + * Note that the data may be modified to perform in-place string decoding. + * + * \param size Size of data in bytes. + * + * \param end If non-NULL, this will be filled a pointer to the byte just beyond the end + * of the valid JSON. + * + * \return Number of values parsed, or negative on failure: + * SPDK_JSON_PARSE_INVALID if the provided data was not valid JSON, or + * SPDK_JSON_PARSE_INCOMPLETE if the provided data was not a complete JSON value. + */ +ssize_t spdk_json_parse(void *json, size_t size, struct spdk_json_val *values, size_t num_values, + void **end, uint32_t flags); + +typedef int (*spdk_json_decode_fn)(const struct spdk_json_val *val, void *out); + +struct spdk_json_object_decoder { + const char *name; + size_t offset; + spdk_json_decode_fn decode_func; + bool optional; +}; + +int spdk_json_decode_object(const struct spdk_json_val *values, + const struct spdk_json_object_decoder *decoders, size_t num_decoders, void *out); +int spdk_json_decode_array(const struct spdk_json_val *values, spdk_json_decode_fn decode_func, + void *out, size_t max_size, size_t *out_size, size_t stride); + +int spdk_json_decode_int32(const struct spdk_json_val *val, void *out); +int spdk_json_decode_uint32(const struct spdk_json_val *val, void *out); +int spdk_json_decode_string(const struct spdk_json_val *val, void *out); + +/** + * Get length of a value in number of values. + * + * This can be used to skip over a value while interpreting parse results. + * + * For SPDK_JSON_VAL_ARRAY_BEGIN and SPDK_JSON_VAL_OBJECT_BEGIN, + * this returns the number of values contained within this value, plus the _BEGIN and _END values. + * + * For all other values, this returns 1. + */ +size_t spdk_json_val_len(const struct spdk_json_val *val); + +/** + * Compare JSON string with null terminated C string. + * + * \return true if strings are equal or false if not + */ +bool spdk_json_strequal(const struct spdk_json_val *val, const char *str); + +/** + * Equivalent of strdup() for JSON string values. + * + * If val is not representable as a C string (contains embedded '\0' characters), + * returns NULL. + * + * Caller is responsible for passing the result to free() when it is no longer needed. + */ +char *spdk_json_strdup(const struct spdk_json_val *val); + +int spdk_json_number_to_double(const struct spdk_json_val *val, double *num); +int spdk_json_number_to_int32(const struct spdk_json_val *val, int32_t *num); +int spdk_json_number_to_uint32(const struct spdk_json_val *val, uint32_t *num); + +struct spdk_json_write_ctx; + +typedef int (*spdk_json_write_cb)(void *cb_ctx, const void *data, size_t size); + +struct spdk_json_write_ctx *spdk_json_write_begin(spdk_json_write_cb write_cb, void *cb_ctx, + uint32_t flags); +int spdk_json_write_end(struct spdk_json_write_ctx *w); +int spdk_json_write_null(struct spdk_json_write_ctx *w); +int spdk_json_write_bool(struct spdk_json_write_ctx *w, bool val); +int spdk_json_write_int32(struct spdk_json_write_ctx *w, int32_t val); +int spdk_json_write_uint32(struct spdk_json_write_ctx *w, uint32_t val); +int spdk_json_write_string(struct spdk_json_write_ctx *w, const char *val); +int spdk_json_write_string_raw(struct spdk_json_write_ctx *w, const char *val, size_t len); +int spdk_json_write_array_begin(struct spdk_json_write_ctx *w); +int spdk_json_write_array_end(struct spdk_json_write_ctx *w); +int spdk_json_write_object_begin(struct spdk_json_write_ctx *w); +int spdk_json_write_object_end(struct spdk_json_write_ctx *w); +int spdk_json_write_name(struct spdk_json_write_ctx *w, const char *name); +int spdk_json_write_name_raw(struct spdk_json_write_ctx *w, const char *name, size_t len); + +int spdk_json_write_val(struct spdk_json_write_ctx *w, const struct spdk_json_val *val); + +/* + * Append bytes directly to the output stream without validation. + * + * Can be used to write values with specific encodings that differ from the JSON writer output. + */ +int spdk_json_write_val_raw(struct spdk_json_write_ctx *w, const void *data, size_t len); + +#endif diff --git a/lib/Makefile b/lib/Makefile index e75daeed2..d6f1b3859 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -34,7 +34,7 @@ SPDK_ROOT_DIR := $(CURDIR)/.. include $(SPDK_ROOT_DIR)/mk/spdk.common.mk -DIRS-y += conf memory util nvme ioat +DIRS-y += conf json memory util nvme ioat .PHONY: all clean $(DIRS-y) diff --git a/lib/json/Makefile b/lib/json/Makefile new file mode 100644 index 000000000..68c9964be --- /dev/null +++ b/lib/json/Makefile @@ -0,0 +1,39 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +SPDK_ROOT_DIR := $(CURDIR)/../.. + +C_SRCS = json_parse.c json_util.c json_write.c +LIBNAME = json + +include $(SPDK_ROOT_DIR)/mk/spdk.lib.mk diff --git a/lib/json/json_internal.h b/lib/json/json_internal.h new file mode 100644 index 000000000..c8a88b0cb --- /dev/null +++ b/lib/json/json_internal.h @@ -0,0 +1,290 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SPDK_JSON_INTERNAL_H_ +#define SPDK_JSON_INTERNAL_H_ + +#include "spdk/json.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SPDK_JSON_MAX_NESTING_DEPTH 64 + +static inline bool +utf8_tail(uint8_t c) +{ + /* c >= 0x80 && c <= 0xBF, or binary 01xxxxxx */ + return (c & 0xC0) == 0x80; +} + +/* + * Check for a valid UTF-8 encoding of a single codepoint. + * + * \return Length of valid UTF-8 byte sequence, or negative if invalid. + */ +static inline int +utf8_valid(const uint8_t *start, const uint8_t *end) +{ + const uint8_t *p = start; + uint8_t b0, b1, b2, b3; + + if (p == end) { + return 0; + } + + b0 = *p; + + if (b0 <= 0x7F) { + return 1; + } + + if (b0 <= 0xC1) { + /* Invalid start byte */ + return -1; + } + + if (++p == end) { + /* Not enough bytes left */ + return -1; + } + b1 = *p; + + if (b0 <= 0xDF) { + /* C2..DF 80..BF */ + if (!utf8_tail(b1)) { + return -1; + } + return 2; + } + + if (++p == end) { + /* Not enough bytes left */ + return -1; + } + b2 = *p; + + if (b0 == 0xE0) { + /* E0 A0..BF 80..BF */ + if (b1 < 0xA0 || b1 > 0xBF || !utf8_tail(b2)) { + return -1; + } + return 3; + } else if (b0 == 0xED && b1 >= 0xA0) { + /* + * UTF-16 surrogate pairs use U+D800..U+DFFF, which would be encoded as + * ED A0..BF 80..BF in UTF-8; however, surrogate pairs are not allowed in UTF-8. + */ + return -1; + } else if (b0 <= 0xEF) { + /* E1..EF 80..BF 80..BF */ + if (!utf8_tail(b1) || !utf8_tail(b2)) { + return -1; + } + return 3; + } + + if (++p == end) { + /* Not enough bytes left */ + return -1; + } + b3 = *p; + + if (b0 == 0xF0) { + /* F0 90..BF 80..BF 80..BF */ + if (b1 < 0x90 || b1 > 0xBF || !utf8_tail(b2) || !utf8_tail(b3)) { + return -1; + } + return 4; + } else if (b0 <= 0xF3) { + /* F1..F3 80..BF 80..BF 80..BF */ + if (!utf8_tail(b1) || !utf8_tail(b2) || !utf8_tail(b3)) { + return -1; + } + return 4; + } else if (b0 == 0xF4) { + /* F4 80..8F 80..BF 80..BF */ + if (b1 < 0x80 || b1 > 0x8F || !utf8_tail(b2) || !utf8_tail(b3)) { + return -1; + } + return 4; + } + + return -1; +} + +static inline uint32_t +utf8_decode_unsafe_1(const uint8_t *data) +{ + return data[0]; +} + +static inline uint32_t +utf8_decode_unsafe_2(const uint8_t *data) +{ + uint32_t codepoint; + + codepoint = ((data[0] & 0x1F) << 6); + codepoint |= (data[1] & 0x3F); + + return codepoint; +} + +static inline uint32_t +utf8_decode_unsafe_3(const uint8_t *data) +{ + uint32_t codepoint; + + codepoint = ((data[0] & 0x0F) << 12); + codepoint |= (data[1] & 0x3F) << 6; + codepoint |= (data[2] & 0x3F); + + return codepoint; +} + +static inline uint32_t +utf8_decode_unsafe_4(const uint8_t *data) +{ + uint32_t codepoint; + + codepoint = ((data[0] & 0x07) << 18); + codepoint |= (data[1] & 0x3F) << 12; + codepoint |= (data[2] & 0x3F) << 6; + codepoint |= (data[3] & 0x3F); + + return codepoint; +} + +/* + * Encode a single Unicode codepoint as UTF-8. + * + * buf must have at least 4 bytes of space available (hence unsafe). + * + * \return Number of bytes appended to buf, or negative if encoding failed. + */ +static inline int +utf8_encode_unsafe(uint8_t *buf, uint32_t c) +{ + if (c <= 0x7F) { + buf[0] = c; + return 1; + } else if (c <= 0x7FF) { + buf[0] = 0xC0 | (c >> 6); + buf[1] = 0x80 | (c & 0x3F); + return 2; + } else if (c >= 0xD800 && c <= 0xDFFF) { + /* UTF-16 surrogate pairs - invalid in UTF-8 */ + return -1; + } else if (c <= 0xFFFF) { + buf[0] = 0xE0 | (c >> 12); + buf[1] = 0x80 | ((c >> 6) & 0x3F); + buf[2] = 0x80 | (c & 0x3F); + return 3; + } else if (c <= 0x10FFFF) { + buf[0] = 0xF0 | (c >> 18); + buf[1] = 0x80 | ((c >> 12) & 0x3F); + buf[2] = 0x80 | ((c >> 6) & 0x3F); + buf[3] = 0x80 | (c & 0x3F); + return 4; + } + return -1; +} + +static inline int +utf8_codepoint_len(uint32_t c) +{ + if (c <= 0x7F) { + return 1; + } else if (c <= 0x7FF) { + return 2; + } else if (c >= 0xD800 && c <= 0xDFFF) { + /* UTF-16 surrogate pairs - invalid in UTF-8 */ + return -1; + } else if (c <= 0xFFFF) { + return 3; + } else if (c <= 0x10FFFF) { + return 4; + } + return -1; +} + +static inline bool +utf16_valid_surrogate_high(uint32_t val) +{ + return val >= 0xD800 && val <= 0xDBFF; +} + +static inline bool +utf16_valid_surrogate_low(uint32_t val) +{ + return val >= 0xDC00 && val <= 0xDFFF; +} + +static inline uint32_t +utf16_decode_surrogate_pair(uint32_t high, uint32_t low) +{ + uint32_t codepoint; + + assert(utf16_valid_surrogate_high(high)); + assert(utf16_valid_surrogate_low(low)); + + codepoint = low; + codepoint &= 0x3FF; + codepoint |= ((high & 0x3FF) << 10); + codepoint += 0x10000; + + return codepoint; +} + +static inline void +utf16_encode_surrogate_pair(uint32_t codepoint, uint16_t *high, uint16_t *low) +{ + assert(codepoint >= 0x10000); + assert(codepoint <= 0x10FFFF); + + codepoint -= 0x10000; + *high = 0xD800 | (codepoint >> 10); + *low = 0xDC00 | (codepoint & 0x3FF); + + assert(utf16_valid_surrogate_high(*high)); + assert(utf16_valid_surrogate_low(*low)); +} + +#endif diff --git a/lib/json/json_parse.c b/lib/json/json_parse.c new file mode 100644 index 000000000..968c55bbb --- /dev/null +++ b/lib/json/json_parse.c @@ -0,0 +1,616 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "json_internal.h" + +static int +hex_value(uint8_t c) +{ +#define V(x, y) [x] = y + 1 + static const int8_t val[256] = { + V('0', 0), V('1', 1), V('2', 2), V('3', 3), V('4', 4), + V('5', 5), V('6', 6), V('7', 7), V('8', 8), V('9', 9), + V('A', 0xA), V('B', 0xB), V('C', 0xC), V('D', 0xD), V('E', 0xE), V('F', 0xF), + V('a', 0xA), V('b', 0xB), V('c', 0xC), V('d', 0xD), V('e', 0xE), V('f', 0xF), + }; +#undef V + + return val[c] - 1; +} + +static int +json_decode_string_escape_unicode(uint8_t **strp, uint8_t *buf_end, uint8_t *out) +{ + uint8_t *str = *strp; + int v0, v1, v2, v3; + uint32_t val; + uint32_t surrogate_high = 0; + int rc; +decode: + /* \uXXXX */ + assert(buf_end > str); + + if (*str++ != '\\') return SPDK_JSON_PARSE_INVALID; + if (buf_end == str) return SPDK_JSON_PARSE_INCOMPLETE; + + if (*str++ != 'u') return SPDK_JSON_PARSE_INVALID; + if (buf_end == str) return SPDK_JSON_PARSE_INCOMPLETE; + + if ((v3 = hex_value(*str++)) < 0) return SPDK_JSON_PARSE_INVALID; + if (buf_end == str) return SPDK_JSON_PARSE_INCOMPLETE; + + if ((v2 = hex_value(*str++)) < 0) return SPDK_JSON_PARSE_INVALID; + if (buf_end == str) return SPDK_JSON_PARSE_INCOMPLETE; + + if ((v1 = hex_value(*str++)) < 0) return SPDK_JSON_PARSE_INVALID; + if (buf_end == str) return SPDK_JSON_PARSE_INCOMPLETE; + + if ((v0 = hex_value(*str++)) < 0) return SPDK_JSON_PARSE_INVALID; + if (buf_end == str) return SPDK_JSON_PARSE_INCOMPLETE; + + val = v0 | (v1 << 4) | (v2 << 8) | (v3 << 12); + + if (surrogate_high) { + /* We already parsed the high surrogate, so this should be the low part. */ + if (!utf16_valid_surrogate_low(val)) { + return SPDK_JSON_PARSE_INVALID; + } + + /* Convert UTF-16 surrogate pair into codepoint and fall through to utf8_encode. */ + val = utf16_decode_surrogate_pair(surrogate_high, val); + } else if (utf16_valid_surrogate_high(val)) { + surrogate_high = val; + + /* + * We parsed a \uXXXX sequence that decoded to the first half of a + * UTF-16 surrogate pair, so it must be immediately followed by another + * \uXXXX escape. + * + * Loop around to get the low half of the surrogate pair. + */ + if (buf_end == str) return SPDK_JSON_PARSE_INCOMPLETE; + goto decode; + } else if (utf16_valid_surrogate_low(val)) { + /* + * We found the second half of surrogate pair without the first half; + * this is an invalid encoding. + */ + return SPDK_JSON_PARSE_INVALID; + } + + /* + * Convert Unicode escape (or surrogate pair) to UTF-8 in place. + * + * This is safe (will not write beyond the buffer) because the \uXXXX sequence is 6 bytes + * (or 12 bytes for surrogate pairs), and the longest possible UTF-8 encoding of a + * single codepoint is 4 bytes. + */ + if (out) { + rc = utf8_encode_unsafe(out, val); + } else { + rc = utf8_codepoint_len(val); + } + if (rc < 0) { + return SPDK_JSON_PARSE_INVALID; + } + + *strp = str; /* update input pointer */ + return rc; /* return number of bytes decoded */ +} + +static int +json_decode_string_escape_twochar(uint8_t **strp, uint8_t *buf_end, uint8_t *out) +{ + static const uint8_t escapes[256] = { + ['b'] = '\b', + ['f'] = '\f', + ['n'] = '\n', + ['r'] = '\r', + ['t'] = '\t', + ['/'] = '/', + ['"'] = '"', + ['\\'] = '\\', + }; + uint8_t *str = *strp; + uint8_t c; + + assert(buf_end > str); + if (buf_end - str < 2) { + return SPDK_JSON_PARSE_INCOMPLETE; + } + + assert(str[0] == '\\'); + + c = escapes[str[1]]; + if (c) { + if (out) { + *out = c; + } + *strp += 2; /* consumed two bytes */ + return 1; /* produced one byte */ + } + + return SPDK_JSON_PARSE_INVALID; +} + +/* + * Decode JSON string backslash escape. + * \param strp pointer to pointer to first character of escape (the backslash). + * *strp is also advanced to indicate how much input was consumed. + * + * \return Number of bytes appended to out + */ +static int +json_decode_string_escape(uint8_t **strp, uint8_t *buf_end, uint8_t *out) +{ + int rc; + + rc = json_decode_string_escape_twochar(strp, buf_end, out); + if (rc > 0) { + return rc; + } + + return json_decode_string_escape_unicode(strp, buf_end, out); +} + +/* + * Decode JSON string in place. + * + * \param str_start Pointer to the beginning of the string (the opening " character). + * + * \return Number of bytes in decoded string (beginning from start). + */ +static int +json_decode_string(uint8_t *str_start, uint8_t *buf_end, uint8_t **str_end, uint32_t flags) +{ + uint8_t *str = str_start; + uint8_t *out = str_start + 1; /* Decode string in place (skip the initial quote) */ + int rc; + + if (buf_end - str_start < 2) { + /* + * Shortest valid string (the empty string) is two bytes (""), + * so this can't possibly be valid + */ + return SPDK_JSON_PARSE_INCOMPLETE; + } + + if (*str++ != '"') { + return SPDK_JSON_PARSE_INVALID; + } + + while (str < buf_end) { + if (str[0] == '"') { + /* + * End of string. + * Update str_end to point at next input byte and return output length. + */ + *str_end = str + 1; + return out - str_start - 1; + } else if (str[0] == '\\') { + rc = json_decode_string_escape(&str, buf_end, + flags & SPDK_JSON_PARSE_FLAG_DECODE_IN_PLACE ? out : NULL); + assert(rc != 0); + if (rc < 0) { + return rc; + } + out += rc; + } else if (str[0] <= 0x1f) { + /* control characters must be escaped */ + return SPDK_JSON_PARSE_INVALID; + } else { + rc = utf8_valid(str, buf_end); + if (rc == 0) { + return SPDK_JSON_PARSE_INCOMPLETE; + } else if (rc < 0) { + return SPDK_JSON_PARSE_INVALID; + } + + if (out && out != str && (flags & SPDK_JSON_PARSE_FLAG_DECODE_IN_PLACE)) { + memmove(out, str, rc); + } + out += rc; + str += rc; + } + } + + /* If execution gets here, we ran out of buffer. */ + return SPDK_JSON_PARSE_INCOMPLETE; +} + +static int +json_valid_number(uint8_t *start, uint8_t *buf_end) +{ + uint8_t *p = start; + enum { + NUM_STATE_START, + NUM_STATE_INT_FIRST_DIGIT, + NUM_STATE_INT_DIGITS, + NUM_STATE_FRAC_OR_EXP, + NUM_STATE_FRAC_FIRST_DIGIT, + NUM_STATE_FRAC_DIGITS, + NUM_STATE_EXP_SIGN, + NUM_STATE_EXP_FIRST_DIGIT, + NUM_STATE_EXP_DIGITS, + } state = NUM_STATE_START; + + if (p >= buf_end) return -1; + + while (p != buf_end) { + uint8_t c = *p++; + + switch (c) { + case '0': + if (state == NUM_STATE_START || state == NUM_STATE_INT_FIRST_DIGIT) { + /* + * If the very first digit is 0, + * it must be the last digit of the integer part + * (no leading zeroes allowed). + */ + state = NUM_STATE_FRAC_OR_EXP; + break; + } + /* fallthrough */ + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + switch (state) { + case NUM_STATE_START: + case NUM_STATE_INT_FIRST_DIGIT: + state = NUM_STATE_INT_DIGITS; + break; + + case NUM_STATE_FRAC_FIRST_DIGIT: + state = NUM_STATE_FRAC_DIGITS; + break; + + case NUM_STATE_EXP_SIGN: + case NUM_STATE_EXP_FIRST_DIGIT: + state = NUM_STATE_EXP_DIGITS; + break; + + case NUM_STATE_INT_DIGITS: + case NUM_STATE_FRAC_DIGITS: + case NUM_STATE_EXP_DIGITS: + /* stay in same state */ + break; + + default: + return SPDK_JSON_PARSE_INVALID; + } + break; + + case '.': + if (state != NUM_STATE_INT_DIGITS && state != NUM_STATE_FRAC_OR_EXP) { + return SPDK_JSON_PARSE_INVALID; + } + state = NUM_STATE_FRAC_FIRST_DIGIT; + break; + + case 'e': + case 'E': + switch (state) { + case NUM_STATE_INT_DIGITS: + case NUM_STATE_FRAC_OR_EXP: + case NUM_STATE_FRAC_DIGITS: + state = NUM_STATE_EXP_SIGN; + break; + default: + return SPDK_JSON_PARSE_INVALID; + } + break; + + case '-': + if (state == NUM_STATE_START) { + state = NUM_STATE_INT_FIRST_DIGIT; + break; + } + /* fallthrough */ + case '+': + if (state == NUM_STATE_EXP_SIGN) { + state = NUM_STATE_EXP_FIRST_DIGIT; + } else { + return SPDK_JSON_PARSE_INVALID; + } + break; + default: + /* + * Got an unexpected character - back up and stop parsing number. + * The top-level parsing code will handle invalid trailing characters. + */ + p--; + goto done; + } + } + +done: + switch (state) { + case NUM_STATE_INT_DIGITS: + case NUM_STATE_FRAC_OR_EXP: + case NUM_STATE_FRAC_DIGITS: + case NUM_STATE_EXP_DIGITS: + /* Valid end state */ + return p - start; + + default: + return SPDK_JSON_PARSE_INCOMPLETE; + } +} + +struct json_literal { + enum spdk_json_val_type type; + uint32_t len; + uint8_t str[8]; +}; + +/* + * JSON only defines 3 possible literals; they can be uniquely identified by bits + * 3 and 4 of the first character: + * 'f' = 0b11[00]110 + * 'n' = 0b11[01]110 + * 't' = 0b11[10]100 + * These two bits can be used as an index into the g_json_literals array. + */ +static const struct json_literal g_json_literals[] = { + {SPDK_JSON_VAL_FALSE, 5, "false"}, + {SPDK_JSON_VAL_NULL, 4, "null"}, + {SPDK_JSON_VAL_TRUE, 4, "true"}, + {} +}; + +static int +match_literal(const uint8_t *start, const uint8_t *end, const uint8_t *literal, size_t len) +{ + assert(end >= start); + if ((size_t)(end - start) < len) { + return SPDK_JSON_PARSE_INCOMPLETE; + } + + if (memcmp(start, literal, len) != 0) { + return SPDK_JSON_PARSE_INVALID; + } + + return len; +} + +ssize_t +spdk_json_parse(void *json, size_t size, struct spdk_json_val *values, size_t num_values, + void **end, uint32_t flags) +{ + uint8_t *json_end = json + size; + enum spdk_json_val_type containers[SPDK_JSON_MAX_NESTING_DEPTH]; + size_t con_value[SPDK_JSON_MAX_NESTING_DEPTH]; + enum spdk_json_val_type con_type = SPDK_JSON_VAL_INVALID; + bool trailing_comma = false; + size_t depth = 0; /* index into containers */ + size_t cur_value = 0; /* index into values */ + size_t con_start_value; + uint8_t *data = json; + uint8_t *new_data; + int rc; + const struct json_literal *lit; + enum { + STATE_VALUE, /* initial state */ + STATE_VALUE_SEPARATOR, /* value separator (comma) */ + STATE_NAME, /* "name": value */ + STATE_NAME_SEPARATOR, /* colon */ + STATE_END, /* parsed the complete value, so only whitespace is valid */ + } state = STATE_VALUE; + +#define ADD_VALUE(t, val_start_ptr, val_end_ptr) \ + if (values && cur_value < num_values) { \ + values[cur_value].type = t; \ + values[cur_value].start = val_start_ptr; \ + values[cur_value].len = val_end_ptr - val_start_ptr; \ + } \ + cur_value++ + + while (data < json_end) { + uint8_t c = *data; + + switch (c) { + case ' ': + case '\t': + case '\r': + case '\n': + /* Whitespace is allowed between any tokens. */ + data++; + break; + + case 't': + case 'f': + case 'n': + /* true, false, or null */ + if (state != STATE_VALUE) return SPDK_JSON_PARSE_INVALID; + lit = &g_json_literals[(c >> 3) & 3]; /* See comment above g_json_literals[] */ + assert(lit->str[0] == c); + rc = match_literal(data, json_end, lit->str, lit->len); + if (rc < 0) return rc; + ADD_VALUE(lit->type, data, data + rc); + data += rc; + state = depth ? STATE_VALUE_SEPARATOR : STATE_END; + trailing_comma = false; + break; + + case '"': + if (state != STATE_VALUE && state != STATE_NAME) return SPDK_JSON_PARSE_INVALID; + rc = json_decode_string(data, json_end, &new_data, flags); + if (rc < 0) return rc; + /* + * Start is data + 1 to skip initial quote. + * Length is data + rc - 1 to skip both quotes. + */ + ADD_VALUE(state == STATE_VALUE ? SPDK_JSON_VAL_STRING : SPDK_JSON_VAL_NAME, + data + 1, data + rc - 1); + data = new_data; + if (state == STATE_NAME) { + state = STATE_NAME_SEPARATOR; + } else { + state = depth ? STATE_VALUE_SEPARATOR : STATE_END; + } + trailing_comma = false; + break; + + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (state != STATE_VALUE) return SPDK_JSON_PARSE_INVALID; + rc = json_valid_number(data, json_end); + if (rc < 0) return rc; + ADD_VALUE(SPDK_JSON_VAL_NUMBER, data, data + rc); + data += rc; + state = depth ? STATE_VALUE_SEPARATOR : STATE_END; + trailing_comma = false; + break; + + case '{': + case '[': + if (state != STATE_VALUE) return SPDK_JSON_PARSE_INVALID; + if (depth == SPDK_JSON_MAX_NESTING_DEPTH) { + return SPDK_JSON_PARSE_MAX_DEPTH_EXCEEDED; + } + if (c == '{') { + con_type = SPDK_JSON_VAL_OBJECT_BEGIN; + state = STATE_NAME; + } else { + con_type = SPDK_JSON_VAL_ARRAY_BEGIN; + state = STATE_VALUE; + } + con_value[depth] = cur_value; + containers[depth++] = con_type; + ADD_VALUE(con_type, data, data + 1); + data++; + trailing_comma = false; + break; + + case '}': + case ']': + if (trailing_comma) return SPDK_JSON_PARSE_INVALID; + if (depth == 0) return SPDK_JSON_PARSE_INVALID; + con_type = containers[--depth]; + con_start_value = con_value[depth]; + if (values && con_start_value < num_values) { + values[con_start_value].len = cur_value - con_start_value - 1; + } + if (c == '}') { + if (state != STATE_NAME && state != STATE_VALUE_SEPARATOR) { + return SPDK_JSON_PARSE_INVALID; + } + if (con_type != SPDK_JSON_VAL_OBJECT_BEGIN) { + return SPDK_JSON_PARSE_INVALID; + } + ADD_VALUE(SPDK_JSON_VAL_OBJECT_END, data, data + 1); + } else { + if (state != STATE_VALUE && state != STATE_VALUE_SEPARATOR) { + return SPDK_JSON_PARSE_INVALID; + } + if (con_type != SPDK_JSON_VAL_ARRAY_BEGIN) { + return SPDK_JSON_PARSE_INVALID; + } + ADD_VALUE(SPDK_JSON_VAL_ARRAY_END, data, data + 1); + } + con_type = depth == 0 ? SPDK_JSON_VAL_INVALID : containers[depth - 1]; + data++; + state = depth ? STATE_VALUE_SEPARATOR : STATE_END; + trailing_comma = false; + break; + + case ',': + if (state != STATE_VALUE_SEPARATOR) return SPDK_JSON_PARSE_INVALID; + data++; + assert(con_type == SPDK_JSON_VAL_ARRAY_BEGIN || + con_type == SPDK_JSON_VAL_OBJECT_BEGIN); + state = con_type == SPDK_JSON_VAL_ARRAY_BEGIN ? STATE_VALUE : STATE_NAME; + trailing_comma = true; + break; + + case ':': + if (state != STATE_NAME_SEPARATOR) return SPDK_JSON_PARSE_INVALID; + data++; + state = STATE_VALUE; + break; + + default: + return SPDK_JSON_PARSE_INVALID; + } + + if (state == STATE_END) { + break; + } + } + + if (state == STATE_END) { + /* Skip trailing whitespace */ + while (data < json_end) { + uint8_t c = *data; + + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') { + data++; + } else { + break; + } + } + + /* + * These asserts are just for sanity checking - they are guaranteed by the allowed + * state transitions. + */ + assert(depth == 0); + assert(trailing_comma == false); + assert(data <= json_end); + if (end) { + *end = data; + } + return cur_value; + } + + /* Invalid end state - ran out of data */ + if (end) { + *end = data; + } + return SPDK_JSON_PARSE_INCOMPLETE; +} diff --git a/lib/json/json_util.c b/lib/json/json_util.c new file mode 100644 index 000000000..6df9492e8 --- /dev/null +++ b/lib/json/json_util.c @@ -0,0 +1,284 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "json_internal.h" + +size_t +spdk_json_val_len(const struct spdk_json_val *val) +{ + if (val == NULL) { + return 0; + } + + if (val->type == SPDK_JSON_VAL_ARRAY_BEGIN || val->type == SPDK_JSON_VAL_OBJECT_BEGIN) { + return val->len + 2; + } + + return 1; +} + +bool +spdk_json_strequal(const struct spdk_json_val *val, const char *str) +{ + size_t len; + + if (val->type != SPDK_JSON_VAL_STRING && val->type != SPDK_JSON_VAL_NAME) { + return false; + } + + len = strlen(str); + if (val->len != len) { + return false; + } + + return memcmp(val->start, str, len) == 0; +} + +char * +spdk_json_strdup(const struct spdk_json_val *val) +{ + size_t len; + char *s; + + if (val->type != SPDK_JSON_VAL_STRING && val->type != SPDK_JSON_VAL_NAME) { + return NULL; + } + + len = val->len; + + if (memchr(val->start, '\0', len)) { + /* String contains embedded NUL, so it is not a valid C string. */ + return NULL; + } + + s = malloc(len + 1); + if (s == NULL) { + return s; + } + + memcpy(s, val->start, len); + s[len] = '\0'; + + return s; +} + +int +spdk_json_number_to_double(const struct spdk_json_val *val, double *num) +{ + char buf[32]; + char *end; + + if (val->type != SPDK_JSON_VAL_NUMBER || val->len >= sizeof(buf)) { + *num = 0.0; + return -1; + } + + memcpy(buf, val->start, val->len); + buf[val->len] = '\0'; + + errno = 0; + /* TODO: strtod() uses locale for decimal point (. is not guaranteed) */ + *num = strtod(buf, &end); + if (*end != '\0' || errno != 0) { + return -1; + } + + return 0; +} + +int +spdk_json_number_to_int32(const struct spdk_json_val *val, int32_t *num) +{ + double dbl; + + if (spdk_json_number_to_double(val, &dbl)) { + return -1; + } + + *num = (int32_t)dbl; + if (dbl != (double)*num) { + return -1; + } + + return 0; +} + +int +spdk_json_number_to_uint32(const struct spdk_json_val *val, uint32_t *num) +{ + double dbl; + + if (spdk_json_number_to_double(val, &dbl)) { + return -1; + } + + if (dbl < 0) { + return -1; + } + + *num = (uint32_t)dbl; + if (dbl != (double)*num) { + return -1; + } + + return 0; +} + +int +spdk_json_decode_object(const struct spdk_json_val *values, + const struct spdk_json_object_decoder *decoders, size_t num_decoders, void *out) +{ + uint32_t i; + bool invalid = false; + size_t decidx; + bool *seen; + + if (values == NULL || values->type != SPDK_JSON_VAL_OBJECT_BEGIN) { + return -1; + } + + seen = calloc(sizeof(bool), num_decoders); + if (seen == NULL) { + return -1; + } + + for (i = 0; i < values->len;) { + const struct spdk_json_val *name = &values[i + 1]; + const struct spdk_json_val *v = &values[i + 2]; + bool found = false; + + for (decidx = 0; decidx < num_decoders; decidx++) { + const struct spdk_json_object_decoder *dec = &decoders[decidx]; + if (spdk_json_strequal(name, dec->name)) { + void *field = (void *)((uintptr_t)out + dec->offset); + + found = true; + + if (seen[decidx]) { + /* duplicate field name */ + invalid = true; + } else { + seen[decidx] = true; + if (dec->decode_func(v, field)) { + invalid = true; + /* keep going to fill out any other valid keys */ + } + } + break; + } + } + + if (!found) { + invalid = true; + } + + i += 1 + spdk_json_val_len(v); + } + + for (decidx = 0; decidx < num_decoders; decidx++) { + if (!decoders[decidx].optional && !seen[decidx]) { + /* required field is missing */ + invalid = true; + break; + } + } + + free(seen); + return invalid ? -1 : 0; +} + +int +spdk_json_decode_array(const struct spdk_json_val *values, spdk_json_decode_fn decode_func, + void *out, size_t max_size, size_t *out_size, size_t stride) +{ + uint32_t i; + char *field; + + if (values == NULL || values->type != SPDK_JSON_VAL_ARRAY_BEGIN) { + return -1; + } + + if (values->len >= max_size) { + return -1; + } + + *out_size = 0; + field = out; + for (i = 0; i < values->len;) { + const struct spdk_json_val *v = &values[i + 1]; + + if (decode_func(v, field)) { + return -1; + } + + i += spdk_json_val_len(v); + field += stride; + (*out_size)++; + } + + return 0; +} + +int +spdk_json_decode_int32(const struct spdk_json_val *val, void *out) +{ + int32_t *i = out; + + return spdk_json_number_to_int32(val, i); +} + +int +spdk_json_decode_uint32(const struct spdk_json_val *val, void *out) +{ + uint32_t *i = out; + + return spdk_json_number_to_uint32(val, i); +} + +int +spdk_json_decode_string(const struct spdk_json_val *val, void *out) +{ + char **s = out; + + if (*s) { + free(*s); + } + + *s = spdk_json_strdup(val); + + if (*s) { + return 0; + } else { + return -1; + } +} diff --git a/lib/json/json_write.c b/lib/json/json_write.c new file mode 100644 index 000000000..fb55c638f --- /dev/null +++ b/lib/json/json_write.c @@ -0,0 +1,365 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "json_internal.h" + +struct spdk_json_write_ctx { + spdk_json_write_cb write_cb; + void *cb_ctx; + bool first_value; + bool failed; +}; + +struct spdk_json_write_ctx * +spdk_json_write_begin(spdk_json_write_cb write_cb, void *cb_ctx, uint32_t flags) +{ + struct spdk_json_write_ctx *w; + + w = calloc(1, sizeof(*w)); + if (w == NULL) { + return w; + } + + w->write_cb = write_cb; + w->cb_ctx = cb_ctx; + w->first_value = true; + w->failed = false; + + return w; +} + +int +spdk_json_write_end(struct spdk_json_write_ctx *w) +{ + bool failed; + + if (w == NULL) { + return 0; + } + + failed = w->failed; + + free(w); + + return failed ? -1 : 0; +} + +static int +fail(struct spdk_json_write_ctx *w) +{ + w->failed = true; + return -1; +} + +static int +emit(struct spdk_json_write_ctx *w, const void *data, size_t size) +{ + int rc; + + rc = w->write_cb(w->cb_ctx, data, size); + if (rc != 0) { + return fail(w); + } + return 0; +} + +static int +begin_value(struct spdk_json_write_ctx *w) +{ + // TODO: check for value state + if (!w->first_value) { + if (emit(w, ",", 1)) return fail(w); + } + w->first_value = false; + return 0; +} + +int +spdk_json_write_val_raw(struct spdk_json_write_ctx *w, const void *data, size_t len) +{ + if (begin_value(w)) return fail(w); + return emit(w, data, len); +} + +int +spdk_json_write_null(struct spdk_json_write_ctx *w) +{ + if (begin_value(w)) return fail(w); + return emit(w, "null", 4); +} + +int +spdk_json_write_bool(struct spdk_json_write_ctx *w, bool val) +{ + if (begin_value(w)) return fail(w); + if (val) { + return emit(w, "true", 4); + } else { + return emit(w, "false", 5); + } +} + +int +spdk_json_write_int32(struct spdk_json_write_ctx *w, int32_t val) +{ + char buf[32]; + int count; + + if (begin_value(w)) return fail(w); + count = snprintf(buf, sizeof(buf), "%" PRId32, val); + if (count <= 0 || (size_t)count >= sizeof(buf)) return fail(w); + return emit(w, buf, count); +} + +int +spdk_json_write_uint32(struct spdk_json_write_ctx *w, uint32_t val) +{ + char buf[32]; + int count; + + if (begin_value(w)) return fail(w); + count = snprintf(buf, sizeof(buf), "%" PRIu32, val); + if (count <= 0 || (size_t)count >= sizeof(buf)) return fail(w); + return emit(w, buf, count); +} + +static void +write_hex_4(void *dest, uint16_t val) +{ + uint8_t *p = dest; + char hex[] = "0123456789ABCDEF"; + + p[0] = hex[(val >> 12)]; + p[1] = hex[(val >> 8) & 0xF]; + p[2] = hex[(val >> 4) & 0xF]; + p[3] = hex[val & 0xF]; +} + +static int +write_string_or_name(struct spdk_json_write_ctx *w, const char *val, size_t len) +{ + const uint8_t *p = val; + const uint8_t *end = val + len; + static const uint8_t escapes[] = { + ['\b'] = 'b', + ['\f'] = 'f', + ['\n'] = 'n', + ['\r'] = 'r', + ['\t'] = 't', + ['"'] = '"', + ['\\'] = '\\', + /* + * Forward slash (/) is intentionally not converted to an escape + * (it is valid unescaped). + */ + }; + + if (emit(w, "\"", 1)) return fail(w); + + while (p != end) { + int codepoint_len; + uint32_t codepoint; + uint16_t high, low; + char out[13]; + size_t out_len; + + codepoint_len = utf8_valid(p, end); + switch (codepoint_len) { + case 1: + codepoint = utf8_decode_unsafe_1(p); + break; + case 2: + codepoint = utf8_decode_unsafe_2(p); + break; + case 3: + codepoint = utf8_decode_unsafe_3(p); + break; + case 4: + codepoint = utf8_decode_unsafe_4(p); + break; + default: + return fail(w); + } + + if (codepoint < sizeof(escapes) && escapes[codepoint]) { + out[0] = '\\'; + out[1] = escapes[codepoint]; + out_len = 2; + } else if (codepoint >= 0x20 && codepoint < 0x7F) { + /* + * Encode plain ASCII directly (except 0x7F, since it is really + * a control character, despite the JSON spec not considering it one). + */ + out[0] = (uint8_t)codepoint; + out_len = 1; + } else if (codepoint < 0x10000) { + out[0] = '\\'; + out[1] = 'u'; + write_hex_4(&out[2], (uint16_t)codepoint); + out_len = 6; + } else { + utf16_encode_surrogate_pair(codepoint, &high, &low); + out[0] = '\\'; + out[1] = 'u'; + write_hex_4(&out[2], high); + out[6] = '\\'; + out[7] = 'u'; + write_hex_4(&out[8], low); + out_len = 12; + } + + if (emit(w, out, out_len)) return fail(w); + p += codepoint_len; + } + + return emit(w, "\"", 1); +} + +int +spdk_json_write_string_raw(struct spdk_json_write_ctx *w, const char *val, size_t len) +{ + if (begin_value(w)) return fail(w); + return write_string_or_name(w, val, len); +} + +int +spdk_json_write_string(struct spdk_json_write_ctx *w, const char *val) +{ + return spdk_json_write_string_raw(w, val, strlen(val)); +} + +int +spdk_json_write_array_begin(struct spdk_json_write_ctx *w) +{ + if (begin_value(w)) return fail(w); + w->first_value = true; + return emit(w, "[", 1); +} + +int +spdk_json_write_array_end(struct spdk_json_write_ctx *w) +{ + w->first_value = false; + return emit(w, "]", 1); +} + +int +spdk_json_write_object_begin(struct spdk_json_write_ctx *w) +{ + if (begin_value(w)) return fail(w); + w->first_value = true; + return emit(w, "{", 1); +} + +int +spdk_json_write_object_end(struct spdk_json_write_ctx *w) +{ + w->first_value = false; + return emit(w, "}", 1); +} + +int +spdk_json_write_name_raw(struct spdk_json_write_ctx *w, const char *name, size_t len) +{ + /* TODO: check that container is an object */ + if (begin_value(w)) return fail(w); + if (write_string_or_name(w, name, len)) return fail(w); + w->first_value = true; + return emit(w, ":", 1); +} + +int +spdk_json_write_name(struct spdk_json_write_ctx *w, const char *name) +{ + return spdk_json_write_name_raw(w, name, strlen(name)); +} + +int +spdk_json_write_val(struct spdk_json_write_ctx *w, const struct spdk_json_val *val) +{ + size_t num_values, i; + + switch (val->type) { + case SPDK_JSON_VAL_NUMBER: + return spdk_json_write_val_raw(w, val->start, val->len); + + case SPDK_JSON_VAL_STRING: + return spdk_json_write_string_raw(w, val->start, val->len); + + case SPDK_JSON_VAL_NAME: + return spdk_json_write_name_raw(w, val->start, val->len); + + case SPDK_JSON_VAL_TRUE: + return spdk_json_write_bool(w, true); + + case SPDK_JSON_VAL_FALSE: + return spdk_json_write_bool(w, false); + + case SPDK_JSON_VAL_NULL: + return spdk_json_write_null(w); + + case SPDK_JSON_VAL_ARRAY_BEGIN: + case SPDK_JSON_VAL_OBJECT_BEGIN: + num_values = val[0].len; + + if (val[0].type == SPDK_JSON_VAL_OBJECT_BEGIN) { + if (spdk_json_write_object_begin(w)) { + return fail(w); + } + } else { + if (spdk_json_write_array_begin(w)) { + return fail(w); + } + } + + // Loop up to and including the _END value + for (i = 0; i < num_values + 1; i++) { + if (spdk_json_write_val(w, &val[i + 1])) { + return fail(w); + } + } + break; + + case SPDK_JSON_VAL_ARRAY_END: + return spdk_json_write_array_end(w); + + case SPDK_JSON_VAL_OBJECT_END: + return spdk_json_write_object_end(w); + + case SPDK_JSON_VAL_INVALID: + // Handle INVALID to make the compiler happy (and catch other unhandled types) + return fail(w); + } + + return fail(w); +} diff --git a/mk/json.unittest.mk b/mk/json.unittest.mk new file mode 100644 index 000000000..cbbf6d73f --- /dev/null +++ b/mk/json.unittest.mk @@ -0,0 +1,60 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +SPDK_ROOT_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/.. +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +JSON_DIR := $(SPDK_ROOT_DIR)/lib/json + +C_SRCS = $(TEST_FILE) $(OTHER_FILES) + +CFLAGS += -I$(JSON_DIR) +CFLAGS += -I$(SPDK_ROOT_DIR)/lib +CFLAGS += -I$(SPDK_ROOT_DIR)/test + +LIBS += -lcunit -lpthread + +APP = $(TEST_FILE:.c=) + +all: $(APP) + +$(APP) : $(OBJS) + $(LINK_C) + +clean: + $(CLEAN_C) $(APP) + +%.o: $(JSON_DIR)/%.c %.d $(MAKEFILE_LIST) + $(COMPILE_C) + +include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk diff --git a/test/lib/Makefile b/test/lib/Makefile index 4350f3eb4..966b16fda 100644 --- a/test/lib/Makefile +++ b/test/lib/Makefile @@ -34,7 +34,7 @@ SPDK_ROOT_DIR := $(CURDIR)/../.. include $(SPDK_ROOT_DIR)/mk/spdk.common.mk -DIRS-y = nvme memory ioat +DIRS-y = json nvme memory ioat .PHONY: all clean $(DIRS-y) diff --git a/test/lib/json/Makefile b/test/lib/json/Makefile new file mode 100644 index 000000000..b074914ea --- /dev/null +++ b/test/lib/json/Makefile @@ -0,0 +1,44 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +SPDK_ROOT_DIR := $(CURDIR)/../../.. +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y = parse util write + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/test/lib/json/json.sh b/test/lib/json/json.sh new file mode 100755 index 000000000..25b95a954 --- /dev/null +++ b/test/lib/json/json.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -xe + +testdir=$(readlink -f $(dirname $0)) +rootdir=$testdir/../../.. +source $rootdir/scripts/autotest_common.sh + +timing_enter json + +$testdir/parse/json_parse_ut +$testdir/util/json_util_ut +$testdir/write/json_write_ut + +timing_exit json diff --git a/test/lib/json/parse/.gitignore b/test/lib/json/parse/.gitignore new file mode 100644 index 000000000..2b4445fd8 --- /dev/null +++ b/test/lib/json/parse/.gitignore @@ -0,0 +1 @@ +json_parse_ut diff --git a/test/lib/json/parse/Makefile b/test/lib/json/parse/Makefile new file mode 100644 index 000000000..14c36ac61 --- /dev/null +++ b/test/lib/json/parse/Makefile @@ -0,0 +1,38 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +SPDK_ROOT_DIR := $(CURDIR)/../../../.. + +TEST_FILE = json_parse_ut.c + +include $(SPDK_ROOT_DIR)/mk/json.unittest.mk diff --git a/test/lib/json/parse/json_parse_ut.c b/test/lib/json/parse/json_parse_ut.c new file mode 100644 index 000000000..9416e4585 --- /dev/null +++ b/test/lib/json/parse/json_parse_ut.c @@ -0,0 +1,838 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk_cunit.h" + +#include "json_parse.c" + +#include +#include + +static uint8_t g_buf[1000]; +static void *g_end; +static struct spdk_json_val g_vals[100]; +static int g_cur_val; + +/* Fill buf with raw data */ +#define BUF_SETUP(in) \ + memset(g_buf, 0, sizeof(g_buf)); \ + if (sizeof(in) > 1) { \ + memcpy(g_buf, in, sizeof(in) - 1); \ + } \ + g_end = NULL + +/* + * Do two checks - first pass NULL for values to ensure the count is correct, + * then pass g_vals to get the actual values. + */ +#define PARSE_PASS(in, num_vals, trailing) \ + BUF_SETUP(in); \ + CU_ASSERT(spdk_json_parse(g_buf, sizeof(in) - 1, NULL, 0, &g_end, 0) == num_vals); \ + memset(g_vals, 0, sizeof(g_vals)); \ + CU_ASSERT(spdk_json_parse(g_buf, sizeof(in) - 1, g_vals, sizeof(g_vals), &g_end, SPDK_JSON_PARSE_FLAG_DECODE_IN_PLACE) == num_vals); \ + CU_ASSERT(g_end == g_buf + sizeof(in) - sizeof(trailing)); \ + CU_ASSERT(memcmp(g_end, trailing, sizeof(trailing) - 1) == 0); \ + g_cur_val = 0 + +#define PARSE_FAIL(in, retval) \ + BUF_SETUP(in); \ + CU_ASSERT(spdk_json_parse(g_buf, sizeof(in) - 1, NULL, 0, &g_end, 0) == retval) + +#define VAL_STRING_MATCH(str, var_type) \ + CU_ASSERT(g_vals[g_cur_val].type == var_type); \ + CU_ASSERT(g_vals[g_cur_val].len == sizeof(str) - 1); \ + if (g_vals[g_cur_val].len == sizeof(str) - 1 && sizeof(str) > 1) { \ + CU_ASSERT(memcmp(g_vals[g_cur_val].start, str, g_vals[g_cur_val].len) == 0); \ + } \ + g_cur_val++ + +#define VAL_STRING(str) VAL_STRING_MATCH(str, SPDK_JSON_VAL_STRING) +#define VAL_NAME(str) VAL_STRING_MATCH(str, SPDK_JSON_VAL_NAME) +#define VAL_NUMBER(num) VAL_STRING_MATCH(num, SPDK_JSON_VAL_NUMBER) + +#define VAL_LITERAL(str, val_type) \ + CU_ASSERT(g_vals[g_cur_val].type == val_type); \ + CU_ASSERT(g_vals[g_cur_val].len == strlen(str)); \ + if (g_vals[g_cur_val].len == strlen(str)) { \ + CU_ASSERT(memcmp(g_vals[g_cur_val].start, str, g_vals[g_cur_val].len) == 0); \ + } \ + g_cur_val++ + +#define VAL_TRUE() VAL_LITERAL("true", SPDK_JSON_VAL_TRUE) +#define VAL_FALSE() VAL_LITERAL("false", SPDK_JSON_VAL_FALSE) +#define VAL_NULL() VAL_LITERAL("null", SPDK_JSON_VAL_NULL) + +#define VAL_ARRAY_BEGIN(count) \ + CU_ASSERT(g_vals[g_cur_val].type == SPDK_JSON_VAL_ARRAY_BEGIN); \ + CU_ASSERT(g_vals[g_cur_val].len == count); \ + g_cur_val++ + +#define VAL_ARRAY_END() \ + CU_ASSERT(g_vals[g_cur_val].type == SPDK_JSON_VAL_ARRAY_END); \ + g_cur_val++ + +#define VAL_OBJECT_BEGIN(count) \ + CU_ASSERT(g_vals[g_cur_val].type == SPDK_JSON_VAL_OBJECT_BEGIN); \ + CU_ASSERT(g_vals[g_cur_val].len == count); \ + g_cur_val++ + +#define VAL_OBJECT_END() \ + CU_ASSERT(g_vals[g_cur_val].type == SPDK_JSON_VAL_OBJECT_END); \ + g_cur_val++ + +/* Simplified macros for string-only testing */ +#define STR_PASS(in, out) \ + PARSE_PASS("\"" in "\"", 1, ""); \ + VAL_STRING(out) + +#define STR_FAIL(in, retval) \ + PARSE_FAIL("\"" in "\"", retval) + +/* Simplified macros for number-only testing (no whitespace allowed) */ +#define NUM_PASS(in) \ + PARSE_PASS(in, 1, ""); \ + VAL_NUMBER(in) + +#define NUM_FAIL(in, retval) \ + PARSE_FAIL(in, retval) + +static void +test_parse_literal(void) +{ + PARSE_PASS("true", 1, ""); + VAL_TRUE(); + + PARSE_PASS(" true ", 1, ""); + VAL_TRUE(); + + PARSE_PASS("false", 1, ""); + VAL_FALSE(); + + PARSE_PASS("null", 1, ""); + VAL_NULL(); + + PARSE_PASS("trueaaa", 1, "aaa"); + VAL_TRUE(); + + PARSE_PASS("truefalse", 1, "false"); + VAL_TRUE(); + + PARSE_PASS("true false", 1, "false"); + VAL_TRUE(); + + PARSE_PASS("true,false", 1, ",false"); + VAL_TRUE(); + + PARSE_PASS("true,", 1, ","); + VAL_TRUE(); + + PARSE_FAIL("True", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("abcdef", SPDK_JSON_PARSE_INVALID); + + PARSE_FAIL("t", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("tru", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("f", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("fals", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("n", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("nul", SPDK_JSON_PARSE_INCOMPLETE); +} + +static void +test_parse_string_simple(void) +{ + PARSE_PASS("\"\"", 1, ""); + VAL_STRING(""); + + PARSE_PASS("\"hello world\"", 1, ""); + VAL_STRING("hello world"); + + PARSE_PASS(" \"hello world\" ", 1, ""); + VAL_STRING("hello world"); + + /* Unterminated string */ + PARSE_FAIL("\"hello world", SPDK_JSON_PARSE_INCOMPLETE); + + /* Trailing comma */ + PARSE_PASS("\"hello world\",", 1, ","); + VAL_STRING("hello world"); +} + +static void +test_parse_string_control_chars(void) +{ + /* U+0000 through U+001F must be escaped */ + STR_FAIL("\x00", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x01", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x02", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x03", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x04", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x05", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x06", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x07", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x08", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x09", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x0A", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x0B", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x0C", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x0D", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x0E", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x0F", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x10", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x11", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x12", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x13", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x14", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x15", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x16", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x17", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x18", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x19", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x1A", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x1B", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x1C", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x1D", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x1E", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\x1F", SPDK_JSON_PARSE_INVALID); + STR_PASS(" ", " "); /* \x20 (first valid unescaped char) */ + + /* Test control chars in the middle of a string */ + STR_FAIL("abc\ndef", SPDK_JSON_PARSE_INVALID); + STR_FAIL("abc\tdef", SPDK_JSON_PARSE_INVALID); +} + +static void +test_parse_string_utf8(void) +{ + /* Valid one-, two-, three-, and four-byte sequences */ + STR_PASS("\x41", "A"); + STR_PASS("\xC3\xB6", "\xC3\xB6"); + STR_PASS("\xE2\x88\x9A", "\xE2\x88\x9A"); + STR_PASS("\xF0\xA0\x9C\x8E", "\xF0\xA0\x9C\x8E"); + + /* Examples from RFC 3629 */ + STR_PASS("\x41\xE2\x89\xA2\xCE\x91\x2E", "\x41\xE2\x89\xA2\xCE\x91\x2E"); + STR_PASS("\xED\x95\x9C\xEA\xB5\xAD\xEC\x96\xB4", "\xED\x95\x9C\xEA\xB5\xAD\xEC\x96\xB4"); + STR_PASS("\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E", "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E"); + STR_PASS("\xEF\xBB\xBF\xF0\xA3\x8E\xB4", "\xEF\xBB\xBF\xF0\xA3\x8E\xB4"); + + /* Edge cases */ + STR_PASS("\x7F", "\x7F"); + STR_FAIL("\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xC1", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xC2", SPDK_JSON_PARSE_INVALID); + STR_PASS("\xC2\x80", "\xC2\x80"); + STR_PASS("\xC2\xBF", "\xC2\xBF"); + STR_PASS("\xDF\x80", "\xDF\x80"); + STR_PASS("\xDF\xBF", "\xDF\xBF"); + STR_FAIL("\xDF", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xE0\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xE0\x1F", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xE0\x1F\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xE0", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xE0\xA0", SPDK_JSON_PARSE_INVALID); + STR_PASS("\xE0\xA0\x80", "\xE0\xA0\x80"); + STR_PASS("\xE0\xA0\xBF", "\xE0\xA0\xBF"); + STR_FAIL("\xE0\xA0\xC0", SPDK_JSON_PARSE_INVALID); + STR_PASS("\xE0\xBF\x80", "\xE0\xBF\x80"); + STR_PASS("\xE0\xBF\xBF", "\xE0\xBF\xBF"); + STR_FAIL("\xE0\xC0\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xE1", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xE1\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xE1\x7F\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xE1\x80\x7F", SPDK_JSON_PARSE_INVALID); + STR_PASS("\xE1\x80\x80", "\xE1\x80\x80"); + STR_PASS("\xE1\x80\xBF", "\xE1\x80\xBF"); + STR_PASS("\xE1\xBF\x80", "\xE1\xBF\x80"); + STR_PASS("\xE1\xBF\xBF", "\xE1\xBF\xBF"); + STR_FAIL("\xE1\xC0\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xE1\x80\xC0", SPDK_JSON_PARSE_INVALID); + STR_PASS("\xEF\x80\x80", "\xEF\x80\x80"); + STR_PASS("\xEF\xBF\xBF", "\xEF\xBF\xBF"); + STR_FAIL("\xF0", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF0\x90", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF0\x90\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF0\x80\x80\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF0\x8F\x80\x80", SPDK_JSON_PARSE_INVALID); + STR_PASS("\xF0\x90\x80\x80", "\xF0\x90\x80\x80"); + STR_PASS("\xF0\x90\x80\xBF", "\xF0\x90\x80\xBF"); + STR_PASS("\xF0\x90\xBF\x80", "\xF0\x90\xBF\x80"); + STR_PASS("\xF0\xBF\x80\x80", "\xF0\xBF\x80\x80"); + STR_FAIL("\xF0\xC0\x80\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF1", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF1\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF1\x80\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF1\x80\x80\x7F", SPDK_JSON_PARSE_INVALID); + STR_PASS("\xF1\x80\x80\x80", "\xF1\x80\x80\x80"); + STR_PASS("\xF1\x80\x80\xBF", "\xF1\x80\x80\xBF"); + STR_PASS("\xF1\x80\xBF\x80", "\xF1\x80\xBF\x80"); + STR_PASS("\xF1\xBF\x80\x80", "\xF1\xBF\x80\x80"); + STR_PASS("\xF3\x80\x80\x80", "\xF3\x80\x80\x80"); + STR_FAIL("\xF3\xC0\x80\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF3\x80\xC0\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF3\x80\x80\xC0", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF4", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF4\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF4\x80\x80", SPDK_JSON_PARSE_INVALID); + STR_PASS("\xF4\x80\x80\x80", "\xF4\x80\x80\x80"); + STR_PASS("\xF4\x8F\x80\x80", "\xF4\x8F\x80\x80"); + STR_PASS("\xF4\x8F\xBF\xBF", "\xF4\x8F\xBF\xBF"); + STR_FAIL("\xF4\x90\x80\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF5", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF5\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF5\x80\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF5\x80\x80\x80", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\xF5\x80\x80\x80\x80", SPDK_JSON_PARSE_INVALID); + + /* Overlong encodings */ + STR_FAIL("\xC0\x80", SPDK_JSON_PARSE_INVALID); + + /* Surrogate pairs */ + STR_FAIL("\xED\xA0\x80", SPDK_JSON_PARSE_INVALID); /* U+D800 First high surrogate */ + STR_FAIL("\xED\xAF\xBF", SPDK_JSON_PARSE_INVALID); /* U+DBFF Last high surrogate */ + STR_FAIL("\xED\xB0\x80", SPDK_JSON_PARSE_INVALID); /* U+DC00 First low surrogate */ + STR_FAIL("\xED\xBF\xBF", SPDK_JSON_PARSE_INVALID); /* U+DFFF Last low surrogate */ + STR_FAIL("\xED\xA1\x8C\xED\xBE\xB4", + SPDK_JSON_PARSE_INVALID); /* U+233B4 (invalid surrogate pair encoding) */ +} + +static void +test_parse_string_escapes_twochar(void) +{ + STR_PASS("\\\"", "\""); + STR_PASS("\\\\", "\\"); + STR_PASS("\\/", "/"); + STR_PASS("\\b", "\b"); + STR_PASS("\\f", "\f"); + STR_PASS("\\n", "\n"); + STR_PASS("\\r", "\r"); + STR_PASS("\\t", "\t"); + + STR_PASS("abc\\tdef", "abc\tdef"); + STR_PASS("abc\\\"def", "abc\"def"); + + /* Backslash at end of string (will be treated as escaped quote) */ + STR_FAIL("\\", SPDK_JSON_PARSE_INCOMPLETE); + STR_FAIL("abc\\", SPDK_JSON_PARSE_INCOMPLETE); + + /* Invalid C-like escapes */ + STR_FAIL("\\a", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\v", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\'", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\?", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\0", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\x00", SPDK_JSON_PARSE_INVALID); + + /* Other invalid escapes */ + STR_FAIL("\\B", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\z", SPDK_JSON_PARSE_INVALID); +} + +static void +test_parse_string_escapes_unicode(void) +{ + STR_PASS("\\u0000", "\0"); + STR_PASS("\\u0001", "\1"); + STR_PASS("\\u0041", "A"); + STR_PASS("\\uAAAA", "\xEA\xAA\xAA"); + STR_PASS("\\uaaaa", "\xEA\xAA\xAA"); + STR_PASS("\\uAaAa", "\xEA\xAA\xAA"); + + STR_FAIL("\\u", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\u0", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\u00", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\u000", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\u000g", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\U", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\U0000", SPDK_JSON_PARSE_INVALID); + + PARSE_FAIL("\"\\u", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("\"\\u0", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("\"\\u00", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("\"\\u000", SPDK_JSON_PARSE_INCOMPLETE); + + /* Surrogate pair */ + STR_PASS("\\uD834\\uDD1E", "\xF0\x9D\x84\x9E"); + + /* Low surrogate without high */ + STR_FAIL("\\uDC00", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\uDC00\\uDC00", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\uDC00abcdef", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\uDEAD", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("\"\\uD834", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("\"\\uD834\\", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("\"\\uD834\\u", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("\"\\uD834\\uD", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("\"\\uD834\\uDD1", SPDK_JSON_PARSE_INCOMPLETE); + + /* High surrogate without low */ + STR_FAIL("\\uD800", SPDK_JSON_PARSE_INVALID); + STR_FAIL("\\uD800abcdef", SPDK_JSON_PARSE_INVALID); +} + +static void +test_parse_number(void) +{ + NUM_PASS("0"); + NUM_PASS("1"); + NUM_PASS("100"); + NUM_PASS("-1"); + NUM_PASS("-0"); + NUM_PASS("3.0"); + NUM_PASS("3.00"); + NUM_PASS("3.001"); + NUM_PASS("3.14159"); + NUM_PASS("3.141592653589793238462643383279"); + NUM_PASS("1e400"); + NUM_PASS("1E400"); + NUM_PASS("0e10"); + NUM_PASS("0e0"); + NUM_PASS("-0e0"); + NUM_PASS("-0e+0"); + NUM_PASS("-0e-0"); + NUM_PASS("1e+400"); + NUM_PASS("1e-400"); + NUM_PASS("6.022e23"); + NUM_PASS("-1.234e+56"); + NUM_PASS("1.23e+56"); + NUM_PASS("-1.23e-56"); + NUM_PASS("1.23e-56"); + NUM_PASS("1e04"); + + /* Trailing garbage */ + PARSE_PASS("0A", 1, "A"); + VAL_NUMBER("0"); + + PARSE_PASS("0,", 1, ","); + VAL_NUMBER("0"); + + PARSE_PASS("0true", 1, "true"); + VAL_NUMBER("0"); + + NUM_FAIL("00", SPDK_JSON_PARSE_INVALID); + NUM_FAIL("007", SPDK_JSON_PARSE_INVALID); + NUM_FAIL("345.", SPDK_JSON_PARSE_INCOMPLETE); + NUM_FAIL("345.678.1", SPDK_JSON_PARSE_INVALID); + NUM_FAIL("+1", SPDK_JSON_PARSE_INVALID); + NUM_FAIL("--1", SPDK_JSON_PARSE_INVALID); + NUM_FAIL("3.", SPDK_JSON_PARSE_INCOMPLETE); + NUM_FAIL("3.+4", SPDK_JSON_PARSE_INVALID); + NUM_FAIL("3.2e+-4", SPDK_JSON_PARSE_INVALID); + NUM_FAIL("3.2e-+4", SPDK_JSON_PARSE_INVALID); + NUM_FAIL("3.2e-4+5", SPDK_JSON_PARSE_INVALID); + NUM_FAIL("3.4.5", SPDK_JSON_PARSE_INVALID); + NUM_FAIL("3e+", SPDK_JSON_PARSE_INCOMPLETE); + NUM_FAIL("3e-", SPDK_JSON_PARSE_INCOMPLETE); + NUM_FAIL("3.e4", SPDK_JSON_PARSE_INVALID); + NUM_FAIL("-", SPDK_JSON_PARSE_INCOMPLETE); + NUM_FAIL("NaN", SPDK_JSON_PARSE_INVALID); + NUM_FAIL(".123", SPDK_JSON_PARSE_INVALID); +} + +static void +test_parse_array(void) +{ + PARSE_PASS("[]", 2, ""); + VAL_ARRAY_BEGIN(0); + VAL_ARRAY_END(); + + PARSE_PASS("[true]", 3, ""); + VAL_ARRAY_BEGIN(1); + VAL_TRUE(); + VAL_ARRAY_END(); + + PARSE_PASS("[true, false]", 4, ""); + VAL_ARRAY_BEGIN(2); + VAL_TRUE(); + VAL_FALSE(); + VAL_ARRAY_END(); + + PARSE_PASS("[\"hello\"]", 3, ""); + VAL_ARRAY_BEGIN(1); + VAL_STRING("hello"); + VAL_ARRAY_END(); + + PARSE_PASS("[[]]", 4, ""); + VAL_ARRAY_BEGIN(2); + VAL_ARRAY_BEGIN(0); + VAL_ARRAY_END(); + VAL_ARRAY_END(); + + PARSE_PASS("[\"hello\", \"world\"]", 4, ""); + VAL_ARRAY_BEGIN(2); + VAL_STRING("hello"); + VAL_STRING("world"); + VAL_ARRAY_END(); + + PARSE_PASS("[],", 2, ","); + VAL_ARRAY_BEGIN(0); + VAL_ARRAY_END(); + + PARSE_FAIL("]", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("[", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("[true", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("[\"hello", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("[\"hello\"", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("[true,]", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("[,]", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("[,true]", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("[true}", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("[true,,true]", SPDK_JSON_PARSE_INVALID); +} + +static void +test_parse_object(void) +{ + PARSE_PASS("{}", 2, ""); + VAL_OBJECT_BEGIN(0); + VAL_OBJECT_END(); + + PARSE_PASS("{\"a\": true}", 4, ""); + VAL_OBJECT_BEGIN(2); + VAL_NAME("a"); + VAL_TRUE(); + VAL_OBJECT_END(); + + PARSE_PASS("{\"abc\": \"def\"}", 4, ""); + VAL_OBJECT_BEGIN(2); + VAL_NAME("abc"); + VAL_STRING("def"); + VAL_OBJECT_END(); + + PARSE_PASS("{\"a\": true, \"b\": false}", 6, ""); + VAL_OBJECT_BEGIN(4); + VAL_NAME("a"); + VAL_TRUE(); + VAL_NAME("b"); + VAL_FALSE(); + VAL_OBJECT_END(); + + PARSE_PASS("{\"a\": { \"b\": true } }", 7, ""); + VAL_OBJECT_BEGIN(5); + VAL_NAME("a"); + VAL_OBJECT_BEGIN(2); + VAL_NAME("b"); + VAL_TRUE(); + VAL_OBJECT_END(); + VAL_OBJECT_END(); + + PARSE_PASS("{\"{test\": 0}", 4, ""); + VAL_OBJECT_BEGIN(2); + VAL_NAME("{test"); + VAL_NUMBER("0"); + VAL_OBJECT_END(); + + PARSE_PASS("{\"test}\": 1}", 4, ""); + VAL_OBJECT_BEGIN(2); + VAL_NAME("test}"); + VAL_NUMBER("1"); + VAL_OBJECT_END(); + + PARSE_PASS("{\"\\\"\": 2}", 4, ""); + VAL_OBJECT_BEGIN(2); + VAL_NAME("\""); + VAL_NUMBER("2"); + VAL_OBJECT_END(); + + PARSE_PASS("{\"a\":true},", 4, ","); + VAL_OBJECT_BEGIN(2); + VAL_NAME("a"); + VAL_TRUE(); + VAL_OBJECT_END(); + + /* Object end without object begin (trailing garbage) */ + PARSE_PASS("true}", 1, "}"); + VAL_TRUE(); + + PARSE_PASS("0}", 1, "}"); + VAL_NUMBER("0"); + + PARSE_PASS("\"a\"}", 1, "}"); + VAL_STRING("a"); + + PARSE_FAIL("}", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("{", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("{\"a", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("{\"a\"", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("{\"a\":", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("{\"a\":true", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("{\"a\":true,", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("{\"a\":true,}", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("{\"a\":true,\"}", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("{\"a\":true,\"b}", SPDK_JSON_PARSE_INCOMPLETE); + PARSE_FAIL("{\"a\":true,\"b\"}", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("{\"a\":true,\"b\":}", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("{\"a\":true,\"b\",}", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("{\"a\",}", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("{,\"a\": true}", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("{a:true}", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("{'a':true}", SPDK_JSON_PARSE_INVALID); +} + +static void +test_parse_nesting(void) +{ + PARSE_PASS("[[[[[[[[]]]]]]]]", 16, ""); + + PARSE_PASS("{\"a\": [0, 1, 2]}", 8, ""); + VAL_OBJECT_BEGIN(6); + VAL_NAME("a"); + VAL_ARRAY_BEGIN(3); + VAL_NUMBER("0"); + VAL_NUMBER("1"); + VAL_NUMBER("2"); + VAL_ARRAY_END(); + VAL_OBJECT_END(); + + PARSE_PASS("{\"a\": [0, 1, 2], \"b\": 3 }", 10, ""); + VAL_OBJECT_BEGIN(8); + VAL_NAME("a"); + VAL_ARRAY_BEGIN(3); + VAL_NUMBER("0"); + VAL_NUMBER("1"); + VAL_NUMBER("2"); + VAL_ARRAY_END(); + VAL_NAME("b"); + VAL_NUMBER("3"); + VAL_OBJECT_END(); + + PARSE_PASS("[0, 1, {\"a\": 3}, 4, 5]", 10, ""); + VAL_ARRAY_BEGIN(8); + VAL_NUMBER("0"); + VAL_NUMBER("1"); + VAL_OBJECT_BEGIN(2); + VAL_NAME("a"); + VAL_NUMBER("3"); + VAL_OBJECT_END(); + VAL_NUMBER("4"); + VAL_NUMBER("5"); + VAL_ARRAY_END(); + + PARSE_PASS("\t[ { \"a\": {\"b\": [ {\"c\": 1}, 2 ],\n\"d\": 3}, \"e\" : 4}, 5 ] ", 20, ""); + VAL_ARRAY_BEGIN(18); + VAL_OBJECT_BEGIN(15); + VAL_NAME("a"); + VAL_OBJECT_BEGIN(10); + VAL_NAME("b"); + VAL_ARRAY_BEGIN(5); + VAL_OBJECT_BEGIN(2); + VAL_NAME("c"); + VAL_NUMBER("1"); + VAL_OBJECT_END(); + VAL_NUMBER("2"); + VAL_ARRAY_END(); + VAL_NAME("d"); + VAL_NUMBER("3"); + VAL_OBJECT_END(); + VAL_NAME("e"); + VAL_NUMBER("4"); + VAL_OBJECT_END(); + VAL_NUMBER("5"); + VAL_ARRAY_END(); + + /* Examples from RFC 7159 */ + PARSE_PASS( + "{\n" + " \"Image\": {\n" + " \"Width\": 800,\n" + " \"Height\": 600,\n" + " \"Title\": \"View from 15th Floor\",\n" + " \"Thumbnail\": {\n" + " \"Url\": \"http://www.example.com/image/481989943\",\n" + " \"Height\": 125,\n" + " \"Width\": 100\n" + " },\n" + " \"Animated\" : false,\n" + " \"IDs\": [116, 943, 234, 38793]\n" + " }\n" + "}\n", + 29, ""); + + VAL_OBJECT_BEGIN(27); + VAL_NAME("Image"); + VAL_OBJECT_BEGIN(24); + VAL_NAME("Width"); + VAL_NUMBER("800"); + VAL_NAME("Height"); + VAL_NUMBER("600"); + VAL_NAME("Title"); + VAL_STRING("View from 15th Floor"); + VAL_NAME("Thumbnail"); + VAL_OBJECT_BEGIN(6); + VAL_NAME("Url"); + VAL_STRING("http://www.example.com/image/481989943"); + VAL_NAME("Height"); + VAL_NUMBER("125"); + VAL_NAME("Width"); + VAL_NUMBER("100"); + VAL_OBJECT_END(); + VAL_NAME("Animated"); + VAL_FALSE(); + VAL_NAME("IDs"); + VAL_ARRAY_BEGIN(4); + VAL_NUMBER("116"); + VAL_NUMBER("943"); + VAL_NUMBER("234"); + VAL_NUMBER("38793"); + VAL_ARRAY_END(); + VAL_OBJECT_END(); + VAL_OBJECT_END(); + + PARSE_PASS( + "[\n" + " {\n" + " \"precision\": \"zip\",\n" + " \"Latitude\": 37.7668,\n" + " \"Longitude\": -122.3959,\n" + " \"Address\": \"\",\n" + " \"City\": \"SAN FRANCISCO\",\n" + " \"State\": \"CA\",\n" + " \"Zip\": \"94107\",\n" + " \"Country\": \"US\"\n" + " },\n" + " {\n" + " \"precision\": \"zip\",\n" + " \"Latitude\": 37.371991,\n" + " \"Longitude\": -122.026020,\n" + " \"Address\": \"\",\n" + " \"City\": \"SUNNYVALE\",\n" + " \"State\": \"CA\",\n" + " \"Zip\": \"94085\",\n" + " \"Country\": \"US\"\n" + " }\n" + "]", + 38, ""); + + VAL_ARRAY_BEGIN(36); + VAL_OBJECT_BEGIN(16); + VAL_NAME("precision"); + VAL_STRING("zip"); + VAL_NAME("Latitude"); + VAL_NUMBER("37.7668"); + VAL_NAME("Longitude"); + VAL_NUMBER("-122.3959"); + VAL_NAME("Address"); + VAL_STRING(""); + VAL_NAME("City"); + VAL_STRING("SAN FRANCISCO"); + VAL_NAME("State"); + VAL_STRING("CA"); + VAL_NAME("Zip"); + VAL_STRING("94107"); + VAL_NAME("Country"); + VAL_STRING("US"); + VAL_OBJECT_END(); + VAL_OBJECT_BEGIN(16); + VAL_NAME("precision"); + VAL_STRING("zip"); + VAL_NAME("Latitude"); + VAL_NUMBER("37.371991"); + VAL_NAME("Longitude"); + VAL_NUMBER("-122.026020"); + VAL_NAME("Address"); + VAL_STRING(""); + VAL_NAME("City"); + VAL_STRING("SUNNYVALE"); + VAL_NAME("State"); + VAL_STRING("CA"); + VAL_NAME("Zip"); + VAL_STRING("94085"); + VAL_NAME("Country"); + VAL_STRING("US"); + VAL_OBJECT_END(); + VAL_ARRAY_END(); + + /* Trailing garbage */ + PARSE_PASS("{\"a\": [0, 1, 2]}]", 8, "]"); + VAL_OBJECT_BEGIN(6); + VAL_NAME("a"); + VAL_ARRAY_BEGIN(3); + VAL_NUMBER("0"); + VAL_NUMBER("1"); + VAL_NUMBER("2"); + VAL_ARRAY_END(); + VAL_OBJECT_END(); + + PARSE_PASS("{\"a\": [0, 1, 2]}}", 8, "}"); + PARSE_PASS("{\"a\": [0, 1, 2]}]", 8, "]"); + VAL_OBJECT_BEGIN(6); + VAL_NAME("a"); + VAL_ARRAY_BEGIN(3); + VAL_NUMBER("0"); + VAL_NUMBER("1"); + VAL_NUMBER("2"); + VAL_ARRAY_END(); + VAL_OBJECT_END(); + + PARSE_FAIL("{\"a\": [0, 1, 2}]", SPDK_JSON_PARSE_INVALID); + PARSE_FAIL("{\"a\": [0, 1, 2]", SPDK_JSON_PARSE_INCOMPLETE); +} + +int main(int argc, char **argv) +{ + CU_pSuite suite = NULL; + unsigned int num_failures; + + if (CU_initialize_registry() != CUE_SUCCESS) { + return CU_get_error(); + } + + suite = CU_add_suite("json", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "parse_literal", test_parse_literal) == NULL || + CU_add_test(suite, "parse_string_simple", test_parse_string_simple) == NULL || + CU_add_test(suite, "parse_string_control_chars", test_parse_string_control_chars) == NULL || + CU_add_test(suite, "parse_string_utf8", test_parse_string_utf8) == NULL || + CU_add_test(suite, "parse_string_escapes_twochar", test_parse_string_escapes_twochar) == NULL || + CU_add_test(suite, "parse_string_escapes_unicode", test_parse_string_escapes_unicode) == NULL || + CU_add_test(suite, "parse_number", test_parse_number) == NULL || + CU_add_test(suite, "parse_array", test_parse_array) == NULL || + CU_add_test(suite, "parse_object", test_parse_object) == NULL || + CU_add_test(suite, "parse_nesting", test_parse_nesting) == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + + CU_basic_run_tests(); + + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + + return num_failures; +} diff --git a/test/lib/json/util/.gitignore b/test/lib/json/util/.gitignore new file mode 100644 index 000000000..02f6d50c5 --- /dev/null +++ b/test/lib/json/util/.gitignore @@ -0,0 +1 @@ +json_util_ut diff --git a/test/lib/json/util/Makefile b/test/lib/json/util/Makefile new file mode 100644 index 000000000..cafd1fbbb --- /dev/null +++ b/test/lib/json/util/Makefile @@ -0,0 +1,38 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +SPDK_ROOT_DIR := $(CURDIR)/../../../.. + +TEST_FILE = json_util_ut.c + +include $(SPDK_ROOT_DIR)/mk/json.unittest.mk diff --git a/test/lib/json/util/json_util_ut.c b/test/lib/json/util/json_util_ut.c new file mode 100644 index 000000000..5d994bf6d --- /dev/null +++ b/test/lib/json/util/json_util_ut.c @@ -0,0 +1,137 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk_cunit.h" + +#include "json_util.c" + +#include +#include + +#define NUM_SETUP(x) \ + snprintf(buf, sizeof(buf), "%s", x); \ + v.type = SPDK_JSON_VAL_NUMBER; \ + v.start = buf; \ + v.len = sizeof(x) - 1 + +#define NUM_INT32_PASS(s, i) \ + NUM_SETUP(s); \ + CU_ASSERT(spdk_json_number_to_int32(&v, &i32) == 0); \ + CU_ASSERT(i32 == i) + +#define NUM_INT32_FAIL(s) \ + NUM_SETUP(s); \ + CU_ASSERT(spdk_json_number_to_int32(&v, &i32) != 0) + +static void +test_strequal(void) +{ + struct spdk_json_val v; + + v.type = SPDK_JSON_VAL_STRING; + v.start = "test"; + v.len = sizeof("test") - 1; + CU_ASSERT(spdk_json_strequal(&v, "test") == true); + CU_ASSERT(spdk_json_strequal(&v, "TEST") == false); + CU_ASSERT(spdk_json_strequal(&v, "hello") == false); + CU_ASSERT(spdk_json_strequal(&v, "t") == false); + + v.type = SPDK_JSON_VAL_NAME; + CU_ASSERT(spdk_json_strequal(&v, "test") == true); + + v.type = SPDK_JSON_VAL_NUMBER; + CU_ASSERT(spdk_json_strequal(&v, "test") == false); + + v.type = SPDK_JSON_VAL_STRING; + v.start = "test\0hello"; + v.len = sizeof("test\0hello") - 1; + CU_ASSERT(spdk_json_strequal(&v, "test") == false); +} + +static void +test_num_to_int32(void) +{ + struct spdk_json_val v; + char buf[100]; + int32_t i32; + + NUM_SETUP("1234"); + CU_ASSERT(spdk_json_number_to_int32(&v, &i32) == 0); + CU_ASSERT(i32 == 1234); + + + NUM_INT32_PASS("0", 0); + NUM_INT32_PASS("1234", 1234); + NUM_INT32_PASS("-1234", -1234); + NUM_INT32_PASS("1234.00000", 1234); + NUM_INT32_PASS("1.2e1", 12); + NUM_INT32_PASS("12340e-1", 1234); + NUM_INT32_PASS("-0", 0); + + NUM_INT32_FAIL("1.2"); + NUM_INT32_FAIL("1.2E0"); + NUM_INT32_FAIL("1.234e1"); + NUM_INT32_FAIL("12341e-1"); +} + +int main(int argc, char **argv) +{ + CU_pSuite suite = NULL; + unsigned int num_failures; + + if (CU_initialize_registry() != CUE_SUCCESS) { + return CU_get_error(); + } + + suite = CU_add_suite("json", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "strequal", test_strequal) == NULL || + CU_add_test(suite, "num_to_int32", test_num_to_int32) == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + + CU_basic_run_tests(); + + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + + return num_failures; +} diff --git a/test/lib/json/write/.gitignore b/test/lib/json/write/.gitignore new file mode 100644 index 000000000..dd576b238 --- /dev/null +++ b/test/lib/json/write/.gitignore @@ -0,0 +1 @@ +json_write_ut diff --git a/test/lib/json/write/Makefile b/test/lib/json/write/Makefile new file mode 100644 index 000000000..0bb706d87 --- /dev/null +++ b/test/lib/json/write/Makefile @@ -0,0 +1,38 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +SPDK_ROOT_DIR := $(CURDIR)/../../../.. + +TEST_FILE = json_write_ut.c + +include $(SPDK_ROOT_DIR)/mk/json.unittest.mk diff --git a/test/lib/json/write/json_write_ut.c b/test/lib/json/write/json_write_ut.c new file mode 100644 index 000000000..64502bfec --- /dev/null +++ b/test/lib/json/write/json_write_ut.c @@ -0,0 +1,619 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk_cunit.h" + +#include "json_write.c" + +#include +#include + +static uint8_t g_buf[1000]; +static uint8_t *g_write_pos; + +static int +write_cb(void *cb_ctx, const void *data, size_t size) +{ + size_t buf_free = g_buf + sizeof(g_buf) - g_write_pos; + + if (size > buf_free) { + return -1; + } + + memcpy(g_write_pos, data, size); + g_write_pos += size; + + return 0; +} + +#define BEGIN() \ + memset(g_buf, 0, sizeof(g_buf)); \ + g_write_pos = g_buf; \ + w = spdk_json_write_begin(write_cb, NULL, 0); \ + CU_ASSERT_FATAL(w != NULL) + +#define END(json) \ + CU_ASSERT(spdk_json_write_end(w) == 0); \ + CU_ASSERT(g_write_pos - g_buf == sizeof(json) - 1); \ + CU_ASSERT(memcmp(json, g_buf, sizeof(json) - 1) == 0) + +#define END_NOCMP() \ + CU_ASSERT(spdk_json_write_end(w) == 0) + +#define END_FAIL() \ + CU_ASSERT(spdk_json_write_end(w) < 0) + +#define VAL_STRING(str) \ + CU_ASSERT(spdk_json_write_string_raw(w, str, sizeof(str) - 1) == 0) + +#define VAL_STRING_FAIL(str) \ + CU_ASSERT(spdk_json_write_string_raw(w, str, sizeof(str) - 1) < 0) + +#define STR_PASS(in, out) \ + BEGIN(); VAL_STRING(in); END("\"" out "\"") + +#define STR_FAIL(in) \ + BEGIN(); VAL_STRING_FAIL(in); END_FAIL() + +#define VAL_NAME(name) \ + CU_ASSERT(spdk_json_write_name_raw(w, name, sizeof(name) - 1) == 0) + +#define VAL_NULL() CU_ASSERT(spdk_json_write_null(w) == 0) +#define VAL_TRUE() CU_ASSERT(spdk_json_write_bool(w, true) == 0) +#define VAL_FALSE() CU_ASSERT(spdk_json_write_bool(w, false) == 0) + +#define VAL_INT32(i) CU_ASSERT(spdk_json_write_int32(w, i) == 0); +#define VAL_UINT32(u) CU_ASSERT(spdk_json_write_uint32(w, u) == 0); + +#define VAL_ARRAY_BEGIN() CU_ASSERT(spdk_json_write_array_begin(w) == 0) +#define VAL_ARRAY_END() CU_ASSERT(spdk_json_write_array_end(w) == 0) + +#define VAL_OBJECT_BEGIN() CU_ASSERT(spdk_json_write_object_begin(w) == 0) +#define VAL_OBJECT_END() CU_ASSERT(spdk_json_write_object_end(w) == 0) + +static void +test_write_literal(void) +{ + struct spdk_json_write_ctx *w; + + BEGIN(); + VAL_NULL(); + END("null"); + + BEGIN(); + VAL_TRUE(); + END("true"); + + BEGIN(); + VAL_FALSE(); + END("false"); +} + +static void +test_write_string_simple(void) +{ + struct spdk_json_write_ctx *w; + + STR_PASS("hello world", "hello world"); + STR_PASS(" ", " "); + STR_PASS("~", "~"); +} + +static void +test_write_string_escapes(void) +{ + struct spdk_json_write_ctx *w; + + /* Two-character escapes */ + STR_PASS("\b", "\\b"); + STR_PASS("\f", "\\f"); + STR_PASS("\n", "\\n"); + STR_PASS("\r", "\\r"); + STR_PASS("\t", "\\t"); + STR_PASS("\"", "\\\""); + STR_PASS("\\", "\\\\"); + + /* JSON defines an escape for forward slash, but it is optional */ + STR_PASS("/", "/"); + + STR_PASS("hello\nworld", "hello\\nworld"); + + STR_PASS("\x00", "\\u0000"); + STR_PASS("\x01", "\\u0001"); + STR_PASS("\x02", "\\u0002"); + + STR_PASS("\xC3\xB6", "\\u00F6"); + STR_PASS("\xE2\x88\x9A", "\\u221A"); + STR_PASS("\xEA\xAA\xAA", "\\uAAAA"); + + /* Surrogate pairs */ + STR_PASS("\xF0\x9D\x84\x9E", "\\uD834\\uDD1E"); + STR_PASS("\xF0\xA0\x9C\x8E", "\\uD841\\uDF0E"); + + /* Examples from RFC 3629 */ + STR_PASS("\x41\xE2\x89\xA2\xCE\x91\x2E", "A\\u2262\\u0391."); + STR_PASS("\xED\x95\x9C\xEA\xB5\xAD\xEC\x96\xB4", "\\uD55C\\uAD6D\\uC5B4"); + STR_PASS("\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E", "\\u65E5\\u672C\\u8A9E"); + STR_PASS("\xEF\xBB\xBF\xF0\xA3\x8E\xB4", "\\uFEFF\\uD84C\\uDFB4"); + + /* UTF-8 edge cases */ + STR_PASS("\x7F", "\\u007F"); + STR_FAIL("\x80"); + STR_FAIL("\xC1"); + STR_FAIL("\xC2"); + STR_PASS("\xC2\x80", "\\u0080"); + STR_PASS("\xC2\xBF", "\\u00BF"); + STR_PASS("\xDF\x80", "\\u07C0"); + STR_PASS("\xDF\xBF", "\\u07FF"); + STR_FAIL("\xDF"); + STR_FAIL("\xE0\x80"); + STR_FAIL("\xE0\x1F"); + STR_FAIL("\xE0\x1F\x80"); + STR_FAIL("\xE0"); + STR_FAIL("\xE0\xA0"); + STR_PASS("\xE0\xA0\x80", "\\u0800"); + STR_PASS("\xE0\xA0\xBF", "\\u083F"); + STR_FAIL("\xE0\xA0\xC0"); + STR_PASS("\xE0\xBF\x80", "\\u0FC0"); + STR_PASS("\xE0\xBF\xBF", "\\u0FFF"); + STR_FAIL("\xE0\xC0\x80"); + STR_FAIL("\xE1"); + STR_FAIL("\xE1\x80"); + STR_FAIL("\xE1\x7F\x80"); + STR_FAIL("\xE1\x80\x7F"); + STR_PASS("\xE1\x80\x80", "\\u1000"); + STR_PASS("\xE1\x80\xBF", "\\u103F"); + STR_PASS("\xE1\xBF\x80", "\\u1FC0"); + STR_PASS("\xE1\xBF\xBF", "\\u1FFF"); + STR_FAIL("\xE1\xC0\x80"); + STR_FAIL("\xE1\x80\xC0"); + STR_PASS("\xEF\x80\x80", "\\uF000"); + STR_PASS("\xEF\xBF\xBF", "\\uFFFF"); + STR_FAIL("\xF0"); + STR_FAIL("\xF0\x90"); + STR_FAIL("\xF0\x90\x80"); + STR_FAIL("\xF0\x80\x80\x80"); + STR_FAIL("\xF0\x8F\x80\x80"); + STR_PASS("\xF0\x90\x80\x80", "\\uD800\\uDC00"); + STR_PASS("\xF0\x90\x80\xBF", "\\uD800\\uDC3F"); + STR_PASS("\xF0\x90\xBF\x80", "\\uD803\\uDFC0"); + STR_PASS("\xF0\xBF\x80\x80", "\\uD8BC\\uDC00"); + STR_FAIL("\xF0\xC0\x80\x80"); + STR_FAIL("\xF1"); + STR_FAIL("\xF1\x80"); + STR_FAIL("\xF1\x80\x80"); + STR_FAIL("\xF1\x80\x80\x7F"); + STR_PASS("\xF1\x80\x80\x80", "\\uD8C0\\uDC00"); + STR_PASS("\xF1\x80\x80\xBF", "\\uD8C0\\uDC3F"); + STR_PASS("\xF1\x80\xBF\x80", "\\uD8C3\\uDFC0"); + STR_PASS("\xF1\xBF\x80\x80", "\\uD9BC\\uDC00"); + STR_PASS("\xF3\x80\x80\x80", "\\uDAC0\\uDC00"); + STR_FAIL("\xF3\xC0\x80\x80"); + STR_FAIL("\xF3\x80\xC0\x80"); + STR_FAIL("\xF3\x80\x80\xC0"); + STR_FAIL("\xF4"); + STR_FAIL("\xF4\x80"); + STR_FAIL("\xF4\x80\x80"); + STR_PASS("\xF4\x80\x80\x80", "\\uDBC0\\uDC00"); + STR_PASS("\xF4\x8F\x80\x80", "\\uDBFC\\uDC00"); + STR_PASS("\xF4\x8F\xBF\xBF", "\\uDBFF\\uDFFF"); + STR_FAIL("\xF4\x90\x80\x80"); + STR_FAIL("\xF5"); + STR_FAIL("\xF5\x80"); + STR_FAIL("\xF5\x80\x80"); + STR_FAIL("\xF5\x80\x80\x80"); + STR_FAIL("\xF5\x80\x80\x80\x80"); + + /* Overlong encodings */ + STR_FAIL("\xC0\x80"); + + /* Surrogate pairs */ + STR_FAIL("\xED\xA0\x80"); /* U+D800 First high surrogate */ + STR_FAIL("\xED\xAF\xBF"); /* U+DBFF Last high surrogate */ + STR_FAIL("\xED\xB0\x80"); /* U+DC00 First low surrogate */ + STR_FAIL("\xED\xBF\xBF"); /* U+DFFF Last low surrogate */ + STR_FAIL("\xED\xA1\x8C\xED\xBE\xB4"); /* U+233B4 (invalid surrogate pair encoding) */ +} + +static void +test_write_number_int32(void) +{ + struct spdk_json_write_ctx *w; + + BEGIN(); + VAL_INT32(0); + END("0"); + + BEGIN(); + VAL_INT32(1); + END("1"); + + BEGIN(); + VAL_INT32(123); + END("123"); + + BEGIN(); + VAL_INT32(-123); + END("-123"); + + BEGIN(); + VAL_INT32(2147483647); + END("2147483647"); + + BEGIN(); + VAL_INT32(-2147483648); + END("-2147483648"); +} + +static void +test_write_number_uint32(void) +{ + struct spdk_json_write_ctx *w; + + BEGIN(); + VAL_UINT32(0); + END("0"); + + BEGIN(); + VAL_UINT32(1); + END("1"); + + BEGIN(); + VAL_UINT32(123); + END("123"); + + BEGIN(); + VAL_UINT32(2147483647); + END("2147483647"); + + BEGIN(); + VAL_UINT32(4294967295); + END("4294967295"); +} + +static void +test_write_array(void) +{ + struct spdk_json_write_ctx *w; + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_ARRAY_END(); + END("[]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_INT32(0); + VAL_ARRAY_END(); + END("[0]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_INT32(0); + VAL_INT32(1); + VAL_ARRAY_END(); + END("[0,1]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_INT32(0); + VAL_INT32(1); + VAL_INT32(2); + VAL_ARRAY_END(); + END("[0,1,2]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_STRING("a"); + VAL_ARRAY_END(); + END("[\"a\"]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_STRING("a"); + VAL_STRING("b"); + VAL_ARRAY_END(); + END("[\"a\",\"b\"]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_STRING("a"); + VAL_STRING("b"); + VAL_STRING("c"); + VAL_ARRAY_END(); + END("[\"a\",\"b\",\"c\"]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_TRUE(); + VAL_ARRAY_END(); + END("[true]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_TRUE(); + VAL_FALSE(); + VAL_ARRAY_END(); + END("[true,false]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_TRUE(); + VAL_FALSE(); + VAL_TRUE(); + VAL_ARRAY_END(); + END("[true,false,true]"); +} + +static void +test_write_object(void) +{ + struct spdk_json_write_ctx *w; + + BEGIN(); + VAL_OBJECT_BEGIN(); + VAL_OBJECT_END(); + END("{}"); + + BEGIN(); + VAL_OBJECT_BEGIN(); + VAL_NAME("a"); + VAL_INT32(0); + VAL_OBJECT_END(); + END("{\"a\":0}"); + + BEGIN(); + VAL_OBJECT_BEGIN(); + VAL_NAME("a"); + VAL_INT32(0); + VAL_NAME("b"); + VAL_INT32(1); + VAL_OBJECT_END(); + END("{\"a\":0,\"b\":1}"); + + BEGIN(); + VAL_OBJECT_BEGIN(); + VAL_NAME("a"); + VAL_INT32(0); + VAL_NAME("b"); + VAL_INT32(1); + VAL_NAME("c"); + VAL_INT32(2); + VAL_OBJECT_END(); + END("{\"a\":0,\"b\":1,\"c\":2}"); +} + +static void +test_write_nesting(void) +{ + struct spdk_json_write_ctx *w; + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_ARRAY_END(); + VAL_ARRAY_END(); + END("[[]]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_ARRAY_END(); + VAL_ARRAY_END(); + VAL_ARRAY_END(); + END("[[[]]]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_INT32(0); + VAL_ARRAY_BEGIN(); + VAL_ARRAY_END(); + VAL_ARRAY_END(); + END("[0,[]]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_ARRAY_END(); + VAL_INT32(0); + VAL_ARRAY_END(); + END("[[],0]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_INT32(0); + VAL_ARRAY_BEGIN(); + VAL_INT32(1); + VAL_ARRAY_END(); + VAL_INT32(2); + VAL_ARRAY_END(); + END("[0,[1],2]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_INT32(0); + VAL_INT32(1); + VAL_ARRAY_BEGIN(); + VAL_INT32(2); + VAL_INT32(3); + VAL_ARRAY_END(); + VAL_INT32(4); + VAL_INT32(5); + VAL_ARRAY_END(); + END("[0,1,[2,3],4,5]"); + + BEGIN(); + VAL_OBJECT_BEGIN(); + VAL_NAME("a"); + VAL_OBJECT_BEGIN(); + VAL_OBJECT_END(); + VAL_OBJECT_END(); + END("{\"a\":{}}"); + + BEGIN(); + VAL_OBJECT_BEGIN(); + VAL_NAME("a"); + VAL_OBJECT_BEGIN(); + VAL_NAME("b"); + VAL_INT32(0); + VAL_OBJECT_END(); + VAL_OBJECT_END(); + END("{\"a\":{\"b\":0}}"); + + BEGIN(); + VAL_OBJECT_BEGIN(); + VAL_NAME("a"); + VAL_ARRAY_BEGIN(); + VAL_INT32(0); + VAL_ARRAY_END(); + VAL_OBJECT_END(); + END("{\"a\":[0]}"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_OBJECT_BEGIN(); + VAL_NAME("a"); + VAL_INT32(0); + VAL_OBJECT_END(); + VAL_ARRAY_END(); + END("[{\"a\":0}]"); + + BEGIN(); + VAL_ARRAY_BEGIN(); + VAL_OBJECT_BEGIN(); + VAL_NAME("a"); + VAL_OBJECT_BEGIN(); + VAL_NAME("b"); + VAL_ARRAY_BEGIN(); + VAL_OBJECT_BEGIN(); + VAL_NAME("c"); + VAL_INT32(1); + VAL_OBJECT_END(); + VAL_INT32(2); + VAL_ARRAY_END(); + VAL_NAME("d"); + VAL_INT32(3); + VAL_OBJECT_END(); + VAL_NAME("e"); + VAL_INT32(4); + VAL_OBJECT_END(); + VAL_INT32(5); + VAL_ARRAY_END(); + END("[{\"a\":{\"b\":[{\"c\":1},2],\"d\":3},\"e\":4},5]"); + + /* Examples from RFC 7159 */ + BEGIN(); + VAL_OBJECT_BEGIN(); + VAL_NAME("Image"); + VAL_OBJECT_BEGIN(); + VAL_NAME("Width"); + VAL_INT32(800); + VAL_NAME("Height"); + VAL_INT32(600); + VAL_NAME("Title"); + VAL_STRING("View from 15th Floor"); + VAL_NAME("Thumbnail"); + VAL_OBJECT_BEGIN(); + VAL_NAME("Url"); + VAL_STRING("http://www.example.com/image/481989943"); + VAL_NAME("Height"); + VAL_INT32(125); + VAL_NAME("Width"); + VAL_INT32(100); + VAL_OBJECT_END(); + VAL_NAME("Animated"); + VAL_FALSE(); + VAL_NAME("IDs"); + VAL_ARRAY_BEGIN(); + VAL_INT32(116); + VAL_INT32(943); + VAL_INT32(234); + VAL_INT32(38793); + VAL_ARRAY_END(); + VAL_OBJECT_END(); + VAL_OBJECT_END(); + END( + "{\"Image\":" + "{" + "\"Width\":800," + "\"Height\":600," + "\"Title\":\"View from 15th Floor\"," + "\"Thumbnail\":{" + "\"Url\":\"http://www.example.com/image/481989943\"," + "\"Height\":125," + "\"Width\":100" + "}," + "\"Animated\":false," + "\"IDs\":[116,943,234,38793]" + "}" + "}"); +} + +int main(int argc, char **argv) +{ + CU_pSuite suite = NULL; + unsigned int num_failures; + + if (CU_initialize_registry() != CUE_SUCCESS) { + return CU_get_error(); + } + + suite = CU_add_suite("json", NULL, NULL); + if (suite == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if ( + CU_add_test(suite, "write_literal", test_write_literal) == NULL || + CU_add_test(suite, "write_string_simple", test_write_string_simple) == NULL || + CU_add_test(suite, "write_string_escapes", test_write_string_escapes) == NULL || + CU_add_test(suite, "write_number_int32", test_write_number_int32) == NULL || + CU_add_test(suite, "write_number_uint32", test_write_number_uint32) == NULL || + CU_add_test(suite, "write_array", test_write_array) == NULL || + CU_add_test(suite, "write_object", test_write_object) == NULL || + CU_add_test(suite, "write_nesting", test_write_nesting) == NULL) { + CU_cleanup_registry(); + return CU_get_error(); + } + + CU_basic_set_mode(CU_BRM_VERBOSE); + + CU_basic_run_tests(); + + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + + return num_failures; +} diff --git a/unittest.sh b/unittest.sh index 98c0a2720..df928b581 100755 --- a/unittest.sh +++ b/unittest.sh @@ -13,3 +13,9 @@ test/lib/nvme/unit/nvme_qpair_c/nvme_qpair_ut make -C test/lib/ioat/unit CONFIG_WERROR=y test/lib/ioat/unit/ioat_ut + +make -C test/lib/json CONFIG_WERROR=y + +test/lib/json/parse/json_parse_ut +test/lib/json/util/json_util_ut +test/lib/json/write/json_write_ut