Skip to content

Commit

Permalink
Merge pull request #2908 from nlohmann/issue2863
Browse files Browse the repository at this point in the history
Fix binary subtypes
  • Loading branch information
nlohmann authored Aug 14, 2021
2 parents 6ac037c + 1aceeff commit 1300d2e
Show file tree
Hide file tree
Showing 16 changed files with 378 additions and 116 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion doc/mkdocs/docs/api/basic_json/binary_t.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
8 changes: 6 additions & 2 deletions doc/mkdocs/docs/api/basic_json/cbor_tag_handler_t.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
enum class cbor_tag_handler_t
{
error,
ignore
ignore,
store
};
```

Expand All @@ -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.
4 changes: 3 additions & 1 deletion doc/mkdocs/docs/features/binary_formats/cbor.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion doc/mkdocs/docs/features/binary_formats/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

Expand Down
9 changes: 5 additions & 4 deletions doc/mkdocs/docs/features/binary_values.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -68,14 +68,15 @@ 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);
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
```
Expand Down Expand Up @@ -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
{
Expand Down
23 changes: 13 additions & 10 deletions include/nlohmann/byte_container_with_subtype.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#pragma once

#include <cstdint> // uint8_t
#include <cstdint> // uint8_t, uint64_t
#include <tuple> // tie
#include <utility> // move

Expand All @@ -18,14 +18,16 @@ order to override the binary type.
@tparam BinaryType container to store bytes (`std::vector<std::uint8_t>` 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<typename BinaryType>
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()
Expand All @@ -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)
Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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);
}

/*!
Expand Down Expand Up @@ -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;
};

Expand Down
2 changes: 1 addition & 1 deletion include/nlohmann/detail/hash.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ std::size_t hash(const BasicJsonType& j)
auto seed = combine(type, j.get_binary().size());
const auto h = std::hash<bool> {}(j.get_binary().has_subtype());
seed = combine(seed, h);
seed = combine(seed, j.get_binary().subtype());
seed = combine(seed, static_cast<std::size_t>(j.get_binary().subtype()));
for (const auto byte : j.get_binary())
{
seed = combine(seed, std::hash<std::uint8_t> {}(byte));
Expand Down
63 changes: 53 additions & 10 deletions include/nlohmann/detail/input/binary_reader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

/*!
Expand Down Expand Up @@ -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:
Expand All @@ -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<typename binary_t::subtype_type>(subtype));
break;
}
case 0xD9:
{
std::uint16_t subtype{};
get_number(input_format_t::cbor, subtype);
b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));
break;
}
case 0xDA:
{
std::uint32_t subtype{};
get_number(input_format_t::cbor, subtype);
b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));
break;
}
case 0xDB:
{
std::uint64_t subtype{};
get_number(input_format_t::cbor, subtype);
b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(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
Expand Down
2 changes: 1 addition & 1 deletion include/nlohmann/detail/input/parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
24 changes: 21 additions & 3 deletions include/nlohmann/detail/output/binary_writer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,26 @@ class binary_writer
{
if (j.m_value.binary->has_subtype())
{
write_number(static_cast<std::uint8_t>(0xd8));
write_number(j.m_value.binary->subtype());
if (j.m_value.binary->subtype() <= (std::numeric_limits<std::uint8_t>::max)())
{
write_number(static_cast<std::uint8_t>(0xd8));
write_number(static_cast<std::uint8_t>(j.m_value.binary->subtype()));
}
else if (j.m_value.binary->subtype() <= (std::numeric_limits<std::uint16_t>::max)())
{
write_number(static_cast<std::uint8_t>(0xd9));
write_number(static_cast<std::uint16_t>(j.m_value.binary->subtype()));
}
else if (j.m_value.binary->subtype() <= (std::numeric_limits<std::uint32_t>::max)())
{
write_number(static_cast<std::uint8_t>(0xda));
write_number(static_cast<std::uint32_t>(j.m_value.binary->subtype()));
}
else if (j.m_value.binary->subtype() <= (std::numeric_limits<std::uint64_t>::max)())
{
write_number(static_cast<std::uint8_t>(0xdb));
write_number(static_cast<std::uint64_t>(j.m_value.binary->subtype()));
}
}

// step 1: write control byte and the binary array size
Expand Down Expand Up @@ -1109,7 +1127,7 @@ class binary_writer
write_bson_entry_header(name, 0x05);

write_number<std::int32_t, true>(static_cast<std::int32_t>(value.size()));
write_number(value.has_subtype() ? value.subtype() : std::uint8_t(0x00));
write_number(value.has_subtype() ? static_cast<std::uint8_t>(value.subtype()) : std::uint8_t(0x00));

oa->write_characters(reinterpret_cast<const CharType*>(value.data()), value.size());
}
Expand Down
5 changes: 3 additions & 2 deletions include/nlohmann/detail/output/serializer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ class serializer

for (std::size_t i = 0; i < s.size(); ++i)
{
const auto byte = static_cast<uint8_t>(s[i]);
const auto byte = static_cast<std::uint8_t>(s[i]);

switch (decode(state, codepoint, byte))
{
Expand Down Expand Up @@ -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<NumberType>::value ||
std::is_same<NumberType, number_unsigned_t>::value ||
std::is_same<NumberType, number_integer_t>::value ||
std::is_same<NumberType, binary_char_t>::value,
Expand Down Expand Up @@ -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<NumberType, number_integer_t>::value && !(x >= 0); // see issue #755
const bool is_negative = std::is_signed<NumberType>::value && !(x >= 0); // see issue #755
number_unsigned_t abs_value;

unsigned int n_chars{};
Expand Down
Loading

0 comments on commit 1300d2e

Please sign in to comment.