aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrei Homescu <ah@immunant.com>2021-03-25 20:15:14 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2021-03-25 20:15:14 +0000
commit09ba61cedc0fadf59832e4d2be7858d4b4466aab (patch)
tree783985d5fa72db3f8f992149c9a263fbebb13f65
parent2fd28c9d366ff015e33a2ce746d70cc9e771eb8a (diff)
parent7da24633c1b826815c61d39b5182848a1a05b56a (diff)
downloadlibcppbor-09ba61cedc0fadf59832e4d2be7858d4b4466aab.tar.gz
Add view items for zero-copy parsing of CBOR strings am: 4d171a791a am: decff6ea66 am: 7da24633c1
Original change: https://android-review.googlesource.com/c/platform/external/libcppbor/+/1588192 Change-Id: I95db2120fe0bc1c98b27a914bbe1193d6062d775
-rw-r--r--include/cppbor/cppbor.h106
-rw-r--r--include/cppbor/cppbor_parse.h45
-rw-r--r--src/cppbor.cpp79
-rw-r--r--src/cppbor_parse.cpp43
-rw-r--r--tests/cppbor_test.cpp199
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();