mirror of
https://github.com/nlohmann/json.git
synced 2026-10-01 22:45:17 +00:00
* Drop stale LCOV_EXCL_LINE from the json_pointer out_of_range.410 throw The comment said the size_type overflow check in array_index() is only triggered on special platforms like 32-bit, and the throw was excluded from coverage. On 64-bit platforms the check is true for SIZE_MAX itself, and unit-json_pointer.cpp has asserted that case four times since #5395, so the line is executed in the coverage job. Reword the comment and remove the exclusion marker so the coverage report notices if the tests stop reaching it. Part of #5725 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Name all three C-array check aliases in the enum-macro NOLINTs NLOHMANN_JSON_SERIALIZE_ENUM(_STRICT) suppressed the c-array warning under modernize-avoid-c-arrays only, but clang-tidy emits the same diagnostic under the aliases cppcoreguidelines-avoid-c-arrays and hicpp-avoid-c-arrays too. Any user running those checks got a false positive at every macro expansion, and our own tests needed a local NOLINT at each call site to work around it. Name all three aliases in the four macro comments instead, and drop the now-redundant c-array names from the five test call-site NOLINTs. Comment-only change; behavior, the public API, and the ABI do not change. Ran make amalgamate. Part of #5725 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Include doctest as a SYSTEM directory instead of disabling warnings for all tests test_main added -Wno-deprecated and -Wno-float-equal as PUBLIC compile options for every non-MSVC compiler, so they were applied to every translation unit, library headers included, and silenced the CI warnings meant to check the library's own -Wfloat-equal pragmas. The only code that actually needed the suppression was the vendored doctest.h, which was included as a normal (non-SYSTEM) directory. Include thirdparty/doctest as SYSTEM for test_main, matching what tests/abi/CMakeLists.txt already does, and drop the two suppressions from both targets. Verified locally that unit-comparison, unit-conversions and unit-constructor1 compile clean with -Werror -Weverything and doctest as -isystem, and that CMake still configures with JSON_BuildTests=ON. Part of #5725 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Remove the no-op ci_clang_analyze target ci_clang_analyze configured the build with the real compiler and only then wrapped ninja with scan-build. scan-build intercepts compiles by overriding CC/CXX, but build.ninja already had the compiler path baked in from the configure step, so every run bypassed the analyzer: CI logs show "No bugs found" after a normal build, never an analysis. The job also used Debian's frozen clang-tools-14 rather than the image's own clang, and CLANG_ANALYZER_CHECKS still named three valist.* checkers that current clang merged into security.VAList. ci_clang_tidy already runs every clang-analyzer-* check (via .clang-tidy's "Checks: '*'") with warnings as errors, so nothing is lost by removing the dead job. Delete ci_clang_analyze, CLANG_ANALYZER_CHECKS and the SCAN_BUILD_TOOL lookup from cmake/ci.cmake, drop it from the ubuntu.yml ci_static_analysis_clang matrix, and drop the now-unused clang-tools apt package (iwyu stays for ci_single_binaries). Reword quality_assurance.md and assurance_case.md, which described the dead job as a working control, to say the Clang Static Analyzer checks run through clang-tidy. Verified that `cmake -DJSON_CI=ON` still configures cleanly and that ci_clang_analyze no longer appears in the generated build or in any CMake/workflow file. Part of #5725 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Re-enable portability-template-virtual-member-function; remove redundant forwards .clang-tidy disabled three checks "to get the CI going" (#4489, 2024-11-13): portability-template-virtual-member-function, bugprone-use-after-move and its alias hicpp-invalid-access-moved. portability-template-virtual-member-function only flagged output_stream_adapter::write_character/write_characters; annotate both with NOLINT and re-enable the check. bugprone-use-after-move flagged several double forwards that have no effect at runtime: - from_json.hpp calls std::forward<BasicJsonType>(j).at(Idx) inside pack expansions; at() has no ref-qualified overloads and always returns an lvalue reference, so the forward is a no-op. Replace with plain j.at(Idx) in all four places. - the move constructor forwards the whole object to its base class and then reads other's members. That is item 9 of #5724 (together with its cppcheck suppressions) and is left to that change. - input_adapters.hpp forwards the container twice on purpose, so the begin/end iterator types match adapter_type; annotate with NOLINT and a comment instead of changing behavior. The check still flags the move constructor (see above) and two sites in at(KeyType&&) (both overloads, json.hpp, in the throw's string_t(std::forward<KeyType>(key)) after find(std::forward<KeyType>(key))). Open PR #5689 rewrites that hunk, so bugprone-use-after-move (and hicpp-invalid-access-moved) stay disabled for now, with a comment explaining why; re-enable them once #5689 and the #5724 move-constructor change have landed. Also resolve the portability-avoid-pragma-once TODO: single_include never has #pragma once (amalgamate.py strips it) and every supported compiler accepts it in include/, so keep it disabled with an explanatory comment instead of a TODO. Fix the stale "json.hpp, around line 1265" comment in unit-class_parser.cpp, which now points at the move constructor's actual line. Behavior, the public API and the ABI do not change. Verified with clang-tidy 22.1.8 that portability-template-virtual-member-function now reports nothing, that bugprone-use-after-move/ hicpp-invalid-access-moved report only the known at(KeyType&&) and move-constructor sites, and that unit-custom-base-class, unit-constructor1, unit-conversions, unit-element_access2, unit-class_parser and unit-diagnostic-positions (JSON_DIAGNOSTIC_POSITIONS=1) compile under ASan/UBSan and pass with the same assertion counts as before. Ran make amalgamate. Part of #5725 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix stale and malformed NOLINT comments json_sax.hpp named "-warnings-as-errors" in the NOLINT list on the two JSON_ASSERT(false) lines; that is the suffix clang-tidy appends to a diagnostic tag under WarningsAsErrors, not a check name, and every other JSON_ASSERT(false) omits it. unit-capacity.cpp carried 30 "// NOLINT(misc-const-correctness)" comments on "json j = ...;" declarations that are all used with non-const members afterwards, so the check has nothing to report there. unit-constructor2.cpp used a blanket "// NOLINT: access after move is OK here" on a use-after-move that hides every check on the line; naming bugprone-use-after-move and hicpp-invalid-access-moved keeps the intent once those checks are re-enabled (#5724). Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5725 item 10 * Remove stale .clang-tidy entries -google-runtime-references disabled a check that neither clang-tidy 22.1.8 nor 23.1.2 lists under --list-checks -checks='*'; it was removed upstream. The commented-out HeaderFilterRegex line has been unused since the active HeaderFilterRegex was introduced in #2561 (2021). Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5725 item 11 * Remove the GCC C++20 -Wignored-attributes pragma in json.hpp The pragma (added in #5164) claimed to work around the C++ modules redefinition errors of #5103, but #5103 is about hard errors (e.g. "redefinition of std::__is_constant_evaluated()", conflicting std::integral_constant) that ignoring a warning cannot suppress; they are traced to GCC PR 124430 and reproduce with <map> or <string> instead of json.hpp too. A GCC 16.2 -std=gnu++20 -fmodules build following #5103's repro steps still fails with the pragma in place, and a build of all test TUs with GCC_CXXFLAGS (which enable -Wignored-attributes) and the pragma removed produces no such warning. The block only hid a warning class from GCC C++20 users while suggesting #5103 was handled. Overlaps #5610, whose hunks touch the closing half of this pragma to insert the json_literals.hpp include. Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5725 item 9 * Fix stale doxygen comments hidden by the -Wdocumentation pragma macro_scope.hpp ignores -Wdocumentation and -Wdocumentation-unknown-command for the whole library, which also hides genuine documentation mistakes: - detail::unescape() documented "@return unescaped string" but returns void and unescapes its argument in place; reworded to "@param[in,out] s string to unescape in place" and dropped the bogus @return. - basic_json::get()'s copy-conversion overload wrote "converted to @tparam ValueType" inside @return, which Doxygen and Clang parse as a second, malformed @tparam; changed to "@a ValueType", matching the two other get() overloads a few lines above that already use it. This narrows the gap the -Wdocumentation pragma needs to cover; fully replacing the Doxygen-only commands it also hides (item 2c) is left for after #5267. Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5725 item 2 * Fix -Wextra-semi-stmt at its actual source, not assert() clang_flags.cmake blamed the global -Wno-extra-semi-stmt on assert(), but assert() expands to an expression under glibc and libc++ and does not trigger this warning. unit-assert_macro.cpp overrides JSON_ASSERT with "{if (!(x)) ++assert_counter; }", a bare block followed by a semicolon at every JSON_ASSERT(...) call site in the library; that was the actual source of 151 of the 208 -Wextra-semi-stmt sites found in a Clang 22 -Weverything sweep of the test suite with the flag removed. Switched to the standard do/while(false) macro idiom, which does not expand to a statement-plus-semicolon, and corrected the comment to name the remaining source instead: vendored Doctest's CAPTURE(x) shim, which already ends in a semicolon. Verified with clang++ -Wextra-semi-stmt (plus the file's other CI ignores) that unit-assert_macro.cpp now compiles without any -Wextra-semi-stmt diagnostic. Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5725 item 8 (step 1 of 2; step 2 covers the CAPTURE() call sites) * Drop the redundant semicolon from CAPTURE() call sites; remove -Wno-extra-semi-stmt doctest_compatibility.h defines CAPTURE(x) as DOCTEST_CAPTURE(x); (with a trailing semicolon baked into the macro), specifically so call sites do not need to add one themselves; most of the ~267 call sites already follow that convention. The remaining 64 call sites across 20 files wrote "CAPTURE(x);" anyway, turning into a statement plus an empty statement and triggering -Wextra-semi-stmt. Dropped the redundant semicolon at each of those sites. With item 6 having already made vendored Doctest a SYSTEM include, and this the last known source of -Wextra-semi-stmt findings, removed the flag from clang_flags.cmake entirely. Verified with clang++ -Wextra-semi-stmt (plus the file's other CI ignores) that all 20 touched files, plus a file with no CAPTURE() use (unit-json_pointer.cpp), compile without any -Wextra-semi-stmt diagnostic. Signed-off-by: Niels Lohmann <mail@nlohmann.me> #5725 item 8 (step 2 of 2) * Switch ci_static_analysis_clang off the frozen LLVM 22 dev image ubuntu.yml pinned the clang-tidy/clang-tidy-sanitizer/single-binaries job to silkeh/clang:dev, a tag last pushed 2026-02-18 that reports "clang version 22.0.0 (...+20251015...)", a pre-release snapshot from before the LLVM 22 release; the maintainer now updates dev-unstable, 22, and latest instead. Switched to silkeh/clang:22, matching the other clang jobs on :latest. Verified with clang-tidy 22.1.8 (the image's actual version) against this repository's .clang-tidy and library headers what the release image newly reports compared to :dev: - readability-redundant-typename fires at ~250 sites across the _cpp20-relevant conversion/to_chars headers; the library targets C++11 and keeps the typenames, so the check is disabled in .clang-tidy, matching how the file already handles checks that don't fit a C++11 codebase. - misc-anonymous-namespace-in-header fires on the two anonymous namespaces in from_json.hpp and to_json.hpp; added the alias to their existing NOLINT (cert-dcl59-cpp, fuchsia-header-anon-namespaces, google-build-namespaces). - bugprone-std-namespace-modification fires on every addition to namespace std: the std::hash, std::formatter and std::swap overloads in json.hpp, and the std::tuple_size/std::tuple_element specializations in iteration_proxy.hpp (this last file is not named in #5725's item 5, found by actually running clang-tidy 22.1.8 against the current tree). All six are legal, deliberate additions to namespace std (explicit/partial specializations of std types, or the pre-C++20 std::swap overload); annotated each with the check name next to its existing cert-dcl58-cpp NOLINT. - modernize-avoid-c-style-cast reported nothing new. Also added clang++-22/21, clang-tidy-22/21, g++-16 and gcov-16 to the find_program search lists in ci.cmake so a local "maximal warnings" configure prefers the current toolchain version over an older one on PATH. #5725 item 5 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Regenerate cmake/gcc_flags.cmake for GCC 16.2.0 GCC_CXXFLAGS was generated for GCC 15.1.0, but ci_test_gcc and ci_test_gcc_cxx{11..26} now run in gcc:latest, currently GCC 16.2.0, so the "maximal warnings" job was missing warnings introduced since 15.1.0 while carrying entries GCC 16 treats as duplicates or no-ops. Regenerated with https://github.com/nlohmann/gcc_flags (patched locally to not crash on an option whose "-x c++ <opt> -" probe fails before it reads stdin, e.g. -Wabi=; the tool otherwise raises BrokenPipeError instead of recording the option as an error) run against g++ 16.2.0 in the official gcc:16 Docker image, keeping the documented -Wno-* exclusions and the same alphabetical placement scheme as before. Also added three GCC 16 warnings the generator cannot discover on its own because it only probes value ranges/lists it finds in the -Q option name itself, not in the enum choices --help=warnings documents separately: - -Wbidi-chars=any, -Wleading-whitespace=spaces: manually verified these compile cleanly with g++ 16.2.0. - -Wstrict-flex-arrays: deliberately NOT added, unlike the other two. Without -fstrict-flex-arrays (which the library does not enable, as it would change codegen for flexible array members), GCC prints "'-Wstrict-flex-arrays' is ignored when '-fstrict-flex-arrays' is not present" on every translation unit, and under our -Werror that note itself aborts the build. This differs from the harmless no-op warnings already kept in the file (-Whsa, -Wsynth, -Wunreachable-code, -Wunsafe-loop-optimizations), which emit nothing; #5725 item 7 named -Wstrict-flex-arrays as one of the flags GCC 16 adds, but did not anticipate this failure mode. Verified: compiled the library header and a representative set of test translation units (including ones touched by items 1, 3, 8, 9, 10 of this issue) with the regenerated GCC_CXXFLAGS plus -Werror under g++ 16.2.0 at -std=c++11 through -std=c++26, with zero warnings; ran the full local test suite (129/129 passing, unrelated to this compiler) as a regression check. CI must still confirm the actual ci_test_gcc / ci_test_standards_gcc targets end to end, since this was verified with direct g++ invocations rather than through the CMake/ CXXFLAGS environment-variable plumbing in ci.cmake. #5725 item 7 Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Avoid std::basic_string<CharType> for non-character output_adapter CharType output_adapter<CharType, StringType> defaulted StringType to std::basic_string<CharType>, and (with JSON_NO_IO undefined) always declared a std::basic_ostream<CharType>&-taking constructor. For CharType with no non-deprecated std::char_traits specialization (only std::uint8_t is ever used this way, by the binary writers), simply naming either type - as an unused default template argument, or as an unused, never-called constructor's parameter type - instantiates std::char_traits<CharType> merely to name it, which some standard libraries mark deprecated: with the library-wide -Wdocumentation pragma (item 2's other half, left for a later commit) temporarily removed, an Apple clang 21 / libc++ TU calling json::to_cbor(j, vec) with std::vector<std::uint8_t>& got one -Wdeprecated-declarations warning per binary writer at the old output_adapters.hpp:193. Replaced the eager std::basic_string<CharType> / std::basic_ostream <CharType> defaults with a bool-tagged partial specialization (not std::conditional, which requires naming both branches' types up front regardless of which is selected, reproducing the same warning) that only ever names std::basic_string<CharType> / std::basic_ostream <CharType> when CharType is actually one of char, wchar_t, char16_t, char32_t, or (with __cpp_lib_char8_t) char8_t. For any other CharType, output_adapter's StringType and ostream-constructor parameter fall back to two distinct empty placeholder types, kept distinct so the two constructor overloads do not collide into a single redeclaration. Public API / behavior: passing a std::basic_string<std::uint8_t>& or std::basic_ostream<std::uint8_t>& directly to a binary writer's output_adapter now fails to compile instead of compiling with a deprecation warning; this was neither documented nor tested. All documented uses (std::vector<CharType>, std::basic_ostream<CharType> and StringType for character CharType) are unaffected. Verified with Apple clang 21 / libc++, with the two -Wdocumentation* "ignored" pragma lines in macro_scope.hpp temporarily removed and -std=c++11/c++20 plus the project's -Weverything flag set: calling to_cbor/to_msgpack/to_ubjson/to_bjdata/to_bson/to_bon8 on a std::vector<std::uint8_t> now produces no char_traits<unsigned char> (or any other) deprecation warning, while the char-based string- and ostream-adapter paths, and a to_cbor/from_cbor round trip, still compile and run correctly; also verified with GCC 16.2.0. Ran the full local test suite, including the binary-format unit tests (unit-cbor, unit-msgpack, unit-ubjson, unit-bjdata, unit-bson, unit-bon8, unit-binary_writer_sinks, unit-binary_formats, unit-custom-binary-type): 129/129 passing. #5725 item 2 (step a) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Remove the library-wide -Wdocumentation pragma; fix what it hid macro_scope.hpp / macro_unscope.hpp pushed and popped a Clang diagnostic region over the entire library that ignored -Wdocumentation and -Wdocumentation-unknown-command. Removed both pragmas and fixed every finding a full -Wdocumentation (which implies -Wdocumentation-unknown-command and -Wdocumentation-deprecated-sync) build reports, so the library now compiles clean under Clang's documentation checks without a blanket suppression. Overlaps #5267, which is still open and edits a nearby doc block (json.hpp's get()/get_impl() @return, already fixed in the item 2 step (b) commit of this branch); this commit does not touch that block again. Unknown Doxygen alias commands (Doxyfile removed in #3071, so these were never rendered by anything) rewritten as plain prose, keeping the same information: - @requirement REQ-JSON-01 / REQ-JSON-02 (iter_impl.hpp, json_reverse_iterator.hpp): now "This class satisfies the following concept requirements (REQ-JSON-0N):". - @liveexample{prose,example-id} (three sites in json.hpp): kept the prose, dropped the command wrapper and the trailing example-id (docs/mkdocs/docs/examples/*.cpp still exist and are used directly by the rendered docs, not through this in-header alias) and unescaped the "\," commas that were only needed for the old alias's comma-separated argument syntax. - @complexity X (json.hpp x4, json_pointer.hpp x2, serializer.hpp x1): now "Complexity: X". Backslash sequences Clang's comment lexer tried to parse as commands, escaped to render as literal backslashes: - lexer.hpp get_codepoint(): two `\u` occurrences. - binary_reader.hpp get_bson_cstr() / get_bson_cstr_bulk(): two `\x00` occurrences. - serializer.hpp: three `\uXXXX` occurrences (constructor @param, append_codepoint_to_string_buffer() @brief, and the ensure_ascii member comment). One finding remained after all of the above: Clang reports "declaration is marked with '@deprecated' command but does not have a deprecation attribute" on the deprecated sax_parse(span_input_adapter&&, ...) overload, even though JSON_HEDLEY_DEPRECATED_FOR does expand to __attribute__((deprecated(...))) for Clang. Several isolated reproductions of this exact declaration shape - doc comment, template<>, two stacked __attribute__ macros, an overload set sharing the name - did not reproduce the warning, so this looks like a Clang comment/declaration-association quirk specific to this overload inside the much larger basic_json class template, not an actual documentation defect. Rather than keep the pragma library-wide for one Clang false positive, added a tightly scoped -Wdocumentation-deprecated-sync push/pop around just that overload. Verified with Apple clang 21 and the project's actual -Weverything flag set (cmake/clang_flags.cmake) on the full header at -std=c++11 and -std=c++20: zero -Wdocumentation* diagnostics. Also compiled clean with GCC 16.2.0 (the pragmas are already __clang__-gated, so this only confirms no unrelated breakage). Ran make check-amalgamation and the full local test suite: 129/129 passing. #5725 item 2 (step c) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Take the JSON value by const reference in the array and tuple from_json paths Review feedback on #5737 (gregmarr): once the no-op std::forward calls are gone, the forwarding references have no purpose. from_json_fn passes the value as const BasicJsonType&, so these functions were only ever instantiated with a const lvalue anyway. The std::array, std::pair and std::tuple overloads of from_json and their helpers now take const BasicJsonType& and pass j on unchanged. Because the deduced BasicJsonType is now the plain type, tuple_type and the static_assert name const BasicJsonType& explicitly, so the reference checks are unchanged: get<std::tuple<const std::string&>>() still works, and get<std::tuple<std::string&>>() still fails the same static_assert. from_json_tuple_get_impl keeps its forwarding reference, since tuple_type calls it through std::declval. Behavior, the public API and the ABI do not change. unit-conversions, unit-constructor1, unit-udt, unit-udt_macro, unit-regression1/2/3, unit-deserialization, unit-noexcept, unit-items, unit-allocator, unit-custom-object-type, unit-ordered_json2 and unit-brace-init-copy-semantics pass at C++11, C++17 and C++20 with unchanged assertion counts. Ran make amalgamate. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
3249 lines
161 KiB
C++
3249 lines
161 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
|
// | | |__ | | | | | | version 3.12.0
|
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
//
|
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#include "doctest_compatibility.h"
|
|
|
|
#include <nlohmann/json.hpp>
|
|
using nlohmann::json;
|
|
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
#include <limits>
|
|
#include <list>
|
|
#include <set>
|
|
#include "make_test_data_available.hpp"
|
|
#include "round_trip_corpus.hpp"
|
|
#include "test_utils.hpp"
|
|
#include "sax_countdown.hpp"
|
|
using utils::SaxCountdown;
|
|
|
|
|
|
TEST_CASE("CBOR")
|
|
{
|
|
SECTION("individual values")
|
|
{
|
|
SECTION("discarded")
|
|
{
|
|
// discarded values are not serialized
|
|
json const j = json::value_t::discarded;
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result.empty());
|
|
}
|
|
|
|
SECTION("NaN")
|
|
{
|
|
// NaN value
|
|
json const j = std::numeric_limits<json::number_float_t>::quiet_NaN();
|
|
const std::vector<uint8_t> expected = {0xf9, 0x7e, 0x00};
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result == expected);
|
|
}
|
|
|
|
SECTION("Infinity")
|
|
{
|
|
// Infinity value
|
|
json const j = std::numeric_limits<json::number_float_t>::infinity();
|
|
const std::vector<uint8_t> expected = {0xf9, 0x7c, 0x00};
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result == expected);
|
|
}
|
|
|
|
SECTION("null")
|
|
{
|
|
const json j = nullptr;
|
|
const std::vector<uint8_t> expected = {0xf6};
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result == expected);
|
|
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
|
|
SECTION("boolean")
|
|
{
|
|
SECTION("true")
|
|
{
|
|
const json j = true;
|
|
const std::vector<uint8_t> expected = {0xf5};
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result == expected);
|
|
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
|
|
SECTION("false")
|
|
{
|
|
const json j = false;
|
|
const std::vector<uint8_t> expected = {0xf4};
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result == expected);
|
|
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
}
|
|
|
|
SECTION("number")
|
|
{
|
|
SECTION("signed")
|
|
{
|
|
SECTION("-9223372036854775808..-4294967297")
|
|
{
|
|
const std::vector<int64_t> numbers
|
|
{
|
|
(std::numeric_limits<int64_t>::min)(),
|
|
-1000000000000000000,
|
|
-100000000000000000,
|
|
-10000000000000000,
|
|
-1000000000000000,
|
|
-100000000000000,
|
|
-10000000000000,
|
|
-1000000000000,
|
|
-100000000000,
|
|
-10000000000,
|
|
-4294967297,
|
|
};
|
|
for (const auto i : numbers)
|
|
{
|
|
CAPTURE(i)
|
|
|
|
// create JSON value with integer number
|
|
const json j = i;
|
|
|
|
// check type
|
|
CHECK(j.is_number_integer());
|
|
|
|
// create expected byte vector
|
|
const auto positive = static_cast<uint64_t>(-1 - i);
|
|
const std::vector<uint8_t> expected
|
|
{
|
|
static_cast<uint8_t>(0x3b),
|
|
static_cast<uint8_t>((positive >> 56) & 0xff),
|
|
static_cast<uint8_t>((positive >> 48) & 0xff),
|
|
static_cast<uint8_t>((positive >> 40) & 0xff),
|
|
static_cast<uint8_t>((positive >> 32) & 0xff),
|
|
static_cast<uint8_t>((positive >> 24) & 0xff),
|
|
static_cast<uint8_t>((positive >> 16) & 0xff),
|
|
static_cast<uint8_t>((positive >> 8) & 0xff),
|
|
static_cast<uint8_t>(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);
|
|
const uint64_t restored = (static_cast<uint64_t>(result[1]) << 070) +
|
|
(static_cast<uint64_t>(result[2]) << 060) +
|
|
(static_cast<uint64_t>(result[3]) << 050) +
|
|
(static_cast<uint64_t>(result[4]) << 040) +
|
|
(static_cast<uint64_t>(result[5]) << 030) +
|
|
(static_cast<uint64_t>(result[6]) << 020) +
|
|
(static_cast<uint64_t>(result[7]) << 010) +
|
|
static_cast<uint64_t>(result[8]);
|
|
CHECK(restored == positive);
|
|
CHECK(-1 - static_cast<int64_t>(restored) == i);
|
|
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
}
|
|
|
|
SECTION("-4294967296..-65537")
|
|
{
|
|
const std::vector<int64_t> numbers
|
|
{
|
|
-65537,
|
|
-100000,
|
|
-1000000,
|
|
-10000000,
|
|
-100000000,
|
|
-1000000000,
|
|
-4294967296,
|
|
};
|
|
for (const auto i : numbers)
|
|
{
|
|
CAPTURE(i)
|
|
|
|
// create JSON value with integer number
|
|
const json j = i;
|
|
|
|
// check type
|
|
CHECK(j.is_number_integer());
|
|
|
|
// create expected byte vector
|
|
auto positive = static_cast<uint32_t>(static_cast<uint64_t>(-1 - i) & 0x00000000ffffffff);
|
|
const std::vector<uint8_t> expected
|
|
{
|
|
static_cast<uint8_t>(0x3a),
|
|
static_cast<uint8_t>((positive >> 24) & 0xff),
|
|
static_cast<uint8_t>((positive >> 16) & 0xff),
|
|
static_cast<uint8_t>((positive >> 8) & 0xff),
|
|
static_cast<uint8_t>(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);
|
|
const uint32_t restored = (static_cast<uint32_t>(result[1]) << 030) +
|
|
(static_cast<uint32_t>(result[2]) << 020) +
|
|
(static_cast<uint32_t>(result[3]) << 010) +
|
|
static_cast<uint32_t>(result[4]);
|
|
CHECK(restored == positive);
|
|
CHECK(-1LL - restored == i);
|
|
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
}
|
|
|
|
SECTION("-65536..-257")
|
|
{
|
|
for (int32_t i = -65536; i <= -257; i = utils::next_integer_sample(i, -257, 7))
|
|
{
|
|
CAPTURE(i)
|
|
|
|
// create JSON value with integer number
|
|
const json j = i;
|
|
|
|
// check type
|
|
CHECK(j.is_number_integer());
|
|
|
|
// create expected byte vector
|
|
const auto positive = static_cast<uint16_t>(-1 - i);
|
|
const std::vector<uint8_t> expected
|
|
{
|
|
static_cast<uint8_t>(0x39),
|
|
static_cast<uint8_t>((positive >> 8) & 0xff),
|
|
static_cast<uint8_t>(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);
|
|
const auto restored = static_cast<uint16_t>((static_cast<uint8_t>(result[1]) * 256) + static_cast<uint8_t>(result[2]));
|
|
CHECK(restored == positive);
|
|
CHECK(-1 - restored == i);
|
|
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
}
|
|
|
|
SECTION("-9263 (int 16)")
|
|
{
|
|
const json j = -9263;
|
|
std::vector<uint8_t> expected = {0x39, 0x24, 0x2e};
|
|
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result == expected);
|
|
|
|
const auto restored = static_cast<int16_t>(-1 - ((result[1] << 8) + result[2]));
|
|
CHECK(restored == -9263);
|
|
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
|
|
SECTION("-256..-24")
|
|
{
|
|
for (auto i = -256; i < -24; ++i)
|
|
{
|
|
CAPTURE(i)
|
|
|
|
// create JSON value with integer number
|
|
const json j = i;
|
|
|
|
// check type
|
|
CHECK(j.is_number_integer());
|
|
|
|
// create expected byte vector
|
|
const std::vector<uint8_t> expected
|
|
{
|
|
0x38,
|
|
static_cast<uint8_t>(-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<int16_t>(-1 - result[1]) == i);
|
|
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
}
|
|
|
|
SECTION("-24..-1")
|
|
{
|
|
for (auto i = -24; i <= -1; ++i)
|
|
{
|
|
CAPTURE(i)
|
|
|
|
// create JSON value with integer number
|
|
const json j = i;
|
|
|
|
// check type
|
|
CHECK(j.is_number_integer());
|
|
|
|
// create expected byte vector
|
|
const std::vector<uint8_t> expected
|
|
{
|
|
static_cast<uint8_t>(0x20 - 1 - static_cast<uint8_t>(i)),
|
|
};
|
|
|
|
// compare result + size
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result == expected);
|
|
CHECK(result.size() == 1);
|
|
|
|
// check individual bytes
|
|
CHECK(static_cast<int8_t>(0x20 - 1 - result[0]) == i);
|
|
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result, true, false) == 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<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
|
|
|
|
// check type
|
|
CHECK(j.is_number_integer());
|
|
|
|
// create expected byte vector
|
|
const std::vector<uint8_t> expected
|
|
{
|
|
static_cast<uint8_t>(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);
|
|
CHECK(json::from_cbor(result, true, false) == 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<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
|
|
|
|
// check type
|
|
CHECK(j.is_number_integer());
|
|
|
|
// create expected byte vector
|
|
const std::vector<uint8_t> expected
|
|
{
|
|
static_cast<uint8_t>(0x18),
|
|
static_cast<uint8_t>(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);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
}
|
|
|
|
SECTION("256..65535")
|
|
{
|
|
for (size_t i = 256; i <= 65535; i = utils::next_integer_sample(i, static_cast<size_t>(65535), static_cast<size_t>(7)))
|
|
{
|
|
CAPTURE(i)
|
|
|
|
// create JSON value with integer number
|
|
json j = -1;
|
|
j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
|
|
|
|
// check type
|
|
CHECK(j.is_number_integer());
|
|
|
|
// create expected byte vector
|
|
const std::vector<uint8_t> expected
|
|
{
|
|
static_cast<uint8_t>(0x19),
|
|
static_cast<uint8_t>((i >> 8) & 0xff),
|
|
static_cast<uint8_t>(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);
|
|
const auto restored = static_cast<uint16_t>((static_cast<uint8_t>(result[1]) * 256) + static_cast<uint8_t>(result[2]));
|
|
CHECK(restored == i);
|
|
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
}
|
|
|
|
SECTION("65536..4294967295")
|
|
{
|
|
for (const uint32_t i :
|
|
{
|
|
65536u, 77777u, 1048576u
|
|
})
|
|
{
|
|
CAPTURE(i)
|
|
|
|
// create JSON value with integer number
|
|
json j = -1;
|
|
j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
|
|
|
|
// check type
|
|
CHECK(j.is_number_integer());
|
|
|
|
// create expected byte vector
|
|
const std::vector<uint8_t> expected
|
|
{
|
|
0x1a,
|
|
static_cast<uint8_t>((i >> 24) & 0xff),
|
|
static_cast<uint8_t>((i >> 16) & 0xff),
|
|
static_cast<uint8_t>((i >> 8) & 0xff),
|
|
static_cast<uint8_t>(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);
|
|
const uint32_t restored = (static_cast<uint32_t>(result[1]) << 030) +
|
|
(static_cast<uint32_t>(result[2]) << 020) +
|
|
(static_cast<uint32_t>(result[3]) << 010) +
|
|
static_cast<uint32_t>(result[4]);
|
|
CHECK(restored == i);
|
|
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
}
|
|
|
|
SECTION("4294967296..4611686018427387903")
|
|
{
|
|
for (const uint64_t i :
|
|
{
|
|
4294967296ul, 4611686018427387903ul
|
|
})
|
|
{
|
|
CAPTURE(i)
|
|
|
|
// create JSON value with integer number
|
|
json j = -1;
|
|
j.get_ref<json::number_integer_t&>() = static_cast<json::number_integer_t>(i);
|
|
|
|
// check type
|
|
CHECK(j.is_number_integer());
|
|
|
|
// create expected byte vector
|
|
const std::vector<uint8_t> expected
|
|
{
|
|
0x1b,
|
|
static_cast<uint8_t>((i >> 070) & 0xff),
|
|
static_cast<uint8_t>((i >> 060) & 0xff),
|
|
static_cast<uint8_t>((i >> 050) & 0xff),
|
|
static_cast<uint8_t>((i >> 040) & 0xff),
|
|
static_cast<uint8_t>((i >> 030) & 0xff),
|
|
static_cast<uint8_t>((i >> 020) & 0xff),
|
|
static_cast<uint8_t>((i >> 010) & 0xff),
|
|
static_cast<uint8_t>(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);
|
|
const uint64_t restored = (static_cast<uint64_t>(result[1]) << 070) +
|
|
(static_cast<uint64_t>(result[2]) << 060) +
|
|
(static_cast<uint64_t>(result[3]) << 050) +
|
|
(static_cast<uint64_t>(result[4]) << 040) +
|
|
(static_cast<uint64_t>(result[5]) << 030) +
|
|
(static_cast<uint64_t>(result[6]) << 020) +
|
|
(static_cast<uint64_t>(result[7]) << 010) +
|
|
static_cast<uint64_t>(result[8]);
|
|
CHECK(restored == i);
|
|
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
}
|
|
|
|
SECTION("-32768..-129 (int 16)")
|
|
{
|
|
for (int16_t i = -32768; i <= static_cast<std::int16_t>(-129); i = utils::next_integer_sample(i, static_cast<int16_t>(-129), static_cast<int16_t>(7)))
|
|
{
|
|
CAPTURE(i)
|
|
|
|
// create JSON value with integer number
|
|
const json j = i;
|
|
|
|
// check type
|
|
CHECK(j.is_number_integer());
|
|
|
|
// create expected byte vector
|
|
const std::vector<uint8_t> expected
|
|
{
|
|
0xd1,
|
|
static_cast<uint8_t>((i >> 8) & 0xff),
|
|
static_cast<uint8_t>(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);
|
|
const auto restored = static_cast<int16_t>((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
|
|
const json j = i;
|
|
|
|
// check type
|
|
CHECK(j.is_number_unsigned());
|
|
|
|
// create expected byte vector
|
|
const std::vector<uint8_t> expected
|
|
{
|
|
static_cast<uint8_t>(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);
|
|
CHECK(json::from_cbor(result, true, false) == 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
|
|
const json j = i;
|
|
|
|
// check type
|
|
CHECK(j.is_number_unsigned());
|
|
|
|
// create expected byte vector
|
|
const std::vector<uint8_t> expected
|
|
{
|
|
0x18,
|
|
static_cast<uint8_t>(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);
|
|
const auto restored = static_cast<uint8_t>(result[1]);
|
|
CHECK(restored == i);
|
|
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
}
|
|
|
|
SECTION("256..65535 (two-byte uint16_t)")
|
|
{
|
|
for (size_t i = 256; i <= 65535; i = utils::next_integer_sample(i, static_cast<size_t>(65535), static_cast<size_t>(7)))
|
|
{
|
|
CAPTURE(i)
|
|
|
|
// create JSON value with unsigned integer number
|
|
const json j = i;
|
|
|
|
// check type
|
|
CHECK(j.is_number_unsigned());
|
|
|
|
// create expected byte vector
|
|
const std::vector<uint8_t> expected
|
|
{
|
|
0x19,
|
|
static_cast<uint8_t>((i >> 8) & 0xff),
|
|
static_cast<uint8_t>(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);
|
|
const auto restored = static_cast<uint16_t>((static_cast<uint8_t>(result[1]) * 256) + static_cast<uint8_t>(result[2]));
|
|
CHECK(restored == i);
|
|
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
}
|
|
|
|
SECTION("65536..4294967295 (four-byte uint32_t)")
|
|
{
|
|
for (const uint32_t i :
|
|
{
|
|
65536u, 77777u, 1048576u
|
|
})
|
|
{
|
|
CAPTURE(i)
|
|
|
|
// create JSON value with unsigned integer number
|
|
const json j = i;
|
|
|
|
// check type
|
|
CHECK(j.is_number_unsigned());
|
|
|
|
// create expected byte vector
|
|
const std::vector<uint8_t> expected
|
|
{
|
|
0x1a,
|
|
static_cast<uint8_t>((i >> 24) & 0xff),
|
|
static_cast<uint8_t>((i >> 16) & 0xff),
|
|
static_cast<uint8_t>((i >> 8) & 0xff),
|
|
static_cast<uint8_t>(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);
|
|
const uint32_t restored = (static_cast<uint32_t>(result[1]) << 030) +
|
|
(static_cast<uint32_t>(result[2]) << 020) +
|
|
(static_cast<uint32_t>(result[3]) << 010) +
|
|
static_cast<uint32_t>(result[4]);
|
|
CHECK(restored == i);
|
|
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
}
|
|
|
|
SECTION("4294967296..4611686018427387903 (eight-byte uint64_t)")
|
|
{
|
|
for (const uint64_t i :
|
|
{
|
|
4294967296ul, 4611686018427387903ul
|
|
})
|
|
{
|
|
CAPTURE(i)
|
|
|
|
// create JSON value with integer number
|
|
const json j = i;
|
|
|
|
// check type
|
|
CHECK(j.is_number_unsigned());
|
|
|
|
// create expected byte vector
|
|
const std::vector<uint8_t> expected
|
|
{
|
|
0x1b,
|
|
static_cast<uint8_t>((i >> 070) & 0xff),
|
|
static_cast<uint8_t>((i >> 060) & 0xff),
|
|
static_cast<uint8_t>((i >> 050) & 0xff),
|
|
static_cast<uint8_t>((i >> 040) & 0xff),
|
|
static_cast<uint8_t>((i >> 030) & 0xff),
|
|
static_cast<uint8_t>((i >> 020) & 0xff),
|
|
static_cast<uint8_t>((i >> 010) & 0xff),
|
|
static_cast<uint8_t>(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);
|
|
const uint64_t restored = (static_cast<uint64_t>(result[1]) << 070) +
|
|
(static_cast<uint64_t>(result[2]) << 060) +
|
|
(static_cast<uint64_t>(result[3]) << 050) +
|
|
(static_cast<uint64_t>(result[4]) << 040) +
|
|
(static_cast<uint64_t>(result[5]) << 030) +
|
|
(static_cast<uint64_t>(result[6]) << 020) +
|
|
(static_cast<uint64_t>(result[7]) << 010) +
|
|
static_cast<uint64_t>(result[8]);
|
|
CHECK(restored == i);
|
|
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
}
|
|
}
|
|
|
|
SECTION("double-precision float")
|
|
{
|
|
SECTION("3.1415925")
|
|
{
|
|
double v = 3.1415925;
|
|
const json j = v;
|
|
std::vector<uint8_t> expected =
|
|
{
|
|
0xfb, 0x40, 0x09, 0x21, 0xfb, 0x3f, 0xa6, 0xde, 0xfc
|
|
};
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result == expected);
|
|
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result) == v);
|
|
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
}
|
|
|
|
SECTION("single-precision float")
|
|
{
|
|
SECTION("0.5")
|
|
{
|
|
double v = 0.5;
|
|
const json j = v;
|
|
// its double-precision float binary value is
|
|
// {0xfb, 0x3f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
|
// but to save memory, we can store it as single-precision float.
|
|
const std::vector<uint8_t> expected = {0xfa, 0x3f, 0x00, 0x00, 0x00};
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result == expected);
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result) == v);
|
|
}
|
|
SECTION("0.0")
|
|
{
|
|
double v = 0.0;
|
|
const json j = v;
|
|
// its double-precision binary value is:
|
|
// {0xfb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
|
const std::vector<uint8_t> expected = {0xfa, 0x00, 0x00, 0x00, 0x00};
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result == expected);
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result) == v);
|
|
}
|
|
SECTION("-0.0")
|
|
{
|
|
double v = -0.0;
|
|
const json j = v;
|
|
// its double-precision binary value is:
|
|
// {0xfb, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
|
const std::vector<uint8_t> expected = {0xfa, 0x80, 0x00, 0x00, 0x00};
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result == expected);
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result) == v);
|
|
}
|
|
SECTION("100.0")
|
|
{
|
|
double v = 100.0;
|
|
const json j = v;
|
|
// its double-precision binary value is:
|
|
// {0xfb, 0x40, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
|
const std::vector<uint8_t> expected = {0xfa, 0x42, 0xc8, 0x00, 0x00};
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result == expected);
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result) == v);
|
|
}
|
|
SECTION("200.0")
|
|
{
|
|
double v = 200.0;
|
|
const json j = v;
|
|
// its double-precision binary value is:
|
|
// {0xfb, 0x40, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
|
const std::vector<uint8_t> expected = {0xfa, 0x43, 0x48, 0x00, 0x00};
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result == expected);
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result) == v);
|
|
}
|
|
SECTION("3.40282e+38(max float)")
|
|
{
|
|
float v = (std::numeric_limits<float>::max)();
|
|
const json j = v;
|
|
const std::vector<uint8_t> expected =
|
|
{
|
|
0xfa, 0x7f, 0x7f, 0xff, 0xff
|
|
};
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result == expected);
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result) == v);
|
|
}
|
|
SECTION("-3.40282e+38(lowest float)")
|
|
{
|
|
auto v = static_cast<double>(std::numeric_limits<float>::lowest());
|
|
const json j = v;
|
|
const std::vector<uint8_t> expected =
|
|
{
|
|
0xfa, 0xff, 0x7f, 0xff, 0xff
|
|
};
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result == expected);
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result) == v);
|
|
}
|
|
SECTION("1 + 3.40282e+38(more than max float)")
|
|
{
|
|
double v = static_cast<double>((std::numeric_limits<float>::max)()) + 0.1e+34;
|
|
const json j = v;
|
|
const std::vector<uint8_t> expected =
|
|
{
|
|
0xfb, 0x47, 0xf0, 0x00, 0x03, 0x04, 0xdc, 0x64, 0x49
|
|
};
|
|
// double
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result == expected);
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result) == v);
|
|
}
|
|
SECTION("-1 - 3.40282e+38(less than lowest float)")
|
|
{
|
|
double v = static_cast<double>(std::numeric_limits<float>::lowest()) - 1.0;
|
|
const json j = v;
|
|
const std::vector<uint8_t> expected =
|
|
{
|
|
0xfa, 0xff, 0x7f, 0xff, 0xff
|
|
};
|
|
// the same with the lowest float
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result == expected);
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result) == v);
|
|
}
|
|
|
|
}
|
|
|
|
SECTION("half-precision float (edge cases)")
|
|
{
|
|
SECTION("errors")
|
|
{
|
|
SECTION("no byte follows")
|
|
{
|
|
json _;
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xf9})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xf9}), true, false).is_discarded());
|
|
}
|
|
SECTION("only one byte follows")
|
|
{
|
|
json _;
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xf9, 0x7c})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xf9, 0x7c}), true, false).is_discarded());
|
|
}
|
|
}
|
|
|
|
SECTION("exp = 0b00000")
|
|
{
|
|
SECTION("0 (0 00000 0000000000)")
|
|
{
|
|
json const j = json::from_cbor(std::vector<uint8_t>({0xf9, 0x00, 0x00}));
|
|
const json::number_float_t d{j};
|
|
CHECK(d == 0.0);
|
|
}
|
|
|
|
SECTION("-0 (1 00000 0000000000)")
|
|
{
|
|
json const j = json::from_cbor(std::vector<uint8_t>({0xf9, 0x80, 0x00}));
|
|
const json::number_float_t d{j};
|
|
CHECK(d == -0.0);
|
|
}
|
|
|
|
SECTION("2**-24 (0 00000 0000000001)")
|
|
{
|
|
json const j = json::from_cbor(std::vector<uint8_t>({0xf9, 0x00, 0x01}));
|
|
const json::number_float_t d{j};
|
|
CHECK(d == std::pow(2.0, -24.0));
|
|
}
|
|
}
|
|
|
|
SECTION("exp = 0b11111")
|
|
{
|
|
SECTION("infinity (0 11111 0000000000)")
|
|
{
|
|
json const j = json::from_cbor(std::vector<uint8_t>({0xf9, 0x7c, 0x00}));
|
|
const json::number_float_t d{j};
|
|
CHECK(d == std::numeric_limits<json::number_float_t>::infinity());
|
|
CHECK(j.dump() == "null");
|
|
}
|
|
|
|
SECTION("-infinity (1 11111 0000000000)")
|
|
{
|
|
json const j = json::from_cbor(std::vector<uint8_t>({0xf9, 0xfc, 0x00}));
|
|
const json::number_float_t d{j};
|
|
CHECK(d == -std::numeric_limits<json::number_float_t>::infinity());
|
|
CHECK(j.dump() == "null");
|
|
}
|
|
}
|
|
|
|
SECTION("other values from https://en.wikipedia.org/wiki/Half-precision_floating-point_format")
|
|
{
|
|
SECTION("1 (0 01111 0000000000)")
|
|
{
|
|
json const j = json::from_cbor(std::vector<uint8_t>({0xf9, 0x3c, 0x00}));
|
|
const json::number_float_t d{j};
|
|
CHECK(d == 1);
|
|
}
|
|
|
|
SECTION("-2 (1 10000 0000000000)")
|
|
{
|
|
json const j = json::from_cbor(std::vector<uint8_t>({0xf9, 0xc0, 0x00}));
|
|
const json::number_float_t d{j};
|
|
CHECK(d == -2);
|
|
}
|
|
|
|
SECTION("65504 (0 11110 1111111111)")
|
|
{
|
|
json const j = json::from_cbor(std::vector<uint8_t>({0xf9, 0x7b, 0xff}));
|
|
const json::number_float_t d{j};
|
|
CHECK(d == 65504);
|
|
}
|
|
}
|
|
|
|
SECTION("infinity")
|
|
{
|
|
json const j = json::from_cbor(std::vector<uint8_t>({0xf9, 0x7c, 0x00}));
|
|
json::number_float_t const d{j};
|
|
CHECK(!std::isfinite(d));
|
|
CHECK(j.dump() == "null");
|
|
}
|
|
|
|
SECTION("NaN")
|
|
{
|
|
json const j = json::from_cbor(std::vector<uint8_t>({0xf9, 0x7e, 0x00}));
|
|
json::number_float_t const d{j};
|
|
CHECK(std::isnan(d));
|
|
CHECK(j.dump() == "null");
|
|
}
|
|
}
|
|
}
|
|
|
|
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');
|
|
const json j = s;
|
|
|
|
// create expected byte vector
|
|
std::vector<uint8_t> expected;
|
|
expected.push_back(static_cast<uint8_t>(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);
|
|
CHECK(json::from_cbor(result, true, false) == 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');
|
|
const json j = s;
|
|
|
|
// create expected byte vector
|
|
std::vector<uint8_t> expected;
|
|
|
|
expected.push_back(0x78);
|
|
expected.push_back(static_cast<uint8_t>(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);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
}
|
|
|
|
SECTION("N = 256..65535")
|
|
{
|
|
for (const 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');
|
|
const json j = s;
|
|
|
|
// create expected byte vector (hack: create string first)
|
|
std::vector<uint8_t> expected(N, 'x');
|
|
// reverse order of commands, because we insert at begin()
|
|
expected.insert(expected.begin(), static_cast<uint8_t>(N & 0xff));
|
|
expected.insert(expected.begin(), static_cast<uint8_t>((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);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
}
|
|
|
|
SECTION("N = 65536..4294967295")
|
|
{
|
|
for (const size_t N :
|
|
{
|
|
65536u, 77777u, 1048576u
|
|
})
|
|
{
|
|
CAPTURE(N)
|
|
|
|
// create JSON value with string containing of N * 'x'
|
|
const auto s = std::string(N, 'x');
|
|
const json j = s;
|
|
|
|
// create expected byte vector (hack: create string first)
|
|
std::vector<uint8_t> expected(N, 'x');
|
|
// reverse order of commands, because we insert at begin()
|
|
expected.insert(expected.begin(), static_cast<uint8_t>(N & 0xff));
|
|
expected.insert(expected.begin(), static_cast<uint8_t>((N >> 8) & 0xff));
|
|
expected.insert(expected.begin(), static_cast<uint8_t>((N >> 16) & 0xff));
|
|
expected.insert(expected.begin(), static_cast<uint8_t>((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);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
}
|
|
}
|
|
|
|
SECTION("array")
|
|
{
|
|
SECTION("empty")
|
|
{
|
|
const json j = json::array();
|
|
std::vector<uint8_t> expected = {0x80};
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result == expected);
|
|
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
|
|
SECTION("[null]")
|
|
{
|
|
const json j = {nullptr};
|
|
const std::vector<uint8_t> expected = {0x81, 0xf6};
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result == expected);
|
|
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
|
|
SECTION("[1,2,3,4,5]")
|
|
{
|
|
const json j = json::parse("[1,2,3,4,5]");
|
|
const std::vector<uint8_t> expected = {0x85, 0x01, 0x02, 0x03, 0x04, 0x05};
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result == expected);
|
|
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
|
|
SECTION("[[[[]]]]")
|
|
{
|
|
const json j = json::parse("[[[[]]]]");
|
|
const std::vector<uint8_t> expected = {0x81, 0x81, 0x81, 0x80};
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result == expected);
|
|
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
|
|
SECTION("array with uint16_t elements")
|
|
{
|
|
const json j(257, nullptr);
|
|
std::vector<uint8_t> 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);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
|
|
SECTION("array with uint32_t elements")
|
|
{
|
|
const json j(65793, nullptr);
|
|
std::vector<uint8_t> 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);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
}
|
|
|
|
SECTION("object")
|
|
{
|
|
SECTION("empty")
|
|
{
|
|
const json j = json::object();
|
|
const std::vector<uint8_t> expected = {0xa0};
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result == expected);
|
|
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
|
|
SECTION("{\"\":null}")
|
|
{
|
|
const json j = {{"", nullptr}};
|
|
const std::vector<uint8_t> expected = {0xa1, 0x60, 0xf6};
|
|
const auto result = json::to_cbor(j);
|
|
CHECK(result == expected);
|
|
|
|
// roundtrip
|
|
CHECK(json::from_cbor(result) == j);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
|
|
SECTION("{\"a\": {\"b\": {\"c\": {}}}}")
|
|
{
|
|
const json j = json::parse(R"({"a": {"b": {"c": {}}}})");
|
|
const std::vector<uint8_t> 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);
|
|
CHECK(json::from_cbor(result, true, false) == 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);
|
|
CHECK(json::from_cbor(result, true, false) == 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);
|
|
CHECK(json::from_cbor(result, true, false) == 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);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
}
|
|
|
|
SECTION("binary")
|
|
{
|
|
SECTION("N = 0..23")
|
|
{
|
|
for (size_t N = 0; N <= 0x17; ++N)
|
|
{
|
|
CAPTURE(N)
|
|
|
|
// create JSON value with byte array containing of N * 'x'
|
|
const auto s = std::vector<uint8_t>(N, 'x');
|
|
const json j = json::binary(s);
|
|
|
|
// create expected byte vector
|
|
std::vector<uint8_t> expected;
|
|
expected.push_back(static_cast<uint8_t>(0x40 + N));
|
|
for (size_t i = 0; i < N; ++i)
|
|
{
|
|
expected.push_back(0x78);
|
|
}
|
|
|
|
// 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);
|
|
CHECK(json::from_cbor(result, true, false) == 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::vector<uint8_t>(N, 'x');
|
|
const json j = json::binary(s);
|
|
|
|
// create expected byte vector
|
|
std::vector<uint8_t> expected;
|
|
expected.push_back(0x58);
|
|
expected.push_back(static_cast<uint8_t>(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);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
}
|
|
|
|
SECTION("N = 256..65535")
|
|
{
|
|
for (const size_t N :
|
|
{
|
|
256u, 999u, 1025u, 3333u, 2048u, 65535u
|
|
})
|
|
{
|
|
CAPTURE(N)
|
|
|
|
// create JSON value with string containing of N * 'x'
|
|
const auto s = std::vector<uint8_t>(N, 'x');
|
|
const json j = json::binary(s);
|
|
|
|
// create expected byte vector (hack: create string first)
|
|
std::vector<uint8_t> expected(N, 'x');
|
|
// reverse order of commands, because we insert at begin()
|
|
expected.insert(expected.begin(), static_cast<uint8_t>(N & 0xff));
|
|
expected.insert(expected.begin(), static_cast<uint8_t>((N >> 8) & 0xff));
|
|
expected.insert(expected.begin(), 0x59);
|
|
|
|
// 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);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
}
|
|
|
|
SECTION("N = 65536..4294967295")
|
|
{
|
|
for (const size_t N :
|
|
{
|
|
65536u, 77777u, 1048576u
|
|
})
|
|
{
|
|
CAPTURE(N)
|
|
|
|
// create JSON value with string containing of N * 'x'
|
|
const auto s = std::vector<uint8_t>(N, 'x');
|
|
const json j = json::binary(s);
|
|
|
|
// create expected byte vector (hack: create string first)
|
|
std::vector<uint8_t> expected(N, 'x');
|
|
// reverse order of commands, because we insert at begin()
|
|
expected.insert(expected.begin(), static_cast<uint8_t>(N & 0xff));
|
|
expected.insert(expected.begin(), static_cast<uint8_t>((N >> 8) & 0xff));
|
|
expected.insert(expected.begin(), static_cast<uint8_t>((N >> 16) & 0xff));
|
|
expected.insert(expected.begin(), static_cast<uint8_t>((N >> 24) & 0xff));
|
|
expected.insert(expected.begin(), 0x5a);
|
|
|
|
// 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);
|
|
CHECK(json::from_cbor(result, true, false) == j);
|
|
}
|
|
}
|
|
|
|
SECTION("indefinite size")
|
|
{
|
|
std::vector<std::uint8_t> const input = {0x5F, 0x44, 0xaa, 0xbb, 0xcc, 0xdd, 0x43, 0xee, 0xff, 0x99, 0xFF};
|
|
auto j = json::from_cbor(input);
|
|
CHECK(j.is_binary());
|
|
auto k = json::binary({0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x99});
|
|
CAPTURE(j.dump(0, ' ', false, json::error_handler_t::strict))
|
|
CHECK(j == k);
|
|
}
|
|
|
|
SECTION("binary in array")
|
|
{
|
|
// array with three empty byte strings
|
|
std::vector<std::uint8_t> const input = {0x83, 0x40, 0x40, 0x40};
|
|
json _;
|
|
CHECK_NOTHROW(_ = json::from_cbor(input));
|
|
}
|
|
|
|
SECTION("binary in object")
|
|
{
|
|
// object mapping "foo" to empty byte string
|
|
std::vector<std::uint8_t> const input = {0xA1, 0x63, 0x66, 0x6F, 0x6F, 0x40};
|
|
json _;
|
|
CHECK_NOTHROW(_ = json::from_cbor(input));
|
|
}
|
|
|
|
SECTION("SAX callback with binary")
|
|
{
|
|
// object mapping "foo" to byte string
|
|
std::vector<std::uint8_t> const input = {0xA1, 0x63, 0x66, 0x6F, 0x6F, 0x41, 0x00};
|
|
|
|
// callback to set binary_seen to true if a binary value was seen
|
|
bool binary_seen = false;
|
|
auto callback = [&binary_seen](int /*depth*/, json::parse_event_t /*event*/, json & parsed) noexcept
|
|
{
|
|
if (parsed.is_binary())
|
|
{
|
|
binary_seen = true;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
json j;
|
|
auto cbp = nlohmann::detail::json_sax_dom_callback_parser<json, nlohmann::detail::string_input_adapter_type>(j, callback, true);
|
|
CHECK(json::sax_parse(input, &cbp, json::input_format_t::cbor));
|
|
CHECK(j.at("foo").is_binary());
|
|
CHECK(binary_seen);
|
|
}
|
|
}
|
|
}
|
|
|
|
SECTION("additional deserialization")
|
|
{
|
|
SECTION("0x5b (byte array)")
|
|
{
|
|
std::vector<uint8_t> const given = {0x5b, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x01, 0x61
|
|
};
|
|
const json j = json::from_cbor(given);
|
|
CHECK(j == json::binary(std::vector<uint8_t> {'a'}));
|
|
}
|
|
|
|
SECTION("0x7b (string)")
|
|
{
|
|
std::vector<uint8_t> const given = {0x7b, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x01, 0x61
|
|
};
|
|
const json j = json::from_cbor(given);
|
|
CHECK(j == "a");
|
|
}
|
|
|
|
SECTION("0x9b (array)")
|
|
{
|
|
std::vector<uint8_t> const given = {0x9b, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x01, 0xf4
|
|
};
|
|
const json j = json::from_cbor(given);
|
|
CHECK(j == json::parse("[false]"));
|
|
}
|
|
|
|
SECTION("0xbb (map)")
|
|
{
|
|
std::vector<uint8_t> const given = {0xbb, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x01, 0x60, 0xf4
|
|
};
|
|
const json j = json::from_cbor(given);
|
|
CHECK(j == json::parse("{\"\": false}"));
|
|
}
|
|
}
|
|
|
|
SECTION("errors")
|
|
{
|
|
SECTION("empty byte vector")
|
|
{
|
|
json _;
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>()), "[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
|
CHECK(json::from_cbor(std::vector<uint8_t>(), true, false).is_discarded());
|
|
}
|
|
|
|
SECTION("too short byte vector")
|
|
{
|
|
json _;
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x18})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x19})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x19, 0x00})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1a})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1a, 0x00})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1a, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1a, 0x00, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1b})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1b, 0x00})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 6: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 7: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 9: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x38})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x39})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x39, 0x00})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x3a})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x3a, 0x00})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x3a, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x3a, 0x00, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x3b})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x3b, 0x00})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 9: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x62})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x62, 0x60})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x7F})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x7F, 0x60})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x82, 0x01})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x9F, 0x01})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xBF, 0x61, 0x61, 0xF5})), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0x61, 0X61})), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xBF, 0x61, 0X61})), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x5F})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR binary: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x5F, 0x00})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR binary: expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x00", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x41})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR binary: unexpected end of input", json::parse_error&);
|
|
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x18}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x19}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x19, 0x00}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x1a}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x1a, 0x00}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x1a, 0x00, 0x00}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x1a, 0x00, 0x00, 0x00}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x1b}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x1b, 0x00}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x38}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x39}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x39, 0x00}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x3a}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x3a, 0x00}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x3a, 0x00, 0x00}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x3a, 0x00, 0x00, 0x00}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x3b}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x3b, 0x00}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x62}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x62, 0x60}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0x60}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x82, 0x01}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x9F, 0x01}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xBF, 0x61, 0x61, 0xF5}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xA1, 0x61, 0x61}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xBF, 0x61, 0x61}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x5F}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x5F, 0x00}), true, false).is_discarded());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x41}), true, false).is_discarded());
|
|
}
|
|
|
|
SECTION("unsupported bytes")
|
|
{
|
|
SECTION("concrete examples")
|
|
{
|
|
json _;
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1c})), "[json.exception.parse_error.112] parse error at byte 1: syntax error while parsing CBOR value: invalid byte: 0x1C", json::parse_error&);
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x1c}), true, false).is_discarded());
|
|
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xf8})), "[json.exception.parse_error.112] parse error at byte 1: syntax error while parsing CBOR value: invalid byte: 0xF8", json::parse_error&);
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xf8}), true, false).is_discarded());
|
|
}
|
|
|
|
SECTION("all unsupported bytes")
|
|
{
|
|
for (const auto byte :
|
|
{
|
|
// ?
|
|
0x1c, 0x1d, 0x1e, 0x1f,
|
|
// ?
|
|
0x3c, 0x3d, 0x3e, 0x3f,
|
|
// ?
|
|
0x5c, 0x5d, 0x5e,
|
|
// ?
|
|
0x7c, 0x7d, 0x7e,
|
|
// ?
|
|
0x9c, 0x9d, 0x9e,
|
|
// ?
|
|
0xbc, 0xbd, 0xbe,
|
|
// date/time
|
|
0xc0, 0xc1,
|
|
// bignum
|
|
0xc2, 0xc3,
|
|
// fraction
|
|
0xc4,
|
|
// bigfloat
|
|
0xc5,
|
|
// tagged item
|
|
0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4,
|
|
// expected conversion
|
|
0xd5, 0xd6, 0xd7,
|
|
// more tagged items
|
|
0xd8, 0xd9, 0xda, 0xdb,
|
|
// ?
|
|
0xdc, 0xdd, 0xde, 0xdf,
|
|
// (simple value)
|
|
0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3,
|
|
// undefined
|
|
0xf7,
|
|
// simple value
|
|
0xf8
|
|
})
|
|
{
|
|
json _;
|
|
CHECK_THROWS_AS(_ = json::from_cbor(std::vector<uint8_t>({static_cast<uint8_t>(byte)})), json::parse_error&);
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({static_cast<uint8_t>(byte)}), true, false).is_discarded());
|
|
}
|
|
}
|
|
}
|
|
|
|
SECTION("invalid string in map")
|
|
{
|
|
json _;
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found a break stop code; last byte: 0xFF", json::parse_error&);
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01}), true, false).is_discarded());
|
|
}
|
|
|
|
SECTION("non-string key (see #2766 and #3381)")
|
|
{
|
|
// only text strings map to JSON object keys; any other key is
|
|
// rejected with a message naming its type
|
|
const std::vector<std::pair<std::vector<std::uint8_t>, std::string>> cases =
|
|
{
|
|
{{0xA1, 0x01, 0x01}, "an unsigned integer; last byte: 0x01"},
|
|
{{0xA1, 0x20, 0x01}, "a negative integer; last byte: 0x20"},
|
|
{{0xA1, 0x41, 0x61, 0x01}, "a byte string; last byte: 0x41"},
|
|
{{0xA1, 0x80, 0x01}, "an array; last byte: 0x80"},
|
|
{{0xA1, 0xA0, 0x01}, "a map; last byte: 0xA0"},
|
|
{{0xA1, 0xC0, 0x61, 0x61, 0x01}, "a tag; last byte: 0xC0"},
|
|
{{0xA1, 0xF4, 0x01}, "a boolean; last byte: 0xF4"},
|
|
{{0xA1, 0xF5, 0x01}, "a boolean; last byte: 0xF5"},
|
|
{{0xA1, 0xF6, 0x01}, "null; last byte: 0xF6"},
|
|
{{0xA1, 0xF7, 0x01}, "undefined; last byte: 0xF7"},
|
|
{{0xA1, 0xF9, 0x3C, 0x00, 0x01}, "a floating-point number; last byte: 0xF9"},
|
|
{{0xA1, 0xFA, 0x3F, 0x80, 0x00, 0x00, 0x01}, "a floating-point number; last byte: 0xFA"},
|
|
{{0xA1, 0xFB, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, "a floating-point number; last byte: 0xFB"},
|
|
{{0xA1, 0xE0, 0x01}, "a simple value; last byte: 0xE0"},
|
|
{{0xA1, 0xF8, 0x20, 0x01}, "a simple value; last byte: 0xF8"},
|
|
// indefinite-length map
|
|
{{0xBF, 0x01, 0x01, 0xFF}, "an unsigned integer; last byte: 0x01"},
|
|
};
|
|
|
|
for (const auto& c : cases)
|
|
{
|
|
CAPTURE(c.first)
|
|
const std::string expected = "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found " + c.second;
|
|
json _;
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(c.first), expected.c_str(), json::parse_error&);
|
|
CHECK(json::from_cbor(c.first, true, false).is_discarded());
|
|
}
|
|
|
|
// a key of major type 3 with a reserved length is still reported as
|
|
// a malformed string, and a missing key as the end of input
|
|
json _;
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0x7C, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x7C", json::parse_error&);
|
|
}
|
|
|
|
SECTION("invalid UTF-8 in string (see #5529)")
|
|
{
|
|
// a two-character text string (major type 3) whose bytes are not
|
|
// valid UTF-8 (0xC0 0xAE is an overlong encoding of '.') must be
|
|
// rejected at decode time, matching every other kind of
|
|
// malformed binary input, rather than only failing later when
|
|
// the resulting value is dumped
|
|
json _;
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x62, 0xc0, 0xae})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x62, 0xc0, 0xae}), true, false).is_discarded());
|
|
|
|
// a CBOR byte string (major type 2) with the very same bytes is
|
|
// NOT text and must still be accepted as-is
|
|
CHECK_NOTHROW(_ = json::from_cbor(std::vector<uint8_t>({0x42, 0xc0, 0xae})));
|
|
CHECK(_ == json::binary(std::vector<std::uint8_t>({0xc0, 0xae})));
|
|
|
|
// valid UTF-8 must still round-trip
|
|
const json j = "h\xc3\xa9llo, w\xc3\xb6rld! \xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"; // héllo, wörld! 日本語
|
|
CHECK(json::from_cbor(json::to_cbor(j)) == j);
|
|
}
|
|
|
|
SECTION("invalid UTF-8 in indefinite-length string")
|
|
{
|
|
json _;
|
|
|
|
// every chunk must be valid UTF-8 on its own (RFC 8949, Section
|
|
// 3.2.3), so a code point split across two chunks is rejected
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff}), true, false).is_discarded());
|
|
|
|
// an ill-formed later chunk is rejected after valid ones
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x7f, 0x62, 0xc3, 0xa9, 0x62, 0xc0, 0xae, 0xff})), "[json.exception.parse_error.113] parse error at byte 7: syntax error while parsing CBOR string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
|
|
|
// valid multi-byte chunks are accepted
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x7f, 0x62, 0xc3, 0xa9, 0x62, 0xc3, 0xb6, 0xff})) == "\xc3\xa9\xc3\xb6");
|
|
}
|
|
|
|
SECTION("many chunks in indefinite-length string")
|
|
{
|
|
// only the newly read chunk is validated, not the whole string
|
|
// collected so far; validating the latter made this input take
|
|
// quadratic time (about ten seconds for 100000 chunks)
|
|
constexpr std::size_t chunks = 100000;
|
|
std::vector<uint8_t> v{0x7f};
|
|
for (std::size_t i = 0; i < chunks; ++i)
|
|
{
|
|
v.push_back(0x61);
|
|
v.push_back('a');
|
|
}
|
|
v.push_back(0xff);
|
|
CHECK(json::from_cbor(v) == std::string(chunks, 'a'));
|
|
}
|
|
|
|
SECTION("strict mode")
|
|
{
|
|
std::vector<uint8_t> const vec = {0xf6, 0xf6};
|
|
SECTION("non-strict mode")
|
|
{
|
|
const auto result = json::from_cbor(vec, false);
|
|
CHECK(result == json());
|
|
CHECK(!json::from_cbor(vec, false, false).is_discarded());
|
|
}
|
|
|
|
SECTION("strict mode")
|
|
{
|
|
json _;
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR value: expected end of input; last byte: 0xF6", json::parse_error&);
|
|
CHECK(json::from_cbor(vec, true, false).is_discarded());
|
|
}
|
|
}
|
|
}
|
|
|
|
SECTION("SAX aborts")
|
|
{
|
|
SECTION("start_array(len)")
|
|
{
|
|
std::vector<uint8_t> const v = {0x83, 0x01, 0x02, 0x03};
|
|
SaxCountdown scp(0);
|
|
CHECK(!json::sax_parse(v, &scp, json::input_format_t::cbor));
|
|
}
|
|
|
|
SECTION("start_object(len)")
|
|
{
|
|
std::vector<uint8_t> const v = {0xA1, 0x63, 0x66, 0x6F, 0x6F, 0xF4};
|
|
SaxCountdown scp(0);
|
|
CHECK(!json::sax_parse(v, &scp, json::input_format_t::cbor));
|
|
}
|
|
|
|
SECTION("key()")
|
|
{
|
|
std::vector<uint8_t> const v = {0xA1, 0x63, 0x66, 0x6F, 0x6F, 0xF4};
|
|
SaxCountdown scp(1);
|
|
CHECK(!json::sax_parse(v, &scp, json::input_format_t::cbor));
|
|
}
|
|
}
|
|
}
|
|
|
|
// use this testcase outside [hide] to run it with Valgrind
|
|
TEST_CASE("single CBOR roundtrip")
|
|
{
|
|
SECTION("sample.json")
|
|
{
|
|
std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json";
|
|
|
|
// parse JSON file
|
|
std::ifstream f_json(filename);
|
|
const json j1 = json::parse(f_json);
|
|
|
|
// parse CBOR file
|
|
auto packed = utils::read_binary_file(filename + ".cbor");
|
|
json j2;
|
|
CHECK_NOTHROW(j2 = json::from_cbor(packed));
|
|
|
|
// compare parsed JSON values
|
|
CHECK(j1 == j2);
|
|
|
|
SECTION("roundtrips")
|
|
{
|
|
SECTION("std::ostringstream")
|
|
{
|
|
std::basic_ostringstream<char> ss;
|
|
json::to_cbor(j1, ss);
|
|
json j3 = json::from_cbor(ss.str());
|
|
CHECK(j1 == j3);
|
|
}
|
|
|
|
SECTION("std::string")
|
|
{
|
|
std::string s;
|
|
json::to_cbor(j1, s);
|
|
json j3 = json::from_cbor(s);
|
|
CHECK(j1 == j3);
|
|
}
|
|
}
|
|
|
|
// check with different start index
|
|
packed.insert(packed.begin(), 5, 0xff);
|
|
CHECK(j1 == json::from_cbor(packed.begin() + 5, packed.end()));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Parse CBOR directly from a file using iterator and sentinel")
|
|
{
|
|
std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json.cbor";
|
|
std::ifstream file(filename, std::ios::binary);
|
|
const std::istreambuf_iterator<char> first(file);
|
|
const json parsed = json::from_cbor(first, utils::istreambuf_sentinel{});
|
|
CHECK((parsed.is_object() || parsed.is_array()));
|
|
}
|
|
|
|
#if !defined(JSON_NOEXCEPTION)
|
|
TEST_CASE("CBOR regressions")
|
|
{
|
|
SECTION("fuzz test results")
|
|
{
|
|
/*
|
|
The following test cases were found during a two-day session with
|
|
AFL-Fuzz. As a result, empty byte vectors and excessive lengths are
|
|
detected.
|
|
*/
|
|
for (const std::string filename :
|
|
{
|
|
TEST_DATA_DIRECTORY "/cbor_regression/test01",
|
|
TEST_DATA_DIRECTORY "/cbor_regression/test02",
|
|
TEST_DATA_DIRECTORY "/cbor_regression/test03",
|
|
TEST_DATA_DIRECTORY "/cbor_regression/test04",
|
|
TEST_DATA_DIRECTORY "/cbor_regression/test05",
|
|
TEST_DATA_DIRECTORY "/cbor_regression/test06",
|
|
TEST_DATA_DIRECTORY "/cbor_regression/test07",
|
|
TEST_DATA_DIRECTORY "/cbor_regression/test08",
|
|
TEST_DATA_DIRECTORY "/cbor_regression/test09",
|
|
TEST_DATA_DIRECTORY "/cbor_regression/test10",
|
|
TEST_DATA_DIRECTORY "/cbor_regression/test11",
|
|
TEST_DATA_DIRECTORY "/cbor_regression/test12",
|
|
TEST_DATA_DIRECTORY "/cbor_regression/test13",
|
|
TEST_DATA_DIRECTORY "/cbor_regression/test14",
|
|
TEST_DATA_DIRECTORY "/cbor_regression/test15",
|
|
TEST_DATA_DIRECTORY "/cbor_regression/test16",
|
|
TEST_DATA_DIRECTORY "/cbor_regression/test17",
|
|
TEST_DATA_DIRECTORY "/cbor_regression/test18",
|
|
TEST_DATA_DIRECTORY "/cbor_regression/test19",
|
|
TEST_DATA_DIRECTORY "/cbor_regression/test20",
|
|
TEST_DATA_DIRECTORY "/cbor_regression/test21"
|
|
})
|
|
{
|
|
CAPTURE(filename)
|
|
|
|
try
|
|
{
|
|
// parse CBOR file
|
|
auto vec1 = utils::read_binary_file(filename);
|
|
const json j1 = json::from_cbor(vec1);
|
|
|
|
try
|
|
{
|
|
// step 2: round trip
|
|
std::vector<uint8_t> const vec2 = json::to_cbor(j1);
|
|
|
|
// parse serialization
|
|
json j2 = json::from_cbor(vec2);
|
|
|
|
// deserializations must match
|
|
CHECK(j1 == j2);
|
|
}
|
|
catch (const json::parse_error&)
|
|
{
|
|
// parsing a CBOR serialization must not fail
|
|
CHECK(false);
|
|
}
|
|
}
|
|
catch (const json::parse_error&) // NOLINT(bugprone-empty-catch)
|
|
{
|
|
// parse errors are ok, because input may be random bytes
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
TEST_CASE("CBOR definite length equal to the indefinite-length sentinel")
|
|
{
|
|
// A definite-length array or map whose declared element count equals the
|
|
// reserved unknown_size() sentinel (SIZE_MAX) must be rejected. Otherwise
|
|
// it is read as an indefinite-length container and the following bytes are
|
|
// silently accepted instead of the (impossible) count being reported.
|
|
json _;
|
|
|
|
SECTION("array")
|
|
{
|
|
// 0x9B: array with eight-byte length; length = 0xFFFFFFFFFFFFFFFF
|
|
const std::vector<uint8_t> input = {0x9B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x02, 0xFF};
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.out_of_range.408] syntax error while parsing CBOR size: excessive array size", json::out_of_range&);
|
|
}
|
|
|
|
SECTION("map")
|
|
{
|
|
// 0xBB: map with eight-byte length; length = 0xFFFFFFFFFFFFFFFF
|
|
const std::vector<uint8_t> input = {0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x61, 0x61, 0x01, 0xFF};
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.out_of_range.408] syntax error while parsing CBOR size: excessive map size", json::out_of_range&);
|
|
}
|
|
|
|
SECTION("indefinite-length containers are unaffected")
|
|
{
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x9F, 0x01, 0x02, 0xFF})) == json({1, 2}));
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xBF, 0x61, 0x61, 0x01, 0xFF})) == json({{"a", 1}}));
|
|
}
|
|
|
|
SECTION("ordinary four-byte length containers are unaffected")
|
|
{
|
|
// 0x9A/0xBA carry a four-byte length; a normal count still parses
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x9A, 0x00, 0x00, 0x00, 0x02, 0x01, 0x02})) == json({1, 2}));
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xBA, 0x00, 0x00, 0x00, 0x01, 0x61, 0x61, 0x01})) == json({{"a", 1}}));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("CBOR nesting does not consume the call stack")
|
|
{
|
|
// Containers used to be read by calling back into the value reader once
|
|
// per element, and a tag by calling it for the tagged value, so the native
|
|
// call stack grew with the nesting depth of the input. Each of the three
|
|
// costs a single byte to encode -- 0x9F, 0x81 and 0xC2 -- so a payload of
|
|
// repeated bytes crashed the process (#5104). The containers are kept on a
|
|
// heap stack now, and a tag is read in a loop.
|
|
//
|
|
// Deeply nested values must not be compared, copied or dumped here: those
|
|
// operations are still recursive and would reintroduce the crash.
|
|
json _;
|
|
|
|
SECTION("indefinite-length containers")
|
|
{
|
|
const std::vector<uint8_t> input(500000, 0x9F);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.parse_error.110] parse error at byte 500001: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
|
CHECK(json::from_cbor(input, true, false).is_discarded());
|
|
}
|
|
|
|
SECTION("definite-length containers")
|
|
{
|
|
const std::vector<uint8_t> input(500000, 0x81);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.parse_error.110] parse error at byte 500001: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
|
CHECK(json::from_cbor(input, true, false).is_discarded());
|
|
}
|
|
|
|
SECTION("tags")
|
|
{
|
|
// a tag is not a value of its own, so a chain of them used to recurse
|
|
const std::vector<uint8_t> input(500000, 0xC2);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input, true, true, json::cbor_tag_handler_t::ignore), "[json.exception.parse_error.110] parse error at byte 500001: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
|
CHECK(json::from_cbor(input, true, false, json::cbor_tag_handler_t::ignore).is_discarded());
|
|
}
|
|
|
|
SECTION("stored tags")
|
|
{
|
|
// a tag over something other than a byte string is read like for
|
|
// ignore, so a chain of them must not recurse either (#5316)
|
|
std::vector<uint8_t> input;
|
|
for (std::size_t i = 0; i < 500000; ++i)
|
|
{
|
|
input.push_back(0xD8);
|
|
input.push_back(0x18);
|
|
}
|
|
input.push_back(0x01);
|
|
CHECK(json::from_cbor(input, true, true, json::cbor_tag_handler_t::store) == 1);
|
|
}
|
|
|
|
SECTION("a well-formed deep value is read through the SAX interface")
|
|
{
|
|
std::vector<uint8_t> input(200000, 0x9F);
|
|
input.insert(input.end(), 200000, 0xFF);
|
|
|
|
SaxCountdown accept_all(1000000);
|
|
CHECK(json::sax_parse(input, &accept_all, json::input_format_t::cbor));
|
|
}
|
|
|
|
SECTION("a well-formed deep value is read into a value")
|
|
{
|
|
const std::size_t depth = 10000;
|
|
std::vector<uint8_t> input(depth, 0x81);
|
|
input.push_back(0x00);
|
|
|
|
json j = json::from_cbor(input);
|
|
|
|
std::size_t measured = 0;
|
|
const json* p = &j;
|
|
while (p->is_array() && !p->empty())
|
|
{
|
|
p = &p->front();
|
|
++measured;
|
|
}
|
|
CHECK(measured == depth);
|
|
CHECK(p->is_number());
|
|
}
|
|
|
|
SECTION("containers are still read the same way")
|
|
{
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x80})) == json::array());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xA0})) == json::object());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x9F, 0xFF})) == json::array());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xBF, 0xFF})) == json::object());
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x9F, 0x01, 0x02, 0xFF})) == json({1, 2}));
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xBF, 0x61, 'a', 0x01, 0xFF})) == json({{"a", 1}}));
|
|
// definite and indefinite forms nested inside each other
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x9F, 0x82, 0x01, 0x02, 0xA1, 0x61, 'k', 0xBF, 0xFF, 0xFF})) == json({{1, 2}, {{"k", json::object()}}}));
|
|
}
|
|
|
|
SECTION("tagged values are still read the same way")
|
|
{
|
|
const auto ignore = json::cbor_tag_handler_t::ignore;
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xC2, 0x01}), true, true, ignore) == json(1));
|
|
// a chain of tags resolves to the value that follows it
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xC2, 0xC2, 0xC2, 0x01}), true, true, ignore) == json(1));
|
|
// a tag inside a container, and one in front of a container
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x82, 0xC2, 0x01, 0x02}), true, true, ignore) == json({1, 2}));
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xC2, 0x82, 0x01, 0x02}), true, true, ignore) == json({1, 2}));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("CBOR input that cannot be read is discarded by every overload")
|
|
{
|
|
std::vector<std::uint8_t> input = json::to_cbor(json({{"a", {1, 2}}}));
|
|
input.pop_back();
|
|
|
|
json _;
|
|
CHECK_THROWS_AS(_ = json::from_cbor(input.begin(), input.end()), json::parse_error&);
|
|
CHECK(json::from_cbor(input, true, false).is_discarded());
|
|
CHECK(json::from_cbor(input.begin(), input.end(), true, false).is_discarded());
|
|
CHECK(json::from_cbor(input.data(), input.size(), true, false).is_discarded());
|
|
CHECK(json::from_cbor({input.data(), input.size()}, true, false).is_discarded());
|
|
|
|
// a string that ends early, read through iterators that are not
|
|
// contiguous and have to be copied from one element at a time
|
|
const std::list<std::uint8_t> truncated_string = {0x63, 'a', 'b'};
|
|
CHECK(json::from_cbor(truncated_string.begin(), truncated_string.end(), true, false).is_discarded());
|
|
const std::list<std::uint8_t> complete_string = {0x63, 'a', 'b', 'c'};
|
|
CHECK(json::from_cbor(complete_string.begin(), complete_string.end()) == "abc");
|
|
}
|
|
|
|
TEST_CASE("CBOR SAX parsing stops at every event")
|
|
{
|
|
// Containers are opened and closed by the loop that reads them; a SAX
|
|
// handler that rejects any event - including the end of a nested
|
|
// container - must stop the parse right there.
|
|
const auto count_events = [](const std::vector<std::uint8_t>& input)
|
|
{
|
|
int events = 0;
|
|
while (true)
|
|
{
|
|
SaxCountdown scp(events);
|
|
if (json::sax_parse(input, &scp, json::input_format_t::cbor))
|
|
{
|
|
return events;
|
|
}
|
|
++events;
|
|
REQUIRE(events < 1000);
|
|
}
|
|
};
|
|
|
|
// 20 events: every container kind closes inside another one
|
|
const json j = json::parse(R"({"a": [1, {"b": []}], "c": {"d": [[2]]}})");
|
|
CHECK(count_events(json::to_cbor(j)) == 20);
|
|
CHECK(count_events(std::vector<std::uint8_t>({0xBF, 0x61, 'a', 0x9F, 0x01, 0xFF, 0xFF})) == 6);
|
|
}
|
|
|
|
TEST_CASE("CBOR indefinite-length strings do not recurse per chunk")
|
|
{
|
|
// Reading an indefinite-length string or byte array used to call itself
|
|
// once per chunk, so a payload of repeated 0x7F (or 0x5F) bytes exhausted
|
|
// the call stack before any of the input was rejected. The open levels are
|
|
// counted now, and the levels below prove the reader still reads the same
|
|
// values and reports the same errors at the same byte offsets.
|
|
json _;
|
|
|
|
SECTION("many open levels are reported, not crashed on")
|
|
{
|
|
const std::vector<uint8_t> input(200000, 0x7F);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.parse_error.110] parse error at byte 200001: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
|
|
CHECK(json::from_cbor(input, true, false).is_discarded());
|
|
}
|
|
|
|
SECTION("many open levels are reported, not crashed on (binary)")
|
|
{
|
|
const std::vector<uint8_t> input(200000, 0x5F);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.parse_error.110] parse error at byte 200001: syntax error while parsing CBOR binary: unexpected end of input", json::parse_error&);
|
|
CHECK(json::from_cbor(input, true, false).is_discarded());
|
|
}
|
|
|
|
SECTION("chunks are still concatenated")
|
|
{
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0xFF})) == json(""));
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0x61, 0x61, 0xFF})) == json("a"));
|
|
// nested indefinite-length strings are concatenated across levels
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0x7F, 0x61, 0x61, 0xFF, 0x61, 0x62, 0xFF})) == json("ab"));
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0x7F, 0x7F, 0x61, 0x7A, 0xFF, 0xFF, 0xFF})) == json("z"));
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xA1, 0x7F, 0x61, 0x61, 0xFF, 0x01})) == json({{"a", 1}}));
|
|
}
|
|
|
|
SECTION("chunks are still concatenated (binary)")
|
|
{
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x5F, 0x41, 0x61, 0xFF})) == json::binary({0x61}));
|
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x5F, 0x5F, 0x41, 0x61, 0xFF, 0x41, 0x62, 0xFF})) == json::binary({0x61, 0x62}));
|
|
}
|
|
|
|
SECTION("a chunk that is not a string is still rejected")
|
|
{
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x7F, 0x7F, 0x00})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x00", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x5F, 0x5F, 0x00})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR binary: expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x00", json::parse_error&);
|
|
}
|
|
|
|
SECTION("a break marker outside an indefinite-length string is not a string")
|
|
{
|
|
// 0xFF only closes a string that was opened; on its own it is not one
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0xFF, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found a break stop code; last byte: 0xFF", json::parse_error&);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("issue #5405 - array reserve for definite-length CBOR arrays")
|
|
{
|
|
#if !defined(JSON_NOEXCEPTION)
|
|
// this SECTION relies on catching a thrown exception to distinguish
|
|
// which of two acceptable, bounded rejections a hostile header took;
|
|
// under JSON_NOEXCEPTION, JSON_THROW never produces a catchable C++
|
|
// exception (it aborts instead), so this cannot be tested that way here
|
|
SECTION("a huge claimed length with no element data must not over-allocate")
|
|
{
|
|
// 0x9A: array with a four-byte length; claims 0xFFFFFFFF (4294967295)
|
|
// elements but provides none. max_size() for a std::vector is far
|
|
// larger than this count, so it does not reject the header outright;
|
|
// the (capped) reservation must not attempt to allocate space for
|
|
// billions of elements before the missing data is detected.
|
|
json _;
|
|
const std::vector<uint8_t> input = {0x9A, 0xFF, 0xFF, 0xFF, 0xFF};
|
|
// On a platform where std::size_t is narrower than 64 bits (e.g.
|
|
// 32-bit), the claimed count 0xFFFFFFFF coincides with that
|
|
// platform's detail::unknown_size() sentinel (SIZE_MAX), so the
|
|
// format-level size check rejects it outright (out_of_range.408,
|
|
// "excessive ... size") before the SAX consumer's own max_size()
|
|
// check would even run; on a 64-bit platform it passes both of
|
|
// those checks and is only found short of data once the (capped)
|
|
// reservation looks for element bytes that were never provided
|
|
// (parse_error.110). Either is an acceptable, bounded rejection of
|
|
// the hostile header -- the property under test is that no path
|
|
// attempts to allocate space for billions of elements.
|
|
bool threw = false;
|
|
try
|
|
{
|
|
_ = json::from_cbor(input);
|
|
}
|
|
catch (const json::parse_error& e)
|
|
{
|
|
threw = true;
|
|
CHECK(e.id == 110);
|
|
CHECK(std::string(e.what()) == "[json.exception.parse_error.110] parse error at byte 6: syntax error while parsing CBOR value: unexpected end of input");
|
|
}
|
|
catch (const json::out_of_range& e)
|
|
{
|
|
threw = true;
|
|
CHECK(e.id == 408);
|
|
CHECK(std::string(e.what()).find("excessive") != std::string::npos);
|
|
}
|
|
CHECK(threw);
|
|
CHECK(json::from_cbor(input, true, false).is_discarded());
|
|
}
|
|
#endif
|
|
|
|
SECTION("arrays of various sizes decode to the same value as before the reserve optimization")
|
|
{
|
|
for (const auto size :
|
|
{
|
|
std::size_t{0}, std::size_t{1}, std::size_t{5}, // small
|
|
std::size_t{16384}, // exactly at the reserve cap
|
|
std::size_t{20000} // above the reserve cap
|
|
})
|
|
{
|
|
CAPTURE(size)
|
|
json j = json::array();
|
|
for (std::size_t i = 0; i < size; ++i)
|
|
{
|
|
j.push_back(static_cast<int>(i % 1000));
|
|
}
|
|
|
|
const auto packed = json::to_cbor(j);
|
|
CHECK(json::from_cbor(packed) == j);
|
|
}
|
|
}
|
|
|
|
SECTION("a user-defined SAX consumer is unaffected by the internal DOM reserve optimization")
|
|
{
|
|
// the reserve() call is local to json_sax_dom_parser / json_sax_dom_callback_parser;
|
|
// a custom SAX consumer that does not touch a DOM array sees identical events
|
|
json j = json::array();
|
|
for (int i = 0; i < 100; ++i)
|
|
{
|
|
j.push_back(i);
|
|
}
|
|
const auto packed = json::to_cbor(j);
|
|
|
|
SaxCountdown scp(1000000); // large enough to never trigger an abort
|
|
CHECK(json::sax_parse(packed, &scp, json::input_format_t::cbor));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("CBOR round-trip invariants")
|
|
{
|
|
// This checks what the parse_cbor_fuzzer driver checks (see
|
|
// tests/src/fuzzer-parse_cbor.cpp), so that a regression shows up in CI
|
|
// rather than as an OSS-Fuzz report: anything from_cbor() returns (j1)
|
|
// can be serialized, parsed back (j2), and serialized again to reproduce
|
|
// the exact bytes.
|
|
for (const auto& j0 : utils::round_trip_corpus::values())
|
|
{
|
|
json j1;
|
|
try
|
|
{
|
|
// turn the corpus value into a value as from_cbor() returns it
|
|
j1 = json::from_cbor(json::to_cbor(j0));
|
|
}
|
|
catch (const json::exception&)
|
|
{
|
|
// not every corpus value survives a CBOR round trip (e.g., a
|
|
// binary subtype is written with a tag the default tag handler
|
|
// then rejects); the fuzzer driver only ever sees values
|
|
// from_cbor() actually produced, so skip those here, too
|
|
continue;
|
|
}
|
|
|
|
INFO("j1 = " << j1.dump());
|
|
const std::vector<std::uint8_t> vec = json::to_cbor(j1);
|
|
json j2;
|
|
// anything the library writes must be parsable by the library
|
|
REQUIRE_NOTHROW(j2 = json::from_cbor(vec));
|
|
CHECK(json::to_cbor(j2) == vec);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("CBOR roundtrips" * doctest::skip())
|
|
{
|
|
SECTION("input from flynn")
|
|
{
|
|
// most of these are excluded due to differences in key order (not a real problem)
|
|
std::set<std::string> exclude_packed;
|
|
exclude_packed.insert(TEST_DATA_DIRECTORY "/json.org/1.json");
|
|
exclude_packed.insert(TEST_DATA_DIRECTORY "/json.org/2.json");
|
|
exclude_packed.insert(TEST_DATA_DIRECTORY "/json.org/3.json");
|
|
exclude_packed.insert(TEST_DATA_DIRECTORY "/json.org/4.json");
|
|
exclude_packed.insert(TEST_DATA_DIRECTORY "/json.org/5.json");
|
|
exclude_packed.insert(TEST_DATA_DIRECTORY "/json_testsuite/sample.json"); // kills AppVeyor
|
|
exclude_packed.insert(TEST_DATA_DIRECTORY "/json_tests/pass1.json");
|
|
exclude_packed.insert(TEST_DATA_DIRECTORY "/regression/working_file.json");
|
|
exclude_packed.insert(TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object.json");
|
|
exclude_packed.insert(TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_duplicated_key.json");
|
|
exclude_packed.insert(TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_long_strings.json");
|
|
|
|
for (const std::string filename :
|
|
{
|
|
TEST_DATA_DIRECTORY "/json_nlohmann_tests/all_unicode.json",
|
|
TEST_DATA_DIRECTORY "/json.org/1.json",
|
|
TEST_DATA_DIRECTORY "/json.org/2.json",
|
|
TEST_DATA_DIRECTORY "/json.org/3.json",
|
|
TEST_DATA_DIRECTORY "/json.org/4.json",
|
|
TEST_DATA_DIRECTORY "/json.org/5.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip01.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip02.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip03.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip04.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip05.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip06.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip07.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip08.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip09.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip10.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip11.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip12.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip13.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip14.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip15.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip16.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip17.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip18.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip19.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip20.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip21.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip22.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip23.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip24.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip25.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip26.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip27.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip28.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip29.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip30.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip31.json",
|
|
TEST_DATA_DIRECTORY "/json_roundtrip/roundtrip32.json",
|
|
TEST_DATA_DIRECTORY "/json_testsuite/sample.json", // kills AppVeyor
|
|
TEST_DATA_DIRECTORY "/json_tests/pass1.json",
|
|
TEST_DATA_DIRECTORY "/json_tests/pass2.json",
|
|
TEST_DATA_DIRECTORY "/json_tests/pass3.json",
|
|
TEST_DATA_DIRECTORY "/regression/floats.json",
|
|
TEST_DATA_DIRECTORY "/regression/signed_ints.json",
|
|
TEST_DATA_DIRECTORY "/regression/unsigned_ints.json",
|
|
TEST_DATA_DIRECTORY "/regression/working_file.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_arraysWithSpaces.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_empty-string.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_empty.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_ending_with_newline.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_false.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_heterogeneous.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_null.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_1_and_newline.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_leading_space.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_several_null.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_array_with_trailing_space.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_0e+1.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_0e1.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_after_space.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_double_close_to_zero.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_double_huge_neg_exp.json",
|
|
//TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_huge_exp.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_int_with_exp.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_minus_zero.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_negative_int.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_negative_one.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_negative_zero.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_capital_e.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_capital_e_neg_exp.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_capital_e_pos_exp.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_exponent.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_fraction_exponent.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_neg_exp.json",
|
|
//TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_neg_overflow.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_pos_exponent.json",
|
|
//TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_pos_overflow.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_real_underflow.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_simple_int.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_simple_real.json",
|
|
//TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_too_big_neg_int.json",
|
|
//TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_too_big_pos_int.json",
|
|
//TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_number_very_big_negative_int.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_basic.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_duplicated_key.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_duplicated_key_and_value.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_empty.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_empty_key.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_escaped_null_in_key.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_extreme_numbers.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_long_strings.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_simple.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_string_unicode.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_object_with_newlines.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_UTF-16_Surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pair.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_accepted_surrogate_pairs.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_allowed_escapes.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_backslash_and_u_escaped_zero.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_backslash_doublequotes.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_comments.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_double_escape_a.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_double_escape_n.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_escaped_control_character.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_escaped_noncharacter.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_in_array.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_in_array_with_leading_space.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_last_surrogates_1_and_2.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_newline_uescaped.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+1FFFF.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_null_escape.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_one-byte-utf-8.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_pi.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_simple_ascii.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_space.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_three-byte-utf-8.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_two-byte-utf-8.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_u+2028_line_sep.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_u+2029_par_sep.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_uEscape.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unescaped_char_delete.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicodeEscapedBackslash.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_2.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_U+2064_invisible_plus.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_unicode_escaped_double_quote.json",
|
|
// TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_utf16.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_utf8.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_string_with_del_character.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_false.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_int.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_negative_real.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_null.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_string.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_lonely_true.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_string_empty.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_trailing_newline.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_true_in_array.json",
|
|
TEST_DATA_DIRECTORY "/nst_json_testsuite/test_parsing/y_structure_whitespace_array.json"
|
|
})
|
|
{
|
|
CAPTURE(filename)
|
|
|
|
std::ifstream f_json(filename);
|
|
const json j1 = json::parse(f_json);
|
|
const auto packed = utils::read_binary_file(filename + ".cbor");
|
|
|
|
{
|
|
INFO_WITH_TEMP(filename + ": std::vector<uint8_t>");
|
|
json j2;
|
|
CHECK_NOTHROW(j2 = json::from_cbor(packed));
|
|
CHECK(j1 == j2);
|
|
}
|
|
|
|
{
|
|
INFO_WITH_TEMP(filename + ": std::ifstream");
|
|
std::ifstream f_cbor(filename + ".cbor", std::ios::binary);
|
|
json j2;
|
|
CHECK_NOTHROW(j2 = json::from_cbor(f_cbor));
|
|
CHECK(j1 == j2);
|
|
}
|
|
|
|
{
|
|
INFO_WITH_TEMP(filename + ": uint8_t* and size");
|
|
json j2;
|
|
CHECK_NOTHROW(j2 = json::from_cbor({packed.data(), packed.size()}));
|
|
CHECK(j1 == j2);
|
|
}
|
|
|
|
{
|
|
INFO_WITH_TEMP(filename + ": output to output adapters");
|
|
if (exclude_packed.count(filename) == 0u)
|
|
{
|
|
{
|
|
INFO_WITH_TEMP(filename + ": output adapters: std::vector<uint8_t>");
|
|
std::vector<uint8_t> vec;
|
|
json::to_cbor(j1, vec);
|
|
CHECK(vec == packed);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if !defined(JSON_NOEXCEPTION)
|
|
TEST_CASE("all CBOR first bytes")
|
|
{
|
|
// these bytes will fail immediately with exception parse_error.112
|
|
std::set<uint8_t> unsupported =
|
|
{
|
|
//// types not supported by this library
|
|
|
|
// date/time
|
|
0xc0, 0xc1,
|
|
// bignum
|
|
0xc2, 0xc3,
|
|
// decimal fracion
|
|
0xc4,
|
|
// bigfloat
|
|
0xc5,
|
|
// tagged item
|
|
0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd,
|
|
0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd8,
|
|
0xd9, 0xda, 0xdb,
|
|
// expected conversion
|
|
0xd5, 0xd6, 0xd7,
|
|
// simple value
|
|
0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
|
|
0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xef, 0xf0,
|
|
0xf1, 0xf2, 0xf3,
|
|
0xf8,
|
|
// undefined
|
|
0xf7,
|
|
|
|
//// bytes not specified by CBOR
|
|
|
|
0x1c, 0x1d, 0x1e, 0x1f,
|
|
0x3c, 0x3d, 0x3e, 0x3f,
|
|
0x5c, 0x5d, 0x5e,
|
|
0x7c, 0x7d, 0x7e,
|
|
0x9c, 0x9d, 0x9e,
|
|
0xbc, 0xbd, 0xbe,
|
|
0xdc, 0xdd, 0xde, 0xdf,
|
|
0xee,
|
|
0xfc, 0xfe, 0xfd,
|
|
|
|
/// break cannot be the first byte
|
|
|
|
0xff
|
|
};
|
|
|
|
for (auto i = 0; i < 256; ++i)
|
|
{
|
|
const auto byte = static_cast<uint8_t>(i);
|
|
|
|
try
|
|
{
|
|
auto res = json::from_cbor(std::vector<uint8_t>(1, byte));
|
|
}
|
|
catch (const json::parse_error& e)
|
|
{
|
|
// check that parse_error.112 is only thrown if the
|
|
// first byte is in the unsupported set
|
|
INFO_WITH_TEMP(e.what());
|
|
if (unsupported.find(byte) != unsupported.end())
|
|
{
|
|
CHECK(e.id == 112);
|
|
}
|
|
else
|
|
{
|
|
CHECK(e.id != 112);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
TEST_CASE("examples from RFC 8949 Appendix A")
|
|
{
|
|
SECTION("numbers")
|
|
{
|
|
CHECK(json::to_cbor(json::parse("0")) == std::vector<uint8_t>({0x00}));
|
|
CHECK(json::parse("0") == json::from_cbor(std::vector<uint8_t>({0x00})));
|
|
|
|
CHECK(json::to_cbor(json::parse("1")) == std::vector<uint8_t>({0x01}));
|
|
CHECK(json::parse("1") == json::from_cbor(std::vector<uint8_t>({0x01})));
|
|
|
|
CHECK(json::to_cbor(json::parse("10")) == std::vector<uint8_t>({0x0a}));
|
|
CHECK(json::parse("10") == json::from_cbor(std::vector<uint8_t>({0x0a})));
|
|
|
|
CHECK(json::to_cbor(json::parse("23")) == std::vector<uint8_t>({0x17}));
|
|
CHECK(json::parse("23") == json::from_cbor(std::vector<uint8_t>({0x17})));
|
|
|
|
CHECK(json::to_cbor(json::parse("24")) == std::vector<uint8_t>({0x18, 0x18}));
|
|
CHECK(json::parse("24") == json::from_cbor(std::vector<uint8_t>({0x18, 0x18})));
|
|
|
|
CHECK(json::to_cbor(json::parse("25")) == std::vector<uint8_t>({0x18, 0x19}));
|
|
CHECK(json::parse("25") == json::from_cbor(std::vector<uint8_t>({0x18, 0x19})));
|
|
|
|
CHECK(json::to_cbor(json::parse("100")) == std::vector<uint8_t>({0x18, 0x64}));
|
|
CHECK(json::parse("100") == json::from_cbor(std::vector<uint8_t>({0x18, 0x64})));
|
|
|
|
CHECK(json::to_cbor(json::parse("1000")) == std::vector<uint8_t>({0x19, 0x03, 0xe8}));
|
|
CHECK(json::parse("1000") == json::from_cbor(std::vector<uint8_t>({0x19, 0x03, 0xe8})));
|
|
|
|
CHECK(json::to_cbor(json::parse("1000000")) == std::vector<uint8_t>({0x1a, 0x00, 0x0f, 0x42, 0x40}));
|
|
CHECK(json::parse("1000000") == json::from_cbor(std::vector<uint8_t>({0x1a, 0x00, 0x0f, 0x42, 0x40})));
|
|
|
|
CHECK(json::to_cbor(json::parse("1000000000000")) == std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0xe8, 0xd4, 0xa5, 0x10, 0x00}));
|
|
CHECK(json::parse("1000000000000") == json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0xe8, 0xd4, 0xa5, 0x10, 0x00})));
|
|
|
|
CHECK(json::to_cbor(json::parse("18446744073709551615")) == std::vector<uint8_t>({0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}));
|
|
CHECK(json::parse("18446744073709551615") == json::from_cbor(std::vector<uint8_t>({0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})));
|
|
|
|
// positive bignum is not supported
|
|
//CHECK(json::to_cbor(json::parse("18446744073709551616")) == std::vector<uint8_t>({0xc2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}));
|
|
//CHECK(json::parse("18446744073709551616") == json::from_cbor(std::vector<uint8_t>({0xc2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})));
|
|
|
|
//CHECK(json::to_cbor(json::parse("-18446744073709551616")) == std::vector<uint8_t>({0x3b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}));
|
|
//CHECK(json::parse("-18446744073709551616") == json::from_cbor(std::vector<uint8_t>({0x3b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff})));
|
|
|
|
// negative bignum is not supported
|
|
//CHECK(json::to_cbor(json::parse("-18446744073709551617")) == std::vector<uint8_t>({0xc3, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}));
|
|
//CHECK(json::parse("-18446744073709551617") == json::from_cbor(std::vector<uint8_t>({0xc3, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})));
|
|
|
|
CHECK(json::to_cbor(json::parse("-1")) == std::vector<uint8_t>({0x20}));
|
|
CHECK(json::parse("-1") == json::from_cbor(std::vector<uint8_t>({0x20})));
|
|
|
|
CHECK(json::to_cbor(json::parse("-10")) == std::vector<uint8_t>({0x29}));
|
|
CHECK(json::parse("-10") == json::from_cbor(std::vector<uint8_t>({0x29})));
|
|
|
|
CHECK(json::to_cbor(json::parse("-100")) == std::vector<uint8_t>({0x38, 0x63}));
|
|
CHECK(json::parse("-100") == json::from_cbor(std::vector<uint8_t>({0x38, 0x63})));
|
|
|
|
CHECK(json::to_cbor(json::parse("-1000")) == std::vector<uint8_t>({0x39, 0x03, 0xe7}));
|
|
CHECK(json::parse("-1000") == json::from_cbor(std::vector<uint8_t>({0x39, 0x03, 0xe7})));
|
|
|
|
// half-precision float
|
|
//CHECK(json::to_cbor(json::parse("0.0")) == std::vector<uint8_t>({0xf9, 0x00, 0x00}));
|
|
CHECK(json::parse("0.0") == json::from_cbor(std::vector<uint8_t>({0xf9, 0x00, 0x00})));
|
|
|
|
// half-precision float
|
|
//CHECK(json::to_cbor(json::parse("-0.0")) == std::vector<uint8_t>({0xf9, 0x80, 0x00}));
|
|
CHECK(json::parse("-0.0") == json::from_cbor(std::vector<uint8_t>({0xf9, 0x80, 0x00})));
|
|
|
|
// half-precision float
|
|
//CHECK(json::to_cbor(json::parse("1.0")) == std::vector<uint8_t>({0xf9, 0x3c, 0x00}));
|
|
CHECK(json::parse("1.0") == json::from_cbor(std::vector<uint8_t>({0xf9, 0x3c, 0x00})));
|
|
|
|
CHECK(json::to_cbor(json::parse("1.1")) == std::vector<uint8_t>({0xfb, 0x3f, 0xf1, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a}));
|
|
CHECK(json::parse("1.1") == json::from_cbor(std::vector<uint8_t>({0xfb, 0x3f, 0xf1, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a})));
|
|
|
|
// the writer never emits half-precision floats, so these can only be decoded, not encoded
|
|
CHECK(json::parse("1.5") == json::from_cbor(std::vector<uint8_t>({0xf9, 0x3e, 0x00})));
|
|
CHECK(json::parse("65504.0") == json::from_cbor(std::vector<uint8_t>({0xf9, 0x7b, 0xff})));
|
|
|
|
CHECK(json::to_cbor(json::parse("100000.0")) == std::vector<uint8_t>({0xfa, 0x47, 0xc3, 0x50, 0x00}));
|
|
CHECK(json::parse("100000.0") == json::from_cbor(std::vector<uint8_t>({0xfa, 0x47, 0xc3, 0x50, 0x00})));
|
|
|
|
CHECK(json::to_cbor(json::parse("3.4028234663852886e+38")) == std::vector<uint8_t>({0xfa, 0x7f, 0x7f, 0xff, 0xff}));
|
|
CHECK(json::parse("3.4028234663852886e+38") == json::from_cbor(std::vector<uint8_t>({0xfa, 0x7f, 0x7f, 0xff, 0xff})));
|
|
|
|
CHECK(json::to_cbor(json::parse("1.0e+300")) == std::vector<uint8_t>({0xfb, 0x7e, 0x37, 0xe4, 0x3c, 0x88, 0x00, 0x75, 0x9c}));
|
|
CHECK(json::parse("1.0e+300") == json::from_cbor(std::vector<uint8_t>({0xfb, 0x7e, 0x37, 0xe4, 0x3c, 0x88, 0x00, 0x75, 0x9c})));
|
|
|
|
CHECK(json::parse("5.960464477539063e-8") == json::from_cbor(std::vector<uint8_t>({0xf9, 0x00, 0x01})));
|
|
CHECK(json::parse("0.00006103515625") == json::from_cbor(std::vector<uint8_t>({0xf9, 0x04, 0x00})));
|
|
CHECK(json::parse("-4.0") == json::from_cbor(std::vector<uint8_t>({0xf9, 0xc4, 0x00})));
|
|
|
|
CHECK(json::to_cbor(json::parse("-4.1")) == std::vector<uint8_t>({0xfb, 0xc0, 0x10, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66}));
|
|
CHECK(json::parse("-4.1") == json::from_cbor(std::vector<uint8_t>({0xfb, 0xc0, 0x10, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66})));
|
|
}
|
|
|
|
SECTION("simple values")
|
|
{
|
|
CHECK(json::to_cbor(json::parse("false")) == std::vector<uint8_t>({0xf4}));
|
|
CHECK(json::parse("false") == json::from_cbor(std::vector<uint8_t>({0xf4})));
|
|
|
|
CHECK(json::to_cbor(json::parse("true")) == std::vector<uint8_t>({0xf5}));
|
|
CHECK(json::parse("true") == json::from_cbor(std::vector<uint8_t>({0xf5})));
|
|
|
|
CHECK(json::to_cbor(json::parse("true")) == std::vector<uint8_t>({0xf5}));
|
|
CHECK(json::parse("true") == json::from_cbor(std::vector<uint8_t>({0xf5})));
|
|
}
|
|
|
|
SECTION("strings")
|
|
{
|
|
CHECK(json::to_cbor(json::parse("\"\"")) == std::vector<uint8_t>({0x60}));
|
|
CHECK(json::parse("\"\"") == json::from_cbor(std::vector<uint8_t>({0x60})));
|
|
|
|
CHECK(json::to_cbor(json::parse("\"a\"")) == std::vector<uint8_t>({0x61, 0x61}));
|
|
CHECK(json::parse("\"a\"") == json::from_cbor(std::vector<uint8_t>({0x61, 0x61})));
|
|
|
|
CHECK(json::to_cbor(json::parse("\"IETF\"")) == std::vector<uint8_t>({0x64, 0x49, 0x45, 0x54, 0x46}));
|
|
CHECK(json::parse("\"IETF\"") == json::from_cbor(std::vector<uint8_t>({0x64, 0x49, 0x45, 0x54, 0x46})));
|
|
|
|
CHECK(json::to_cbor(json::parse("\"\\u00fc\"")) == std::vector<uint8_t>({0x62, 0xc3, 0xbc}));
|
|
CHECK(json::parse("\"\\u00fc\"") == json::from_cbor(std::vector<uint8_t>({0x62, 0xc3, 0xbc})));
|
|
|
|
CHECK(json::to_cbor(json::parse("\"\\u6c34\"")) == std::vector<uint8_t>({0x63, 0xe6, 0xb0, 0xb4}));
|
|
CHECK(json::parse("\"\\u6c34\"") == json::from_cbor(std::vector<uint8_t>({0x63, 0xe6, 0xb0, 0xb4})));
|
|
|
|
CHECK(json::to_cbor(json::parse("\"\\ud800\\udd51\"")) == std::vector<uint8_t>({0x64, 0xf0, 0x90, 0x85, 0x91}));
|
|
CHECK(json::parse("\"\\ud800\\udd51\"") == json::from_cbor(std::vector<uint8_t>({0x64, 0xf0, 0x90, 0x85, 0x91})));
|
|
|
|
// indefinite length strings
|
|
CHECK(json::parse("\"streaming\"") == json::from_cbor(std::vector<uint8_t>({0x7f, 0x65, 0x73, 0x74, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x67, 0xff})));
|
|
}
|
|
|
|
SECTION("byte arrays")
|
|
{
|
|
const auto packed = utils::read_binary_file(TEST_DATA_DIRECTORY "/binary_data/cbor_binary.cbor");
|
|
json j;
|
|
CHECK_NOTHROW(j = json::from_cbor(packed));
|
|
|
|
const auto expected = utils::read_binary_file(TEST_DATA_DIRECTORY "/binary_data/cbor_binary.out");
|
|
CHECK(j == json::binary(expected));
|
|
|
|
// 0xd8
|
|
CHECK(json::to_cbor(json::binary(std::vector<uint8_t> {}, 0x42)) == std::vector<uint8_t> {0xd8, 0x42, 0x40});
|
|
CHECK(!json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 0x42)), true, true, json::cbor_tag_handler_t::ignore).get_binary().has_subtype());
|
|
CHECK(json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 0x42)), true, true, json::cbor_tag_handler_t::store).get_binary().subtype() == 0x42);
|
|
// 0xd9
|
|
CHECK(json::to_cbor(json::binary(std::vector<uint8_t> {}, 1000)) == std::vector<uint8_t> {0xd9, 0x03, 0xe8, 0x40});
|
|
CHECK(!json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 1000)), true, true, json::cbor_tag_handler_t::ignore).get_binary().has_subtype());
|
|
CHECK(json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 1000)), true, true, json::cbor_tag_handler_t::store).get_binary().subtype() == 1000);
|
|
// 0xda
|
|
CHECK(json::to_cbor(json::binary(std::vector<uint8_t> {}, 394216)) == std::vector<uint8_t> {0xda, 0x00, 0x06, 0x03, 0xe8, 0x40});
|
|
CHECK(!json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 394216)), true, true, json::cbor_tag_handler_t::ignore).get_binary().has_subtype());
|
|
CHECK(json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 394216)), true, true, json::cbor_tag_handler_t::store).get_binary().subtype() == 394216);
|
|
// 0xdb
|
|
CHECK(json::to_cbor(json::binary(std::vector<uint8_t> {}, 8589934590)) == std::vector<uint8_t> {0xdb, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xfe, 0x40});
|
|
CHECK(!json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 8589934590)), true, true, json::cbor_tag_handler_t::ignore).get_binary().has_subtype());
|
|
CHECK(json::from_cbor(json::to_cbor(json::binary(std::vector<uint8_t> {}, 8589934590)), true, true, json::cbor_tag_handler_t::store).get_binary().subtype() == 8589934590);
|
|
}
|
|
|
|
SECTION("arrays")
|
|
{
|
|
CHECK(json::to_cbor(json::parse("[]")) == std::vector<uint8_t>({0x80}));
|
|
CHECK(json::parse("[]") == json::from_cbor(std::vector<uint8_t>({0x80})));
|
|
|
|
CHECK(json::to_cbor(json::parse("[1, 2, 3]")) == std::vector<uint8_t>({0x83, 0x01, 0x02, 0x03}));
|
|
CHECK(json::parse("[1, 2, 3]") == json::from_cbor(std::vector<uint8_t>({0x83, 0x01, 0x02, 0x03})));
|
|
|
|
CHECK(json::to_cbor(json::parse("[1, [2, 3], [4, 5]]")) == std::vector<uint8_t>({0x83, 0x01, 0x82, 0x02, 0x03, 0x82, 0x04, 0x05}));
|
|
CHECK(json::parse("[1, [2, 3], [4, 5]]") == json::from_cbor(std::vector<uint8_t>({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<uint8_t>({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<uint8_t>({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<uint8_t>({0x9f, 0xff})));
|
|
CHECK(json::parse("[1, [2, 3], [4, 5]] ") == json::from_cbor(std::vector<uint8_t>({0x9f, 0x01, 0x82, 0x02, 0x03, 0x9f, 0x04, 0x05, 0xff, 0xff})));
|
|
CHECK(json::parse("[1, [2, 3], [4, 5]]") == json::from_cbor(std::vector<uint8_t>({0x9f, 0x01, 0x82, 0x02, 0x03, 0x82, 0x04, 0x05, 0xff})));
|
|
CHECK(json::parse("[1, [2, 3], [4, 5]]") == json::from_cbor(std::vector<uint8_t>({0x83, 0x01, 0x82, 0x02, 0x03, 0x9f, 0x04, 0x05, 0xff})));
|
|
CHECK(json::parse("[1, [2, 3], [4, 5]]") == json::from_cbor(std::vector<uint8_t>({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<uint8_t>({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<uint8_t>({0xa0}));
|
|
CHECK(json::parse("{}") == json::from_cbor(std::vector<uint8_t>({0xa0})));
|
|
|
|
CHECK(json::to_cbor(json::parse("{\"a\": 1, \"b\": [2, 3]}")) == std::vector<uint8_t>({0xa2, 0x61, 0x61, 0x01, 0x61, 0x62, 0x82, 0x02, 0x03}));
|
|
CHECK(json::parse("{\"a\": 1, \"b\": [2, 3]}") == json::from_cbor(std::vector<uint8_t>({0xa2, 0x61, 0x61, 0x01, 0x61, 0x62, 0x82, 0x02, 0x03})));
|
|
|
|
CHECK(json::to_cbor(json::parse("[\"a\", {\"b\": \"c\"}]")) == std::vector<uint8_t>({0x82, 0x61, 0x61, 0xa1, 0x61, 0x62, 0x61, 0x63}));
|
|
CHECK(json::parse("[\"a\", {\"b\": \"c\"}]") == json::from_cbor(std::vector<uint8_t>({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<uint8_t>({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<uint8_t>({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<uint8_t>({0xbf, 0x61, 0x61, 0x01, 0x61, 0x62, 0x9f, 0x02, 0x03, 0xff, 0xff})));
|
|
CHECK(json::parse("[\"a\", {\"b\": \"c\"}]") == json::from_cbor(std::vector<uint8_t>({0x82, 0x61, 0x61, 0xbf, 0x61, 0x62, 0x61, 0x63, 0xff})));
|
|
CHECK(json::parse("{\"Fun\": true, \"Amt\": -2}") == json::from_cbor(std::vector<uint8_t>({0xbf, 0x63, 0x46, 0x75, 0x6e, 0xf5, 0x63, 0x41, 0x6d, 0x74, 0x21, 0xff})));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("Tagged values")
|
|
{
|
|
const json j = "s";
|
|
auto v = json::to_cbor(j);
|
|
|
|
const json j_bin_payload = json::binary(std::vector<std::uint8_t> {0x01, 0x02, 0x03});
|
|
auto v_bin_payload = json::to_cbor(j_bin_payload);
|
|
|
|
SECTION("0xC0..0xD7")
|
|
{
|
|
for (const auto b : std::vector<std::uint8_t>
|
|
{
|
|
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5,
|
|
0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4,
|
|
0xD5, 0xD6, 0xD7
|
|
})
|
|
{
|
|
CAPTURE(b)
|
|
|
|
// add tag to value
|
|
auto v_tagged = v;
|
|
v_tagged.insert(v_tagged.begin(), b);
|
|
|
|
// check that parsing fails in error mode
|
|
json _;
|
|
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged), json::parse_error);
|
|
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::error), json::parse_error);
|
|
|
|
// check that parsing succeeds and gets original value in ignore mode
|
|
auto j_tagged = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::ignore);
|
|
CHECK(j_tagged == j);
|
|
|
|
auto j_tagged_stored = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::store);
|
|
CHECK(j_tagged_stored == j);
|
|
|
|
auto v_binary_tagged = v_bin_payload;
|
|
v_binary_tagged.insert(v_binary_tagged.begin(), b);
|
|
auto j_binary_tagged_stored = json::from_cbor(v_binary_tagged, true, true, json::cbor_tag_handler_t::store);
|
|
CHECK(j_binary_tagged_stored == j_bin_payload);
|
|
CHECK(!j_binary_tagged_stored.get_binary().has_subtype());
|
|
}
|
|
}
|
|
|
|
SECTION("0xD8 - 1 byte follows")
|
|
{
|
|
SECTION("success")
|
|
{
|
|
// add tag to value
|
|
auto v_tagged = v;
|
|
v_tagged.insert(v_tagged.begin(), 0x42); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0xD8); // tag
|
|
|
|
// check that parsing fails in error mode
|
|
json _;
|
|
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged), json::parse_error);
|
|
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::error), json::parse_error);
|
|
|
|
// check that parsing succeeds and gets original value in ignore mode
|
|
auto j_tagged = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::ignore);
|
|
CHECK(j_tagged == j);
|
|
}
|
|
|
|
SECTION("missing byte after tag")
|
|
{
|
|
// add tag to value
|
|
auto v_tagged = v;
|
|
v_tagged.insert(v_tagged.begin(), 0xD8); // tag
|
|
|
|
// check that parsing fails in all modes
|
|
json _;
|
|
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged), json::parse_error);
|
|
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::error), json::parse_error);
|
|
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::ignore), json::parse_error);
|
|
}
|
|
}
|
|
|
|
SECTION("0xD9 - 2 byte follow")
|
|
{
|
|
SECTION("success")
|
|
{
|
|
// add tag to value
|
|
auto v_tagged = v;
|
|
v_tagged.insert(v_tagged.begin(), 0x42); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0x23); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0xD9); // tag
|
|
|
|
// check that parsing fails in error mode
|
|
json _;
|
|
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged), json::parse_error);
|
|
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::error), json::parse_error);
|
|
|
|
// check that parsing succeeds and gets original value in ignore mode
|
|
auto j_tagged = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::ignore);
|
|
CHECK(j_tagged == j);
|
|
}
|
|
|
|
SECTION("missing byte after tag")
|
|
{
|
|
// add tag to value
|
|
auto v_tagged = v;
|
|
v_tagged.insert(v_tagged.begin(), 0x23); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0xD9); // tag
|
|
|
|
// check that parsing fails in all modes
|
|
json _;
|
|
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged), json::parse_error);
|
|
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::error), json::parse_error);
|
|
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::ignore), json::parse_error);
|
|
}
|
|
}
|
|
|
|
SECTION("0xDA - 4 bytes follow")
|
|
{
|
|
SECTION("success")
|
|
{
|
|
// add tag to value
|
|
auto v_tagged = v;
|
|
v_tagged.insert(v_tagged.begin(), 0x42); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0x23); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0x22); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0x11); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0xDA); // tag
|
|
|
|
// check that parsing fails in error mode
|
|
json _;
|
|
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged), json::parse_error);
|
|
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::error), json::parse_error);
|
|
|
|
// check that parsing succeeds and gets original value in ignore mode
|
|
auto j_tagged = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::ignore);
|
|
CHECK(j_tagged == j);
|
|
}
|
|
|
|
SECTION("missing bytes after tag")
|
|
{
|
|
// add tag to value
|
|
auto v_tagged = v;
|
|
v_tagged.insert(v_tagged.begin(), 0x23); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0x22); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0x11); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0xDA); // tag
|
|
|
|
// check that parsing fails in all modes
|
|
json _;
|
|
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged), json::parse_error);
|
|
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::error), json::parse_error);
|
|
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::ignore), json::parse_error);
|
|
}
|
|
}
|
|
|
|
SECTION("0xDB - 8 bytes follow")
|
|
{
|
|
SECTION("success")
|
|
{
|
|
// add tag to value
|
|
auto v_tagged = v;
|
|
v_tagged.insert(v_tagged.begin(), 0x42); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0x23); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0x22); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0x11); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0x42); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0x23); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0x22); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0x11); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0xDB); // tag
|
|
|
|
// check that parsing fails in error mode
|
|
json _;
|
|
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged), json::parse_error);
|
|
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::error), json::parse_error);
|
|
|
|
// check that parsing succeeds and gets original value in ignore mode
|
|
auto j_tagged = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::ignore);
|
|
CHECK(j_tagged == j);
|
|
}
|
|
|
|
SECTION("missing byte after tag")
|
|
{
|
|
// add tag to value
|
|
auto v_tagged = v;
|
|
v_tagged.insert(v_tagged.begin(), 0x42); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0x23); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0x22); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0x11); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0x23); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0x22); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0x11); // 1 byte
|
|
v_tagged.insert(v_tagged.begin(), 0xDB); // tag
|
|
|
|
// check that parsing fails in all modes
|
|
json _;
|
|
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged), json::parse_error);
|
|
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::error), json::parse_error);
|
|
CHECK_THROWS_AS(_ = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::ignore), json::parse_error);
|
|
}
|
|
|
|
SECTION("issue #5316 - cbor_tag_handler_t::store on non-binary tagged items")
|
|
{
|
|
// 55799({"a": 1}) -- CBOR self-describe magic followed by a map
|
|
const std::vector<std::uint8_t> v_map{0xD9, 0xD9, 0xF7, 0xA1, 0x61, 0x61, 0x01};
|
|
CHECK(json::from_cbor(v_map, true, true, json::cbor_tag_handler_t::ignore) == json({{"a", 1}}));
|
|
CHECK(json::from_cbor(v_map, true, true, json::cbor_tag_handler_t::store) == json({{"a", 1}}));
|
|
|
|
// Tag 24 over unsigned integer 5
|
|
const std::vector<std::uint8_t> v_int{0xD8, 0x18, 0x05};
|
|
CHECK(json::from_cbor(v_int, true, true, json::cbor_tag_handler_t::ignore) == 5);
|
|
CHECK(json::from_cbor(v_int, true, true, json::cbor_tag_handler_t::store) == 5);
|
|
|
|
// Tag 24 over text string "foo"
|
|
const std::vector<std::uint8_t> v_str{0xD8, 0x18, 0x63, 'f', 'o', 'o'};
|
|
CHECK(json::from_cbor(v_str, true, true, json::cbor_tag_handler_t::ignore) == "foo");
|
|
CHECK(json::from_cbor(v_str, true, true, json::cbor_tag_handler_t::store) == "foo");
|
|
|
|
// Tag 24 over array [1, 2]
|
|
const std::vector<std::uint8_t> v_arr{0xD8, 0x18, 0x82, 0x01, 0x02};
|
|
CHECK(json::from_cbor(v_arr, true, true, json::cbor_tag_handler_t::ignore) == json({1, 2}));
|
|
CHECK(json::from_cbor(v_arr, true, true, json::cbor_tag_handler_t::store) == json({1, 2}));
|
|
|
|
// Tag 24 over boolean true
|
|
const std::vector<std::uint8_t> v_bool{0xD8, 0x18, 0xF5};
|
|
CHECK(json::from_cbor(v_bool, true, true, json::cbor_tag_handler_t::ignore) == true);
|
|
CHECK(json::from_cbor(v_bool, true, true, json::cbor_tag_handler_t::store) == true);
|
|
|
|
// Tag 24 over null
|
|
const std::vector<std::uint8_t> v_null{0xD8, 0x18, 0xF6};
|
|
CHECK(json::from_cbor(v_null, true, true, json::cbor_tag_handler_t::ignore) == nullptr);
|
|
CHECK(json::from_cbor(v_null, true, true, json::cbor_tag_handler_t::store) == nullptr);
|
|
|
|
// Nested tags: tag 55799 over tag 24 over integer 42
|
|
const std::vector<std::uint8_t> v_nested{0xD9, 0xD9, 0xF7, 0xD8, 0x18, 0x18, 0x2A};
|
|
CHECK(json::from_cbor(v_nested, true, true, json::cbor_tag_handler_t::ignore) == 42);
|
|
CHECK(json::from_cbor(v_nested, true, true, json::cbor_tag_handler_t::store) == 42);
|
|
|
|
// Tag 24 over byte string continues to store subtype as before
|
|
const std::vector<std::uint8_t> v_bin{0xD8, 0x18, 0x42, 0xCA, 0xFE};
|
|
auto j_bin_store = json::from_cbor(v_bin, true, true, json::cbor_tag_handler_t::store);
|
|
CHECK(j_bin_store.is_binary());
|
|
CHECK(j_bin_store.get_binary().has_subtype());
|
|
CHECK(j_bin_store.get_binary().subtype() == 24);
|
|
CHECK(j_bin_store.get_binary() == json::binary({0xCA, 0xFE}, 24).get_binary());
|
|
|
|
// Tagged values inside a container under store: [24(1), 25(h'0001')]
|
|
const std::vector<std::uint8_t> v_container{0x82, 0xD8, 0x18, 0x01, 0xD8, 0x19, 0x42, 0x00, 0x01};
|
|
auto j_container_store = json::from_cbor(v_container, true, true, json::cbor_tag_handler_t::store);
|
|
CHECK(j_container_store.is_array());
|
|
CHECK(j_container_store.size() == 2);
|
|
CHECK(j_container_store[0] == 1);
|
|
CHECK(j_container_store[1].is_binary());
|
|
CHECK(j_container_store[1].get_binary().has_subtype());
|
|
CHECK(j_container_store[1].get_binary().subtype() == 25);
|
|
CHECK(j_container_store[1].get_binary() == json::binary({0x00, 0x01}, 25).get_binary());
|
|
|
|
// Tagged values as object values under store: {"a": 55799(1), "b": 24(h'01')}
|
|
const std::vector<std::uint8_t> v_object{0xA2, 0x61, 'a', 0xD9, 0xD9, 0xF7, 0x01, 0x61, 'b', 0xD8, 0x18, 0x41, 0x01};
|
|
CHECK(json::from_cbor(v_object, true, true, json::cbor_tag_handler_t::store) == json({{"a", 1}, {"b", json::binary({0x01}, 24)}}));
|
|
|
|
// two tags in a row before a byte string: the inner tag is stored
|
|
// (this uses item_read and then the byte-string path)
|
|
const std::vector<std::uint8_t> v_nested_byte_string{0xD8, 0x18, 0xD8, 0x19, 0x42, 0x00, 0x01};
|
|
CHECK(json::from_cbor(v_nested_byte_string, true, true, json::cbor_tag_handler_t::store) == json::binary({0x00, 0x01}, 25));
|
|
|
|
// errors after a stored tag are now the same as with ignore
|
|
json _;
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t> {0xD8, 0x18}, true, true, json::cbor_tag_handler_t::store), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t> {0xD8, 0x18, 0x1C}, true, true, json::cbor_tag_handler_t::store), "[json.exception.parse_error.112] parse error at byte 3: syntax error while parsing CBOR value: invalid byte: 0x1C", json::parse_error&);
|
|
}
|
|
}
|
|
|
|
SECTION("negative integer overflow")
|
|
{
|
|
// CBOR encodes negative integers as: result = -1 - n
|
|
// For type 0x3B, n is an 8-byte uint64_t. Valid range for n with
|
|
// the default int64_t is [0, INT64_MAX], producing results in [INT64_MIN, -1].
|
|
// When n > INT64_MAX, the result exceeds int64_t range and is rejected.
|
|
|
|
SECTION("n = 0 is valid (result = -1)")
|
|
{
|
|
// n = 0, result = -1 - 0 = -1 (smallest magnitude negative)
|
|
const std::vector<uint8_t> input = {0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
const auto result = json::from_cbor(input);
|
|
CHECK(result.is_number_integer());
|
|
CHECK(result.get<int64_t>() == -1);
|
|
}
|
|
|
|
SECTION("n = INT64_MAX is valid (result = INT64_MIN)")
|
|
{
|
|
// n = INT64_MAX (0x7FFFFFFFFFFFFFFF)
|
|
// result = -1 - INT64_MAX = INT64_MIN (-9223372036854775808)
|
|
const std::vector<uint8_t> input = {0x3B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
|
const auto result = json::from_cbor(input);
|
|
CHECK(result.is_number_integer());
|
|
CHECK(result.get<int64_t>() == (std::numeric_limits<int64_t>::min)());
|
|
}
|
|
|
|
SECTION("n = INT64_MAX + 1 is rejected (overflow)")
|
|
{
|
|
// n = INT64_MAX + 1 (0x8000000000000000)
|
|
// result = -1 - n = -9223372036854775809, which exceeds int64_t range
|
|
const std::vector<uint8_t> input = {0x3B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
json _;
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input),
|
|
"[json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: negative integer overflow",
|
|
json::parse_error);
|
|
}
|
|
|
|
SECTION("n = UINT64_MAX is rejected (overflow)")
|
|
{
|
|
// n = UINT64_MAX (0xFFFFFFFFFFFFFFFF)
|
|
// result = -1 - n = -18446744073709551616, which exceeds int64_t range
|
|
const std::vector<uint8_t> input = {0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
|
json _;
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input),
|
|
"[json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: negative integer overflow",
|
|
json::parse_error);
|
|
}
|
|
|
|
SECTION("overflow with allow_exceptions=false returns discarded")
|
|
{
|
|
const std::vector<uint8_t> input = {0x3B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
const auto result = json::from_cbor(input, true, false);
|
|
CHECK(result.is_discarded());
|
|
}
|
|
}
|
|
|
|
SECTION("tagged binary")
|
|
{
|
|
// create a binary value of subtype 42
|
|
json j_binary;
|
|
j_binary["binary"] = json::binary({0xCA, 0xFE, 0xBA, 0xBE}, 42);
|
|
|
|
// convert to CBOR
|
|
const auto vec = json::to_cbor(j_binary);
|
|
CHECK(vec == std::vector<std::uint8_t> {0xA1, 0x66, 0x62, 0x69, 0x6E, 0x61, 0x72, 0x79, 0xD8, 0x2A, 0x44, 0xCA, 0xFE, 0xBA, 0xBE});
|
|
|
|
// parse error when parsing tagged value
|
|
json _;
|
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vec), "[json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: invalid byte: 0xD8", json::parse_error);
|
|
|
|
// binary without subtype when tags are ignored
|
|
json jb = json::from_cbor(vec, true, true, json::cbor_tag_handler_t::ignore);
|
|
CHECK(jb.is_object());
|
|
CHECK(jb["binary"].is_binary());
|
|
CHECK(!jb["binary"].get_binary().has_subtype());
|
|
}
|
|
}
|
|
|
|
TEST_CASE("CBOR large strings and binaries (chunked reader)")
|
|
{
|
|
// The binary reader reads strings and byte arrays in bounded chunks; make
|
|
// sure roundtripping is correct for lengths around and beyond the internal
|
|
// chunk size (4096 bytes), for both vector (iterator) and pointer inputs.
|
|
for (const std::size_t len :
|
|
{
|
|
std::size_t{0}, std::size_t{1}, std::size_t{4095}, std::size_t{4096},
|
|
std::size_t{4097}, std::size_t{8192}, std::size_t{100000}
|
|
})
|
|
{
|
|
CAPTURE(len)
|
|
|
|
// text string
|
|
const json j_string = std::string(len, 'x');
|
|
const std::vector<std::uint8_t> v_string = json::to_cbor(j_string);
|
|
CHECK(json::from_cbor(v_string) == j_string);
|
|
// pointer input exercises the std::memcpy fast path
|
|
CHECK(json::from_cbor(reinterpret_cast<const char*>(v_string.data()),
|
|
reinterpret_cast<const char*>(v_string.data()) + v_string.size()) == j_string);
|
|
|
|
// byte string
|
|
const json j_binary = json::binary(std::vector<std::uint8_t>(len, 0xCD));
|
|
const std::vector<std::uint8_t> v_binary = json::to_cbor(j_binary);
|
|
CHECK(json::from_cbor(v_binary) == j_binary);
|
|
CHECK(json::from_cbor(reinterpret_cast<const char*>(v_binary.data()),
|
|
reinterpret_cast<const char*>(v_binary.data()) + v_binary.size()) == j_binary);
|
|
|
|
// a truncated payload must still be reported as an error, never crash
|
|
// or loop, regardless of the (large) announced length
|
|
if (len > 16)
|
|
{
|
|
std::vector<std::uint8_t> truncated = v_string;
|
|
truncated.resize(truncated.size() - 8);
|
|
json _;
|
|
CHECK_THROWS_AS(_ = json::from_cbor(truncated), json::parse_error);
|
|
}
|
|
}
|
|
}
|