diff options
author | Andrei Homescu <ah@immunant.com> | 2021-03-25 19:00:04 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-03-25 19:00:04 +0000 |
commit | decff6ea665ed46db44a041d2197d37ed66d66a3 (patch) | |
tree | 783985d5fa72db3f8f992149c9a263fbebb13f65 | |
parent | ad80fb83f579372e142e97cba8ea9bddd8362814 (diff) | |
parent | 4d171a791a4e44ae1b061f122282d0448dc612b3 (diff) | |
download | libcppbor-decff6ea665ed46db44a041d2197d37ed66d66a3.tar.gz |
Add view items for zero-copy parsing of CBOR strings am: 4d171a791a
Original change: https://android-review.googlesource.com/c/platform/external/libcppbor/+/1588192
Change-Id: I97ebf04460b75af783b1759a8e5816bc198cf321
-rw-r--r-- | include/cppbor/cppbor.h | 106 | ||||
-rw-r--r-- | include/cppbor/cppbor_parse.h | 45 | ||||
-rw-r--r-- | src/cppbor.cpp | 79 | ||||
-rw-r--r-- | src/cppbor_parse.cpp | 43 | ||||
-rw-r--r-- | tests/cppbor_test.cpp | 199 |
5 files changed, 452 insertions, 20 deletions
diff --git a/include/cppbor/cppbor.h b/include/cppbor/cppbor.h index 1409bf8..004c894 100644 --- a/include/cppbor/cppbor.h +++ b/include/cppbor/cppbor.h @@ -16,12 +16,14 @@ #pragma once +#include <cassert> #include <cstdint> #include <functional> #include <iterator> #include <memory> #include <numeric> #include <string> +#include <string_view> #include <vector> namespace cppbor { @@ -65,6 +67,8 @@ class Map; class Null; class SemanticTag; class EncodedItem; +class ViewTstr; +class ViewBstr; /** * Returns the size of a CBOR header that contains the additional info value addlInfo. @@ -129,6 +133,11 @@ class Item { virtual Array* asArray() { return nullptr; } const Array* asArray() const { return const_cast<Item*>(this)->asArray(); } + virtual ViewTstr* asViewTstr() { return nullptr; } + const ViewTstr* asViewTstr() const { return const_cast<Item*>(this)->asViewTstr(); } + virtual ViewBstr* asViewBstr() { return nullptr; } + const ViewBstr* asViewBstr() const { return const_cast<Item*>(this)->asViewBstr(); } + // Like those above, these methods safely downcast an Item when it's actually a SemanticTag. // However, if you think you want to use these methods, you probably don't. Typically, the way // you should handle tagged Items is by calling the appropriate method above (e.g. asInt()) @@ -409,6 +418,55 @@ class Bstr : public Item { }; /** + * ViewBstr is a read-only version of Bstr backed by std::string_view + */ +class ViewBstr : public Item { + public: + static constexpr MajorType kMajorType = BSTR; + + // Construct an empty ViewBstr + explicit ViewBstr() {} + + // Construct from a string_view of uint8_t values + explicit ViewBstr(std::basic_string_view<uint8_t> v) : mView(std::move(v)) {} + + // Construct from a string_view + explicit ViewBstr(std::string_view v) + : mView(reinterpret_cast<const uint8_t*>(v.data()), v.size()) {} + + // Construct from an iterator range + template <typename I1, typename I2, + typename = typename std::iterator_traits<I1>::iterator_category, + typename = typename std::iterator_traits<I2>::iterator_category> + ViewBstr(I1 begin, I2 end) : mView(begin, end) {} + + // Construct from a uint8_t pointer pair + ViewBstr(const uint8_t* begin, const uint8_t* end) + : mView(begin, std::distance(begin, end)) {} + + bool operator==(const ViewBstr& other) const& { return mView == other.mView; } + + MajorType type() const override { return kMajorType; } + ViewBstr* asViewBstr() override { return this; } + size_t encodedSize() const override { return headerSize(mView.size()) + mView.size(); } + using Item::encode; + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override; + void encode(EncodeCallback encodeCallback) const override { + encodeHeader(mView.size(), encodeCallback); + encodeValue(encodeCallback); + } + + const std::basic_string_view<uint8_t>& view() const { return mView; } + + std::unique_ptr<Item> clone() const override { return std::make_unique<ViewBstr>(mView); } + + private: + void encodeValue(EncodeCallback encodeCallback) const; + + std::basic_string_view<uint8_t> mView; +}; + +/** * Tstr is a concrete Item that implements major type 3. */ class Tstr : public Item { @@ -459,6 +517,52 @@ class Tstr : public Item { std::string mValue; }; +/** + * ViewTstr is a read-only version of Tstr backed by std::string_view + */ +class ViewTstr : public Item { + public: + static constexpr MajorType kMajorType = TSTR; + + // Construct an empty ViewTstr + explicit ViewTstr() {} + + // Construct from a string_view + explicit ViewTstr(std::string_view v) : mView(std::move(v)) {} + + // Construct from an iterator range + template <typename I1, typename I2, + typename = typename std::iterator_traits<I1>::iterator_category, + typename = typename std::iterator_traits<I2>::iterator_category> + ViewTstr(I1 begin, I2 end) : mView(begin, end) {} + + // Construct from a uint8_t pointer pair + ViewTstr(const uint8_t* begin, const uint8_t* end) + : mView(reinterpret_cast<const char*>(begin), + std::distance(begin, end)) {} + + bool operator==(const ViewTstr& other) const& { return mView == other.mView; } + + MajorType type() const override { return kMajorType; } + ViewTstr* asViewTstr() override { return this; } + size_t encodedSize() const override { return headerSize(mView.size()) + mView.size(); } + using Item::encode; + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override; + void encode(EncodeCallback encodeCallback) const override { + encodeHeader(mView.size(), encodeCallback); + encodeValue(encodeCallback); + } + + const std::string_view& view() const { return mView; } + + std::unique_ptr<Item> clone() const override { return std::make_unique<ViewTstr>(mView); } + + private: + void encodeValue(EncodeCallback encodeCallback) const; + + std::string_view mView; +}; + /* * Array is a concrete Item that implements CBOR major type 4. * @@ -686,6 +790,8 @@ class SemanticTag : public Item { Simple* asSimple() override { return mTaggedItem->asSimple(); } Map* asMap() override { return mTaggedItem->asMap(); } Array* asArray() override { return mTaggedItem->asArray(); } + ViewTstr* asViewTstr() override { return mTaggedItem->asViewTstr(); } + ViewBstr* asViewBstr() override { return mTaggedItem->asViewBstr(); } std::unique_ptr<Item> clone() const override; diff --git a/include/cppbor/cppbor_parse.h b/include/cppbor/cppbor_parse.h index f1b3647..22cd18d 100644 --- a/include/cppbor/cppbor_parse.h +++ b/include/cppbor/cppbor_parse.h @@ -37,6 +37,24 @@ using ParseResult = std::tuple<std::unique_ptr<Item> /* result */, const uint8_t ParseResult parse(const uint8_t* begin, const uint8_t* end); /** + * Parse the first CBOR data item (possibly compound) from the range [begin, end). + * + * Returns a tuple of Item pointer, buffer pointer and error message. If parsing is successful, the + * Item pointer is non-null, the buffer pointer points to the first byte after the + * successfully-parsed item and the error message string is empty. If parsing fails, the Item + * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte + * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is + * too large for the remaining buffer, etc.) and the string contains an error message describing the + * problem encountered. + * + * The returned CBOR data item will contain View* items backed by + * std::string_view types over the input range. + * WARNING! If the input range changes underneath, the corresponding views will + * carry the same change. + */ +ParseResult parseWithViews(const uint8_t* begin, const uint8_t* end); + +/** * Parse the first CBOR data item (possibly compound) from the byte vector. * * Returns a tuple of Item pointer, buffer pointer and error message. If parsing is successful, the @@ -67,6 +85,26 @@ inline ParseResult parse(const uint8_t* begin, size_t size) { } /** + * Parse the first CBOR data item (possibly compound) from the range [begin, begin + size). + * + * Returns a tuple of Item pointer, buffer pointer and error message. If parsing is successful, the + * Item pointer is non-null, the buffer pointer points to the first byte after the + * successfully-parsed item and the error message string is empty. If parsing fails, the Item + * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte + * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is + * too large for the remaining buffer, etc.) and the string contains an error message describing the + * problem encountered. + * + * The returned CBOR data item will contain View* items backed by + * std::string_view types over the input range. + * WARNING! If the input range changes underneath, the corresponding views will + * carry the same change. + */ +inline ParseResult parseWithViews(const uint8_t* begin, size_t size) { + return parseWithViews(begin, begin + size); +} + +/** * Parse the first CBOR data item (possibly compound) from the value contained in a Bstr. * * Returns a tuple of Item pointer, buffer pointer and error message. If parsing is successful, the @@ -92,6 +130,13 @@ class ParseClient; void parse(const uint8_t* begin, const uint8_t* end, ParseClient* parseClient); /** + * Parse the CBOR data in the range [begin, end) in streaming fashion, calling methods on the + * provided ParseClient when elements are found. Uses the View* item types + * instead of the copying ones. + */ +void parseWithViews(const uint8_t* begin, const uint8_t* end, ParseClient* parseClient); + +/** * Parse the CBOR data in the vector in streaming fashion, calling methods on the * provided ParseClient when elements are found. */ diff --git a/src/cppbor.cpp b/src/cppbor.cpp index f113a32..0e9c939 100644 --- a/src/cppbor.cpp +++ b/src/cppbor.cpp @@ -121,27 +121,41 @@ bool prettyPrintInternal(const Item* item, string& out, size_t indent, size_t ma break; case BSTR: { + const uint8_t* valueData; + size_t valueSize; const Bstr* bstr = item->asBstr(); - const vector<uint8_t>& value = bstr->value(); - if (value.size() > maxBStrSize) { + if (bstr != nullptr) { + const vector<uint8_t>& value = bstr->value(); + valueData = value.data(); + valueSize = value.size(); + } else { + const ViewBstr* viewBstr = item->asViewBstr(); + assert(viewBstr != nullptr); + + std::basic_string_view view = viewBstr->view(); + valueData = view.data(); + valueSize = view.size(); + } + + if (valueSize > maxBStrSize) { unsigned char digest[SHA_DIGEST_LENGTH]; SHA_CTX ctx; SHA1_Init(&ctx); - SHA1_Update(&ctx, value.data(), value.size()); + SHA1_Update(&ctx, valueData, valueSize); SHA1_Final(digest, &ctx); char buf2[SHA_DIGEST_LENGTH * 2 + 1]; for (size_t n = 0; n < SHA_DIGEST_LENGTH; n++) { snprintf(buf2 + n * 2, 3, "%02x", digest[n]); } - snprintf(buf, sizeof(buf), "<bstr size=%zd sha1=%s>", value.size(), buf2); + snprintf(buf, sizeof(buf), "<bstr size=%zd sha1=%s>", valueSize, buf2); out.append(buf); } else { out.append("{"); - for (size_t n = 0; n < value.size(); n++) { + for (size_t n = 0; n < valueSize; n++) { if (n > 0) { out.append(", "); } - snprintf(buf, sizeof(buf), "0x%02x", value[n]); + snprintf(buf, sizeof(buf), "0x%02x", valueData[n]); out.append(buf); } out.append("}"); @@ -152,7 +166,13 @@ bool prettyPrintInternal(const Item* item, string& out, size_t indent, size_t ma out.append("'"); { // TODO: escape "'" characters - out.append(item->asTstr()->value().c_str()); + if (item->asTstr() != nullptr) { + out.append(item->asTstr()->value().c_str()); + } else { + const ViewTstr* viewTstr = item->asViewTstr(); + assert(viewTstr != nullptr); + out.append(viewTstr->view()); + } } out.append("'"); break; @@ -306,9 +326,26 @@ bool Item::operator==(const Item& other) const& { case NINT: return *asNint() == *(other.asNint()); case BSTR: - return *asBstr() == *(other.asBstr()); + if (asBstr() != nullptr && other.asBstr() != nullptr) { + return *asBstr() == *(other.asBstr()); + } + if (asViewBstr() != nullptr && other.asViewBstr() != nullptr) { + return *asViewBstr() == *(other.asViewBstr()); + } + // Interesting corner case: comparing a Bstr and ViewBstr with + // identical contents. The function currently returns false for + // this case. + // TODO: if it should return true, this needs a deep comparison + return false; case TSTR: - return *asTstr() == *(other.asTstr()); + if (asTstr() != nullptr && other.asTstr() != nullptr) { + return *asTstr() == *(other.asTstr()); + } + if (asViewTstr() != nullptr && other.asViewTstr() != nullptr) { + return *asViewTstr() == *(other.asViewTstr()); + } + // Same corner case as Bstr + return false; case ARRAY: return *asArray() == *(other.asArray()); case MAP: @@ -353,6 +390,18 @@ void Bstr::encodeValue(EncodeCallback encodeCallback) const { } } +uint8_t* ViewBstr::encode(uint8_t* pos, const uint8_t* end) const { + pos = encodeHeader(mView.size(), pos, end); + if (!pos || end - pos < static_cast<ptrdiff_t>(mView.size())) return nullptr; + return std::copy(mView.begin(), mView.end(), pos); +} + +void ViewBstr::encodeValue(EncodeCallback encodeCallback) const { + for (auto c : mView) { + encodeCallback(static_cast<uint8_t>(c)); + } +} + uint8_t* Tstr::encode(uint8_t* pos, const uint8_t* end) const { pos = encodeHeader(mValue.size(), pos, end); if (!pos || end - pos < static_cast<ptrdiff_t>(mValue.size())) return nullptr; @@ -365,6 +414,18 @@ void Tstr::encodeValue(EncodeCallback encodeCallback) const { } } +uint8_t* ViewTstr::encode(uint8_t* pos, const uint8_t* end) const { + pos = encodeHeader(mView.size(), pos, end); + if (!pos || end - pos < static_cast<ptrdiff_t>(mView.size())) return nullptr; + return std::copy(mView.begin(), mView.end(), pos); +} + +void ViewTstr::encodeValue(EncodeCallback encodeCallback) const { + for (auto c : mView) { + encodeCallback(static_cast<uint8_t>(c)); + } +} + bool Array::operator==(const Array& other) const& { return size() == other.size() // Can't use vector::operator== because the contents are pointers. std::equal lets us diff --git a/src/cppbor_parse.cpp b/src/cppbor_parse.cpp index 42d74fb..5cf76b2 100644 --- a/src/cppbor_parse.cpp +++ b/src/cppbor_parse.cpp @@ -54,7 +54,7 @@ std::tuple<bool, uint64_t, const uint8_t*> parseLength(const uint8_t* pos, const } std::tuple<const uint8_t*, ParseClient*> parseRecursively(const uint8_t* begin, const uint8_t* end, - ParseClient* parseClient); + bool emitViews, ParseClient* parseClient); std::tuple<const uint8_t*, ParseClient*> handleUint(uint64_t value, const uint8_t* hdrBegin, const uint8_t* hdrEnd, @@ -162,6 +162,7 @@ class IncompleteSemanticTag : public SemanticTag, public IncompleteItem { std::tuple<const uint8_t*, ParseClient*> handleEntries(size_t entryCount, const uint8_t* hdrBegin, const uint8_t* pos, const uint8_t* end, const std::string& typeName, + bool emitViews, ParseClient* parseClient) { while (entryCount > 0) { --entryCount; @@ -169,7 +170,7 @@ std::tuple<const uint8_t*, ParseClient*> handleEntries(size_t entryCount, const parseClient->error(hdrBegin, "Not enough entries for " + typeName + "."); return {hdrBegin, nullptr /* end parsing */}; } - std::tie(pos, parseClient) = parseRecursively(pos, end, parseClient); + std::tie(pos, parseClient) = parseRecursively(pos, end, emitViews, parseClient); if (!parseClient) return {hdrBegin, nullptr}; } return {pos, parseClient}; @@ -178,21 +179,21 @@ std::tuple<const uint8_t*, ParseClient*> handleEntries(size_t entryCount, const std::tuple<const uint8_t*, ParseClient*> handleCompound( std::unique_ptr<Item> item, uint64_t entryCount, const uint8_t* hdrBegin, const uint8_t* valueBegin, const uint8_t* end, const std::string& typeName, - ParseClient* parseClient) { + bool emitViews, ParseClient* parseClient) { parseClient = parseClient->item(item, hdrBegin, valueBegin, valueBegin /* don't know the end yet */); if (!parseClient) return {hdrBegin, nullptr}; const uint8_t* pos; std::tie(pos, parseClient) = - handleEntries(entryCount, hdrBegin, valueBegin, end, typeName, parseClient); + handleEntries(entryCount, hdrBegin, valueBegin, end, typeName, emitViews, parseClient); if (!parseClient) return {hdrBegin, nullptr}; return {pos, parseClient->itemEnd(item, hdrBegin, valueBegin, pos)}; } std::tuple<const uint8_t*, ParseClient*> parseRecursively(const uint8_t* begin, const uint8_t* end, - ParseClient* parseClient) { + bool emitViews, ParseClient* parseClient) { const uint8_t* pos = begin; MajorType type = static_cast<MajorType>(*pos & 0xE0); @@ -237,22 +238,30 @@ std::tuple<const uint8_t*, ParseClient*> parseRecursively(const uint8_t* begin, return handleNint(addlData, begin, pos, parseClient); case BSTR: - return handleString<Bstr>(addlData, begin, pos, end, "byte string", parseClient); + if (emitViews) { + return handleString<ViewBstr>(addlData, begin, pos, end, "byte string", parseClient); + } else { + return handleString<Bstr>(addlData, begin, pos, end, "byte string", parseClient); + } case TSTR: - return handleString<Tstr>(addlData, begin, pos, end, "text string", parseClient); + if (emitViews) { + return handleString<ViewTstr>(addlData, begin, pos, end, "text string", parseClient); + } else { + return handleString<Tstr>(addlData, begin, pos, end, "text string", parseClient); + } case ARRAY: return handleCompound(std::make_unique<IncompleteArray>(addlData), addlData, begin, pos, - end, "array", parseClient); + end, "array", emitViews, parseClient); case MAP: return handleCompound(std::make_unique<IncompleteMap>(addlData), addlData * 2, begin, - pos, end, "map", parseClient); + pos, end, "map", emitViews, parseClient); case SEMANTIC: return handleCompound(std::make_unique<IncompleteSemanticTag>(addlData), 1, begin, pos, - end, "semantic", parseClient); + end, "semantic", emitViews, parseClient); case SIMPLE: switch (addlData) { @@ -346,7 +355,7 @@ class FullParseClient : public ParseClient { } // anonymous namespace void parse(const uint8_t* begin, const uint8_t* end, ParseClient* parseClient) { - parseRecursively(begin, end, parseClient); + parseRecursively(begin, end, false, parseClient); } std::tuple<std::unique_ptr<Item> /* result */, const uint8_t* /* newPos */, @@ -357,4 +366,16 @@ parse(const uint8_t* begin, const uint8_t* end) { return parseClient.parseResult(); } +void parseWithViews(const uint8_t* begin, const uint8_t* end, ParseClient* parseClient) { + parseRecursively(begin, end, true, parseClient); +} + +std::tuple<std::unique_ptr<Item> /* result */, const uint8_t* /* newPos */, + std::string /* errMsg */> +parseWithViews(const uint8_t* begin, const uint8_t* end) { + FullParseClient parseClient; + parseWithViews(begin, end, &parseClient); + return parseClient.parseResult(); +} + } // namespace cppbor diff --git a/tests/cppbor_test.cpp b/tests/cppbor_test.cpp index 4d9a1f0..8a81e4e 100644 --- a/tests/cppbor_test.cpp +++ b/tests/cppbor_test.cpp @@ -153,6 +153,31 @@ TEST(SimpleValueTest, NestedSemanticTagEncoding) { tripleTagged.toString()); } +TEST(SimpleValueTest, ViewByteStringEncodings) { + EXPECT_EQ("\x40", ViewBstr("").toString()); + EXPECT_EQ("\x41\x61", ViewBstr("a").toString()); + EXPECT_EQ("\x41\x41", ViewBstr("A").toString()); + EXPECT_EQ("\x44\x49\x45\x54\x46", ViewBstr("IETF").toString()); + EXPECT_EQ("\x42\x22\x5c", ViewBstr("\"\\").toString()); + EXPECT_EQ("\x42\xc3\xbc", ViewBstr("\xc3\xbc").toString()); + EXPECT_EQ("\x43\xe6\xb0\xb4", ViewBstr("\xe6\xb0\xb4").toString()); + EXPECT_EQ("\x44\xf0\x90\x85\x91", ViewBstr("\xf0\x90\x85\x91").toString()); + EXPECT_EQ("\x44\x01\x02\x03\x04", ViewBstr("\x01\x02\x03\x04").toString()); + EXPECT_EQ("\x44\x40\x40\x40\x40", ViewBstr("@@@@").toString()); +} + +TEST(SimpleValueTest, ViewTextStringEncodings) { + EXPECT_EQ("\x60"s, ViewTstr("").toString()); + EXPECT_EQ("\x61\x61"s, ViewTstr("a").toString()); + EXPECT_EQ("\x61\x41"s, ViewTstr("A").toString()); + EXPECT_EQ("\x64\x49\x45\x54\x46"s, ViewTstr("IETF").toString()); + EXPECT_EQ("\x62\x22\x5c"s, ViewTstr("\"\\").toString()); + EXPECT_EQ("\x62\xc3\xbc"s, ViewTstr("\xc3\xbc").toString()); + EXPECT_EQ("\x63\xe6\xb0\xb4"s, ViewTstr("\xe6\xb0\xb4").toString()); + EXPECT_EQ("\x64\xf0\x90\x85\x91"s, ViewTstr("\xf0\x90\x85\x91").toString()); + EXPECT_EQ("\x64\x01\x02\x03\x04"s, ViewTstr("\x01\x02\x03\x04").toString()); +} + TEST(IsIteratorPairOverTest, All) { EXPECT_TRUE(( details::is_iterator_pair_over<pair<string::iterator, string::iterator>, char>::value)); @@ -505,6 +530,8 @@ TEST(EqualityTest, Uint) { EXPECT_NE(val, Bool(false)); EXPECT_NE(val, Array(99, 1)); EXPECT_NE(val, Map(99, 1)); + EXPECT_NE(val, ViewTstr("99")); + EXPECT_NE(val, ViewBstr("99")); } TEST(EqualityTest, Nint) { @@ -518,6 +545,8 @@ TEST(EqualityTest, Nint) { EXPECT_NE(val, Bool(false)); EXPECT_NE(val, Array(99)); EXPECT_NE(val, Map(99, 1)); + EXPECT_NE(val, ViewTstr("99")); + EXPECT_NE(val, ViewBstr("99")); } TEST(EqualityTest, Tstr) { @@ -532,6 +561,8 @@ TEST(EqualityTest, Tstr) { EXPECT_NE(val, Bool(false)); EXPECT_NE(val, Array(99, 1)); EXPECT_NE(val, Map(99, 1)); + EXPECT_NE(val, ViewTstr("99")); + EXPECT_NE(val, ViewBstr("99")); } TEST(EqualityTest, Bstr) { @@ -546,6 +577,8 @@ TEST(EqualityTest, Bstr) { EXPECT_NE(val, Bool(false)); EXPECT_NE(val, Array(99, 1)); EXPECT_NE(val, Map(99, 1)); + EXPECT_NE(val, ViewTstr("99")); + EXPECT_NE(val, ViewBstr("99")); } TEST(EqualityTest, Bool) { @@ -560,6 +593,8 @@ TEST(EqualityTest, Bool) { EXPECT_NE(val, Bool(true)); EXPECT_NE(val, Array(99, 1)); EXPECT_NE(val, Map(99, 1)); + EXPECT_NE(val, ViewTstr("99")); + EXPECT_NE(val, ViewBstr("98")); } TEST(EqualityTest, Array) { @@ -576,6 +611,8 @@ TEST(EqualityTest, Array) { EXPECT_NE(val, Array(98, 1)); EXPECT_NE(val, Array(99, 1, 2)); EXPECT_NE(val, Map(99, 1)); + EXPECT_NE(val, ViewTstr("99")); + EXPECT_NE(val, ViewBstr("98")); } TEST(EqualityTest, Map) { @@ -591,6 +628,8 @@ TEST(EqualityTest, Map) { EXPECT_NE(val, Array(99, 1)); EXPECT_NE(val, Map(99, 2)); EXPECT_NE(val, Map(99, 1, 99, 2)); + EXPECT_NE(val, ViewTstr("99")); + EXPECT_NE(val, ViewBstr("98")); } TEST(EqualityTest, Null) { @@ -606,6 +645,8 @@ TEST(EqualityTest, Null) { EXPECT_NE(val, Array(99, 1)); EXPECT_NE(val, Map(99, 2)); EXPECT_NE(val, Map(99, 1, 99, 2)); + EXPECT_NE(val, ViewTstr("99")); + EXPECT_NE(val, ViewBstr("98")); } TEST(EqualityTest, SemanticTag) { @@ -636,6 +677,40 @@ TEST(EqualityTest, NestedSemanticTag) { EXPECT_NE(val, Array(99, 1)); EXPECT_NE(val, Map(99, 2)); EXPECT_NE(val, Null()); + EXPECT_NE(val, ViewTstr("99")); + EXPECT_NE(val, ViewBstr("98")); +} + +TEST(EqualityTest, ViewTstr) { + ViewTstr val("99"); + EXPECT_EQ(val, ViewTstr("99")); + + EXPECT_NE(val, Uint(99)); + EXPECT_NE(val, Nint(-1)); + EXPECT_NE(val, Nint(-4)); + EXPECT_NE(val, Tstr("99")); + EXPECT_NE(val, Bstr("99")); + EXPECT_NE(val, Bool(false)); + EXPECT_NE(val, Array(99, 1)); + EXPECT_NE(val, Map(99, 1)); + EXPECT_NE(val, ViewTstr("98")); + EXPECT_NE(val, ViewBstr("99")); +} + +TEST(EqualityTest, ViewBstr) { + ViewBstr val("99"); + EXPECT_EQ(val, ViewBstr("99")); + + EXPECT_NE(val, Uint(99)); + EXPECT_NE(val, Nint(-1)); + EXPECT_NE(val, Nint(-4)); + EXPECT_NE(val, Tstr("99")); + EXPECT_NE(val, Bstr("99")); + EXPECT_NE(val, Bool(false)); + EXPECT_NE(val, Array(99, 1)); + EXPECT_NE(val, Map(99, 1)); + EXPECT_NE(val, ViewTstr("99")); + EXPECT_NE(val, ViewBstr("98")); } TEST(ConvertTest, Uint) { @@ -650,6 +725,8 @@ TEST(ConvertTest, Uint) { EXPECT_EQ(nullptr, item->asSimple()); EXPECT_EQ(nullptr, item->asMap()); EXPECT_EQ(nullptr, item->asArray()); + EXPECT_EQ(nullptr, item->asViewTstr()); + EXPECT_EQ(nullptr, item->asViewBstr()); EXPECT_EQ(10, item->asInt()->value()); EXPECT_EQ(10, item->asUint()->value()); @@ -667,6 +744,8 @@ TEST(ConvertTest, Nint) { EXPECT_EQ(nullptr, item->asSimple()); EXPECT_EQ(nullptr, item->asMap()); EXPECT_EQ(nullptr, item->asArray()); + EXPECT_EQ(nullptr, item->asViewTstr()); + EXPECT_EQ(nullptr, item->asViewBstr()); EXPECT_EQ(-10, item->asInt()->value()); EXPECT_EQ(-10, item->asNint()->value()); @@ -684,6 +763,8 @@ TEST(ConvertTest, Tstr) { EXPECT_EQ(nullptr, item->asSimple()); EXPECT_EQ(nullptr, item->asMap()); EXPECT_EQ(nullptr, item->asArray()); + EXPECT_EQ(nullptr, item->asViewTstr()); + EXPECT_EQ(nullptr, item->asViewBstr()); EXPECT_EQ("hello"s, item->asTstr()->value()); } @@ -701,6 +782,8 @@ TEST(ConvertTest, Bstr) { EXPECT_EQ(nullptr, item->asSimple()); EXPECT_EQ(nullptr, item->asMap()); EXPECT_EQ(nullptr, item->asArray()); + EXPECT_EQ(nullptr, item->asViewTstr()); + EXPECT_EQ(nullptr, item->asViewBstr()); EXPECT_EQ(vec, item->asBstr()->value()); } @@ -717,6 +800,8 @@ TEST(ConvertTest, Bool) { EXPECT_NE(nullptr, item->asSimple()); EXPECT_EQ(nullptr, item->asMap()); EXPECT_EQ(nullptr, item->asArray()); + EXPECT_EQ(nullptr, item->asViewTstr()); + EXPECT_EQ(nullptr, item->asViewBstr()); EXPECT_EQ(BOOLEAN, item->asSimple()->simpleType()); EXPECT_NE(nullptr, item->asSimple()->asBool()); @@ -737,6 +822,8 @@ TEST(ConvertTest, Map) { EXPECT_EQ(nullptr, item->asSimple()); EXPECT_NE(nullptr, item->asMap()); EXPECT_EQ(nullptr, item->asArray()); + EXPECT_EQ(nullptr, item->asViewTstr()); + EXPECT_EQ(nullptr, item->asViewBstr()); EXPECT_EQ(0U, item->asMap()->size()); } @@ -753,6 +840,8 @@ TEST(ConvertTest, Array) { EXPECT_EQ(nullptr, item->asSimple()); EXPECT_EQ(nullptr, item->asMap()); EXPECT_NE(nullptr, item->asArray()); + EXPECT_EQ(nullptr, item->asViewTstr()); + EXPECT_EQ(nullptr, item->asViewBstr()); EXPECT_EQ(0U, item->asArray()->size()); } @@ -768,6 +857,8 @@ TEST(ConvertTest, SemanticTag) { EXPECT_EQ(nullptr, item->asSimple()); EXPECT_EQ(nullptr, item->asMap()); EXPECT_EQ(nullptr, item->asArray()); + EXPECT_EQ(nullptr, item->asViewTstr()); + EXPECT_EQ(nullptr, item->asViewBstr()); // Both asTstr() (the contained type) and asSemanticTag() return non-null. EXPECT_NE(nullptr, item->asTstr()); @@ -794,6 +885,8 @@ TEST(ConvertTest, NestedSemanticTag) { EXPECT_EQ(nullptr, item->asSimple()); EXPECT_EQ(nullptr, item->asMap()); EXPECT_EQ(nullptr, item->asArray()); + EXPECT_EQ(nullptr, item->asViewTstr()); + EXPECT_EQ(nullptr, item->asViewBstr()); // Both asTstr() (the contained type) and asSemanticTag() return non-null. EXPECT_NE(nullptr, item->asTstr()); @@ -823,12 +916,52 @@ TEST(ConvertTest, Null) { EXPECT_NE(nullptr, item->asSimple()); EXPECT_EQ(nullptr, item->asMap()); EXPECT_EQ(nullptr, item->asArray()); + EXPECT_EQ(nullptr, item->asViewTstr()); + EXPECT_EQ(nullptr, item->asViewBstr()); EXPECT_EQ(NULL_T, item->asSimple()->simpleType()); EXPECT_EQ(nullptr, item->asSimple()->asBool()); EXPECT_NE(nullptr, item->asSimple()->asNull()); } +TEST(ConvertTest, ViewTstr) { + unique_ptr<Item> item = details::makeItem(ViewTstr("hello")); + + EXPECT_EQ(TSTR, item->type()); + EXPECT_EQ(nullptr, item->asInt()); + EXPECT_EQ(nullptr, item->asUint()); + EXPECT_EQ(nullptr, item->asNint()); + EXPECT_EQ(nullptr, item->asTstr()); + EXPECT_EQ(nullptr, item->asBstr()); + EXPECT_EQ(nullptr, item->asSimple()); + EXPECT_EQ(nullptr, item->asMap()); + EXPECT_EQ(nullptr, item->asArray()); + EXPECT_NE(nullptr, item->asViewTstr()); + EXPECT_EQ(nullptr, item->asViewBstr()); + + EXPECT_EQ("hello"sv, item->asViewTstr()->view()); +} + +TEST(ConvertTest, ViewBstr) { + array<uint8_t, 3> vec{0x23, 0x24, 0x22}; + basic_string_view sv(vec.data(), vec.size()); + unique_ptr<Item> item = details::makeItem(ViewBstr(sv)); + + EXPECT_EQ(BSTR, item->type()); + EXPECT_EQ(nullptr, item->asInt()); + EXPECT_EQ(nullptr, item->asUint()); + EXPECT_EQ(nullptr, item->asNint()); + EXPECT_EQ(nullptr, item->asTstr()); + EXPECT_EQ(nullptr, item->asBstr()); + EXPECT_EQ(nullptr, item->asSimple()); + EXPECT_EQ(nullptr, item->asMap()); + EXPECT_EQ(nullptr, item->asArray()); + EXPECT_EQ(nullptr, item->asViewTstr()); + EXPECT_NE(nullptr, item->asViewBstr()); + + EXPECT_EQ(sv, item->asViewBstr()->view()); +} + TEST(CloningTest, Uint) { Uint item(10); auto clone = item.clone(); @@ -937,6 +1070,26 @@ TEST(CloningTest, NestedSemanticTag) { EXPECT_EQ(*clone->asSemanticTag(), copy); } +TEST(CloningTest, ViewTstr) { + ViewTstr item("qwertyasdfgh"); + auto clone = item.clone(); + EXPECT_EQ(clone->type(), TSTR); + EXPECT_NE(clone->asViewTstr(), nullptr); + EXPECT_EQ(item, *clone->asViewTstr()); + EXPECT_EQ(*clone->asViewTstr(), ViewTstr("qwertyasdfgh")); +} + +TEST(CloningTest, ViewBstr) { + array<uint8_t, 5> vec{1, 2, 3, 255, 0}; + basic_string_view sv(vec.data(), vec.size()); + ViewBstr item(sv); + auto clone = item.clone(); + EXPECT_EQ(clone->type(), BSTR); + EXPECT_NE(clone->asViewBstr(), nullptr); + EXPECT_EQ(item, *clone->asViewBstr()); + EXPECT_EQ(*clone->asViewBstr(), ViewBstr(sv)); +} + TEST(PrettyPrintingTest, NestedSemanticTag) { SemanticTag item(20, // SemanticTag(30, // @@ -1363,6 +1516,36 @@ TEST(StreamParseTest, Map) { parse(encoded.data(), encoded.data() + encoded.size(), &mpc); } +TEST(StreamParseTest, ViewTstr) { + MockParseClient mpc; + + ViewTstr val("Hello"); + auto encoded = val.encode(); + uint8_t* encBegin = encoded.data(); + uint8_t* encEnd = encoded.data() + encoded.size(); + + EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encBegin + 1, encEnd)).WillOnce(Return(&mpc)); + EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0); + EXPECT_CALL(mpc, error(_, _)).Times(0); + + parseWithViews(encoded.data(), encoded.data() + encoded.size(), &mpc); +} + +TEST(StreamParseTest, ViewBstr) { + MockParseClient mpc; + + ViewBstr val("Hello"); + auto encoded = val.encode(); + uint8_t* encBegin = encoded.data(); + uint8_t* encEnd = encoded.data() + encoded.size(); + + EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encBegin + 1, encEnd)).WillOnce(Return(&mpc)); + EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0); + EXPECT_CALL(mpc, error(_, _)).Times(0); + + parseWithViews(encoded.data(), encoded.data() + encoded.size(), &mpc); +} + TEST(FullParserTest, Uint) { Uint val(10); @@ -1515,6 +1698,22 @@ TEST(FullParserTest, MapWithTruncatedEntry) { EXPECT_EQ("Need 4 byte(s) for length field, have 3.", message); } +TEST(FullParserTest, ViewTstr) { + ViewTstr val("Hello"); + + auto enc = val.encode(); + auto [item, pos, message] = parseWithViews(enc.data(), enc.size()); + EXPECT_THAT(item, MatchesItem(val)); +} + +TEST(FullParserTest, ViewBstr) { + ViewBstr val("\x00\x01\x02"s); + + auto enc = val.encode(); + auto [item, pos, message] = parseWithViews(enc.data(), enc.size()); + EXPECT_THAT(item, MatchesItem(val)); +} + TEST(MapGetValueByKeyTest, Map) { Array compoundItem(1, 2, 3, 4, 5, Map(4, 5, "a", "b")); auto clone = compoundItem.clone(); |