diff --git a/.gitignore b/.gitignore index bd8c8699..42a18e82 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ benchmarks/files/numbers/*.json .idea cmake-build-debug + +test/test-* diff --git a/.travis.yml b/.travis.yml index d9896384..bb0e504d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,7 +39,7 @@ matrix: sources: ['ubuntu-toolchain-r-test'] packages: [g++-4.9, valgrind] after_success: - - valgrind --error-exitcode=1 --leak-check=full test/json_unit + - make check TEST_PREFIX="valgrind --error-exitcode=1 --leak-check=full " TEST_PATTERN="" # cppcheck @@ -68,9 +68,10 @@ matrix: - tar xf lcov_1.11.orig.tar.gz - sudo make -C lcov-1.11/ install - gem install coveralls-lcov + - pip install --user cpp-coveralls after_success: - make clean - - CXXFLAGS="--coverage -g -O0" CPPFLAGS="-DNDEBUG" make + - CXXFLAGS="--coverage -g -O0" CPPFLAGS="-DNDEBUG" make json_unit - test/json_unit "*" - coveralls --build-root test --exclude src/catch.hpp --exclude src/unit-algorithms.cpp --exclude src/unit-allocator.cpp --exclude src/unit-capacity.cpp --exclude src/unit-class_const_iterator.cpp --exclude src/unit-class_iterator.cpp --exclude src/unit-class_lexer.cpp --exclude src/unit-class_parser.cpp --exclude src/unit-comparison.cpp --exclude src/unit-concepts.cpp --exclude src/unit-constructor1.cpp --exclude src/unit-constructor2.cpp --exclude src/unit-convenience.cpp --exclude src/unit-conversions.cpp --exclude src/unit-deserialization.cpp --exclude src/unit-element_access1.cpp --exclude src/unit-element_access2.cpp --exclude src/unit-inspection.cpp --exclude src/unit-iterator_wrapper.cpp --exclude src/unit-iterators1.cpp --exclude src/unit-iterators2.cpp --exclude src/unit-json_patch.cpp --exclude src/unit-json_pointer.cpp --exclude src/unit-modifiers.cpp --exclude src/unit-pointer_access.cpp --exclude src/unit-readme.cpp --exclude src/unit-reference_access.cpp --exclude src/unit-regression.cpp --exclude src/unit-serialization.cpp --exclude src/unit-testsuites.cpp --exclude src/unit-unicode.cpp --include ../src/json.hpp --gcov-options '\-lp' --gcov 'gcov-4.9' - lcov --directory src --directory test/src --capture --output-file coverage.info --rc lcov_branch_coverage=1 --no-external @@ -236,11 +237,8 @@ script: - uname -a - $CXX --version - # compile - - make - - # execute unit tests - - test/json_unit "*" + # compile and execute unit tests + - make check # check if homebrew works (only checks develop branch) - if [ `which brew` ]; then @@ -249,51 +247,3 @@ script: brew install nlohmann_json --HEAD ; brew test nlohmann_json ; fi - -#language: cpp -# -#dist: trusty -#sudo: required -# -#env: -# global: -# # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created -# # via the "travis encrypt" command using the project repo's public key -# - secure: "m89SSgE+ASLO38rSKx7MTXK3n5NkP9bIx95jwY71YEiuFzib30PDJ/DifKnXxBjvy/AkCGztErQRk/8ZCvq+4HXozU2knEGnL/RUitvlwbhzfh2D4lmS3BvWBGS3N3NewoPBrRmdcvnT0xjOGXxtZaJ3P74TkB9GBnlz/HmKORA=" -# -## from http://stackoverflow.com/a/32127147/266378 -#matrix: -# include: -# - os: linux -# compiler: gcc -# addons: -# apt: -# sources: ['ubuntu-toolchain-r-test'] -# packages: ['g++-4.9', 'valgrind', 'python-pip', 'python-yaml'] -# before_script: -# - pip install --user git+git://github.com/eddyxu/cpp-coveralls.git -# after_success: -# - make clean -# - touch src/json.hpp -# - make json_unit CXXFLAGS="-fprofile-arcs -ftest-coverage -std=c++11 -lstdc++" CXX=$COMPILER -# - test/json_unit "*" -# - coveralls --build-root test --exclude src/catch.hpp --exclude src/unit-algorithms.cpp --exclude src/unit-allocator.cpp --exclude src/unit-capacity.cpp --exclude src/unit-class_const_iterator.cpp --exclude src/unit-class_iterator.cpp --exclude src/unit-class_lexer.cpp --exclude src/unit-class_parser.cpp --exclude src/unit-comparison.cpp --exclude src/unit-concepts.cpp --exclude src/unit-constructor1.cpp --exclude src/unit-constructor2.cpp --exclude src/unit-convenience.cpp --exclude src/unit-conversions.cpp --exclude src/unit-deserialization.cpp --exclude src/unit-element_access1.cpp --exclude src/unit-element_access2.cpp --exclude src/unit-inspection.cpp --exclude src/unit-iterator_wrapper.cpp --exclude src/unit-iterators1.cpp --exclude src/unit-iterators2.cpp --exclude src/unit-json_patch.cpp --exclude src/unit-json_pointer.cpp --exclude src/unit-modifiers.cpp --exclude src/unit-pointer_access.cpp --exclude src/unit-readme.cpp --exclude src/unit-reference_access.cpp --exclude src/unit-regression.cpp --exclude src/unit-serialization.cpp --exclude src/unit-testsuites.cpp --exclude src/unit-unicode.cpp --include ../src/json.hpp --gcov-options '\-lp' --gcov 'gcov-4.9' -# env: COMPILER=g++-4.9 -# -# - os: linux -# compiler: gcc -# before_install: echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca-certificates.crt -# addons: -# apt: -# sources: ['ubuntu-toolchain-r-test'] -# packages: ['g++-5', 'valgrind'] -# coverity_scan: -# project: -# name: "nlohmann/json" -# description: "Build submitted via Travis CI" -# notification_email: niels.lohmann@gmail.com -# build_command_prepend: "make clean ; sudo cp $(which g++-5) $(which g++)" -# build_command: "make" -# branch_pattern: coverity_scan -# env: COMPILER=g++-5 -# diff --git a/Makefile b/Makefile index 3fd6d54b..fcce453e 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,8 @@ RE2C = re2c SED = sed # main target -all: json_unit +all: + $(MAKE) -C test # clean up clean: @@ -21,14 +22,14 @@ clean: # build unit tests json_unit: - @$(MAKE) -C test + @$(MAKE) json_unit -C test # run unit tests -check: json_unit - test/json_unit "*" +check: + $(MAKE) check -C test -check-fast: json_unit - test/json_unit +check-fast: + $(MAKE) check -C test TEST_PATTERN="" ########################################################################## @@ -69,6 +70,7 @@ cppcheck: clang_sanitize: clean CXX=clang++ CXXFLAGS="-g -O2 -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer" $(MAKE) + ########################################################################## # maintainer targets ########################################################################## diff --git a/doc/examples/from_cbor.cpp b/doc/examples/from_cbor.cpp new file mode 100644 index 00000000..92b05225 --- /dev/null +++ b/doc/examples/from_cbor.cpp @@ -0,0 +1,18 @@ +#include + +using json = nlohmann::json; + +int main() +{ + // create byte vector + std::vector v = {0xa2, 0x67, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, + 0x74, 0xf5, 0x66, 0x73, 0x63, 0x68, 0x65, 0x6d, + 0x61, 0x00 + }; + + // deserialize it with CBOR + json j = json::from_cbor(v); + + // print the deserialized JSON value + std::cout << std::setw(2) << j << std::endl; +} diff --git a/doc/examples/from_cbor.link b/doc/examples/from_cbor.link new file mode 100644 index 00000000..81204989 --- /dev/null +++ b/doc/examples/from_cbor.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/from_cbor.output b/doc/examples/from_cbor.output new file mode 100644 index 00000000..259f63bd --- /dev/null +++ b/doc/examples/from_cbor.output @@ -0,0 +1,4 @@ +{ + "compact": true, + "schema": 0 +} diff --git a/doc/examples/from_msgpack.cpp b/doc/examples/from_msgpack.cpp new file mode 100644 index 00000000..d275f13a --- /dev/null +++ b/doc/examples/from_msgpack.cpp @@ -0,0 +1,18 @@ +#include + +using json = nlohmann::json; + +int main() +{ + // create byte vector + std::vector v = {0x82, 0xa7, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, + 0x74, 0xc3, 0xa6, 0x73, 0x63, 0x68, 0x65, 0x6d, + 0x61, 0x00 + }; + + // deserialize it with MessagePack + json j = json::from_msgpack(v); + + // print the deserialized JSON value + std::cout << std::setw(2) << j << std::endl; +} diff --git a/doc/examples/from_msgpack.link b/doc/examples/from_msgpack.link new file mode 100644 index 00000000..0d5e7831 --- /dev/null +++ b/doc/examples/from_msgpack.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/from_msgpack.output b/doc/examples/from_msgpack.output new file mode 100644 index 00000000..259f63bd --- /dev/null +++ b/doc/examples/from_msgpack.output @@ -0,0 +1,4 @@ +{ + "compact": true, + "schema": 0 +} diff --git a/doc/examples/to_cbor.cpp b/doc/examples/to_cbor.cpp new file mode 100644 index 00000000..21a5ce86 --- /dev/null +++ b/doc/examples/to_cbor.cpp @@ -0,0 +1,19 @@ +#include + +using json = nlohmann::json; + +int main() +{ + // create a JSON value + json j = R"({"compact": true, "schema": 0})"_json; + + // serialize it to CBOR + std::vector v = json::to_cbor(j); + + // print the vector content + for (auto& byte : v) + { + std::cout << "0x" << std::hex << std::setw(2) << std::setfill('0') << (int)byte << " "; + } + std::cout << std::endl; +} diff --git a/doc/examples/to_cbor.link b/doc/examples/to_cbor.link new file mode 100644 index 00000000..3ab655c1 --- /dev/null +++ b/doc/examples/to_cbor.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/to_cbor.output b/doc/examples/to_cbor.output new file mode 100644 index 00000000..02c9adab --- /dev/null +++ b/doc/examples/to_cbor.output @@ -0,0 +1 @@ +0xa2 0x67 0x63 0x6f 0x6d 0x70 0x61 0x63 0x74 0xf5 0x66 0x73 0x63 0x68 0x65 0x6d 0x61 0x00 diff --git a/doc/examples/to_msgpack.cpp b/doc/examples/to_msgpack.cpp new file mode 100644 index 00000000..21c8817a --- /dev/null +++ b/doc/examples/to_msgpack.cpp @@ -0,0 +1,19 @@ +#include + +using json = nlohmann::json; + +int main() +{ + // create a JSON value + json j = R"({"compact": true, "schema": 0})"_json; + + // serialize it to MessagePack + std::vector v = json::to_msgpack(j); + + // print the vector content + for (auto& byte : v) + { + std::cout << "0x" << std::hex << std::setw(2) << std::setfill('0') << (int)byte << " "; + } + std::cout << std::endl; +} diff --git a/doc/examples/to_msgpack.link b/doc/examples/to_msgpack.link new file mode 100644 index 00000000..4c7d3a27 --- /dev/null +++ b/doc/examples/to_msgpack.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/to_msgpack.output b/doc/examples/to_msgpack.output new file mode 100644 index 00000000..4d6c40ab --- /dev/null +++ b/doc/examples/to_msgpack.output @@ -0,0 +1 @@ +0x82 0xa7 0x63 0x6f 0x6d 0x70 0x61 0x63 0x74 0xc3 0xa6 0x73 0x63 0x68 0x65 0x6d 0x61 0x00 diff --git a/src/json.hpp b/src/json.hpp index 533a8987..450c6467 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -34,7 +34,7 @@ SOFTWARE. #include // assert #include // isdigit #include // and, not, or -#include // isfinite, signbit +#include // isfinite, ldexp, signbit #include // nullptr_t, ptrdiff_t, size_t #include // int64_t, uint64_t #include // strtod, strtof, strtold, strtoul @@ -6218,6 +6218,1432 @@ class basic_json /// @} + ////////////////////////////////////////// + // binary serialization/deserialization // + ////////////////////////////////////////// + + /// @name binary serialization/deserialization support + /// @{ + + private: + template + static void add_to_vector(std::vector& vec, size_t bytes, const T number) + { + assert(bytes == 1 or bytes == 2 or bytes == 4 or bytes == 8); + + switch (bytes) + { + case 8: + { + vec.push_back(static_cast((number >> 070) & 0xff)); + vec.push_back(static_cast((number >> 060) & 0xff)); + vec.push_back(static_cast((number >> 050) & 0xff)); + vec.push_back(static_cast((number >> 040) & 0xff)); + // intentional fall-through + } + + case 4: + { + vec.push_back(static_cast((number >> 030) & 0xff)); + vec.push_back(static_cast((number >> 020) & 0xff)); + // intentional fall-through + } + + case 2: + { + vec.push_back(static_cast((number >> 010) & 0xff)); + // intentional fall-through + } + + case 1: + { + vec.push_back(static_cast(number & 0xff)); + break; + } + } + } + + /*! + @brief take sufficient bytes from a vector to fill an integer variable + + In the context of binary serialization formats, we need to read several + bytes from a byte vector and combine them to multi-byte integral data + types. + + @param[in] vec byte vector to read from + @param[in] current_index the psition in the vector after which to read + + @return the next sizeof(T) bytes from @a vec, in reverse order as T + + @tparam T the integral return type + + @throw std::out_of_range if there are less than sizeof(T)+1 bytes in the + vector @a vec to read + + Precondition: + + vec: | | | a | b | c | d | | | T: | | | | | + ^ ^ ^ ^ + current_index idx ptr sizeof(T) + + Postcondition: + + vec: | | | a | b | c | d | | | T: | d | c | b | a | + ^ ^ ^ + | idx ptr + current_index + + @sa Code from . + */ + template + static T get_from_vector(const std::vector& vec, const size_t current_index) + { + if (current_index + sizeof(T) + 1 > vec.size()) + { + throw std::out_of_range("cannot read " + std::to_string(sizeof(T)) + " bytes from vector"); + } + + T result; + uint8_t* ptr = reinterpret_cast(&result); + size_t idx = current_index + 1 + sizeof(T); + while (idx > current_index) + { + *ptr++ = vec[--idx]; + } + return result; + } + + /*! + @brief create a MessagePack serialization of a given JSON value + + This is a straightforward implementation of the MessagePack specification. + + @param[in] j JSON value to serialize + @param[in,out] v byte vector to write the serialization to + + @sa https://github.com/msgpack/msgpack/blob/master/spec.md + */ + static void to_msgpack_internal(const basic_json& j, std::vector& v) + { + switch (j.type()) + { + case value_t::null: + { + // nil + v.push_back(0xc0); + break; + } + + case value_t::boolean: + { + // true and false + v.push_back(j.m_value.boolean ? 0xc3 : 0xc2); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // MessagePack does not differentiate between positive + // signed integers and unsigned integers. Therefore, we used + // the code from the value_t::number_unsigned case here. + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + add_to_vector(v, 1, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT8_MAX) + { + // uint 8 + v.push_back(0xcc); + add_to_vector(v, 1, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT16_MAX) + { + // uint 16 + v.push_back(0xcd); + add_to_vector(v, 2, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT32_MAX) + { + // uint 32 + v.push_back(0xce); + add_to_vector(v, 4, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT64_MAX) + { + // uint 64 + v.push_back(0xcf); + add_to_vector(v, 8, j.m_value.number_unsigned); + } + } + else + { + if (j.m_value.number_integer >= -32) + { + // negative fixnum + add_to_vector(v, 1, j.m_value.number_integer); + } + else if (j.m_value.number_integer >= INT8_MIN and j.m_value.number_integer <= INT8_MAX) + { + // int 8 + v.push_back(0xd0); + add_to_vector(v, 1, j.m_value.number_integer); + } + else if (j.m_value.number_integer >= INT16_MIN and j.m_value.number_integer <= INT16_MAX) + { + // int 16 + v.push_back(0xd1); + add_to_vector(v, 2, j.m_value.number_integer); + } + else if (j.m_value.number_integer >= INT32_MIN and j.m_value.number_integer <= INT32_MAX) + { + // int 32 + v.push_back(0xd2); + add_to_vector(v, 4, j.m_value.number_integer); + } + else if (j.m_value.number_integer >= INT64_MIN and j.m_value.number_integer <= INT64_MAX) + { + // int 64 + v.push_back(0xd3); + add_to_vector(v, 8, j.m_value.number_integer); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + add_to_vector(v, 1, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT8_MAX) + { + // uint 8 + v.push_back(0xcc); + add_to_vector(v, 1, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT16_MAX) + { + // uint 16 + v.push_back(0xcd); + add_to_vector(v, 2, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT32_MAX) + { + // uint 32 + v.push_back(0xce); + add_to_vector(v, 4, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT64_MAX) + { + // uint 64 + v.push_back(0xcf); + add_to_vector(v, 8, j.m_value.number_unsigned); + } + break; + } + + case value_t::number_float: + { + // float 64 + v.push_back(0xcb); + const uint8_t* helper = reinterpret_cast(&(j.m_value.number_float)); + for (size_t i = 0; i < 8; ++i) + { + v.push_back(helper[7 - i]); + } + break; + } + + case value_t::string: + { + const auto N = j.m_value.string->size(); + if (N <= 31) + { + // fixstr + v.push_back(static_cast(0xa0 | N)); + } + else if (N <= 255) + { + // str 8 + v.push_back(0xd9); + add_to_vector(v, 1, N); + } + else if (N <= 65535) + { + // str 16 + v.push_back(0xda); + add_to_vector(v, 2, N); + } + else if (N <= 4294967295) + { + // str 32 + v.push_back(0xdb); + add_to_vector(v, 4, N); + } + + // append string + std::copy(j.m_value.string->begin(), j.m_value.string->end(), + std::back_inserter(v)); + break; + } + + case value_t::array: + { + const auto N = j.m_value.array->size(); + if (N <= 15) + { + // fixarray + v.push_back(static_cast(0x90 | N)); + } + else if (N <= 0xffff) + { + // array 16 + v.push_back(0xdc); + add_to_vector(v, 2, N); + } + else if (N <= 0xffffffff) + { + // array 32 + v.push_back(0xdd); + add_to_vector(v, 4, N); + } + + // append each element + for (const auto& el : *j.m_value.array) + { + to_msgpack_internal(el, v); + } + break; + } + + case value_t::object: + { + const auto N = j.m_value.object->size(); + if (N <= 15) + { + // fixmap + v.push_back(static_cast(0x80 | (N & 0xf))); + } + else if (N <= 65535) + { + // map 16 + v.push_back(0xde); + add_to_vector(v, 2, N); + } + else if (N <= 4294967295) + { + // map 32 + v.push_back(0xdf); + add_to_vector(v, 4, N); + } + + // append each element + for (const auto& el : *j.m_value.object) + { + to_msgpack_internal(el.first, v); + to_msgpack_internal(el.second, v); + } + break; + } + + default: + { + break; + } + } + } + + /*! + @brief create a CBOR serialization of a given JSON value + + This is a straightforward implementation of the CBOR specification. + + @param[in] j JSON value to serialize + @param[in,out] v byte vector to write the serialization to + + @sa https://tools.ietf.org/html/rfc7049 + */ + static void to_cbor_internal(const basic_json& j, std::vector& v) + { + switch (j.type()) + { + case value_t::null: + { + v.push_back(0xf6); + break; + } + + case value_t::boolean: + { + v.push_back(j.m_value.boolean ? 0xf5 : 0xf4); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // CBOR does not differentiate between positive signed + // integers and unsigned integers. Therefore, we used the + // code from the value_t::number_unsigned case here. + if (j.m_value.number_integer <= 0x17) + { + add_to_vector(v, 1, j.m_value.number_integer); + } + else if (j.m_value.number_integer <= UINT8_MAX) + { + v.push_back(0x18); + // one-byte uint8_t + add_to_vector(v, 1, j.m_value.number_integer); + } + else if (j.m_value.number_integer <= UINT16_MAX) + { + v.push_back(0x19); + // two-byte uint16_t + add_to_vector(v, 2, j.m_value.number_integer); + } + else if (j.m_value.number_integer <= UINT32_MAX) + { + v.push_back(0x1a); + // four-byte uint32_t + add_to_vector(v, 4, j.m_value.number_integer); + } + else if (j.m_value.number_integer <= UINT64_MAX) + { + v.push_back(0x1b); + // eight-byte uint64_t + add_to_vector(v, 8, j.m_value.number_integer); + } + } + else + { + // The conversions below encode the sign in the first byte, + // and the value is converted to a positive number. + const auto positive_number = -1 - j.m_value.number_integer; + if (j.m_value.number_integer >= -24) + { + v.push_back(static_cast(0x20 + positive_number)); + } + else if (positive_number <= UINT8_MAX) + { + // int 8 + v.push_back(0x38); + add_to_vector(v, 1, positive_number); + } + else if (positive_number <= UINT16_MAX) + { + // int 16 + v.push_back(0x39); + add_to_vector(v, 2, positive_number); + } + else if (positive_number <= UINT32_MAX) + { + // int 32 + v.push_back(0x3a); + add_to_vector(v, 4, positive_number); + } + else if (positive_number <= UINT64_MAX) + { + // int 64 + v.push_back(0x3b); + add_to_vector(v, 8, positive_number); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned <= 0x17) + { + v.push_back(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= 0xff) + { + v.push_back(0x18); + // one-byte uint8_t + add_to_vector(v, 1, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= 0xffff) + { + v.push_back(0x19); + // two-byte uint16_t + add_to_vector(v, 2, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= 0xffffffff) + { + v.push_back(0x1a); + // four-byte uint32_t + add_to_vector(v, 4, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= 0xffffffffffffffff) + { + v.push_back(0x1b); + // eight-byte uint64_t + add_to_vector(v, 8, j.m_value.number_unsigned); + } + break; + } + + case value_t::number_float: + { + // Double-Precision Float + v.push_back(0xfb); + const uint8_t* helper = reinterpret_cast(&(j.m_value.number_float)); + for (size_t i = 0; i < 8; ++i) + { + v.push_back(helper[7 - i]); + } + break; + } + + case value_t::string: + { + const auto N = j.m_value.string->size(); + if (N <= 0x17) + { + v.push_back(0x60 + N); // 1 byte for string + size + } + else if (N <= 0xff) + { + v.push_back(0x78); // one-byte uint8_t for N + add_to_vector(v, 1, N); + } + else if (N <= 0xffff) + { + v.push_back(0x79); // two-byte uint16_t for N + add_to_vector(v, 2, N); + } + else if (N <= 0xffffffff) + { + v.push_back(0x7a); // four-byte uint32_t for N + add_to_vector(v, 4, N); + } + // LCOV_EXCL_START + else if (N <= 0xffffffffffffffff) + { + v.push_back(0x7b); // eight-byte uint64_t for N + add_to_vector(v, 8, N); + } + // LCOV_EXCL_STOP + + // append string + std::copy(j.m_value.string->begin(), j.m_value.string->end(), + std::back_inserter(v)); + break; + } + + case value_t::array: + { + const auto N = j.m_value.array->size(); + if (N <= 0x17) + { + v.push_back(0x80 + N); // 1 byte for array + size + } + else if (N <= 0xff) + { + v.push_back(0x98); // one-byte uint8_t for N + add_to_vector(v, 1, N); + } + else if (N <= 0xffff) + { + v.push_back(0x99); // two-byte uint16_t for N + add_to_vector(v, 2, N); + } + else if (N <= 0xffffffff) + { + v.push_back(0x9a); // four-byte uint32_t for N + add_to_vector(v, 4, N); + } + // LCOV_EXCL_START + else if (N <= 0xffffffffffffffff) + { + v.push_back(0x9b); // eight-byte uint64_t for N + add_to_vector(v, 8, N); + } + // LCOV_EXCL_STOP + + // append each element + for (const auto& el : *j.m_value.array) + { + to_cbor_internal(el, v); + } + break; + } + + case value_t::object: + { + const auto N = j.m_value.object->size(); + if (N <= 0x17) + { + v.push_back(0xa0 + N); // 1 byte for object + size + } + else if (N <= 0xff) + { + v.push_back(0xb8); + add_to_vector(v, 1, N); // one-byte uint8_t for N + } + else if (N <= 0xffff) + { + v.push_back(0xb9); + add_to_vector(v, 2, N); // two-byte uint16_t for N + } + else if (N <= 0xffffffff) + { + v.push_back(0xba); + add_to_vector(v, 4, N); // four-byte uint32_t for N + } + // LCOV_EXCL_START + else if (N <= 0xffffffffffffffff) + { + v.push_back(0xbb); + add_to_vector(v, 8, N); // eight-byte uint64_t for N + } + // LCOV_EXCL_STOP + + // append each element + for (const auto& el : *j.m_value.object) + { + to_cbor_internal(el.first, v); + to_cbor_internal(el.second, v); + } + break; + } + + default: + { + break; + } + } + } + + /*! + @brief create a JSON value from a given MessagePack vector + + @param[in] v MessagePack serialization + @param[in] idx byte index to start reading from @a v + + @return deserialized JSON value + + @throw std::invalid_argument if unsupported features from MessagePack were + used in the given vector @a v or if the input is not valid MessagePack + @throw std::out_of_range if the given vector ends prematurely + + @sa https://github.com/msgpack/msgpack/blob/master/spec.md + */ + static basic_json from_msgpack_internal(const std::vector& v, size_t& idx) + { + // store and increment index + const size_t current_idx = idx++; + + if (v[current_idx] <= 0xbf) + { + if (v[current_idx] <= 0x7f) // positive fixint + { + return v[current_idx]; + } + else if (v[current_idx] <= 0x8f) // fixmap + { + basic_json result = value_t::object; + const size_t len = v[current_idx] & 0x0f; + for (size_t i = 0; i < len; ++i) + { + std::string key = from_msgpack_internal(v, idx); + result[key] = from_msgpack_internal(v, idx); + } + return result; + } + else if (v[current_idx] <= 0x9f) // fixarray + { + basic_json result = value_t::array; + const size_t len = v[current_idx] & 0x0f; + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_msgpack_internal(v, idx)); + } + return result; + } + else // fixstr + { + const size_t len = v[current_idx] & 0x1f; + const size_t offset = current_idx + 1; + idx += len; // skip content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + } + else if (v[current_idx] >= 0xe0) // negative fixint + { + return static_cast(v[current_idx]); + } + else + { + switch (v[current_idx]) + { + case 0xc0: // nil + { + return value_t::null; + } + + case 0xc2: // false + { + return false; + } + + case 0xc3: // true + { + return true; + } + + case 0xca: // float 32 + { + // copy bytes in reverse order into the double variable + float res; + for (size_t byte = 0; byte < sizeof(float); ++byte) + { + reinterpret_cast(&res)[sizeof(float) - byte - 1] = v[current_idx + 1 + byte]; + } + idx += sizeof(float); // skip content bytes + return res; + } + + case 0xcb: // float 64 + { + // copy bytes in reverse order into the double variable + double res; + for (size_t byte = 0; byte < sizeof(double); ++byte) + { + reinterpret_cast(&res)[sizeof(double) - byte - 1] = v[current_idx + 1 + byte]; + } + idx += sizeof(double); // skip content bytes + return res; + } + + case 0xcc: // uint 8 + { + idx += 1; // skip content byte + return get_from_vector(v, current_idx); + } + + case 0xcd: // uint 16 + { + idx += 2; // skip 2 content bytes + return get_from_vector(v, current_idx); + } + + case 0xce: // uint 32 + { + idx += 4; // skip 4 content bytes + return get_from_vector(v, current_idx); + } + + case 0xcf: // uint 64 + { + idx += 8; // skip 8 content bytes + return get_from_vector(v, current_idx); + } + + case 0xd0: // int 8 + { + idx += 1; // skip content byte + return get_from_vector(v, current_idx); + } + + case 0xd1: // int 16 + { + idx += 2; // skip 2 content bytes + return get_from_vector(v, current_idx); + } + + case 0xd2: // int 32 + { + idx += 4; // skip 4 content bytes + return get_from_vector(v, current_idx); + } + + case 0xd3: // int 64 + { + idx += 8; // skip 8 content bytes + return get_from_vector(v, current_idx); + } + + case 0xd9: // str 8 + { + const auto len = get_from_vector(v, current_idx); + const size_t offset = current_idx + 2; + idx += len + 1; // skip size byte + content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0xda: // str 16 + { + const auto len = get_from_vector(v, current_idx); + const size_t offset = current_idx + 3; + idx += len + 2; // skip 2 size bytes + content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0xdb: // str 32 + { + const auto len = get_from_vector(v, current_idx); + const size_t offset = current_idx + 5; + idx += len + 4; // skip 4 size bytes + content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0xdc: // array 16 + { + basic_json result = value_t::array; + const auto len = get_from_vector(v, current_idx); + idx += 2; // skip 2 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_msgpack_internal(v, idx)); + } + return result; + } + + case 0xdd: // array 32 + { + basic_json result = value_t::array; + const auto len = get_from_vector(v, current_idx); + idx += 4; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_msgpack_internal(v, idx)); + } + return result; + } + + case 0xde: // map 16 + { + basic_json result = value_t::object; + const auto len = get_from_vector(v, current_idx); + idx += 2; // skip 2 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_msgpack_internal(v, idx); + result[key] = from_msgpack_internal(v, idx); + } + return result; + } + + case 0xdf: // map 32 + { + basic_json result = value_t::object; + const auto len = get_from_vector(v, current_idx); + idx += 4; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_msgpack_internal(v, idx); + result[key] = from_msgpack_internal(v, idx); + } + return result; + } + + default: + { + throw std::invalid_argument("error parsing a msgpack @ " + std::to_string(current_idx)); + } + } + } + } + + /*! + @brief create a JSON value from a given CBOR vector + + @param[in] v CBOR serialization + @param[in] idx byte index to start reading from @a v + + @return deserialized JSON value + + @throw std::invalid_argument if unsupported features from CBOR were used in + the given vector @a v or if the input is not valid CBOR + @throw std::out_of_range if the given vector ends prematurely + + @sa https://tools.ietf.org/html/rfc7049 + */ + static basic_json from_cbor_internal(const std::vector& v, size_t& idx) + { + // store and increment index + const size_t current_idx = idx++; + + switch (v[current_idx]) + { + // Integer 0x00..0x17 (0..23) + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + case 0x0e: + case 0x0f: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + { + return v[current_idx]; + } + + case 0x18: // Unsigned integer (one-byte uint8_t follows) + { + idx += 1; // skip content byte + return get_from_vector(v, current_idx); + } + + case 0x19: // Unsigned integer (two-byte uint16_t follows) + { + idx += 2; // skip 2 content bytes + return get_from_vector(v, current_idx); + } + + case 0x1a: // Unsigned integer (four-byte uint32_t follows) + { + idx += 4; // skip 4 content bytes + return get_from_vector(v, current_idx); + } + + case 0x1b: // Unsigned integer (eight-byte uint64_t follows) + { + idx += 8; // skip 8 content bytes + return get_from_vector(v, current_idx); + } + + // Negative integer -1-0x00..-1-0x17 (-1..-24) + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2a: + case 0x2b: + case 0x2c: + case 0x2d: + case 0x2e: + case 0x2f: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + { + return static_cast(0x20 - 1 - v[current_idx]); + } + + case 0x38: // Negative integer (one-byte uint8_t follows) + { + idx += 1; // skip content byte + // must be uint8_t ! + return static_cast(-1) - get_from_vector(v, current_idx); + } + + case 0x39: // Negative integer -1-n (two-byte uint16_t follows) + { + idx += 2; // skip 2 content bytes + return static_cast(-1) - get_from_vector(v, current_idx); + } + + case 0x3a: // Negative integer -1-n (four-byte uint32_t follows) + { + idx += 4; // skip 4 content bytes + return static_cast(-1) - get_from_vector(v, current_idx); + } + + case 0x3b: // Negative integer -1-n (eight-byte uint64_t follows) + { + idx += 8; // skip 8 content bytes + return static_cast(-1) - get_from_vector(v, current_idx); + } + + // UTF-8 string (0x00..0x17 bytes follow) + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6a: + case 0x6b: + case 0x6c: + case 0x6d: + case 0x6e: + case 0x6f: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + { + const size_t len = v[current_idx] - 0x60; + const size_t offset = current_idx + 1; + idx += len; // skip content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + { + const auto len = get_from_vector(v, current_idx); + const size_t offset = current_idx + 2; + idx += len + 1; // skip size byte + content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + { + const auto len = get_from_vector(v, current_idx); + const size_t offset = current_idx + 3; + idx += len + 2; // skip 2 size bytes + content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x7a: // UTF-8 string (four-byte uint32_t for n follow) + { + const auto len = get_from_vector(v, current_idx); + const size_t offset = current_idx + 5; + idx += len + 4; // skip 4 size bytes + content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x7b: // UTF-8 string (eight-byte uint64_t for n follow) + { + const auto len = get_from_vector(v, current_idx); + const size_t offset = current_idx + 9; + idx += len + 8; // skip 8 size bytes + content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x7f: // UTF-8 string (indefinite length) + { + std::string result; + while (v[idx] != 0xff) + { + string_t s = from_cbor_internal(v, idx); + result += s; + } + // skip break byte (0xFF) + idx += 1; + return result; + } + + // array (0x00..0x17 data items follow) + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8a: + case 0x8b: + case 0x8c: + case 0x8d: + case 0x8e: + case 0x8f: + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + { + basic_json result = value_t::array; + const size_t len = v[current_idx] - 0x80; + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x98: // array (one-byte uint8_t for n follows) + { + basic_json result = value_t::array; + const auto len = get_from_vector(v, current_idx); + idx += 1; // skip 1 size byte + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x99: // array (two-byte uint16_t for n follow) + { + basic_json result = value_t::array; + const auto len = get_from_vector(v, current_idx); + idx += 2; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x9a: // array (four-byte uint32_t for n follow) + { + basic_json result = value_t::array; + const auto len = get_from_vector(v, current_idx); + idx += 4; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x9b: // array (eight-byte uint64_t for n follow) + { + basic_json result = value_t::array; + const auto len = get_from_vector(v, current_idx); + idx += 8; // skip 8 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x9f: // array (indefinite length) + { + basic_json result = value_t::array; + while (v[idx] != 0xff) + { + result.push_back(from_cbor_internal(v, idx)); + } + // skip break byte (0xFF) + idx += 1; + return result; + } + + // map (0x00..0x17 pairs of data items follow) + case 0xa0: + case 0xa1: + case 0xa2: + case 0xa3: + case 0xa4: + case 0xa5: + case 0xa6: + case 0xa7: + case 0xa8: + case 0xa9: + case 0xaa: + case 0xab: + case 0xac: + case 0xad: + case 0xae: + case 0xaf: + case 0xb0: + case 0xb1: + case 0xb2: + case 0xb3: + case 0xb4: + case 0xb5: + case 0xb6: + case 0xb7: + { + basic_json result = value_t::object; + const size_t len = v[current_idx] - 0xa0; + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xb8: // map (one-byte uint8_t for n follows) + { + basic_json result = value_t::object; + const auto len = get_from_vector(v, current_idx); + idx += 1; // skip 1 size byte + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xb9: // map (two-byte uint16_t for n follow) + { + basic_json result = value_t::object; + const auto len = get_from_vector(v, current_idx); + idx += 2; // skip 2 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xba: // map (four-byte uint32_t for n follow) + { + basic_json result = value_t::object; + const auto len = get_from_vector(v, current_idx); + idx += 4; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xbb: // map (eight-byte uint64_t for n follow) + { + basic_json result = value_t::object; + const auto len = get_from_vector(v, current_idx); + idx += 8; // skip 8 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xbf: // map (indefinite length) + { + basic_json result = value_t::object; + while (v[idx] != 0xff) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + // skip break byte (0xFF) + idx += 1; + return result; + } + + case 0xf4: // false + { + return false; + } + + case 0xf5: // true + { + return true; + } + + case 0xf6: // null + { + return value_t::null; + } + + case 0xf9: // Half-Precision Float (two-byte IEEE 754) + { + idx += 2; // skip two content bytes + + // code from RFC 7049, Appendix D, Figure 3: + // As half-precision floating-point numbers were only added to + // IEEE 754 in 2008, today's programming platforms often still + // only have limited support for them. It is very easy to + // include at least decoding support for them even without such + // support. An example of a small decoder for half-precision + // floating-point numbers in the C language is shown in Fig. 3. + const int half = (v[current_idx + 1] << 8) + v[current_idx + 2]; + const int exp = (half >> 10) & 0x1f; + const int mant = half & 0x3ff; + double val; + if (exp == 0) + { + val = std::ldexp(mant, -24); + } + else if (exp != 31) + { + val = std::ldexp(mant + 1024, exp - 25); + } + else + { + val = mant == 0 ? INFINITY : NAN; + } + return half & 0x8000 ? -val : val; + } + + case 0xfa: // Single-Precision Float (four-byte IEEE 754) + { + // copy bytes in reverse order into the float variable + float res; + for (size_t byte = 0; byte < sizeof(float); ++byte) + { + reinterpret_cast(&res)[sizeof(float) - byte - 1] = v[current_idx + 1 + byte]; + } + idx += sizeof(float); // skip content bytes + return res; + } + + case 0xfb: // Double-Precision Float (eight-byte IEEE 754) + { + // copy bytes in reverse order into the double variable + double res; + for (size_t byte = 0; byte < sizeof(double); ++byte) + { + reinterpret_cast(&res)[sizeof(double) - byte - 1] = v[current_idx + 1 + byte]; + } + idx += sizeof(double); // skip content bytes + return res; + } + + default: // anything else (0xFF is handled inside the other types) + { + throw std::invalid_argument("error parsing a CBOR @ " + std::to_string(current_idx) + ": " + std::to_string(v[current_idx])); + } + } + } + + public: + /*! + @brief create a MessagePack serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the MessagePack + serialization format. MessagePack is a binary serialization format which + aims to be more compact than JSON itself, yet more efficient to parse. + + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in MessagePack format.,to_msgpack} + + @sa http://msgpack.org + @sa @ref from_msgpack(const std::vector&) for the analogous + deserialization + @sa @ref to_cbor(const basic_json& for the related CBOR format + */ + static std::vector to_msgpack(const basic_json& j) + { + std::vector result; + to_msgpack_internal(j, result); + return result; + } + + /*! + @brief create a JSON value from a byte vector in MessagePack format + + Deserializes a given byte vector @a v to a JSON value using the MessagePack + serialization format. + + @param[in] v a byte vector in MessagePack format + @return deserialized JSON value + + @throw std::invalid_argument if unsupported features from MessagePack were + used in the given vector @a v or if the input is not valid MessagePack + @throw std::out_of_range if the given vector ends prematurely + + @complexity Linear in the size of the byte vector @a v. + + @liveexample{The example shows the deserialization of a byte vector in + MessagePack format to a JSON value.,from_msgpack} + + @sa http://msgpack.org + @sa @ref to_msgpack(const basic_json&) for the analogous serialization + @sa @ref from_cbor(const std::vector&) for the related CBOR format + */ + static basic_json from_msgpack(const std::vector& v) + { + size_t i = 0; + return from_msgpack_internal(v, i); + } + + /*! + @brief create a MessagePack serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the CBOR (Concise + Binary Object Representation) serialization format. CBOR is a binary + serialization format which aims to be more compact than JSON itself, yet + more efficient to parse. + + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in CBOR format.,to_cbor} + + @sa http://cbor.io + @sa @ref from_cbor(const std::vector&) for the analogous + deserialization + @sa @ref to_msgpack(const basic_json& for the related MessagePack format + */ + static std::vector to_cbor(const basic_json& j) + { + std::vector result; + to_cbor_internal(j, result); + return result; + } + + /*! + @brief create a JSON value from a byte vector in CBOR format + + Deserializes a given byte vector @a v to a JSON value using the CBOR + (Concise Binary Object Representation) serialization format. + + @param[in] v a byte vector in CBOR format + @return deserialized JSON value + + @throw std::invalid_argument if unsupported features from CBOR were used in + the given vector @a v or if the input is not valid MessagePack + @throw std::out_of_range if the given vector ends prematurely + + @complexity Linear in the size of the byte vector @a v. + + @liveexample{The example shows the deserialization of a byte vector in CBOR + format to a JSON value.,from_cbor} + + @sa http://cbor.io + @sa @ref to_cbor(const basic_json&) for the analogous serialization + @sa @ref from_msgpack(const std::vector&) for the related + MessagePack format + */ + static basic_json from_cbor(const std::vector& v) + { + size_t i = 0; + return from_cbor_internal(v, i); + } + + /// @} private: /////////////////////////// diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index f3c19780..c381682c 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -34,7 +34,7 @@ SOFTWARE. #include // assert #include // isdigit #include // and, not, or -#include // isfinite, signbit +#include // isfinite, ldexp, signbit #include // nullptr_t, ptrdiff_t, size_t #include // int64_t, uint64_t #include // strtod, strtof, strtold, strtoul @@ -6218,6 +6218,1432 @@ class basic_json /// @} + ////////////////////////////////////////// + // binary serialization/deserialization // + ////////////////////////////////////////// + + /// @name binary serialization/deserialization support + /// @{ + + private: + template + static void add_to_vector(std::vector& vec, size_t bytes, const T number) + { + assert(bytes == 1 or bytes == 2 or bytes == 4 or bytes == 8); + + switch (bytes) + { + case 8: + { + vec.push_back(static_cast((number >> 070) & 0xff)); + vec.push_back(static_cast((number >> 060) & 0xff)); + vec.push_back(static_cast((number >> 050) & 0xff)); + vec.push_back(static_cast((number >> 040) & 0xff)); + // intentional fall-through + } + + case 4: + { + vec.push_back(static_cast((number >> 030) & 0xff)); + vec.push_back(static_cast((number >> 020) & 0xff)); + // intentional fall-through + } + + case 2: + { + vec.push_back(static_cast((number >> 010) & 0xff)); + // intentional fall-through + } + + case 1: + { + vec.push_back(static_cast(number & 0xff)); + break; + } + } + } + + /*! + @brief take sufficient bytes from a vector to fill an integer variable + + In the context of binary serialization formats, we need to read several + bytes from a byte vector and combine them to multi-byte integral data + types. + + @param[in] vec byte vector to read from + @param[in] current_index the psition in the vector after which to read + + @return the next sizeof(T) bytes from @a vec, in reverse order as T + + @tparam T the integral return type + + @throw std::out_of_range if there are less than sizeof(T)+1 bytes in the + vector @a vec to read + + Precondition: + + vec: | | | a | b | c | d | | | T: | | | | | + ^ ^ ^ ^ + current_index idx ptr sizeof(T) + + Postcondition: + + vec: | | | a | b | c | d | | | T: | d | c | b | a | + ^ ^ ^ + | idx ptr + current_index + + @sa Code from . + */ + template + static T get_from_vector(const std::vector& vec, const size_t current_index) + { + if (current_index + sizeof(T) + 1 > vec.size()) + { + throw std::out_of_range("cannot read " + std::to_string(sizeof(T)) + " bytes from vector"); + } + + T result; + uint8_t* ptr = reinterpret_cast(&result); + size_t idx = current_index + 1 + sizeof(T); + while (idx > current_index) + { + *ptr++ = vec[--idx]; + } + return result; + } + + /*! + @brief create a MessagePack serialization of a given JSON value + + This is a straightforward implementation of the MessagePack specification. + + @param[in] j JSON value to serialize + @param[in,out] v byte vector to write the serialization to + + @sa https://github.com/msgpack/msgpack/blob/master/spec.md + */ + static void to_msgpack_internal(const basic_json& j, std::vector& v) + { + switch (j.type()) + { + case value_t::null: + { + // nil + v.push_back(0xc0); + break; + } + + case value_t::boolean: + { + // true and false + v.push_back(j.m_value.boolean ? 0xc3 : 0xc2); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // MessagePack does not differentiate between positive + // signed integers and unsigned integers. Therefore, we used + // the code from the value_t::number_unsigned case here. + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + add_to_vector(v, 1, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT8_MAX) + { + // uint 8 + v.push_back(0xcc); + add_to_vector(v, 1, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT16_MAX) + { + // uint 16 + v.push_back(0xcd); + add_to_vector(v, 2, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT32_MAX) + { + // uint 32 + v.push_back(0xce); + add_to_vector(v, 4, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT64_MAX) + { + // uint 64 + v.push_back(0xcf); + add_to_vector(v, 8, j.m_value.number_unsigned); + } + } + else + { + if (j.m_value.number_integer >= -32) + { + // negative fixnum + add_to_vector(v, 1, j.m_value.number_integer); + } + else if (j.m_value.number_integer >= INT8_MIN and j.m_value.number_integer <= INT8_MAX) + { + // int 8 + v.push_back(0xd0); + add_to_vector(v, 1, j.m_value.number_integer); + } + else if (j.m_value.number_integer >= INT16_MIN and j.m_value.number_integer <= INT16_MAX) + { + // int 16 + v.push_back(0xd1); + add_to_vector(v, 2, j.m_value.number_integer); + } + else if (j.m_value.number_integer >= INT32_MIN and j.m_value.number_integer <= INT32_MAX) + { + // int 32 + v.push_back(0xd2); + add_to_vector(v, 4, j.m_value.number_integer); + } + else if (j.m_value.number_integer >= INT64_MIN and j.m_value.number_integer <= INT64_MAX) + { + // int 64 + v.push_back(0xd3); + add_to_vector(v, 8, j.m_value.number_integer); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + add_to_vector(v, 1, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT8_MAX) + { + // uint 8 + v.push_back(0xcc); + add_to_vector(v, 1, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT16_MAX) + { + // uint 16 + v.push_back(0xcd); + add_to_vector(v, 2, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT32_MAX) + { + // uint 32 + v.push_back(0xce); + add_to_vector(v, 4, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT64_MAX) + { + // uint 64 + v.push_back(0xcf); + add_to_vector(v, 8, j.m_value.number_unsigned); + } + break; + } + + case value_t::number_float: + { + // float 64 + v.push_back(0xcb); + const uint8_t* helper = reinterpret_cast(&(j.m_value.number_float)); + for (size_t i = 0; i < 8; ++i) + { + v.push_back(helper[7 - i]); + } + break; + } + + case value_t::string: + { + const auto N = j.m_value.string->size(); + if (N <= 31) + { + // fixstr + v.push_back(static_cast(0xa0 | N)); + } + else if (N <= 255) + { + // str 8 + v.push_back(0xd9); + add_to_vector(v, 1, N); + } + else if (N <= 65535) + { + // str 16 + v.push_back(0xda); + add_to_vector(v, 2, N); + } + else if (N <= 4294967295) + { + // str 32 + v.push_back(0xdb); + add_to_vector(v, 4, N); + } + + // append string + std::copy(j.m_value.string->begin(), j.m_value.string->end(), + std::back_inserter(v)); + break; + } + + case value_t::array: + { + const auto N = j.m_value.array->size(); + if (N <= 15) + { + // fixarray + v.push_back(static_cast(0x90 | N)); + } + else if (N <= 0xffff) + { + // array 16 + v.push_back(0xdc); + add_to_vector(v, 2, N); + } + else if (N <= 0xffffffff) + { + // array 32 + v.push_back(0xdd); + add_to_vector(v, 4, N); + } + + // append each element + for (const auto& el : *j.m_value.array) + { + to_msgpack_internal(el, v); + } + break; + } + + case value_t::object: + { + const auto N = j.m_value.object->size(); + if (N <= 15) + { + // fixmap + v.push_back(static_cast(0x80 | (N & 0xf))); + } + else if (N <= 65535) + { + // map 16 + v.push_back(0xde); + add_to_vector(v, 2, N); + } + else if (N <= 4294967295) + { + // map 32 + v.push_back(0xdf); + add_to_vector(v, 4, N); + } + + // append each element + for (const auto& el : *j.m_value.object) + { + to_msgpack_internal(el.first, v); + to_msgpack_internal(el.second, v); + } + break; + } + + default: + { + break; + } + } + } + + /*! + @brief create a CBOR serialization of a given JSON value + + This is a straightforward implementation of the CBOR specification. + + @param[in] j JSON value to serialize + @param[in,out] v byte vector to write the serialization to + + @sa https://tools.ietf.org/html/rfc7049 + */ + static void to_cbor_internal(const basic_json& j, std::vector& v) + { + switch (j.type()) + { + case value_t::null: + { + v.push_back(0xf6); + break; + } + + case value_t::boolean: + { + v.push_back(j.m_value.boolean ? 0xf5 : 0xf4); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // CBOR does not differentiate between positive signed + // integers and unsigned integers. Therefore, we used the + // code from the value_t::number_unsigned case here. + if (j.m_value.number_integer <= 0x17) + { + add_to_vector(v, 1, j.m_value.number_integer); + } + else if (j.m_value.number_integer <= UINT8_MAX) + { + v.push_back(0x18); + // one-byte uint8_t + add_to_vector(v, 1, j.m_value.number_integer); + } + else if (j.m_value.number_integer <= UINT16_MAX) + { + v.push_back(0x19); + // two-byte uint16_t + add_to_vector(v, 2, j.m_value.number_integer); + } + else if (j.m_value.number_integer <= UINT32_MAX) + { + v.push_back(0x1a); + // four-byte uint32_t + add_to_vector(v, 4, j.m_value.number_integer); + } + else if (j.m_value.number_integer <= UINT64_MAX) + { + v.push_back(0x1b); + // eight-byte uint64_t + add_to_vector(v, 8, j.m_value.number_integer); + } + } + else + { + // The conversions below encode the sign in the first byte, + // and the value is converted to a positive number. + const auto positive_number = -1 - j.m_value.number_integer; + if (j.m_value.number_integer >= -24) + { + v.push_back(static_cast(0x20 + positive_number)); + } + else if (positive_number <= UINT8_MAX) + { + // int 8 + v.push_back(0x38); + add_to_vector(v, 1, positive_number); + } + else if (positive_number <= UINT16_MAX) + { + // int 16 + v.push_back(0x39); + add_to_vector(v, 2, positive_number); + } + else if (positive_number <= UINT32_MAX) + { + // int 32 + v.push_back(0x3a); + add_to_vector(v, 4, positive_number); + } + else if (positive_number <= UINT64_MAX) + { + // int 64 + v.push_back(0x3b); + add_to_vector(v, 8, positive_number); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned <= 0x17) + { + v.push_back(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= 0xff) + { + v.push_back(0x18); + // one-byte uint8_t + add_to_vector(v, 1, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= 0xffff) + { + v.push_back(0x19); + // two-byte uint16_t + add_to_vector(v, 2, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= 0xffffffff) + { + v.push_back(0x1a); + // four-byte uint32_t + add_to_vector(v, 4, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= 0xffffffffffffffff) + { + v.push_back(0x1b); + // eight-byte uint64_t + add_to_vector(v, 8, j.m_value.number_unsigned); + } + break; + } + + case value_t::number_float: + { + // Double-Precision Float + v.push_back(0xfb); + const uint8_t* helper = reinterpret_cast(&(j.m_value.number_float)); + for (size_t i = 0; i < 8; ++i) + { + v.push_back(helper[7 - i]); + } + break; + } + + case value_t::string: + { + const auto N = j.m_value.string->size(); + if (N <= 0x17) + { + v.push_back(0x60 + N); // 1 byte for string + size + } + else if (N <= 0xff) + { + v.push_back(0x78); // one-byte uint8_t for N + add_to_vector(v, 1, N); + } + else if (N <= 0xffff) + { + v.push_back(0x79); // two-byte uint16_t for N + add_to_vector(v, 2, N); + } + else if (N <= 0xffffffff) + { + v.push_back(0x7a); // four-byte uint32_t for N + add_to_vector(v, 4, N); + } + // LCOV_EXCL_START + else if (N <= 0xffffffffffffffff) + { + v.push_back(0x7b); // eight-byte uint64_t for N + add_to_vector(v, 8, N); + } + // LCOV_EXCL_STOP + + // append string + std::copy(j.m_value.string->begin(), j.m_value.string->end(), + std::back_inserter(v)); + break; + } + + case value_t::array: + { + const auto N = j.m_value.array->size(); + if (N <= 0x17) + { + v.push_back(0x80 + N); // 1 byte for array + size + } + else if (N <= 0xff) + { + v.push_back(0x98); // one-byte uint8_t for N + add_to_vector(v, 1, N); + } + else if (N <= 0xffff) + { + v.push_back(0x99); // two-byte uint16_t for N + add_to_vector(v, 2, N); + } + else if (N <= 0xffffffff) + { + v.push_back(0x9a); // four-byte uint32_t for N + add_to_vector(v, 4, N); + } + // LCOV_EXCL_START + else if (N <= 0xffffffffffffffff) + { + v.push_back(0x9b); // eight-byte uint64_t for N + add_to_vector(v, 8, N); + } + // LCOV_EXCL_STOP + + // append each element + for (const auto& el : *j.m_value.array) + { + to_cbor_internal(el, v); + } + break; + } + + case value_t::object: + { + const auto N = j.m_value.object->size(); + if (N <= 0x17) + { + v.push_back(0xa0 + N); // 1 byte for object + size + } + else if (N <= 0xff) + { + v.push_back(0xb8); + add_to_vector(v, 1, N); // one-byte uint8_t for N + } + else if (N <= 0xffff) + { + v.push_back(0xb9); + add_to_vector(v, 2, N); // two-byte uint16_t for N + } + else if (N <= 0xffffffff) + { + v.push_back(0xba); + add_to_vector(v, 4, N); // four-byte uint32_t for N + } + // LCOV_EXCL_START + else if (N <= 0xffffffffffffffff) + { + v.push_back(0xbb); + add_to_vector(v, 8, N); // eight-byte uint64_t for N + } + // LCOV_EXCL_STOP + + // append each element + for (const auto& el : *j.m_value.object) + { + to_cbor_internal(el.first, v); + to_cbor_internal(el.second, v); + } + break; + } + + default: + { + break; + } + } + } + + /*! + @brief create a JSON value from a given MessagePack vector + + @param[in] v MessagePack serialization + @param[in] idx byte index to start reading from @a v + + @return deserialized JSON value + + @throw std::invalid_argument if unsupported features from MessagePack were + used in the given vector @a v or if the input is not valid MessagePack + @throw std::out_of_range if the given vector ends prematurely + + @sa https://github.com/msgpack/msgpack/blob/master/spec.md + */ + static basic_json from_msgpack_internal(const std::vector& v, size_t& idx) + { + // store and increment index + const size_t current_idx = idx++; + + if (v[current_idx] <= 0xbf) + { + if (v[current_idx] <= 0x7f) // positive fixint + { + return v[current_idx]; + } + else if (v[current_idx] <= 0x8f) // fixmap + { + basic_json result = value_t::object; + const size_t len = v[current_idx] & 0x0f; + for (size_t i = 0; i < len; ++i) + { + std::string key = from_msgpack_internal(v, idx); + result[key] = from_msgpack_internal(v, idx); + } + return result; + } + else if (v[current_idx] <= 0x9f) // fixarray + { + basic_json result = value_t::array; + const size_t len = v[current_idx] & 0x0f; + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_msgpack_internal(v, idx)); + } + return result; + } + else // fixstr + { + const size_t len = v[current_idx] & 0x1f; + const size_t offset = current_idx + 1; + idx += len; // skip content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + } + else if (v[current_idx] >= 0xe0) // negative fixint + { + return static_cast(v[current_idx]); + } + else + { + switch (v[current_idx]) + { + case 0xc0: // nil + { + return value_t::null; + } + + case 0xc2: // false + { + return false; + } + + case 0xc3: // true + { + return true; + } + + case 0xca: // float 32 + { + // copy bytes in reverse order into the double variable + float res; + for (size_t byte = 0; byte < sizeof(float); ++byte) + { + reinterpret_cast(&res)[sizeof(float) - byte - 1] = v[current_idx + 1 + byte]; + } + idx += sizeof(float); // skip content bytes + return res; + } + + case 0xcb: // float 64 + { + // copy bytes in reverse order into the double variable + double res; + for (size_t byte = 0; byte < sizeof(double); ++byte) + { + reinterpret_cast(&res)[sizeof(double) - byte - 1] = v[current_idx + 1 + byte]; + } + idx += sizeof(double); // skip content bytes + return res; + } + + case 0xcc: // uint 8 + { + idx += 1; // skip content byte + return get_from_vector(v, current_idx); + } + + case 0xcd: // uint 16 + { + idx += 2; // skip 2 content bytes + return get_from_vector(v, current_idx); + } + + case 0xce: // uint 32 + { + idx += 4; // skip 4 content bytes + return get_from_vector(v, current_idx); + } + + case 0xcf: // uint 64 + { + idx += 8; // skip 8 content bytes + return get_from_vector(v, current_idx); + } + + case 0xd0: // int 8 + { + idx += 1; // skip content byte + return get_from_vector(v, current_idx); + } + + case 0xd1: // int 16 + { + idx += 2; // skip 2 content bytes + return get_from_vector(v, current_idx); + } + + case 0xd2: // int 32 + { + idx += 4; // skip 4 content bytes + return get_from_vector(v, current_idx); + } + + case 0xd3: // int 64 + { + idx += 8; // skip 8 content bytes + return get_from_vector(v, current_idx); + } + + case 0xd9: // str 8 + { + const auto len = get_from_vector(v, current_idx); + const size_t offset = current_idx + 2; + idx += len + 1; // skip size byte + content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0xda: // str 16 + { + const auto len = get_from_vector(v, current_idx); + const size_t offset = current_idx + 3; + idx += len + 2; // skip 2 size bytes + content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0xdb: // str 32 + { + const auto len = get_from_vector(v, current_idx); + const size_t offset = current_idx + 5; + idx += len + 4; // skip 4 size bytes + content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0xdc: // array 16 + { + basic_json result = value_t::array; + const auto len = get_from_vector(v, current_idx); + idx += 2; // skip 2 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_msgpack_internal(v, idx)); + } + return result; + } + + case 0xdd: // array 32 + { + basic_json result = value_t::array; + const auto len = get_from_vector(v, current_idx); + idx += 4; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_msgpack_internal(v, idx)); + } + return result; + } + + case 0xde: // map 16 + { + basic_json result = value_t::object; + const auto len = get_from_vector(v, current_idx); + idx += 2; // skip 2 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_msgpack_internal(v, idx); + result[key] = from_msgpack_internal(v, idx); + } + return result; + } + + case 0xdf: // map 32 + { + basic_json result = value_t::object; + const auto len = get_from_vector(v, current_idx); + idx += 4; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_msgpack_internal(v, idx); + result[key] = from_msgpack_internal(v, idx); + } + return result; + } + + default: + { + throw std::invalid_argument("error parsing a msgpack @ " + std::to_string(current_idx)); + } + } + } + } + + /*! + @brief create a JSON value from a given CBOR vector + + @param[in] v CBOR serialization + @param[in] idx byte index to start reading from @a v + + @return deserialized JSON value + + @throw std::invalid_argument if unsupported features from CBOR were used in + the given vector @a v or if the input is not valid CBOR + @throw std::out_of_range if the given vector ends prematurely + + @sa https://tools.ietf.org/html/rfc7049 + */ + static basic_json from_cbor_internal(const std::vector& v, size_t& idx) + { + // store and increment index + const size_t current_idx = idx++; + + switch (v[current_idx]) + { + // Integer 0x00..0x17 (0..23) + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + case 0x0e: + case 0x0f: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + { + return v[current_idx]; + } + + case 0x18: // Unsigned integer (one-byte uint8_t follows) + { + idx += 1; // skip content byte + return get_from_vector(v, current_idx); + } + + case 0x19: // Unsigned integer (two-byte uint16_t follows) + { + idx += 2; // skip 2 content bytes + return get_from_vector(v, current_idx); + } + + case 0x1a: // Unsigned integer (four-byte uint32_t follows) + { + idx += 4; // skip 4 content bytes + return get_from_vector(v, current_idx); + } + + case 0x1b: // Unsigned integer (eight-byte uint64_t follows) + { + idx += 8; // skip 8 content bytes + return get_from_vector(v, current_idx); + } + + // Negative integer -1-0x00..-1-0x17 (-1..-24) + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2a: + case 0x2b: + case 0x2c: + case 0x2d: + case 0x2e: + case 0x2f: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + { + return static_cast(0x20 - 1 - v[current_idx]); + } + + case 0x38: // Negative integer (one-byte uint8_t follows) + { + idx += 1; // skip content byte + // must be uint8_t ! + return static_cast(-1) - get_from_vector(v, current_idx); + } + + case 0x39: // Negative integer -1-n (two-byte uint16_t follows) + { + idx += 2; // skip 2 content bytes + return static_cast(-1) - get_from_vector(v, current_idx); + } + + case 0x3a: // Negative integer -1-n (four-byte uint32_t follows) + { + idx += 4; // skip 4 content bytes + return static_cast(-1) - get_from_vector(v, current_idx); + } + + case 0x3b: // Negative integer -1-n (eight-byte uint64_t follows) + { + idx += 8; // skip 8 content bytes + return static_cast(-1) - get_from_vector(v, current_idx); + } + + // UTF-8 string (0x00..0x17 bytes follow) + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6a: + case 0x6b: + case 0x6c: + case 0x6d: + case 0x6e: + case 0x6f: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + { + const size_t len = v[current_idx] - 0x60; + const size_t offset = current_idx + 1; + idx += len; // skip content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + { + const auto len = get_from_vector(v, current_idx); + const size_t offset = current_idx + 2; + idx += len + 1; // skip size byte + content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + { + const auto len = get_from_vector(v, current_idx); + const size_t offset = current_idx + 3; + idx += len + 2; // skip 2 size bytes + content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x7a: // UTF-8 string (four-byte uint32_t for n follow) + { + const auto len = get_from_vector(v, current_idx); + const size_t offset = current_idx + 5; + idx += len + 4; // skip 4 size bytes + content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x7b: // UTF-8 string (eight-byte uint64_t for n follow) + { + const auto len = get_from_vector(v, current_idx); + const size_t offset = current_idx + 9; + idx += len + 8; // skip 8 size bytes + content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x7f: // UTF-8 string (indefinite length) + { + std::string result; + while (v[idx] != 0xff) + { + string_t s = from_cbor_internal(v, idx); + result += s; + } + // skip break byte (0xFF) + idx += 1; + return result; + } + + // array (0x00..0x17 data items follow) + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8a: + case 0x8b: + case 0x8c: + case 0x8d: + case 0x8e: + case 0x8f: + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + { + basic_json result = value_t::array; + const size_t len = v[current_idx] - 0x80; + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x98: // array (one-byte uint8_t for n follows) + { + basic_json result = value_t::array; + const auto len = get_from_vector(v, current_idx); + idx += 1; // skip 1 size byte + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x99: // array (two-byte uint16_t for n follow) + { + basic_json result = value_t::array; + const auto len = get_from_vector(v, current_idx); + idx += 2; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x9a: // array (four-byte uint32_t for n follow) + { + basic_json result = value_t::array; + const auto len = get_from_vector(v, current_idx); + idx += 4; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x9b: // array (eight-byte uint64_t for n follow) + { + basic_json result = value_t::array; + const auto len = get_from_vector(v, current_idx); + idx += 8; // skip 8 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x9f: // array (indefinite length) + { + basic_json result = value_t::array; + while (v[idx] != 0xff) + { + result.push_back(from_cbor_internal(v, idx)); + } + // skip break byte (0xFF) + idx += 1; + return result; + } + + // map (0x00..0x17 pairs of data items follow) + case 0xa0: + case 0xa1: + case 0xa2: + case 0xa3: + case 0xa4: + case 0xa5: + case 0xa6: + case 0xa7: + case 0xa8: + case 0xa9: + case 0xaa: + case 0xab: + case 0xac: + case 0xad: + case 0xae: + case 0xaf: + case 0xb0: + case 0xb1: + case 0xb2: + case 0xb3: + case 0xb4: + case 0xb5: + case 0xb6: + case 0xb7: + { + basic_json result = value_t::object; + const size_t len = v[current_idx] - 0xa0; + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xb8: // map (one-byte uint8_t for n follows) + { + basic_json result = value_t::object; + const auto len = get_from_vector(v, current_idx); + idx += 1; // skip 1 size byte + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xb9: // map (two-byte uint16_t for n follow) + { + basic_json result = value_t::object; + const auto len = get_from_vector(v, current_idx); + idx += 2; // skip 2 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xba: // map (four-byte uint32_t for n follow) + { + basic_json result = value_t::object; + const auto len = get_from_vector(v, current_idx); + idx += 4; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xbb: // map (eight-byte uint64_t for n follow) + { + basic_json result = value_t::object; + const auto len = get_from_vector(v, current_idx); + idx += 8; // skip 8 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xbf: // map (indefinite length) + { + basic_json result = value_t::object; + while (v[idx] != 0xff) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + // skip break byte (0xFF) + idx += 1; + return result; + } + + case 0xf4: // false + { + return false; + } + + case 0xf5: // true + { + return true; + } + + case 0xf6: // null + { + return value_t::null; + } + + case 0xf9: // Half-Precision Float (two-byte IEEE 754) + { + idx += 2; // skip two content bytes + + // code from RFC 7049, Appendix D, Figure 3: + // As half-precision floating-point numbers were only added to + // IEEE 754 in 2008, today's programming platforms often still + // only have limited support for them. It is very easy to + // include at least decoding support for them even without such + // support. An example of a small decoder for half-precision + // floating-point numbers in the C language is shown in Fig. 3. + const int half = (v[current_idx + 1] << 8) + v[current_idx + 2]; + const int exp = (half >> 10) & 0x1f; + const int mant = half & 0x3ff; + double val; + if (exp == 0) + { + val = std::ldexp(mant, -24); + } + else if (exp != 31) + { + val = std::ldexp(mant + 1024, exp - 25); + } + else + { + val = mant == 0 ? INFINITY : NAN; + } + return half & 0x8000 ? -val : val; + } + + case 0xfa: // Single-Precision Float (four-byte IEEE 754) + { + // copy bytes in reverse order into the float variable + float res; + for (size_t byte = 0; byte < sizeof(float); ++byte) + { + reinterpret_cast(&res)[sizeof(float) - byte - 1] = v[current_idx + 1 + byte]; + } + idx += sizeof(float); // skip content bytes + return res; + } + + case 0xfb: // Double-Precision Float (eight-byte IEEE 754) + { + // copy bytes in reverse order into the double variable + double res; + for (size_t byte = 0; byte < sizeof(double); ++byte) + { + reinterpret_cast(&res)[sizeof(double) - byte - 1] = v[current_idx + 1 + byte]; + } + idx += sizeof(double); // skip content bytes + return res; + } + + default: // anything else (0xFF is handled inside the other types) + { + throw std::invalid_argument("error parsing a CBOR @ " + std::to_string(current_idx) + ": " + std::to_string(v[current_idx])); + } + } + } + + public: + /*! + @brief create a MessagePack serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the MessagePack + serialization format. MessagePack is a binary serialization format which + aims to be more compact than JSON itself, yet more efficient to parse. + + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in MessagePack format.,to_msgpack} + + @sa http://msgpack.org + @sa @ref from_msgpack(const std::vector&) for the analogous + deserialization + @sa @ref to_cbor(const basic_json& for the related CBOR format + */ + static std::vector to_msgpack(const basic_json& j) + { + std::vector result; + to_msgpack_internal(j, result); + return result; + } + + /*! + @brief create a JSON value from a byte vector in MessagePack format + + Deserializes a given byte vector @a v to a JSON value using the MessagePack + serialization format. + + @param[in] v a byte vector in MessagePack format + @return deserialized JSON value + + @throw std::invalid_argument if unsupported features from MessagePack were + used in the given vector @a v or if the input is not valid MessagePack + @throw std::out_of_range if the given vector ends prematurely + + @complexity Linear in the size of the byte vector @a v. + + @liveexample{The example shows the deserialization of a byte vector in + MessagePack format to a JSON value.,from_msgpack} + + @sa http://msgpack.org + @sa @ref to_msgpack(const basic_json&) for the analogous serialization + @sa @ref from_cbor(const std::vector&) for the related CBOR format + */ + static basic_json from_msgpack(const std::vector& v) + { + size_t i = 0; + return from_msgpack_internal(v, i); + } + + /*! + @brief create a MessagePack serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the CBOR (Concise + Binary Object Representation) serialization format. CBOR is a binary + serialization format which aims to be more compact than JSON itself, yet + more efficient to parse. + + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in CBOR format.,to_cbor} + + @sa http://cbor.io + @sa @ref from_cbor(const std::vector&) for the analogous + deserialization + @sa @ref to_msgpack(const basic_json& for the related MessagePack format + */ + static std::vector to_cbor(const basic_json& j) + { + std::vector result; + to_cbor_internal(j, result); + return result; + } + + /*! + @brief create a JSON value from a byte vector in CBOR format + + Deserializes a given byte vector @a v to a JSON value using the CBOR + (Concise Binary Object Representation) serialization format. + + @param[in] v a byte vector in CBOR format + @return deserialized JSON value + + @throw std::invalid_argument if unsupported features from CBOR were used in + the given vector @a v or if the input is not valid MessagePack + @throw std::out_of_range if the given vector ends prematurely + + @complexity Linear in the size of the byte vector @a v. + + @liveexample{The example shows the deserialization of a byte vector in CBOR + format to a JSON value.,from_cbor} + + @sa http://cbor.io + @sa @ref to_cbor(const basic_json&) for the analogous serialization + @sa @ref from_msgpack(const std::vector&) for the related + MessagePack format + */ + static basic_json from_cbor(const std::vector& v) + { + size_t i = 0; + return from_cbor_internal(v, i); + } + + /// @} private: /////////////////////////// @@ -7884,6 +9310,7 @@ class basic_json re2c:define:YYMARKER = m_marker; re2c:define:YYFILL = "fill_line_buffer(@@); // LCOV_EXCL_LINE"; re2c:define:YYFILL:naked = 1; + re2c:yyfill:enable = 1; re2c:indent:string = " "; re2c:indent:top = 1; re2c:labelprefix = "basic_json_parser_"; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 782d5b53..f57ddf71 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,6 +6,7 @@ add_executable(${JSON_UNITTEST_TARGET_NAME} "src/unit-algorithms.cpp" "src/unit-allocator.cpp" "src/unit-capacity.cpp" + "src/unit-cbor.cpp" "src/unit-class_const_iterator.cpp" "src/unit-class_iterator.cpp" "src/unit-class_lexer.cpp" @@ -26,6 +27,7 @@ add_executable(${JSON_UNITTEST_TARGET_NAME} "src/unit-json_patch.cpp" "src/unit-json_pointer.cpp" "src/unit-modifiers.cpp" + "src/unit-msgpack.cpp" "src/unit-pointer_access.cpp" "src/unit-readme.cpp" "src/unit-reference_access.cpp" diff --git a/test/Makefile b/test/Makefile index ead1f073..488cec89 100644 --- a/test/Makefile +++ b/test/Makefile @@ -10,6 +10,7 @@ SOURCES = src/unit.cpp \ src/unit-algorithms.cpp \ src/unit-allocator.cpp \ src/unit-capacity.cpp \ + src/unit-cbor.cpp \ src/unit-class_const_iterator.cpp \ src/unit-class_iterator.cpp \ src/unit-class_lexer.cpp \ @@ -30,6 +31,7 @@ SOURCES = src/unit.cpp \ src/unit-json_patch.cpp \ src/unit-json_pointer.cpp \ src/unit-modifiers.cpp \ + src/unit-msgpack.cpp \ src/unit-pointer_access.cpp \ src/unit-readme.cpp \ src/unit-reference_access.cpp \ @@ -40,7 +42,20 @@ SOURCES = src/unit.cpp \ OBJECTS = $(SOURCES:.cpp=.o) -all: json_unit +TESTCASES = $(patsubst src/unit-%.cpp,test-%,$(wildcard src/unit-*.cpp)) + +############################################################################## +# main rules +############################################################################## + +all: $(TESTCASES) + +clean: + rm -fr json_unit $(OBJECTS) $(SOURCES:.cpp=.gcno) $(SOURCES:.cpp=.gcda) $(TESTCASES) + +############################################################################## +# single test file +############################################################################## json_unit: $(OBJECTS) ../src/json.hpp src/catch.hpp @echo "[CXXLD] $@" @@ -50,5 +65,16 @@ json_unit: $(OBJECTS) ../src/json.hpp src/catch.hpp @echo "[CXX] $@" @$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $< -o $@ -clean: - rm -fr json_unit $(OBJECTS) $(SOURCES:.cpp=.gcno) $(SOURCES:.cpp=.gcda) + +############################################################################## +# individual test cases +############################################################################## + +test-%: src/unit-%.cpp ../src/json.hpp src/catch.hpp + @echo "[CXXLD] $@" + @$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) -DCATCH_CONFIG_MAIN $< -o $@ + +TEST_PATTERN = "*" +TEST_PREFIX = "" +check: $(TESTCASES) + @cd .. ; for testcase in $(TESTCASES); do echo "Executing $$testcase..."; $(TEST_PREFIX)test/$$testcase $(TEST_PATTERN) || exit 1; done diff --git a/test/data/json.org/1.json.cbor b/test/data/json.org/1.json.cbor new file mode 100644 index 00000000..e31034c2 --- /dev/null +++ b/test/data/json.org/1.json.cbor @@ -0,0 +1 @@ +¡hglossary¢hGlossDiv¢iGlossList¡jGlossEntry§hGlossDef¢lGlossSeeAlso‚cGMLcXMLdparaxHA meta-markup language, used to create markup languages such as DocBook.hGlossSeefmarkupgAcronymdSGMLiGlossTermx$Standard Generalized Markup LanguagefAbbrevmISO 8879:1986fSortAsdSGMLbIDdSGMLetitleaSetitlepexample glossary \ No newline at end of file diff --git a/test/data/json.org/1.json.msgpack b/test/data/json.org/1.json.msgpack new file mode 100644 index 00000000..726875ce Binary files /dev/null and b/test/data/json.org/1.json.msgpack differ diff --git a/test/data/json.org/2.json.cbor b/test/data/json.org/2.json.cbor new file mode 100644 index 00000000..ddce5165 --- /dev/null +++ b/test/data/json.org/2.json.cbor @@ -0,0 +1 @@ +¡dmenu£epopup¡hmenuitemƒ¢gonclicknCreateNewDoc()evaluecNew¢gonclickiOpenDoc()evaluedOpen¢gonclickjCloseDoc()evalueeClosebiddfileevaluedFile \ No newline at end of file diff --git a/test/data/json.org/2.json.msgpack b/test/data/json.org/2.json.msgpack new file mode 100644 index 00000000..3321e1e2 --- /dev/null +++ b/test/data/json.org/2.json.msgpack @@ -0,0 +1 @@ +¤menuƒ¥popup¨menuitem“‚§onclick®CreateNewDoc()¥value£New‚§onclick©OpenDoc()¥value¤Open‚§onclickªCloseDoc()¥value¥Close¢id¤file¥value¤File \ No newline at end of file diff --git a/test/data/json.org/3.json.cbor b/test/data/json.org/3.json.cbor new file mode 100644 index 00000000..6bd57bf8 --- /dev/null +++ b/test/data/json.org/3.json.cbor @@ -0,0 +1 @@ +¡fwidget¤edebugbondtext¨gvOffsetdestyledbolddnameetext1ghOffsetúionMouseUpx)sun1.opacity = (sun1.opacity / 100) * 90;ddatajClick Hereialignmentfcenterdsize$fwindow¤ewidthôfheightôdnamekmain_windowetitlexSample Konfabulator Widgeteimage¥gvOffsetúcsrcnImages/Sun.pngialignmentfcenterdnamedsun1ghOffsetú \ No newline at end of file diff --git a/test/data/json.org/3.json.msgpack b/test/data/json.org/3.json.msgpack new file mode 100644 index 00000000..e261cd5f Binary files /dev/null and b/test/data/json.org/3.json.msgpack differ diff --git a/test/data/json.org/4.json.cbor b/test/data/json.org/4.json.cbor new file mode 100644 index 00000000..e994deeb --- /dev/null +++ b/test/data/json.org/4.json.cbor @@ -0,0 +1,4 @@ +¡gweb-app£oservlet-mapping¥jcofaxToolsh/tools/*hcofaxCDSa/kfileServleti/static/*jcofaxAdminh/admin/*jcofaxEmails/cofaxutil/aemail/*ftaglib¢otaglib-locationw/WEB-INF/tlds/cofax.tldjtaglib-uriicofax.tldgservlet…£lservlet-namehcofaxCDSjinit-param¸*ocachePagesStoredxsearchEngineListTemplatexforSearchEnginesList.htmxconfigGlossary:adminEmailmksm@pobox.comlmaxUrlLengthôrdataStoreTestQueryx"SET NOCOUNT ON;select test='test';sdefaultFileTemplatesarticleTemplate.htmpdataStoreLogFilex$/usr/local/tomcat/logs/datastore.logstemplateLoaderClassxorg.cofax.FilesTemplateLoaderndataStoreClassvorg.cofax.SqlDataStorepredirectionClassxorg.cofax.SqlRedirectionttemplateOverridePath`scacheTemplatesStore2ldataStoreUrlx;jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goonxsearchEngineFileTemplatetforSearchEngines.htmocachePagesTrackÈucachePackageTagsStoreÈmdataStoreNameecofaxqdataStorePasswordrdataStoreTestQueryfuseJSPôsdefaultListTemplateplistTemplate.htmxconfigGlossary:poweredByeCofaxmdataStoreUserbsaojspListTemplateplistTemplate.jspojspFileTemplatesarticleTemplate.jspqdataStoreMaxConnsdscachePagesDirtyRead +qcachePagesRefresh +scacheTemplatesTrackdwdataStoreConnUsageLimitdxconfigGlossary:installationAtpPhiladelphia, PAtsearchEngineRobotsDbqWEB-INF/robots.dbvtemplateProcessorClassxorg.cofax.WysiwygTemplatewcachePackageTagsRefresh. +Copyright (c) 2013-2016 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "catch.hpp" + +#include "json.hpp" +using nlohmann::json; + +#include + +TEST_CASE("CBOR") +{ + SECTION("individual values") + { + SECTION("discarded") + { + // discarded values are not serialized + json j = json::value_t::discarded; + const auto result = json::to_cbor(j); + CHECK(result.empty()); + } + + SECTION("null") + { + json j = nullptr; + std::vector expected = {0xf6}; + const auto result = json::to_cbor(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + + SECTION("boolean") + { + SECTION("true") + { + json j = true; + std::vector expected = {0xf5}; + const auto result = json::to_cbor(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + + SECTION("false") + { + json j = false; + std::vector expected = {0xf4}; + const auto result = json::to_cbor(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + } + + SECTION("number") + { + SECTION("signed") + { + SECTION("-9223372036854775808..-4294967297") + { + std::vector numbers; + numbers.push_back(INT64_MIN); + numbers.push_back(-1000000000000000000); + numbers.push_back(-100000000000000000); + numbers.push_back(-10000000000000000); + numbers.push_back(-1000000000000000); + numbers.push_back(-100000000000000); + numbers.push_back(-10000000000000); + numbers.push_back(-1000000000000); + numbers.push_back(-100000000000); + numbers.push_back(-10000000000); + numbers.push_back(-4294967297); + for (auto i : numbers) + { + CAPTURE(i); + + // create JSON value with integer number + json j = i; + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast(0x3b)); + uint64_t positive = static_cast(-1 - i); + expected.push_back(static_cast((positive >> 56) & 0xff)); + expected.push_back(static_cast((positive >> 48) & 0xff)); + expected.push_back(static_cast((positive >> 40) & 0xff)); + expected.push_back(static_cast((positive >> 32) & 0xff)); + expected.push_back(static_cast((positive >> 24) & 0xff)); + expected.push_back(static_cast((positive >> 16) & 0xff)); + expected.push_back(static_cast((positive >> 8) & 0xff)); + expected.push_back(static_cast(positive & 0xff)); + + // compare result + size + const auto result = json::to_cbor(j); + CHECK(result == expected); + CHECK(result.size() == 9); + + // check individual bytes + CHECK(result[0] == 0x3b); + uint64_t restored = static_cast((static_cast(result[1]) << 070) + + (static_cast(result[2]) << 060) + + (static_cast(result[3]) << 050) + + (static_cast(result[4]) << 040) + + (static_cast(result[5]) << 030) + + (static_cast(result[6]) << 020) + + (static_cast(result[7]) << 010) + + static_cast(result[8])); + CHECK(restored == positive); + CHECK(-1 - restored == i); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + } + + SECTION("-4294967296..-65537") + { + std::vector numbers; + numbers.push_back(-65537); + numbers.push_back(-100000); + numbers.push_back(-1000000); + numbers.push_back(-10000000); + numbers.push_back(-100000000); + numbers.push_back(-1000000000); + numbers.push_back(-4294967296); + for (auto i : numbers) + { + CAPTURE(i); + + // create JSON value with integer number + json j = i; + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast(0x3a)); + uint32_t positive = static_cast(static_cast(-1 - i) & 0x00000000ffffffff); + expected.push_back(static_cast((positive >> 24) & 0xff)); + expected.push_back(static_cast((positive >> 16) & 0xff)); + expected.push_back(static_cast((positive >> 8) & 0xff)); + expected.push_back(static_cast(positive & 0xff)); + + // compare result + size + const auto result = json::to_cbor(j); + CHECK(result == expected); + CHECK(result.size() == 5); + + // check individual bytes + CHECK(result[0] == 0x3a); + uint32_t restored = static_cast((static_cast(result[1]) << 030) + + (static_cast(result[2]) << 020) + + (static_cast(result[3]) << 010) + + static_cast(result[4])); + CHECK(restored == positive); + CHECK(-1ll - restored == i); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + } + + SECTION("-65536..-257") + { + for (int32_t i = -65536; i <= -257; ++i) + { + CAPTURE(i); + + // create JSON value with integer number + json j = i; + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast(0x39)); + uint16_t positive = static_cast(-1 - i); + expected.push_back(static_cast((positive >> 8) & 0xff)); + expected.push_back(static_cast(positive & 0xff)); + + // compare result + size + const auto result = json::to_cbor(j); + CHECK(result == expected); + CHECK(result.size() == 3); + + // check individual bytes + CHECK(result[0] == 0x39); + uint16_t restored = static_cast(result[1]) * 256 + static_cast(result[2]); + CHECK(restored == positive); + CHECK(-1 - restored == i); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + } + + SECTION("-9263 (int 16)") + { + json j = -9263; + std::vector expected = {0x39, 0x24, 0x2e}; + + const auto result = json::to_cbor(j); + CHECK(result == expected); + + int16_t restored = -1 - ((result[1] << 8) + result[2]); + CHECK(restored == -9263); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + + SECTION("-256..-24") + { + for (auto i = -256; i < -24; ++i) + { + CAPTURE(i); + + // create JSON value with integer number + json j = i; + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(0x38); + expected.push_back(static_cast(-1 - i)); + + // compare result + size + const auto result = json::to_cbor(j); + CHECK(result == expected); + CHECK(result.size() == 2); + + // check individual bytes + CHECK(result[0] == 0x38); + CHECK(static_cast(-1 - result[1]) == i); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + } + + SECTION("-24..-1") + { + for (auto i = -24; i <= -1; ++i) + { + CAPTURE(i); + + // create JSON value with integer number + json j = i; + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(0x20 - 1 - static_cast(i)); + + // compare result + size + const auto result = json::to_cbor(j); + CHECK(result == expected); + CHECK(result.size() == 1); + + // check individual bytes + CHECK(static_cast(0x20 - 1 - result[0]) == i); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + } + + SECTION("0..23") + { + for (size_t i = 0; i <= 23; ++i) + { + CAPTURE(i); + + // create JSON value with integer number + json j = -1; + j.get_ref() = static_cast(i); + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast(i)); + + // compare result + size + const auto result = json::to_cbor(j); + CHECK(result == expected); + CHECK(result.size() == 1); + + // check individual bytes + CHECK(result[0] == i); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + } + + SECTION("24..255") + { + for (size_t i = 24; i <= 255; ++i) + { + CAPTURE(i); + + // create JSON value with integer number + json j = -1; + j.get_ref() = static_cast(i); + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast(0x18)); + expected.push_back(static_cast(i)); + + // compare result + size + const auto result = json::to_cbor(j); + CHECK(result == expected); + CHECK(result.size() == 2); + + // check individual bytes + CHECK(result[0] == 0x18); + CHECK(result[1] == i); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + } + + SECTION("256..65535") + { + for (size_t i = 256; i <= 65535; ++i) + { + CAPTURE(i); + + // create JSON value with integer number + json j = -1; + j.get_ref() = static_cast(i); + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast(0x19)); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_cbor(j); + CHECK(result == expected); + CHECK(result.size() == 3); + + // check individual bytes + CHECK(result[0] == 0x19); + uint16_t restored = static_cast(result[1]) * 256 + static_cast(result[2]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + } + + SECTION("65536..4294967295") + { + for (uint32_t i : + { + 65536u, 77777u, 1048576u + }) + { + CAPTURE(i); + + // create JSON value with integer number + json j = -1; + j.get_ref() = static_cast(i); + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(0x1a); + expected.push_back(static_cast((i >> 24) & 0xff)); + expected.push_back(static_cast((i >> 16) & 0xff)); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_cbor(j); + CHECK(result == expected); + CHECK(result.size() == 5); + + // check individual bytes + CHECK(result[0] == 0x1a); + uint32_t restored = static_cast((static_cast(result[1]) << 030) + + (static_cast(result[2]) << 020) + + (static_cast(result[3]) << 010) + + static_cast(result[4])); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + } + + SECTION("4294967296..4611686018427387903") + { + for (uint64_t i : + { + 4294967296ul, 4611686018427387903ul + }) + { + CAPTURE(i); + + // create JSON value with integer number + json j = -1; + j.get_ref() = static_cast(i); + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(0x1b); + expected.push_back(static_cast((i >> 070) & 0xff)); + expected.push_back(static_cast((i >> 060) & 0xff)); + expected.push_back(static_cast((i >> 050) & 0xff)); + expected.push_back(static_cast((i >> 040) & 0xff)); + expected.push_back(static_cast((i >> 030) & 0xff)); + expected.push_back(static_cast((i >> 020) & 0xff)); + expected.push_back(static_cast((i >> 010) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_cbor(j); + CHECK(result == expected); + CHECK(result.size() == 9); + + // check individual bytes + CHECK(result[0] == 0x1b); + uint64_t restored = static_cast((static_cast(result[1]) << 070) + + (static_cast(result[2]) << 060) + + (static_cast(result[3]) << 050) + + (static_cast(result[4]) << 040) + + (static_cast(result[5]) << 030) + + (static_cast(result[6]) << 020) + + (static_cast(result[7]) << 010) + + static_cast(result[8])); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + } + + /* + SECTION("-32768..-129 (int 16)") + { + for (int16_t i = -32768; i <= -129; ++i) + { + CAPTURE(i); + + // create JSON value with integer number + json j = i; + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(0xd1); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_msgpack(j); + CHECK(result == expected); + CHECK(result.size() == 3); + + // check individual bytes + CHECK(result[0] == 0xd1); + int16_t restored = (result[1] << 8) + result[2]; + CHECK(restored == i); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + */ + } + + SECTION("unsigned") + { + SECTION("0..23 (Integer)") + { + for (size_t i = 0; i <= 23; ++i) + { + CAPTURE(i); + + // create JSON value with unsigned integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast(i)); + + // compare result + size + const auto result = json::to_cbor(j); + CHECK(result == expected); + CHECK(result.size() == 1); + + // check individual bytes + CHECK(result[0] == i); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + } + + SECTION("24..255 (one-byte uint8_t)") + { + for (size_t i = 24; i <= 255; ++i) + { + CAPTURE(i); + + // create JSON value with unsigned integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back(0x18); + expected.push_back(static_cast(i)); + + // compare result + size + const auto result = json::to_cbor(j); + CHECK(result == expected); + CHECK(result.size() == 2); + + // check individual bytes + CHECK(result[0] == 0x18); + uint8_t restored = static_cast(result[1]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + } + + SECTION("256..65535 (two-byte uint16_t)") + { + for (size_t i = 256; i <= 65535; ++i) + { + CAPTURE(i); + + // create JSON value with unsigned integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back(0x19); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_cbor(j); + CHECK(result == expected); + CHECK(result.size() == 3); + + // check individual bytes + CHECK(result[0] == 0x19); + uint16_t restored = static_cast(result[1]) * 256 + static_cast(result[2]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + } + + SECTION("65536..4294967295 (four-byte uint32_t)") + { + for (uint32_t i : + { + 65536u, 77777u, 1048576u + }) + { + CAPTURE(i); + + // create JSON value with unsigned integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back(0x1a); + expected.push_back(static_cast((i >> 24) & 0xff)); + expected.push_back(static_cast((i >> 16) & 0xff)); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_cbor(j); + CHECK(result == expected); + CHECK(result.size() == 5); + + // check individual bytes + CHECK(result[0] == 0x1a); + uint32_t restored = static_cast((static_cast(result[1]) << 030) + + (static_cast(result[2]) << 020) + + (static_cast(result[3]) << 010) + + static_cast(result[4])); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + } + + SECTION("4294967296..4611686018427387903 (eight-byte uint64_t)") + { + for (uint64_t i : + { + 4294967296ul, 4611686018427387903ul + }) + { + CAPTURE(i); + + // create JSON value with integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back(0x1b); + expected.push_back(static_cast((i >> 070) & 0xff)); + expected.push_back(static_cast((i >> 060) & 0xff)); + expected.push_back(static_cast((i >> 050) & 0xff)); + expected.push_back(static_cast((i >> 040) & 0xff)); + expected.push_back(static_cast((i >> 030) & 0xff)); + expected.push_back(static_cast((i >> 020) & 0xff)); + expected.push_back(static_cast((i >> 010) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_cbor(j); + CHECK(result == expected); + CHECK(result.size() == 9); + + // check individual bytes + CHECK(result[0] == 0x1b); + uint64_t restored = static_cast((static_cast(result[1]) << 070) + + (static_cast(result[2]) << 060) + + (static_cast(result[3]) << 050) + + (static_cast(result[4]) << 040) + + (static_cast(result[5]) << 030) + + (static_cast(result[6]) << 020) + + (static_cast(result[7]) << 010) + + static_cast(result[8])); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + } + } + + SECTION("float") + { + SECTION("3.1415925") + { + double v = 3.1415925; + json j = v; + std::vector expected = + { + 0xfb, 0x40, 0x09, 0x21, 0xfb, 0x3f, 0xa6, 0xde, 0xfc + }; + const auto result = json::to_cbor(j); + CHECK(result == expected); + + // restore value (reverse array for endianess) + double restored; + std::reverse(expected.begin(), expected.end()); + memcpy(&restored, expected.data(), sizeof(double)); + CHECK(restored == v); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + } + + SECTION("half-precision float (edge cases)") + { + SECTION("infinity") + { + json j = json::from_cbor(std::vector({0xf9, 0x7c, 0x00})); + CHECK(j == nullptr); + } + + SECTION("NaN") + { + json j = json::from_cbor(std::vector({0xf9, 0x7c, 0x01})); + CHECK(j == nullptr); + } + } + } + + SECTION("string") + { + SECTION("N = 0..23") + { + for (size_t N = 0; N <= 0x17; ++N) + { + CAPTURE(N); + + // create JSON value with string containing of N * 'x' + const auto s = std::string(N, 'x'); + json j = s; + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast(0x60 + N)); + for (size_t i = 0; i < N; ++i) + { + expected.push_back('x'); + } + + // compare result + size + const auto result = json::to_cbor(j); + CHECK(result == expected); + CHECK(result.size() == N + 1); + // check that no null byte is appended + if (N > 0) + { + CHECK(result.back() != '\x00'); + } + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + } + + SECTION("N = 24..255") + { + for (size_t N = 24; N <= 255; ++N) + { + CAPTURE(N); + + // create JSON value with string containing of N * 'x' + const auto s = std::string(N, 'x'); + json j = s; + + // create expected byte vector + std::vector expected; + expected.push_back(0x78); + expected.push_back(static_cast(N)); + for (size_t i = 0; i < N; ++i) + { + expected.push_back('x'); + } + + // compare result + size + const auto result = json::to_cbor(j); + CHECK(result == expected); + CHECK(result.size() == N + 2); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + } + + SECTION("N = 256..65535") + { + for (size_t N : + { + 256u, 999u, 1025u, 3333u, 2048u, 65535u + }) + { + CAPTURE(N); + + // create JSON value with string containing of N * 'x' + const auto s = std::string(N, 'x'); + json j = s; + + // create expected byte vector (hack: create string first) + std::vector expected(N, 'x'); + // reverse order of commands, because we insert at begin() + expected.insert(expected.begin(), static_cast(N & 0xff)); + expected.insert(expected.begin(), static_cast((N >> 8) & 0xff)); + expected.insert(expected.begin(), 0x79); + + // compare result + size + const auto result = json::to_cbor(j); + CHECK(result == expected); + CHECK(result.size() == N + 3); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + } + + SECTION("N = 65536..4294967295") + { + for (size_t N : + { + 65536u, 77777u, 1048576u + }) + { + CAPTURE(N); + + // create JSON value with string containing of N * 'x' + const auto s = std::string(N, 'x'); + json j = s; + + // create expected byte vector (hack: create string first) + std::vector expected(N, 'x'); + // reverse order of commands, because we insert at begin() + expected.insert(expected.begin(), static_cast(N & 0xff)); + expected.insert(expected.begin(), static_cast((N >> 8) & 0xff)); + expected.insert(expected.begin(), static_cast((N >> 16) & 0xff)); + expected.insert(expected.begin(), static_cast((N >> 24) & 0xff)); + expected.insert(expected.begin(), 0x7a); + + // compare result + size + const auto result = json::to_cbor(j); + CHECK(result == expected); + CHECK(result.size() == N + 5); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + } + } + + SECTION("array") + { + SECTION("empty") + { + json j = json::array(); + std::vector expected = {0x80}; + const auto result = json::to_cbor(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + + SECTION("[null]") + { + json j = {nullptr}; + std::vector expected = {0x81, 0xf6}; + const auto result = json::to_cbor(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + + SECTION("[1,2,3,4,5]") + { + json j = json::parse("[1,2,3,4,5]"); + std::vector expected = {0x85, 0x01, 0x02, 0x03, 0x04, 0x05}; + const auto result = json::to_cbor(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + + SECTION("[[[[]]]]") + { + json j = json::parse("[[[[]]]]"); + std::vector expected = {0x81, 0x81, 0x81, 0x80}; + const auto result = json::to_cbor(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + + SECTION("array with uint16_t elements") + { + json j(257, nullptr); + std::vector expected(j.size() + 3, 0xf6); // all null + expected[0] = 0x99; // array 16 bit + expected[1] = 0x01; // size (0x0101), byte 0 + expected[2] = 0x01; // size (0x0101), byte 1 + const auto result = json::to_cbor(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + + SECTION("array with uint32_t elements") + { + json j(65793, nullptr); + std::vector expected(j.size() + 5, 0xf6); // all null + expected[0] = 0x9a; // array 32 bit + expected[1] = 0x00; // size (0x00010101), byte 0 + expected[2] = 0x01; // size (0x00010101), byte 1 + expected[3] = 0x01; // size (0x00010101), byte 2 + expected[4] = 0x01; // size (0x00010101), byte 3 + const auto result = json::to_cbor(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + + /* + SECTION("array with uint64_t elements") + { + json j(4294967296, nullptr); + std::vector expected(j.size() + 9, 0xf6); // all null + expected[0] = 0x9b; // array 64 bit + expected[1] = 0x00; // size (0x0000000100000000), byte 0 + expected[2] = 0x00; // size (0x0000000100000000), byte 1 + expected[3] = 0x00; // size (0x0000000100000000), byte 2 + expected[4] = 0x01; // size (0x0000000100000000), byte 3 + expected[5] = 0x00; // size (0x0000000100000000), byte 4 + expected[6] = 0x00; // size (0x0000000100000000), byte 5 + expected[7] = 0x00; // size (0x0000000100000000), byte 6 + expected[8] = 0x00; // size (0x0000000100000000), byte 7 + const auto result = json::to_cbor(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + */ + } + + SECTION("object") + { + SECTION("empty") + { + json j = json::object(); + std::vector expected = {0xa0}; + const auto result = json::to_cbor(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + + SECTION("{\"\":null}") + { + json j = {{"", nullptr}}; + std::vector expected = {0xa1, 0x60, 0xf6}; + const auto result = json::to_cbor(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + + SECTION("{\"a\": {\"b\": {\"c\": {}}}}") + { + json j = json::parse("{\"a\": {\"b\": {\"c\": {}}}}"); + std::vector expected = + { + 0xa1, 0x61, 0x61, 0xa1, 0x61, 0x62, 0xa1, 0x61, 0x63, 0xa0 + }; + const auto result = json::to_cbor(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + + SECTION("object with uint8_t elements") + { + json j; + for (auto i = 0; i < 255; ++i) + { + // format i to a fixed width of 5 + // each entry will need 7 bytes: 6 for string, 1 for null + std::stringstream ss; + ss << std::setw(5) << std::setfill('0') << i; + j.emplace(ss.str(), nullptr); + } + + const auto result = json::to_cbor(j); + + // Checking against an expected vector byte by byte is + // difficult, because no assumption on the order of key/value + // pairs are made. We therefore only check the prefix (type and + // size and the overall size. The rest is then handled in the + // roundtrip check. + CHECK(result.size() == 1787); // 1 type, 1 size, 255*7 content + CHECK(result[0] == 0xb8); // map 8 bit + CHECK(result[1] == 0xff); // size byte (0xff) + // roundtrip + CHECK(json::from_cbor(result) == j); + } + + SECTION("object with uint16_t elements") + { + json j; + for (auto i = 0; i < 256; ++i) + { + // format i to a fixed width of 5 + // each entry will need 7 bytes: 6 for string, 1 for null + std::stringstream ss; + ss << std::setw(5) << std::setfill('0') << i; + j.emplace(ss.str(), nullptr); + } + + const auto result = json::to_cbor(j); + + // Checking against an expected vector byte by byte is + // difficult, because no assumption on the order of key/value + // pairs are made. We therefore only check the prefix (type and + // size and the overall size. The rest is then handled in the + // roundtrip check. + CHECK(result.size() == 1795); // 1 type, 2 size, 256*7 content + CHECK(result[0] == 0xb9); // map 16 bit + CHECK(result[1] == 0x01); // byte 0 of size (0x0100) + CHECK(result[2] == 0x00); // byte 1 of size (0x0100) + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + + SECTION("object with uint32_t elements") + { + json j; + for (auto i = 0; i < 65536; ++i) + { + // format i to a fixed width of 5 + // each entry will need 7 bytes: 6 for string, 1 for null + std::stringstream ss; + ss << std::setw(5) << std::setfill('0') << i; + j.emplace(ss.str(), nullptr); + } + + const auto result = json::to_cbor(j); + + // Checking against an expected vector byte by byte is + // difficult, because no assumption on the order of key/value + // pairs are made. We therefore only check the prefix (type and + // size and the overall size. The rest is then handled in the + // roundtrip check. + CHECK(result.size() == 458757); // 1 type, 4 size, 65536*7 content + CHECK(result[0] == 0xba); // map 32 bit + CHECK(result[1] == 0x00); // byte 0 of size (0x00010000) + CHECK(result[2] == 0x01); // byte 1 of size (0x00010000) + CHECK(result[3] == 0x00); // byte 2 of size (0x00010000) + CHECK(result[4] == 0x00); // byte 3 of size (0x00010000) + + // roundtrip + CHECK(json::from_cbor(result) == j); + } + } + } + + SECTION("additonal deserialization") + { + SECTION("0x7b (string)") + { + std::vector given = {0x7b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x61 + }; + json j = json::from_cbor(given); + CHECK(j == "a"); + } + + SECTION("0x9b (array)") + { + std::vector given = {0x9b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0xf4 + }; + json j = json::from_cbor(given); + CHECK(j == json::parse("[false]")); + } + + SECTION("0xbb (map)") + { + std::vector given = {0xbb, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x60, 0xf4 + }; + json j = json::from_cbor(given); + CHECK(j == json::parse("{\"\": false}")); + } + } + + SECTION("errors") + { + SECTION("too short byte vector") + { + CHECK_THROWS_AS(json::from_cbor(std::vector({0x18})), std::out_of_range); + CHECK_THROWS_AS(json::from_cbor(std::vector({0x19})), std::out_of_range); + CHECK_THROWS_AS(json::from_cbor(std::vector({0x19, 0x00})), std::out_of_range); + CHECK_THROWS_AS(json::from_cbor(std::vector({0x1a})), std::out_of_range); + CHECK_THROWS_AS(json::from_cbor(std::vector({0x1a, 0x00})), std::out_of_range); + CHECK_THROWS_AS(json::from_cbor(std::vector({0x1a, 0x00, 0x00})), std::out_of_range); + CHECK_THROWS_AS(json::from_cbor(std::vector({0x1a, 0x00, 0x00, 0x00})), std::out_of_range); + CHECK_THROWS_AS(json::from_cbor(std::vector({0x1b})), std::out_of_range); + CHECK_THROWS_AS(json::from_cbor(std::vector({0x1b, 0x00})), std::out_of_range); + CHECK_THROWS_AS(json::from_cbor(std::vector({0x1b, 0x00, 0x00})), std::out_of_range); + CHECK_THROWS_AS(json::from_cbor(std::vector({0x1b, 0x00, 0x00, 0x00})), std::out_of_range); + CHECK_THROWS_AS(json::from_cbor(std::vector({0x1b, 0x00, 0x00, 0x00, 0x00})), std::out_of_range); + CHECK_THROWS_AS(json::from_cbor(std::vector({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00})), std::out_of_range); + CHECK_THROWS_AS(json::from_cbor(std::vector({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})), std::out_of_range); + CHECK_THROWS_AS(json::from_cbor(std::vector({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})), std::out_of_range); + } + } +} + +// use this testcase outside [hide] to run it with Valgrind +TEST_CASE("single CBOR roundtrip") +{ + SECTION("sample.json") + { + std::string filename = "test/data/json_testsuite/sample.json"; + + // parse JSON file + std::ifstream f_json(filename); + json j1 = json::parse(f_json); + + // parse MessagePack file + std::ifstream f_cbor(filename + ".cbor", std::ios::binary); + std::vector packed((std::istreambuf_iterator(f_cbor)), + std::istreambuf_iterator()); + json j2; + CHECK_NOTHROW(j2 = json::from_cbor(packed)); + + // compare parsed JSON values + CHECK(j1 == j2); + } +} + +TEST_CASE("CBOR roundtrips", "[hide]") +{ + SECTION("input from flynn") + { + for (std::string filename : + { + "test/data/json_nlohmann_tests/all_unicode.json", + "test/data/json.org/1.json", + "test/data/json.org/2.json", + "test/data/json.org/3.json", + "test/data/json.org/4.json", + "test/data/json.org/5.json", + "test/data/json_roundtrip/roundtrip01.json", + "test/data/json_roundtrip/roundtrip02.json", + "test/data/json_roundtrip/roundtrip03.json", + "test/data/json_roundtrip/roundtrip04.json", + "test/data/json_roundtrip/roundtrip05.json", + "test/data/json_roundtrip/roundtrip06.json", + "test/data/json_roundtrip/roundtrip07.json", + "test/data/json_roundtrip/roundtrip08.json", + "test/data/json_roundtrip/roundtrip09.json", + "test/data/json_roundtrip/roundtrip10.json", + "test/data/json_roundtrip/roundtrip11.json", + "test/data/json_roundtrip/roundtrip12.json", + "test/data/json_roundtrip/roundtrip13.json", + "test/data/json_roundtrip/roundtrip14.json", + "test/data/json_roundtrip/roundtrip15.json", + "test/data/json_roundtrip/roundtrip16.json", + "test/data/json_roundtrip/roundtrip17.json", + "test/data/json_roundtrip/roundtrip18.json", + "test/data/json_roundtrip/roundtrip19.json", + "test/data/json_roundtrip/roundtrip20.json", + "test/data/json_roundtrip/roundtrip21.json", + "test/data/json_roundtrip/roundtrip22.json", + "test/data/json_roundtrip/roundtrip23.json", + "test/data/json_roundtrip/roundtrip24.json", + "test/data/json_roundtrip/roundtrip25.json", + "test/data/json_roundtrip/roundtrip26.json", + "test/data/json_roundtrip/roundtrip27.json", + "test/data/json_roundtrip/roundtrip28.json", + "test/data/json_roundtrip/roundtrip29.json", + "test/data/json_roundtrip/roundtrip30.json", + "test/data/json_roundtrip/roundtrip31.json", + "test/data/json_roundtrip/roundtrip32.json", + "test/data/json_testsuite/sample.json", // kills AppVeyor + "test/data/json_tests/pass1.json", + "test/data/json_tests/pass2.json", + "test/data/json_tests/pass3.json", + "test/data/regression/floats.json", + "test/data/regression/signed_ints.json", + "test/data/regression/unsigned_ints.json", + "test/data/regression/working_file.json", + "test/data/nst_json_testsuite/test_parsing/y_array_arraysWithSpaces.json", + "test/data/nst_json_testsuite/test_parsing/y_array_empty-string.json", + "test/data/nst_json_testsuite/test_parsing/y_array_empty.json", + "test/data/nst_json_testsuite/test_parsing/y_array_ending_with_newline.json", + "test/data/nst_json_testsuite/test_parsing/y_array_false.json", + "test/data/nst_json_testsuite/test_parsing/y_array_heterogeneous.json", + "test/data/nst_json_testsuite/test_parsing/y_array_null.json", + "test/data/nst_json_testsuite/test_parsing/y_array_with_1_and_newline.json", + "test/data/nst_json_testsuite/test_parsing/y_array_with_leading_space.json", + "test/data/nst_json_testsuite/test_parsing/y_array_with_several_null.json", + "test/data/nst_json_testsuite/test_parsing/y_array_with_trailing_space.json", + "test/data/nst_json_testsuite/test_parsing/y_number.json", + "test/data/nst_json_testsuite/test_parsing/y_number_0e+1.json", + "test/data/nst_json_testsuite/test_parsing/y_number_0e1.json", + "test/data/nst_json_testsuite/test_parsing/y_number_after_space.json", + "test/data/nst_json_testsuite/test_parsing/y_number_double_close_to_zero.json", + "test/data/nst_json_testsuite/test_parsing/y_number_double_huge_neg_exp.json", + "test/data/nst_json_testsuite/test_parsing/y_number_huge_exp.json", + "test/data/nst_json_testsuite/test_parsing/y_number_int_with_exp.json", + "test/data/nst_json_testsuite/test_parsing/y_number_minus_zero.json", + "test/data/nst_json_testsuite/test_parsing/y_number_negative_int.json", + "test/data/nst_json_testsuite/test_parsing/y_number_negative_one.json", + "test/data/nst_json_testsuite/test_parsing/y_number_negative_zero.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_capital_e.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_capital_e_neg_exp.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_capital_e_pos_exp.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_exponent.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_fraction_exponent.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_neg_exp.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_neg_overflow.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_pos_exponent.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_pos_overflow.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_underflow.json", + "test/data/nst_json_testsuite/test_parsing/y_number_simple_int.json", + "test/data/nst_json_testsuite/test_parsing/y_number_simple_real.json", + //"test/data/nst_json_testsuite/test_parsing/y_number_too_big_neg_int.json", + //"test/data/nst_json_testsuite/test_parsing/y_number_too_big_pos_int.json", + //"test/data/nst_json_testsuite/test_parsing/y_number_very_big_negative_int.json", + "test/data/nst_json_testsuite/test_parsing/y_object.json", + "test/data/nst_json_testsuite/test_parsing/y_object_basic.json", + "test/data/nst_json_testsuite/test_parsing/y_object_duplicated_key.json", + "test/data/nst_json_testsuite/test_parsing/y_object_duplicated_key_and_value.json", + "test/data/nst_json_testsuite/test_parsing/y_object_empty.json", + "test/data/nst_json_testsuite/test_parsing/y_object_empty_key.json", + "test/data/nst_json_testsuite/test_parsing/y_object_escaped_null_in_key.json", + "test/data/nst_json_testsuite/test_parsing/y_object_extreme_numbers.json", + "test/data/nst_json_testsuite/test_parsing/y_object_long_strings.json", + "test/data/nst_json_testsuite/test_parsing/y_object_simple.json", + "test/data/nst_json_testsuite/test_parsing/y_object_string_unicode.json", + "test/data/nst_json_testsuite/test_parsing/y_object_with_newlines.json", + "test/data/nst_json_testsuite/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json", + "test/data/nst_json_testsuite/test_parsing/y_string_UTF-16_Surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json", + "test/data/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pair.json", + "test/data/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pairs.json", + "test/data/nst_json_testsuite/test_parsing/y_string_allowed_escapes.json", + "test/data/nst_json_testsuite/test_parsing/y_string_backslash_and_u_escaped_zero.json", + "test/data/nst_json_testsuite/test_parsing/y_string_backslash_doublequotes.json", + "test/data/nst_json_testsuite/test_parsing/y_string_comments.json", + "test/data/nst_json_testsuite/test_parsing/y_string_double_escape_a.json", + "test/data/nst_json_testsuite/test_parsing/y_string_double_escape_n.json", + "test/data/nst_json_testsuite/test_parsing/y_string_escaped_control_character.json", + "test/data/nst_json_testsuite/test_parsing/y_string_escaped_noncharacter.json", + "test/data/nst_json_testsuite/test_parsing/y_string_in_array.json", + "test/data/nst_json_testsuite/test_parsing/y_string_in_array_with_leading_space.json", + "test/data/nst_json_testsuite/test_parsing/y_string_last_surrogates_1_and_2.json", + "test/data/nst_json_testsuite/test_parsing/y_string_newline_uescaped.json", + "test/data/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json", + "test/data/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+1FFFF.json", + "test/data/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json", + "test/data/nst_json_testsuite/test_parsing/y_string_null_escape.json", + "test/data/nst_json_testsuite/test_parsing/y_string_one-byte-utf-8.json", + "test/data/nst_json_testsuite/test_parsing/y_string_pi.json", + "test/data/nst_json_testsuite/test_parsing/y_string_simple_ascii.json", + "test/data/nst_json_testsuite/test_parsing/y_string_space.json", + "test/data/nst_json_testsuite/test_parsing/y_string_three-byte-utf-8.json", + "test/data/nst_json_testsuite/test_parsing/y_string_two-byte-utf-8.json", + "test/data/nst_json_testsuite/test_parsing/y_string_u+2028_line_sep.json", + "test/data/nst_json_testsuite/test_parsing/y_string_u+2029_par_sep.json", + "test/data/nst_json_testsuite/test_parsing/y_string_uEscape.json", + "test/data/nst_json_testsuite/test_parsing/y_string_unescaped_char_delete.json", + "test/data/nst_json_testsuite/test_parsing/y_string_unicode.json", + "test/data/nst_json_testsuite/test_parsing/y_string_unicodeEscapedBackslash.json", + "test/data/nst_json_testsuite/test_parsing/y_string_unicode_2.json", + "test/data/nst_json_testsuite/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json", + "test/data/nst_json_testsuite/test_parsing/y_string_unicode_U+2064_invisible_plus.json", + "test/data/nst_json_testsuite/test_parsing/y_string_unicode_escaped_double_quote.json", + // "test/data/nst_json_testsuite/test_parsing/y_string_utf16.json", + "test/data/nst_json_testsuite/test_parsing/y_string_utf8.json", + "test/data/nst_json_testsuite/test_parsing/y_string_with_del_character.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_false.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_int.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_negative_real.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_null.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_string.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_true.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_string_empty.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_trailing_newline.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_true_in_array.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_whitespace_array.json" + }) + { + CAPTURE(filename); + + // parse JSON file + std::ifstream f_json(filename); + json j1 = json::parse(f_json); + + // parse CBOR file + std::ifstream f_cbor(filename + ".cbor", std::ios::binary); + std::vector packed( + (std::istreambuf_iterator(f_cbor)), + std::istreambuf_iterator()); + json j2; + CHECK_NOTHROW(j2 = json::from_cbor(packed)); + + // compare parsed JSON values + CHECK(j1 == j2); + } + } +} + +TEST_CASE("examples from RFC 7049 Appendix A") +{ + SECTION("numbers") + { + CHECK(json::to_cbor(json::parse("0")) == std::vector({0x00})); + CHECK(json::parse("0") == json::from_cbor(std::vector({0x00}))); + + CHECK(json::to_cbor(json::parse("1")) == std::vector({0x01})); + CHECK(json::parse("1") == json::from_cbor(std::vector({0x01}))); + + CHECK(json::to_cbor(json::parse("10")) == std::vector({0x0a})); + CHECK(json::parse("10") == json::from_cbor(std::vector({0x0a}))); + + CHECK(json::to_cbor(json::parse("23")) == std::vector({0x17})); + CHECK(json::parse("23") == json::from_cbor(std::vector({0x17}))); + + CHECK(json::to_cbor(json::parse("24")) == std::vector({0x18, 0x18})); + CHECK(json::parse("24") == json::from_cbor(std::vector({0x18, 0x18}))); + + CHECK(json::to_cbor(json::parse("25")) == std::vector({0x18, 0x19})); + CHECK(json::parse("25") == json::from_cbor(std::vector({0x18, 0x19}))); + + CHECK(json::to_cbor(json::parse("100")) == std::vector({0x18, 0x64})); + CHECK(json::parse("100") == json::from_cbor(std::vector({0x18, 0x64}))); + + CHECK(json::to_cbor(json::parse("1000")) == std::vector({0x19, 0x03, 0xe8})); + CHECK(json::parse("1000") == json::from_cbor(std::vector({0x19, 0x03, 0xe8}))); + + CHECK(json::to_cbor(json::parse("1000000")) == std::vector({0x1a, 0x00, 0x0f, 0x42, 0x40})); + CHECK(json::parse("1000000") == json::from_cbor(std::vector({0x1a, 0x00, 0x0f, 0x42, 0x40}))); + + CHECK(json::to_cbor(json::parse("1000000000000")) == std::vector({0x1b, 0x00, 0x00, 0x00, 0xe8, 0xd4, 0xa5, 0x10, 0x00})); + CHECK(json::parse("1000000000000") == json::from_cbor(std::vector({0x1b, 0x00, 0x00, 0x00, 0xe8, 0xd4, 0xa5, 0x10, 0x00}))); + + CHECK(json::to_cbor(json::parse("18446744073709551615")) == std::vector({0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})); + CHECK(json::parse("18446744073709551615") == json::from_cbor(std::vector({0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}))); + + // positive bignum is not supported + //CHECK(json::to_cbor(json::parse("18446744073709551616")) == std::vector({0xc2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + //CHECK(json::parse("18446744073709551616") == json::from_cbor(std::vector({0xc2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}))); + + //CHECK(json::to_cbor(json::parse("-18446744073709551616")) == std::vector({0x3b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})); + //CHECK(json::parse("-18446744073709551616") == json::from_cbor(std::vector({0x3b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}))); + + // negative bignum is not supported + //CHECK(json::to_cbor(json::parse("-18446744073709551617")) == std::vector({0xc3, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})); + //CHECK(json::parse("-18446744073709551617") == json::from_cbor(std::vector({0xc3, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}))); + + CHECK(json::to_cbor(json::parse("-1")) == std::vector({0x20})); + CHECK(json::parse("-1") == json::from_cbor(std::vector({0x20}))); + + CHECK(json::to_cbor(json::parse("-10")) == std::vector({0x29})); + CHECK(json::parse("-10") == json::from_cbor(std::vector({0x29}))); + + CHECK(json::to_cbor(json::parse("-100")) == std::vector({0x38, 0x63})); + CHECK(json::parse("-100") == json::from_cbor(std::vector({0x38, 0x63}))); + + CHECK(json::to_cbor(json::parse("-1000")) == std::vector({0x39, 0x03, 0xe7})); + CHECK(json::parse("-1000") == json::from_cbor(std::vector({0x39, 0x03, 0xe7}))); + + // half-precision float + //CHECK(json::to_cbor(json::parse("0.0")) == std::vector({0xf9, 0x00, 0x00})); + CHECK(json::parse("0.0") == json::from_cbor(std::vector({0xf9, 0x00, 0x00}))); + + // half-precision float + //CHECK(json::to_cbor(json::parse("-0.0")) == std::vector({0xf9, 0x80, 0x00})); + CHECK(json::parse("-0.0") == json::from_cbor(std::vector({0xf9, 0x80, 0x00}))); + + // half-precision float + //CHECK(json::to_cbor(json::parse("1.0")) == std::vector({0xf9, 0x3c, 0x00})); + CHECK(json::parse("1.0") == json::from_cbor(std::vector({0xf9, 0x3c, 0x00}))); + + CHECK(json::to_cbor(json::parse("1.1")) == std::vector({0xfb, 0x3f, 0xf1, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a})); + CHECK(json::parse("1.1") == json::from_cbor(std::vector({0xfb, 0x3f, 0xf1, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a}))); + + // half-precision float + //CHECK(json::to_cbor(json::parse("1.5")) == std::vector({0xf9, 0x3e, 0x00})); + CHECK(json::parse("1.5") == json::from_cbor(std::vector({0xf9, 0x3e, 0x00}))); + + // half-precision float + //CHECK(json::to_cbor(json::parse("65504.0")) == std::vector({0xf9, 0x7b, 0xff})); + CHECK(json::parse("65504.0") == json::from_cbor(std::vector({0xf9, 0x7b, 0xff}))); + + //CHECK(json::to_cbor(json::parse("100000.0")) == std::vector({0xfa, 0x47, 0xc3, 0x50, 0x00})); + CHECK(json::parse("100000.0") == json::from_cbor(std::vector({0xfa, 0x47, 0xc3, 0x50, 0x00}))); + + //CHECK(json::to_cbor(json::parse("3.4028234663852886e+38")) == std::vector({0xfa, 0x7f, 0x7f, 0xff, 0xff})); + CHECK(json::parse("3.4028234663852886e+38") == json::from_cbor(std::vector({0xfa, 0x7f, 0x7f, 0xff, 0xff}))); + + CHECK(json::to_cbor(json::parse("1.0e+300")) == std::vector({0xfb, 0x7e, 0x37, 0xe4, 0x3c, 0x88, 0x00, 0x75, 0x9c})); + CHECK(json::parse("1.0e+300") == json::from_cbor(std::vector({0xfb, 0x7e, 0x37, 0xe4, 0x3c, 0x88, 0x00, 0x75, 0x9c}))); + + // half-precision float + //CHECK(json::to_cbor(json::parse("5.960464477539063e-8")) == std::vector({0xf9, 0x00, 0x01})); + CHECK(json::parse("-4.0") == json::from_cbor(std::vector({0xf9, 0xc4, 0x00}))); + + // half-precision float + //CHECK(json::to_cbor(json::parse("0.00006103515625")) == std::vector({0xf9, 0x04, 0x00})); + CHECK(json::parse("-4.0") == json::from_cbor(std::vector({0xf9, 0xc4, 0x00}))); + + // half-precision float + //CHECK(json::to_cbor(json::parse("-4.0")) == std::vector({0xf9, 0xc4, 0x00})); + CHECK(json::parse("-4.0") == json::from_cbor(std::vector({0xf9, 0xc4, 0x00}))); + + CHECK(json::to_cbor(json::parse("-4.1")) == std::vector({0xfb, 0xc0, 0x10, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66})); + CHECK(json::parse("-4.1") == json::from_cbor(std::vector({0xfb, 0xc0, 0x10, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66}))); + } + + SECTION("simple values") + { + CHECK(json::to_cbor(json::parse("false")) == std::vector({0xf4})); + CHECK(json::parse("false") == json::from_cbor(std::vector({0xf4}))); + + CHECK(json::to_cbor(json::parse("true")) == std::vector({0xf5})); + CHECK(json::parse("true") == json::from_cbor(std::vector({0xf5}))); + + CHECK(json::to_cbor(json::parse("true")) == std::vector({0xf5})); + CHECK(json::parse("true") == json::from_cbor(std::vector({0xf5}))); + } + + SECTION("strings") + { + CHECK(json::to_cbor(json::parse("\"\"")) == std::vector({0x60})); + CHECK(json::parse("\"\"") == json::from_cbor(std::vector({0x60}))); + + CHECK(json::to_cbor(json::parse("\"a\"")) == std::vector({0x61, 0x61})); + CHECK(json::parse("\"a\"") == json::from_cbor(std::vector({0x61, 0x61}))); + + CHECK(json::to_cbor(json::parse("\"IETF\"")) == std::vector({0x64, 0x49, 0x45, 0x54, 0x46})); + CHECK(json::parse("\"IETF\"") == json::from_cbor(std::vector({0x64, 0x49, 0x45, 0x54, 0x46}))); + + CHECK(json::to_cbor(json::parse("\"\\u00fc\"")) == std::vector({0x62, 0xc3, 0xbc})); + CHECK(json::parse("\"\\u00fc\"") == json::from_cbor(std::vector({0x62, 0xc3, 0xbc}))); + + CHECK(json::to_cbor(json::parse("\"\\u6c34\"")) == std::vector({0x63, 0xe6, 0xb0, 0xb4})); + CHECK(json::parse("\"\\u6c34\"") == json::from_cbor(std::vector({0x63, 0xe6, 0xb0, 0xb4}))); + + CHECK(json::to_cbor(json::parse("\"\\ud800\\udd51\"")) == std::vector({0x64, 0xf0, 0x90, 0x85, 0x91})); + CHECK(json::parse("\"\\ud800\\udd51\"") == json::from_cbor(std::vector({0x64, 0xf0, 0x90, 0x85, 0x91}))); + + // indefinite length strings + CHECK(json::parse("\"streaming\"") == json::from_cbor(std::vector({0x7f, 0x65, 0x73, 0x74, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x67, 0xff}))); + } + + SECTION("arrays") + { + CHECK(json::to_cbor(json::parse("[]")) == std::vector({0x80})); + CHECK(json::parse("[]") == json::from_cbor(std::vector({0x80}))); + + CHECK(json::to_cbor(json::parse("[1, 2, 3]")) == std::vector({0x83, 0x01, 0x02, 0x03})); + CHECK(json::parse("[1, 2, 3]") == json::from_cbor(std::vector({0x83, 0x01, 0x02, 0x03}))); + + CHECK(json::to_cbor(json::parse("[1, [2, 3], [4, 5]]")) == std::vector({0x83, 0x01, 0x82, 0x02, 0x03, 0x82, 0x04, 0x05})); + CHECK(json::parse("[1, [2, 3], [4, 5]]") == json::from_cbor(std::vector({0x83, 0x01, 0x82, 0x02, 0x03, 0x82, 0x04, 0x05}))); + + CHECK(json::to_cbor(json::parse("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]")) == std::vector({0x98, 0x19, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x18, 0x18, 0x19})); + CHECK(json::parse("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]") == json::from_cbor(std::vector({0x98, 0x19, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x18, 0x18, 0x19}))); + + // indefinite length arrays + CHECK(json::parse("[]") == json::from_cbor(std::vector({0x9f, 0xff}))); + CHECK(json::parse("[1, [2, 3], [4, 5]] ") == json::from_cbor(std::vector({0x9f, 0x01, 0x82, 0x02, 0x03, 0x9f, 0x04, 0x05, 0xff, 0xff}))); + CHECK(json::parse("[1, [2, 3], [4, 5]]") == json::from_cbor(std::vector({0x9f, 0x01, 0x82, 0x02, 0x03, 0x82, 0x04, 0x05, 0xff}))); + CHECK(json::parse("[1, [2, 3], [4, 5]]") == json::from_cbor(std::vector({0x83, 0x01, 0x82, 0x02, 0x03, 0x9f, 0x04, 0x05, 0xff}))); + CHECK(json::parse("[1, [2, 3], [4, 5]]") == json::from_cbor(std::vector({0x83, 0x01, 0x9f, 0x02, 0x03, 0xff, 0x82, 0x04, 0x05}))); + CHECK(json::parse("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]") == json::from_cbor(std::vector({0x9f, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x18, 0x18, 0x19, 0xff}))); + } + + SECTION("objects") + { + CHECK(json::to_cbor(json::parse("{}")) == std::vector({0xa0})); + CHECK(json::parse("{}") == json::from_cbor(std::vector({0xa0}))); + + CHECK(json::to_cbor(json::parse("{\"a\": 1, \"b\": [2, 3]}")) == std::vector({0xa2, 0x61, 0x61, 0x01, 0x61, 0x62, 0x82, 0x02, 0x03})); + CHECK(json::parse("{\"a\": 1, \"b\": [2, 3]}") == json::from_cbor(std::vector({0xa2, 0x61, 0x61, 0x01, 0x61, 0x62, 0x82, 0x02, 0x03}))); + + CHECK(json::to_cbor(json::parse("[\"a\", {\"b\": \"c\"}]")) == std::vector({0x82, 0x61, 0x61, 0xa1, 0x61, 0x62, 0x61, 0x63})); + CHECK(json::parse("[\"a\", {\"b\": \"c\"}]") == json::from_cbor(std::vector({0x82, 0x61, 0x61, 0xa1, 0x61, 0x62, 0x61, 0x63}))); + + CHECK(json::to_cbor(json::parse("{\"a\": \"A\", \"b\": \"B\", \"c\": \"C\", \"d\": \"D\", \"e\": \"E\"}")) == std::vector({0xa5, 0x61, 0x61, 0x61, 0x41, 0x61, 0x62, 0x61, 0x42, 0x61, 0x63, 0x61, 0x43, 0x61, 0x64, 0x61, 0x44, 0x61, 0x65, 0x61, 0x45})); + CHECK(json::parse("{\"a\": \"A\", \"b\": \"B\", \"c\": \"C\", \"d\": \"D\", \"e\": \"E\"}") == json::from_cbor(std::vector({0xa5, 0x61, 0x61, 0x61, 0x41, 0x61, 0x62, 0x61, 0x42, 0x61, 0x63, 0x61, 0x43, 0x61, 0x64, 0x61, 0x44, 0x61, 0x65, 0x61, 0x45}))); + + // indefinite length objects + CHECK(json::parse("{\"a\": 1, \"b\": [2, 3]}") == json::from_cbor(std::vector({0xbf, 0x61, 0x61, 0x01, 0x61, 0x62, 0x9f, 0x02, 0x03, 0xff, 0xff}))); + CHECK(json::parse("[\"a\", {\"b\": \"c\"}]") == json::from_cbor(std::vector({0x82, 0x61, 0x61, 0xbf, 0x61, 0x62, 0x61, 0x63, 0xff}))); + CHECK(json::parse("{\"Fun\": true, \"Amt\": -2}") == json::from_cbor(std::vector({0xbf, 0x63, 0x46, 0x75, 0x6e, 0xf5, 0x63, 0x41, 0x6d, 0x74, 0x21, 0xff}))); + } +} diff --git a/test/src/unit-msgpack.cpp b/test/src/unit-msgpack.cpp new file mode 100644 index 00000000..7498940a --- /dev/null +++ b/test/src/unit-msgpack.cpp @@ -0,0 +1,1216 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ (test suite) +| | |__ | | | | | | version 2.0.7 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +Copyright (c) 2013-2016 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "catch.hpp" + +#include "json.hpp" +using nlohmann::json; + +#include + +TEST_CASE("MessagePack") +{ + SECTION("individual values") + { + SECTION("discarded") + { + // discarded values are not serialized + json j = json::value_t::discarded; + const auto result = json::to_msgpack(j); + CHECK(result.empty()); + } + + SECTION("null") + { + json j = nullptr; + std::vector expected = {0xc0}; + const auto result = json::to_msgpack(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + + SECTION("boolean") + { + SECTION("true") + { + json j = true; + std::vector expected = {0xc3}; + const auto result = json::to_msgpack(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + + SECTION("false") + { + json j = false; + std::vector expected = {0xc2}; + const auto result = json::to_msgpack(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + + SECTION("number") + { + SECTION("signed") + { + SECTION("-32..-1 (negative fixnum)") + { + for (auto i = -32; i <= -1; ++i) + { + CAPTURE(i); + + // create JSON value with integer number + json j = i; + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast(i)); + + // compare result + size + const auto result = json::to_msgpack(j); + CHECK(result == expected); + CHECK(result.size() == 1); + + // check individual bytes + CHECK(static_cast(result[0]) == i); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + + SECTION("0..127 (positive fixnum)") + { + for (size_t i = 0; i <= 127; ++i) + { + CAPTURE(i); + + // create JSON value with integer number + json j = -1; + j.get_ref() = static_cast(i); + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast(i)); + + // compare result + size + const auto result = json::to_msgpack(j); + CHECK(result == expected); + CHECK(result.size() == 1); + + // check individual bytes + CHECK(result[0] == i); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + + SECTION("128..255 (int 8)") + { + for (size_t i = 128; i <= 255; ++i) + { + CAPTURE(i); + + // create JSON value with integer number + json j = -1; + j.get_ref() = static_cast(i); + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(0xcc); + expected.push_back(static_cast(i)); + + // compare result + size + const auto result = json::to_msgpack(j); + CHECK(result == expected); + CHECK(result.size() == 2); + + // check individual bytes + CHECK(result[0] == 0xcc); + uint8_t restored = static_cast(result[1]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + + SECTION("256..65535 (int 16)") + { + for (size_t i = 256; i <= 65535; ++i) + { + CAPTURE(i); + + // create JSON value with integer number + json j = -1; + j.get_ref() = static_cast(i); + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(0xcd); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_msgpack(j); + CHECK(result == expected); + CHECK(result.size() == 3); + + // check individual bytes + CHECK(result[0] == 0xcd); + uint16_t restored = static_cast(result[1]) * 256 + static_cast(result[2]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + + SECTION("65536..4294967295 (int 32)") + { + for (uint32_t i : + { + 65536u, 77777u, 1048576u, 4294967295u + }) + { + CAPTURE(i); + + // create JSON value with integer number + json j = -1; + j.get_ref() = static_cast(i); + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(0xce); + expected.push_back(static_cast((i >> 24) & 0xff)); + expected.push_back(static_cast((i >> 16) & 0xff)); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_msgpack(j); + CHECK(result == expected); + CHECK(result.size() == 5); + + // check individual bytes + CHECK(result[0] == 0xce); + uint32_t restored = static_cast((static_cast(result[1]) << 030) + + (static_cast(result[2]) << 020) + + (static_cast(result[3]) << 010) + + static_cast(result[4])); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + + SECTION("4294967296..9223372036854775807 (int 64)") + { + for (uint64_t i : + { + 4294967296lu, 9223372036854775807lu + }) + { + CAPTURE(i); + + // create JSON value with integer number + json j = -1; + j.get_ref() = static_cast(i); + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(0xcf); + expected.push_back(static_cast((i >> 070) & 0xff)); + expected.push_back(static_cast((i >> 060) & 0xff)); + expected.push_back(static_cast((i >> 050) & 0xff)); + expected.push_back(static_cast((i >> 040) & 0xff)); + expected.push_back(static_cast((i >> 030) & 0xff)); + expected.push_back(static_cast((i >> 020) & 0xff)); + expected.push_back(static_cast((i >> 010) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_msgpack(j); + CHECK(result == expected); + CHECK(result.size() == 9); + + // check individual bytes + CHECK(result[0] == 0xcf); + uint64_t restored = static_cast((static_cast(result[1]) << 070) + + (static_cast(result[2]) << 060) + + (static_cast(result[3]) << 050) + + (static_cast(result[4]) << 040) + + (static_cast(result[5]) << 030) + + (static_cast(result[6]) << 020) + + (static_cast(result[7]) << 010) + + static_cast(result[8])); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + + SECTION("-128..-33 (int 8)") + { + for (auto i = -128; i <= -33; ++i) + { + CAPTURE(i); + + // create JSON value with integer number + json j = i; + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(0xd0); + expected.push_back(static_cast(i)); + + // compare result + size + const auto result = json::to_msgpack(j); + CHECK(result == expected); + CHECK(result.size() == 2); + + // check individual bytes + CHECK(result[0] == 0xd0); + CHECK(static_cast(result[1]) == i); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + + SECTION("-9263 (int 16)") + { + json j = -9263; + std::vector expected = {0xd1, 0xdb, 0xd1}; + + const auto result = json::to_msgpack(j); + CHECK(result == expected); + + int16_t restored = (result[1] << 8) + result[2]; + CHECK(restored == -9263); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + + SECTION("-32768..-129 (int 16)") + { + for (int16_t i = -32768; i <= -129; ++i) + { + CAPTURE(i); + + // create JSON value with integer number + json j = i; + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(0xd1); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_msgpack(j); + CHECK(result == expected); + CHECK(result.size() == 3); + + // check individual bytes + CHECK(result[0] == 0xd1); + int16_t restored = (result[1] << 8) + result[2]; + CHECK(restored == i); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + + SECTION("-32769..-2147483648") + { + std::vector numbers; + numbers.push_back(-32769); + numbers.push_back(-65536); + numbers.push_back(-77777); + numbers.push_back(-1048576); + numbers.push_back(-2147483648); + for (auto i : numbers) + { + CAPTURE(i); + + // create JSON value with integer number + json j = i; + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(0xd2); + expected.push_back(static_cast((i >> 24) & 0xff)); + expected.push_back(static_cast((i >> 16) & 0xff)); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_msgpack(j); + CHECK(result == expected); + CHECK(result.size() == 5); + + // check individual bytes + CHECK(result[0] == 0xd2); + uint32_t restored = static_cast((static_cast(result[1]) << 030) + + (static_cast(result[2]) << 020) + + (static_cast(result[3]) << 010) + + static_cast(result[4])); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + + SECTION("-9223372036854775808..-2147483649 (int 64)") + { + std::vector numbers; + numbers.push_back(INT64_MIN); + numbers.push_back(-2147483649ll); + for (auto i : numbers) + { + CAPTURE(i); + + // create JSON value with unsigned integer number + json j = i; + + // check type + CHECK(j.is_number_integer()); + + // create expected byte vector + std::vector expected; + expected.push_back(0xd3); + expected.push_back(static_cast((i >> 070) & 0xff)); + expected.push_back(static_cast((i >> 060) & 0xff)); + expected.push_back(static_cast((i >> 050) & 0xff)); + expected.push_back(static_cast((i >> 040) & 0xff)); + expected.push_back(static_cast((i >> 030) & 0xff)); + expected.push_back(static_cast((i >> 020) & 0xff)); + expected.push_back(static_cast((i >> 010) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_msgpack(j); + CHECK(result == expected); + CHECK(result.size() == 9); + + // check individual bytes + CHECK(result[0] == 0xd3); + int64_t restored = static_cast((static_cast(result[1]) << 070) + + (static_cast(result[2]) << 060) + + (static_cast(result[3]) << 050) + + (static_cast(result[4]) << 040) + + (static_cast(result[5]) << 030) + + (static_cast(result[6]) << 020) + + (static_cast(result[7]) << 010) + + static_cast(result[8])); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + } + + SECTION("unsigned") + { + SECTION("0..127 (positive fixnum)") + { + for (size_t i = 0; i <= 127; ++i) + { + CAPTURE(i); + + // create JSON value with unsigned integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back(static_cast(i)); + + // compare result + size + const auto result = json::to_msgpack(j); + CHECK(result == expected); + CHECK(result.size() == 1); + + // check individual bytes + CHECK(result[0] == i); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + + SECTION("128..255 (uint 8)") + { + for (size_t i = 128; i <= 255; ++i) + { + CAPTURE(i); + + // create JSON value with unsigned integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back(0xcc); + expected.push_back(static_cast(i)); + + // compare result + size + const auto result = json::to_msgpack(j); + CHECK(result == expected); + CHECK(result.size() == 2); + + // check individual bytes + CHECK(result[0] == 0xcc); + uint8_t restored = static_cast(result[1]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + + SECTION("256..65535 (uint 16)") + { + for (size_t i = 256; i <= 65535; ++i) + { + CAPTURE(i); + + // create JSON value with unsigned integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back(0xcd); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_msgpack(j); + CHECK(result == expected); + CHECK(result.size() == 3); + + // check individual bytes + CHECK(result[0] == 0xcd); + uint16_t restored = static_cast(result[1]) * 256 + static_cast(result[2]); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + + SECTION("65536..4294967295 (uint 32)") + { + for (uint32_t i : + { + 65536u, 77777u, 1048576u, 4294967295u + }) + { + CAPTURE(i); + + // create JSON value with unsigned integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back(0xce); + expected.push_back(static_cast((i >> 24) & 0xff)); + expected.push_back(static_cast((i >> 16) & 0xff)); + expected.push_back(static_cast((i >> 8) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_msgpack(j); + CHECK(result == expected); + CHECK(result.size() == 5); + + // check individual bytes + CHECK(result[0] == 0xce); + uint32_t restored = static_cast((static_cast(result[1]) << 030) + + (static_cast(result[2]) << 020) + + (static_cast(result[3]) << 010) + + static_cast(result[4])); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + + SECTION("4294967296..18446744073709551615 (uint 64)") + { + for (uint64_t i : + { + 4294967296lu, 18446744073709551615lu + }) + { + CAPTURE(i); + + // create JSON value with unsigned integer number + json j = i; + + // check type + CHECK(j.is_number_unsigned()); + + // create expected byte vector + std::vector expected; + expected.push_back(0xcf); + expected.push_back(static_cast((i >> 070) & 0xff)); + expected.push_back(static_cast((i >> 060) & 0xff)); + expected.push_back(static_cast((i >> 050) & 0xff)); + expected.push_back(static_cast((i >> 040) & 0xff)); + expected.push_back(static_cast((i >> 030) & 0xff)); + expected.push_back(static_cast((i >> 020) & 0xff)); + expected.push_back(static_cast((i >> 010) & 0xff)); + expected.push_back(static_cast(i & 0xff)); + + // compare result + size + const auto result = json::to_msgpack(j); + CHECK(result == expected); + CHECK(result.size() == 9); + + // check individual bytes + CHECK(result[0] == 0xcf); + uint64_t restored = static_cast((static_cast(result[1]) << 070) + + (static_cast(result[2]) << 060) + + (static_cast(result[3]) << 050) + + (static_cast(result[4]) << 040) + + (static_cast(result[5]) << 030) + + (static_cast(result[6]) << 020) + + (static_cast(result[7]) << 010) + + static_cast(result[8])); + CHECK(restored == i); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + } + + SECTION("float") + { + SECTION("3.1415925") + { + double v = 3.1415925; + json j = v; + std::vector expected = + { + 0xcb, 0x40, 0x09, 0x21, 0xfb, 0x3f, 0xa6, 0xde, 0xfc + }; + const auto result = json::to_msgpack(j); + CHECK(result == expected); + + // restore value (reverse array for endianess) + double restored; + std::reverse(expected.begin(), expected.end()); + memcpy(&restored, expected.data(), sizeof(double)); + CHECK(restored == v); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + } + + SECTION("string") + { + SECTION("N = 0..31") + { + // explicitly enumerate the first byte for all 32 strings + const std::vector first_bytes = + { + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, + 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, + 0xbb, 0xbc, 0xbd, 0xbe, 0xbf + }; + + for (size_t N = 0; N < first_bytes.size(); ++N) + { + CAPTURE(N); + + // create JSON value with string containing of N * 'x' + const auto s = std::string(N, 'x'); + json j = s; + + // create expected byte vector + std::vector expected; + expected.push_back(first_bytes[N]); + for (size_t i = 0; i < N; ++i) + { + expected.push_back('x'); + } + + // check first byte + CHECK((first_bytes[N] & 0x1f) == N); + + // compare result + size + const auto result = json::to_msgpack(j); + CHECK(result == expected); + CHECK(result.size() == N + 1); + // check that no null byte is appended + if (N > 0) + { + CHECK(result.back() != '\x00'); + } + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + + SECTION("N = 32..255") + { + for (size_t N = 32; N <= 255; ++N) + { + CAPTURE(N); + + // create JSON value with string containing of N * 'x' + const auto s = std::string(N, 'x'); + json j = s; + + // create expected byte vector + std::vector expected; + expected.push_back(0xd9); + expected.push_back(static_cast(N)); + for (size_t i = 0; i < N; ++i) + { + expected.push_back('x'); + } + + // compare result + size + const auto result = json::to_msgpack(j); + CHECK(result == expected); + CHECK(result.size() == N + 2); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + + SECTION("N = 256..65535") + { + for (size_t N : + { + 256u, 999u, 1025u, 3333u, 2048u, 65535u + }) + { + CAPTURE(N); + + // create JSON value with string containing of N * 'x' + const auto s = std::string(N, 'x'); + json j = s; + + // create expected byte vector (hack: create string first) + std::vector expected(N, 'x'); + // reverse order of commands, because we insert at begin() + expected.insert(expected.begin(), static_cast(N & 0xff)); + expected.insert(expected.begin(), static_cast((N >> 8) & 0xff)); + expected.insert(expected.begin(), 0xda); + + // compare result + size + const auto result = json::to_msgpack(j); + CHECK(result == expected); + CHECK(result.size() == N + 3); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + + SECTION("N = 65536..4294967295") + { + for (size_t N : + { + 65536u, 77777u, 1048576u + }) + { + CAPTURE(N); + + // create JSON value with string containing of N * 'x' + const auto s = std::string(N, 'x'); + json j = s; + + // create expected byte vector (hack: create string first) + std::vector expected(N, 'x'); + // reverse order of commands, because we insert at begin() + expected.insert(expected.begin(), static_cast(N & 0xff)); + expected.insert(expected.begin(), static_cast((N >> 8) & 0xff)); + expected.insert(expected.begin(), static_cast((N >> 16) & 0xff)); + expected.insert(expected.begin(), static_cast((N >> 24) & 0xff)); + expected.insert(expected.begin(), 0xdb); + + // compare result + size + const auto result = json::to_msgpack(j); + CHECK(result == expected); + CHECK(result.size() == N + 5); + // check that no null byte is appended + CHECK(result.back() != '\x00'); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + } + + SECTION("array") + { + SECTION("empty") + { + json j = json::array(); + std::vector expected = {0x90}; + const auto result = json::to_msgpack(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + + SECTION("[null]") + { + json j = {nullptr}; + std::vector expected = {0x91, 0xc0}; + const auto result = json::to_msgpack(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + + SECTION("[1,2,3,4,5]") + { + json j = json::parse("[1,2,3,4,5]"); + std::vector expected = {0x95, 0x01, 0x02, 0x03, 0x04, 0x05}; + const auto result = json::to_msgpack(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + + SECTION("[[[[]]]]") + { + json j = json::parse("[[[[]]]]"); + std::vector expected = {0x91, 0x91, 0x91, 0x90}; + const auto result = json::to_msgpack(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + + SECTION("array 16") + { + json j(16, nullptr); + std::vector expected(j.size() + 3, 0xc0); // all null + expected[0] = 0xdc; // array 16 + expected[1] = 0x00; // size (0x0010), byte 0 + expected[2] = 0x10; // size (0x0010), byte 1 + const auto result = json::to_msgpack(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + + SECTION("array 32") + { + json j(65536, nullptr); + std::vector expected(j.size() + 5, 0xc0); // all null + expected[0] = 0xdd; // array 32 + expected[1] = 0x00; // size (0x00100000), byte 0 + expected[2] = 0x01; // size (0x00100000), byte 1 + expected[3] = 0x00; // size (0x00100000), byte 2 + expected[4] = 0x00; // size (0x00100000), byte 3 + const auto result = json::to_msgpack(j); + //CHECK(result == expected); + + CHECK(result.size() == expected.size()); + for (size_t i = 0; i < expected.size(); ++i) + { + CAPTURE(i); + CHECK(result[i] == expected[i]); + } + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + + SECTION("object") + { + SECTION("empty") + { + json j = json::object(); + std::vector expected = {0x80}; + const auto result = json::to_msgpack(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + + SECTION("{\"\":null}") + { + json j = {{"", nullptr}}; + std::vector expected = {0x81, 0xa0, 0xc0}; + const auto result = json::to_msgpack(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + + SECTION("{\"a\": {\"b\": {\"c\": {}}}}") + { + json j = json::parse("{\"a\": {\"b\": {\"c\": {}}}}"); + std::vector expected = + { + 0x81, 0xa1, 0x61, 0x81, 0xa1, 0x62, 0x81, 0xa1, 0x63, 0x80 + }; + const auto result = json::to_msgpack(j); + CHECK(result == expected); + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + + SECTION("map 16") + { + json j = R"({"00": null, "01": null, "02": null, "03": null, + "04": null, "05": null, "06": null, "07": null, + "08": null, "09": null, "10": null, "11": null, + "12": null, "13": null, "14": null, "15": null})"_json; + + const auto result = json::to_msgpack(j); + + // Checking against an expected vector byte by byte is + // difficult, because no assumption on the order of key/value + // pairs are made. We therefore only check the prefix (type and + // size and the overall size. The rest is then handled in the + // roundtrip check. + CHECK(result.size() == 67); // 1 type, 2 size, 16*4 content + CHECK(result[0] == 0xde); // map 16 + CHECK(result[1] == 0x00); // byte 0 of size (0x0010) + CHECK(result[2] == 0x10); // byte 1 of size (0x0010) + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + + SECTION("map 32") + { + json j; + for (auto i = 0; i < 65536; ++i) + { + // format i to a fixed width of 5 + // each entry will need 7 bytes: 6 for fixstr, 1 for null + std::stringstream ss; + ss << std::setw(5) << std::setfill('0') << i; + j.emplace(ss.str(), nullptr); + } + + const auto result = json::to_msgpack(j); + + // Checking against an expected vector byte by byte is + // difficult, because no assumption on the order of key/value + // pairs are made. We therefore only check the prefix (type and + // size and the overall size. The rest is then handled in the + // roundtrip check. + CHECK(result.size() == 458757); // 1 type, 4 size, 65536*7 content + CHECK(result[0] == 0xdf); // map 32 + CHECK(result[1] == 0x00); // byte 0 of size (0x00010000) + CHECK(result[2] == 0x01); // byte 1 of size (0x00010000) + CHECK(result[3] == 0x00); // byte 2 of size (0x00010000) + CHECK(result[4] == 0x00); // byte 3 of size (0x00010000) + + // roundtrip + CHECK(json::from_msgpack(result) == j); + } + } + } + + SECTION("from float32") + { + auto given = std::vector({0xca, 0x41, 0xc8, 0x00, 0x01}); + json j = json::from_msgpack(given); + CHECK(j.get() == Approx(25.0000019073486)); + } +} + + +// use this testcase outside [hide] to run it with Valgrind +TEST_CASE("single MessagePack roundtrip") +{ + SECTION("sample.json") + { + std::string filename = "test/data/json_testsuite/sample.json"; + + // parse JSON file + std::ifstream f_json(filename); + json j1 = json::parse(f_json); + + // parse MessagePack file + std::ifstream f_msgpack(filename + ".msgpack", std::ios::binary); + std::vector packed((std::istreambuf_iterator(f_msgpack)), + std::istreambuf_iterator()); + json j2; + CHECK_NOTHROW(j2 = json::from_msgpack(packed)); + + // compare parsed JSON values + CHECK(j1 == j2); + } +} + + +TEST_CASE("MessagePack roundtrips", "[hide]") +{ + SECTION("input from msgpack-python") + { + for (std::string filename : + { + "test/data/json_nlohmann_tests/all_unicode.json", + "test/data/json.org/1.json", + "test/data/json.org/2.json", + "test/data/json.org/3.json", + "test/data/json.org/4.json", + "test/data/json.org/5.json", + "test/data/json_roundtrip/roundtrip01.json", + "test/data/json_roundtrip/roundtrip02.json", + "test/data/json_roundtrip/roundtrip03.json", + "test/data/json_roundtrip/roundtrip04.json", + "test/data/json_roundtrip/roundtrip05.json", + "test/data/json_roundtrip/roundtrip06.json", + "test/data/json_roundtrip/roundtrip07.json", + "test/data/json_roundtrip/roundtrip08.json", + "test/data/json_roundtrip/roundtrip09.json", + "test/data/json_roundtrip/roundtrip10.json", + "test/data/json_roundtrip/roundtrip11.json", + "test/data/json_roundtrip/roundtrip12.json", + "test/data/json_roundtrip/roundtrip13.json", + "test/data/json_roundtrip/roundtrip14.json", + "test/data/json_roundtrip/roundtrip15.json", + "test/data/json_roundtrip/roundtrip16.json", + "test/data/json_roundtrip/roundtrip17.json", + "test/data/json_roundtrip/roundtrip18.json", + "test/data/json_roundtrip/roundtrip19.json", + "test/data/json_roundtrip/roundtrip20.json", + "test/data/json_roundtrip/roundtrip21.json", + "test/data/json_roundtrip/roundtrip22.json", + "test/data/json_roundtrip/roundtrip23.json", + "test/data/json_roundtrip/roundtrip24.json", + "test/data/json_roundtrip/roundtrip25.json", + "test/data/json_roundtrip/roundtrip26.json", + "test/data/json_roundtrip/roundtrip27.json", + "test/data/json_roundtrip/roundtrip28.json", + "test/data/json_roundtrip/roundtrip29.json", + "test/data/json_roundtrip/roundtrip30.json", + "test/data/json_roundtrip/roundtrip31.json", + "test/data/json_roundtrip/roundtrip32.json", + "test/data/json_testsuite/sample.json", // kills AppVeyor + "test/data/json_tests/pass1.json", + "test/data/json_tests/pass2.json", + "test/data/json_tests/pass3.json", + "test/data/regression/floats.json", + "test/data/regression/signed_ints.json", + "test/data/regression/unsigned_ints.json", + "test/data/regression/working_file.json", + "test/data/nst_json_testsuite/test_parsing/y_array_arraysWithSpaces.json", + "test/data/nst_json_testsuite/test_parsing/y_array_empty-string.json", + "test/data/nst_json_testsuite/test_parsing/y_array_empty.json", + "test/data/nst_json_testsuite/test_parsing/y_array_ending_with_newline.json", + "test/data/nst_json_testsuite/test_parsing/y_array_false.json", + "test/data/nst_json_testsuite/test_parsing/y_array_heterogeneous.json", + "test/data/nst_json_testsuite/test_parsing/y_array_null.json", + "test/data/nst_json_testsuite/test_parsing/y_array_with_1_and_newline.json", + "test/data/nst_json_testsuite/test_parsing/y_array_with_leading_space.json", + "test/data/nst_json_testsuite/test_parsing/y_array_with_several_null.json", + "test/data/nst_json_testsuite/test_parsing/y_array_with_trailing_space.json", + "test/data/nst_json_testsuite/test_parsing/y_number.json", + "test/data/nst_json_testsuite/test_parsing/y_number_0e+1.json", + "test/data/nst_json_testsuite/test_parsing/y_number_0e1.json", + "test/data/nst_json_testsuite/test_parsing/y_number_after_space.json", + "test/data/nst_json_testsuite/test_parsing/y_number_double_close_to_zero.json", + "test/data/nst_json_testsuite/test_parsing/y_number_double_huge_neg_exp.json", + "test/data/nst_json_testsuite/test_parsing/y_number_huge_exp.json", + "test/data/nst_json_testsuite/test_parsing/y_number_int_with_exp.json", + "test/data/nst_json_testsuite/test_parsing/y_number_minus_zero.json", + "test/data/nst_json_testsuite/test_parsing/y_number_negative_int.json", + "test/data/nst_json_testsuite/test_parsing/y_number_negative_one.json", + "test/data/nst_json_testsuite/test_parsing/y_number_negative_zero.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_capital_e.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_capital_e_neg_exp.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_capital_e_pos_exp.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_exponent.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_fraction_exponent.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_neg_exp.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_neg_overflow.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_pos_exponent.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_pos_overflow.json", + "test/data/nst_json_testsuite/test_parsing/y_number_real_underflow.json", + "test/data/nst_json_testsuite/test_parsing/y_number_simple_int.json", + "test/data/nst_json_testsuite/test_parsing/y_number_simple_real.json", + //"test/data/nst_json_testsuite/test_parsing/y_number_too_big_neg_int.json", + //"test/data/nst_json_testsuite/test_parsing/y_number_too_big_pos_int.json", + //"test/data/nst_json_testsuite/test_parsing/y_number_very_big_negative_int.json", + "test/data/nst_json_testsuite/test_parsing/y_object.json", + "test/data/nst_json_testsuite/test_parsing/y_object_basic.json", + "test/data/nst_json_testsuite/test_parsing/y_object_duplicated_key.json", + "test/data/nst_json_testsuite/test_parsing/y_object_duplicated_key_and_value.json", + "test/data/nst_json_testsuite/test_parsing/y_object_empty.json", + "test/data/nst_json_testsuite/test_parsing/y_object_empty_key.json", + "test/data/nst_json_testsuite/test_parsing/y_object_escaped_null_in_key.json", + "test/data/nst_json_testsuite/test_parsing/y_object_extreme_numbers.json", + "test/data/nst_json_testsuite/test_parsing/y_object_long_strings.json", + "test/data/nst_json_testsuite/test_parsing/y_object_simple.json", + "test/data/nst_json_testsuite/test_parsing/y_object_string_unicode.json", + "test/data/nst_json_testsuite/test_parsing/y_object_with_newlines.json", + "test/data/nst_json_testsuite/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json", + "test/data/nst_json_testsuite/test_parsing/y_string_UTF-16_Surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json", + "test/data/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pair.json", + "test/data/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pairs.json", + "test/data/nst_json_testsuite/test_parsing/y_string_allowed_escapes.json", + "test/data/nst_json_testsuite/test_parsing/y_string_backslash_and_u_escaped_zero.json", + "test/data/nst_json_testsuite/test_parsing/y_string_backslash_doublequotes.json", + "test/data/nst_json_testsuite/test_parsing/y_string_comments.json", + "test/data/nst_json_testsuite/test_parsing/y_string_double_escape_a.json", + "test/data/nst_json_testsuite/test_parsing/y_string_double_escape_n.json", + "test/data/nst_json_testsuite/test_parsing/y_string_escaped_control_character.json", + "test/data/nst_json_testsuite/test_parsing/y_string_escaped_noncharacter.json", + "test/data/nst_json_testsuite/test_parsing/y_string_in_array.json", + "test/data/nst_json_testsuite/test_parsing/y_string_in_array_with_leading_space.json", + "test/data/nst_json_testsuite/test_parsing/y_string_last_surrogates_1_and_2.json", + "test/data/nst_json_testsuite/test_parsing/y_string_newline_uescaped.json", + "test/data/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json", + "test/data/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+1FFFF.json", + "test/data/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json", + "test/data/nst_json_testsuite/test_parsing/y_string_null_escape.json", + "test/data/nst_json_testsuite/test_parsing/y_string_one-byte-utf-8.json", + "test/data/nst_json_testsuite/test_parsing/y_string_pi.json", + "test/data/nst_json_testsuite/test_parsing/y_string_simple_ascii.json", + "test/data/nst_json_testsuite/test_parsing/y_string_space.json", + "test/data/nst_json_testsuite/test_parsing/y_string_three-byte-utf-8.json", + "test/data/nst_json_testsuite/test_parsing/y_string_two-byte-utf-8.json", + "test/data/nst_json_testsuite/test_parsing/y_string_u+2028_line_sep.json", + "test/data/nst_json_testsuite/test_parsing/y_string_u+2029_par_sep.json", + "test/data/nst_json_testsuite/test_parsing/y_string_uEscape.json", + "test/data/nst_json_testsuite/test_parsing/y_string_unescaped_char_delete.json", + "test/data/nst_json_testsuite/test_parsing/y_string_unicode.json", + "test/data/nst_json_testsuite/test_parsing/y_string_unicodeEscapedBackslash.json", + "test/data/nst_json_testsuite/test_parsing/y_string_unicode_2.json", + "test/data/nst_json_testsuite/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json", + "test/data/nst_json_testsuite/test_parsing/y_string_unicode_U+2064_invisible_plus.json", + "test/data/nst_json_testsuite/test_parsing/y_string_unicode_escaped_double_quote.json", + // "test/data/nst_json_testsuite/test_parsing/y_string_utf16.json", + "test/data/nst_json_testsuite/test_parsing/y_string_utf8.json", + "test/data/nst_json_testsuite/test_parsing/y_string_with_del_character.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_false.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_int.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_negative_real.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_null.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_string.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_lonely_true.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_string_empty.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_trailing_newline.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_true_in_array.json", + "test/data/nst_json_testsuite/test_parsing/y_structure_whitespace_array.json" + }) + { + CAPTURE(filename); + + // parse JSON file + std::ifstream f_json(filename); + json j1 = json::parse(f_json); + + // parse MessagePack file + std::ifstream f_msgpack(filename + ".msgpack", std::ios::binary); + std::vector packed((std::istreambuf_iterator(f_msgpack)), + std::istreambuf_iterator()); + json j2; + CHECK_NOTHROW(j2 = json::from_msgpack(packed)); + + // compare parsed JSON values + CHECK(j1 == j2); + } + } +}