From 5319e0edce7c74823b913ce8ac1da2407c7f3279 Mon Sep 17 00:00:00 2001 From: ky438 <119654600+ky438@users.noreply.github.com> Date: Wed, 8 Jul 2026 21:12:25 +1000 Subject: [PATCH] harden cbor_view and add true RFC 8949 ordering --- include/jsoncons/source.hpp | 10 +- include/jsoncons_ext/cbor/cbor_encoder.hpp | 226 +------- include/jsoncons_ext/cbor/cbor_parser.hpp | 27 +- include/jsoncons_ext/cbor/cbor_view.hpp | 620 ++++----------------- test/cbor/src/cbor_cursor_tests.cpp | 196 ++++++- 5 files changed, 305 insertions(+), 774 deletions(-) diff --git a/include/jsoncons/source.hpp b/include/jsoncons/source.hpp index cd8e7b42a..71b577898 100644 --- a/include/jsoncons/source.hpp +++ b/include/jsoncons/source.hpp @@ -465,7 +465,7 @@ namespace jsoncons { std::streamsize count = sbuf_->sgetn(reinterpret_cast(buffer_ + length_), buffer_size_ - length_); std::size_t len = static_cast(count); length_ += len; - if (len == 0 || length_ < length) + if (len == 0) { stream_ptr_->clear(stream_ptr_->rdstate() | std::ios::eofbit); } @@ -692,6 +692,10 @@ namespace jsoncons { span read_span(std::size_t length) { + if (length > default_max_buffer_size) + { + return span(); + } if (length > buffer_.size()) { buffer_.resize(length); @@ -924,6 +928,10 @@ namespace jsoncons { span read_span(std::size_t length) { + if (length > default_max_buffer_size) + { + return span(); + } if (length > buffer_.size()) { buffer_.resize(length); diff --git a/include/jsoncons_ext/cbor/cbor_encoder.hpp b/include/jsoncons_ext/cbor/cbor_encoder.hpp index 7ab7a6126..21cbc2b0c 100644 --- a/include/jsoncons_ext/cbor/cbor_encoder.hpp +++ b/include/jsoncons_ext/cbor/cbor_encoder.hpp @@ -284,39 +284,7 @@ class basic_cbor_encoder final : public basic_json_visitor } stack_.emplace_back(cbor_container_type::object, length); - if (length <= 0x17) - { - binary::native_to_big(static_cast(0xa0 + length), - std::back_inserter(sink_)); - } - else if (length <= 0xff) - { - binary::native_to_big(static_cast(0xb8), - std::back_inserter(sink_)); - binary::native_to_big(static_cast(length), - std::back_inserter(sink_)); - } - else if (length <= 0xffff) - { - binary::native_to_big(static_cast(0xb9), - std::back_inserter(sink_)); - binary::native_to_big(static_cast(length), - std::back_inserter(sink_)); - } - else if (length <= 0xffffffff) - { - binary::native_to_big(static_cast(0xba), - std::back_inserter(sink_)); - binary::native_to_big(static_cast(length), - std::back_inserter(sink_)); - } - else if (uint64_t(length) <= (std::numeric_limits::max)()) - { - binary::native_to_big(static_cast(0xbb), - std::back_inserter(sink_)); - binary::native_to_big(static_cast(length), - std::back_inserter(sink_)); - } + write_type_and_length(0xa0, length); JSONCONS_VISITOR_RETURN; } @@ -370,39 +338,7 @@ class basic_cbor_encoder final : public basic_json_visitor JSONCONS_VISITOR_RETURN; } stack_.emplace_back(cbor_container_type::array, length); - if (length <= 0x17) - { - binary::native_to_big(static_cast(0x80 + length), - std::back_inserter(sink_)); - } - else if (length <= 0xff) - { - binary::native_to_big(static_cast(0x98), - std::back_inserter(sink_)); - binary::native_to_big(static_cast(length), - std::back_inserter(sink_)); - } - else if (length <= 0xffff) - { - binary::native_to_big(static_cast(0x99), - std::back_inserter(sink_)); - binary::native_to_big(static_cast(length), - std::back_inserter(sink_)); - } - else if (length <= 0xffffffff) - { - binary::native_to_big(static_cast(0x9a), - std::back_inserter(sink_)); - binary::native_to_big(static_cast(length), - std::back_inserter(sink_)); - } - else if (uint64_t(length) <= (std::numeric_limits::max)()) - { - binary::native_to_big(static_cast(0x9b), - std::back_inserter(sink_)); - binary::native_to_big(static_cast(length), - std::back_inserter(sink_)); - } + write_type_and_length(0x80, length); JSONCONS_VISITOR_RETURN; } @@ -485,39 +421,33 @@ class basic_cbor_encoder final : public basic_json_visitor } } - void write_type_and_length(uint8_t major_type, std::size_t length) + void write_type_and_length(uint8_t major_type, uint64_t length) { if (length <= 0x17) { - binary::native_to_big(static_cast(major_type + length), - std::back_inserter(sink_)); + sink_.push_back(static_cast(major_type + length)); } else if (length <= 0xff) { - binary::native_to_big(static_cast(major_type + 0x18), - std::back_inserter(sink_)); - binary::native_to_big(static_cast(length), - std::back_inserter(sink_)); + sink_.push_back(static_cast(major_type + 0x18)); + sink_.push_back(static_cast(length)); } else if (length <= 0xffff) { - binary::native_to_big(static_cast(major_type + 0x19), - std::back_inserter(sink_)); - binary::native_to_big(static_cast(length), + sink_.push_back(static_cast(major_type + 0x19)); + binary::native_to_big(static_cast(length), std::back_inserter(sink_)); } else if (length <= 0xffffffff) { - binary::native_to_big(static_cast(major_type + 0x1a), - std::back_inserter(sink_)); - binary::native_to_big(static_cast(length), + sink_.push_back(static_cast(major_type + 0x1a)); + binary::native_to_big(static_cast(length), std::back_inserter(sink_)); } - else if (uint64_t(length) <= (std::numeric_limits::max)()) + else { - binary::native_to_big(static_cast(major_type + 0x1b), - std::back_inserter(sink_)); - binary::native_to_big(static_cast(length), + sink_.push_back(static_cast(major_type + 0x1b)); + binary::native_to_big(static_cast(length), std::back_inserter(sink_)); } } @@ -1166,139 +1096,23 @@ class basic_cbor_encoder final : public basic_json_visitor void write_tag(uint64_t value) { - if (value <= 0x17) - { - sink_.push_back(0xc0 | static_cast(value)); - } - else if (value <=(std::numeric_limits::max)()) - { - sink_.push_back(0xd8); - sink_.push_back(static_cast(value)); - } - else if (value <=(std::numeric_limits::max)()) - { - sink_.push_back(0xd9); - binary::native_to_big(static_cast(value), - std::back_inserter(sink_)); - } - else if (value <=(std::numeric_limits::max)()) - { - sink_.push_back(0xda); - binary::native_to_big(static_cast(value), - std::back_inserter(sink_)); - } - else - { - sink_.push_back(0xdb); - binary::native_to_big(static_cast(value), - std::back_inserter(sink_)); - } + write_type_and_length(0xc0, value); } - void write_uint64_value(uint64_t value) + void write_uint64_value(uint64_t value) { - if (value <= 0x17) - { - sink_.push_back(static_cast(value)); - } - else if (value <=(std::numeric_limits::max)()) - { - sink_.push_back(static_cast(0x18)); - sink_.push_back(static_cast(value)); - } - else if (value <=(std::numeric_limits::max)()) - { - sink_.push_back(static_cast(0x19)); - binary::native_to_big(static_cast(value), - std::back_inserter(sink_)); - } - else if (value <=(std::numeric_limits::max)()) - { - sink_.push_back(static_cast(0x1a)); - binary::native_to_big(static_cast(value), - std::back_inserter(sink_)); - } - else if (value <=(std::numeric_limits::max)()) - { - sink_.push_back(static_cast(0x1b)); - binary::native_to_big(static_cast(value), - std::back_inserter(sink_)); - } + write_type_and_length(0x00, value); } void write_int64_value(int64_t value) { if (value >= 0) { - if (value <= 0x17) - { - binary::native_to_big(static_cast(value), - std::back_inserter(sink_)); - } - else if (value <= (std::numeric_limits::max)()) - { - binary::native_to_big(static_cast(0x18), - std::back_inserter(sink_)); - binary::native_to_big(static_cast(value), - std::back_inserter(sink_)); - } - else if (value <= (std::numeric_limits::max)()) - { - binary::native_to_big(static_cast(0x19), - std::back_inserter(sink_)); - binary::native_to_big(static_cast(value), - std::back_inserter(sink_)); - } - else if (value <= (std::numeric_limits::max)()) - { - binary::native_to_big(static_cast(0x1a), - std::back_inserter(sink_)); - binary::native_to_big(static_cast(value), - std::back_inserter(sink_)); - } - else if (value <= (std::numeric_limits::max)()) - { - binary::native_to_big(static_cast(0x1b), - std::back_inserter(sink_)); - binary::native_to_big(static_cast(value), - std::back_inserter(sink_)); - } - } else + write_type_and_length(0x00, static_cast(value)); + } + else { - const auto posnum = -1 - value; - if (value >= -24) - { - binary::native_to_big(static_cast(0x20 + posnum), - std::back_inserter(sink_)); - } - else if (posnum <= (std::numeric_limits::max)()) - { - binary::native_to_big(static_cast(0x38), - std::back_inserter(sink_)); - binary::native_to_big(static_cast(posnum), - std::back_inserter(sink_)); - } - else if (posnum <= (std::numeric_limits::max)()) - { - binary::native_to_big(static_cast(0x39), - std::back_inserter(sink_)); - binary::native_to_big(static_cast(posnum), - std::back_inserter(sink_)); - } - else if (posnum <= (std::numeric_limits::max)()) - { - binary::native_to_big(static_cast(0x3a), - std::back_inserter(sink_)); - binary::native_to_big(static_cast(posnum), - std::back_inserter(sink_)); - } - else if (posnum <= (std::numeric_limits::max)()) - { - binary::native_to_big(static_cast(0x3b), - std::back_inserter(sink_)); - binary::native_to_big(static_cast(posnum), - std::back_inserter(sink_)); - } + write_type_and_length(0x20, static_cast(-1 - value)); } } diff --git a/include/jsoncons_ext/cbor/cbor_parser.hpp b/include/jsoncons_ext/cbor/cbor_parser.hpp index d72bbbaf5..228c4fa47 100644 --- a/include/jsoncons_ext/cbor/cbor_parser.hpp +++ b/include/jsoncons_ext/cbor/cbor_parser.hpp @@ -2045,29 +2045,7 @@ class basic_cbor_parser : public ser_context break; } case 0x15: - { - auto bytes = read.view(ec); - if (JSONCONS_UNLIKELY(ec)) - { - more_ = false; - return; - } - visitor.byte_string_value(bytes, semantic_tag::base64url, *this, ec); - more_ = !cursor_mode_; - break; - } case 0x16: - { - auto bytes = read.view(ec); - if (JSONCONS_UNLIKELY(ec)) - { - more_ = false; - return; - } - visitor.byte_string_value(bytes, semantic_tag::base64, *this, ec); - more_ = !cursor_mode_; - break; - } case 0x17: { auto bytes = read.view(ec); @@ -2076,7 +2054,10 @@ class basic_cbor_parser : public ser_context more_ = false; return; } - visitor.byte_string_value(bytes, semantic_tag::base16, *this, ec); + const semantic_tag tag = raw_tag_ == 0x15 ? semantic_tag::base64url + : raw_tag_ == 0x16 ? semantic_tag::base64 + : semantic_tag::base16; + visitor.byte_string_value(bytes, tag, *this, ec); more_ = !cursor_mode_; break; } diff --git a/include/jsoncons_ext/cbor/cbor_view.hpp b/include/jsoncons_ext/cbor/cbor_view.hpp index 5bbcc9b71..9252dbe32 100644 --- a/include/jsoncons_ext/cbor/cbor_view.hpp +++ b/include/jsoncons_ext/cbor/cbor_view.hpp @@ -8,17 +8,14 @@ #define JSONCONS_EXT_CBOR_CBOR_VIEW_HPP #include -#include #include #include #include #include -#include #include #include #include -#include #include #include @@ -26,20 +23,7 @@ namespace jsoncons { namespace cbor { namespace view { - enum class type_rank : uint8_t - { - null_value = 0, - false_value = 1, - true_value = 2, - undefined_value = 3, - simple_value = 4, - number_value = 5, - text_string = 6, - byte_string = 7, - array = 8, - map = 9, - tag = 10 - }; + constexpr int default_max_nesting_depth = 1024; struct item_head { @@ -66,11 +50,6 @@ namespace view { return (value > 0) - (value < 0); } - inline int compare_uint64(uint64_t a, uint64_t b) noexcept - { - return a < b ? -1 : (a > b ? 1 : 0); - } - inline int compare_size(std::size_t a, std::size_t b) noexcept { return a < b ? -1 : (a > b ? 1 : 0); @@ -156,7 +135,7 @@ namespace view { return read_uint(p, end, head.additional_info, head.value, ec); } - inline bool skip_item(const uint8_t*& p, const uint8_t* end, std::error_code& ec); + inline bool skip_item(const uint8_t*& p, const uint8_t* end, int depth, std::error_code& ec); inline bool skip_string_chunks(const uint8_t*& p, const uint8_t* end, cbor::detail::cbor_major_type expected_major, std::error_code& ec) @@ -193,7 +172,8 @@ namespace view { } } - inline bool skip_array_items(const uint8_t*& p, const uint8_t* end, const item_head& head, std::error_code& ec) + inline bool skip_container_items(const uint8_t*& p, const uint8_t* end, const item_head& head, + std::size_t items_per_entry, int depth, std::error_code& ec) { if (head.indefinite()) { @@ -209,77 +189,47 @@ namespace view { ++p; return true; } - if (!skip_item(p, end, ec)) + for (std::size_t i = 0; i < items_per_entry; ++i) { - return false; + if (!skip_item(p, end, depth, ec)) + { + return false; + } } } } for (uint64_t i = 0; i < head.value; ++i) { - if (!skip_item(p, end, ec)) - { - return false; - } - } - return true; - } - - inline bool skip_map_items(const uint8_t*& p, const uint8_t* end, const item_head& head, std::error_code& ec) - { - if (head.indefinite()) - { - for (;;) + for (std::size_t j = 0; j < items_per_entry; ++j) { - if (p >= end) + if (!skip_item(p, end, depth, ec)) { - ec = cbor_errc::unexpected_eof; return false; } - if (*p == 0xff) - { - ++p; - return true; - } - if (!skip_item(p, end, ec) || !skip_item(p, end, ec)) - { - return false; - } - } - } - - for (uint64_t i = 0; i < head.value; ++i) - { - if (!skip_item(p, end, ec) || !skip_item(p, end, ec)) - { - return false; } } return true; } - inline bool skip_item(const uint8_t*& p, const uint8_t* end, std::error_code& ec) + inline bool skip_item(const uint8_t*& p, const uint8_t* end, int depth, std::error_code& ec) { item_head head; - if (!read_head(p, end, head, ec)) + do { - return false; + if (!read_head(p, end, head, ec)) + { + return false; + } } + while (head.major_type == cbor::detail::cbor_major_type::semantic_tag); switch (head.major_type) { case cbor::detail::cbor_major_type::unsigned_integer: case cbor::detail::cbor_major_type::negative_integer: - return true; - case cbor::detail::cbor_major_type::simple: - if (head.indefinite()) - { - ec = cbor_errc::unknown_type; - return false; - } - return !head.indefinite(); + return true; case cbor::detail::cbor_major_type::byte_string: case cbor::detail::cbor_major_type::text_string: @@ -296,111 +246,17 @@ namespace view { return true; case cbor::detail::cbor_major_type::array: - return skip_array_items(p, end, head, ec); - case cbor::detail::cbor_major_type::map: - return skip_map_items(p, end, head, ec); - - case cbor::detail::cbor_major_type::semantic_tag: - return skip_item(p, end, ec); - } - - ec = cbor_errc::unknown_type; - return false; - } - - inline span read_item_span(const uint8_t*& p, const uint8_t* end, std::error_code& ec) - { - const uint8_t* first = p; - if (!skip_item(p, end, ec)) - { - return span(); - } - return span(first, static_cast(p - first)); - } - - inline type_rank rank_for(const item_head& head) noexcept - { - switch (head.major_type) - { - case cbor::detail::cbor_major_type::unsigned_integer: - case cbor::detail::cbor_major_type::negative_integer: - return type_rank::number_value; - case cbor::detail::cbor_major_type::byte_string: - return type_rank::byte_string; - case cbor::detail::cbor_major_type::text_string: - return type_rank::text_string; - case cbor::detail::cbor_major_type::array: - return type_rank::array; - case cbor::detail::cbor_major_type::map: - return type_rank::map; - case cbor::detail::cbor_major_type::semantic_tag: - return type_rank::tag; - case cbor::detail::cbor_major_type::simple: { - const uint64_t simple = head.additional_info == 24 ? head.value : head.additional_info; - switch (simple) + if (JSONCONS_UNLIKELY(depth <= 0)) { - case 20: return type_rank::false_value; - case 21: return type_rank::true_value; - case 22: return type_rank::null_value; - case 23: return type_rank::undefined_value; - default: - return (head.additional_info == 25 || head.additional_info == 26 || head.additional_info == 27) - ? type_rank::number_value - : type_rank::simple_value; + ec = cbor_errc::max_nesting_depth_exceeded; + return false; } + const std::size_t items_per_entry = + head.major_type == cbor::detail::cbor_major_type::map ? 2 : 1; + return skip_container_items(p, end, head, items_per_entry, depth - 1, ec); } - } - return type_rank::simple_value; - } - - inline bool parse_number(span item, long double& value, std::error_code& ec) - { - const uint8_t* p = item.data(); - const uint8_t* end = p + item.size(); - item_head head; - if (!read_head(p, end, head, ec)) - { - return false; - } - - switch (head.major_type) - { - case cbor::detail::cbor_major_type::unsigned_integer: - value = static_cast(head.value); - return true; - - case cbor::detail::cbor_major_type::negative_integer: - value = -1.0L - static_cast(head.value); - return true; - - case cbor::detail::cbor_major_type::simple: - switch (head.additional_info) - { - case 25: - value = static_cast(binary::decode_half(static_cast(head.value))); - return true; - case 26: - { - uint32_t bits = static_cast(head.value); - float f; - std::memcpy(&f, &bits, sizeof(f)); - value = static_cast(f); - return true; - } - case 27: - { - uint64_t bits = head.value; - double d; - std::memcpy(&d, &bits, sizeof(d)); - value = static_cast(d); - return true; - } - default: - break; - } - break; default: break; @@ -410,232 +266,18 @@ namespace view { return false; } - inline int compare_numbers(span a, span b, std::error_code& ec) + inline span read_item_span(const uint8_t*& p, const uint8_t* end, int depth, std::error_code& ec) { - long double av = 0; - long double bv = 0; - if (!parse_number(a, av, ec) || !parse_number(b, bv, ec)) - { - return 0; - } - - const bool anan = std::isnan(av); - const bool bnan = std::isnan(bv); - if (anan || bnan) - { - return anan == bnan ? 0 : (anan ? -1 : 1); - } - if (av == 0.0L && bv == 0.0L) - { - return 0; - } - return av < bv ? -1 : (av > bv ? 1 : 0); - } - - class string_chunk_reader - { - cbor::detail::cbor_major_type major_type_{}; - const uint8_t* p_{nullptr}; - const uint8_t* end_{nullptr}; - span payload_; - bool indefinite_{false}; - bool done_{false}; - - public: - string_chunk_reader(span item, std::error_code& ec) - { - p_ = item.data(); - end_ = p_ + item.size(); - item_head head; - if (!read_head(p_, end_, head, ec)) - { - done_ = true; - return; - } - major_type_ = head.major_type; - if (major_type_ != cbor::detail::cbor_major_type::byte_string && - major_type_ != cbor::detail::cbor_major_type::text_string) - { - ec = cbor_errc::unknown_type; - done_ = true; - return; - } - indefinite_ = head.indefinite(); - if (!indefinite_) - { - if (static_cast(end_ - p_) < head.value) - { - ec = cbor_errc::unexpected_eof; - done_ = true; - return; - } - payload_ = span(p_, static_cast(head.value)); - p_ += static_cast(head.value); - } - } - - bool next(span& chunk, std::error_code& ec) - { - chunk = span(); - if (done_) - { - return false; - } - if (!indefinite_) - { - done_ = true; - chunk = payload_; - return true; - } - - for (;;) - { - if (p_ >= end_) - { - ec = cbor_errc::unexpected_eof; - done_ = true; - return false; - } - if (*p_ == 0xff) - { - ++p_; - done_ = true; - return false; - } - - item_head head; - if (!read_head(p_, end_, head, ec)) - { - done_ = true; - return false; - } - if (head.major_type != major_type_ || head.indefinite()) - { - ec = cbor_errc::illegal_chunked_string; - done_ = true; - return false; - } - if (static_cast(end_ - p_) < head.value) - { - ec = cbor_errc::unexpected_eof; - done_ = true; - return false; - } - chunk = span(p_, static_cast(head.value)); - p_ += static_cast(head.value); - return true; - } - } - }; - - inline int compare_string_items(span a, span b, std::error_code& ec) - { - string_chunk_reader ar(a, ec); - if (ec) { return 0; } - string_chunk_reader br(b, ec); - if (ec) { return 0; } - - span ac; - span bc; - std::size_t ai = 0; - std::size_t bi = 0; - bool ahas = false; - bool bhas = false; - - for (;;) - { - while (!ahas || ai == ac.size()) - { - ahas = ar.next(ac, ec); - if (ec) { return 0; } - ai = 0; - if (!ahas || ac.size() != 0) - { - break; - } - } - while (!bhas || bi == bc.size()) - { - bhas = br.next(bc, ec); - if (ec) { return 0; } - bi = 0; - if (!bhas || bc.size() != 0) - { - break; - } - } - - if (!ahas || !bhas) - { - return ahas == bhas ? 0 : (ahas ? 1 : -1); - } - - const std::size_t n = (std::min)(ac.size() - ai, bc.size() - bi); - if (n != 0) - { - int r = std::memcmp(ac.data() + ai, bc.data() + bi, n); - if (r != 0) - { - return sign(r); - } - ai += n; - bi += n; - } - } - } - - inline int compare_items(span a, span b, std::error_code& ec); - - inline int compare_arrays(span a, span b, std::error_code& ec) - { - const uint8_t* ap = a.data(); - const uint8_t* bp = b.data(); - const uint8_t* aend = ap + a.size(); - const uint8_t* bend = bp + b.size(); - item_head ah; - item_head bh; - if (!read_head(ap, aend, ah, ec) || !read_head(bp, bend, bh, ec)) - { - return 0; - } - - uint64_t ai = 0; - uint64_t bi = 0; - for (;;) + const uint8_t* first = p; + if (!skip_item(p, end, depth, ec)) { - if (ah.indefinite() && ap >= aend) - { - ec = cbor_errc::unexpected_eof; - return 0; - } - if (bh.indefinite() && bp >= bend) - { - ec = cbor_errc::unexpected_eof; - return 0; - } - const bool adone = ah.indefinite() ? (*ap == 0xff) : (ai == ah.value); - const bool bdone = bh.indefinite() ? (*bp == 0xff) : (bi == bh.value); - if (adone || bdone) - { - return adone == bdone ? 0 : (adone ? -1 : 1); - } - - auto av = read_item_span(ap, aend, ec); - if (ec) { return 0; } - auto bv = read_item_span(bp, bend, ec); - if (ec) { return 0; } - ++ai; - ++bi; - - int c = compare_items(av, bv, ec); - if (ec || c != 0) - { - return c; - } + return span(); } + return span(first, static_cast(p - first)); } - inline bool read_map_entries(span item, std::vector& entries, std::error_code& ec) + inline bool read_map_entries(span item, std::vector& entries, + int depth, std::error_code& ec) { const uint8_t* p = item.data(); const uint8_t* end = p + item.size(); @@ -649,6 +291,11 @@ namespace view { ec = cbor_errc::unknown_type; return false; } + if (JSONCONS_UNLIKELY(depth <= 0)) + { + ec = cbor_errc::max_nesting_depth_exceeded; + return false; + } if (!head.indefinite()) { @@ -657,12 +304,13 @@ namespace view { ec = cbor_errc::number_too_large; return false; } - entries.reserve(static_cast(head.value)); + entries.reserve(static_cast( + (std::min)(head.value, static_cast(end - p) / 2))); for (uint64_t i = 0; i < head.value; ++i) { - auto key = read_item_span(p, end, ec); + auto key = read_item_span(p, end, depth - 1, ec); if (ec) { return false; } - auto value = read_item_span(p, end, ec); + auto value = read_item_span(p, end, depth - 1, ec); if (ec) { return false; } entries.push_back(map_entry_view{key, value}); } @@ -681,160 +329,77 @@ namespace view { ++p; return true; } - auto key = read_item_span(p, end, ec); + auto key = read_item_span(p, end, depth - 1, ec); if (ec) { return false; } - auto value = read_item_span(p, end, ec); + auto value = read_item_span(p, end, depth - 1, ec); if (ec) { return false; } entries.push_back(map_entry_view{key, value}); } } - inline int compare_maps(span a, span b, std::error_code& ec) - { - std::vector ae; - std::vector be; - if (!read_map_entries(a, ae, ec) || !read_map_entries(b, be, ec)) - { - return 0; - } - - auto less = [&ec](const map_entry_view& x, const map_entry_view& y) - { - int ck = compare_items(x.key, y.key, ec); - if (ec || ck != 0) - { - return ck < 0; - } - return compare_items(x.value, y.value, ec) < 0; - }; - std::sort(ae.begin(), ae.end(), less); - if (ec) { return 0; } - std::sort(be.begin(), be.end(), less); - if (ec) { return 0; } - - const std::size_t n = (std::min)(ae.size(), be.size()); - for (std::size_t i = 0; i < n; ++i) - { - int ck = compare_items(ae[i].key, be[i].key, ec); - if (ec || ck != 0) - { - return ck; - } - int cv = compare_items(ae[i].value, be[i].value, ec); - if (ec || cv != 0) - { - return cv; - } - } - return compare_size(ae.size(), be.size()); - } + } // namespace detail_view - inline int compare_tags(span a, span b, std::error_code& ec) + struct bytewise_order + { + int operator()(span a, span b) const noexcept { - const uint8_t* ap = a.data(); - const uint8_t* bp = b.data(); - const uint8_t* aend = ap + a.size(); - const uint8_t* bend = bp + b.size(); - item_head ah; - item_head bh; - if (!read_head(ap, aend, ah, ec) || !read_head(bp, bend, bh, ec)) - { - return 0; - } - int tag_compare = compare_uint64(ah.value, bh.value); - if (tag_compare != 0) - { - return tag_compare; - } - auto av = read_item_span(ap, aend, ec); - if (ec) { return 0; } - auto bv = read_item_span(bp, bend, ec); - if (ec) { return 0; } - return compare_items(av, bv, ec); + return detail_view::compare_bytes(a, b); } + }; - inline int compare_items(span a, span b, std::error_code& ec) + struct length_first_order + { + int operator()(span a, span b) const noexcept { - if (a.size() == b.size() && compare_bytes(a, b) == 0) - { - return 0; - } - - const uint8_t* ap = a.data(); - const uint8_t* bp = b.data(); - item_head ah; - item_head bh; - if (!read_head(ap, ap + a.size(), ah, ec) || !read_head(bp, bp + b.size(), bh, ec)) - { - return 0; - } - - const type_rank ar = rank_for(ah); - const type_rank br = rank_for(bh); - if (ar != br) - { - return static_cast(ar) < static_cast(br) ? -1 : 1; - } - - switch (ar) - { - case type_rank::null_value: - case type_rank::false_value: - case type_rank::true_value: - case type_rank::undefined_value: - return 0; - - case type_rank::simple_value: - return compare_uint64(ah.additional_info == 24 ? ah.value : ah.additional_info, - bh.additional_info == 24 ? bh.value : bh.additional_info); - - case type_rank::number_value: - return compare_numbers(a, b, ec); - - case type_rank::text_string: - case type_rank::byte_string: - return compare_string_items(a, b, ec); - - case type_rank::array: - return compare_arrays(a, b, ec); - - case type_rank::map: - return compare_maps(a, b, ec); - - case type_rank::tag: - return compare_tags(a, b, ec); - } - return 0; + const int r = detail_view::compare_size(a.size(), b.size()); + return r != 0 ? r : detail_view::compare_bytes(a, b); } + }; - } // namespace detail_view - - inline bool skip_item(const uint8_t*& p, const uint8_t* end, std::error_code& ec) + inline bool skip_item(const uint8_t*& p, const uint8_t* end, std::error_code& ec, + int max_nesting_depth = default_max_nesting_depth) { - return detail_view::skip_item(p, end, ec); + return detail_view::skip_item(p, end, max_nesting_depth, ec); } - inline span item_span(span input, std::error_code& ec) + inline span item_span(span input, std::error_code& ec, + int max_nesting_depth = default_max_nesting_depth) { const uint8_t* p = input.data(); - return detail_view::read_item_span(p, p + input.size(), ec); + return detail_view::read_item_span(p, p + input.size(), max_nesting_depth, ec); } - inline bool map_entries(span input, std::vector& entries, std::error_code& ec) + inline bool map_entries(span input, std::vector& entries, std::error_code& ec, + int max_nesting_depth = default_max_nesting_depth) { entries.clear(); - return detail_view::read_map_entries(input, entries, ec); + return detail_view::read_map_entries(input, entries, max_nesting_depth, ec); } - inline int compare(span a, span b, std::error_code& ec) + template + int compare(span a, span b, std::error_code& ec, + Order order = Order(), int max_nesting_depth = default_max_nesting_depth) { - return detail_view::compare_items(a, b, ec); + const uint8_t* ap = a.data(); + auto ia = detail_view::read_item_span(ap, ap + a.size(), max_nesting_depth, ec); + if (ec) + { + return 0; + } + const uint8_t* bp = b.data(); + auto ib = detail_view::read_item_span(bp, bp + b.size(), max_nesting_depth, ec); + if (ec) + { + return 0; + } + return order(ia, ib); } - inline int compare(span a, span b) + template + int compare(span a, span b) { std::error_code ec; - int result = compare(a, b, ec); + int result = compare(a, b, ec, Order()); if (JSONCONS_UNLIKELY(ec)) { JSONCONS_THROW(ser_error(ec)); @@ -842,6 +407,25 @@ namespace view { return result; } + template + bool map_keys_sorted(span input, std::error_code& ec, + Order order = Order(), int max_nesting_depth = default_max_nesting_depth) + { + std::vector entries; + if (!detail_view::read_map_entries(input, entries, max_nesting_depth, ec)) + { + return false; + } + for (std::size_t i = 1; i < entries.size(); ++i) + { + if (order(entries[i-1].key, entries[i].key) >= 0) + { + return false; + } + } + return true; + } + } // namespace view } // namespace cbor } // namespace jsoncons diff --git a/test/cbor/src/cbor_cursor_tests.cpp b/test/cbor/src/cbor_cursor_tests.cpp index 06814b0a7..d4789770a 100644 --- a/test/cbor/src/cbor_cursor_tests.cpp +++ b/test/cbor/src/cbor_cursor_tests.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -84,31 +85,47 @@ TEST_CASE("cbor raw view utilities") CHECK(item[0] == 0x01); } - SECTION("numbers share one collation rank") + SECTION("trailing bytes after the first item are ignored") { - std::vector one = {0x01}; - std::vector one_double = {0xfb,0x3f,0xf0,0x00,0x00,0x00,0x00,0x00,0x00}; + std::vector a = {0x01,0x63,'a','b','c'}; + std::vector b = {0x01}; std::error_code ec; - CHECK(cbor::view::compare(jsoncons::span(one), jsoncons::span(one_double), ec) == 0); + CHECK(cbor::view::compare(jsoncons::span(a), jsoncons::span(b), ec) == 0); CHECK_FALSE(ec); } - SECTION("arrays compare lexicographically") + SECTION("deeply nested items are bounded") { - std::vector a = {0x81,0x01}; - std::vector b = {0x82,0x01,0x00}; + std::vector data(2000, 0x81); + data.push_back(0x01); std::error_code ec; - CHECK(cbor::view::compare(jsoncons::span(a), jsoncons::span(b), ec) < 0); - CHECK_FALSE(ec); + auto item = cbor::view::item_span(jsoncons::span(data), ec); + CHECK(item.empty()); + CHECK(ec == cbor::cbor_errc::max_nesting_depth_exceeded); + + ec.clear(); + item = cbor::view::item_span(jsoncons::span(data), ec, 4000); + REQUIRE_FALSE(ec); + CHECK(item.size() == data.size()); } - SECTION("maps are key-order independent") + SECTION("tag chains do not consume nesting depth or stack") { - std::vector m1 = {0xa2,0x01,0x61,0x61,0x61,0x6b,0x02}; - std::vector m2 = {0xa2,0x61,0x6b,0x02,0x01,0x61,0x61}; + std::vector data(1000000, 0xc0); + data.push_back(0x01); std::error_code ec; - CHECK(cbor::view::compare(jsoncons::span(m1), jsoncons::span(m2), ec) == 0); - CHECK_FALSE(ec); + auto item = cbor::view::item_span(jsoncons::span(data), ec); + REQUIRE_FALSE(ec); + CHECK(item.size() == data.size()); + } + + SECTION("map claiming a huge entry count is rejected") + { + std::vector data = {0xba,0xff,0xff,0xff,0xff}; + std::vector entries; + std::error_code ec; + CHECK_FALSE(cbor::view::map_entries(jsoncons::span(data), entries, ec)); + CHECK(ec == cbor::cbor_errc::unexpected_eof); } SECTION("map entries expose key and value spans") @@ -129,27 +146,154 @@ TEST_CASE("cbor raw view utilities") CHECK(entries[1].value.size() == 1); } - SECTION("indefinite strings compare semantically") + SECTION("invalid indefinite integer is rejected") + { + std::vector data = {0x1f}; + std::error_code ec; + auto item = cbor::view::item_span(jsoncons::span(data), ec); + CHECK(item.empty()); + CHECK(ec == cbor::cbor_errc::unknown_type); + } +} + +TEST_CASE("cbor view items compare in deterministic encoding key orders") +{ + const std::vector> bytewise_sorted = { + {0x0a}, + {0x18,0x64}, + {0x20}, + {0x61,0x7a}, + {0x62,0x61,0x61}, + {0x81,0x18,0x64}, + {0x81,0x20}, + {0xf4} + }; + + const std::vector> length_first_sorted = { + {0x0a}, + {0x20}, + {0xf4}, + {0x18,0x64}, + {0x61,0x7a}, + {0x81,0x20}, + {0x62,0x61,0x61}, + {0x81,0x18,0x64} + }; + + SECTION("bytewise order matches the RFC 8949 4.2.1 example") { - std::vector definite_text = {0x65,'h','e','l','l','o'}; - std::vector chunked_text = {0x7f,0x61,'h',0x64,'e','l','l','o',0xff}; - std::vector definite_bytes = {0x42,0x01,0x02}; - std::vector chunked_bytes = {0x5f,0x41,0x01,0x41,0x02,0xff}; + for (std::size_t i = 0; i < bytewise_sorted.size(); ++i) + { + for (std::size_t j = 0; j < bytewise_sorted.size(); ++j) + { + std::error_code ec; + const int r = cbor::view::compare(jsoncons::span(bytewise_sorted[i]), + jsoncons::span(bytewise_sorted[j]), ec); + REQUIRE_FALSE(ec); + CHECK((i < j ? r < 0 : (i > j ? r > 0 : r == 0))); + } + } + } + + SECTION("length-first order matches the RFC 8949 4.2.3 example") + { + for (std::size_t i = 0; i < length_first_sorted.size(); ++i) + { + for (std::size_t j = 0; j < length_first_sorted.size(); ++j) + { + std::error_code ec; + const int r = cbor::view::compare(jsoncons::span(length_first_sorted[i]), + jsoncons::span(length_first_sorted[j]), ec, cbor::view::length_first_order()); + REQUIRE_FALSE(ec); + CHECK((i < j ? r < 0 : (i > j ? r > 0 : r == 0))); + } + } + } + + SECTION("other orderings can be plugged in") + { + struct reverse_bytewise_order + { + int operator()(jsoncons::span a, jsoncons::span b) const noexcept + { + return -cbor::view::bytewise_order()(a, b); + } + }; + + std::vector ten = {0x0a}; + std::vector hundred = {0x18,0x64}; std::error_code ec; - CHECK(cbor::view::compare(jsoncons::span(definite_text), jsoncons::span(chunked_text), ec) == 0); + CHECK(cbor::view::compare(jsoncons::span(ten), jsoncons::span(hundred), ec) < 0); REQUIRE_FALSE(ec); - CHECK(cbor::view::compare(jsoncons::span(definite_bytes), jsoncons::span(chunked_bytes), ec) == 0); + CHECK(cbor::view::compare(jsoncons::span(ten), jsoncons::span(hundred), ec, reverse_bytewise_order()) > 0); REQUIRE_FALSE(ec); } +} - SECTION("invalid indefinite integer is rejected") +TEST_CASE("cbor view map_keys_sorted") +{ + SECTION("detects deterministically ordered keys") { - std::vector data = {0x1f}; + std::vector sorted_map = {0xa2,0x01,0x61,0x61,0x61,0x6b,0x02}; + std::vector unsorted_map = {0xa2,0x61,0x6b,0x02,0x01,0x61,0x61}; std::error_code ec; - auto item = cbor::view::item_span(jsoncons::span(data), ec); - CHECK(item.empty()); - CHECK(ec == cbor::cbor_errc::unknown_type); + CHECK(cbor::view::map_keys_sorted(jsoncons::span(sorted_map), ec)); + CHECK_FALSE(ec); + CHECK_FALSE(cbor::view::map_keys_sorted(jsoncons::span(unsorted_map), ec)); + CHECK_FALSE(ec); } + + SECTION("duplicate keys are not sorted") + { + std::vector dup_map = {0xa2,0x01,0x00,0x01,0x01}; + std::error_code ec; + CHECK_FALSE(cbor::view::map_keys_sorted(jsoncons::span(dup_map), ec)); + CHECK_FALSE(ec); + } + + SECTION("bytewise and length-first orders can disagree") + { + std::vector m = {0xa2,0x18,0x64,0x00,0x20,0x00}; + std::error_code ec; + CHECK(cbor::view::map_keys_sorted(jsoncons::span(m), ec)); + CHECK_FALSE(ec); + CHECK_FALSE(cbor::view::map_keys_sorted(jsoncons::span(m), ec, cbor::view::length_first_order())); + CHECK_FALSE(ec); + } +} + +TEST_CASE("cbor stream source spans consecutive straddled strings") +{ + std::string data; + data.push_back('\x82'); + data.push_back('\x65'); + data.append("abcde"); + data.push_back('\x65'); + data.append("fghij"); + std::istringstream is(data); + jsoncons::binary_stream_source source(is, 3); + std::error_code ec; + cbor::cbor_stream_cursor cursor(std::move(source), ec); + + REQUIRE_FALSE(ec); + REQUIRE(staj_events::begin_array == cursor.current().event_type()); + cursor.next(ec); + REQUIRE_FALSE(ec); + REQUIRE(staj_events::string_value == cursor.current().event_type()); + CHECK(cursor.current().get() == "abcde"); + cursor.next(ec); + REQUIRE_FALSE(ec); + REQUIRE(staj_events::string_value == cursor.current().event_type()); + CHECK(cursor.current().get() == "fghij"); +} + +TEST_CASE("cbor iterator source rejects a huge claimed byte string cleanly") +{ + const std::vector data = {0x5b,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00}; + std::list input(data.begin(), data.end()); + + auto result = cbor::try_decode_cbor(input.begin(), input.end()); + REQUIRE_FALSE(result); } TEST_CASE("cbor_cursor reputon test")