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

Roll inspector_protocol to 134539780e606a77d660d58bf95b5ab55875bc3c

... with [[nodiscard]] removed following the revert of
https://crrev.com/c/v8/v8/+/3662540

Change-Id: Ie35f54af1e22a2002c10325642b980f94f666321
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3669472
Auto-Submit: Andrey Kosyakov <caseq@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/main@{#80805}
parent 3bb3c99d
...@@ -2,7 +2,7 @@ Name: inspector protocol ...@@ -2,7 +2,7 @@ Name: inspector protocol
Short Name: inspector_protocol Short Name: inspector_protocol
URL: https://chromium.googlesource.com/deps/inspector_protocol/ URL: https://chromium.googlesource.com/deps/inspector_protocol/
Version: 0 Version: 0
Revision: 817313aa48ebb9a53cba1bd88bbe6a1c5048060c Revision: 134539780e606a77d660d58bf95b5ab55875bc3c
License: BSD License: BSD
License File: LICENSE License File: LICENSE
Security Critical: no Security Critical: no
......
...@@ -46,7 +46,13 @@ constexpr uint8_t EncodeInitialByte(MajorType type, uint8_t additional_info) { ...@@ -46,7 +46,13 @@ constexpr uint8_t EncodeInitialByte(MajorType type, uint8_t additional_info) {
// byte string carries its size (byte length). // byte string carries its size (byte length).
// https://tools.ietf.org/html/rfc7049#section-2.4.4.1 // https://tools.ietf.org/html/rfc7049#section-2.4.4.1
static constexpr uint8_t kInitialByteForEnvelope = 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 // The initial byte for a byte string with at most 2^32 bytes
// of payload. This is used for envelope encoding, even if // of payload. This is used for envelope encoding, even if
// the byte string is shorter. // the byte string is shorter.
...@@ -190,30 +196,25 @@ void WriteTokenStart(MajorType type, ...@@ -190,30 +196,25 @@ void WriteTokenStart(MajorType type,
// Detecting CBOR content // Detecting CBOR content
// ============================================================================= // =============================================================================
uint8_t InitialByteForEnvelope() {
return kInitialByteForEnvelope;
}
uint8_t InitialByteFor32BitLengthByteString() {
return kInitialByteFor32BitLengthByteString;
}
bool IsCBORMessage(span<uint8_t> msg) { bool IsCBORMessage(span<uint8_t> msg) {
return msg.size() >= 6 && msg[0] == InitialByteForEnvelope() && return msg.size() >= 4 && msg[0] == kInitialByteForEnvelope &&
msg[1] == InitialByteFor32BitLengthByteString(); (msg[1] == kInitialByteFor32BitLengthByteString ||
(msg[1] == kCBOREnvelopeTag &&
msg[2] == kInitialByteFor32BitLengthByteString));
} }
Status CheckCBORMessage(span<uint8_t> msg) { Status CheckCBORMessage(span<uint8_t> msg) {
if (msg.empty()) if (msg.empty())
return Status(Error::CBOR_NO_INPUT, 0); return Status(Error::CBOR_UNEXPECTED_EOF_IN_ENVELOPE, 0);
if (msg[0] != InitialByteForEnvelope()) if (msg[0] != kInitialByteForEnvelope)
return Status(Error::CBOR_INVALID_START_BYTE, 0); return Status(Error::CBOR_INVALID_START_BYTE, 0);
if (msg.size() < 6 || msg[1] != InitialByteFor32BitLengthByteString()) StatusOr<EnvelopeHeader> status_or_header = EnvelopeHeader::Parse(msg);
return Status(Error::CBOR_INVALID_ENVELOPE, 1); if (!status_or_header.ok())
if (msg[2] == 0 && msg[3] == 0 && msg[4] == 0 && msg[5] == 0) return status_or_header.status();
return Status(Error::CBOR_INVALID_ENVELOPE, 1); const size_t pos = (*status_or_header).header_size();
if (msg.size() < 7 || msg[6] != EncodeIndefiniteLengthMapStart()) assert(pos < msg.size()); // EnvelopeParser would not allow empty envelope.
return Status(Error::CBOR_MAP_START_EXPECTED, 6); if (msg[pos] != EncodeIndefiniteLengthMapStart())
return Status(Error::CBOR_MAP_START_EXPECTED, pos);
return Status(); return Status();
} }
...@@ -324,11 +325,6 @@ void EncodeBinary(span<uint8_t> in, std::vector<uint8_t>* out) { ...@@ -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. // (kInitialByteForDouble) plus the 64 bits of payload for its value.
constexpr size_t kEncodedDoubleSize = 1 + sizeof(uint64_t); 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) { void EncodeDouble(double value, std::vector<uint8_t>* out) {
// The additional_info=27 indicates 64 bits for the double follow. // The additional_info=27 indicates 64 bits for the double follow.
// See RFC 7049 Section 2.3, Table 1. // See RFC 7049 Section 2.3, Table 1.
...@@ -348,6 +344,7 @@ void EncodeDouble(double value, std::vector<uint8_t>* out) { ...@@ -348,6 +344,7 @@ void EncodeDouble(double value, std::vector<uint8_t>* out) {
void EnvelopeEncoder::EncodeStart(std::vector<uint8_t>* out) { void EnvelopeEncoder::EncodeStart(std::vector<uint8_t>* out) {
assert(byte_size_pos_ == 0); assert(byte_size_pos_ == 0);
out->push_back(kInitialByteForEnvelope); out->push_back(kInitialByteForEnvelope);
// TODO(caseq): encode tag as an additional byte here.
out->push_back(kInitialByteFor32BitLengthByteString); out->push_back(kInitialByteFor32BitLengthByteString);
byte_size_pos_ = out->size(); byte_size_pos_ = out->size();
out->resize(out->size() + sizeof(uint32_t)); out->resize(out->size() + sizeof(uint32_t));
...@@ -369,6 +366,54 @@ bool EnvelopeEncoder::EncodeStop(std::vector<uint8_t>* out) { ...@@ -369,6 +366,54 @@ bool EnvelopeEncoder::EncodeStop(std::vector<uint8_t>* out) {
return true; 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 // cbor::NewCBOREncoder - for encoding from a streaming parser
// ============================================================================= // =============================================================================
...@@ -490,8 +535,9 @@ std::unique_ptr<ParserHandler> NewCBOREncoder(std::vector<uint8_t>* out, ...@@ -490,8 +535,9 @@ std::unique_ptr<ParserHandler> NewCBOREncoder(std::vector<uint8_t>* out,
// cbor::CBORTokenizer - for parsing individual CBOR items // cbor::CBORTokenizer - for parsing individual CBOR items
// ============================================================================= // =============================================================================
CBORTokenizer::CBORTokenizer(span<uint8_t> bytes) : bytes_(bytes) { CBORTokenizer::CBORTokenizer(span<uint8_t> bytes)
ReadNextToken(/*enter_envelope=*/false); : bytes_(bytes), status_(Error::OK, 0) {
ReadNextToken();
} }
CBORTokenizer::~CBORTokenizer() {} CBORTokenizer::~CBORTokenizer() {}
...@@ -504,12 +550,12 @@ void CBORTokenizer::Next() { ...@@ -504,12 +550,12 @@ void CBORTokenizer::Next() {
if (token_tag_ == CBORTokenTag::ERROR_VALUE || if (token_tag_ == CBORTokenTag::ERROR_VALUE ||
token_tag_ == CBORTokenTag::DONE) token_tag_ == CBORTokenTag::DONE)
return; return;
ReadNextToken(/*enter_envelope=*/false); ReadNextToken();
} }
void CBORTokenizer::EnterEnvelope() { void CBORTokenizer::EnterEnvelope() {
assert(token_tag_ == CBORTokenTag::ENVELOPE); token_byte_length_ = GetEnvelopeHeader().header_size();
ReadNextToken(/*enter_envelope=*/true); ReadNextToken();
} }
Status CBORTokenizer::Status() const { Status CBORTokenizer::Status() const {
...@@ -562,15 +608,18 @@ span<uint8_t> CBORTokenizer::GetBinary() const { ...@@ -562,15 +608,18 @@ span<uint8_t> CBORTokenizer::GetBinary() const {
} }
span<uint8_t> CBORTokenizer::GetEnvelope() const { span<uint8_t> CBORTokenizer::GetEnvelope() const {
assert(token_tag_ == CBORTokenTag::ENVELOPE); return bytes_.subspan(status_.pos, GetEnvelopeHeader().outer_size());
auto length = static_cast<size_t>(token_start_internal_value_);
return bytes_.subspan(status_.pos, length + kEncodedEnvelopeHeaderSize);
} }
span<uint8_t> CBORTokenizer::GetEnvelopeContents() const { 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); assert(token_tag_ == CBORTokenTag::ENVELOPE);
auto length = static_cast<size_t>(token_start_internal_value_); return envelope_header_;
return bytes_.subspan(status_.pos + kEncodedEnvelopeHeaderSize, length);
} }
// All error checking happens in ::ReadNextToken, so that the accessors // All error checking happens in ::ReadNextToken, so that the accessors
...@@ -600,14 +649,10 @@ static const uint64_t kMaxValidLength = ...@@ -600,14 +649,10 @@ static const uint64_t kMaxValidLength =
std::min<uint64_t>(std::numeric_limits<uint64_t>::max() >> 2, std::min<uint64_t>(std::numeric_limits<uint64_t>::max() >> 2,
std::numeric_limits<size_t>::max()); std::numeric_limits<size_t>::max());
void CBORTokenizer::ReadNextToken(bool enter_envelope) { void CBORTokenizer::ReadNextToken() {
if (enter_envelope) { status_.pos += token_byte_length_;
status_.pos += kEncodedEnvelopeHeaderSize;
} else {
status_.pos =
status_.pos == Status::npos() ? 0 : status_.pos + token_byte_length_;
}
status_.error = Error::OK; status_.error = Error::OK;
envelope_header_ = EnvelopeHeader();
if (status_.pos >= bytes_.size()) { if (status_.pos >= bytes_.size()) {
token_tag_ = CBORTokenTag::DONE; token_tag_ = CBORTokenTag::DONE;
return; return;
...@@ -660,29 +705,16 @@ void CBORTokenizer::ReadNextToken(bool enter_envelope) { ...@@ -660,29 +705,16 @@ void CBORTokenizer::ReadNextToken(bool enter_envelope) {
return; return;
} }
case kInitialByteForEnvelope: { // ENVELOPE case kInitialByteForEnvelope: { // ENVELOPE
if (kEncodedEnvelopeHeaderSize > remaining_bytes) { StatusOr<EnvelopeHeader> status_or_header =
SetError(Error::CBOR_INVALID_ENVELOPE); EnvelopeHeader::Parse(bytes_.subspan(status_.pos));
return; if (!status_or_header.ok()) {
} status_.pos += status_or_header.status().pos;
// The envelope must be a byte string with 32 bit length. SetError(status_or_header.status().error);
if (bytes_[status_.pos + 1] != kInitialByteFor32BitLengthByteString) {
SetError(Error::CBOR_INVALID_ENVELOPE);
return; return;
} }
// Read the length of the byte string. assert((*status_or_header).outer_size() <= remaining_bytes);
token_start_internal_value_ = ReadBytesMostSignificantByteFirst<uint32_t>( envelope_header_ = *status_or_header;
bytes_.subspan(status_.pos + 2)); SetToken(CBORTokenTag::ENVELOPE, envelope_header_.outer_size());
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));
return; return;
} }
default: { default: {
...@@ -795,9 +827,6 @@ bool ParseArray(int32_t stack_depth, ...@@ -795,9 +827,6 @@ bool ParseArray(int32_t stack_depth,
bool ParseValue(int32_t stack_depth, bool ParseValue(int32_t stack_depth,
CBORTokenizer* tokenizer, CBORTokenizer* tokenizer,
ParserHandler* out); ParserHandler* out);
bool ParseEnvelope(int32_t stack_depth,
CBORTokenizer* tokenizer,
ParserHandler* out);
void ParseUTF16String(CBORTokenizer* tokenizer, ParserHandler* out) { void ParseUTF16String(CBORTokenizer* tokenizer, ParserHandler* out) {
std::vector<uint16_t> value; std::vector<uint16_t> value;
...@@ -823,9 +852,8 @@ bool ParseEnvelope(int32_t stack_depth, ...@@ -823,9 +852,8 @@ bool ParseEnvelope(int32_t stack_depth,
// expect to see after we're done parsing the envelope contents. // expect to see after we're done parsing the envelope contents.
// This way we can compare and produce an error if the contents // This way we can compare and produce an error if the contents
// didn't fit exactly into the envelope length. // didn't fit exactly into the envelope length.
size_t pos_past_envelope = tokenizer->Status().pos + size_t pos_past_envelope =
kEncodedEnvelopeHeaderSize + tokenizer->Status().pos + tokenizer->GetEnvelopeHeader().outer_size();
tokenizer->GetEnvelopeContents().size();
tokenizer->EnterEnvelope(); tokenizer->EnterEnvelope();
switch (tokenizer->TokenTag()) { switch (tokenizer->TokenTag()) {
case CBORTokenTag::ERROR_VALUE: case CBORTokenTag::ERROR_VALUE:
...@@ -983,7 +1011,7 @@ bool ParseMap(int32_t stack_depth, ...@@ -983,7 +1011,7 @@ bool ParseMap(int32_t stack_depth,
void ParseCBOR(span<uint8_t> bytes, ParserHandler* out) { void ParseCBOR(span<uint8_t> bytes, ParserHandler* out) {
if (bytes.empty()) { if (bytes.empty()) {
out->HandleError(Status{Error::CBOR_NO_INPUT, 0}); out->HandleError(Status{Error::CBOR_UNEXPECTED_EOF_IN_ENVELOPE, 0});
return; return;
} }
CBORTokenizer tokenizer(bytes); CBORTokenizer tokenizer(bytes);
...@@ -1009,33 +1037,37 @@ void ParseCBOR(span<uint8_t> bytes, ParserHandler* out) { ...@@ -1009,33 +1037,37 @@ void ParseCBOR(span<uint8_t> bytes, ParserHandler* out) {
Status AppendString8EntryToCBORMap(span<uint8_t> string8_key, Status AppendString8EntryToCBORMap(span<uint8_t> string8_key,
span<uint8_t> string8_value, span<uint8_t> string8_value,
std::vector<uint8_t>* cbor) { std::vector<uint8_t>* cbor) {
// Careful below: Don't compare (*cbor)[idx] with a uint8_t, since span<uint8_t> bytes(cbor->data(), cbor->size());
// it could be a char (signed!). Instead, use bytes.
span<uint8_t> bytes(reinterpret_cast<const uint8_t*>(cbor->data()),
cbor->size());
CBORTokenizer tokenizer(bytes); CBORTokenizer tokenizer(bytes);
if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE) if (tokenizer.TokenTag() == CBORTokenTag::ERROR_VALUE)
return tokenizer.Status(); return tokenizer.Status();
if (tokenizer.TokenTag() != CBORTokenTag::ENVELOPE) if (tokenizer.TokenTag() != CBORTokenTag::ENVELOPE)
return Status(Error::CBOR_INVALID_ENVELOPE, 0); 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(); 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); return Status(Error::CBOR_INVALID_ENVELOPE, 0);
if (envelope_size == 0 || assert(env_header.content_size() > 0);
(tokenizer.GetEnvelopeContents()[0] != EncodeIndefiniteLengthMapStart())) if (tokenizer.GetEnvelopeContents()[0] != EncodeIndefiniteLengthMapStart())
return Status(Error::CBOR_MAP_START_EXPECTED, kEncodedEnvelopeHeaderSize); return Status(Error::CBOR_MAP_START_EXPECTED, env_header.header_size());
if (bytes[bytes.size() - 1] != EncodeStop()) if (bytes[bytes.size() - 1] != EncodeStop())
return Status(Error::CBOR_MAP_STOP_EXPECTED, cbor->size() - 1); 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(); cbor->pop_back();
EncodeString8(string8_key, cbor); EncodeString8(string8_key, cbor);
EncodeString8(string8_value, cbor); EncodeString8(string8_value, cbor);
cbor->push_back(EncodeStop()); 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()) if (new_envelope_size > std::numeric_limits<uint32_t>::max())
return Status(Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED, 0); return Status(Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED, 0);
size_t size_pos = cbor->size() - new_envelope_size - sizeof(uint32_t); std::vector<uint8_t>::iterator out =
uint8_t* out = reinterpret_cast<uint8_t*>(&cbor->at(size_pos)); cbor->begin() + env_header.header_size() - sizeof(int32_t);
*(out++) = (new_envelope_size >> 24) & 0xff; *(out++) = (new_envelope_size >> 24) & 0xff;
*(out++) = (new_envelope_size >> 16) & 0xff; *(out++) = (new_envelope_size >> 16) & 0xff;
*(out++) = (new_envelope_size >> 8) & 0xff; *(out++) = (new_envelope_size >> 8) & 0xff;
......
...@@ -43,13 +43,6 @@ namespace cbor { ...@@ -43,13 +43,6 @@ namespace cbor {
// Detecting CBOR content // 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. // Checks whether |msg| is a cbor message.
bool IsCBORMessage(span<uint8_t> msg); bool IsCBORMessage(span<uint8_t> msg);
...@@ -129,6 +122,29 @@ class EnvelopeEncoder { ...@@ -129,6 +122,29 @@ class EnvelopeEncoder {
size_t byte_size_pos_ = 0; 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 // cbor::NewCBOREncoder - for encoding from a streaming parser
// ============================================================================= // =============================================================================
...@@ -256,17 +272,22 @@ class CBORTokenizer { ...@@ -256,17 +272,22 @@ class CBORTokenizer {
// enclosing envelope (the header, basically). // enclosing envelope (the header, basically).
span<uint8_t> GetEnvelopeContents() const; span<uint8_t> GetEnvelopeContents() const;
// To be called only if ::TokenTag() == CBORTokenTag::ENVELOPE.
// Returns the envelope header.
const EnvelopeHeader& GetEnvelopeHeader() const;
private: private:
void ReadNextToken(bool enter_envelope); void ReadNextToken();
void SetToken(CBORTokenTag token, size_t token_byte_length); void SetToken(CBORTokenTag token, size_t token_byte_length);
void SetError(Error error); void SetError(Error error);
span<uint8_t> bytes_; const span<uint8_t> bytes_;
CBORTokenTag token_tag_; CBORTokenTag token_tag_;
struct Status status_; struct Status status_;
size_t token_byte_length_; size_t token_byte_length_ = 0;
MajorType token_start_type_; MajorType token_start_type_;
uint64_t token_start_internal_value_; uint64_t token_start_internal_value_;
EnvelopeHeader envelope_header_;
}; };
// ============================================================================= // =============================================================================
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include "test_platform.h" #include "test_platform.h"
using testing::ElementsAreArray; using testing::ElementsAreArray;
using testing::Eq;
namespace v8_crdtp { namespace v8_crdtp {
namespace cbor { namespace cbor {
...@@ -72,7 +73,7 @@ TEST(CheckCBORMessage, ValidCBORButNotValidMessage) { ...@@ -72,7 +73,7 @@ TEST(CheckCBORMessage, ValidCBORButNotValidMessage) {
TEST(CheckCBORMessage, EmptyMessage) { TEST(CheckCBORMessage, EmptyMessage) {
std::vector<uint8_t> empty; std::vector<uint8_t> empty;
Status status = CheckCBORMessage(SpanFrom(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) { TEST(CheckCBORMessage, InvalidStartByte) {
...@@ -86,25 +87,25 @@ TEST(CheckCBORMessage, InvalidStartByte) { ...@@ -86,25 +87,25 @@ TEST(CheckCBORMessage, InvalidStartByte) {
TEST(CheckCBORMessage, InvalidEnvelopes) { TEST(CheckCBORMessage, InvalidEnvelopes) {
std::vector<uint8_t> bytes = {0xd8, 0x5a}; std::vector<uint8_t> bytes = {0xd8, 0x5a};
EXPECT_THAT(CheckCBORMessage(SpanFrom(bytes)), EXPECT_THAT(CheckCBORMessage(SpanFrom(bytes)),
StatusIs(Error::CBOR_INVALID_ENVELOPE, 1)); StatusIs(Error::CBOR_UNEXPECTED_EOF_IN_ENVELOPE, 2));
bytes = {0xd8, 0x5a, 0}; bytes = {0xd8, 0x5a, 0};
EXPECT_THAT(CheckCBORMessage(SpanFrom(bytes)), EXPECT_THAT(CheckCBORMessage(SpanFrom(bytes)),
StatusIs(Error::CBOR_INVALID_ENVELOPE, 1)); StatusIs(Error::CBOR_UNEXPECTED_EOF_IN_ENVELOPE, 3));
bytes = {0xd8, 0x5a, 0, 0}; bytes = {0xd8, 0x5a, 0, 0};
EXPECT_THAT(CheckCBORMessage(SpanFrom(bytes)), 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}; bytes = {0xd8, 0x5a, 0, 0, 0};
EXPECT_THAT(CheckCBORMessage(SpanFrom(bytes)), 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}; bytes = {0xd8, 0x5a, 0, 0, 0, 0};
EXPECT_THAT(CheckCBORMessage(SpanFrom(bytes)), EXPECT_THAT(CheckCBORMessage(SpanFrom(bytes)),
StatusIs(Error::CBOR_INVALID_ENVELOPE, 1)); StatusIs(Error::CBOR_MAP_OR_ARRAY_EXPECTED_IN_ENVELOPE, 6));
} }
TEST(CheckCBORMessage, MapStartExpected) { TEST(CheckCBORMessage, MapStartExpected) {
std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, 1}; std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, 1};
EXPECT_THAT(CheckCBORMessage(SpanFrom(bytes)), 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) { ...@@ -871,12 +872,8 @@ TEST(ParseCBORTest, ParseCBORHelloWorld) {
TEST(ParseCBORTest, UTF8IsSupportedInKeys) { TEST(ParseCBORTest, UTF8IsSupportedInKeys) {
const uint8_t kPayloadLen = 11; const uint8_t kPayloadLen = 11;
std::vector<uint8_t> bytes = {cbor::InitialByteForEnvelope(), std::vector<uint8_t> bytes = {0xd8, 0x5a, // envelope
cbor::InitialByteFor32BitLengthByteString(), 0, 0, 0, kPayloadLen};
0,
0,
0,
kPayloadLen};
bytes.push_back(cbor::EncodeIndefiniteLengthMapStart()); bytes.push_back(cbor::EncodeIndefiniteLengthMapStart());
// Two UTF16 chars. // Two UTF16 chars.
EncodeString8(SpanFrom("🌎"), &bytes); EncodeString8(SpanFrom("🌎"), &bytes);
...@@ -901,7 +898,7 @@ TEST(ParseCBORTest, NoInputError) { ...@@ -901,7 +898,7 @@ TEST(ParseCBORTest, NoInputError) {
std::unique_ptr<ParserHandler> json_writer = std::unique_ptr<ParserHandler> json_writer =
json::NewJSONEncoder(&out, &status); json::NewJSONEncoder(&out, &status);
ParseCBOR(span<uint8_t>(in.data(), in.size()), json_writer.get()); 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); EXPECT_EQ("", out);
} }
...@@ -954,6 +951,39 @@ TEST(ParseCBORTest, UnexpectedEofInMapError) { ...@@ -954,6 +951,39 @@ TEST(ParseCBORTest, UnexpectedEofInMapError) {
EXPECT_EQ("", out); 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) { TEST(ParseCBORTest, NoEmptyEnvelopesAllowed) {
std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, 0}; // envelope std::vector<uint8_t> bytes = {0xd8, 0x5a, 0, 0, 0, 0}; // envelope
std::string out; std::string out;
...@@ -971,7 +1001,7 @@ TEST(ParseCBORTest, OnlyMapsAndArraysSupportedInsideEnvelopes) { ...@@ -971,7 +1001,7 @@ TEST(ParseCBORTest, OnlyMapsAndArraysSupportedInsideEnvelopes) {
// is an envelope that contains just a number (1). We don't // is an envelope that contains just a number (1). We don't
// allow numbers to be contained in an envelope though, only // allow numbers to be contained in an envelope though, only
// maps and arrays. // maps and arrays.
constexpr uint8_t kPayloadLen = 1; constexpr uint8_t kPayloadLen = 8;
std::vector<uint8_t> bytes = {0xd8, std::vector<uint8_t> bytes = {0xd8,
0x5a, 0x5a,
0, 0,
...@@ -1247,6 +1277,69 @@ TEST(ParseCBORTest, EnvelopeContentsLengthMismatch) { ...@@ -1247,6 +1277,69 @@ TEST(ParseCBORTest, EnvelopeContentsLengthMismatch) {
EXPECT_EQ("", out); 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 // cbor::AppendString8EntryToMap - for limited in-place editing of messages
// ============================================================================= // =============================================================================
...@@ -1331,7 +1424,7 @@ TEST(AppendString8EntryToMapTest, InvalidEnvelope_Error) { ...@@ -1331,7 +1424,7 @@ TEST(AppendString8EntryToMapTest, InvalidEnvelope_Error) {
0xd8, 0x7a, 0, 0, 0, 2, EncodeIndefiniteLengthMapStart(), EncodeStop()}; 0xd8, 0x7a, 0, 0, 0, 2, EncodeIndefiniteLengthMapStart(), EncodeStop()};
Status status = Status status =
AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg); 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. { // Invalid envelope size example.
std::vector<uint8_t> msg = { std::vector<uint8_t> msg = {
...@@ -1339,7 +1432,8 @@ TEST(AppendString8EntryToMapTest, InvalidEnvelope_Error) { ...@@ -1339,7 +1432,8 @@ TEST(AppendString8EntryToMapTest, InvalidEnvelope_Error) {
}; };
Status status = Status status =
AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg); 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. { // Invalid envelope size example.
std::vector<uint8_t> msg = { std::vector<uint8_t> msg = {
...@@ -1347,7 +1441,7 @@ TEST(AppendString8EntryToMapTest, InvalidEnvelope_Error) { ...@@ -1347,7 +1441,7 @@ TEST(AppendString8EntryToMapTest, InvalidEnvelope_Error) {
}; };
Status status = Status status =
AppendString8EntryToCBORMap(SpanFrom("key"), SpanFrom("value"), &msg); 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 } // namespace cbor
......
...@@ -78,13 +78,17 @@ DispatchResponse DispatchResponse::ServerError(std::string message) { ...@@ -78,13 +78,17 @@ DispatchResponse DispatchResponse::ServerError(std::string message) {
return result; 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 // 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) { Dispatchable::Dispatchable(span<uint8_t> serialized) : serialized_(serialized) {
Status s = cbor::CheckCBORMessage(serialized); Status s = cbor::CheckCBORMessage(serialized);
if (!s.ok()) { if (!s.ok()) {
...@@ -105,9 +109,8 @@ Dispatchable::Dispatchable(span<uint8_t> serialized) : serialized_(serialized) { ...@@ -105,9 +109,8 @@ Dispatchable::Dispatchable(span<uint8_t> serialized) : serialized_(serialized) {
// expect to see after we're done parsing the envelope contents. // expect to see after we're done parsing the envelope contents.
// This way we can compare and produce an error if the contents // This way we can compare and produce an error if the contents
// didn't fit exactly into the envelope length. // didn't fit exactly into the envelope length.
const size_t pos_past_envelope = tokenizer.Status().pos + const size_t pos_past_envelope =
kEncodedEnvelopeHeaderSize + tokenizer.Status().pos + tokenizer.GetEnvelopeHeader().outer_size();
tokenizer.GetEnvelopeContents().size();
tokenizer.EnterEnvelope(); tokenizer.EnterEnvelope();
if (tokenizer.TokenTag() == cbor::CBORTokenTag::ERROR_VALUE) { if (tokenizer.TokenTag() == cbor::CBORTokenTag::ERROR_VALUE) {
status_ = tokenizer.Status(); status_ = tokenizer.Status();
......
...@@ -38,6 +38,7 @@ enum class DispatchCode { ...@@ -38,6 +38,7 @@ enum class DispatchCode {
INVALID_PARAMS = -32602, INVALID_PARAMS = -32602,
INTERNAL_ERROR = -32603, INTERNAL_ERROR = -32603,
SERVER_ERROR = -32000, SERVER_ERROR = -32000,
SESSION_NOT_FOUND = SERVER_ERROR - 1,
}; };
// Information returned by command handlers. Usually returned after command // Information returned by command handlers. Usually returned after command
...@@ -76,6 +77,10 @@ class DispatchResponse { ...@@ -76,6 +77,10 @@ class DispatchResponse {
// Used for application level errors, e.g. within protocol agents. // Used for application level errors, e.g. within protocol agents.
static DispatchResponse ServerError(std::string message); 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: private:
DispatchResponse() = default; DispatchResponse() = default;
DispatchCode code_; DispatchCode code_;
......
...@@ -27,6 +27,13 @@ TEST(DispatchResponseTest, ServerError) { ...@@ -27,6 +27,13 @@ TEST(DispatchResponseTest, ServerError) {
EXPECT_EQ("Oops!", error.Message()); 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) { TEST(DispatchResponseTest, InternalError) {
DispatchResponse error = DispatchResponse::InternalError(); DispatchResponse error = DispatchResponse::InternalError();
EXPECT_FALSE(error.IsSuccess()); EXPECT_FALSE(error.IsSuccess());
......
...@@ -58,16 +58,16 @@ std::string Status::Message() const { ...@@ -58,16 +58,16 @@ std::string Status::Message() const {
return "CBOR: invalid binary"; return "CBOR: invalid binary";
case Error::CBOR_UNSUPPORTED_VALUE: case Error::CBOR_UNSUPPORTED_VALUE:
return "CBOR: unsupported value"; return "CBOR: unsupported value";
case Error::CBOR_NO_INPUT: case Error::CBOR_UNEXPECTED_EOF_IN_ENVELOPE:
return "CBOR: no input"; return "CBOR: unexpected EOF reading envelope";
case Error::CBOR_INVALID_START_BYTE: case Error::CBOR_INVALID_START_BYTE:
return "CBOR: invalid start byte"; return "CBOR: invalid start byte";
case Error::CBOR_UNEXPECTED_EOF_EXPECTED_VALUE: 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: 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: 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: case Error::CBOR_INVALID_MAP_KEY:
return "CBOR: invalid map key"; return "CBOR: invalid map key";
case Error::CBOR_DUPLICATE_MAP_KEY: case Error::CBOR_DUPLICATE_MAP_KEY:
......
...@@ -46,7 +46,7 @@ enum class Error { ...@@ -46,7 +46,7 @@ enum class Error {
CBOR_INVALID_STRING16 = 0x14, CBOR_INVALID_STRING16 = 0x14,
CBOR_INVALID_BINARY = 0x15, CBOR_INVALID_BINARY = 0x15,
CBOR_UNSUPPORTED_VALUE = 0x16, CBOR_UNSUPPORTED_VALUE = 0x16,
CBOR_NO_INPUT = 0x17, CBOR_UNEXPECTED_EOF_IN_ENVELOPE = 0x17,
CBOR_INVALID_START_BYTE = 0x18, CBOR_INVALID_START_BYTE = 0x18,
CBOR_UNEXPECTED_EOF_EXPECTED_VALUE = 0x19, CBOR_UNEXPECTED_EOF_EXPECTED_VALUE = 0x19,
CBOR_UNEXPECTED_EOF_IN_ARRAY = 0x1a, CBOR_UNEXPECTED_EOF_IN_ARRAY = 0x1a,
......
...@@ -31,7 +31,6 @@ using {{config.crdtp.namespace}}::cbor::EncodeStop; ...@@ -31,7 +31,6 @@ using {{config.crdtp.namespace}}::cbor::EncodeStop;
using {{config.crdtp.namespace}}::cbor::EncodeString8; using {{config.crdtp.namespace}}::cbor::EncodeString8;
using {{config.crdtp.namespace}}::cbor::EncodeTrue; using {{config.crdtp.namespace}}::cbor::EncodeTrue;
using {{config.crdtp.namespace}}::cbor::EnvelopeEncoder; using {{config.crdtp.namespace}}::cbor::EnvelopeEncoder;
using {{config.crdtp.namespace}}::cbor::InitialByteForEnvelope;
} // namespace cbor } // namespace cbor
// Uses the parsing events received from driver of |ParserHandler| // 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