Commit dec192fd authored by Andrey Kosyakov's avatar Andrey Kosyakov Committed by V8 LUCI CQ

Roll inspector_protocol to 87e75896dcfcafda7869b0c9714db9b6cdc4c765

This lets us accept spec-compliant CBOR tag for message envelopes.

This also includes a change in v8-inspector-session-impl.cc that
relaxes an envelope check to allow spec-compliant envelopes.

Change-Id: Id77c1e0fc4b62d78e8580f81ef38d50e3eb54a1d
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3662540Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Commit-Queue: Andrey Kosyakov <caseq@chromium.org>
Cr-Commit-Position: refs/heads/main@{#80761}
parent c4d09aef
......@@ -34,8 +34,10 @@ using v8_crdtp::json::ConvertCBORToJSON;
using v8_crdtp::json::ConvertJSONToCBOR;
bool IsCBORMessage(StringView msg) {
return msg.is8Bit() && msg.length() >= 2 && msg.characters8()[0] == 0xd8 &&
msg.characters8()[1] == 0x5a;
if (!msg.is8Bit() || msg.length() < 3) return false;
const uint8_t* bytes = msg.characters8();
return bytes[0] == 0xd8 &&
(bytes[1] == 0x5a || (bytes[1] == 0x18 && bytes[2] == 0x5a));
}
Status ConvertToCBOR(StringView state, std::vector<uint8_t>* cbor) {
......
......@@ -2,7 +2,7 @@ Name: inspector protocol
Short Name: inspector_protocol
URL: https://chromium.googlesource.com/deps/inspector_protocol/
Version: 0
Revision: 817313aa48ebb9a53cba1bd88bbe6a1c5048060c
Revision: 87e75896dcfcafda7869b0c9714db9b6cdc4c765
License: BSD
License File: LICENSE
Security Critical: no
......
......@@ -46,7 +46,13 @@ constexpr uint8_t EncodeInitialByte(MajorType type, uint8_t additional_info) {
// byte string carries its size (byte length).
// https://tools.ietf.org/html/rfc7049#section-2.4.4.1
static constexpr uint8_t kInitialByteForEnvelope =
EncodeInitialByte(MajorType::TAG, 24);
EncodeInitialByte(MajorType::TAG, kAdditionalInformation1Byte);
// The standalone byte for "envelope" tag, to follow kInitialByteForEnvelope
// in the correct implementation, as it is above in-tag value max (which is
// also, confusingly, 24). See EnvelopeHeader::Parse() for more.
static constexpr uint8_t kCBOREnvelopeTag = 24;
// The initial byte for a byte string with at most 2^32 bytes
// of payload. This is used for envelope encoding, even if
// the byte string is shorter.
......@@ -190,30 +196,25 @@ void WriteTokenStart(MajorType type,
// Detecting CBOR content
// =============================================================================
uint8_t InitialByteForEnvelope() {
return kInitialByteForEnvelope;
}
uint8_t InitialByteFor32BitLengthByteString() {
return kInitialByteFor32BitLengthByteString;
}
bool IsCBORMessage(span<uint8_t> msg) {
return msg.size() >= 6 && msg[0] == InitialByteForEnvelope() &&
msg[1] == InitialByteFor32BitLengthByteString();
return msg.size() >= 4 && msg[0] == kInitialByteForEnvelope &&
(msg[1] == kInitialByteFor32BitLengthByteString ||
(msg[1] == kCBOREnvelopeTag &&
msg[2] == kInitialByteFor32BitLengthByteString));
}
Status CheckCBORMessage(span<uint8_t> msg) {
if (msg.empty())
return Status(Error::CBOR_NO_INPUT, 0);
if (msg[0] != InitialByteForEnvelope())
return Status(Error::CBOR_UNEXPECTED_EOF_IN_ENVELOPE, 0);
if (msg[0] != kInitialByteForEnvelope)
return Status(Error::CBOR_INVALID_START_BYTE, 0);
if (msg.size() < 6 || msg[1] != InitialByteFor32BitLengthByteString())
return Status(Error::CBOR_INVALID_ENVELOPE, 1);
if (msg[2] == 0 && msg[3] == 0 && msg[4] == 0 && msg[5] == 0)
return Status(Error::CBOR_INVALID_ENVELOPE, 1);
if (msg.size() < 7 || msg[6] != EncodeIndefiniteLengthMapStart())
return Status(Error::CBOR_MAP_START_EXPECTED, 6);
StatusOr<EnvelopeHeader> status_or_header = EnvelopeHeader::Parse(msg);
if (!status_or_header.ok())
return status_or_header.status();
const size_t pos = (*status_or_header).header_size();
assert(pos < msg.size()); // EnvelopeParser would not allow empty envelope.
if (msg[pos] != EncodeIndefiniteLengthMapStart())
return Status(Error::CBOR_MAP_START_EXPECTED, pos);
return Status();
}
......@@ -324,11 +325,6 @@ void EncodeBinary(span<uint8_t> in, std::vector<uint8_t>* out) {
// (kInitialByteForDouble) plus the 64 bits of payload for its value.
constexpr size_t kEncodedDoubleSize = 1 + sizeof(uint64_t);
// An envelope is encoded with a specific initial byte
// (kInitialByteForEnvelope), plus the start byte for a BYTE_STRING with a 32
// bit wide length, plus a 32 bit length for that string.
constexpr size_t kEncodedEnvelopeHeaderSize = 1 + 1 + sizeof(uint32_t);
void EncodeDouble(double value, std::vector<uint8_t>* out) {
// The additional_info=27 indicates 64 bits for the double follow.
// See RFC 7049 Section 2.3, Table 1.
......@@ -348,6 +344,7 @@ void EncodeDouble(double value, std::vector<uint8_t>* out) {
void EnvelopeEncoder::EncodeStart(std::vector<uint8_t>* out) {
assert(byte_size_pos_ == 0);
out->push_back(kInitialByteForEnvelope);
// TODO(caseq): encode tag as an additional byte here.
out->push_back(kInitialByteFor32BitLengthByteString);
byte_size_pos_ = out->size();
out->resize(out->size() + sizeof(uint32_t));
......@@ -369,6 +366,54 @@ bool EnvelopeEncoder::EncodeStop(std::vector<uint8_t>* out) {
return true;
}
// static
StatusOr<EnvelopeHeader> EnvelopeHeader::Parse(span<uint8_t> in) {
auto header_or_status = ParseFromFragment(in);
if (!header_or_status.ok())
return header_or_status;
if ((*header_or_status).outer_size() > in.size()) {
return StatusOr<EnvelopeHeader>(
Status(Error::CBOR_ENVELOPE_CONTENTS_LENGTH_MISMATCH, in.size()));
}
return header_or_status;
}
// static
StatusOr<EnvelopeHeader> EnvelopeHeader::ParseFromFragment(span<uint8_t> in) {
// Our copy of StatusOr<> requires explicit constructor.
using Ret = StatusOr<EnvelopeHeader>;
constexpr size_t kMinEnvelopeSize = 2 + /* for envelope tag */
1 + /* for byte string */
1; /* for contents, a map or an array */
if (in.size() < kMinEnvelopeSize)
return Ret(Status(Error::CBOR_UNEXPECTED_EOF_IN_ENVELOPE, in.size()));
assert(in[0] == kInitialByteForEnvelope); // Caller should assure that.
size_t offset = 1;
// TODO(caseq): require this! We're currently accepting both a legacy,
// non spec-compliant envelope tag (that this implementation still currently
// produces), as well as a well-formed two-byte tag that a correct
// implementation should emit.
if (in[offset] == kCBOREnvelopeTag)
++offset;
MajorType type;
uint64_t size;
size_t string_header_size =
internals::ReadTokenStart(in.subspan(offset), &type, &size);
if (!string_header_size)
return Ret(Status(Error::CBOR_UNEXPECTED_EOF_IN_ENVELOPE, in.size()));
if (type != MajorType::BYTE_STRING)
return Ret(Status(Error::CBOR_INVALID_ENVELOPE, offset));
// Do not allow empty envelopes -- at least an empty map/array should fit.
if (!size) {
return Ret(Status(Error::CBOR_MAP_OR_ARRAY_EXPECTED_IN_ENVELOPE,
offset + string_header_size));
}
if (size > std::numeric_limits<uint32_t>::max())
return Ret(Status(Error::CBOR_INVALID_ENVELOPE, offset));
offset += string_header_size;
return Ret(EnvelopeHeader(offset, static_cast<size_t>(size)));
}
// =============================================================================
// cbor::NewCBOREncoder - for encoding from a streaming parser
// =============================================================================
......@@ -490,8 +535,9 @@ std::unique_ptr<ParserHandler> NewCBOREncoder(std::vector<uint8_t>* out,
// cbor::CBORTokenizer - for parsing individual CBOR items
// =============================================================================
CBORTokenizer::CBORTokenizer(span<uint8_t> bytes) : bytes_(bytes) {
ReadNextToken(/*enter_envelope=*/false);
CBORTokenizer::CBORTokenizer(span<uint8_t> bytes)
: bytes_(bytes), status_(Error::OK, 0) {
ReadNextToken();
}
CBORTokenizer::~CBORTokenizer() {}
......@@ -504,12 +550,12 @@ void CBORTokenizer::Next() {
if (token_tag_ == CBORTokenTag::ERROR_VALUE ||
token_tag_ == CBORTokenTag::DONE)
return;
ReadNextToken(/*enter_envelope=*/false);
ReadNextToken();
}
void CBORTokenizer::EnterEnvelope() {
assert(token_tag_ == CBORTokenTag::ENVELOPE);
ReadNextToken(/*enter_envelope=*/true);
token_byte_length_ = GetEnvelopeHeader().header_size();
ReadNextToken();
}
Status CBORTokenizer::Status() const {
......@@ -562,15 +608,18 @@ span<uint8_t> CBORTokenizer::GetBinary() const {
}
span<uint8_t> CBORTokenizer::GetEnvelope() const {
assert(token_tag_ == CBORTokenTag::ENVELOPE);
auto length = static_cast<size_t>(token_start_internal_value_);
return bytes_.subspan(status_.pos, length + kEncodedEnvelopeHeaderSize);
return bytes_.subspan(status_.pos, GetEnvelopeHeader().outer_size());
}
span<uint8_t> CBORTokenizer::GetEnvelopeContents() const {
const EnvelopeHeader& header = GetEnvelopeHeader();
return bytes_.subspan(status_.pos + header.header_size(),
header.content_size());
}
const EnvelopeHeader& CBORTokenizer::GetEnvelopeHeader() const {
assert(token_tag_ == CBORTokenTag::ENVELOPE);
auto length = static_cast<size_t>(token_start_internal_value_);
return bytes_.subspan(status_.pos + kEncodedEnvelopeHeaderSize, length);
return envelope_header_;
}
// All error checking happens in ::ReadNextToken, so that the accessors
......@@ -600,14 +649,10 @@ static const uint64_t kMaxValidLength =
std::min<uint64_t>(std::numeric_limits<uint64_t>::max() >> 2,
std::numeric_limits<size_t>::max());
void CBORTokenizer::ReadNextToken(bool enter_envelope) {
if (enter_envelope) {
status_.pos += kEncodedEnvelopeHeaderSize;
} else {
status_.pos =
status_.pos == Status::npos() ? 0 : status_.pos + token_byte_length_;
}
void CBORTokenizer::ReadNextToken() {
status_.pos += token_byte_length_;
status_.error = Error::OK;
envelope_header_ = EnvelopeHeader();
if (status_.pos >= bytes_.size()) {
token_tag_ = CBORTokenTag::DONE;
return;
......@@ -660,29 +705,16 @@ void CBORTokenizer::ReadNextToken(bool enter_envelope) {
return;
}
case kInitialByteForEnvelope: { // ENVELOPE
if (kEncodedEnvelopeHeaderSize > remaining_bytes) {
SetError(Error::CBOR_INVALID_ENVELOPE);
return;
}
// The envelope must be a byte string with 32 bit length.
if (bytes_[status_.pos + 1] != kInitialByteFor32BitLengthByteString) {
SetError(Error::CBOR_INVALID_ENVELOPE);
StatusOr<EnvelopeHeader> status_or_header =
EnvelopeHeader::Parse(bytes_.subspan(status_.pos));
if (!status_or_header.ok()) {
status_.pos += status_or_header.status().pos;
SetError(status_or_header.status().error);
return;
}
// Read the length of the byte string.
token_start_internal_value_ = ReadBytesMostSignificantByteFirst<uint32_t>(
bytes_.subspan(status_.pos + 2));
if (token_start_internal_value_ > kMaxValidLength) {
SetError(Error::CBOR_INVALID_ENVELOPE);
return;
}
uint64_t token_byte_length =
token_start_internal_value_ + kEncodedEnvelopeHeaderSize;
if (token_byte_length > remaining_bytes) {
SetError(Error::CBOR_INVALID_ENVELOPE);
return;
}
SetToken(CBORTokenTag::ENVELOPE, static_cast<size_t>(token_byte_length));
assert((*status_or_header).outer_size() <= remaining_bytes);
envelope_header_ = *status_or_header;
SetToken(CBORTokenTag::ENVELOPE, envelope_header_.outer_size());
return;
}
default: {
......@@ -795,9 +827,6 @@ bool ParseArray(int32_t stack_depth,
bool ParseValue(int32_t stack_depth,
CBORTokenizer* tokenizer,
ParserHandler* out);
bool ParseEnvelope(int32_t stack_depth,
CBORTokenizer* tokenizer,
ParserHandler* out);
void ParseUTF16String(CBORTokenizer* tokenizer, ParserHandler* out) {
std::vector<uint16_t> value;
......@@ -823,9 +852,8 @@ bool ParseEnvelope(int32_t stack_depth,
// expect to see after we're done parsing the envelope contents.
// This way we can compare and produce an error if the contents
// didn't fit exactly into the envelope length.
size_t pos_past_envelope = tokenizer->Status().pos +
kEncodedEnvelopeHeaderSize +
tokenizer->GetEnvelopeContents().size();
size_t pos_past_envelope =
tokenizer->Status().pos + tokenizer->GetEnvelopeHeader().outer_size();
tokenizer->EnterEnvelope();
switch (tokenizer->TokenTag()) {
case CBORTokenTag::ERROR_VALUE:
......@@ -983,7 +1011,7 @@ bool ParseMap(int32_t stack_depth,
void ParseCBOR(span<uint8_t> bytes, ParserHandler* out) {
if (bytes.empty()) {
out->HandleError(Status{Error::CBOR_NO_INPUT, 0});
out->HandleError(Status{Error::CBOR_UNEXPECTED_EOF_IN_ENVELOPE, 0});
return;
}
CBORTokenizer tokenizer(bytes);
......@@ -1009,33 +1037,37 @@ void ParseCBOR(span<uint8_t> bytes, ParserHandler* out) {
Status AppendString8EntryToCBORMap(span<uint8_t> string8_key,
span<uint8_t> string8_value,
std::vector<uint8_t>* cbor) {
// Careful below: Don't compare (*cbor)[idx] with a uint8_t, since
// it could be a char (signed!). Instead, use bytes.
span<uint8_t> bytes(reinterpret_cast<const uint8_t*>(cbor->data()),
cbor->size());
span<uint8_t> bytes(cbor->data(), cbor->size());
CBORTokenizer tokenizer(bytes);
if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE)
return tokenizer.Status();
if (tokenizer.TokenTag() != CBORTokenTag::ENVELOPE)
return Status(Error::CBOR_INVALID_ENVELOPE, 0);
size_t envelope_size = tokenizer.GetEnvelopeContents().size();
EnvelopeHeader env_header = tokenizer.GetEnvelopeHeader();
size_t old_size = cbor->size();
if (old_size != envelope_size + kEncodedEnvelopeHeaderSize)
if (old_size != env_header.outer_size())
return Status(Error::CBOR_INVALID_ENVELOPE, 0);
if (envelope_size == 0 ||
(tokenizer.GetEnvelopeContents()[0] != EncodeIndefiniteLengthMapStart()))
return Status(Error::CBOR_MAP_START_EXPECTED, kEncodedEnvelopeHeaderSize);
assert(env_header.content_size() > 0);
if (tokenizer.GetEnvelopeContents()[0] != EncodeIndefiniteLengthMapStart())
return Status(Error::CBOR_MAP_START_EXPECTED, env_header.header_size());
if (bytes[bytes.size() - 1] != EncodeStop())
return Status(Error::CBOR_MAP_STOP_EXPECTED, cbor->size() - 1);
// We generally accept envelope headers with size specified in all possible
// widths, but when it comes to modifying, we only support the fixed 4 byte
// widths that we produce.
const size_t byte_string_pos = bytes[1] == kCBOREnvelopeTag ? 2 : 1;
if (bytes[byte_string_pos] != kInitialByteFor32BitLengthByteString)
return Status(Error::CBOR_INVALID_ENVELOPE, byte_string_pos);
cbor->pop_back();
EncodeString8(string8_key, cbor);
EncodeString8(string8_value, cbor);
cbor->push_back(EncodeStop());
size_t new_envelope_size = envelope_size + (cbor->size() - old_size);
size_t new_envelope_size =
env_header.content_size() + (cbor->size() - old_size);
if (new_envelope_size > std::numeric_limits<uint32_t>::max())
return Status(Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED, 0);
size_t size_pos = cbor->size() - new_envelope_size - sizeof(uint32_t);
uint8_t* out = reinterpret_cast<uint8_t*>(&cbor->at(size_pos));
std::vector<uint8_t>::iterator out =
cbor->begin() + env_header.header_size() - sizeof(int32_t);
*(out++) = (new_envelope_size >> 24) & 0xff;
*(out++) = (new_envelope_size >> 16) & 0xff;
*(out++) = (new_envelope_size >> 8) & 0xff;
......
......@@ -43,13 +43,6 @@ namespace cbor {
// Detecting CBOR content
// =============================================================================
// The first byte for an envelope, which we use for wrapping dictionaries
// and arrays; and the byte that indicates a byte string with 32 bit length.
// These two bytes start an envelope, and thereby also any CBOR message
// produced or consumed by this protocol. See also |EnvelopeEncoder| below.
uint8_t InitialByteForEnvelope();
uint8_t InitialByteFor32BitLengthByteString();
// Checks whether |msg| is a cbor message.
bool IsCBORMessage(span<uint8_t> msg);
......@@ -129,6 +122,29 @@ class EnvelopeEncoder {
size_t byte_size_pos_ = 0;
};
class EnvelopeHeader {
public:
EnvelopeHeader() = default;
~EnvelopeHeader() = default;
// Parse envelope. Implies that `in` accomodates the entire size of envelope.
static StatusOr<EnvelopeHeader> Parse(span<uint8_t> in);
// Parse envelope, but allow `in` to only include the beginning of the
// envelope.
static StatusOr<EnvelopeHeader> ParseFromFragment(span<uint8_t> in);
size_t header_size() const { return header_size_; }
size_t content_size() const { return content_size_; }
size_t outer_size() const { return header_size_ + content_size_; }
private:
EnvelopeHeader(size_t header_size, size_t content_size)
: header_size_(header_size), content_size_(content_size) {}
size_t header_size_ = 0;
size_t content_size_ = 0;
};
// =============================================================================
// cbor::NewCBOREncoder - for encoding from a streaming parser
// =============================================================================
......@@ -256,17 +272,22 @@ class CBORTokenizer {
// enclosing envelope (the header, basically).
span<uint8_t> GetEnvelopeContents() const;
// To be called only if ::TokenTag() == CBORTokenTag::ENVELOPE.
// Returns the envelope header.
const EnvelopeHeader& GetEnvelopeHeader() const;
private:
void ReadNextToken(bool enter_envelope);
void ReadNextToken();
void SetToken(CBORTokenTag token, size_t token_byte_length);
void SetError(Error error);
span<uint8_t> bytes_;
const span<uint8_t> bytes_;
CBORTokenTag token_tag_;
struct Status status_;
size_t token_byte_length_;
size_t token_byte_length_ = 0;
MajorType token_start_type_;
uint64_t token_start_internal_value_;
EnvelopeHeader envelope_header_;
};
// =============================================================================
......
......@@ -21,6 +21,7 @@
#include "test_platform.h"
using testing::ElementsAreArray;
using testing::Eq;
namespace v8_crdtp {
namespace cbor {
......@@ -72,7 +73,7 @@ TEST(CheckCBORMessage, ValidCBORButNotValidMessage) {
TEST(CheckCBORMessage, EmptyMessage) {
std::vector<uint8_t> empty;
Status status = CheckCBORMessage(SpanFrom(empty));
EXPECT_THAT(status, StatusIs(Error::CBOR_NO_INPUT, 0));
EXPECT_THAT(status, StatusIs(Error::CBOR_UNEXPECTED_EOF_IN_ENVELOPE, 0));
}
TEST(CheckCBORMessage, InvalidStartByte) {
......@@ -86,25 +87,25 @@ TEST(CheckCBORMessage, InvalidStartByte) {
TEST(CheckCBORMessage, InvalidEnvelopes) {
std::vector<uint8_t> bytes = {0xd8, 0x5a};
EXPECT_THAT(CheckCBORMessage(SpanFrom(bytes)),
StatusIs(Error::CBOR_INVALID_ENVELOPE, 1));
StatusIs(Error::CBOR_UNEXPECTED_EOF_IN_ENVELOPE, 2));
bytes = {0xd8, 0x5a, 0};
EXPECT_THAT(CheckCBORMessage(SpanFrom(bytes)),
StatusIs(Error::CBOR_INVALID_ENVELOPE, 1));
StatusIs(Error::CBOR_UNEXPECTED_EOF_IN_ENVELOPE, 3));
bytes = {0xd8, 0x5a, 0, 0};
EXPECT_THAT(CheckCBORMessage(SpanFrom(bytes)),
StatusIs(Error::CBOR_INVALID_ENVELOPE, 1));
StatusIs(Error::CBOR_UNEXPECTED_EOF_IN_ENVELOPE, 4));
bytes = {0xd8, 0x5a, 0, 0, 0};
EXPECT_THAT(CheckCBORMessage(SpanFrom(bytes)),
StatusIs(Error::CBOR_INVALID_ENVELOPE, 1));
StatusIs(Error::CBOR_UNEXPECTED_EOF_IN_ENVELOPE, 5));
bytes = {0xd8, 0x5a, 0, 0, 0, 0};
EXPECT_THAT(CheckCBORMessage(SpanFrom(bytes)),
StatusIs(Error::CBOR_INVALID_ENVELOPE, 1));
StatusIs(Error::CBOR_MAP_OR_ARRAY_EXPECTED_IN_ENVELOPE, 6));
}
TEST(CheckCBORMessage, MapStartExpected) {
std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, 1};
EXPECT_THAT(CheckCBORMessage(SpanFrom(bytes)),
StatusIs(Error::CBOR_MAP_START_EXPECTED, 6));
StatusIs(Error::CBOR_ENVELOPE_CONTENTS_LENGTH_MISMATCH, 6));
}
// =============================================================================
......@@ -871,12 +872,8 @@ TEST(ParseCBORTest, ParseCBORHelloWorld) {
TEST(ParseCBORTest, UTF8IsSupportedInKeys) {
const uint8_t kPayloadLen = 11;
std::vector<uint8_t> bytes = {cbor::InitialByteForEnvelope(),
cbor::InitialByteFor32BitLengthByteString(),
0,
0,
0,
kPayloadLen};
std::vector<uint8_t> bytes = {0xd8, 0x5a, // envelope
0, 0, 0, kPayloadLen};
bytes.push_back(cbor::EncodeIndefiniteLengthMapStart());
// Two UTF16 chars.
EncodeString8(SpanFrom("🌎"), &bytes);
......@@ -901,7 +898,7 @@ TEST(ParseCBORTest, NoInputError) {
std::unique_ptr<ParserHandler> json_writer =
json::NewJSONEncoder(&out, &status);
ParseCBOR(span<uint8_t>(in.data(), in.size()), json_writer.get());
EXPECT_THAT(status, StatusIs(Error::CBOR_NO_INPUT, 0u));
EXPECT_THAT(status, StatusIs(Error::CBOR_UNEXPECTED_EOF_IN_ENVELOPE, 0u));
EXPECT_EQ("", out);
}
......@@ -954,6 +951,39 @@ TEST(ParseCBORTest, UnexpectedEofInMapError) {
EXPECT_EQ("", out);
}
TEST(ParseCBORTest, EnvelopeEncodingLegacy) {
constexpr uint8_t kPayloadLen = 8;
std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, kPayloadLen}; // envelope
bytes.push_back(cbor::EncodeIndefiniteLengthMapStart());
EncodeString8(SpanFrom("foo"), &bytes);
EncodeInt32(42, &bytes);
bytes.emplace_back(EncodeStop());
std::string out;
Status status;
std::unique_ptr<ParserHandler> json_writer =
json::NewJSONEncoder(&out, &status);
ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
EXPECT_THAT(status, StatusIsOk());
EXPECT_EQ(out, "{\"foo\":42}");
}
TEST(ParseCBORTest, EnvelopeEncodingBySpec) {
constexpr uint8_t kPayloadLen = 8;
std::vector<uint8_t> bytes = {0xd8, 0x18, 0x5a, 0,
0, 0, kPayloadLen}; // envelope
bytes.push_back(cbor::EncodeIndefiniteLengthMapStart());
EncodeString8(SpanFrom("foo"), &bytes);
EncodeInt32(42, &bytes);
bytes.emplace_back(EncodeStop());
std::string out;
Status status;
std::unique_ptr<ParserHandler> json_writer =
json::NewJSONEncoder(&out, &status);
ParseCBOR(span<uint8_t>(bytes.data(), bytes.size()), json_writer.get());
EXPECT_THAT(status, StatusIsOk());
EXPECT_EQ(out, "{\"foo\":42}");
}
TEST(ParseCBORTest, NoEmptyEnvelopesAllowed) {
std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, 0}; // envelope
std::string out;
......@@ -971,7 +1001,7 @@ TEST(ParseCBORTest, OnlyMapsAndArraysSupportedInsideEnvelopes) {
// is an envelope that contains just a number (1). We don't
// allow numbers to be contained in an envelope though, only
// maps and arrays.
constexpr uint8_t kPayloadLen = 1;
constexpr uint8_t kPayloadLen = 8;
std::vector<uint8_t> bytes = {0xd8,
0x5a,
0,
......@@ -1247,6 +1277,69 @@ TEST(ParseCBORTest, EnvelopeContentsLengthMismatch) {
EXPECT_EQ("", out);
}
// =============================================================================
// cbor::EnvelopeHeader - for parsing envelope headers
// =============================================================================
// Note most of converage for this is historically on a higher level of
// ParseCBOR(). This provides just a few essnetial scenarios for now.
template <typename T>
class EnvelopeHeaderTest : public ::testing::Test {};
TEST(EnvelopeHeaderTest, EnvelopeStartLegacy) {
std::vector<uint8_t> bytes = {0xd8, // Tag start
0x5a, // Byte string, 4 bytes length
0, 0, 0, 2, // Length
0xbf, 0xff}; // map start / map end
auto result = EnvelopeHeader::Parse(SpanFrom(bytes));
ASSERT_THAT(result.status(), StatusIsOk());
EXPECT_THAT((*result).header_size(), Eq(6u));
EXPECT_THAT((*result).content_size(), Eq(2u));
EXPECT_THAT((*result).outer_size(), Eq(8u));
}
TEST(EnvelopeHeaderTest, EnvelopeStartSpecCompliant) {
std::vector<uint8_t> bytes = {0xd8, // Tag start
0x18, // Tag type (CBOR)
0x5a, // Byte string, 4 bytes length
0, 0, 0, 2, // Length
0xbf, 0xff}; // map start / map end
auto result = EnvelopeHeader::Parse(SpanFrom(bytes));
ASSERT_THAT(result.status(), StatusIsOk());
EXPECT_THAT((*result).header_size(), Eq(7u));
EXPECT_THAT((*result).content_size(), Eq(2u));
EXPECT_THAT((*result).outer_size(), Eq(9u));
}
TEST(EnvelopeHeaderTest, EnvelopeStartShortLen) {
std::vector<uint8_t> bytes = {0xd8, // Tag start
0x18, // Tag type (CBOR)
0x58, // Byte string, 1 byte length
2, // Length
0xbf, 0xff}; // map start / map end
auto result = EnvelopeHeader::Parse(SpanFrom(bytes));
ASSERT_THAT(result.status(), StatusIsOk());
EXPECT_THAT((*result).header_size(), Eq(4u));
EXPECT_THAT((*result).content_size(), Eq(2u));
EXPECT_THAT((*result).outer_size(), Eq(6u));
}
TEST(EnvelopeHeaderTest, ParseFragment) {
std::vector<uint8_t> bytes = {0xd8, // Tag start
0x18, // Tag type (CBOR)
0x5a, // Byte string, 4 bytes length
0, 0, 0, 20, 0xbf}; // map start
auto result = EnvelopeHeader::ParseFromFragment(SpanFrom(bytes));
ASSERT_THAT(result.status(), StatusIsOk());
EXPECT_THAT((*result).header_size(), Eq(7u));
EXPECT_THAT((*result).content_size(), Eq(20u));
EXPECT_THAT((*result).outer_size(), Eq(27u));
result = EnvelopeHeader::Parse(SpanFrom(bytes));
ASSERT_THAT(result.status(),
StatusIs(Error::CBOR_ENVELOPE_CONTENTS_LENGTH_MISMATCH, 8));
}
// =============================================================================
// cbor::AppendString8EntryToMap - for limited in-place editing of messages
// =============================================================================
......@@ -1331,7 +1424,7 @@ TEST(AppendString8EntryToMapTest, InvalidEnvelope_Error) {
0xd8, 0x7a, 0, 0, 0, 2, EncodeIndefiniteLengthMapStart(), EncodeStop()};
Status status =
AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg);
EXPECT_THAT(status, StatusIs(Error::CBOR_INVALID_ENVELOPE, 0u));
EXPECT_THAT(status, StatusIs(Error::CBOR_INVALID_ENVELOPE, 1u));
}
{ // Invalid envelope size example.
std::vector<uint8_t> msg = {
......@@ -1339,7 +1432,8 @@ TEST(AppendString8EntryToMapTest, InvalidEnvelope_Error) {
};
Status status =
AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg);
EXPECT_THAT(status, StatusIs(Error::CBOR_INVALID_ENVELOPE, 0u));
EXPECT_THAT(status,
StatusIs(Error::CBOR_ENVELOPE_CONTENTS_LENGTH_MISMATCH, 8u));
}
{ // Invalid envelope size example.
std::vector<uint8_t> msg = {
......@@ -1347,7 +1441,7 @@ TEST(AppendString8EntryToMapTest, InvalidEnvelope_Error) {
};
Status status =
AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg);
EXPECT_THAT(status, StatusIs(Error::CBOR_INVALID_ENVELOPE, 0u));
EXPECT_THAT(status, StatusIs(Error::CBOR_INVALID_ENVELOPE, 0));
}
}
} // namespace cbor
......
......@@ -78,13 +78,17 @@ DispatchResponse DispatchResponse::ServerError(std::string message) {
return result;
}
// static
DispatchResponse DispatchResponse::SessionNotFound(std::string message) {
DispatchResponse result;
result.code_ = DispatchCode::SESSION_NOT_FOUND;
result.message_ = std::move(message);
return result;
}
// =============================================================================
// Dispatchable - a shallow parser for CBOR encoded DevTools messages
// =============================================================================
namespace {
constexpr size_t kEncodedEnvelopeHeaderSize = 1 + 1 + sizeof(uint32_t);
} // namespace
Dispatchable::Dispatchable(span<uint8_t> serialized) : serialized_(serialized) {
Status s = cbor::CheckCBORMessage(serialized);
if (!s.ok()) {
......@@ -105,9 +109,8 @@ Dispatchable::Dispatchable(span<uint8_t> serialized) : serialized_(serialized) {
// expect to see after we're done parsing the envelope contents.
// This way we can compare and produce an error if the contents
// didn't fit exactly into the envelope length.
const size_t pos_past_envelope = tokenizer.Status().pos +
kEncodedEnvelopeHeaderSize +
tokenizer.GetEnvelopeContents().size();
const size_t pos_past_envelope =
tokenizer.Status().pos + tokenizer.GetEnvelopeHeader().outer_size();
tokenizer.EnterEnvelope();
if (tokenizer.TokenTag() == cbor::CBORTokenTag::ERROR_VALUE) {
status_ = tokenizer.Status();
......
......@@ -38,6 +38,7 @@ enum class DispatchCode {
INVALID_PARAMS = -32602,
INTERNAL_ERROR = -32603,
SERVER_ERROR = -32000,
SESSION_NOT_FOUND = SERVER_ERROR - 1,
};
// Information returned by command handlers. Usually returned after command
......@@ -76,6 +77,10 @@ class DispatchResponse {
// Used for application level errors, e.g. within protocol agents.
static DispatchResponse ServerError(std::string message);
// Indicate that session with the id specified in the protocol message
// was not found (e.g. because it has already been detached).
static DispatchResponse SessionNotFound(std::string message);
private:
DispatchResponse() = default;
DispatchCode code_;
......
......@@ -27,6 +27,13 @@ TEST(DispatchResponseTest, ServerError) {
EXPECT_EQ("Oops!", error.Message());
}
TEST(DispatchResponseTest, SessionNotFound) {
DispatchResponse error = DispatchResponse::SessionNotFound("OMG!");
EXPECT_FALSE(error.IsSuccess());
EXPECT_EQ(DispatchCode::SESSION_NOT_FOUND, error.Code());
EXPECT_EQ("OMG!", error.Message());
}
TEST(DispatchResponseTest, InternalError) {
DispatchResponse error = DispatchResponse::InternalError();
EXPECT_FALSE(error.IsSuccess());
......
......@@ -58,16 +58,16 @@ std::string Status::Message() const {
return "CBOR: invalid binary";
case Error::CBOR_UNSUPPORTED_VALUE:
return "CBOR: unsupported value";
case Error::CBOR_NO_INPUT:
return "CBOR: no input";
case Error::CBOR_UNEXPECTED_EOF_IN_ENVELOPE:
return "CBOR: unexpected EOF reading envelope";
case Error::CBOR_INVALID_START_BYTE:
return "CBOR: invalid start byte";
case Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE:
return "CBOR: unexpected eof expected value";
return "CBOR: unexpected EOF expected value";
case Error::CBOR_UNEXPECTED_EOF_IN_ARRAY:
return "CBOR: unexpected eof in array";
return "CBOR: unexpected EOF in array";
case Error::CBOR_UNEXPECTED_EOF_IN_MAP:
return "CBOR: unexpected eof in map";
return "CBOR: unexpected EOF in map";
case Error::CBOR_INVALID_MAP_KEY:
return "CBOR: invalid map key";
case Error::CBOR_DUPLICATE_MAP_KEY:
......
......@@ -46,7 +46,7 @@ enum class Error {
CBOR_INVALID_STRING16 = 0x14,
CBOR_INVALID_BINARY = 0x15,
CBOR_UNSUPPORTED_VALUE = 0x16,
CBOR_NO_INPUT = 0x17,
CBOR_UNEXPECTED_EOF_IN_ENVELOPE = 0x17,
CBOR_INVALID_START_BYTE = 0x18,
CBOR_UNEXPECTED_EOF_EXPECTED_VALUE = 0x19,
CBOR_UNEXPECTED_EOF_IN_ARRAY = 0x1a,
......@@ -90,7 +90,7 @@ struct Status {
Error error = Error::OK;
size_t pos = npos();
Status(Error error, size_t pos) : error(error), pos(pos) {}
[[nodiscard]] Status(Error error, size_t pos) : error(error), pos(pos) {}
Status() = default;
bool IsMessageError() const {
......
......@@ -31,7 +31,6 @@ using {{config.crdtp.namespace}}::cbor::EncodeStop;
using {{config.crdtp.namespace}}::cbor::EncodeString8;
using {{config.crdtp.namespace}}::cbor::EncodeTrue;
using {{config.crdtp.namespace}}::cbor::EnvelopeEncoder;
using {{config.crdtp.namespace}}::cbor::InitialByteForEnvelope;
} // namespace cbor
// Uses the parsing events received from driver of |ParserHandler|
......
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