diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 4978e5d973..ef4e1266dd 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -43,7 +43,7 @@ jobs: run: cmake -S . -B build -G "Visual Studio 15 2017" -A ${{ matrix.architecture }} -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/W4 /WX" if: matrix.build_type == 'Release' && matrix.architecture != 'x64' - name: cmake - run: cmake -S . -B build -G "Visual Studio 15 2017" -A ${{ matrix.architecture }} -DJSON_BuildTests=On -DJSON_FastTests=ON -DCMAKE_CXX_FLAGS="/W4 /WX" + run: cmake -S . -B build -G "Visual Studio 15 2017" -A ${{ matrix.architecture }} -DJSON_BuildTests=On -DJSON_FastTests=ON -DCMAKE_EXE_LINKER_FLAGS="/STACK:4000000" -DCMAKE_CXX_FLAGS="/W4 /WX" if: matrix.build_type == 'Debug' - name: build run: cmake --build build --config ${{ matrix.build_type }} --parallel 10 @@ -75,7 +75,7 @@ jobs: run: cmake -S . -B build -G "Visual Studio 16 2019" -A ${{ matrix.architecture }} -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/W4 /WX" if: matrix.build_type == 'Release' - name: cmake - run: cmake -S . -B build -G "Visual Studio 16 2019" -A ${{ matrix.architecture }} -DJSON_BuildTests=On -DJSON_FastTests=ON -DCMAKE_CXX_FLAGS="/W4 /WX" + run: cmake -S . -B build -G "Visual Studio 16 2019" -A ${{ matrix.architecture }} -DJSON_BuildTests=On -DJSON_FastTests=ON -DCMAKE_EXE_LINKER_FLAGS="/STACK:4000000" -DCMAKE_CXX_FLAGS="/W4 /WX" if: matrix.build_type == 'Debug' - name: build run: cmake --build build --config ${{ matrix.build_type }} --parallel 10 @@ -88,7 +88,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: cmake - run: cmake -S . -B build -G "Visual Studio 16 2019" -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/permissive- /std:c++latest /utf-8 /W4 /WX" + run: cmake -S . -B build -G "Visual Studio 16 2019" -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/permissive- /std:c++latest /utf-8 /W4 /WX" -DCMAKE_EXE_LINKER_FLAGS="/STACK:4000000" - name: build run: cmake --build build --config Release --parallel 10 - name: test diff --git a/doc/mkdocs/docs/api/basic_json/binary_t.md b/doc/mkdocs/docs/api/basic_json/binary_t.md index 2d6cd574e3..0dd859dccd 100644 --- a/doc/mkdocs/docs/api/basic_json/binary_t.md +++ b/doc/mkdocs/docs/api/basic_json/binary_t.md @@ -64,4 +64,4 @@ type `#!cpp binary_t*` must be dereferenced. ## Version history -- Added in version 3.8.0. +- Added in version 3.8.0. Changed type of subtype to `std::uint64_t` in version 3.9.2. diff --git a/doc/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md b/doc/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md index ea417de55a..9281157187 100644 --- a/doc/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md +++ b/doc/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md @@ -4,7 +4,8 @@ enum class cbor_tag_handler_t { error, - ignore + ignore, + store }; ``` @@ -16,6 +17,9 @@ error ignore : ignore tags +store +: store tagged values as binary container with subtype (for bytes 0xd8..0xdb) + ## Version history -- Added in version 3.9.0. +- Added in version 3.9.0. Added value `store` in 3.9.2. diff --git a/doc/mkdocs/docs/features/binary_formats/cbor.md b/doc/mkdocs/docs/features/binary_formats/cbor.md index daa29be875..a7a8fc0026 100644 --- a/doc/mkdocs/docs/features/binary_formats/cbor.md +++ b/doc/mkdocs/docs/features/binary_formats/cbor.md @@ -55,6 +55,8 @@ binary | *size*: 256..65535 | byte string (2 by binary | *size*: 65536..4294967295 | byte string (4 bytes follow) | 0x5A binary | *size*: 4294967296..18446744073709551615 | byte string (8 bytes follow) | 0x5B +Binary values with subtype are mapped to tagged values (0xD8..0xDB) depending on the subtype, followed by a byte string, +see "binary" cells in the table above. !!! success "Complete mapping" @@ -162,7 +164,7 @@ Double-Precision Float | number_float | 0xFB !!! warning "Tagged items" - Tagged items will throw a parse error by default. However, they can be ignored by passing `cbor_tag_handler_t::ignore` to function `from_cbor`. + Tagged items will throw a parse error by default. They can be ignored by passing `cbor_tag_handler_t::ignore` to function `from_cbor`. They can be stored by passing `cbor_tag_handler_t::store` to function `from_cbor`. ??? example diff --git a/doc/mkdocs/docs/features/binary_formats/index.md b/doc/mkdocs/docs/features/binary_formats/index.md index 55f963c2d2..279009d118 100644 --- a/doc/mkdocs/docs/features/binary_formats/index.md +++ b/doc/mkdocs/docs/features/binary_formats/index.md @@ -25,7 +25,7 @@ to efficiently encode JSON values to byte vectors and to decode such vectors. | Format | Binary values | Binary subtypes | | ----------- | ------------- | --------------- | | BSON | supported | supported | -| CBOR | supported | not supported | +| CBOR | supported | supported | | MessagePack | supported | supported | | UBJSON | not supported | not supported | diff --git a/doc/mkdocs/docs/features/binary_values.md b/doc/mkdocs/docs/features/binary_values.md index 4716aac7eb..66e7216881 100644 --- a/doc/mkdocs/docs/features/binary_values.md +++ b/doc/mkdocs/docs/features/binary_values.md @@ -9,10 +9,10 @@ JSON itself does not have a binary value. As such, binary values are an extensio ```plantuml class json::binary_t { -- setters -- - +void set_subtype(std::uint8_t subtype) + +void set_subtype(std::uint64_t subtype) +void clear_subtype() -- getters -- - +std::uint8_t subtype() const + +std::uint64_t subtype() const +bool has_subtype() const } @@ -68,7 +68,7 @@ j.get_binary().has_subtype(); // returns true j.get_binary().size(); // returns 4 ``` -For convencience, binary JSON values can be constructed via `json::binary`: +For convenience, binary JSON values can be constructed via `json::binary`: ```cpp auto j2 = json::binary({0xCA, 0xFE, 0xBA, 0xBE}, 23); @@ -76,6 +76,7 @@ auto j3 = json::binary({0xCA, 0xFE, 0xBA, 0xBE}); j2 == j; // returns true j3.get_binary().has_subtype(); // returns false +j3.get_binary().subtype(); // returns std::uint64_t(-1) as j3 has no subtype ``` @@ -184,7 +185,7 @@ JSON does not have a binary type, and this library does not introduce a new type 0xCA 0xFE 0xBA 0xBE // content ``` - Note that the subtype is serialized as tag. However, parsing tagged values yield a parse error unless `json::cbor_tag_handler_t::ignore` is passed to `json::from_cbor`. + Note that the subtype is serialized as tag. However, parsing tagged values yield a parse error unless `json::cbor_tag_handler_t::ignore` or `json::cbor_tag_handler_t::store` is passed to `json::from_cbor`. ```json { diff --git a/include/nlohmann/byte_container_with_subtype.hpp b/include/nlohmann/byte_container_with_subtype.hpp index df68395a25..0c117bff5f 100644 --- a/include/nlohmann/byte_container_with_subtype.hpp +++ b/include/nlohmann/byte_container_with_subtype.hpp @@ -1,6 +1,6 @@ #pragma once -#include // uint8_t +#include // uint8_t, uint64_t #include // tie #include // move @@ -18,7 +18,7 @@ order to override the binary type. @tparam BinaryType container to store bytes (`std::vector` by default) -@since version 3.8.0 +@since version 3.8.0; changed type of subtypes to std::uint64_t in 3.9.2. */ template class byte_container_with_subtype : public BinaryType @@ -26,6 +26,8 @@ class byte_container_with_subtype : public BinaryType public: /// the type of the underlying container using container_type = BinaryType; + /// the type of the subtype + using subtype_type = std::uint64_t; byte_container_with_subtype() noexcept(noexcept(container_type())) : container_type() @@ -39,13 +41,13 @@ class byte_container_with_subtype : public BinaryType : container_type(std::move(b)) {} - byte_container_with_subtype(const container_type& b, std::uint8_t subtype_) noexcept(noexcept(container_type(b))) + byte_container_with_subtype(const container_type& b, subtype_type subtype_) noexcept(noexcept(container_type(b))) : container_type(b) , m_subtype(subtype_) , m_has_subtype(true) {} - byte_container_with_subtype(container_type&& b, std::uint8_t subtype_) noexcept(noexcept(container_type(std::move(b)))) + byte_container_with_subtype(container_type&& b, subtype_type subtype_) noexcept(noexcept(container_type(std::move(b)))) : container_type(std::move(b)) , m_subtype(subtype_) , m_has_subtype(true) @@ -80,7 +82,7 @@ class byte_container_with_subtype : public BinaryType @since version 3.8.0 */ - void set_subtype(std::uint8_t subtype_) noexcept + void set_subtype(subtype_type subtype_) noexcept { m_subtype = subtype_; m_has_subtype = true; @@ -90,7 +92,7 @@ class byte_container_with_subtype : public BinaryType @brief return the binary subtype Returns the numerical subtype of the value if it has a subtype. If it does - not have a subtype, this function will return size_t(-1) as a sentinel + not have a subtype, this function will return subtype_type(-1) as a sentinel value. @return the numerical subtype of the binary value @@ -105,11 +107,12 @@ class byte_container_with_subtype : public BinaryType @sa see @ref has_subtype() -- returns whether or not the binary value has a subtype - @since version 3.8.0 + @since version 3.8.0; fixed return value to properly return + subtype_type(-1) as documented in version 3.9.2 */ - constexpr std::uint8_t subtype() const noexcept + constexpr subtype_type subtype() const noexcept { - return m_subtype; + return m_has_subtype ? m_subtype : subtype_type(-1); } /*! @@ -159,7 +162,7 @@ class byte_container_with_subtype : public BinaryType } private: - std::uint8_t m_subtype = 0; + subtype_type m_subtype = 0; bool m_has_subtype = false; }; diff --git a/include/nlohmann/detail/hash.hpp b/include/nlohmann/detail/hash.hpp index 70c5daf338..ac07c2e8f0 100644 --- a/include/nlohmann/detail/hash.hpp +++ b/include/nlohmann/detail/hash.hpp @@ -103,7 +103,7 @@ std::size_t hash(const BasicJsonType& j) auto seed = combine(type, j.get_binary().size()); const auto h = std::hash {}(j.get_binary().has_subtype()); seed = combine(seed, h); - seed = combine(seed, j.get_binary().subtype()); + seed = combine(seed, static_cast(j.get_binary().subtype())); for (const auto byte : j.get_binary()) { seed = combine(seed, std::hash {}(byte)); diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 5e21629abf..f1b65ba547 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -30,8 +30,9 @@ namespace detail /// how to treat CBOR tags enum class cbor_tag_handler_t { - error, ///< throw a parse_error exception in case of a tag - ignore ///< ignore tags + error, ///< throw a parse_error exception in case of a tag + ignore, ///< ignore tags + store ///< store tags as binary type }; /*! @@ -723,30 +724,31 @@ class binary_reader case cbor_tag_handler_t::ignore: { + // ignore binary subtype switch (current) { case 0xD8: { - std::uint8_t len{}; - get_number(input_format_t::cbor, len); + std::uint8_t subtype_to_ignore{}; + get_number(input_format_t::cbor, subtype_to_ignore); break; } case 0xD9: { - std::uint16_t len{}; - get_number(input_format_t::cbor, len); + std::uint16_t subtype_to_ignore{}; + get_number(input_format_t::cbor, subtype_to_ignore); break; } case 0xDA: { - std::uint32_t len{}; - get_number(input_format_t::cbor, len); + std::uint32_t subtype_to_ignore{}; + get_number(input_format_t::cbor, subtype_to_ignore); break; } case 0xDB: { - std::uint64_t len{}; - get_number(input_format_t::cbor, len); + std::uint64_t subtype_to_ignore{}; + get_number(input_format_t::cbor, subtype_to_ignore); break; } default: @@ -755,6 +757,47 @@ class binary_reader return parse_cbor_internal(true, tag_handler); } + case cbor_tag_handler_t::store: + { + binary_t b; + // use binary subtype and store in binary container + switch (current) + { + case 0xD8: + { + std::uint8_t subtype{}; + get_number(input_format_t::cbor, subtype); + b.set_subtype(detail::conditional_static_cast(subtype)); + break; + } + case 0xD9: + { + std::uint16_t subtype{}; + get_number(input_format_t::cbor, subtype); + b.set_subtype(detail::conditional_static_cast(subtype)); + break; + } + case 0xDA: + { + std::uint32_t subtype{}; + get_number(input_format_t::cbor, subtype); + b.set_subtype(detail::conditional_static_cast(subtype)); + break; + } + case 0xDB: + { + std::uint64_t subtype{}; + get_number(input_format_t::cbor, subtype); + b.set_subtype(detail::conditional_static_cast(subtype)); + break; + } + default: + return parse_cbor_internal(true, tag_handler); + } + get(); + return get_cbor_binary(b) && sax->binary(b); + } + default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE return false; // LCOV_EXCL_LINE diff --git a/include/nlohmann/detail/input/parser.hpp b/include/nlohmann/detail/input/parser.hpp index 6ec509f813..99cdd05ecb 100644 --- a/include/nlohmann/detail/input/parser.hpp +++ b/include/nlohmann/detail/input/parser.hpp @@ -23,7 +23,7 @@ namespace detail // parser // //////////// -enum class parse_event_t : uint8_t +enum class parse_event_t : std::uint8_t { /// the parser read `{` and started to process a JSON object object_start, diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index d89a4a0d99..1fa0cd2f7a 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -291,8 +291,26 @@ class binary_writer { if (j.m_value.binary->has_subtype()) { - write_number(static_cast(0xd8)); - write_number(j.m_value.binary->subtype()); + if (j.m_value.binary->subtype() <= (std::numeric_limits::max)()) + { + write_number(static_cast(0xd8)); + write_number(static_cast(j.m_value.binary->subtype())); + } + else if (j.m_value.binary->subtype() <= (std::numeric_limits::max)()) + { + write_number(static_cast(0xd9)); + write_number(static_cast(j.m_value.binary->subtype())); + } + else if (j.m_value.binary->subtype() <= (std::numeric_limits::max)()) + { + write_number(static_cast(0xda)); + write_number(static_cast(j.m_value.binary->subtype())); + } + else if (j.m_value.binary->subtype() <= (std::numeric_limits::max)()) + { + write_number(static_cast(0xdb)); + write_number(static_cast(j.m_value.binary->subtype())); + } } // step 1: write control byte and the binary array size @@ -1109,7 +1127,7 @@ class binary_writer write_bson_entry_header(name, 0x05); write_number(static_cast(value.size())); - write_number(value.has_subtype() ? value.subtype() : std::uint8_t(0x00)); + write_number(value.has_subtype() ? static_cast(value.subtype()) : std::uint8_t(0x00)); oa->write_characters(reinterpret_cast(value.data()), value.size()); } diff --git a/include/nlohmann/detail/output/serializer.hpp b/include/nlohmann/detail/output/serializer.hpp index 9c2182c4a4..b9f53477a9 100644 --- a/include/nlohmann/detail/output/serializer.hpp +++ b/include/nlohmann/detail/output/serializer.hpp @@ -389,7 +389,7 @@ class serializer for (std::size_t i = 0; i < s.size(); ++i) { - const auto byte = static_cast(s[i]); + const auto byte = static_cast(s[i]); switch (decode(state, codepoint, byte)) { @@ -674,6 +674,7 @@ class serializer @tparam NumberType either @a number_integer_t or @a number_unsigned_t */ template < typename NumberType, detail::enable_if_t < + std::is_integral::value || std::is_same::value || std::is_same::value || std::is_same::value, @@ -706,7 +707,7 @@ class serializer // use a pointer to fill the buffer auto buffer_ptr = number_buffer.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto,cppcoreguidelines-pro-type-vararg,hicpp-vararg) - const bool is_negative = std::is_same::value && !(x >= 0); // see issue #755 + const bool is_negative = std::is_signed::value && !(x >= 0); // see issue #755 number_unsigned_t abs_value; unsigned int n_chars{}; diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 81705ffcb6..024266f362 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -902,8 +902,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec #### Notes on subtypes - CBOR - - Binary values are represented as byte strings. No subtypes are - supported and will be ignored when CBOR is written. + - Binary values are represented as byte strings. Subtypes are serialized + as tagged values. - MessagePack - If a subtype is given and the binary array contains exactly 1, 2, 4, 8, or 16 elements, the fixext family (fixext1, fixext2, fixext4, fixext8) @@ -1512,7 +1512,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @ref number_float_t, and all convertible number types such as `int`, `size_t`, `int64_t`, `float` or `double` can be used. - **boolean**: @ref boolean_t / `bool` can be used. - - **binary**: @ref binary_t / `std::vector` may be used, + - **binary**: @ref binary_t / `std::vector` may be used, unfortunately because string literals cannot be distinguished from binary character arrays by the C++ type system, all types compatible with `const char*` will be directed to the string constructor instead. This is both @@ -1832,7 +1832,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @since version 3.8.0 */ JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json binary(const typename binary_t::container_type& init, std::uint8_t subtype) + static basic_json binary(const typename binary_t::container_type& init, typename binary_t::subtype_type subtype) { auto res = basic_json(); res.m_type = value_t::binary; @@ -1850,9 +1850,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return res; } - /// @copydoc binary(const typename binary_t::container_type&, std::uint8_t) + /// @copydoc binary(const typename binary_t::container_type&, typename binary_t::subtype_type) JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json binary(typename binary_t::container_type&& init, std::uint8_t subtype) + static basic_json binary(typename binary_t::container_type&& init, typename binary_t::subtype_type subtype) { auto res = basic_json(); res.m_type = value_t::binary; @@ -7248,6 +7248,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec binary | *size*: 65536..4294967295 | byte string (4 bytes follow) | 0x5A binary | *size*: 4294967296..18446744073709551615 | byte string (8 bytes follow) | 0x5B + Binary values with subtype are mapped to tagged values (0xD8..0xDB) + depending on the subtype, followed by a byte string, see "binary" cells + in the table above. + @note The mapping is **complete** in the sense that any JSON value type can be converted to a CBOR value. @@ -7288,16 +7292,16 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @since version 2.0.9; compact representation of floating-point numbers since version 3.8.0 */ - static std::vector to_cbor(const basic_json& j) + static std::vector to_cbor(const basic_json& j) { - std::vector result; + std::vector result; to_cbor(j, result); return result; } - static void to_cbor(const basic_json& j, detail::output_adapter o) + static void to_cbor(const basic_json& j, detail::output_adapter o) { - binary_writer(o).write_cbor(j); + binary_writer(o).write_cbor(j); } static void to_cbor(const basic_json& j, detail::output_adapter o) @@ -7383,16 +7387,16 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @since version 2.0.9 */ - static std::vector to_msgpack(const basic_json& j) + static std::vector to_msgpack(const basic_json& j) { - std::vector result; + std::vector result; to_msgpack(j, result); return result; } - static void to_msgpack(const basic_json& j, detail::output_adapter o) + static void to_msgpack(const basic_json& j, detail::output_adapter o) { - binary_writer(o).write_msgpack(j); + binary_writer(o).write_msgpack(j); } static void to_msgpack(const basic_json& j, detail::output_adapter o) @@ -7486,19 +7490,19 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @since version 3.1.0 */ - static std::vector to_ubjson(const basic_json& j, - const bool use_size = false, - const bool use_type = false) + static std::vector to_ubjson(const basic_json& j, + const bool use_size = false, + const bool use_type = false) { - std::vector result; + std::vector result; to_ubjson(j, result, use_size, use_type); return result; } - static void to_ubjson(const basic_json& j, detail::output_adapter o, + static void to_ubjson(const basic_json& j, detail::output_adapter o, const bool use_size = false, const bool use_type = false) { - binary_writer(o).write_ubjson(j, use_size, use_type); + binary_writer(o).write_ubjson(j, use_size, use_type); } static void to_ubjson(const basic_json& j, detail::output_adapter o, @@ -7564,9 +7568,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @sa see @ref to_cbor(const basic_json&) for the related CBOR format @sa see @ref to_msgpack(const basic_json&) for the related MessagePack format */ - static std::vector to_bson(const basic_json& j) + static std::vector to_bson(const basic_json& j) { - std::vector result; + std::vector result; to_bson(j, result); return result; } @@ -7579,13 +7583,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @pre The input `j` shall be an object: `j.is_object() == true` @sa see @ref to_bson(const basic_json&) */ - static void to_bson(const basic_json& j, detail::output_adapter o) + static void to_bson(const basic_json& j, detail::output_adapter o) { - binary_writer(o).write_bson(j); + binary_writer(o).write_bson(j); } /*! - @copydoc to_bson(const basic_json&, detail::output_adapter) + @copydoc to_bson(const basic_json&, detail::output_adapter) */ static void to_bson(const basic_json& j, detail::output_adapter o) { diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 761e9ef92e..16afb8a6a7 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -4985,7 +4985,7 @@ struct adl_serializer // #include -#include // uint8_t +#include // uint8_t, uint64_t #include // tie #include // move @@ -5003,7 +5003,7 @@ order to override the binary type. @tparam BinaryType container to store bytes (`std::vector` by default) -@since version 3.8.0 +@since version 3.8.0; changed type of subtypes to std::uint64_t in 3.9.2. */ template class byte_container_with_subtype : public BinaryType @@ -5011,6 +5011,8 @@ class byte_container_with_subtype : public BinaryType public: /// the type of the underlying container using container_type = BinaryType; + /// the type of the subtype + using subtype_type = std::uint64_t; byte_container_with_subtype() noexcept(noexcept(container_type())) : container_type() @@ -5024,13 +5026,13 @@ class byte_container_with_subtype : public BinaryType : container_type(std::move(b)) {} - byte_container_with_subtype(const container_type& b, std::uint8_t subtype_) noexcept(noexcept(container_type(b))) + byte_container_with_subtype(const container_type& b, subtype_type subtype_) noexcept(noexcept(container_type(b))) : container_type(b) , m_subtype(subtype_) , m_has_subtype(true) {} - byte_container_with_subtype(container_type&& b, std::uint8_t subtype_) noexcept(noexcept(container_type(std::move(b)))) + byte_container_with_subtype(container_type&& b, subtype_type subtype_) noexcept(noexcept(container_type(std::move(b)))) : container_type(std::move(b)) , m_subtype(subtype_) , m_has_subtype(true) @@ -5065,7 +5067,7 @@ class byte_container_with_subtype : public BinaryType @since version 3.8.0 */ - void set_subtype(std::uint8_t subtype_) noexcept + void set_subtype(subtype_type subtype_) noexcept { m_subtype = subtype_; m_has_subtype = true; @@ -5075,7 +5077,7 @@ class byte_container_with_subtype : public BinaryType @brief return the binary subtype Returns the numerical subtype of the value if it has a subtype. If it does - not have a subtype, this function will return size_t(-1) as a sentinel + not have a subtype, this function will return subtype_type(-1) as a sentinel value. @return the numerical subtype of the binary value @@ -5090,11 +5092,12 @@ class byte_container_with_subtype : public BinaryType @sa see @ref has_subtype() -- returns whether or not the binary value has a subtype - @since version 3.8.0 + @since version 3.8.0; fixed return value to properly return + subtype_type(-1) as documented in version 3.9.2 */ - constexpr std::uint8_t subtype() const noexcept + constexpr subtype_type subtype() const noexcept { - return m_subtype; + return m_has_subtype ? m_subtype : subtype_type(-1); } /*! @@ -5144,7 +5147,7 @@ class byte_container_with_subtype : public BinaryType } private: - std::uint8_t m_subtype = 0; + subtype_type m_subtype = 0; bool m_has_subtype = false; }; @@ -5263,7 +5266,7 @@ std::size_t hash(const BasicJsonType& j) auto seed = combine(type, j.get_binary().size()); const auto h = std::hash {}(j.get_binary().has_subtype()); seed = combine(seed, h); - seed = combine(seed, j.get_binary().subtype()); + seed = combine(seed, static_cast(j.get_binary().subtype())); for (const auto byte : j.get_binary()) { seed = combine(seed, std::hash {}(byte)); @@ -8296,8 +8299,9 @@ namespace detail /// how to treat CBOR tags enum class cbor_tag_handler_t { - error, ///< throw a parse_error exception in case of a tag - ignore ///< ignore tags + error, ///< throw a parse_error exception in case of a tag + ignore, ///< ignore tags + store ///< store tags as binary type }; /*! @@ -8989,30 +8993,31 @@ class binary_reader case cbor_tag_handler_t::ignore: { + // ignore binary subtype switch (current) { case 0xD8: { - std::uint8_t len{}; - get_number(input_format_t::cbor, len); + std::uint8_t subtype_to_ignore{}; + get_number(input_format_t::cbor, subtype_to_ignore); break; } case 0xD9: { - std::uint16_t len{}; - get_number(input_format_t::cbor, len); + std::uint16_t subtype_to_ignore{}; + get_number(input_format_t::cbor, subtype_to_ignore); break; } case 0xDA: { - std::uint32_t len{}; - get_number(input_format_t::cbor, len); + std::uint32_t subtype_to_ignore{}; + get_number(input_format_t::cbor, subtype_to_ignore); break; } case 0xDB: { - std::uint64_t len{}; - get_number(input_format_t::cbor, len); + std::uint64_t subtype_to_ignore{}; + get_number(input_format_t::cbor, subtype_to_ignore); break; } default: @@ -9021,6 +9026,47 @@ class binary_reader return parse_cbor_internal(true, tag_handler); } + case cbor_tag_handler_t::store: + { + binary_t b; + // use binary subtype and store in binary container + switch (current) + { + case 0xD8: + { + std::uint8_t subtype{}; + get_number(input_format_t::cbor, subtype); + b.set_subtype(detail::conditional_static_cast(subtype)); + break; + } + case 0xD9: + { + std::uint16_t subtype{}; + get_number(input_format_t::cbor, subtype); + b.set_subtype(detail::conditional_static_cast(subtype)); + break; + } + case 0xDA: + { + std::uint32_t subtype{}; + get_number(input_format_t::cbor, subtype); + b.set_subtype(detail::conditional_static_cast(subtype)); + break; + } + case 0xDB: + { + std::uint64_t subtype{}; + get_number(input_format_t::cbor, subtype); + b.set_subtype(detail::conditional_static_cast(subtype)); + break; + } + default: + return parse_cbor_internal(true, tag_handler); + } + get(); + return get_cbor_binary(b) && sax->binary(b); + } + default: // LCOV_EXCL_LINE JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE return false; // LCOV_EXCL_LINE @@ -10783,7 +10829,7 @@ namespace detail // parser // //////////// -enum class parse_event_t : uint8_t +enum class parse_event_t : std::uint8_t { /// the parser read `{` and started to process a JSON object object_start, @@ -13792,8 +13838,26 @@ class binary_writer { if (j.m_value.binary->has_subtype()) { - write_number(static_cast(0xd8)); - write_number(j.m_value.binary->subtype()); + if (j.m_value.binary->subtype() <= (std::numeric_limits::max)()) + { + write_number(static_cast(0xd8)); + write_number(static_cast(j.m_value.binary->subtype())); + } + else if (j.m_value.binary->subtype() <= (std::numeric_limits::max)()) + { + write_number(static_cast(0xd9)); + write_number(static_cast(j.m_value.binary->subtype())); + } + else if (j.m_value.binary->subtype() <= (std::numeric_limits::max)()) + { + write_number(static_cast(0xda)); + write_number(static_cast(j.m_value.binary->subtype())); + } + else if (j.m_value.binary->subtype() <= (std::numeric_limits::max)()) + { + write_number(static_cast(0xdb)); + write_number(static_cast(j.m_value.binary->subtype())); + } } // step 1: write control byte and the binary array size @@ -14610,7 +14674,7 @@ class binary_writer write_bson_entry_header(name, 0x05); write_number(static_cast(value.size())); - write_number(value.has_subtype() ? value.subtype() : std::uint8_t(0x00)); + write_number(value.has_subtype() ? static_cast(value.subtype()) : std::uint8_t(0x00)); oa->write_characters(reinterpret_cast(value.data()), value.size()); } @@ -16629,7 +16693,7 @@ class serializer for (std::size_t i = 0; i < s.size(); ++i) { - const auto byte = static_cast(s[i]); + const auto byte = static_cast(s[i]); switch (decode(state, codepoint, byte)) { @@ -16914,6 +16978,7 @@ class serializer @tparam NumberType either @a number_integer_t or @a number_unsigned_t */ template < typename NumberType, detail::enable_if_t < + std::is_integral::value || std::is_same::value || std::is_same::value || std::is_same::value, @@ -16946,7 +17011,7 @@ class serializer // use a pointer to fill the buffer auto buffer_ptr = number_buffer.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto,cppcoreguidelines-pro-type-vararg,hicpp-vararg) - const bool is_negative = std::is_same::value && !(x >= 0); // see issue #755 + const bool is_negative = std::is_signed::value && !(x >= 0); // see issue #755 number_unsigned_t abs_value; unsigned int n_chars{}; @@ -18217,8 +18282,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec #### Notes on subtypes - CBOR - - Binary values are represented as byte strings. No subtypes are - supported and will be ignored when CBOR is written. + - Binary values are represented as byte strings. Subtypes are serialized + as tagged values. - MessagePack - If a subtype is given and the binary array contains exactly 1, 2, 4, 8, or 16 elements, the fixext family (fixext1, fixext2, fixext4, fixext8) @@ -18827,7 +18892,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @ref number_float_t, and all convertible number types such as `int`, `size_t`, `int64_t`, `float` or `double` can be used. - **boolean**: @ref boolean_t / `bool` can be used. - - **binary**: @ref binary_t / `std::vector` may be used, + - **binary**: @ref binary_t / `std::vector` may be used, unfortunately because string literals cannot be distinguished from binary character arrays by the C++ type system, all types compatible with `const char*` will be directed to the string constructor instead. This is both @@ -19147,7 +19212,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @since version 3.8.0 */ JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json binary(const typename binary_t::container_type& init, std::uint8_t subtype) + static basic_json binary(const typename binary_t::container_type& init, typename binary_t::subtype_type subtype) { auto res = basic_json(); res.m_type = value_t::binary; @@ -19165,9 +19230,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return res; } - /// @copydoc binary(const typename binary_t::container_type&, std::uint8_t) + /// @copydoc binary(const typename binary_t::container_type&, typename binary_t::subtype_type) JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json binary(typename binary_t::container_type&& init, std::uint8_t subtype) + static basic_json binary(typename binary_t::container_type&& init, typename binary_t::subtype_type subtype) { auto res = basic_json(); res.m_type = value_t::binary; @@ -24563,6 +24628,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec binary | *size*: 65536..4294967295 | byte string (4 bytes follow) | 0x5A binary | *size*: 4294967296..18446744073709551615 | byte string (8 bytes follow) | 0x5B + Binary values with subtype are mapped to tagged values (0xD8..0xDB) + depending on the subtype, followed by a byte string, see "binary" cells + in the table above. + @note The mapping is **complete** in the sense that any JSON value type can be converted to a CBOR value. @@ -24603,16 +24672,16 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @since version 2.0.9; compact representation of floating-point numbers since version 3.8.0 */ - static std::vector to_cbor(const basic_json& j) + static std::vector to_cbor(const basic_json& j) { - std::vector result; + std::vector result; to_cbor(j, result); return result; } - static void to_cbor(const basic_json& j, detail::output_adapter o) + static void to_cbor(const basic_json& j, detail::output_adapter o) { - binary_writer(o).write_cbor(j); + binary_writer(o).write_cbor(j); } static void to_cbor(const basic_json& j, detail::output_adapter o) @@ -24698,16 +24767,16 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @since version 2.0.9 */ - static std::vector to_msgpack(const basic_json& j) + static std::vector to_msgpack(const basic_json& j) { - std::vector result; + std::vector result; to_msgpack(j, result); return result; } - static void to_msgpack(const basic_json& j, detail::output_adapter o) + static void to_msgpack(const basic_json& j, detail::output_adapter o) { - binary_writer(o).write_msgpack(j); + binary_writer(o).write_msgpack(j); } static void to_msgpack(const basic_json& j, detail::output_adapter o) @@ -24801,19 +24870,19 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @since version 3.1.0 */ - static std::vector to_ubjson(const basic_json& j, - const bool use_size = false, - const bool use_type = false) + static std::vector to_ubjson(const basic_json& j, + const bool use_size = false, + const bool use_type = false) { - std::vector result; + std::vector result; to_ubjson(j, result, use_size, use_type); return result; } - static void to_ubjson(const basic_json& j, detail::output_adapter o, + static void to_ubjson(const basic_json& j, detail::output_adapter o, const bool use_size = false, const bool use_type = false) { - binary_writer(o).write_ubjson(j, use_size, use_type); + binary_writer(o).write_ubjson(j, use_size, use_type); } static void to_ubjson(const basic_json& j, detail::output_adapter o, @@ -24879,9 +24948,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @sa see @ref to_cbor(const basic_json&) for the related CBOR format @sa see @ref to_msgpack(const basic_json&) for the related MessagePack format */ - static std::vector to_bson(const basic_json& j) + static std::vector to_bson(const basic_json& j) { - std::vector result; + std::vector result; to_bson(j, result); return result; } @@ -24894,13 +24963,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec @pre The input `j` shall be an object: `j.is_object() == true` @sa see @ref to_bson(const basic_json&) */ - static void to_bson(const basic_json& j, detail::output_adapter o) + static void to_bson(const basic_json& j, detail::output_adapter o) { - binary_writer(o).write_bson(j); + binary_writer(o).write_bson(j); } /*! - @copydoc to_bson(const basic_json&, detail::output_adapter) + @copydoc to_bson(const basic_json&, detail::output_adapter) */ static void to_bson(const basic_json& j, detail::output_adapter o) { diff --git a/test/src/unit-byte_container_with_subtype.cpp b/test/src/unit-byte_container_with_subtype.cpp new file mode 100644 index 0000000000..ba9c3c9e1d --- /dev/null +++ b/test/src/unit-byte_container_with_subtype.cpp @@ -0,0 +1,97 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ (test suite) +| | |__ | | | | | | version 3.9.1 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2013-2019 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "doctest_compatibility.h" + +#include +using nlohmann::json; + +TEST_CASE("byte_container_with_subtype") +{ + using subtype_type = nlohmann::byte_container_with_subtype>::subtype_type; + + SECTION("empty container") + { + nlohmann::byte_container_with_subtype> container; + + CHECK(!container.has_subtype()); + CHECK(container.subtype() == subtype_type(-1)); + + container.clear_subtype(); + CHECK(!container.has_subtype()); + CHECK(container.subtype() == subtype_type(-1)); + + container.set_subtype(42); + CHECK(container.has_subtype()); + CHECK(container.subtype() == 42); + } + + SECTION("subtyped container") + { + nlohmann::byte_container_with_subtype> container({}, 42); + CHECK(container.has_subtype()); + CHECK(container.subtype() == 42); + + container.clear_subtype(); + CHECK(!container.has_subtype()); + CHECK(container.subtype() == subtype_type(-1)); + } + + SECTION("comparisons") + { + std::vector bytes = {{0xCA, 0xFE, 0xBA, 0xBE}}; + nlohmann::byte_container_with_subtype> container1; + nlohmann::byte_container_with_subtype> container2({}, 42); + nlohmann::byte_container_with_subtype> container3(bytes); + nlohmann::byte_container_with_subtype> container4(bytes, 42); + + CHECK(container1 == container1); + CHECK(container1 != container2); + CHECK(container1 != container3); + CHECK(container1 != container4); + CHECK(container2 != container1); + CHECK(container2 == container2); + CHECK(container2 != container3); + CHECK(container2 != container4); + CHECK(container3 != container1); + CHECK(container3 != container2); + CHECK(container3 == container3); + CHECK(container3 != container4); + CHECK(container4 != container1); + CHECK(container4 != container2); + CHECK(container4 != container3); + CHECK(container4 == container4); + + container3.clear(); + container4.clear(); + + CHECK(container1 == container3); + CHECK(container2 == container4); + } +} diff --git a/test/src/unit-cbor.cpp b/test/src/unit-cbor.cpp index d3b81d8149..e35d781220 100644 --- a/test/src/unit-cbor.cpp +++ b/test/src/unit-cbor.cpp @@ -2486,7 +2486,22 @@ TEST_CASE("examples from RFC 7049 Appendix A") 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 {}, 0x42)) == std::vector {0xd8, 0x42, 0x40}); + CHECK(!json::from_cbor(json::to_cbor(json::binary(std::vector {}, 0x42)), true, true, json::cbor_tag_handler_t::ignore).get_binary().has_subtype()); + CHECK(json::from_cbor(json::to_cbor(json::binary(std::vector {}, 0x42)), true, true, json::cbor_tag_handler_t::store).get_binary().subtype() == 0x42); + // 0xd9 + CHECK(json::to_cbor(json::binary(std::vector {}, 1000)) == std::vector {0xd9, 0x03, 0xe8, 0x40}); + CHECK(!json::from_cbor(json::to_cbor(json::binary(std::vector {}, 1000)), true, true, json::cbor_tag_handler_t::ignore).get_binary().has_subtype()); + CHECK(json::from_cbor(json::to_cbor(json::binary(std::vector {}, 1000)), true, true, json::cbor_tag_handler_t::store).get_binary().subtype() == 1000); + // 0xda + CHECK(json::to_cbor(json::binary(std::vector {}, 394216)) == std::vector {0xda, 0x00, 0x06, 0x03, 0xe8, 0x40}); + CHECK(!json::from_cbor(json::to_cbor(json::binary(std::vector {}, 394216)), true, true, json::cbor_tag_handler_t::ignore).get_binary().has_subtype()); + CHECK(json::from_cbor(json::to_cbor(json::binary(std::vector {}, 394216)), true, true, json::cbor_tag_handler_t::store).get_binary().subtype() == 394216); + // 0xdb + CHECK(json::to_cbor(json::binary(std::vector {}, 8589934590)) == std::vector {0xdb, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xfe, 0x40}); + CHECK(!json::from_cbor(json::to_cbor(json::binary(std::vector {}, 8589934590)), true, true, json::cbor_tag_handler_t::ignore).get_binary().has_subtype()); + CHECK(json::from_cbor(json::to_cbor(json::binary(std::vector {}, 8589934590)), true, true, json::cbor_tag_handler_t::store).get_binary().subtype() == 8589934590); } SECTION("arrays") @@ -2545,6 +2560,8 @@ TEST_CASE("Tagged values") 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4 }) { + CAPTURE(b); + // add tag to value auto v_tagged = v; v_tagged.insert(v_tagged.begin(), b); @@ -2557,6 +2574,9 @@ TEST_CASE("Tagged values") // 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); } }