blob: ca6db6f30d3a746fecdcdfc0ad874a65d1836cad [file] [log] [blame]
// Copyright 2017 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 "components/cbor/writer.h"
#include <limits>
#include <string>
#include "base/cxx17_backports.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
/* Leveraging RFC 7049 examples from
https://github.com/cbor/test-vectors/blob/master/appendix_a.json. */
namespace cbor {
TEST(CBORWriterTest, TestWriteUint) {
struct UintTestCase {
const int64_t value;
const base::StringPiece cbor;
};
static const UintTestCase kUintTestCases[] = {
// Reminder: must specify length when creating string pieces
// with null bytes, else the string will truncate prematurely.
{0, base::StringPiece("\x00", 1)},
{1, base::StringPiece("\x01")},
{10, base::StringPiece("\x0a")},
{23, base::StringPiece("\x17")},
{24, base::StringPiece("\x18\x18")},
{25, base::StringPiece("\x18\x19")},
{100, base::StringPiece("\x18\x64")},
{1000, base::StringPiece("\x19\x03\xe8")},
{1000000, base::StringPiece("\x1a\x00\x0f\x42\x40", 5)},
{0xFFFFFFFF, base::StringPiece("\x1a\xff\xff\xff\xff")},
{0x100000000,
base::StringPiece("\x1b\x00\x00\x00\x01\x00\x00\x00\x00", 9)},
{std::numeric_limits<int64_t>::max(),
base::StringPiece("\x1b\x7f\xff\xff\xff\xff\xff\xff\xff")}};
for (const UintTestCase& test_case : kUintTestCases) {
auto cbor = Writer::Write(Value(test_case.value));
ASSERT_TRUE(cbor.has_value());
EXPECT_THAT(cbor.value(), testing::ElementsAreArray(test_case.cbor));
}
}
TEST(CBORWriterTest, TestWriteNegativeInteger) {
static const struct {
const int64_t negative_int;
const base::StringPiece cbor;
} kNegativeIntTestCases[] = {
{-1LL, base::StringPiece("\x20")},
{-10LL, base::StringPiece("\x29")},
{-23LL, base::StringPiece("\x36")},
{-24LL, base::StringPiece("\x37")},
{-25LL, base::StringPiece("\x38\x18")},
{-100LL, base::StringPiece("\x38\x63")},
{-1000LL, base::StringPiece("\x39\x03\xe7")},
{-4294967296LL, base::StringPiece("\x3a\xff\xff\xff\xff")},
{-4294967297LL,
base::StringPiece("\x3b\x00\x00\x00\x01\x00\x00\x00\x00", 9)},
{std::numeric_limits<int64_t>::min(),
base::StringPiece("\x3b\x7f\xff\xff\xff\xff\xff\xff\xff")},
};
for (const auto& test_case : kNegativeIntTestCases) {
SCOPED_TRACE(testing::Message() << "testing negative int at index: "
<< test_case.negative_int);
auto cbor = Writer::Write(Value(test_case.negative_int));
ASSERT_TRUE(cbor.has_value());
EXPECT_THAT(cbor.value(), testing::ElementsAreArray(test_case.cbor));
}
}
TEST(CBORWriterTest, TestWriteBytes) {
struct BytesTestCase {
const std::vector<uint8_t> bytes;
const base::StringPiece cbor;
};
static const BytesTestCase kBytesTestCases[] = {
{{}, base::StringPiece("\x40")},
{{0x01, 0x02, 0x03, 0x04}, base::StringPiece("\x44\x01\x02\x03\x04")},
};
for (const BytesTestCase& test_case : kBytesTestCases) {
auto cbor = Writer::Write(Value(test_case.bytes));
ASSERT_TRUE(cbor.has_value());
EXPECT_THAT(cbor.value(), testing::ElementsAreArray(test_case.cbor));
}
}
TEST(CBORWriterTest, TestWriteString) {
struct StringTestCase {
const std::string string;
const base::StringPiece cbor;
};
static const StringTestCase kStringTestCases[] = {
{"", base::StringPiece("\x60")},
{"a", base::StringPiece("\x61\x61")},
{"IETF", base::StringPiece("\x64\x49\x45\x54\x46")},
{"\"\\", base::StringPiece("\x62\x22\x5c")},
{"\xc3\xbc", base::StringPiece("\x62\xc3\xbc")},
{"\xe6\xb0\xb4", base::StringPiece("\x63\xe6\xb0\xb4")},
{"\xf0\x90\x85\x91", base::StringPiece("\x64\xf0\x90\x85\x91")}};
for (const StringTestCase& test_case : kStringTestCases) {
SCOPED_TRACE(testing::Message()
<< "testing encoding string : " << test_case.string);
auto cbor = Writer::Write(Value(test_case.string));
ASSERT_TRUE(cbor.has_value());
EXPECT_THAT(cbor.value(), testing::ElementsAreArray(test_case.cbor));
}
}
TEST(CBORWriterTest, TestWriteArray) {
static const uint8_t kArrayTestCaseCbor[] = {
// clang-format off
0x98, 0x19, // array of 25 elements
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
0x18, 0x18, 0x19,
// clang-format on
};
std::vector<Value> array;
for (int64_t i = 1; i <= 25; i++) {
array.push_back(Value(i));
}
auto cbor = Writer::Write(Value(array));
ASSERT_TRUE(cbor.has_value());
EXPECT_THAT(cbor.value(),
testing::ElementsAreArray(kArrayTestCaseCbor,
base::size(kArrayTestCaseCbor)));
}
TEST(CBORWriterTest, TestWriteMap) {
static const uint8_t kMapTestCaseCbor[] = {
// clang-format off
0xb8, 0x19, // map of 25 pairs:
0x00, // key 0
0x61, 0x61, // value "a"
0x17, // key 23
0x61, 0x62, // value "b"
0x18, 0x18, // key 24
0x61, 0x63, // value "c"
0x18, 0xFF, // key 255
0x61, 0x64, // value "d"
0x19, 0x01, 0x00, // key 256
0x61, 0x65, // value "e"
0x19, 0xFF, 0xFF, // key 65535
0x61, 0x66, // value "f"
0x1A, 0x00, 0x01, 0x00, 0x00, // key 65536
0x61, 0x67, // value "g"
0x1A, 0xFF, 0xFF, 0xFF, 0xFF, // key 4294967295
0x61, 0x68, // value "h"
// key 4294967296
0x1B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x61, 0x69, // value "i"
// key INT64_MAX
0x1b, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x61, 0x6a, // value "j"
0x20, // key -1
0x61, 0x6b, // value "k"
0x37, // key -24
0x61, 0x6c, // value "l"
0x38, 0x18, // key -25
0x61, 0x6d, // value "m"
0x38, 0xFF, // key -256
0x61, 0x6e, // value "n"
0x39, 0x01, 0x00, // key -257
0x61, 0x6f, // value "o"
0x3A, 0x00, 0x01, 0x00, 0x00, // key -65537
0x61, 0x70, // value "p"
0x3A, 0xFF, 0xFF, 0xFF, 0xFF, // key -4294967296
0x61, 0x71, // value "q"
// key -4294967297
0x3B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x61, 0x72, // value "r"
// key INT64_MIN
0x3b, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x61, 0x73, // value "s"
0x41, 'a', // byte string "a"
0x02,
0x43, 'b', 'a', 'r', // byte string "bar"
0x03,
0x43, 'f', 'o', 'o', // byte string "foo"
0x04,
0x60, // key ""
0x61, 0x2e, // value "."
0x61, 0x65, // key "e"
0x61, 0x45, // value "E"
0x62, 0x61, 0x61, // key "aa"
0x62, 0x41, 0x41, // value "AA"
// clang-format on
};
Value::MapValue map;
// Shorter strings sort first in CTAP, thus the “aa” value should be
// serialised last in the map.
map[Value("aa")] = Value("AA");
map[Value("e")] = Value("E");
// The empty string is shorter than all others, so should appear first among
// the strings.
map[Value("")] = Value(".");
// Map keys are sorted by major type, by byte length, and then by
// byte-wise lexical order. So all integer type keys should appear before
// key "" and all positive integer keys should appear before negative integer
// keys.
map[Value(-1)] = Value("k");
map[Value(-24)] = Value("l");
map[Value(-25)] = Value("m");
map[Value(-256)] = Value("n");
map[Value(-257)] = Value("o");
map[Value(-65537)] = Value("p");
map[Value(int64_t(-4294967296))] = Value("q");
map[Value(int64_t(-4294967297))] = Value("r");
map[Value(std::numeric_limits<int64_t>::min())] = Value("s");
map[Value(Value::BinaryValue{'a'})] = Value(2);
map[Value(Value::BinaryValue{'b', 'a', 'r'})] = Value(3);
map[Value(Value::BinaryValue{'f', 'o', 'o'})] = Value(4);
map[Value(0)] = Value("a");
map[Value(23)] = Value("b");
map[Value(24)] = Value("c");
map[Value(std::numeric_limits<uint8_t>::max())] = Value("d");
map[Value(256)] = Value("e");
map[Value(std::numeric_limits<uint16_t>::max())] = Value("f");
map[Value(65536)] = Value("g");
map[Value(int64_t(std::numeric_limits<uint32_t>::max()))] = Value("h");
map[Value(int64_t(4294967296))] = Value("i");
map[Value(std::numeric_limits<int64_t>::max())] = Value("j");
auto cbor = Writer::Write(Value(map));
ASSERT_TRUE(cbor.has_value());
EXPECT_THAT(cbor.value(),
testing::ElementsAreArray(kMapTestCaseCbor,
base::size(kMapTestCaseCbor)));
}
TEST(CBORWriterTest, TestWriteMapWithArray) {
static const uint8_t kMapArrayTestCaseCbor[] = {
// clang-format off
0xa2, // map of 2 pairs
0x61, 0x61, // "a"
0x01,
0x61, 0x62, // "b"
0x82, // array with 2 elements
0x02,
0x03,
// clang-format on
};
Value::MapValue map;
map[Value("a")] = Value(1);
Value::ArrayValue array;
array.push_back(Value(2));
array.push_back(Value(3));
map[Value("b")] = Value(array);
auto cbor = Writer::Write(Value(map));
ASSERT_TRUE(cbor.has_value());
EXPECT_THAT(cbor.value(),
testing::ElementsAreArray(kMapArrayTestCaseCbor,
base::size(kMapArrayTestCaseCbor)));
}
TEST(CBORWriterTest, TestWriteNestedMap) {
static const uint8_t kNestedMapTestCase[] = {
// clang-format off
0xa2, // map of 2 pairs
0x61, 0x61, // "a"
0x01,
0x61, 0x62, // "b"
0xa2, // map of 2 pairs
0x61, 0x63, // "c"
0x02,
0x61, 0x64, // "d"
0x03,
// clang-format on
};
Value::MapValue map;
map[Value("a")] = Value(1);
Value::MapValue nested_map;
nested_map[Value("c")] = Value(2);
nested_map[Value("d")] = Value(3);
map[Value("b")] = Value(nested_map);
auto cbor = Writer::Write(Value(map));
ASSERT_TRUE(cbor.has_value());
EXPECT_THAT(cbor.value(),
testing::ElementsAreArray(kNestedMapTestCase,
base::size(kNestedMapTestCase)));
}
TEST(CBORWriterTest, TestSignedExchangeExample) {
// Example adopted from:
// https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html
static const uint8_t kSignedExchangeExample[] = {
// clang-format off
0xa5, // map of 5 pairs
0x0a, // 10
0x01,
0x18, 0x64, // 100
0x02,
0x20, // -1
0x03,
0x61, 'z', // text string "z"
0x04,
0x62, 'a', 'a', // text string "aa"
0x05,
/*
0x81, 0x18, 0x64, // [100] (array as map key is not yet supported)
0x06,
0x81, 0x20, // [-1] (array as map key is not yet supported)
0x07,
0xf4, // false (boolean as map key is not yet supported)
0x08,
*/
// clang-format on
};
Value::MapValue map;
map[Value(10)] = Value(1);
map[Value(100)] = Value(2);
map[Value(-1)] = Value(3);
map[Value("z")] = Value(4);
map[Value("aa")] = Value(5);
auto cbor = Writer::Write(Value(map));
ASSERT_TRUE(cbor.has_value());
EXPECT_THAT(cbor.value(),
testing::ElementsAreArray(kSignedExchangeExample,
base::size(kSignedExchangeExample)));
}
TEST(CBORWriterTest, TestWriteSimpleValue) {
static const struct {
Value::SimpleValue simple_value;
const base::StringPiece cbor;
} kSimpleTestCase[] = {
{Value::SimpleValue::FALSE_VALUE, base::StringPiece("\xf4")},
{Value::SimpleValue::TRUE_VALUE, base::StringPiece("\xf5")},
{Value::SimpleValue::NULL_VALUE, base::StringPiece("\xf6")},
{Value::SimpleValue::UNDEFINED, base::StringPiece("\xf7")}};
for (const auto& test_case : kSimpleTestCase) {
auto cbor = Writer::Write(Value(test_case.simple_value));
ASSERT_TRUE(cbor.has_value());
EXPECT_THAT(cbor.value(), testing::ElementsAreArray(test_case.cbor));
}
}
// For major type 0, 2, 3, empty CBOR array, and empty CBOR map, the nesting
// depth is expected to be 0 since the CBOR decoder does not need to parse
// any nested CBOR value elements.
TEST(CBORWriterTest, TestWriteSingleLayer) {
const Value simple_uint = Value(1);
const Value simple_string = Value("a");
const std::vector<uint8_t> byte_data = {0x01, 0x02, 0x03, 0x04};
const Value simple_bytestring = Value(byte_data);
Value::ArrayValue empty_cbor_array;
Value::MapValue empty_cbor_map;
const Value empty_array_value = Value(empty_cbor_array);
const Value empty_map_value = Value(empty_cbor_map);
Value::ArrayValue simple_array;
simple_array.push_back(Value(2));
Value::MapValue simple_map;
simple_map[Value("b")] = Value(3);
const Value single_layer_cbor_map = Value(simple_map);
const Value single_layer_cbor_array = Value(simple_array);
EXPECT_TRUE(Writer::Write(simple_uint, 0).has_value());
EXPECT_TRUE(Writer::Write(simple_string, 0).has_value());
EXPECT_TRUE(Writer::Write(simple_bytestring, 0).has_value());
EXPECT_TRUE(Writer::Write(empty_array_value, 0).has_value());
EXPECT_TRUE(Writer::Write(empty_map_value, 0).has_value());
EXPECT_FALSE(Writer::Write(single_layer_cbor_array, 0).has_value());
EXPECT_TRUE(Writer::Write(single_layer_cbor_array, 1).has_value());
EXPECT_FALSE(Writer::Write(single_layer_cbor_map, 0).has_value());
EXPECT_TRUE(Writer::Write(single_layer_cbor_map, 1).has_value());
}
// Major type 5 nested CBOR map value with following structure.
// {"a": 1,
// "b": {"c": 2,
// "d": 3}}
TEST(CBORWriterTest, NestedMaps) {
Value::MapValue cbor_map;
cbor_map[Value("a")] = Value(1);
Value::MapValue nested_map;
nested_map[Value("c")] = Value(2);
nested_map[Value("d")] = Value(3);
cbor_map[Value("b")] = Value(nested_map);
EXPECT_TRUE(Writer::Write(Value(cbor_map), 2).has_value());
EXPECT_FALSE(Writer::Write(Value(cbor_map), 1).has_value());
}
// Testing Write() function for following CBOR structure with depth of 3.
// [1,
// 2,
// 3,
// {"a": 1,
// "b": {"c": 2,
// "d": 3}}]
TEST(CBORWriterTest, UnbalancedNestedContainers) {
Value::ArrayValue cbor_array;
Value::MapValue cbor_map;
Value::MapValue nested_map;
cbor_map[Value("a")] = Value(1);
nested_map[Value("c")] = Value(2);
nested_map[Value("d")] = Value(3);
cbor_map[Value("b")] = Value(nested_map);
cbor_array.push_back(Value(1));
cbor_array.push_back(Value(2));
cbor_array.push_back(Value(3));
cbor_array.push_back(Value(cbor_map));
EXPECT_TRUE(Writer::Write(Value(cbor_array), 3).has_value());
EXPECT_FALSE(Writer::Write(Value(cbor_array), 2).has_value());
}
// Testing Write() function for following CBOR structure.
// {"a": 1,
// "b": {"c": 2,
// "d": 3
// "h": { "e": 4,
// "f": 5,
// "g": [6, 7, [8]]}}}
// Since above CBOR contains 5 nesting levels. Thus, Write() is expected to
// return empty optional object when maximum nesting layer size is set to 4.
TEST(CBORWriterTest, OverlyNestedCBOR) {
Value::MapValue map;
Value::MapValue nested_map;
Value::MapValue inner_nested_map;
Value::ArrayValue inner_array;
Value::ArrayValue array;
map[Value("a")] = Value(1);
nested_map[Value("c")] = Value(2);
nested_map[Value("d")] = Value(3);
inner_nested_map[Value("e")] = Value(4);
inner_nested_map[Value("f")] = Value(5);
inner_array.push_back(Value(6));
array.push_back(Value(6));
array.push_back(Value(7));
array.push_back(Value(inner_array));
inner_nested_map[Value("g")] = Value(array);
nested_map[Value("h")] = Value(inner_nested_map);
map[Value("b")] = Value(nested_map);
EXPECT_TRUE(Writer::Write(Value(map), 5).has_value());
EXPECT_FALSE(Writer::Write(Value(map), 4).has_value());
}
} // namespace cbor