summaryrefslogtreecommitdiff
path: root/tools/inspector_protocol/encoding/encoding_test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'tools/inspector_protocol/encoding/encoding_test.cc')
-rw-r--r--tools/inspector_protocol/encoding/encoding_test.cc1838
1 files changed, 1838 insertions, 0 deletions
diff --git a/tools/inspector_protocol/encoding/encoding_test.cc b/tools/inspector_protocol/encoding/encoding_test.cc
new file mode 100644
index 0000000000..b8d75e09ba
--- /dev/null
+++ b/tools/inspector_protocol/encoding/encoding_test.cc
@@ -0,0 +1,1838 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "encoding.h"
+
+#include <array>
+#include <clocale>
+#include <cmath>
+#include <cstdlib>
+#include <cstring>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+#include <string>
+
+#include "encoding_test_helper.h"
+
+using testing::ElementsAreArray;
+
+namespace v8_inspector_protocol_encoding {
+
+class TestPlatform : public json::Platform {
+ bool StrToD(const char* str, double* result) const override {
+ // This is not thread-safe
+ // (see https://en.cppreference.com/w/cpp/locale/setlocale)
+ // but good enough for a unittest.
+ const char* saved_locale = std::setlocale(LC_NUMERIC, nullptr);
+ char* end;
+ *result = std::strtod(str, &end);
+ std::setlocale(LC_NUMERIC, saved_locale);
+ if (errno == ERANGE) {
+ // errno must be reset, e.g. see the example here:
+ // https://en.cppreference.com/w/cpp/string/byte/strtof
+ errno = 0;
+ return false;
+ }
+ return end == str + strlen(str);
+ }
+
+ std::unique_ptr<char[]> DToStr(double value) const override {
+ std::stringstream ss;
+ ss.imbue(std::locale("C"));
+ ss << value;
+ std::string str = ss.str();
+ std::unique_ptr<char[]> result(new char[str.size() + 1]);
+ memcpy(result.get(), str.c_str(), str.size() + 1);
+ return result;
+ }
+};
+
+const json::Platform& GetTestPlatform() {
+ static TestPlatform* platform = new TestPlatform;
+ return *platform;
+}
+
+// =============================================================================
+// span - sequence of bytes
+// =============================================================================
+
+template <typename T>
+class SpanTest : public ::testing::Test {};
+
+using TestTypes = ::testing::Types<uint8_t, uint16_t>;
+TYPED_TEST_SUITE(SpanTest, TestTypes);
+
+TYPED_TEST(SpanTest, Empty) {
+ span<TypeParam> empty;
+ EXPECT_TRUE(empty.empty());
+ EXPECT_EQ(0u, empty.size());
+ EXPECT_EQ(0u, empty.size_bytes());
+ EXPECT_EQ(empty.begin(), empty.end());
+}
+
+TYPED_TEST(SpanTest, SingleItem) {
+ TypeParam single_item = 42;
+ span<TypeParam> singular(&single_item, 1);
+ EXPECT_FALSE(singular.empty());
+ EXPECT_EQ(1u, singular.size());
+ EXPECT_EQ(sizeof(TypeParam), singular.size_bytes());
+ EXPECT_EQ(singular.begin() + 1, singular.end());
+ EXPECT_EQ(42, singular[0]);
+}
+
+TYPED_TEST(SpanTest, FiveItems) {
+ std::vector<TypeParam> test_input = {31, 32, 33, 34, 35};
+ span<TypeParam> five_items(test_input.data(), 5);
+ EXPECT_FALSE(five_items.empty());
+ EXPECT_EQ(5u, five_items.size());
+ EXPECT_EQ(sizeof(TypeParam) * 5, five_items.size_bytes());
+ EXPECT_EQ(five_items.begin() + 5, five_items.end());
+ EXPECT_EQ(31, five_items[0]);
+ EXPECT_EQ(32, five_items[1]);
+ EXPECT_EQ(33, five_items[2]);
+ EXPECT_EQ(34, five_items[3]);
+ EXPECT_EQ(35, five_items[4]);
+ span<TypeParam> three_items = five_items.subspan(2);
+ EXPECT_EQ(3u, three_items.size());
+ EXPECT_EQ(33, three_items[0]);
+ EXPECT_EQ(34, three_items[1]);
+ EXPECT_EQ(35, three_items[2]);
+ span<TypeParam> two_items = five_items.subspan(2, 2);
+ EXPECT_EQ(2u, two_items.size());
+ EXPECT_EQ(33, two_items[0]);
+ EXPECT_EQ(34, two_items[1]);
+}
+
+TEST(SpanFromTest, FromConstCharAndLiteral) {
+ // Testing this is useful because strlen(nullptr) is undefined.
+ EXPECT_EQ(nullptr, SpanFrom(nullptr).data());
+ EXPECT_EQ(0u, SpanFrom(nullptr).size());
+
+ const char* kEmpty = "";
+ EXPECT_EQ(kEmpty, reinterpret_cast<const char*>(SpanFrom(kEmpty).data()));
+ EXPECT_EQ(0u, SpanFrom(kEmpty).size());
+
+ const char* kFoo = "foo";
+ EXPECT_EQ(kFoo, reinterpret_cast<const char*>(SpanFrom(kFoo).data()));
+ EXPECT_EQ(3u, SpanFrom(kFoo).size());
+
+ EXPECT_EQ(3u, SpanFrom("foo").size());
+}
+
+// =============================================================================
+// Status and Error codes
+// =============================================================================
+
+TEST(StatusTest, StatusToASCIIString) {
+ Status ok_status;
+ EXPECT_EQ("OK", ok_status.ToASCIIString());
+ Status json_error(Error::JSON_PARSER_COLON_EXPECTED, 42);
+ EXPECT_EQ("JSON: colon expected at position 42", json_error.ToASCIIString());
+ Status cbor_error(Error::CBOR_TRAILING_JUNK, 21);
+ EXPECT_EQ("CBOR: trailing junk at position 21", cbor_error.ToASCIIString());
+}
+
+namespace cbor {
+
+// =============================================================================
+// Detecting CBOR content
+// =============================================================================
+
+TEST(IsCBORMessage, SomeSmokeTests) {
+ std::vector<uint8_t> empty;
+ EXPECT_FALSE(IsCBORMessage(SpanFrom(empty)));
+ std::vector<uint8_t> hello = {'H', 'e', 'l', 'o', ' ', 't',
+ 'h', 'e', 'r', 'e', '!'};
+ EXPECT_FALSE(IsCBORMessage(SpanFrom(hello)));
+ std::vector<uint8_t> example = {0xd8, 0x5a, 0, 0, 0, 0};
+ EXPECT_TRUE(IsCBORMessage(SpanFrom(example)));
+ std::vector<uint8_t> one = {0xd8, 0x5a, 0, 0, 0, 1, 1};
+ EXPECT_TRUE(IsCBORMessage(SpanFrom(one)));
+}
+
+// =============================================================================
+// Encoding individual CBOR items
+// cbor::CBORTokenizer - for parsing individual CBOR items
+// =============================================================================
+
+//
+// EncodeInt32 / CBORTokenTag::INT32
+//
+TEST(EncodeDecodeInt32Test, Roundtrips23) {
+ // This roundtrips the int32_t value 23 through the pair of EncodeInt32 /
+ // CBORTokenizer; this is interesting since 23 is encoded as a single byte.
+ std::vector<uint8_t> encoded;
+ EncodeInt32(23, &encoded);
+ // first three bits: major type = 0; remaining five bits: additional info =
+ // value 23.
+ EXPECT_THAT(encoded, ElementsAreArray(std::array<uint8_t, 1>{{23}}));
+
+ // Reverse direction: decode with CBORTokenizer.
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::INT32, tokenizer.TokenTag());
+ EXPECT_EQ(23, tokenizer.GetInt32());
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+}
+
+TEST(EncodeDecodeInt32Test, RoundtripsUint8) {
+ // This roundtrips the int32_t value 42 through the pair of EncodeInt32 /
+ // CBORTokenizer. This is different from Roundtrip23 because 42 is encoded
+ // in an extra byte after the initial one.
+ std::vector<uint8_t> encoded;
+ EncodeInt32(42, &encoded);
+ // first three bits: major type = 0;
+ // remaining five bits: additional info = 24, indicating payload is uint8.
+ EXPECT_THAT(encoded, ElementsAreArray(std::array<uint8_t, 2>{{24, 42}}));
+
+ // Reverse direction: decode with CBORTokenizer.
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::INT32, tokenizer.TokenTag());
+ EXPECT_EQ(42, tokenizer.GetInt32());
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+}
+
+TEST(EncodeDecodeInt32Test, RoundtripsUint16) {
+ // 500 is encoded as a uint16 after the initial byte.
+ std::vector<uint8_t> encoded;
+ EncodeInt32(500, &encoded);
+ // 1 for initial byte, 2 for uint16.
+ EXPECT_EQ(3u, encoded.size());
+ // first three bits: major type = 0;
+ // remaining five bits: additional info = 25, indicating payload is uint16.
+ EXPECT_EQ(25, encoded[0]);
+ EXPECT_EQ(0x01, encoded[1]);
+ EXPECT_EQ(0xf4, encoded[2]);
+
+ // Reverse direction: decode with CBORTokenizer.
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::INT32, tokenizer.TokenTag());
+ EXPECT_EQ(500, tokenizer.GetInt32());
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+}
+
+TEST(EncodeDecodeInt32Test, RoundtripsInt32Max) {
+ // std::numeric_limits<int32_t> is encoded as a uint32 after the initial byte.
+ std::vector<uint8_t> encoded;
+ EncodeInt32(std::numeric_limits<int32_t>::max(), &encoded);
+ // 1 for initial byte, 4 for the uint32.
+ // first three bits: major type = 0;
+ // remaining five bits: additional info = 26, indicating payload is uint32.
+ EXPECT_THAT(
+ encoded,
+ ElementsAreArray(std::array<uint8_t, 5>{{26, 0x7f, 0xff, 0xff, 0xff}}));
+
+ // Reverse direction: decode with CBORTokenizer.
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::INT32, tokenizer.TokenTag());
+ EXPECT_EQ(std::numeric_limits<int32_t>::max(), tokenizer.GetInt32());
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+}
+
+TEST(EncodeDecodeInt32Test, CantRoundtripUint32) {
+ // 0xdeadbeef is a value which does not fit below
+ // std::numerical_limits<int32_t>::max(), so we can't encode
+ // it with EncodeInt32. However, CBOR does support this, so we
+ // encode it here manually with the internal routine, just to observe
+ // that it's considered an invalid int32 by CBORTokenizer.
+ std::vector<uint8_t> encoded;
+ internals::WriteTokenStart(MajorType::UNSIGNED, 0xdeadbeef, &encoded);
+ // 1 for initial byte, 4 for the uint32.
+ // first three bits: major type = 0;
+ // remaining five bits: additional info = 26, indicating payload is uint32.
+ EXPECT_THAT(
+ encoded,
+ ElementsAreArray(std::array<uint8_t, 5>{{26, 0xde, 0xad, 0xbe, 0xef}}));
+
+ // Now try to decode; we treat this as an invalid INT32.
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ // 0xdeadbeef is > std::numerical_limits<int32_t>::max().
+ EXPECT_EQ(CBORTokenTag::ERROR_VALUE, tokenizer.TokenTag());
+ EXPECT_EQ(Error::CBOR_INVALID_INT32, tokenizer.Status().error);
+}
+
+TEST(EncodeDecodeInt32Test, DecodeErrorCases) {
+ struct TestCase {
+ std::vector<uint8_t> data;
+ std::string msg;
+ };
+ std::vector<TestCase> tests{
+ {TestCase{
+ {24},
+ "additional info = 24 would require 1 byte of payload (but it's 0)"},
+ TestCase{{27, 0xaa, 0xbb, 0xcc},
+ "additional info = 27 would require 8 bytes of payload (but "
+ "it's 3)"},
+ TestCase{{29}, "additional info = 29 isn't recognized"}}};
+
+ for (const TestCase& test : tests) {
+ SCOPED_TRACE(test.msg);
+ CBORTokenizer tokenizer(SpanFrom(test.data));
+ EXPECT_EQ(CBORTokenTag::ERROR_VALUE, tokenizer.TokenTag());
+ EXPECT_EQ(Error::CBOR_INVALID_INT32, tokenizer.Status().error);
+ }
+}
+
+TEST(EncodeDecodeInt32Test, RoundtripsMinus24) {
+ // This roundtrips the int32_t value -24 through the pair of EncodeInt32 /
+ // CBORTokenizer; this is interesting since -24 is encoded as
+ // a single byte as NEGATIVE, and it tests the specific encoding
+ // (note how for unsigned the single byte covers values up to 23).
+ // Additional examples are covered in RoundtripsAdditionalExamples.
+ std::vector<uint8_t> encoded;
+ EncodeInt32(-24, &encoded);
+ // first three bits: major type = 1; remaining five bits: additional info =
+ // value 23.
+ EXPECT_THAT(encoded, ElementsAreArray(std::array<uint8_t, 1>{{1 << 5 | 23}}));
+
+ // Reverse direction: decode with CBORTokenizer.
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::INT32, tokenizer.TokenTag());
+ EXPECT_EQ(-24, tokenizer.GetInt32());
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+}
+
+TEST(EncodeDecodeInt32Test, RoundtripsAdditionalNegativeExamples) {
+ std::vector<int32_t> examples = {-1,
+ -10,
+ -24,
+ -25,
+ -300,
+ -30000,
+ -300 * 1000,
+ -1000 * 1000,
+ -1000 * 1000 * 1000,
+ std::numeric_limits<int32_t>::min()};
+ for (int32_t example : examples) {
+ SCOPED_TRACE(std::string("example ") + std::to_string(example));
+ std::vector<uint8_t> encoded;
+ EncodeInt32(example, &encoded);
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::INT32, tokenizer.TokenTag());
+ EXPECT_EQ(example, tokenizer.GetInt32());
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+ }
+}
+
+//
+// EncodeString16 / CBORTokenTag::STRING16
+//
+TEST(EncodeDecodeString16Test, RoundtripsEmpty) {
+ // This roundtrips the empty utf16 string through the pair of EncodeString16 /
+ // CBORTokenizer.
+ std::vector<uint8_t> encoded;
+ EncodeString16(span<uint16_t>(), &encoded);
+ EXPECT_EQ(1u, encoded.size());
+ // first three bits: major type = 2; remaining five bits: additional info =
+ // size 0.
+ EXPECT_EQ(2 << 5, encoded[0]);
+
+ // Reverse direction: decode with CBORTokenizer.
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::STRING16, tokenizer.TokenTag());
+ span<uint8_t> decoded_string16_wirerep = tokenizer.GetString16WireRep();
+ EXPECT_TRUE(decoded_string16_wirerep.empty());
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+}
+
+// On the wire, we STRING16 is encoded as little endian (least
+// significant byte first). The host may or may not be little endian,
+// so this routine follows the advice in
+// https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html.
+std::vector<uint16_t> String16WireRepToHost(span<uint8_t> in) {
+ // must be even number of bytes.
+ CHECK_EQ(in.size() & 1, 0u);
+ std::vector<uint16_t> host_out;
+ for (size_t ii = 0; ii < in.size(); ii += 2)
+ host_out.push_back(in[ii + 1] << 8 | in[ii]);
+ return host_out;
+}
+
+TEST(EncodeDecodeString16Test, RoundtripsHelloWorld) {
+ // This roundtrips the hello world message which is given here in utf16
+ // characters. 0xd83c, 0xdf0e: UTF16 encoding for the "Earth Globe Americas"
+ // character, 🌎.
+ std::array<uint16_t, 10> msg{
+ {'H', 'e', 'l', 'l', 'o', ',', ' ', 0xd83c, 0xdf0e, '.'}};
+ std::vector<uint8_t> encoded;
+ EncodeString16(span<uint16_t>(msg.data(), msg.size()), &encoded);
+ // This will be encoded as BYTE_STRING of length 20, so the 20 is encoded in
+ // the additional info part of the initial byte. Payload is two bytes for each
+ // UTF16 character.
+ uint8_t initial_byte = /*major type=*/2 << 5 | /*additional info=*/20;
+ std::array<uint8_t, 21> encoded_expected = {
+ {initial_byte, 'H', 0, 'e', 0, 'l', 0, 'l', 0, 'o', 0,
+ ',', 0, ' ', 0, 0x3c, 0xd8, 0x0e, 0xdf, '.', 0}};
+ EXPECT_THAT(encoded, ElementsAreArray(encoded_expected));
+
+ // Now decode to complete the roundtrip.
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::STRING16, tokenizer.TokenTag());
+ std::vector<uint16_t> decoded =
+ String16WireRepToHost(tokenizer.GetString16WireRep());
+ EXPECT_THAT(decoded, ElementsAreArray(msg));
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+
+ // For bonus points, we look at the decoded message in UTF8 as well so we can
+ // easily see it on the terminal screen.
+ std::string utf8_decoded = UTF16ToUTF8(SpanFrom(decoded));
+ EXPECT_EQ("Hello, 🌎.", utf8_decoded);
+}
+
+TEST(EncodeDecodeString16Test, Roundtrips500) {
+ // We roundtrip a message that has 250 16 bit values. Each of these are just
+ // set to their index. 250 is interesting because the cbor spec uses a
+ // BYTE_STRING of length 500 for one of their examples of how to encode the
+ // start of it (section 2.1) so it's easy for us to look at the first three
+ // bytes closely.
+ std::vector<uint16_t> two_fifty;
+ for (uint16_t ii = 0; ii < 250; ++ii)
+ two_fifty.push_back(ii);
+ std::vector<uint8_t> encoded;
+ EncodeString16(span<uint16_t>(two_fifty.data(), two_fifty.size()), &encoded);
+ EXPECT_EQ(3u + 250u * 2, encoded.size());
+ // Now check the first three bytes:
+ // Major type: 2 (BYTE_STRING)
+ // Additional information: 25, indicating size is represented by 2 bytes.
+ // Bytes 1 and 2 encode 500 (0x01f4).
+ EXPECT_EQ(2 << 5 | 25, encoded[0]);
+ EXPECT_EQ(0x01, encoded[1]);
+ EXPECT_EQ(0xf4, encoded[2]);
+
+ // Now decode to complete the roundtrip.
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::STRING16, tokenizer.TokenTag());
+ std::vector<uint16_t> decoded =
+ String16WireRepToHost(tokenizer.GetString16WireRep());
+ EXPECT_THAT(decoded, ElementsAreArray(two_fifty));
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+}
+
+TEST(EncodeDecodeString16Test, ErrorCases) {
+ struct TestCase {
+ std::vector<uint8_t> data;
+ std::string msg;
+ };
+ std::vector<TestCase> tests{
+ {TestCase{{2 << 5 | 1, 'a'},
+ "length must be divisible by 2 (but it's 1)"},
+ TestCase{{2 << 5 | 29}, "additional info = 29 isn't recognized"},
+ TestCase{{2 << 5 | 9, 1, 2, 3, 4, 5, 6, 7, 8},
+ "length (9) points just past the end of the test case"},
+ TestCase{{2 << 5 | 27, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 'a', 'b', 'c'},
+ "large length pointing past the end of the test case"}}};
+ for (const TestCase& test : tests) {
+ SCOPED_TRACE(test.msg);
+ CBORTokenizer tokenizer(SpanFrom(test.data));
+ EXPECT_EQ(CBORTokenTag::ERROR_VALUE, tokenizer.TokenTag());
+ EXPECT_EQ(Error::CBOR_INVALID_STRING16, tokenizer.Status().error);
+ }
+}
+
+//
+// EncodeString8 / CBORTokenTag::STRING8
+//
+TEST(EncodeDecodeString8Test, RoundtripsHelloWorld) {
+ // This roundtrips the hello world message which is given here in utf8
+ // characters. 🌎 is a four byte utf8 character.
+ std::string utf8_msg = "Hello, 🌎.";
+ std::vector<uint8_t> msg(utf8_msg.begin(), utf8_msg.end());
+ std::vector<uint8_t> encoded;
+ EncodeString8(SpanFrom(utf8_msg), &encoded);
+ // This will be encoded as STRING of length 12, so the 12 is encoded in
+ // the additional info part of the initial byte. Payload is one byte per
+ // utf8 byte.
+ uint8_t initial_byte = /*major type=*/3 << 5 | /*additional info=*/12;
+ std::array<uint8_t, 13> encoded_expected = {{initial_byte, 'H', 'e', 'l', 'l',
+ 'o', ',', ' ', 0xF0, 0x9f, 0x8c,
+ 0x8e, '.'}};
+ EXPECT_THAT(encoded, ElementsAreArray(encoded_expected));
+
+ // Now decode to complete the roundtrip.
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::STRING8, tokenizer.TokenTag());
+ std::vector<uint8_t> decoded(tokenizer.GetString8().begin(),
+ tokenizer.GetString8().end());
+ EXPECT_THAT(decoded, ElementsAreArray(msg));
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+}
+
+TEST(EncodeDecodeString8Test, ErrorCases) {
+ struct TestCase {
+ std::vector<uint8_t> data;
+ std::string msg;
+ };
+ std::vector<TestCase> tests{
+ {TestCase{{3 << 5 | 29}, "additional info = 29 isn't recognized"},
+ TestCase{{3 << 5 | 9, 1, 2, 3, 4, 5, 6, 7, 8},
+ "length (9) points just past the end of the test case"},
+ TestCase{{3 << 5 | 27, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 'a', 'b', 'c'},
+ "large length pointing past the end of the test case"}}};
+ for (const TestCase& test : tests) {
+ SCOPED_TRACE(test.msg);
+ CBORTokenizer tokenizer(SpanFrom(test.data));
+ EXPECT_EQ(CBORTokenTag::ERROR_VALUE, tokenizer.TokenTag());
+ EXPECT_EQ(Error::CBOR_INVALID_STRING8, tokenizer.Status().error);
+ }
+}
+
+TEST(EncodeFromLatin1Test, ConvertsToUTF8IfNeeded) {
+ std::vector<std::pair<std::string, std::string>> examples = {
+ {"Hello, world.", "Hello, world."},
+ {"Above: \xDC"
+ "ber",
+ "Above: Über"},
+ {"\xA5 500 are about \xA3 3.50; a y with umlaut is \xFF",
+ "Β₯ 500 are about Β£ 3.50; a y with umlaut is ΓΏ"}};
+
+ for (const auto& example : examples) {
+ const std::string& latin1 = example.first;
+ const std::string& expected_utf8 = example.second;
+ std::vector<uint8_t> encoded;
+ EncodeFromLatin1(SpanFrom(latin1), &encoded);
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::STRING8, tokenizer.TokenTag());
+ std::vector<uint8_t> decoded(tokenizer.GetString8().begin(),
+ tokenizer.GetString8().end());
+ std::string decoded_str(decoded.begin(), decoded.end());
+ EXPECT_THAT(decoded_str, testing::Eq(expected_utf8));
+ }
+}
+
+TEST(EncodeFromUTF16Test, ConvertsToUTF8IfEasy) {
+ std::vector<uint16_t> ascii = {'e', 'a', 's', 'y'};
+ std::vector<uint8_t> encoded;
+ EncodeFromUTF16(span<uint16_t>(ascii.data(), ascii.size()), &encoded);
+
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::STRING8, tokenizer.TokenTag());
+ std::vector<uint8_t> decoded(tokenizer.GetString8().begin(),
+ tokenizer.GetString8().end());
+ std::string decoded_str(decoded.begin(), decoded.end());
+ EXPECT_THAT(decoded_str, testing::Eq("easy"));
+}
+
+TEST(EncodeFromUTF16Test, EncodesAsString16IfNeeded) {
+ // Since this message contains non-ASCII characters, the routine is
+ // forced to encode as UTF16. We see this below by checking that the
+ // token tag is STRING16.
+ std::vector<uint16_t> msg = {'H', 'e', 'l', 'l', 'o',
+ ',', ' ', 0xd83c, 0xdf0e, '.'};
+ std::vector<uint8_t> encoded;
+ EncodeFromUTF16(span<uint16_t>(msg.data(), msg.size()), &encoded);
+
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::STRING16, tokenizer.TokenTag());
+ std::vector<uint16_t> decoded =
+ String16WireRepToHost(tokenizer.GetString16WireRep());
+ std::string utf8_decoded = UTF16ToUTF8(SpanFrom(decoded));
+ EXPECT_EQ("Hello, 🌎.", utf8_decoded);
+}
+
+//
+// EncodeBinary / CBORTokenTag::BINARY
+//
+TEST(EncodeDecodeBinaryTest, RoundtripsHelloWorld) {
+ std::vector<uint8_t> binary = {'H', 'e', 'l', 'l', 'o', ',', ' ',
+ 'w', 'o', 'r', 'l', 'd', '.'};
+ std::vector<uint8_t> encoded;
+ EncodeBinary(span<uint8_t>(binary.data(), binary.size()), &encoded);
+ // So, on the wire we see that the binary blob travels unmodified.
+ EXPECT_THAT(
+ encoded,
+ ElementsAreArray(std::array<uint8_t, 15>{
+ {(6 << 5 | 22), // tag 22 indicating base64 interpretation in JSON
+ (2 << 5 | 13), // BYTE_STRING (type 2) of length 13
+ 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '.'}}));
+ std::vector<uint8_t> decoded;
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::BINARY, tokenizer.TokenTag());
+ EXPECT_EQ(0, static_cast<int>(tokenizer.Status().error));
+ decoded = std::vector<uint8_t>(tokenizer.GetBinary().begin(),
+ tokenizer.GetBinary().end());
+ EXPECT_THAT(decoded, ElementsAreArray(binary));
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+}
+
+TEST(EncodeDecodeBinaryTest, ErrorCases) {
+ struct TestCase {
+ std::vector<uint8_t> data;
+ std::string msg;
+ };
+ std::vector<TestCase> tests{{TestCase{
+ {6 << 5 | 22, // tag 22 indicating base64 interpretation in JSON
+ 2 << 5 | 27, // BYTE_STRING (type 2), followed by 8 bytes length
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ "large length pointing past the end of the test case"}}};
+ for (const TestCase& test : tests) {
+ SCOPED_TRACE(test.msg);
+ CBORTokenizer tokenizer(SpanFrom(test.data));
+ EXPECT_EQ(CBORTokenTag::ERROR_VALUE, tokenizer.TokenTag());
+ EXPECT_EQ(Error::CBOR_INVALID_BINARY, tokenizer.Status().error);
+ }
+}
+
+//
+// EncodeDouble / CBORTokenTag::DOUBLE
+//
+TEST(EncodeDecodeDoubleTest, RoundtripsWikipediaExample) {
+ // https://en.wikipedia.org/wiki/Double-precision_floating-point_format
+ // provides the example of a hex representation 3FD5 5555 5555 5555, which
+ // approximates 1/3.
+
+ const double kOriginalValue = 1.0 / 3;
+ std::vector<uint8_t> encoded;
+ EncodeDouble(kOriginalValue, &encoded);
+ // first three bits: major type = 7; remaining five bits: additional info =
+ // value 27. This is followed by 8 bytes of payload (which match Wikipedia).
+ EXPECT_THAT(
+ encoded,
+ ElementsAreArray(std::array<uint8_t, 9>{
+ {7 << 5 | 27, 0x3f, 0xd5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55}}));
+
+ // Reverse direction: decode and compare with original value.
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::DOUBLE, tokenizer.TokenTag());
+ EXPECT_THAT(tokenizer.GetDouble(), testing::DoubleEq(kOriginalValue));
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+}
+
+TEST(EncodeDecodeDoubleTest, RoundtripsAdditionalExamples) {
+ std::vector<double> examples = {0.0,
+ 1.0,
+ -1.0,
+ 3.1415,
+ std::numeric_limits<double>::min(),
+ std::numeric_limits<double>::max(),
+ std::numeric_limits<double>::infinity(),
+ std::numeric_limits<double>::quiet_NaN()};
+ for (double example : examples) {
+ SCOPED_TRACE(std::string("example ") + std::to_string(example));
+ std::vector<uint8_t> encoded;
+ EncodeDouble(example, &encoded);
+ CBORTokenizer tokenizer(SpanFrom(encoded));
+ EXPECT_EQ(CBORTokenTag::DOUBLE, tokenizer.TokenTag());
+ if (std::isnan(example))
+ EXPECT_TRUE(std::isnan(tokenizer.GetDouble()));
+ else
+ EXPECT_THAT(tokenizer.GetDouble(), testing::DoubleEq(example));
+ tokenizer.Next();
+ EXPECT_EQ(CBORTokenTag::DONE, tokenizer.TokenTag());
+ }
+}
+
+// =============================================================================
+// cbor::NewCBOREncoder - for encoding from a streaming parser
+// =============================================================================
+
+void EncodeUTF8ForTest(const std::string& key, std::vector<uint8_t>* out) {
+ EncodeString8(SpanFrom(key), out);
+}
+TEST(JSONToCBOREncoderTest, SevenBitStrings) {
+ // When a string can be represented as 7 bit ASCII, the encoder will use the
+ // STRING (major Type 3) type, so the actual characters end up as bytes on the
+ // wire.
+ std::vector<uint8_t> encoded;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> encoder =
+ NewCBOREncoder(&encoded, &status);
+ std::vector<uint16_t> utf16 = {'f', 'o', 'o'};
+ encoder->HandleString16(span<uint16_t>(utf16.data(), utf16.size()));
+ EXPECT_EQ(Error::OK, status.error);
+ // Here we assert that indeed, seven bit strings are represented as
+ // bytes on the wire, "foo" is just "foo".
+ EXPECT_THAT(encoded,
+ ElementsAreArray(std::array<uint8_t, 4>{
+ {/*major type 3*/ 3 << 5 | /*length*/ 3, 'f', 'o', 'o'}}));
+}
+
+TEST(JsonCborRoundtrip, EncodingDecoding) {
+ // Hits all the cases except binary and error in StreamingParserHandler, first
+ // parsing a JSON message into CBOR, then parsing it back from CBOR into JSON.
+ std::string json =
+ "{"
+ "\"string\":\"Hello, \\ud83c\\udf0e.\","
+ "\"double\":3.1415,"
+ "\"int\":1,"
+ "\"negative int\":-1,"
+ "\"bool\":true,"
+ "\"null\":null,"
+ "\"array\":[1,2,3]"
+ "}";
+ std::vector<uint8_t> encoded;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> encoder =
+ NewCBOREncoder(&encoded, &status);
+ span<uint8_t> ascii_in = SpanFrom(json);
+ json::ParseJSON(GetTestPlatform(), ascii_in, encoder.get());
+ std::vector<uint8_t> expected = {
+ 0xd8, // envelope
+ 0x5a, // byte string with 32 bit length
+ 0, 0, 0, 94, // length is 94 bytes
+ };
+ expected.push_back(0xbf); // indef length map start
+ EncodeString8(SpanFrom("string"), &expected);
+ // This is followed by the encoded string for "Hello, 🌎."
+ // So, it's the same bytes that we tested above in
+ // EncodeDecodeString16Test.RoundtripsHelloWorld.
+ expected.push_back(/*major type=*/2 << 5 | /*additional info=*/20);
+ for (uint8_t ch : std::array<uint8_t, 20>{
+ {'H', 0, 'e', 0, 'l', 0, 'l', 0, 'o', 0,
+ ',', 0, ' ', 0, 0x3c, 0xd8, 0x0e, 0xdf, '.', 0}})
+ expected.push_back(ch);
+ EncodeString8(SpanFrom("double"), &expected);
+ EncodeDouble(3.1415, &expected);
+ EncodeString8(SpanFrom("int"), &expected);
+ EncodeInt32(1, &expected);
+ EncodeString8(SpanFrom("negative int"), &expected);
+ EncodeInt32(-1, &expected);
+ EncodeString8(SpanFrom("bool"), &expected);
+ expected.push_back(7 << 5 | 21); // RFC 7049 Section 2.3, Table 2: true
+ EncodeString8(SpanFrom("null"), &expected);
+ expected.push_back(7 << 5 | 22); // RFC 7049 Section 2.3, Table 2: null
+ EncodeString8(SpanFrom("array"), &expected);
+ expected.push_back(0xd8); // envelope
+ expected.push_back(0x5a); // byte string with 32 bit length
+ // the length is 5 bytes (that's up to end indef length array below).
+ for (uint8_t ch : std::array<uint8_t, 4>{{0, 0, 0, 5}})
+ expected.push_back(ch);
+ expected.push_back(0x9f); // RFC 7049 Section 2.2.1, indef length array start
+ expected.push_back(1); // Three UNSIGNED values (easy since Major Type 0)
+ expected.push_back(2);
+ expected.push_back(3);
+ expected.push_back(0xff); // End indef length array
+ expected.push_back(0xff); // End indef length map
+ EXPECT_TRUE(status.ok());
+ EXPECT_THAT(encoded, ElementsAreArray(expected));
+
+ // And now we roundtrip, decoding the message we just encoded.
+ std::string decoded;
+ std::unique_ptr<StreamingParserHandler> json_encoder =
+ NewJSONEncoder(&GetTestPlatform(), &decoded, &status);
+ ParseCBOR(span<uint8_t>(encoded.data(), encoded.size()), json_encoder.get());
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(json, decoded);
+}
+
+TEST(JsonCborRoundtrip, MoreRoundtripExamples) {
+ std::vector<std::string> examples = {
+ // Tests that after closing a nested objects, additional key/value pairs
+ // are considered.
+ "{\"foo\":{\"bar\":1},\"baz\":2}", "{\"foo\":[1,2,3],\"baz\":2}"};
+ for (const std::string& json : examples) {
+ SCOPED_TRACE(std::string("example: ") + json);
+ std::vector<uint8_t> encoded;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> encoder =
+ NewCBOREncoder(&encoded, &status);
+ span<uint8_t> ascii_in = SpanFrom(json);
+ ParseJSON(GetTestPlatform(), ascii_in, encoder.get());
+ std::string decoded;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &decoded, &status);
+ ParseCBOR(span<uint8_t>(encoded.data(), encoded.size()), json_writer.get());
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(json, decoded);
+ }
+}
+
+TEST(JSONToCBOREncoderTest, HelloWorldBinary_WithTripToJson) {
+ // The StreamingParserHandler::HandleBinary is a special case: The JSON parser
+ // will never call this method, because JSON does not natively support the
+ // binary type. So, we can't fully roundtrip. However, the other direction
+ // works: binary will be rendered in JSON, as a base64 string. So, we make
+ // calls to the encoder directly here, to construct a message, and one of
+ // these calls is ::HandleBinary, to which we pass a "binary" string
+ // containing "Hello, world.".
+ std::vector<uint8_t> encoded;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> encoder =
+ NewCBOREncoder(&encoded, &status);
+ encoder->HandleMapBegin();
+ // Emit a key.
+ std::vector<uint16_t> key = {'f', 'o', 'o'};
+ encoder->HandleString16(SpanFrom(key));
+ // Emit the binary payload, an arbitrary array of bytes that happens to
+ // be the ascii message "Hello, world.".
+ encoder->HandleBinary(SpanFrom(std::vector<uint8_t>{
+ 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '.'}));
+ encoder->HandleMapEnd();
+ EXPECT_EQ(Error::OK, status.error);
+
+ // Now drive the json writer via the CBOR decoder.
+ std::string decoded;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &decoded, &status);
+ ParseCBOR(SpanFrom(encoded), json_writer.get());
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(Status::npos(), status.pos);
+ // "Hello, world." in base64 is "SGVsbG8sIHdvcmxkLg==".
+ EXPECT_EQ("{\"foo\":\"SGVsbG8sIHdvcmxkLg==\"}", decoded);
+}
+
+// =============================================================================
+// cbor::ParseCBOR - for receiving streaming parser events for CBOR messages
+// =============================================================================
+
+TEST(ParseCBORTest, ParseEmptyCBORMessage) {
+ // An envelope starting with 0xd8, 0x5a, with the byte length
+ // of 2, containing a map that's empty (0xbf for map
+ // start, and 0xff for map end).
+ std::vector<uint8_t> in = {0xd8, 0x5a, 0, 0, 0, 2, 0xbf, 0xff};
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(in.data(), in.size()), json_writer.get());
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ("{}", out);
+}
+
+TEST(ParseCBORTest, ParseCBORHelloWorld) {
+ const uint8_t kPayloadLen = 27;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen};
+ bytes.push_back(0xbf); // start indef length map.
+ EncodeString8(SpanFrom("msg"), &bytes); // key: msg
+ // Now write the value, the familiar "Hello, 🌎." where the globe is expressed
+ // as two utf16 chars.
+ bytes.push_back(/*major type=*/2 << 5 | /*additional info=*/20);
+ for (uint8_t ch : std::array<uint8_t, 20>{
+ {'H', 0, 'e', 0, 'l', 0, 'l', 0, 'o', 0,
+ ',', 0, ' ', 0, 0x3c, 0xd8, 0x0e, 0xdf, '.', 0}})
+ bytes.push_back(ch);
+ bytes.push_back(0xff); // stop byte
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ("{\"msg\":\"Hello, \\ud83c\\udf0e.\"}", out);
+}
+
+TEST(ParseCBORTest, UTF8IsSupportedInKeys) {
+ const uint8_t kPayloadLen = 11;
+ std::vector<uint8_t> bytes = {cbor::InitialByteForEnvelope(),
+ cbor::InitialByteFor32BitLengthByteString(),
+ 0,
+ 0,
+ 0,
+ kPayloadLen};
+ bytes.push_back(cbor::EncodeIndefiniteLengthMapStart());
+ // Two UTF16 chars.
+ EncodeString8(SpanFrom("🌎"), &bytes);
+ // Can be encoded as a single UTF16 char.
+ EncodeString8(SpanFrom("☾"), &bytes);
+ bytes.push_back(cbor::EncodeStop());
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ("{\"\\ud83c\\udf0e\":\"\\u263e\"}", out);
+}
+
+TEST(ParseCBORTest, NoInputError) {
+ std::vector<uint8_t> in = {};
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(in.data(), in.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_NO_INPUT, status.error);
+ EXPECT_EQ("", out);
+}
+
+TEST(ParseCBORTest, InvalidStartByteError) {
+ // Here we test that some actual json, which usually starts with {,
+ // is not considered CBOR. CBOR messages must start with 0x5a, the
+ // envelope start byte.
+ std::string json = "{\"msg\": \"Hello, world.\"}";
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(SpanFrom(json), json_writer.get());
+ EXPECT_EQ(Error::CBOR_INVALID_START_BYTE, status.error);
+ EXPECT_EQ("", out);
+}
+
+TEST(ParseCBORTest, UnexpectedEofExpectedValueError) {
+ constexpr uint8_t kPayloadLen = 5;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope
+ 0xbf}; // map start
+ // A key; so value would be next.
+ EncodeString8(SpanFrom("key"), &bytes);
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE, status.error);
+ EXPECT_EQ(bytes.size(), status.pos);
+ EXPECT_EQ("", out);
+}
+
+TEST(ParseCBORTest, UnexpectedEofInArrayError) {
+ constexpr uint8_t kPayloadLen = 8;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope
+ 0xbf}; // The byte for starting a map.
+ // A key; so value would be next.
+ EncodeString8(SpanFrom("array"), &bytes);
+ bytes.push_back(0x9f); // byte for indefinite length array start.
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_UNEXPECTED_EOF_IN_ARRAY, status.error);
+ EXPECT_EQ(bytes.size(), status.pos);
+ EXPECT_EQ("", out);
+}
+
+TEST(ParseCBORTest, UnexpectedEofInMapError) {
+ constexpr uint8_t kPayloadLen = 1;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope
+ 0xbf}; // The byte for starting a map.
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_UNEXPECTED_EOF_IN_MAP, status.error);
+ EXPECT_EQ(7u, status.pos);
+ EXPECT_EQ("", out);
+}
+
+TEST(ParseCBORTest, InvalidMapKeyError) {
+ constexpr uint8_t kPayloadLen = 2;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0,
+ 0, 0, kPayloadLen, // envelope
+ 0xbf, // map start
+ 7 << 5 | 22}; // null (not a valid map key)
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_INVALID_MAP_KEY, status.error);
+ EXPECT_EQ(7u, status.pos);
+ EXPECT_EQ("", out);
+}
+
+std::vector<uint8_t> MakeNestedCBOR(int depth) {
+ std::vector<uint8_t> bytes;
+ std::vector<EnvelopeEncoder> envelopes;
+ for (int ii = 0; ii < depth; ++ii) {
+ envelopes.emplace_back();
+ envelopes.back().EncodeStart(&bytes);
+ bytes.push_back(0xbf); // indef length map start
+ EncodeString8(SpanFrom("key"), &bytes);
+ }
+ EncodeString8(SpanFrom("innermost_value"), &bytes);
+ for (int ii = 0; ii < depth; ++ii) {
+ bytes.push_back(0xff); // stop byte, finishes map.
+ envelopes.back().EncodeStop(&bytes);
+ envelopes.pop_back();
+ }
+ return bytes;
+}
+
+TEST(ParseCBORTest, StackLimitExceededError) {
+ { // Depth 3: no stack limit exceeded error and is easy to inspect.
+ std::vector<uint8_t> bytes = MakeNestedCBOR(3);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(Status::npos(), status.pos);
+ EXPECT_EQ("{\"key\":{\"key\":{\"key\":\"innermost_value\"}}}", out);
+ }
+ { // Depth 300: no stack limit exceeded.
+ std::vector<uint8_t> bytes = MakeNestedCBOR(300);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(Status::npos(), status.pos);
+ }
+
+ // We just want to know the length of one opening map so we can compute
+ // where the error is encountered. So we look at a small example and find
+ // the second envelope start.
+ std::vector<uint8_t> small_example = MakeNestedCBOR(3);
+ size_t opening_segment_size = 1; // Start after the first envelope start.
+ while (opening_segment_size < small_example.size() &&
+ small_example[opening_segment_size] != 0xd8)
+ opening_segment_size++;
+
+ { // Depth 301: limit exceeded.
+ std::vector<uint8_t> bytes = MakeNestedCBOR(301);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_STACK_LIMIT_EXCEEDED, status.error);
+ EXPECT_EQ(opening_segment_size * 301, status.pos);
+ }
+ { // Depth 320: still limit exceeded, and at the same pos as for 1001
+ std::vector<uint8_t> bytes = MakeNestedCBOR(320);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_STACK_LIMIT_EXCEEDED, status.error);
+ EXPECT_EQ(opening_segment_size * 301, status.pos);
+ }
+}
+
+TEST(ParseCBORTest, UnsupportedValueError) {
+ constexpr uint8_t kPayloadLen = 6;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope
+ 0xbf}; // map start
+ EncodeString8(SpanFrom("key"), &bytes);
+ size_t error_pos = bytes.size();
+ bytes.push_back(6 << 5 | 5); // tags aren't supported yet.
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_UNSUPPORTED_VALUE, status.error);
+ EXPECT_EQ(error_pos, status.pos);
+ EXPECT_EQ("", out);
+}
+
+TEST(ParseCBORTest, InvalidString16Error) {
+ constexpr uint8_t kPayloadLen = 11;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope
+ 0xbf}; // map start
+ EncodeString8(SpanFrom("key"), &bytes);
+ size_t error_pos = bytes.size();
+ // a BYTE_STRING of length 5 as value; since we interpret these as string16,
+ // it's going to be invalid as each character would need two bytes, but
+ // 5 isn't divisible by 2.
+ bytes.push_back(2 << 5 | 5);
+ for (int ii = 0; ii < 5; ++ii)
+ bytes.push_back(' ');
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_INVALID_STRING16, status.error);
+ EXPECT_EQ(error_pos, status.pos);
+ EXPECT_EQ("", out);
+}
+
+TEST(ParseCBORTest, InvalidString8Error) {
+ constexpr uint8_t kPayloadLen = 6;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope
+ 0xbf}; // map start
+ EncodeString8(SpanFrom("key"), &bytes);
+ size_t error_pos = bytes.size();
+ // a STRING of length 5 as value, but we're at the end of the bytes array
+ // so it can't be decoded successfully.
+ bytes.push_back(3 << 5 | 5);
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_INVALID_STRING8, status.error);
+ EXPECT_EQ(error_pos, status.pos);
+ EXPECT_EQ("", out);
+}
+
+TEST(ParseCBORTest, InvalidBinaryError) {
+ constexpr uint8_t kPayloadLen = 9;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope
+ 0xbf}; // map start
+ EncodeString8(SpanFrom("key"), &bytes);
+ size_t error_pos = bytes.size();
+ bytes.push_back(6 << 5 | 22); // base64 hint for JSON; indicates binary
+ bytes.push_back(2 << 5 | 10); // BYTE_STRING (major type 2) of length 10
+ // Just two garbage bytes, not enough for the binary.
+ bytes.push_back(0x31);
+ bytes.push_back(0x23);
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_INVALID_BINARY, status.error);
+ EXPECT_EQ(error_pos, status.pos);
+ EXPECT_EQ("", out);
+}
+
+TEST(ParseCBORTest, InvalidDoubleError) {
+ constexpr uint8_t kPayloadLen = 8;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope
+ 0xbf}; // map start
+ EncodeString8(SpanFrom("key"), &bytes);
+ size_t error_pos = bytes.size();
+ bytes.push_back(7 << 5 | 27); // initial byte for double
+ // Just two garbage bytes, not enough to represent an actual double.
+ bytes.push_back(0x31);
+ bytes.push_back(0x23);
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_INVALID_DOUBLE, status.error);
+ EXPECT_EQ(error_pos, status.pos);
+ EXPECT_EQ("", out);
+}
+
+TEST(ParseCBORTest, InvalidSignedError) {
+ constexpr uint8_t kPayloadLen = 14;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope
+ 0xbf}; // map start
+ EncodeString8(SpanFrom("key"), &bytes);
+ size_t error_pos = bytes.size();
+ // uint64_t max is a perfectly fine value to encode as CBOR unsigned,
+ // but we don't support this since we only cover the int32_t range.
+ internals::WriteTokenStart(MajorType::UNSIGNED,
+ std::numeric_limits<uint64_t>::max(), &bytes);
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_INVALID_INT32, status.error);
+ EXPECT_EQ(error_pos, status.pos);
+ EXPECT_EQ("", out);
+}
+
+TEST(ParseCBORTest, TrailingJunk) {
+ constexpr uint8_t kPayloadLen = 35;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope
+ 0xbf}; // map start
+ EncodeString8(SpanFrom("key"), &bytes);
+ EncodeString8(SpanFrom("value"), &bytes);
+ bytes.push_back(0xff); // Up to here, it's a perfectly fine msg.
+ size_t error_pos = bytes.size();
+ EncodeString8(SpanFrom("trailing junk"), &bytes);
+
+ internals::WriteTokenStart(MajorType::UNSIGNED,
+ std::numeric_limits<uint64_t>::max(), &bytes);
+ EXPECT_EQ(kPayloadLen, bytes.size() - 6);
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
+ EXPECT_EQ(Error::CBOR_TRAILING_JUNK, status.error);
+ EXPECT_EQ(error_pos, status.pos);
+ EXPECT_EQ("", out);
+}
+
+// =============================================================================
+// cbor::AppendString8EntryToMap - for limited in-place editing of messages
+// =============================================================================
+
+template <typename T>
+class AppendString8EntryToMapTest : public ::testing::Test {};
+
+using ContainerTestTypes = ::testing::Types<std::vector<uint8_t>, std::string>;
+TYPED_TEST_SUITE(AppendString8EntryToMapTest, ContainerTestTypes);
+
+TYPED_TEST(AppendString8EntryToMapTest, AppendsEntrySuccessfully) {
+ constexpr uint8_t kPayloadLen = 12;
+ std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen, // envelope
+ 0xbf}; // map start
+ size_t pos_before_payload = bytes.size() - 1;
+ EncodeString8(SpanFrom("key"), &bytes);
+ EncodeString8(SpanFrom("value"), &bytes);
+ bytes.push_back(0xff); // A perfectly fine cbor message.
+ EXPECT_EQ(kPayloadLen, bytes.size() - pos_before_payload);
+
+ TypeParam msg(bytes.begin(), bytes.end());
+
+ Status status =
+ AppendString8EntryToCBORMap(SpanFrom("foo"), SpanFrom("bar"), &msg);
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(Status::npos(), status.pos);
+ std::string out;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(SpanFrom(msg), json_writer.get());
+ EXPECT_EQ("{\"key\":\"value\",\"foo\":\"bar\"}", out);
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(Status::npos(), status.pos);
+}
+
+TYPED_TEST(AppendString8EntryToMapTest, AppendThreeEntries) {
+ std::vector<uint8_t> encoded = {
+ 0xd8, 0x5a, 0, 0, 0, 2, EncodeIndefiniteLengthMapStart(), EncodeStop()};
+ EXPECT_EQ(Error::OK, AppendString8EntryToCBORMap(SpanFrom("key"),
+ SpanFrom("value"), &encoded)
+ .error);
+ EXPECT_EQ(Error::OK, AppendString8EntryToCBORMap(SpanFrom("key1"),
+ SpanFrom("value1"), &encoded)
+ .error);
+ EXPECT_EQ(Error::OK, AppendString8EntryToCBORMap(SpanFrom("key2"),
+ SpanFrom("value2"), &encoded)
+ .error);
+ TypeParam msg(encoded.begin(), encoded.end());
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> json_writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ ParseCBOR(SpanFrom(msg), json_writer.get());
+ EXPECT_EQ("{\"key\":\"value\",\"key1\":\"value1\",\"key2\":\"value2\"}", out);
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(Status::npos(), status.pos);
+}
+
+TYPED_TEST(AppendString8EntryToMapTest, MapStartExpected_Error) {
+ std::vector<uint8_t> bytes = {
+ 0xd8, 0x5a, 0, 0, 0, 1, EncodeIndefiniteLengthArrayStart()};
+ TypeParam msg(bytes.begin(), bytes.end());
+ Status status =
+ AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg);
+ EXPECT_EQ(Error::CBOR_MAP_START_EXPECTED, status.error);
+ EXPECT_EQ(6u, status.pos);
+}
+
+TYPED_TEST(AppendString8EntryToMapTest, MapStopExpected_Error) {
+ std::vector<uint8_t> bytes = {
+ 0xd8, 0x5a, 0, 0, 0, 2, EncodeIndefiniteLengthMapStart(), 42};
+ TypeParam msg(bytes.begin(), bytes.end());
+ Status status =
+ AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg);
+ EXPECT_EQ(Error::CBOR_MAP_STOP_EXPECTED, status.error);
+ EXPECT_EQ(7u, status.pos);
+}
+
+TYPED_TEST(AppendString8EntryToMapTest, InvalidEnvelope_Error) {
+ { // Second byte is wrong.
+ std::vector<uint8_t> bytes = {
+ 0x5a, 0, 0, 0, 2, EncodeIndefiniteLengthMapStart(), EncodeStop(), 0};
+ TypeParam msg(bytes.begin(), bytes.end());
+ Status status =
+ AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg);
+ EXPECT_EQ(Error::CBOR_INVALID_ENVELOPE, status.error);
+ EXPECT_EQ(0u, status.pos);
+ }
+ { // Second byte is wrong.
+ std::vector<uint8_t> bytes = {
+ 0xd8, 0x7a, 0, 0, 0, 2, EncodeIndefiniteLengthMapStart(), EncodeStop()};
+ TypeParam msg(bytes.begin(), bytes.end());
+ Status status =
+ AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg);
+ EXPECT_EQ(Error::CBOR_INVALID_ENVELOPE, status.error);
+ EXPECT_EQ(0u, status.pos);
+ }
+ { // Invalid envelope size example.
+ std::vector<uint8_t> bytes = {
+ 0xd8, 0x5a, 0, 0, 0, 3, EncodeIndefiniteLengthMapStart(), EncodeStop(),
+ };
+ TypeParam msg(bytes.begin(), bytes.end());
+ Status status =
+ AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg);
+ EXPECT_EQ(Error::CBOR_INVALID_ENVELOPE, status.error);
+ EXPECT_EQ(0u, status.pos);
+ }
+ { // Invalid envelope size example.
+ std::vector<uint8_t> bytes = {
+ 0xd8, 0x5a, 0, 0, 0, 1, EncodeIndefiniteLengthMapStart(), EncodeStop(),
+ };
+ TypeParam msg(bytes.begin(), bytes.end());
+ Status status =
+ AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg);
+ EXPECT_EQ(Error::CBOR_INVALID_ENVELOPE, status.error);
+ EXPECT_EQ(0u, status.pos);
+ }
+}
+} // namespace cbor
+
+namespace json {
+
+// =============================================================================
+// json::NewJSONEncoder - for encoding streaming parser events as JSON
+// =============================================================================
+
+void WriteUTF8AsUTF16(StreamingParserHandler* writer, const std::string& utf8) {
+ writer->HandleString16(SpanFrom(UTF8ToUTF16(SpanFrom(utf8))));
+}
+
+TEST(JsonStdStringWriterTest, HelloWorld) {
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ writer->HandleMapBegin();
+ WriteUTF8AsUTF16(writer.get(), "msg1");
+ WriteUTF8AsUTF16(writer.get(), "Hello, 🌎.");
+ std::string key = "msg1-as-utf8";
+ std::string value = "Hello, 🌎.";
+ writer->HandleString8(SpanFrom(key));
+ writer->HandleString8(SpanFrom(value));
+ WriteUTF8AsUTF16(writer.get(), "msg2");
+ WriteUTF8AsUTF16(writer.get(), "\\\b\r\n\t\f\"");
+ WriteUTF8AsUTF16(writer.get(), "nested");
+ writer->HandleMapBegin();
+ WriteUTF8AsUTF16(writer.get(), "double");
+ writer->HandleDouble(3.1415);
+ WriteUTF8AsUTF16(writer.get(), "int");
+ writer->HandleInt32(-42);
+ WriteUTF8AsUTF16(writer.get(), "bool");
+ writer->HandleBool(false);
+ WriteUTF8AsUTF16(writer.get(), "null");
+ writer->HandleNull();
+ writer->HandleMapEnd();
+ WriteUTF8AsUTF16(writer.get(), "array");
+ writer->HandleArrayBegin();
+ writer->HandleInt32(1);
+ writer->HandleInt32(2);
+ writer->HandleInt32(3);
+ writer->HandleArrayEnd();
+ writer->HandleMapEnd();
+ EXPECT_TRUE(status.ok());
+ EXPECT_EQ(
+ "{\"msg1\":\"Hello, \\ud83c\\udf0e.\","
+ "\"msg1-as-utf8\":\"Hello, \\ud83c\\udf0e.\","
+ "\"msg2\":\"\\\\\\b\\r\\n\\t\\f\\\"\","
+ "\"nested\":{\"double\":3.1415,\"int\":-42,"
+ "\"bool\":false,\"null\":null},\"array\":[1,2,3]}",
+ out);
+}
+
+TEST(JsonStdStringWriterTest, RepresentingNonFiniteValuesAsNull) {
+ // JSON can't represent +Infinity, -Infinity, or NaN.
+ // So in practice it's mapped to null.
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ writer->HandleMapBegin();
+ writer->HandleString8(SpanFrom("Infinity"));
+ writer->HandleDouble(std::numeric_limits<double>::infinity());
+ writer->HandleString8(SpanFrom("-Infinity"));
+ writer->HandleDouble(-std::numeric_limits<double>::infinity());
+ writer->HandleString8(SpanFrom("NaN"));
+ writer->HandleDouble(std::numeric_limits<double>::quiet_NaN());
+ writer->HandleMapEnd();
+ EXPECT_TRUE(status.ok());
+ EXPECT_EQ("{\"Infinity\":null,\"-Infinity\":null,\"NaN\":null}", out);
+}
+
+TEST(JsonStdStringWriterTest, BinaryEncodedAsJsonString) {
+ // The encoder emits binary submitted to StreamingParserHandler::HandleBinary
+ // as base64. The following three examples are taken from
+ // https://en.wikipedia.org/wiki/Base64.
+ {
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ writer->HandleBinary(SpanFrom(std::vector<uint8_t>({'M', 'a', 'n'})));
+ EXPECT_TRUE(status.ok());
+ EXPECT_EQ("\"TWFu\"", out);
+ }
+ {
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ writer->HandleBinary(SpanFrom(std::vector<uint8_t>({'M', 'a'})));
+ EXPECT_TRUE(status.ok());
+ EXPECT_EQ("\"TWE=\"", out);
+ }
+ {
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ writer->HandleBinary(SpanFrom(std::vector<uint8_t>({'M'})));
+ EXPECT_TRUE(status.ok());
+ EXPECT_EQ("\"TQ==\"", out);
+ }
+ { // "Hello, world.", verified with base64decode.org.
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ writer->HandleBinary(SpanFrom(std::vector<uint8_t>(
+ {'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '.'})));
+ EXPECT_TRUE(status.ok());
+ EXPECT_EQ("\"SGVsbG8sIHdvcmxkLg==\"", out);
+ }
+}
+
+TEST(JsonStdStringWriterTest, HandlesErrors) {
+ // When an error is sent via HandleError, it saves it in the provided
+ // status and clears the output.
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> writer =
+ NewJSONEncoder(&GetTestPlatform(), &out, &status);
+ writer->HandleMapBegin();
+ WriteUTF8AsUTF16(writer.get(), "msg1");
+ writer->HandleError(Status{Error::JSON_PARSER_VALUE_EXPECTED, 42});
+ EXPECT_EQ(Error::JSON_PARSER_VALUE_EXPECTED, status.error);
+ EXPECT_EQ(42u, status.pos);
+ EXPECT_EQ("", out);
+}
+
+// We'd use Gmock but unfortunately it only handles copyable return types.
+class MockPlatform : public Platform {
+ public:
+ // Not implemented.
+ bool StrToD(const char* str, double* result) const override { return false; }
+
+ // A map with pre-registered responses for DToSTr.
+ std::map<double, std::string> dtostr_responses_;
+
+ std::unique_ptr<char[]> DToStr(double value) const override {
+ auto it = dtostr_responses_.find(value);
+ CHECK(it != dtostr_responses_.end());
+ const std::string& str = it->second;
+ std::unique_ptr<char[]> response(new char[str.size() + 1]);
+ memcpy(response.get(), str.c_str(), str.size() + 1);
+ return response;
+ }
+};
+
+TEST(JsonStdStringWriterTest, DoubleToString) {
+ // This "broken" platform responds without the leading 0 before the
+ // decimal dot, so it'd be invalid JSON.
+ MockPlatform platform;
+ platform.dtostr_responses_[.1] = ".1";
+ platform.dtostr_responses_[-.7] = "-.7";
+
+ std::string out;
+ Status status;
+ std::unique_ptr<StreamingParserHandler> writer =
+ NewJSONEncoder(&platform, &out, &status);
+ writer->HandleArrayBegin();
+ writer->HandleDouble(.1);
+ writer->HandleDouble(-.7);
+ writer->HandleArrayEnd();
+ EXPECT_EQ("[0.1,-0.7]", out);
+}
+
+// =============================================================================
+// json::ParseJSON - for receiving streaming parser events for JSON
+// =============================================================================
+
+class Log : public StreamingParserHandler {
+ public:
+ void HandleMapBegin() override { log_ << "map begin\n"; }
+
+ void HandleMapEnd() override { log_ << "map end\n"; }
+
+ void HandleArrayBegin() override { log_ << "array begin\n"; }
+
+ void HandleArrayEnd() override { log_ << "array end\n"; }
+
+ void HandleString8(span<uint8_t> chars) override {
+ log_ << "string8: " << std::string(chars.begin(), chars.end()) << "\n";
+ }
+
+ void HandleString16(span<uint16_t> chars) override {
+ log_ << "string16: " << UTF16ToUTF8(chars) << "\n";
+ }
+
+ void HandleBinary(span<uint8_t> bytes) override {
+ // JSON doesn't have native support for arbitrary bytes, so our parser will
+ // never call this.
+ CHECK(false);
+ }
+
+ void HandleDouble(double value) override {
+ log_ << "double: " << value << "\n";
+ }
+
+ void HandleInt32(int32_t value) override { log_ << "int: " << value << "\n"; }
+
+ void HandleBool(bool value) override { log_ << "bool: " << value << "\n"; }
+
+ void HandleNull() override { log_ << "null\n"; }
+
+ void HandleError(Status status) override { status_ = status; }
+
+ std::string str() const { return status_.ok() ? log_.str() : ""; }
+
+ Status status() const { return status_; }
+
+ private:
+ std::ostringstream log_;
+ Status status_;
+};
+
+class JsonParserTest : public ::testing::Test {
+ protected:
+ Log log_;
+};
+
+TEST_F(JsonParserTest, SimpleDictionary) {
+ std::string json = "{\"foo\": 42}";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_TRUE(log_.status().ok());
+ EXPECT_EQ(
+ "map begin\n"
+ "string16: foo\n"
+ "int: 42\n"
+ "map end\n",
+ log_.str());
+}
+
+TEST_F(JsonParserTest, Whitespace) {
+ std::string json = "\n {\n\"msg\"\n: \v\"Hello, world.\"\t\r}\t";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_TRUE(log_.status().ok());
+ EXPECT_EQ(
+ "map begin\n"
+ "string16: msg\n"
+ "string16: Hello, world.\n"
+ "map end\n",
+ log_.str());
+}
+
+TEST_F(JsonParserTest, NestedDictionary) {
+ std::string json = "{\"foo\": {\"bar\": {\"baz\": 1}, \"bar2\": 2}}";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_TRUE(log_.status().ok());
+ EXPECT_EQ(
+ "map begin\n"
+ "string16: foo\n"
+ "map begin\n"
+ "string16: bar\n"
+ "map begin\n"
+ "string16: baz\n"
+ "int: 1\n"
+ "map end\n"
+ "string16: bar2\n"
+ "int: 2\n"
+ "map end\n"
+ "map end\n",
+ log_.str());
+}
+
+TEST_F(JsonParserTest, Doubles) {
+ std::string json = "{\"foo\": 3.1415, \"bar\": 31415e-4}";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_TRUE(log_.status().ok());
+ EXPECT_EQ(
+ "map begin\n"
+ "string16: foo\n"
+ "double: 3.1415\n"
+ "string16: bar\n"
+ "double: 3.1415\n"
+ "map end\n",
+ log_.str());
+}
+
+TEST_F(JsonParserTest, Unicode) {
+ // Globe character. 0xF0 0x9F 0x8C 0x8E in utf8, 0xD83C 0xDF0E in utf16.
+ std::string json = "{\"msg\": \"Hello, \\uD83C\\uDF0E.\"}";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_TRUE(log_.status().ok());
+ EXPECT_EQ(
+ "map begin\n"
+ "string16: msg\n"
+ "string16: Hello, 🌎.\n"
+ "map end\n",
+ log_.str());
+}
+
+TEST_F(JsonParserTest, Unicode_ParseUtf16) {
+ // Globe character. utf8: 0xF0 0x9F 0x8C 0x8E; utf16: 0xD83C 0xDF0E.
+ // Crescent moon character. utf8: 0xF0 0x9F 0x8C 0x99; utf16: 0xD83C 0xDF19.
+
+ // We provide the moon with json escape, but the earth as utf16 input.
+ // Either way they arrive as utf8 (after decoding in log_.str()).
+ std::vector<uint16_t> json =
+ UTF8ToUTF16(SpanFrom("{\"space\": \"🌎 \\uD83C\\uDF19.\"}"));
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_TRUE(log_.status().ok());
+ EXPECT_EQ(
+ "map begin\n"
+ "string16: space\n"
+ "string16: 🌎 πŸŒ™.\n"
+ "map end\n",
+ log_.str());
+}
+
+TEST_F(JsonParserTest, Unicode_ParseUtf8) {
+ // Used below:
+ // Π³Π»Π°ΡΠ½ΠΎΡΡ‚ΡŒ - example for 2 byte utf8, Russian word "glasnost"
+ // 屋 - example for 3 byte utf8, Chinese word for "house"
+ // 🌎 - example for 4 byte utf8: 0xF0 0x9F 0x8C 0x8E; utf16: 0xD83C 0xDF0E.
+ // πŸŒ™ - example for escapes: utf8: 0xF0 0x9F 0x8C 0x99; utf16: 0xD83C 0xDF19.
+
+ // We provide the moon with json escape, but the earth as utf8 input.
+ // Either way they arrive as utf8 (after decoding in log_.str()).
+ std::string json =
+ "{"
+ "\"escapes\": \"\\uD83C\\uDF19\","
+ "\"2 byte\":\"Π³Π»Π°ΡΠ½ΠΎΡΡ‚ΡŒ\","
+ "\"3 byte\":\"屋\","
+ "\"4 byte\":\"🌎\""
+ "}";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_TRUE(log_.status().ok());
+ EXPECT_EQ(
+ "map begin\n"
+ "string16: escapes\n"
+ "string16: πŸŒ™\n"
+ "string16: 2 byte\n"
+ "string16: Π³Π»Π°ΡΠ½ΠΎΡΡ‚ΡŒ\n"
+ "string16: 3 byte\n"
+ "string16: 屋\n"
+ "string16: 4 byte\n"
+ "string16: 🌎\n"
+ "map end\n",
+ log_.str());
+}
+
+TEST_F(JsonParserTest, UnprocessedInputRemainsError) {
+ // Trailing junk after the valid JSON.
+ std::string json = "{\"foo\": 3.1415} junk";
+ size_t junk_idx = json.find("junk");
+ EXPECT_NE(junk_idx, std::string::npos);
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS, log_.status().error);
+ EXPECT_EQ(junk_idx, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+std::string MakeNestedJson(int depth) {
+ std::string json;
+ for (int ii = 0; ii < depth; ++ii)
+ json += "{\"foo\":";
+ json += "42";
+ for (int ii = 0; ii < depth; ++ii)
+ json += "}";
+ return json;
+}
+
+TEST_F(JsonParserTest, StackLimitExceededError_BelowLimit) {
+ // kStackLimit is 300 (see json_parser.cc). First let's
+ // try with a small nested example.
+ std::string json_3 = MakeNestedJson(3);
+ ParseJSON(GetTestPlatform(), SpanFrom(json_3), &log_);
+ EXPECT_TRUE(log_.status().ok());
+ EXPECT_EQ(
+ "map begin\n"
+ "string16: foo\n"
+ "map begin\n"
+ "string16: foo\n"
+ "map begin\n"
+ "string16: foo\n"
+ "int: 42\n"
+ "map end\n"
+ "map end\n"
+ "map end\n",
+ log_.str());
+}
+
+TEST_F(JsonParserTest, StackLimitExceededError_AtLimit) {
+ // Now with kStackLimit (300).
+ std::string json_limit = MakeNestedJson(300);
+ ParseJSON(GetTestPlatform(),
+ span<uint8_t>(reinterpret_cast<const uint8_t*>(json_limit.data()),
+ json_limit.size()),
+ &log_);
+ EXPECT_TRUE(log_.status().ok());
+}
+
+TEST_F(JsonParserTest, StackLimitExceededError_AboveLimit) {
+ // Now with kStackLimit + 1 (301) - it exceeds in the innermost instance.
+ std::string exceeded = MakeNestedJson(301);
+ ParseJSON(GetTestPlatform(), SpanFrom(exceeded), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED, log_.status().error);
+ EXPECT_EQ(strlen("{\"foo\":") * 301, log_.status().pos);
+}
+
+TEST_F(JsonParserTest, StackLimitExceededError_WayAboveLimit) {
+ // Now way past the limit. Still, the point of exceeding is 301.
+ std::string far_out = MakeNestedJson(320);
+ ParseJSON(GetTestPlatform(), SpanFrom(far_out), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED, log_.status().error);
+ EXPECT_EQ(strlen("{\"foo\":") * 301, log_.status().pos);
+}
+
+TEST_F(JsonParserTest, NoInputError) {
+ std::string json = "";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_NO_INPUT, log_.status().error);
+ EXPECT_EQ(0u, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+TEST_F(JsonParserTest, InvalidTokenError) {
+ std::string json = "|";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_INVALID_TOKEN, log_.status().error);
+ EXPECT_EQ(0u, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+TEST_F(JsonParserTest, InvalidNumberError) {
+ // Mantissa exceeds max (the constant used here is int64_t max).
+ std::string json = "1E9223372036854775807";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_INVALID_NUMBER, log_.status().error);
+ EXPECT_EQ(0u, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+TEST_F(JsonParserTest, InvalidStringError) {
+ // \x22 is an unsupported escape sequence
+ std::string json = "\"foo\\x22\"";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_INVALID_STRING, log_.status().error);
+ EXPECT_EQ(0u, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+TEST_F(JsonParserTest, UnexpectedArrayEndError) {
+ std::string json = "[1,2,]";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_UNEXPECTED_ARRAY_END, log_.status().error);
+ EXPECT_EQ(5u, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+TEST_F(JsonParserTest, CommaOrArrayEndExpectedError) {
+ std::string json = "[1,2 2";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED,
+ log_.status().error);
+ EXPECT_EQ(5u, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+TEST_F(JsonParserTest, StringLiteralExpectedError) {
+ // There's an error because the key bar, a string, is not terminated.
+ std::string json = "{\"foo\": 3.1415, \"bar: 31415e-4}";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_STRING_LITERAL_EXPECTED, log_.status().error);
+ EXPECT_EQ(16u, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+TEST_F(JsonParserTest, ColonExpectedError) {
+ std::string json = "{\"foo\", 42}";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_COLON_EXPECTED, log_.status().error);
+ EXPECT_EQ(6u, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+TEST_F(JsonParserTest, UnexpectedMapEndError) {
+ std::string json = "{\"foo\": 42, }";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_UNEXPECTED_MAP_END, log_.status().error);
+ EXPECT_EQ(12u, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+TEST_F(JsonParserTest, CommaOrMapEndExpectedError) {
+ // The second separator should be a comma.
+ std::string json = "{\"foo\": 3.1415: \"bar\": 0}";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_COMMA_OR_MAP_END_EXPECTED, log_.status().error);
+ EXPECT_EQ(14u, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+TEST_F(JsonParserTest, ValueExpectedError) {
+ std::string json = "}";
+ ParseJSON(GetTestPlatform(), SpanFrom(json), &log_);
+ EXPECT_EQ(Error::JSON_PARSER_VALUE_EXPECTED, log_.status().error);
+ EXPECT_EQ(0u, log_.status().pos);
+ EXPECT_EQ("", log_.str());
+}
+
+template <typename T>
+class ConvertJSONToCBORTest : public ::testing::Test {};
+
+using ContainerTestTypes = ::testing::Types<std::vector<uint8_t>, std::string>;
+TYPED_TEST_SUITE(ConvertJSONToCBORTest, ContainerTestTypes);
+
+TYPED_TEST(ConvertJSONToCBORTest, RoundTripValidJson) {
+ std::string json_in = "{\"msg\":\"Hello, world.\",\"lst\":[1,2,3]}";
+ TypeParam json(json_in.begin(), json_in.end());
+ TypeParam cbor;
+ {
+ Status status = ConvertJSONToCBOR(GetTestPlatform(), SpanFrom(json), &cbor);
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(Status::npos(), status.pos);
+ }
+ TypeParam roundtrip_json;
+ {
+ Status status =
+ ConvertCBORToJSON(GetTestPlatform(), SpanFrom(cbor), &roundtrip_json);
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(Status::npos(), status.pos);
+ }
+ EXPECT_EQ(json, roundtrip_json);
+}
+
+TYPED_TEST(ConvertJSONToCBORTest, RoundTripValidJson16) {
+ std::vector<uint16_t> json16 = {
+ '{', '"', 'm', 's', 'g', '"', ':', '"', 'H', 'e', 'l', 'l',
+ 'o', ',', ' ', 0xd83c, 0xdf0e, '.', '"', ',', '"', 'l', 's', 't',
+ '"', ':', '[', '1', ',', '2', ',', '3', ']', '}'};
+ TypeParam cbor;
+ {
+ Status status = ConvertJSONToCBOR(
+ GetTestPlatform(), span<uint16_t>(json16.data(), json16.size()), &cbor);
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(Status::npos(), status.pos);
+ }
+ TypeParam roundtrip_json;
+ {
+ Status status =
+ ConvertCBORToJSON(GetTestPlatform(), SpanFrom(cbor), &roundtrip_json);
+ EXPECT_EQ(Error::OK, status.error);
+ EXPECT_EQ(Status::npos(), status.pos);
+ }
+ std::string json = "{\"msg\":\"Hello, \\ud83c\\udf0e.\",\"lst\":[1,2,3]}";
+ TypeParam expected_json(json.begin(), json.end());
+ EXPECT_EQ(expected_json, roundtrip_json);
+}
+} // namespace json
+} // namespace v8_inspector_protocol_encoding