Commit 8abc3038 authored by Kouhei Ueno's avatar Kouhei Ueno Committed by Commit Bot

CBORReader: Add CBORReader::DecodeDataItemHeader()

This CL introduces *experimental* CBORReader::ReadDataItemHeader()
method which allows streaming consuming of a large CBOR-encoded array.

Since this API exposes CBORReader::DataItemHeader, which should conceptually be
encapsulated inside CBORReader, the method should not be used widely, and
should be replaced by event-based CBORReader API.

The method is really only for short-term use from SignedExchangeParser, which
definitely requires streaming parsing, but is currently unclear if it will
continue to use CBOR encoding in long term.

Bug: 803774, 811717
Change-Id: If37c70b034afcdecbe6e7793c95b4e1f60eee9d0
Reviewed-on: https://chromium-review.googlesource.com/920723
Commit-Queue: Kouhei Ueno <kouhei@chromium.org>
Reviewed-by: default avatarBalazs Engedy <engedy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#537721}
parent cf8cefd7
......@@ -55,13 +55,12 @@ const char kOutOfRangeIntegerValue[] =
} // namespace
CBORReader::CBORReader(base::span<const uint8_t>::const_iterator it,
CBORReader::CBORReader(base::span<const uint8_t>::const_iterator begin,
const base::span<const uint8_t>::const_iterator end)
: begin_(it),
it_(it),
: begin_(begin),
it_(begin),
end_(end),
error_code_(DecoderError::CBOR_NO_ERROR) {}
CBORReader::~CBORReader() {}
// static
......@@ -103,6 +102,26 @@ base::Optional<CBORValue> CBORReader::Read(base::span<uint8_t const> data,
return decoded_cbor;
}
// static
base::Optional<CBORReader::DataItemHeader> CBORReader::ReadDataItemHeader(
base::span<const uint8_t> data,
size_t* num_bytes_consumed,
DecoderError* error_code_out) {
CBORReader reader(data.cbegin(), data.cend());
base::Optional<DataItemHeader> decoded_header = reader.DecodeDataItemHeader();
if (error_code_out)
*error_code_out = reader.GetErrorCode();
if (reader.GetErrorCode() != DecoderError::CBOR_NO_ERROR) {
*num_bytes_consumed = 0;
return base::nullopt;
}
*num_bytes_consumed = reader.num_bytes_consumed();
return decoded_header;
}
base::Optional<CBORValue> CBORReader::DecodeCompleteDataItem(
int max_nesting_level) {
if (max_nesting_level < 0 || max_nesting_level > kCBORMaxDepth) {
......@@ -110,34 +129,25 @@ base::Optional<CBORValue> CBORReader::DecodeCompleteDataItem(
return base::nullopt;
}
if (!CanConsume(1)) {
error_code_ = DecoderError::INCOMPLETE_CBOR_DATA;
base::Optional<DataItemHeader> header = DecodeDataItemHeader();
if (!header.has_value())
return base::nullopt;
}
const uint8_t initial_byte = *it_++;
const auto major_type = GetMajorType(initial_byte);
const uint8_t additional_info = GetAdditionalInfo(initial_byte);
uint64_t value;
if (!ReadVariadicLengthInteger(additional_info, &value))
return base::nullopt;
switch (major_type) {
switch (header->type) {
case CBORValue::Type::UNSIGNED:
return DecodeValueToUnsigned(value);
return DecodeValueToUnsigned(header->value);
case CBORValue::Type::NEGATIVE:
return DecodeValueToNegative(value);
return DecodeValueToNegative(header->value);
case CBORValue::Type::BYTE_STRING:
return ReadBytes(value);
return ReadByteStringContent(*header);
case CBORValue::Type::STRING:
return ReadString(value);
return ReadStringContent(*header);
case CBORValue::Type::ARRAY:
return ReadCBORArray(value, max_nesting_level);
return ReadArrayContent(*header, max_nesting_level);
case CBORValue::Type::MAP:
return ReadCBORMap(value, max_nesting_level);
return ReadMapContent(*header, max_nesting_level);
case CBORValue::Type::SIMPLE_VALUE:
return ReadSimpleValue(additional_info, value);
return DecodeToSimpleValue(*header);
case CBORValue::Type::NONE:
break;
}
......@@ -146,6 +156,23 @@ base::Optional<CBORValue> CBORReader::DecodeCompleteDataItem(
return base::nullopt;
}
base::Optional<CBORReader::DataItemHeader> CBORReader::DecodeDataItemHeader() {
if (!CanConsume(1)) {
error_code_ = DecoderError::INCOMPLETE_CBOR_DATA;
return base::nullopt;
}
const uint8_t initial_byte = *it_++;
const auto major_type = GetMajorType(initial_byte);
const uint8_t additional_info = GetAdditionalInfo(initial_byte);
uint64_t value;
if (!ReadVariadicLengthInteger(additional_info, &value))
return base::nullopt;
return DataItemHeader{major_type, additional_info, value};
}
bool CBORReader::ReadVariadicLengthInteger(uint8_t additional_info,
uint64_t* value) {
uint8_t additional_bytes = 0;
......@@ -198,17 +225,17 @@ base::Optional<CBORValue> CBORReader::DecodeValueToUnsigned(uint64_t value) {
return CBORValue(unsigned_value.ValueOrDie());
}
base::Optional<CBORValue> CBORReader::ReadSimpleValue(uint8_t additional_info,
uint64_t value) {
base::Optional<CBORValue> CBORReader::DecodeToSimpleValue(
const DataItemHeader& header) {
// Floating point numbers are not supported.
if (additional_info > 24 && additional_info < 28) {
if (header.additional_info > 24 && header.additional_info < 28) {
error_code_ = DecoderError::UNSUPPORTED_FLOATING_POINT_VALUE;
return base::nullopt;
}
CHECK_LE(value, 255u);
CHECK_LE(header.value, 255u);
CBORValue::SimpleValue possibly_unsupported_simple_value =
static_cast<CBORValue::SimpleValue>(static_cast<int>(value));
static_cast<CBORValue::SimpleValue>(static_cast<int>(header.value));
switch (possibly_unsupported_simple_value) {
case CBORValue::SimpleValue::FALSE_VALUE:
case CBORValue::SimpleValue::TRUE_VALUE:
......@@ -221,7 +248,9 @@ base::Optional<CBORValue> CBORReader::ReadSimpleValue(uint8_t additional_info,
return base::nullopt;
}
base::Optional<CBORValue> CBORReader::ReadString(uint64_t num_bytes) {
base::Optional<CBORValue> CBORReader::ReadStringContent(
const CBORReader::DataItemHeader& header) {
uint64_t num_bytes = header.value;
if (!CanConsume(num_bytes)) {
error_code_ = DecoderError::INCOMPLETE_CBOR_DATA;
return base::nullopt;
......@@ -235,7 +264,9 @@ base::Optional<CBORValue> CBORReader::ReadString(uint64_t num_bytes) {
: base::nullopt;
}
base::Optional<CBORValue> CBORReader::ReadBytes(uint64_t num_bytes) {
base::Optional<CBORValue> CBORReader::ReadByteStringContent(
const CBORReader::DataItemHeader& header) {
uint64_t num_bytes = header.value;
if (!CanConsume(num_bytes)) {
error_code_ = DecoderError::INCOMPLETE_CBOR_DATA;
return base::nullopt;
......@@ -247,8 +278,10 @@ base::Optional<CBORValue> CBORReader::ReadBytes(uint64_t num_bytes) {
return CBORValue(std::move(cbor_byte_string));
}
base::Optional<CBORValue> CBORReader::ReadCBORArray(uint64_t length,
int max_nesting_level) {
base::Optional<CBORValue> CBORReader::ReadArrayContent(
const CBORReader::DataItemHeader& header,
int max_nesting_level) {
int64_t length = base::checked_cast<int64_t>(header.value);
CBORValue::ArrayValue cbor_array;
while (length-- > 0) {
base::Optional<CBORValue> cbor_element =
......@@ -260,8 +293,10 @@ base::Optional<CBORValue> CBORReader::ReadCBORArray(uint64_t length,
return CBORValue(std::move(cbor_array));
}
base::Optional<CBORValue> CBORReader::ReadCBORMap(uint64_t length,
int max_nesting_level) {
base::Optional<CBORValue> CBORReader::ReadMapContent(
const CBORReader::DataItemHeader& header,
int max_nesting_level) {
int64_t length = base::checked_cast<int64_t>(header.value);
CBORValue::MapValue cbor_map;
while (length-- > 0) {
base::Optional<CBORValue> key =
......
......@@ -69,6 +69,25 @@ class CBOR_EXPORT CBORReader {
OUT_OF_RANGE_INTEGER_VALUE,
};
// Encapsulates information extracted from the header of a CBOR data item,
// which consists of the initial byte, and a variable-length-encoded integer
// (if any).
//
// TODO(crbug.com/811717): This is an CBORReader internal detail which should
// not be exposed outside. We should switch to an event-based interface
// before adding another customer.
struct CBOR_EXPORT DataItemHeader {
// The major type decoded from the initial byte.
CBORValue::Type type;
// The raw 5-bit additional information from the initial byte.
uint8_t additional_info;
// The integer |value| decoded from the |additional_info| and the
// variable-length-encoded integer, if any.
uint64_t value;
};
// CBOR nested depth sufficient for most use cases.
static const int kCBORMaxDepth = 16;
......@@ -93,23 +112,34 @@ class CBOR_EXPORT CBORReader {
DecoderError* error_code_out = nullptr,
int max_nesting_level = kCBORMaxDepth);
// Reads and parses the header of CBOR data item from |input_data|. Optional
// |error_code_out| can be provided by the caller to obtain additional
// information about decoding failures. Never fails with EXTRANEOUS_DATA, but
// informs the caller of how many bytes were consumed through
// |num_bytes_consumed|.
static base::Optional<DataItemHeader> ReadDataItemHeader(
base::span<const uint8_t> input_data,
size_t* num_bytes_consumed = nullptr,
DecoderError* error_code_out = nullptr);
// Translates errors to human-readable error messages.
static const char* ErrorCodeToString(DecoderError error_code);
private:
CBORReader(base::span<const uint8_t>::const_iterator it,
const base::span<const uint8_t>::const_iterator end);
base::Optional<DataItemHeader> DecodeDataItemHeader();
base::Optional<CBORValue> DecodeCompleteDataItem(int max_nesting_level);
base::Optional<CBORValue> DecodeValueToNegative(uint64_t value);
base::Optional<CBORValue> DecodeValueToUnsigned(uint64_t value);
base::Optional<CBORValue> ReadSimpleValue(uint8_t additional_info,
uint64_t value);
base::Optional<CBORValue> DecodeToSimpleValue(const DataItemHeader& header);
bool ReadVariadicLengthInteger(uint8_t additional_info, uint64_t* value);
base::Optional<CBORValue> ReadBytes(uint64_t num_bytes);
base::Optional<CBORValue> ReadString(uint64_t num_bytes);
base::Optional<CBORValue> ReadCBORArray(uint64_t length,
int max_nesting_level);
base::Optional<CBORValue> ReadCBORMap(uint64_t length, int max_nesting_level);
base::Optional<CBORValue> ReadByteStringContent(const DataItemHeader& header);
base::Optional<CBORValue> ReadStringContent(const DataItemHeader& header);
base::Optional<CBORValue> ReadArrayContent(const DataItemHeader& header,
int max_nesting_level);
base::Optional<CBORValue> ReadMapContent(const DataItemHeader& header,
int max_nesting_level);
bool CanConsume(uint64_t bytes);
void CheckExtraneousData();
bool CheckDuplicateKey(const CBORValue& new_key, CBORValue::MapValue* map);
......
......@@ -27,6 +27,92 @@ std::vector<uint8_t> WithExtraneousData(base::span<const uint8_t> original) {
} // namespace
TEST(CBORReaderTest, TestDecodeDataItemHeader) {
static const struct {
CBORReader::DataItemHeader expected_header;
const std::vector<uint8_t> cbor_data;
} kTestCases[] = {
{{CBORValue::Type::UNSIGNED, 0, 0}, {0x00}},
{{CBORValue::Type::UNSIGNED, 24, 24}, {0x18, 0x18}},
{{CBORValue::Type::UNSIGNED, 25, 12345}, {0x19, 0x30, 0x39}},
{{CBORValue::Type::UNSIGNED, 27, 1234567890123456789ull},
{0x1B, 0x11, 0x22, 0x10, 0xF4, 0x7D, 0xE9, 0x81, 0x15}},
{{CBORValue::Type::NEGATIVE, 24, 255}, {0x38, 0xff}},
{{CBORValue::Type::NEGATIVE, 26, 12345677},
{0x3a, 0x00, 0xbc, 0x61, 0x4d}},
{{CBORValue::Type::BYTE_STRING, 4, 4}, {0x44}},
{{CBORValue::Type::STRING, 3, 3}, {0x63}},
{{CBORValue::Type::ARRAY, 24, 25}, {0x98, 0x19}},
{{CBORValue::Type::MAP, 4, 4}, {0xa4}},
{{CBORValue::Type::SIMPLE_VALUE,
static_cast<uint8_t>(CBORValue::SimpleValue::FALSE_VALUE), 20},
{0xf4}},
};
int test_element_index = 0;
for (const auto& test_case : kTestCases) {
SCOPED_TRACE(testing::Message() << "testing case " << test_element_index++);
size_t consumed_bytes;
CBORReader::DecoderError error_code;
base::Optional<CBORReader::DataItemHeader> header =
CBORReader::ReadDataItemHeader(test_case.cbor_data, &consumed_bytes,
&error_code);
ASSERT_TRUE(header.has_value());
EXPECT_EQ(header->type, test_case.expected_header.type);
EXPECT_EQ(header->additional_info,
test_case.expected_header.additional_info);
EXPECT_EQ(header->value, test_case.expected_header.value);
EXPECT_EQ(consumed_bytes, test_case.cbor_data.size());
EXPECT_EQ(error_code, CBORReader::DecoderError::CBOR_NO_ERROR);
auto cbor_data_with_extra_byte = WithExtraneousData(test_case.cbor_data);
header = CBORReader::ReadDataItemHeader(cbor_data_with_extra_byte,
&consumed_bytes, &error_code);
ASSERT_TRUE(header.has_value());
EXPECT_EQ(header->type, test_case.expected_header.type);
EXPECT_EQ(header->additional_info,
test_case.expected_header.additional_info);
EXPECT_EQ(header->value, test_case.expected_header.value);
EXPECT_EQ(consumed_bytes, test_case.cbor_data.size());
EXPECT_EQ(error_code, CBORReader::DecoderError::CBOR_NO_ERROR);
}
}
TEST(CBORReaderTest, TestDecodeIncompleteDataItemHeader) {
static const std::vector<uint8_t> kTestCases[] = {
// clang-format off
{0x18}, // unsigned with pending 1 byte of numeric value.
{0x99}, // array with pending 2 byte of numeric value (length).
{0xba}, // map with pending 4 byte of numeric value (length).
{0x5b}, // byte string with pending 4 byte of numeric value (length).
{0x3b}, // negative integer with pending 8 byte of numeric value.
{0x99, 0x01}, // array with pending 2 byte of numeric value (length),
// with only 1 byte of additional data.
{0xba, 0x01, 0x02, 0x03}, // map with pending 4 byte of numeric value
// (length), with only 3 bytes of additional
// data.
{0x3b, 0x01, 0x02, 0x03,
0x04, 0x05, 0x06, 0x07}, // negative integer with pending 8 byte of
// numeric value, with only 7 bytes of
// additional data.
// clang-format on
};
int test_element_index = 0;
for (const auto& test_case : kTestCases) {
SCOPED_TRACE(testing::Message() << "testing case " << test_element_index++);
size_t consumed_bytes;
CBORReader::DecoderError error_code;
base::Optional<CBORReader::DataItemHeader> header =
CBORReader::ReadDataItemHeader(test_case, &consumed_bytes, &error_code);
ASSERT_FALSE(header.has_value());
EXPECT_EQ(consumed_bytes, 0u);
EXPECT_EQ(error_code, CBORReader::DecoderError::INCOMPLETE_CBOR_DATA);
}
}
TEST(CBORReaderTest, TestReadUint) {
struct UintTestCase {
const int64_t value;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment