Commit dc398ef7 authored by David Van Cleve's avatar David Van Cleve Committed by Commit Bot

Adds length-prefixed read to BigEndianReader

A common task when parsing encoded structs from a network is to
read a serialization of an object with (possibly nested) length-prefixed
members.

//third_party/boringssl's Crypto ByteString bakes
support for this in by having a "length-prefixed read" API that
initially reads a big-endian length L from the buffer, then constructs
and returns a new reader on the L many bytes immediately following
the length, skipping the parent reader over this substring. However,
this API is C-style and, as a consequence, perhaps not suitable for
wide use in Chromium code.

This patch adds similar functionality to base::BigEndianReader
by adding ReadU8LengthPrefixed and ReadU16LengthPrefixed methods.

R=mark

Change-Id: Idaf0b04b414a3484099a861ef02501e5f8a83a47
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1860518
Commit-Queue: David Van Cleve <davidvc@chromium.org>
Reviewed-by: default avatarMark Mentovai <mark@chromium.org>
Cr-Commit-Position: refs/heads/master@{#706074}
parent e36b812d
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "base/big_endian.h" #include "base/big_endian.h"
#include "base/numerics/checked_math.h"
#include "base/strings/string_piece.h" #include "base/strings/string_piece.h"
namespace base { namespace base {
...@@ -59,6 +60,29 @@ bool BigEndianReader::ReadU64(uint64_t* value) { ...@@ -59,6 +60,29 @@ bool BigEndianReader::ReadU64(uint64_t* value) {
return Read(value); return Read(value);
} }
template <typename T>
bool BigEndianReader::ReadLengthPrefixed(base::StringPiece* out) {
T t_len;
if (!Read(&t_len))
return false;
size_t len = strict_cast<size_t>(t_len);
const char* original_ptr = ptr_;
if (!Skip(len)) {
ptr_ -= sizeof(T);
return false;
}
*out = base::StringPiece(original_ptr, len);
return true;
}
bool BigEndianReader::ReadU8LengthPrefixed(base::StringPiece* out) {
return ReadLengthPrefixed<uint8_t>(out);
}
bool BigEndianReader::ReadU16LengthPrefixed(base::StringPiece* out) {
return ReadLengthPrefixed<uint16_t>(out);
}
BigEndianWriter::BigEndianWriter(char* buf, size_t len) BigEndianWriter::BigEndianWriter(char* buf, size_t len)
: ptr_(buf), end_(ptr_ + len) {} : ptr_(buf), end_(ptr_ + len) {}
......
...@@ -67,10 +67,26 @@ class BASE_EXPORT BigEndianReader { ...@@ -67,10 +67,26 @@ class BASE_EXPORT BigEndianReader {
bool ReadU32(uint32_t* value); bool ReadU32(uint32_t* value);
bool ReadU64(uint64_t* value); bool ReadU64(uint64_t* value);
// Reads a length-prefixed region:
// 1. reads a big-endian length L from the buffer;
// 2. sets |*out| to a StringPiece over the next L many bytes
// of the buffer (beyond the end of the bytes encoding the length); and
// 3. skips the main reader past this L-byte substring.
//
// Fails if reading a U8 or U16 fails, or if the parsed length is greater
// than the number of bytes remaining in the stream.
//
// On failure, leaves the stream at the same position
// as before the call.
bool ReadU8LengthPrefixed(base::StringPiece* out);
bool ReadU16LengthPrefixed(base::StringPiece* out);
private: private:
// Hidden to promote type safety. // Hidden to promote type safety.
template<typename T> template<typename T>
bool Read(T* v); bool Read(T* v);
template <typename T>
bool ReadLengthPrefixed(base::StringPiece* out);
const char* ptr_; const char* ptr_;
const char* end_; const char* end_;
......
...@@ -42,6 +42,64 @@ TEST(BigEndianReaderTest, ReadsValues) { ...@@ -42,6 +42,64 @@ TEST(BigEndianReaderTest, ReadsValues) {
EXPECT_EQ(expected.data(), piece.data()); EXPECT_EQ(expected.data(), piece.data());
} }
TEST(BigEndianReaderTest, ReadsLengthPrefixedValues) {
{
char u8_prefixed_data[] = {8, 8, 9, 0xA, 0xB, 0xC, 0xD,
0xE, 0xF, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E};
BigEndianReader reader(u8_prefixed_data, sizeof(u8_prefixed_data));
base::StringPiece piece;
ASSERT_TRUE(reader.ReadU8LengthPrefixed(&piece));
// |reader| should skip both a u8 and the length-8 length-prefixed field.
EXPECT_EQ(reader.ptr(), u8_prefixed_data + 9);
EXPECT_EQ(piece.size(), 8u);
EXPECT_EQ(piece.data(), u8_prefixed_data + 1);
}
{
char u16_prefixed_data[] = {0, 8, 0xD, 0xE, 0xF,
0x1A, 0x2B, 0x3C, 0x4D, 0x5E};
BigEndianReader reader(u16_prefixed_data, sizeof(u16_prefixed_data));
base::StringPiece piece;
ASSERT_TRUE(reader.ReadU16LengthPrefixed(&piece));
// |reader| should skip both a u16 and the length-8 length-prefixed field.
EXPECT_EQ(reader.ptr(), u16_prefixed_data + 10);
EXPECT_EQ(piece.size(), 8u);
EXPECT_EQ(piece.data(), u16_prefixed_data + 2);
// With no data left, we shouldn't be able to
// read another u8 length prefix (or a u16 length prefix,
// for that matter).
EXPECT_FALSE(reader.ReadU8LengthPrefixed(&piece));
EXPECT_FALSE(reader.ReadU16LengthPrefixed(&piece));
}
{
// Make sure there's no issue reading a zero-value length prefix.
char u16_prefixed_data[3] = {};
BigEndianReader reader(u16_prefixed_data, sizeof(u16_prefixed_data));
base::StringPiece piece;
ASSERT_TRUE(reader.ReadU16LengthPrefixed(&piece));
EXPECT_EQ(reader.ptr(), u16_prefixed_data + 2);
EXPECT_EQ(piece.data(), u16_prefixed_data + 2);
EXPECT_EQ(piece.size(), 0u);
}
}
TEST(BigEndianReaderTest, LengthPrefixedReadsFailGracefully) {
// We can't read 0xF (or, for that matter, 0xF8) bytes after the length
// prefix: there isn't enough data.
char data[] = {0xF, 8, 9, 0xA, 0xB, 0xC, 0xD,
0xE, 0xF, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E};
BigEndianReader reader(data, sizeof(data));
base::StringPiece piece;
EXPECT_FALSE(reader.ReadU8LengthPrefixed(&piece));
EXPECT_EQ(data, reader.ptr());
EXPECT_FALSE(reader.ReadU16LengthPrefixed(&piece));
EXPECT_EQ(data, reader.ptr());
}
TEST(BigEndianReaderTest, RespectsLength) { TEST(BigEndianReaderTest, RespectsLength) {
char data[8]; char data[8];
char buf[2]; char buf[2];
......
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