Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

114
deps/inspector_protocol/crdtp/README.md vendored Normal file
View File

@ -0,0 +1,114 @@
# CRDTP - Chrome DevTools Protocol Library.
[Canonical location for this library.](https://chromium.googlesource.com/deps/inspector_protocol/+/refs/heads/main)
This is a support library for the Chrome DevTools protocol implementation.
It's used from within the Jinja templates which we use for code generation
(see ../lib and ../templates) as well as from Chromium (headless,
chrome, content, blink), V8, and other code bases that use the DevTools
protocol.
The library is designed to be portable. The only allowed dependencies are:
- The C/C++ standard libraries, up to C++14.
The litmus test is that it compiles and passes tests for all platforms
supported by V8.
- For testing, we depend on mini_chromium and gtest. This is isolated
into the `crdtp/test_platform.{h,cc}` library.
We support 32 bit and 64 bit architectures.
# Common types used in this library.
- `uint8_t`: a byte, e.g. for raw bytes or UTF8 characters
- `uint16_t`: two bytes, e.g. for UTF16 characters
For input parameters:
- `span<uint8_t>`: pointer to bytes and length
- `span<uint16_t>`: pointer to UTF16 chars and length
For output parameters:
- `std::vector<uint8_t>` - Owned segment of bytes / utf8 characters and length.
- `std::string` - Same, for compatibility, even though char is signed.
# Building and running the tests.
If you're familiar with
[Chromium's development process](https://www.chromium.org/developers/contributing-code)
and have the depot_tools installed, you may use these commands
to fetch the package (and dependencies) and build and run the tests:
fetch inspector_protocol
cd src
gn gen out/Release
ninja -C out/Release crdtp_test
out/Release/crdtp_test
You'll probably also need to install g++, since Clang uses this to find the
standard C++ headers. E.g.,
sudo apt-get install g++-8
# Purpose of the tests
crdtp comes with unittest coverage.
Upstream, in this standalone package, the unittests make development
more pleasant because they are very fast and light (try the previous
section to see).
Downstream (in Chromium, V8, etc.), they ensure that the library behaves
correctly within each specific code base. We have seen bugs from different
architectures / compilers / etc. in the past. We have also seen
that a tweaked downstream crdtp_platform library did not behave correctly,
becaues V8's strtod routine interprets out of range literals as 'inf'.
Thus, the unittests function as a conformance test suite for such code-base
specific tweaks downstream.
# Customization by downstream users (Chrome, V8, google3, etc.).
Downstream users may need to customize the library. We isolate these typical
customizations into two platform libraries (crdtp_plaform and
crdtp_test_platform), to reduce the chance of merge conflicts and grief when
rolling as much as possible. While customized platform libraries may
depend on the downstream code base (e.g. abseil, Chromium's base, V8's utility
functions, Boost, etc.), they are not exposed to the headers that
downstream code depends on.
## crdtp_platform
This platform library is only used by the crdtp library; it is not part of the
crdtp API. Thus far it consists only of json_platform.h and json_platform.cc,
because conversion between a `std::string` and a double is tricky, and different
code bases have different preferences in this regard. In this repository
(upstream), json_platform.cc provides a reference implementation which uses the
C++ standard library.
Downstream, in Chromium, json_platform_chromium.cc has a different
implementation that uses the routines in Chromium's //base, that is, it's a .cc
file that's specific to Chromium. Similarly, in V8, json_platform_v8.cc uses
V8's number conversion utilities, so it's a .cc file that's specific to V8. And
in google3, we use the absl library. crdtp/json_platform.cc is designed to be
easy to modify or replace, and the interface defined by its header is designed
to be stable.
## crdtp_test_platform
This platform library is only used by the tests. Upstream, it's setup to
use mini_chromium and gtest. Downstream, Chromium uses its //base libraries,
and V8 uses theirs; and a small amount of tweaking is needed in each code
base - e.g., Chromium, V8, and google3 each place `#include` declarations into
test_platform.h that are specific to their code base, and they have their
own routines in test_platform.cc which uses their own libraries.
The purpose of crdtp_test_platform is to isolate the tweaking to this small,
stable library (modifying test_platform.h and test_platform.cc). This avoids
having to modify the actual tests (json_test.cc, cbor_test.cc, ...)
when rolling changes downstream. We try to not use patch files.

1078
deps/inspector_protocol/crdtp/cbor.cc vendored Normal file

File diff suppressed because it is too large Load Diff

329
deps/inspector_protocol/crdtp/cbor.h vendored Normal file
View File

@ -0,0 +1,329 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CRDTP_CBOR_H_
#define CRDTP_CBOR_H_
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#include "export.h"
#include "parser_handler.h"
#include "span.h"
namespace crdtp {
namespace cbor {
// The binary encoding for the inspector protocol follows the CBOR specification
// (RFC 7049). Additional constraints:
// - Only indefinite length maps and arrays are supported.
// - Maps and arrays are wrapped with an envelope, that is, a
// CBOR tag with value 24 followed by a byte string specifying
// the byte length of the enclosed map / array. The byte string
// must use a 32 bit wide length.
// - At the top level, a message must be an indefinite length map
// wrapped by an envelope.
// - Maximal size for messages is 2^32 (4 GB).
// - For scalars, we support only the int32_t range, encoded as
// UNSIGNED/NEGATIVE (major types 0 / 1).
// - UTF16 strings, including with unbalanced surrogate pairs, are encoded
// as CBOR BYTE_STRING (major type 2). For such strings, the number of
// bytes encoded must be even.
// - UTF8 strings (major type 3) are supported.
// - 7 bit US-ASCII strings must always be encoded as UTF8 strings, never
// as UTF16 strings.
// - Arbitrary byte arrays, in the inspector protocol called 'binary',
// are encoded as BYTE_STRING (major type 2), prefixed with a byte
// indicating base64 when rendered as JSON.
// =============================================================================
// Detecting CBOR content
// =============================================================================
// Checks whether |msg| is a cbor message.
CRDTP_EXPORT bool IsCBORMessage(span<uint8_t> msg);
// Performs a leightweight check of |msg|.
// Disallows:
// - Empty message
// - Not starting with the two bytes 0xd8, 0x5a
// - Empty envelope (all length bytes are 0)
// - Not starting with a map after the envelope stanza
// DevTools messages should pass this check.
CRDTP_EXPORT Status CheckCBORMessage(span<uint8_t> msg);
// =============================================================================
// Encoding individual CBOR items
// =============================================================================
// Some constants for CBOR tokens that only take a single byte on the wire.
CRDTP_EXPORT uint8_t EncodeTrue();
CRDTP_EXPORT uint8_t EncodeFalse();
CRDTP_EXPORT uint8_t EncodeNull();
CRDTP_EXPORT uint8_t EncodeIndefiniteLengthArrayStart();
CRDTP_EXPORT uint8_t EncodeIndefiniteLengthMapStart();
CRDTP_EXPORT uint8_t EncodeStop();
// Encodes |value| as |UNSIGNED| (major type 0) iff >= 0, or |NEGATIVE|
// (major type 1) iff < 0.
CRDTP_EXPORT void EncodeInt32(int32_t value, std::vector<uint8_t>* out);
// Encodes a UTF16 string as a BYTE_STRING (major type 2). Each utf16
// character in |in| is emitted with most significant byte first,
// appending to |out|.
CRDTP_EXPORT void EncodeString16(span<uint16_t> in, std::vector<uint8_t>* out);
// Encodes a UTF8 string |in| as STRING (major type 3).
CRDTP_EXPORT void EncodeString8(span<uint8_t> in, std::vector<uint8_t>* out);
// Encodes the given |latin1| string as STRING8.
// If any non-ASCII character is present, it will be represented
// as a 2 byte UTF8 sequence.
CRDTP_EXPORT void EncodeFromLatin1(span<uint8_t> latin1,
std::vector<uint8_t>* out);
// Encodes the given |utf16| string as STRING8 if it's entirely US-ASCII.
// Otherwise, encodes as STRING16.
CRDTP_EXPORT void EncodeFromUTF16(span<uint16_t> utf16,
std::vector<uint8_t>* out);
// Encodes arbitrary binary data in |in| as a BYTE_STRING (major type 2) with
// definitive length, prefixed with tag 22 indicating expected conversion to
// base64 (see RFC 7049, Table 3 and Section 2.4.4.2).
CRDTP_EXPORT void EncodeBinary(span<uint8_t> in, std::vector<uint8_t>* out);
// Encodes / decodes a double as Major type 7 (SIMPLE_VALUE),
// with additional info = 27, followed by 8 bytes in big endian.
CRDTP_EXPORT void EncodeDouble(double value, std::vector<uint8_t>* out);
// =============================================================================
// cbor::EnvelopeEncoder - for wrapping submessages
// =============================================================================
// An envelope indicates the byte length of a wrapped item.
// We use this for maps and array, which allows the decoder
// to skip such (nested) values whole sale.
// It's implemented as a CBOR tag (major type 6) with additional
// info = 24, followed by a byte string with a 32 bit length value;
// so the maximal structure that we can wrap is 2^32 bits long.
// See also: https://tools.ietf.org/html/rfc7049#section-2.4.4.1
class CRDTP_EXPORT EnvelopeEncoder {
public:
// Emits the envelope start bytes and records the position for the
// byte size in |byte_size_pos_|. Also emits empty bytes for the
// byte sisze so that encoding can continue.
void EncodeStart(std::vector<uint8_t>* out);
// This records the current size in |out| at position byte_size_pos_.
// Returns true iff successful.
bool EncodeStop(std::vector<uint8_t>* out);
private:
size_t byte_size_pos_ = 0;
};
class CRDTP_EXPORT 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
// =============================================================================
// This can be used to convert to CBOR, by passing the return value to a parser
// that drives it. The handler will encode into |out|, and iff an error occurs
// it will set |status| to an error and clear |out|. Otherwise, |status.ok()|
// will be |true|.
CRDTP_EXPORT std::unique_ptr<ParserHandler> NewCBOREncoder(
std::vector<uint8_t>* out,
Status* status);
// =============================================================================
// cbor::CBORTokenizer - for parsing individual CBOR items
// =============================================================================
// Tags for the tokens within a CBOR message that CBORTokenizer understands.
// Note that this is not the same terminology as the CBOR spec (RFC 7049),
// but rather, our adaptation. For instance, we lump unsigned and signed
// major type into INT32 here (and disallow values outside the int32_t range).
enum class CBORTokenTag {
// Encountered an error in the structure of the message. Consult
// status() for details.
ERROR_VALUE,
// Booleans and NULL.
TRUE_VALUE,
FALSE_VALUE,
NULL_VALUE,
// An int32_t (signed 32 bit integer).
INT32,
// A double (64 bit floating point).
DOUBLE,
// A UTF8 string.
STRING8,
// A UTF16 string.
STRING16,
// A binary string.
BINARY,
// Starts an indefinite length map; after the map start we expect
// alternating keys and values, followed by STOP.
MAP_START,
// Starts an indefinite length array; after the array start we
// expect values, followed by STOP.
ARRAY_START,
// Ends a map or an array.
STOP,
// An envelope indicator, wrapping a map or array.
// Internally this carries the byte length of the wrapped
// map or array. While CBORTokenizer::Next() will read / skip the entire
// envelope, CBORTokenizer::EnterEnvelope() reads the tokens
// inside of it.
ENVELOPE,
// We've reached the end there is nothing else to read.
DONE,
};
// The major types from RFC 7049 Section 2.1.
enum class MajorType {
UNSIGNED = 0,
NEGATIVE = 1,
BYTE_STRING = 2,
STRING = 3,
ARRAY = 4,
MAP = 5,
TAG = 6,
SIMPLE_VALUE = 7
};
// CBORTokenizer segments a CBOR message, presenting the tokens therein as
// numbers, strings, etc. This is not a complete CBOR parser, but makes it much
// easier to implement one (e.g. ParseCBOR, above). It can also be used to parse
// messages partially.
class CRDTP_EXPORT CBORTokenizer {
public:
explicit CBORTokenizer(span<uint8_t> bytes);
~CBORTokenizer();
// Identifies the current token that we're looking at,
// or ERROR_VALUE (in which ase ::Status() has details)
// or DONE (if we're past the last token).
CBORTokenTag TokenTag() const;
// Advances to the next token.
void Next();
// Can only be called if TokenTag() == CBORTokenTag::ENVELOPE.
// While Next() would skip past the entire envelope / what it's
// wrapping, EnterEnvelope positions the cursor inside of the envelope,
// letting the client explore the nested structure.
void EnterEnvelope();
// If TokenTag() is CBORTokenTag::ERROR_VALUE, then Status().error describes
// the error more precisely; otherwise it'll be set to Error::OK.
// In either case, Status().pos is the current position.
struct Status Status() const;
// The following methods retrieve the token values. They can only
// be called if TokenTag() matches.
// To be called only if ::TokenTag() == CBORTokenTag::INT32.
int32_t GetInt32() const;
// To be called only if ::TokenTag() == CBORTokenTag::DOUBLE.
double GetDouble() const;
// To be called only if ::TokenTag() == CBORTokenTag::STRING8.
span<uint8_t> GetString8() const;
// Wire representation for STRING16 is low byte first (little endian).
// To be called only if ::TokenTag() == CBORTokenTag::STRING16.
span<uint8_t> GetString16WireRep() const;
// To be called only if ::TokenTag() == CBORTokenTag::BINARY.
span<uint8_t> GetBinary() const;
// To be called only if ::TokenTag() == CBORTokenTag::ENVELOPE.
// Returns the envelope including its payload; message which
// can be passed to the CBORTokenizer constructor, which will
// then see the envelope token first (looking at it a second time,
// basically).
span<uint8_t> GetEnvelope() const;
// To be called only if ::TokenTag() == CBORTokenTag::ENVELOPE.
// Returns only the payload inside the envelope, e.g., a map
// or an array. This is not a complete message by our
// IsCBORMessage definition, since it doesn't include the
// 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();
void SetToken(CBORTokenTag token, size_t token_byte_length);
void SetError(Error error);
const span<uint8_t> bytes_;
CBORTokenTag token_tag_;
struct Status status_;
size_t token_byte_length_ = 0;
MajorType token_start_type_;
uint64_t token_start_internal_value_;
EnvelopeHeader envelope_header_;
};
// =============================================================================
// cbor::ParseCBOR - for receiving streaming parser events for CBOR messages
// =============================================================================
// Parses a CBOR encoded message from |bytes|, sending events to
// |out|. If an error occurs, sends |out->HandleError|, and parsing stops.
// The client is responsible for discarding the already received information in
// that case.
CRDTP_EXPORT void ParseCBOR(span<uint8_t> bytes, ParserHandler* out);
// =============================================================================
// cbor::AppendString8EntryToMap - for limited in-place editing of messages
// =============================================================================
// Modifies the |cbor| message by appending a new key/value entry at the end
// of the map. Patches up the envelope size; Status.ok() iff successful.
// If not successful, |cbor| may be corrupted after this call.
CRDTP_EXPORT Status AppendString8EntryToCBORMap(span<uint8_t> string8_key,
span<uint8_t> string8_value,
std::vector<uint8_t>* cbor);
namespace internals { // Exposed only for writing tests.
CRDTP_EXPORT size_t ReadTokenStart(span<uint8_t> bytes,
cbor::MajorType* type,
uint64_t* value);
CRDTP_EXPORT void WriteTokenStart(cbor::MajorType type,
uint64_t value,
std::vector<uint8_t>* encoded);
} // namespace internals
} // namespace cbor
} // namespace crdtp
#endif // CRDTP_CBOR_H_

1449
deps/inspector_protocol/crdtp/cbor_test.cc vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,584 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "dispatch.h"
#include <cassert>
#include "cbor.h"
#include "error_support.h"
#include "find_by_first.h"
#include "frontend_channel.h"
#include "protocol_core.h"
namespace crdtp {
// =============================================================================
// DispatchResponse - Error status and chaining / fall through
// =============================================================================
// static
DispatchResponse DispatchResponse::Success() {
DispatchResponse result;
result.code_ = DispatchCode::SUCCESS;
return result;
}
// static
DispatchResponse DispatchResponse::FallThrough() {
DispatchResponse result;
result.code_ = DispatchCode::FALL_THROUGH;
return result;
}
// static
DispatchResponse DispatchResponse::ParseError(std::string message) {
DispatchResponse result;
result.code_ = DispatchCode::PARSE_ERROR;
result.message_ = std::move(message);
return result;
}
// static
DispatchResponse DispatchResponse::InvalidRequest(std::string message) {
DispatchResponse result;
result.code_ = DispatchCode::INVALID_REQUEST;
result.message_ = std::move(message);
return result;
}
// static
DispatchResponse DispatchResponse::MethodNotFound(std::string message) {
DispatchResponse result;
result.code_ = DispatchCode::METHOD_NOT_FOUND;
result.message_ = std::move(message);
return result;
}
// static
DispatchResponse DispatchResponse::InvalidParams(std::string message) {
DispatchResponse result;
result.code_ = DispatchCode::INVALID_PARAMS;
result.message_ = std::move(message);
return result;
}
// static
DispatchResponse DispatchResponse::InternalError() {
DispatchResponse result;
result.code_ = DispatchCode::INTERNAL_ERROR;
result.message_ = "Internal error";
return result;
}
// static
DispatchResponse DispatchResponse::ServerError(std::string message) {
DispatchResponse result;
result.code_ = DispatchCode::SERVER_ERROR;
result.message_ = std::move(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
// =============================================================================
Dispatchable::Dispatchable(span<uint8_t> serialized) : serialized_(serialized) {
Status s = cbor::CheckCBORMessage(serialized);
if (!s.ok()) {
status_ = {Error::MESSAGE_MUST_BE_AN_OBJECT, s.pos};
return;
}
cbor::CBORTokenizer tokenizer(serialized);
if (tokenizer.TokenTag() == cbor::CBORTokenTag::ERROR_VALUE) {
status_ = tokenizer.Status();
return;
}
// We checked for the envelope start byte above, so the tokenizer
// must agree here, since it's not an error.
assert(tokenizer.TokenTag() == cbor::CBORTokenTag::ENVELOPE);
// Before we enter the envelope, we save the position that we
// 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 + tokenizer.GetEnvelopeHeader().outer_size();
tokenizer.EnterEnvelope();
if (tokenizer.TokenTag() == cbor::CBORTokenTag::ERROR_VALUE) {
status_ = tokenizer.Status();
return;
}
if (tokenizer.TokenTag() != cbor::CBORTokenTag::MAP_START) {
status_ = {Error::MESSAGE_MUST_BE_AN_OBJECT, tokenizer.Status().pos};
return;
}
assert(tokenizer.TokenTag() == cbor::CBORTokenTag::MAP_START);
tokenizer.Next(); // Now we should be pointed at the map key.
while (tokenizer.TokenTag() != cbor::CBORTokenTag::STOP) {
switch (tokenizer.TokenTag()) {
case cbor::CBORTokenTag::DONE:
status_ =
Status{Error::CBOR_UNEXPECTED_EOF_IN_MAP, tokenizer.Status().pos};
return;
case cbor::CBORTokenTag::ERROR_VALUE:
status_ = tokenizer.Status();
return;
case cbor::CBORTokenTag::STRING8:
if (!MaybeParseProperty(&tokenizer))
return;
break;
default:
// We require the top-level keys to be UTF8 (US-ASCII in practice).
status_ = Status{Error::CBOR_INVALID_MAP_KEY, tokenizer.Status().pos};
return;
}
}
tokenizer.Next();
if (!has_call_id_) {
status_ = Status{Error::MESSAGE_MUST_HAVE_INTEGER_ID_PROPERTY,
tokenizer.Status().pos};
return;
}
if (method_.empty()) {
status_ = Status{Error::MESSAGE_MUST_HAVE_STRING_METHOD_PROPERTY,
tokenizer.Status().pos};
return;
}
// The contents of the envelope parsed OK, now check that we're at
// the expected position.
if (pos_past_envelope != tokenizer.Status().pos) {
status_ = Status{Error::CBOR_ENVELOPE_CONTENTS_LENGTH_MISMATCH,
tokenizer.Status().pos};
return;
}
if (tokenizer.TokenTag() != cbor::CBORTokenTag::DONE) {
status_ = Status{Error::CBOR_TRAILING_JUNK, tokenizer.Status().pos};
return;
}
}
bool Dispatchable::ok() const {
return status_.ok();
}
DispatchResponse Dispatchable::DispatchError() const {
// TODO(johannes): Replace with DCHECK / similar?
if (status_.ok())
return DispatchResponse::Success();
if (status_.IsMessageError())
return DispatchResponse::InvalidRequest(status_.Message());
return DispatchResponse::ParseError(status_.ToASCIIString());
}
bool Dispatchable::MaybeParseProperty(cbor::CBORTokenizer* tokenizer) {
span<uint8_t> property_name = tokenizer->GetString8();
if (SpanEquals(SpanFrom("id"), property_name))
return MaybeParseCallId(tokenizer);
if (SpanEquals(SpanFrom("method"), property_name))
return MaybeParseMethod(tokenizer);
if (SpanEquals(SpanFrom("params"), property_name))
return MaybeParseParams(tokenizer);
if (SpanEquals(SpanFrom("sessionId"), property_name))
return MaybeParseSessionId(tokenizer);
status_ =
Status{Error::MESSAGE_HAS_UNKNOWN_PROPERTY, tokenizer->Status().pos};
return false;
}
bool Dispatchable::MaybeParseCallId(cbor::CBORTokenizer* tokenizer) {
if (has_call_id_) {
status_ = Status{Error::CBOR_DUPLICATE_MAP_KEY, tokenizer->Status().pos};
return false;
}
tokenizer->Next();
if (tokenizer->TokenTag() != cbor::CBORTokenTag::INT32) {
status_ = Status{Error::MESSAGE_MUST_HAVE_INTEGER_ID_PROPERTY,
tokenizer->Status().pos};
return false;
}
call_id_ = tokenizer->GetInt32();
has_call_id_ = true;
tokenizer->Next();
return true;
}
bool Dispatchable::MaybeParseMethod(cbor::CBORTokenizer* tokenizer) {
if (!method_.empty()) {
status_ = Status{Error::CBOR_DUPLICATE_MAP_KEY, tokenizer->Status().pos};
return false;
}
tokenizer->Next();
if (tokenizer->TokenTag() != cbor::CBORTokenTag::STRING8) {
status_ = Status{Error::MESSAGE_MUST_HAVE_STRING_METHOD_PROPERTY,
tokenizer->Status().pos};
return false;
}
method_ = tokenizer->GetString8();
tokenizer->Next();
return true;
}
bool Dispatchable::MaybeParseParams(cbor::CBORTokenizer* tokenizer) {
if (params_seen_) {
status_ = Status{Error::CBOR_DUPLICATE_MAP_KEY, tokenizer->Status().pos};
return false;
}
params_seen_ = true;
tokenizer->Next();
if (tokenizer->TokenTag() == cbor::CBORTokenTag::NULL_VALUE) {
tokenizer->Next();
return true;
}
if (tokenizer->TokenTag() != cbor::CBORTokenTag::ENVELOPE) {
status_ = Status{Error::MESSAGE_MAY_HAVE_OBJECT_PARAMS_PROPERTY,
tokenizer->Status().pos};
return false;
}
params_ = tokenizer->GetEnvelope();
tokenizer->Next();
return true;
}
bool Dispatchable::MaybeParseSessionId(cbor::CBORTokenizer* tokenizer) {
if (!session_id_.empty()) {
status_ = Status{Error::CBOR_DUPLICATE_MAP_KEY, tokenizer->Status().pos};
return false;
}
tokenizer->Next();
if (tokenizer->TokenTag() != cbor::CBORTokenTag::STRING8) {
status_ = Status{Error::MESSAGE_MAY_HAVE_STRING_SESSION_ID_PROPERTY,
tokenizer->Status().pos};
return false;
}
session_id_ = tokenizer->GetString8();
tokenizer->Next();
return true;
}
namespace {
class ProtocolError : public Serializable {
public:
explicit ProtocolError(DispatchResponse dispatch_response)
: dispatch_response_(std::move(dispatch_response)) {}
void AppendSerialized(std::vector<uint8_t>* out) const override {
Status status;
std::unique_ptr<ParserHandler> encoder = cbor::NewCBOREncoder(out, &status);
encoder->HandleMapBegin();
if (has_call_id_) {
encoder->HandleString8(SpanFrom("id"));
encoder->HandleInt32(call_id_);
}
encoder->HandleString8(SpanFrom("error"));
encoder->HandleMapBegin();
encoder->HandleString8(SpanFrom("code"));
encoder->HandleInt32(static_cast<int32_t>(dispatch_response_.Code()));
encoder->HandleString8(SpanFrom("message"));
encoder->HandleString8(SpanFrom(dispatch_response_.Message()));
if (!data_.empty()) {
encoder->HandleString8(SpanFrom("data"));
encoder->HandleString8(SpanFrom(data_));
}
encoder->HandleMapEnd();
encoder->HandleMapEnd();
assert(status.ok());
}
void SetCallId(int call_id) {
has_call_id_ = true;
call_id_ = call_id;
}
void SetData(std::string data) { data_ = std::move(data); }
private:
const DispatchResponse dispatch_response_;
std::string data_;
int call_id_ = 0;
bool has_call_id_ = false;
};
} // namespace
// =============================================================================
// Helpers for creating protocol cresponses and notifications.
// =============================================================================
std::unique_ptr<Serializable> CreateErrorResponse(
int call_id,
DispatchResponse dispatch_response) {
auto protocol_error =
std::make_unique<ProtocolError>(std::move(dispatch_response));
protocol_error->SetCallId(call_id);
return protocol_error;
}
std::unique_ptr<Serializable> CreateErrorResponse(
int call_id,
DispatchResponse dispatch_response,
const DeserializerState& state) {
auto protocol_error =
std::make_unique<ProtocolError>(std::move(dispatch_response));
protocol_error->SetCallId(call_id);
// TODO(caseq): should we plumb the call name here?
protocol_error->SetData(state.ErrorMessage(MakeSpan("params")));
return protocol_error;
}
std::unique_ptr<Serializable> CreateErrorNotification(
DispatchResponse dispatch_response) {
return std::make_unique<ProtocolError>(std::move(dispatch_response));
}
namespace {
class Response : public Serializable {
public:
Response(int call_id, std::unique_ptr<Serializable> params)
: call_id_(call_id), params_(std::move(params)) {}
void AppendSerialized(std::vector<uint8_t>* out) const override {
Status status;
std::unique_ptr<ParserHandler> encoder = cbor::NewCBOREncoder(out, &status);
encoder->HandleMapBegin();
encoder->HandleString8(SpanFrom("id"));
encoder->HandleInt32(call_id_);
encoder->HandleString8(SpanFrom("result"));
if (params_) {
params_->AppendSerialized(out);
} else {
encoder->HandleMapBegin();
encoder->HandleMapEnd();
}
encoder->HandleMapEnd();
assert(status.ok());
}
private:
const int call_id_;
std::unique_ptr<Serializable> params_;
};
class Notification : public Serializable {
public:
Notification(const char* method, std::unique_ptr<Serializable> params)
: method_(method), params_(std::move(params)) {}
void AppendSerialized(std::vector<uint8_t>* out) const override {
Status status;
std::unique_ptr<ParserHandler> encoder = cbor::NewCBOREncoder(out, &status);
encoder->HandleMapBegin();
encoder->HandleString8(SpanFrom("method"));
encoder->HandleString8(SpanFrom(method_));
encoder->HandleString8(SpanFrom("params"));
if (params_) {
params_->AppendSerialized(out);
} else {
encoder->HandleMapBegin();
encoder->HandleMapEnd();
}
encoder->HandleMapEnd();
assert(status.ok());
}
private:
const char* method_;
std::unique_ptr<Serializable> params_;
};
} // namespace
std::unique_ptr<Serializable> CreateResponse(
int call_id,
std::unique_ptr<Serializable> params) {
return std::make_unique<Response>(call_id, std::move(params));
}
std::unique_ptr<Serializable> CreateNotification(
const char* method,
std::unique_ptr<Serializable> params) {
return std::make_unique<Notification>(method, std::move(params));
}
// =============================================================================
// DomainDispatcher - Dispatching betwen protocol methods within a domain.
// =============================================================================
DomainDispatcher::WeakPtr::WeakPtr(DomainDispatcher* dispatcher)
: dispatcher_(dispatcher) {}
DomainDispatcher::WeakPtr::~WeakPtr() {
if (dispatcher_)
dispatcher_->weak_ptrs_.erase(this);
}
DomainDispatcher::Callback::~Callback() = default;
void DomainDispatcher::Callback::dispose() {
backend_impl_ = nullptr;
}
DomainDispatcher::Callback::Callback(
std::unique_ptr<DomainDispatcher::WeakPtr> backend_impl,
int call_id,
span<uint8_t> method,
span<uint8_t> message)
: backend_impl_(std::move(backend_impl)),
call_id_(call_id),
method_(method),
message_(message.begin(), message.end()) {}
void DomainDispatcher::Callback::sendIfActive(
std::unique_ptr<Serializable> partialMessage,
const DispatchResponse& response) {
if (!backend_impl_ || !backend_impl_->get())
return;
backend_impl_->get()->sendResponse(call_id_, response,
std::move(partialMessage));
backend_impl_ = nullptr;
}
void DomainDispatcher::Callback::fallThroughIfActive() {
if (!backend_impl_ || !backend_impl_->get())
return;
backend_impl_->get()->channel()->FallThrough(call_id_, method_,
SpanFrom(message_));
backend_impl_ = nullptr;
}
DomainDispatcher::DomainDispatcher(FrontendChannel* frontendChannel)
: frontend_channel_(frontendChannel) {}
DomainDispatcher::~DomainDispatcher() {
clearFrontend();
}
void DomainDispatcher::sendResponse(int call_id,
const DispatchResponse& response,
std::unique_ptr<Serializable> result) {
if (!frontend_channel_)
return;
std::unique_ptr<Serializable> serializable;
if (response.IsError()) {
serializable = CreateErrorResponse(call_id, response);
} else {
serializable = CreateResponse(call_id, std::move(result));
}
frontend_channel_->SendProtocolResponse(call_id, std::move(serializable));
}
void DomainDispatcher::ReportInvalidParams(const Dispatchable& dispatchable,
const DeserializerState& state) {
assert(!state.status().ok());
if (frontend_channel_) {
frontend_channel_->SendProtocolResponse(
dispatchable.CallId(),
CreateErrorResponse(
dispatchable.CallId(),
DispatchResponse::InvalidParams("Invalid parameters"), state));
}
}
void DomainDispatcher::clearFrontend() {
frontend_channel_ = nullptr;
for (auto& weak : weak_ptrs_)
weak->dispose();
weak_ptrs_.clear();
}
std::unique_ptr<DomainDispatcher::WeakPtr> DomainDispatcher::weakPtr() {
auto weak = std::make_unique<DomainDispatcher::WeakPtr>(this);
weak_ptrs_.insert(weak.get());
return weak;
}
// =============================================================================
// UberDispatcher - dispatches between domains (backends).
// =============================================================================
UberDispatcher::DispatchResult::DispatchResult(bool method_found,
std::function<void()> runnable)
: method_found_(method_found), runnable_(runnable) {}
void UberDispatcher::DispatchResult::Run() {
if (!runnable_)
return;
runnable_();
runnable_ = nullptr;
}
UberDispatcher::UberDispatcher(FrontendChannel* frontend_channel)
: frontend_channel_(frontend_channel) {
assert(frontend_channel);
}
UberDispatcher::~UberDispatcher() = default;
constexpr size_t kNotFound = std::numeric_limits<size_t>::max();
namespace {
size_t DotIdx(span<uint8_t> method) {
const void* p = memchr(method.data(), '.', method.size());
return p ? reinterpret_cast<const uint8_t*>(p) - method.data() : kNotFound;
}
} // namespace
UberDispatcher::DispatchResult UberDispatcher::Dispatch(
const Dispatchable& dispatchable) const {
span<uint8_t> method = FindByFirst(redirects_, dispatchable.Method(),
/*default_value=*/dispatchable.Method());
size_t dot_idx = DotIdx(method);
if (dot_idx != kNotFound) {
span<uint8_t> domain = method.subspan(0, dot_idx);
span<uint8_t> command = method.subspan(dot_idx + 1);
DomainDispatcher* dispatcher = FindByFirst(dispatchers_, domain);
if (dispatcher) {
std::function<void(const Dispatchable&)> dispatched =
dispatcher->Dispatch(command);
if (dispatched) {
return DispatchResult(
true, [dispatchable, dispatched = std::move(dispatched)]() {
dispatched(dispatchable);
});
}
}
}
return DispatchResult(false, [this, dispatchable]() {
frontend_channel_->SendProtocolResponse(
dispatchable.CallId(),
CreateErrorResponse(dispatchable.CallId(),
DispatchResponse::MethodNotFound(
"'" +
std::string(dispatchable.Method().begin(),
dispatchable.Method().end()) +
"' wasn't found")));
});
}
template <typename T>
struct FirstLessThan {
bool operator()(const std::pair<span<uint8_t>, T>& left,
const std::pair<span<uint8_t>, T>& right) {
return SpanLessThan(left.first, right.first);
}
};
void UberDispatcher::WireBackend(
span<uint8_t> domain,
const std::vector<std::pair<span<uint8_t>, span<uint8_t>>>&
sorted_redirects,
std::unique_ptr<DomainDispatcher> dispatcher) {
auto it = redirects_.insert(redirects_.end(), sorted_redirects.begin(),
sorted_redirects.end());
std::inplace_merge(redirects_.begin(), it, redirects_.end(),
FirstLessThan<span<uint8_t>>());
auto jt = dispatchers_.insert(dispatchers_.end(),
std::make_pair(domain, std::move(dispatcher)));
std::inplace_merge(dispatchers_.begin(), jt, dispatchers_.end(),
FirstLessThan<std::unique_ptr<DomainDispatcher>>());
}
} // namespace crdtp

313
deps/inspector_protocol/crdtp/dispatch.h vendored Normal file
View File

@ -0,0 +1,313 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CRDTP_DISPATCH_H_
#define CRDTP_DISPATCH_H_
#include <cassert>
#include <cstdint>
#include <functional>
#include <string>
#include <unordered_set>
#include "export.h"
#include "serializable.h"
#include "span.h"
#include "status.h"
namespace crdtp {
class DeserializerState;
class ErrorSupport;
class FrontendChannel;
namespace cbor {
class CBORTokenizer;
} // namespace cbor
// =============================================================================
// DispatchResponse - Error status and chaining / fall through
// =============================================================================
enum class DispatchCode {
SUCCESS = 1,
FALL_THROUGH = 2,
// For historical reasons, these error codes correspond to commonly used
// XMLRPC codes (e.g. see METHOD_NOT_FOUND in
// https://github.com/python/cpython/blob/main/Lib/xmlrpc/client.py).
PARSE_ERROR = -32700,
INVALID_REQUEST = -32600,
METHOD_NOT_FOUND = -32601,
INVALID_PARAMS = -32602,
INTERNAL_ERROR = -32603,
SERVER_ERROR = -32000,
SESSION_NOT_FOUND = SERVER_ERROR - 1,
};
// Information returned by command handlers. Usually returned after command
// execution attempts.
class CRDTP_EXPORT DispatchResponse {
public:
const std::string& Message() const { return message_; }
DispatchCode Code() const { return code_; }
bool IsSuccess() const { return code_ == DispatchCode::SUCCESS; }
bool IsFallThrough() const { return code_ == DispatchCode::FALL_THROUGH; }
bool IsError() const { return code_ < DispatchCode::SUCCESS; }
static DispatchResponse Success();
static DispatchResponse FallThrough();
// Indicates that a message could not be parsed. E.g., malformed JSON.
static DispatchResponse ParseError(std::string message);
// Indicates that a request is lacking required top-level properties
// ('id', 'method'), has top-level properties of the wrong type, or has
// unknown top-level properties.
static DispatchResponse InvalidRequest(std::string message);
// Indicates that a protocol method such as "Page.bringToFront" could not be
// dispatched because it's not known to the (domain) dispatcher.
static DispatchResponse MethodNotFound(std::string message);
// Indicates that the params sent to a domain handler are invalid.
static DispatchResponse InvalidParams(std::string message);
// Used for application level errors, e.g. within protocol agents.
static DispatchResponse InternalError();
// 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_;
std::string message_;
};
// =============================================================================
// Dispatchable - a shallow parser for CBOR encoded DevTools messages
// =============================================================================
// This parser extracts only the known top-level fields from a CBOR encoded map;
// method, id, sessionId, and params.
class CRDTP_EXPORT Dispatchable {
public:
// This constructor parses the |serialized| message. If successful,
// |ok()| will yield |true|, and |Method()|, |SessionId()|, |CallId()|,
// |Params()| can be used to access, the extracted contents. Otherwise,
// |ok()| will yield |false|, and |DispatchError()| can be
// used to send a response or notification to the client.
explicit Dispatchable(span<uint8_t> serialized);
// The serialized message that we just parsed.
span<uint8_t> Serialized() const { return serialized_; }
// Yields true if parsing was successful. This is cheaper than calling
// ::DispatchError().
bool ok() const;
// If !ok(), returns a DispatchResponse with appropriate code and error
// which can be sent to the client as a response or notification.
DispatchResponse DispatchError() const;
// Top level field: the command to be executed, fully qualified by
// domain. E.g. "Page.createIsolatedWorld".
span<uint8_t> Method() const { return method_; }
// Used to identify protocol connections attached to a specific
// target. See Target.attachToTarget, Target.setAutoAttach.
span<uint8_t> SessionId() const { return session_id_; }
// The call id, a sequence number that's used in responses to indicate
// the request to which the response belongs.
int32_t CallId() const { return call_id_; }
bool HasCallId() const { return has_call_id_; }
// The payload of the request in CBOR format. The |Dispatchable| parser does
// not parse into this; it only provides access to its raw contents here.
span<uint8_t> Params() const { return params_; }
private:
bool MaybeParseProperty(cbor::CBORTokenizer* tokenizer);
bool MaybeParseCallId(cbor::CBORTokenizer* tokenizer);
bool MaybeParseMethod(cbor::CBORTokenizer* tokenizer);
bool MaybeParseParams(cbor::CBORTokenizer* tokenizer);
bool MaybeParseSessionId(cbor::CBORTokenizer* tokenizer);
span<uint8_t> serialized_;
Status status_;
bool has_call_id_ = false;
int32_t call_id_;
span<uint8_t> method_;
bool params_seen_ = false;
span<uint8_t> params_;
span<uint8_t> session_id_;
};
// =============================================================================
// Helpers for creating protocol cresponses and notifications.
// =============================================================================
// The resulting notifications can be sent to a protocol client,
// usually via a FrontendChannel (see frontend_channel.h).
CRDTP_EXPORT std::unique_ptr<Serializable> CreateErrorResponse(
int callId,
DispatchResponse dispatch_response);
CRDTP_EXPORT std::unique_ptr<Serializable> CreateErrorNotification(
DispatchResponse dispatch_response);
CRDTP_EXPORT std::unique_ptr<Serializable> CreateResponse(
int callId,
std::unique_ptr<Serializable> params);
CRDTP_EXPORT std::unique_ptr<Serializable> CreateNotification(
const char* method,
std::unique_ptr<Serializable> params = nullptr);
// =============================================================================
// DomainDispatcher - Dispatching betwen protocol methods within a domain.
// =============================================================================
// This class is subclassed by |DomainDispatcherImpl|, which we generate per
// DevTools domain. It contains routines called from the generated code,
// e.g. ::MaybeReportInvalidParams, which are optimized for small code size.
// The most important method is ::Dispatch, which implements method dispatch
// by command name lookup.
class CRDTP_EXPORT DomainDispatcher {
public:
class CRDTP_EXPORT WeakPtr {
public:
explicit WeakPtr(DomainDispatcher*);
~WeakPtr();
DomainDispatcher* get() { return dispatcher_; }
void dispose() { dispatcher_ = nullptr; }
private:
DomainDispatcher* dispatcher_;
};
class CRDTP_EXPORT Callback {
public:
virtual ~Callback();
void dispose();
protected:
// |method| must point at static storage (a C++ string literal in practice).
Callback(std::unique_ptr<WeakPtr> backend_impl,
int call_id,
span<uint8_t> method,
span<uint8_t> message);
void sendIfActive(std::unique_ptr<Serializable> partialMessage,
const DispatchResponse& response);
void fallThroughIfActive();
private:
std::unique_ptr<WeakPtr> backend_impl_;
int call_id_;
// Subclasses of this class are instantiated from generated code which
// passes a string literal for the method name to the constructor. So the
// storage for |method| is the binary of the running process.
span<uint8_t> method_;
std::vector<uint8_t> message_;
};
explicit DomainDispatcher(FrontendChannel*);
virtual ~DomainDispatcher();
// Given a |command_name| without domain qualification, looks up the
// corresponding method. If the method is not found, returns nullptr.
// Otherwise, Returns a closure that will parse the provided
// Dispatchable.params() to a protocol object and execute the
// apprpropriate method. If the parsing fails it will issue an
// error response on the frontend channel, otherwise it will execute the
// command.
virtual std::function<void(const Dispatchable&)> Dispatch(
span<uint8_t> command_name) = 0;
// Sends a response to the client via the channel.
void sendResponse(int call_id,
const DispatchResponse&,
std::unique_ptr<Serializable> result = nullptr);
void ReportInvalidParams(const Dispatchable& dispatchable,
const DeserializerState& state);
FrontendChannel* channel() { return frontend_channel_; }
void clearFrontend();
std::unique_ptr<WeakPtr> weakPtr();
private:
FrontendChannel* frontend_channel_;
std::unordered_set<WeakPtr*> weak_ptrs_;
};
// =============================================================================
// UberDispatcher - dispatches between domains (backends).
// =============================================================================
class CRDTP_EXPORT UberDispatcher {
public:
// Return type for ::Dispatch.
class CRDTP_EXPORT DispatchResult {
public:
DispatchResult(bool method_found, std::function<void()> runnable);
// Indicates whether the method was found, that is, it could be dispatched
// to a backend registered with this dispatcher.
bool MethodFound() const { return method_found_; }
// Runs the dispatched result. This will send the appropriate error
// responses if the method wasn't found or if something went wrong during
// parameter parsing.
void Run();
private:
bool method_found_;
std::function<void()> runnable_;
};
// |frontend_hannel| can't be nullptr.
explicit UberDispatcher(FrontendChannel* frontend_channel);
virtual ~UberDispatcher();
// Dispatches the provided |dispatchable| considering all redirects and domain
// handlers registered with this uber dispatcher. Also see |DispatchResult|.
// |dispatchable.ok()| must hold - callers must check this separately and
// deal with errors.
DispatchResult Dispatch(const Dispatchable& dispatchable) const;
// Invoked from generated code for wiring domain backends; that is,
// connecting domain handlers to an uber dispatcher.
// See <domain-namespace>::Dispatcher::Wire(UberDispatcher*,Backend*).
FrontendChannel* channel() const {
assert(frontend_channel_);
return frontend_channel_;
}
// Invoked from generated code for wiring domain backends; that is,
// connecting domain handlers to an uber dispatcher.
// See <domain-namespace>::Dispatcher::Wire(UberDispatcher*,Backend*).
void WireBackend(span<uint8_t> domain,
const std::vector<std::pair<span<uint8_t>, span<uint8_t>>>&,
std::unique_ptr<DomainDispatcher> dispatcher);
private:
DomainDispatcher* findDispatcher(span<uint8_t> method);
FrontendChannel* const frontend_channel_;
// Pairs of ascii strings of the form ("Domain1.method1","Domain2.method2")
// indicating that the first element of each pair redirects to the second.
// Sorted by first element.
std::vector<std::pair<span<uint8_t>, span<uint8_t>>> redirects_;
// Domain dispatcher instances, sorted by their domain name.
std::vector<std::pair<span<uint8_t>, std::unique_ptr<DomainDispatcher>>>
dispatchers_;
};
} // namespace crdtp
#endif // CRDTP_DISPATCH_H_

View File

@ -0,0 +1,442 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <vector>
#include "cbor.h"
#include "dispatch.h"
#include "error_support.h"
#include "frontend_channel.h"
#include "json.h"
#include "test_platform.h"
namespace crdtp {
// =============================================================================
// DispatchResponse - Error status and chaining / fall through
// =============================================================================
TEST(DispatchResponseTest, OK) {
EXPECT_EQ(DispatchCode::SUCCESS, DispatchResponse::Success().Code());
EXPECT_TRUE(DispatchResponse::Success().IsSuccess());
}
TEST(DispatchResponseTest, ServerError) {
DispatchResponse error = DispatchResponse::ServerError("Oops!");
EXPECT_FALSE(error.IsSuccess());
EXPECT_EQ(DispatchCode::SERVER_ERROR, error.Code());
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());
EXPECT_EQ(DispatchCode::INTERNAL_ERROR, error.Code());
EXPECT_EQ("Internal error", error.Message());
}
TEST(DispatchResponseTest, InvalidParams) {
DispatchResponse error = DispatchResponse::InvalidParams("too cool");
EXPECT_FALSE(error.IsSuccess());
EXPECT_EQ(DispatchCode::INVALID_PARAMS, error.Code());
EXPECT_EQ("too cool", error.Message());
}
TEST(DispatchResponseTest, FallThrough) {
DispatchResponse error = DispatchResponse::FallThrough();
EXPECT_FALSE(error.IsSuccess());
EXPECT_TRUE(error.IsFallThrough());
EXPECT_EQ(DispatchCode::FALL_THROUGH, error.Code());
}
// =============================================================================
// Dispatchable - a shallow parser for CBOR encoded DevTools messages
// =============================================================================
TEST(DispatchableTest, MessageMustBeAnObject) {
// Provide no input whatsoever.
span<uint8_t> empty_span;
Dispatchable empty(empty_span);
EXPECT_FALSE(empty.ok());
EXPECT_EQ(DispatchCode::INVALID_REQUEST, empty.DispatchError().Code());
EXPECT_EQ("Message must be an object", empty.DispatchError().Message());
}
TEST(DispatchableTest, MessageMustHaveIntegerIdProperty) {
// Construct an empty map inside of an envelope.
std::vector<uint8_t> cbor;
ASSERT_TRUE(json::ConvertJSONToCBOR(SpanFrom("{}"), &cbor).ok());
Dispatchable dispatchable(SpanFrom(cbor));
EXPECT_FALSE(dispatchable.ok());
EXPECT_FALSE(dispatchable.HasCallId());
EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code());
EXPECT_EQ("Message must have integer 'id' property",
dispatchable.DispatchError().Message());
}
TEST(DispatchableTest, MessageMustHaveIntegerIdProperty_IncorrectType) {
// This time we set the id property, but fail to make it an int32.
std::vector<uint8_t> cbor;
ASSERT_TRUE(
json::ConvertJSONToCBOR(SpanFrom("{\"id\":\"foo\"}"), &cbor).ok());
Dispatchable dispatchable(SpanFrom(cbor));
EXPECT_FALSE(dispatchable.ok());
EXPECT_FALSE(dispatchable.HasCallId());
EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code());
EXPECT_EQ("Message must have integer 'id' property",
dispatchable.DispatchError().Message());
}
TEST(DispatchableTest, MessageMustHaveStringMethodProperty) {
// This time we set the id property, but not the method property.
std::vector<uint8_t> cbor;
ASSERT_TRUE(json::ConvertJSONToCBOR(SpanFrom("{\"id\":42}"), &cbor).ok());
Dispatchable dispatchable(SpanFrom(cbor));
EXPECT_FALSE(dispatchable.ok());
EXPECT_TRUE(dispatchable.HasCallId());
EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code());
EXPECT_EQ("Message must have string 'method' property",
dispatchable.DispatchError().Message());
}
TEST(DispatchableTest, MessageMustHaveStringMethodProperty_IncorrectType) {
// This time we set the method property, but fail to make it a string.
std::vector<uint8_t> cbor;
ASSERT_TRUE(
json::ConvertJSONToCBOR(SpanFrom("{\"id\":42,\"method\":42}"), &cbor)
.ok());
Dispatchable dispatchable(SpanFrom(cbor));
EXPECT_FALSE(dispatchable.ok());
EXPECT_TRUE(dispatchable.HasCallId());
EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code());
EXPECT_EQ("Message must have string 'method' property",
dispatchable.DispatchError().Message());
}
TEST(DispatchableTest, MessageMayHaveStringSessionIdProperty) {
// This time, the session id is an int but it should be a string. Method and
// call id are present.
std::vector<uint8_t> cbor;
ASSERT_TRUE(json::ConvertJSONToCBOR(
SpanFrom("{\"id\":42,\"method\":\"Foo.executeBar\","
"\"sessionId\":42" // int32 is wrong type
"}"),
&cbor)
.ok());
Dispatchable dispatchable(SpanFrom(cbor));
EXPECT_FALSE(dispatchable.ok());
EXPECT_TRUE(dispatchable.HasCallId());
EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code());
EXPECT_EQ("Message may have string 'sessionId' property",
dispatchable.DispatchError().Message());
}
TEST(DispatchableTest, MessageMayHaveObjectParamsProperty) {
// This time, we fail to use the correct type for the params property.
std::vector<uint8_t> cbor;
ASSERT_TRUE(json::ConvertJSONToCBOR(
SpanFrom("{\"id\":42,\"method\":\"Foo.executeBar\","
"\"params\":42" // int32 is wrong type
"}"),
&cbor)
.ok());
Dispatchable dispatchable(SpanFrom(cbor));
EXPECT_FALSE(dispatchable.ok());
EXPECT_TRUE(dispatchable.HasCallId());
EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code());
EXPECT_EQ("Message may have object 'params' property",
dispatchable.DispatchError().Message());
}
TEST(DispatchableTest, MessageWithUnknownProperty) {
// This time we set the 'unknown' property, so we are told what's allowed.
std::vector<uint8_t> cbor;
ASSERT_TRUE(
json::ConvertJSONToCBOR(SpanFrom("{\"id\":42,\"unknown\":42}"), &cbor)
.ok());
Dispatchable dispatchable(SpanFrom(cbor));
EXPECT_FALSE(dispatchable.ok());
EXPECT_TRUE(dispatchable.HasCallId());
EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code());
EXPECT_EQ(
"Message has property other than 'id', 'method', 'sessionId', 'params'",
dispatchable.DispatchError().Message());
}
TEST(DispatchableTest, DuplicateMapKey) {
for (const std::string& json :
{"{\"id\":42,\"id\":42}", "{\"params\":null,\"params\":null}",
"{\"method\":\"foo\",\"method\":\"foo\"}",
"{\"sessionId\":\"42\",\"sessionId\":\"42\"}"}) {
SCOPED_TRACE("json = " + json);
std::vector<uint8_t> cbor;
ASSERT_TRUE(json::ConvertJSONToCBOR(SpanFrom(json), &cbor).ok());
Dispatchable dispatchable(SpanFrom(cbor));
EXPECT_FALSE(dispatchable.ok());
EXPECT_EQ(DispatchCode::PARSE_ERROR, dispatchable.DispatchError().Code());
EXPECT_THAT(dispatchable.DispatchError().Message(),
testing::StartsWith("CBOR: duplicate map key at position "));
}
}
TEST(DispatchableTest, ValidMessageParsesOK_NoParams) {
for (const std::string& json :
{"{\"id\":42,\"method\":\"Foo.executeBar\",\"sessionId\":"
"\"f421ssvaz4\"}",
"{\"id\":42,\"method\":\"Foo.executeBar\",\"sessionId\":\"f421ssvaz4\","
"\"params\":null}"}) {
SCOPED_TRACE("json = " + json);
std::vector<uint8_t> cbor;
ASSERT_TRUE(json::ConvertJSONToCBOR(SpanFrom(json), &cbor).ok());
Dispatchable dispatchable(SpanFrom(cbor));
EXPECT_TRUE(dispatchable.ok());
EXPECT_TRUE(dispatchable.HasCallId());
EXPECT_EQ(42, dispatchable.CallId());
EXPECT_EQ("Foo.executeBar", std::string(dispatchable.Method().begin(),
dispatchable.Method().end()));
EXPECT_EQ("f421ssvaz4", std::string(dispatchable.SessionId().begin(),
dispatchable.SessionId().end()));
EXPECT_TRUE(dispatchable.Params().empty());
}
}
TEST(DispatchableTest, ValidMessageParsesOK_WithParams) {
std::vector<uint8_t> cbor;
cbor::EnvelopeEncoder envelope;
envelope.EncodeStart(&cbor);
cbor.push_back(cbor::EncodeIndefiniteLengthMapStart());
cbor::EncodeString8(SpanFrom("id"), &cbor);
cbor::EncodeInt32(42, &cbor);
cbor::EncodeString8(SpanFrom("method"), &cbor);
cbor::EncodeString8(SpanFrom("Foo.executeBar"), &cbor);
cbor::EncodeString8(SpanFrom("params"), &cbor);
cbor::EnvelopeEncoder params_envelope;
params_envelope.EncodeStart(&cbor);
// The |Dispatchable| class does not parse into the "params" envelope,
// so we can stick anything into there for the purpose of this test.
// For convenience, we use a String8.
cbor::EncodeString8(SpanFrom("params payload"), &cbor);
params_envelope.EncodeStop(&cbor);
cbor::EncodeString8(SpanFrom("sessionId"), &cbor);
cbor::EncodeString8(SpanFrom("f421ssvaz4"), &cbor);
cbor.push_back(cbor::EncodeStop());
envelope.EncodeStop(&cbor);
Dispatchable dispatchable(SpanFrom(cbor));
EXPECT_TRUE(dispatchable.ok());
EXPECT_TRUE(dispatchable.HasCallId());
EXPECT_EQ(42, dispatchable.CallId());
EXPECT_EQ("Foo.executeBar", std::string(dispatchable.Method().begin(),
dispatchable.Method().end()));
EXPECT_EQ("f421ssvaz4", std::string(dispatchable.SessionId().begin(),
dispatchable.SessionId().end()));
cbor::CBORTokenizer params_tokenizer(dispatchable.Params());
ASSERT_EQ(cbor::CBORTokenTag::ENVELOPE, params_tokenizer.TokenTag());
params_tokenizer.EnterEnvelope();
ASSERT_EQ(cbor::CBORTokenTag::STRING8, params_tokenizer.TokenTag());
EXPECT_EQ("params payload", std::string(params_tokenizer.GetString8().begin(),
params_tokenizer.GetString8().end()));
}
TEST(DispatchableTest, FaultyCBORTrailingJunk) {
// In addition to the higher level parsing errors, we also catch CBOR
// structural corruption. E.g., in this case, the message would be
// OK but has some extra trailing bytes.
std::vector<uint8_t> cbor;
cbor::EnvelopeEncoder envelope;
envelope.EncodeStart(&cbor);
cbor.push_back(cbor::EncodeIndefiniteLengthMapStart());
cbor::EncodeString8(SpanFrom("id"), &cbor);
cbor::EncodeInt32(42, &cbor);
cbor::EncodeString8(SpanFrom("method"), &cbor);
cbor::EncodeString8(SpanFrom("Foo.executeBar"), &cbor);
cbor::EncodeString8(SpanFrom("sessionId"), &cbor);
cbor::EncodeString8(SpanFrom("f421ssvaz4"), &cbor);
cbor.push_back(cbor::EncodeStop());
envelope.EncodeStop(&cbor);
size_t trailing_junk_pos = cbor.size();
cbor.push_back('t');
cbor.push_back('r');
cbor.push_back('a');
cbor.push_back('i');
cbor.push_back('l');
Dispatchable dispatchable(SpanFrom(cbor));
EXPECT_FALSE(dispatchable.ok());
EXPECT_EQ(DispatchCode::PARSE_ERROR, dispatchable.DispatchError().Code());
EXPECT_EQ(57u, trailing_junk_pos);
EXPECT_EQ("CBOR: trailing junk at position 57",
dispatchable.DispatchError().Message());
}
// =============================================================================
// Helpers for creating protocol cresponses and notifications.
// =============================================================================
TEST(CreateErrorResponseTest, SmokeTest) {
auto serializable = CreateErrorResponse(
42, DispatchResponse::InvalidParams("invalid params message"));
std::string json;
auto status =
json::ConvertCBORToJSON(SpanFrom(serializable->Serialize()), &json);
ASSERT_TRUE(status.ok());
EXPECT_EQ(
"{\"id\":42,\"error\":"
"{\"code\":-32602,"
"\"message\":\"invalid params message\"}}",
json);
}
TEST(CreateErrorNotificationTest, SmokeTest) {
auto serializable =
CreateErrorNotification(DispatchResponse::InvalidRequest("oops!"));
std::string json;
auto status =
json::ConvertCBORToJSON(SpanFrom(serializable->Serialize()), &json);
ASSERT_TRUE(status.ok());
EXPECT_EQ("{\"error\":{\"code\":-32600,\"message\":\"oops!\"}}", json);
}
TEST(CreateResponseTest, SmokeTest) {
auto serializable = CreateResponse(42, nullptr);
std::string json;
auto status =
json::ConvertCBORToJSON(SpanFrom(serializable->Serialize()), &json);
ASSERT_TRUE(status.ok());
EXPECT_EQ("{\"id\":42,\"result\":{}}", json);
}
TEST(CreateNotificationTest, SmokeTest) {
auto serializable = CreateNotification("Foo.bar");
std::string json;
auto status =
json::ConvertCBORToJSON(SpanFrom(serializable->Serialize()), &json);
ASSERT_TRUE(status.ok());
EXPECT_EQ("{\"method\":\"Foo.bar\",\"params\":{}}", json);
}
// =============================================================================
// UberDispatcher - dispatches between domains (backends).
// =============================================================================
class TestChannel : public FrontendChannel {
public:
std::string JSON() const {
std::string json;
json::ConvertCBORToJSON(SpanFrom(cbor_), &json);
return json;
}
private:
void SendProtocolResponse(int call_id,
std::unique_ptr<Serializable> message) override {
cbor_ = message->Serialize();
}
void SendProtocolNotification(
std::unique_ptr<Serializable> message) override {
cbor_ = message->Serialize();
}
void FallThrough(int call_id,
span<uint8_t> method,
span<uint8_t> message) override {}
void FlushProtocolNotifications() override {}
std::vector<uint8_t> cbor_;
};
TEST(UberDispatcherTest, MethodNotFound) {
// No domain dispatchers are registered, so unsuprisingly, we'll get a method
// not found error and can see that DispatchResult::MethodFound() yields
// false.
TestChannel channel;
UberDispatcher dispatcher(&channel);
std::vector<uint8_t> message;
json::ConvertJSONToCBOR(SpanFrom("{\"id\":42,\"method\":\"Foo.bar\"}"),
&message);
Dispatchable dispatchable(SpanFrom(message));
ASSERT_TRUE(dispatchable.ok());
UberDispatcher::DispatchResult dispatched = dispatcher.Dispatch(dispatchable);
EXPECT_FALSE(dispatched.MethodFound());
dispatched.Run();
EXPECT_EQ(
"{\"id\":42,\"error\":"
"{\"code\":-32601,\"message\":\"'Foo.bar' wasn't found\"}}",
channel.JSON());
}
// A domain dispatcher which captured dispatched and executed commands in fields
// for testing.
class TestDomain : public DomainDispatcher {
public:
explicit TestDomain(FrontendChannel* channel) : DomainDispatcher(channel) {}
std::function<void(const Dispatchable&)> Dispatch(
span<uint8_t> command_name) override {
dispatched_commands_.push_back(
std::string(command_name.begin(), command_name.end()));
return [this](const Dispatchable& dispatchable) {
executed_commands_.push_back(dispatchable.CallId());
};
}
// Command names of the dispatched commands.
std::vector<std::string> DispatchedCommands() const {
return dispatched_commands_;
}
// Call ids of the executed commands.
std::vector<int32_t> ExecutedCommands() const { return executed_commands_; }
private:
std::vector<std::string> dispatched_commands_;
std::vector<int32_t> executed_commands_;
};
TEST(UberDispatcherTest, DispatchingToDomainWithRedirects) {
// This time, we register two domain dispatchers (Foo and Bar) and issue one
// command 'Foo.execute' which executes on Foo and one command 'Foo.redirect'
// which executes as 'Bar.redirected'.
TestChannel channel;
UberDispatcher dispatcher(&channel);
auto foo_dispatcher = std::make_unique<TestDomain>(&channel);
TestDomain* foo = foo_dispatcher.get();
auto bar_dispatcher = std::make_unique<TestDomain>(&channel);
TestDomain* bar = bar_dispatcher.get();
dispatcher.WireBackend(
SpanFrom("Foo"), {{SpanFrom("Foo.redirect"), SpanFrom("Bar.redirected")}},
std::move(foo_dispatcher));
dispatcher.WireBackend(SpanFrom("Bar"), {}, std::move(bar_dispatcher));
{
std::vector<uint8_t> message;
json::ConvertJSONToCBOR(SpanFrom("{\"id\":42,\"method\":\"Foo.execute\"}"),
&message);
Dispatchable dispatchable(SpanFrom(message));
ASSERT_TRUE(dispatchable.ok());
UberDispatcher::DispatchResult dispatched =
dispatcher.Dispatch(dispatchable);
EXPECT_TRUE(dispatched.MethodFound());
dispatched.Run();
}
{
std::vector<uint8_t> message;
json::ConvertJSONToCBOR(SpanFrom("{\"id\":43,\"method\":\"Foo.redirect\"}"),
&message);
Dispatchable dispatchable(SpanFrom(message));
ASSERT_TRUE(dispatchable.ok());
UberDispatcher::DispatchResult dispatched =
dispatcher.Dispatch(dispatchable);
EXPECT_TRUE(dispatched.MethodFound());
dispatched.Run();
}
EXPECT_THAT(foo->DispatchedCommands(), testing::ElementsAre("execute"));
EXPECT_THAT(foo->ExecutedCommands(), testing::ElementsAre(42));
EXPECT_THAT(bar->DispatchedCommands(), testing::ElementsAre("redirected"));
EXPECT_THAT(bar->ExecutedCommands(), testing::ElementsAre(43));
}
} // namespace crdtp

View File

@ -0,0 +1,59 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "error_support.h"
#include <cassert>
namespace crdtp {
void ErrorSupport::Push() {
stack_.emplace_back();
}
void ErrorSupport::Pop() {
stack_.pop_back();
}
void ErrorSupport::SetName(const char* name) {
assert(!stack_.empty());
stack_.back().type = NAME;
stack_.back().name = name;
}
void ErrorSupport::SetIndex(size_t index) {
assert(!stack_.empty());
stack_.back().type = INDEX;
stack_.back().index = index;
}
void ErrorSupport::AddError(const char* error) {
assert(!stack_.empty());
if (!errors_.empty())
errors_ += "; ";
for (size_t ii = 0; ii < stack_.size(); ++ii) {
if (ii)
errors_ += ".";
const Segment& s = stack_[ii];
switch (s.type) {
case NAME:
errors_ += s.name;
continue;
case INDEX:
errors_ += std::to_string(s.index);
continue;
default:
assert(s.type != EMPTY);
continue;
}
}
errors_ += ": ";
errors_ += error;
}
span<uint8_t> ErrorSupport::Errors() const {
return SpanFrom(errors_);
}
} // namespace crdtp

View File

@ -0,0 +1,62 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CRDTP_ERROR_SUPPORT_H_
#define CRDTP_ERROR_SUPPORT_H_
#include <cstdint>
#include <string>
#include <vector>
#include "export.h"
#include "span.h"
namespace crdtp {
// =============================================================================
// ErrorSupport - For tracking errors in tree structures.
// =============================================================================
// This abstraction is used when converting between Values and inspector
// objects, e.g. in lib/ValueConversions_{h,cc}.template. As the processing
// enters and exits a branch, we call Push / Pop. Within the branch,
// we either set the name or an index (in case we're processing the element of a
// list/vector). Only once an error is seen, the path which is now on the
// stack is materialized and prefixes the error message. E.g.,
// "foo.bar.2: some error". After error collection, ::Errors() is used to
// access the message.
class CRDTP_EXPORT ErrorSupport {
public:
// Push / Pop operations for the path segments; after Push, either SetName or
// SetIndex must be called exactly once.
void Push();
void Pop();
// Sets the name of the current segment on the stack; e.g. a field name.
// |name| must be a C++ string literal in 7 bit US-ASCII.
void SetName(const char* name);
// Sets the index of the current segment on the stack; e.g. an array index.
void SetIndex(size_t index);
// Materializes the error internally. |error| must be a C++ string literal
// in 7 bit US-ASCII.
void AddError(const char* error);
// Returns the semicolon-separated list of errors as in 7 bit ASCII.
span<uint8_t> Errors() const;
private:
enum SegmentType { EMPTY, NAME, INDEX };
struct Segment {
SegmentType type = EMPTY;
union {
const char* name;
size_t index;
};
};
std::vector<Segment> stack_;
std::string errors_;
};
} // namespace crdtp
#endif // CRDTP_ERROR_SUPPORT_H_

View File

@ -0,0 +1,45 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "error_support.h"
#include <string>
#include <vector>
#include "test_platform.h"
namespace crdtp {
TEST(ErrorSupportTest, Empty) {
ErrorSupport errors;
EXPECT_TRUE(errors.Errors().empty());
}
TEST(ErrorSupportTest, Nesting) {
ErrorSupport errors;
// Enter field foo, inter element at index 42, enter field bar, and encounter
// an error there ("something wrong").
errors.Push();
errors.SetName("foo");
errors.Push();
errors.SetIndex(42);
errors.Push();
errors.SetName("bar_sibling");
errors.SetName("bar");
errors.AddError("something wrong");
errors.Pop(); // bar
errors.Pop(); // 42
// The common case is actually that we'll enter some field, set the name
// or index, and leave without ever producing an error.
errors.Push();
errors.SetName("no_error_here");
errors.Pop(); // no_error_here
errors.Push();
errors.SetName("bang");
errors.AddError("one last error");
errors.Pop(); // bang
errors.Pop(); // foo
std::string out(errors.Errors().begin(), errors.Errors().end());
EXPECT_EQ("foo.42.bar: something wrong; foo.bang: one last error", out);
}
} // namespace crdtp

29
deps/inspector_protocol/crdtp/export.h vendored Normal file
View File

@ -0,0 +1,29 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CRDTP_EXPORT_H_
#define CRDTP_EXPORT_H_
#if defined(COMPONENT_BUILD)
#if defined(WIN32)
#if defined(CRDTP_IMPLEMENTATION)
#define CRDTP_EXPORT __declspec(dllexport)
#else
#define CRDTP_EXPORT __declspec(dllimport)
#endif // defined(CRDTP_IMPLEMENTATION)
#else // defined(WIN32)
#if defined(CRDTP_IMPLEMENTATION)
#define CRDTP_EXPORT __attribute__((visibility("default")))
#else
#define CRDTP_EXPORT
#endif
#endif
#else // defined(COMPONENT_BUILD)
#define CRDTP_EXPORT
#endif
#endif // CRDTP_EXPORT_H_

View File

@ -0,0 +1,58 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CRDTP_FIND_BY_FIRST_H_
#define CRDTP_FIND_BY_FIRST_H_
#include <algorithm>
#include <cstdint>
#include <memory>
#include <vector>
#include "export.h"
#include "span.h"
namespace crdtp {
// =============================================================================
// FindByFirst - Retrieval from a sorted vector that's keyed by span<uint8_t>.
// =============================================================================
// Given a vector of pairs sorted by the first element of each pair, find
// the corresponding value given a key to be compared to the first element.
// Together with std::inplace_merge and pre-sorting or std::sort, this can
// be used to implement a minimalistic equivalent of Chromium's flat_map.
// In this variant, the template parameter |T| is a value type and a
// |default_value| is provided.
template <typename T>
T FindByFirst(const std::vector<std::pair<span<uint8_t>, T>>& sorted_by_first,
span<uint8_t> key,
T default_value) {
auto it = std::lower_bound(
sorted_by_first.begin(), sorted_by_first.end(), key,
[](const std::pair<span<uint8_t>, T>& left, span<uint8_t> right) {
return SpanLessThan(left.first, right);
});
return (it != sorted_by_first.end() && SpanEquals(it->first, key))
? it->second
: default_value;
}
// In this variant, the template parameter |T| is a class or struct that's
// instantiated in std::unique_ptr, and we return either a T* or a nullptr.
template <typename T>
T* FindByFirst(const std::vector<std::pair<span<uint8_t>, std::unique_ptr<T>>>&
sorted_by_first,
span<uint8_t> key) {
auto it = std::lower_bound(
sorted_by_first.begin(), sorted_by_first.end(), key,
[](const std::pair<span<uint8_t>, std::unique_ptr<T>>& left,
span<uint8_t> right) { return SpanLessThan(left.first, right); });
return (it != sorted_by_first.end() && SpanEquals(it->first, key))
? it->second.get()
: nullptr;
}
} // namespace crdtp
#endif // CRDTP_FIND_BY_FIRST_H_

View File

@ -0,0 +1,76 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string>
#include "find_by_first.h"
#include "test_platform.h"
namespace crdtp {
// =============================================================================
// FindByFirst - Efficient retrieval from a sorted vector.
// =============================================================================
TEST(FindByFirst, SpanBySpan) {
std::vector<std::pair<span<uint8_t>, span<uint8_t>>> sorted_span_by_span = {
{SpanFrom("foo1"), SpanFrom("bar1")},
{SpanFrom("foo2"), SpanFrom("bar2")},
{SpanFrom("foo3"), SpanFrom("bar3")},
};
{
auto result = FindByFirst(sorted_span_by_span, SpanFrom("foo1"),
SpanFrom("not_found"));
EXPECT_EQ("bar1", std::string(result.begin(), result.end()));
}
{
auto result = FindByFirst(sorted_span_by_span, SpanFrom("foo3"),
SpanFrom("not_found"));
EXPECT_EQ("bar3", std::string(result.begin(), result.end()));
}
{
auto result = FindByFirst(sorted_span_by_span, SpanFrom("baz"),
SpanFrom("not_found"));
EXPECT_EQ("not_found", std::string(result.begin(), result.end()));
}
}
namespace {
class TestObject {
public:
explicit TestObject(const std::string& message) : message_(message) {}
const std::string& message() const { return message_; }
private:
std::string message_;
};
} // namespace
TEST(FindByFirst, ObjectBySpan) {
std::vector<std::pair<span<uint8_t>, std::unique_ptr<TestObject>>>
sorted_object_by_span;
sorted_object_by_span.push_back(
std::make_pair(SpanFrom("foo1"), std::make_unique<TestObject>("bar1")));
sorted_object_by_span.push_back(
std::make_pair(SpanFrom("foo2"), std::make_unique<TestObject>("bar2")));
sorted_object_by_span.push_back(
std::make_pair(SpanFrom("foo3"), std::make_unique<TestObject>("bar3")));
{
TestObject* result =
FindByFirst<TestObject>(sorted_object_by_span, SpanFrom("foo1"));
ASSERT_TRUE(result);
ASSERT_EQ("bar1", result->message());
}
{
TestObject* result =
FindByFirst<TestObject>(sorted_object_by_span, SpanFrom("foo3"));
ASSERT_TRUE(result);
ASSERT_EQ("bar3", result->message());
}
{
TestObject* result =
FindByFirst<TestObject>(sorted_object_by_span, SpanFrom("baz"));
ASSERT_FALSE(result);
}
}
} // namespace crdtp

View File

@ -0,0 +1,47 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CRDTP_FRONTEND_CHANNEL_H_
#define CRDTP_FRONTEND_CHANNEL_H_
#include <cstdint>
#include <memory>
#include "export.h"
#include "serializable.h"
#include "span.h"
namespace crdtp {
// =============================================================================
// FrontendChannel - For sending notifications and responses to protocol clients
// =============================================================================
class CRDTP_EXPORT FrontendChannel {
public:
virtual ~FrontendChannel() = default;
// Sends protocol responses and notifications. The |call_id| parameter is
// seemingly redundant because it's also included in the message, but
// responses may be sent from an untrusted source to a trusted process (e.g.
// from Chromium's renderer (blink) to the browser process), which needs
// to be able to match the response to an earlier request without parsing the
// messsage.
virtual void SendProtocolResponse(int call_id,
std::unique_ptr<Serializable> message) = 0;
virtual void SendProtocolNotification(
std::unique_ptr<Serializable> message) = 0;
// FallThrough indicates that |message| should be handled in another layer.
// Usually this means the layer responding to the message didn't handle it,
// but in some cases messages are handled by multiple layers (e.g. both
// the embedder and the content layer in Chromium).
virtual void FallThrough(int call_id,
span<uint8_t> method,
span<uint8_t> message) = 0;
// Session implementations may queue notifications for performance or
// other considerations; this is a hook for domain handlers to manually flush.
virtual void FlushProtocolNotifications() = 0;
};
} // namespace crdtp
#endif // CRDTP_FRONTEND_CHANNEL_H_

1037
deps/inspector_protocol/crdtp/json.cc vendored Normal file

File diff suppressed because it is too large Load Diff

57
deps/inspector_protocol/crdtp/json.h vendored Normal file
View File

@ -0,0 +1,57 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CRDTP_JSON_H_
#define CRDTP_JSON_H_
#include <memory>
#include <vector>
#include "export.h"
#include "parser_handler.h"
namespace crdtp {
namespace json {
// =============================================================================
// json::NewJSONEncoder - for encoding streaming parser events as JSON
// =============================================================================
// Returns a handler object which will write ascii characters to |out|.
// |status->ok()| will be false iff the handler routine HandleError() is called.
// In that case, we'll stop emitting output.
// Except for calling the HandleError routine at any time, the client
// code must call the Handle* methods in an order in which they'd occur
// in valid JSON; otherwise we may crash (the code uses assert).
CRDTP_EXPORT std::unique_ptr<ParserHandler> NewJSONEncoder(
std::vector<uint8_t>* out,
Status* status);
CRDTP_EXPORT std::unique_ptr<ParserHandler> NewJSONEncoder(std::string* out,
Status* status);
// =============================================================================
// json::ParseJSON - for receiving streaming parser events for JSON
// =============================================================================
CRDTP_EXPORT void ParseJSON(span<uint8_t> chars, ParserHandler* handler);
CRDTP_EXPORT void ParseJSON(span<uint16_t> chars, ParserHandler* handler);
// =============================================================================
// json::ConvertCBORToJSON, json::ConvertJSONToCBOR - for transcoding
// =============================================================================
CRDTP_EXPORT Status ConvertCBORToJSON(span<uint8_t> cbor, std::string* json);
CRDTP_EXPORT Status ConvertCBORToJSON(span<uint8_t> cbor,
std::vector<uint8_t>* json);
CRDTP_EXPORT Status ConvertJSONToCBOR(span<uint8_t> json,
std::vector<uint8_t>* cbor);
CRDTP_EXPORT Status ConvertJSONToCBOR(span<uint16_t> json,
std::vector<uint8_t>* cbor);
} // namespace json
} // namespace crdtp
#endif // CRDTP_JSON_H_

View File

@ -0,0 +1,33 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "json_platform.h"
#include <sstream>
// This is a reference implementation using the C++ standard library.
// Downstream projects may invoke their preferred routines instead,
// by modifying / replacing this file to call them.
// Examples of optimized string<->number conversion libraries:
// - https://github.com/google/double-conversion
// - https://github.com/abseil/abseil-cpp/blob/master/absl/strings/numbers.h
namespace crdtp {
namespace json {
namespace platform {
bool StrToD(const char* str, double* result) {
std::istringstream is(str);
is.imbue(std::locale::classic());
is >> *result;
return !is.fail() && is.eof();
}
std::string DToStr(double value) {
std::stringstream ss;
ss.imbue(std::locale::classic());
ss << value;
return ss.str();
}
} // namespace platform
} // namespace json
} // namespace crdtp

View File

@ -0,0 +1,26 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CRDTP_JSON_PLATFORM_H_
#define CRDTP_JSON_PLATFORM_H_
#include <string>
namespace crdtp {
namespace json {
// These routines are implemented in json_platform.cc, or in a
// platform-dependent (code-base dependent) custom replacement.
// E.g., json_platform_chromium.cc, json_platform_v8.cc.
namespace platform {
// Parses |str| into |result|. Returns false iff there are
// leftover characters or parsing errors.
bool StrToD(const char* str, double* result);
// Prints |value| in a format suitable for JSON.
std::string DToStr(double value);
} // namespace platform
} // namespace json
} // namespace crdtp
#endif // CRDTP_JSON_PLATFORM_H_

View File

@ -0,0 +1,753 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "json.h"
#include <array>
#include <clocale>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include "cbor.h"
#include "parser_handler.h"
#include "span.h"
#include "status.h"
#include "status_test_support.h"
#include "test_platform.h"
namespace crdtp {
namespace json {
// =============================================================================
// json::NewJSONEncoder - for encoding streaming parser events as JSON
// =============================================================================
void WriteUTF8AsUTF16(ParserHandler* writer, const std::string& utf8) {
writer->HandleString16(SpanFrom(UTF8ToUTF16(SpanFrom(utf8))));
}
TEST(JsonEncoder, OverlongEncodings) {
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
// We encode 0x7f, which is the DEL ascii character, as a 4 byte UTF8
// sequence. This is called an overlong encoding, because only 1 byte
// is needed to represent 0x7f as UTF8.
std::vector<uint8_t> chars = {
0xf0, // Starts 4 byte utf8 sequence
0x80, // continuation byte
0x81, // continuation byte w/ payload bit 7 set to 1.
0xbf, // continuation byte w/ payload bits 0-6 set to 11111.
};
writer->HandleString8(SpanFrom(chars));
EXPECT_EQ("\"\"", out); // Empty string means that 0x7f was rejected (good).
}
TEST(JsonEncoder, NotAContinuationByte) {
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
// |world| encodes the globe as a 4 byte UTF8 sequence. So, naturally, it'll
// have a start byte, followed by three continuation bytes.
std::string world = "🌎";
ASSERT_EQ(4u, world.size());
ASSERT_EQ(world[1] & 0xc0, 0x80); // checks for continuation byte
ASSERT_EQ(world[2] & 0xc0, 0x80);
ASSERT_EQ(world[3] & 0xc0, 0x80);
// Now create a corrupted UTF8 string, starting with the first two bytes from
// |world|, followed by an ASCII message. Upon encountering '!', our decoder
// will realize that it's not a continuation byte; it'll skip to the end of
// this UTF8 sequence and continue with the next character. In this case, the
// 'H', of "Hello".
std::vector<uint8_t> chars;
chars.push_back(world[0]);
chars.push_back(world[1]);
chars.push_back('!');
chars.push_back('?');
chars.push_back('H');
chars.push_back('e');
chars.push_back('l');
chars.push_back('l');
chars.push_back('o');
writer->HandleString8(SpanFrom(chars));
EXPECT_EQ("\"Hello\"", out); // "Hello" shows we restarted at 'H'.
}
TEST(JsonEncoder, EscapesLoneHighSurrogates) {
// This tests that the JSON encoder escapes lone high surrogates, i.e.
// invalid code points in the range from 0xD800 to 0xDBFF. In
// unescaped form, these cannot be represented in well-formed UTF-8 or
// UTF-16.
std::vector<uint16_t> chars = {'a', 0xd800, 'b', 0xdada, 'c', 0xdbff, 'd'};
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleString16(span<uint16_t>(chars.data(), chars.size()));
EXPECT_EQ("\"a\\ud800b\\udadac\\udbffd\"", out);
}
TEST(JsonEncoder, EscapesLoneLowSurrogates) {
// This tests that the JSON encoder escapes lone low surrogates, i.e.
// invalid code points in the range from 0xDC00 to 0xDFFF. In
// unescaped form, these cannot be represented in well-formed UTF-8 or
// UTF-16.
std::vector<uint16_t> chars = {'a', 0xdc00, 'b', 0xdede, 'c', 0xdfff, 'd'};
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleString16(span<uint16_t>(chars.data(), chars.size()));
EXPECT_EQ("\"a\\udc00b\\udedec\\udfffd\"", out);
}
TEST(JsonEncoder, EscapesFFFF) {
// This tests that the JSON encoder will escape the UTF16 input 0xffff as
// \uffff; useful to check this since it's an edge case.
std::vector<uint16_t> chars = {'a', 'b', 'c', 0xffff, 'd'};
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleString16(span<uint16_t>(chars.data(), chars.size()));
EXPECT_EQ("\"abc\\uffffd\"", out);
}
TEST(JsonEncoder, Passes0x7FString8) {
std::vector<uint8_t> chars = {'a', 0x7f, 'b'};
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleString8(span<uint8_t>(chars.data(), chars.size()));
EXPECT_EQ(
"\"a\x7f"
"b\"",
out);
}
TEST(JsonEncoder, Passes0x7FString16) {
std::vector<uint16_t> chars16 = {'a', 0x7f, 'b'};
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleString16(span<uint16_t>(chars16.data(), chars16.size()));
EXPECT_EQ(
"\"a\x7f"
"b\"",
out);
}
TEST(JsonEncoder, IncompleteUtf8Sequence) {
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleArrayBegin(); // This emits [, which starts an array.
{ // 🌎 takes four bytes to encode in UTF-8. We test with the first three;
// This means we're trying to emit a string that consists solely of an
// incomplete UTF-8 sequence. So the string in the JSON output is empty.
std::string world_utf8 = "🌎";
ASSERT_EQ(4u, world_utf8.size());
std::vector<uint8_t> chars(world_utf8.begin(), world_utf8.begin() + 3);
writer->HandleString8(SpanFrom(chars));
EXPECT_EQ("[\"\"", out); // Incomplete sequence rejected: empty string.
}
{ // This time, the incomplete sequence is at the end of the string.
std::string msg = "Hello, \xF0\x9F\x8C";
std::vector<uint8_t> chars(msg.begin(), msg.end());
writer->HandleString8(SpanFrom(chars));
EXPECT_EQ("[\"\",\"Hello, \"", out); // Incomplete sequence dropped at end.
}
}
TEST(JsonStdStringWriterTest, HelloWorld) {
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleMapBegin();
WriteUTF8AsUTF16(writer.get(), "msg1");
WriteUTF8AsUTF16(writer.get(), "Hello, 🌎.");
std::string key = "msg1-as-utf8";
std::string value = "Hello, 🌎.";
writer->HandleString8(SpanFrom(key));
writer->HandleString8(SpanFrom(value));
WriteUTF8AsUTF16(writer.get(), "msg2");
WriteUTF8AsUTF16(writer.get(), "\\\b\r\n\t\f\"");
WriteUTF8AsUTF16(writer.get(), "nested");
writer->HandleMapBegin();
WriteUTF8AsUTF16(writer.get(), "double");
writer->HandleDouble(3.1415);
WriteUTF8AsUTF16(writer.get(), "int");
writer->HandleInt32(-42);
WriteUTF8AsUTF16(writer.get(), "bool");
writer->HandleBool(false);
WriteUTF8AsUTF16(writer.get(), "null");
writer->HandleNull();
writer->HandleMapEnd();
WriteUTF8AsUTF16(writer.get(), "array");
writer->HandleArrayBegin();
writer->HandleInt32(1);
writer->HandleInt32(2);
writer->HandleInt32(3);
writer->HandleArrayEnd();
writer->HandleMapEnd();
EXPECT_TRUE(status.ok());
EXPECT_EQ(
"{\"msg1\":\"Hello, \\ud83c\\udf0e.\","
"\"msg1-as-utf8\":\"Hello, \\ud83c\\udf0e.\","
"\"msg2\":\"\\\\\\b\\r\\n\\t\\f\\\"\","
"\"nested\":{\"double\":3.1415,\"int\":-42,"
"\"bool\":false,\"null\":null},\"array\":[1,2,3]}",
out);
}
TEST(JsonStdStringWriterTest, ScalarsAreRenderedAsInt) {
// Test that Number.MIN_SAFE_INTEGER / Number.MAX_SAFE_INTEGER from Javascript
// are rendered as integers (no decimal point / rounding), even when we
// encode them from double. Javascript's Number is an IEE754 double, so
// it has 53 bits to represent integers.
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleMapBegin();
writer->HandleString8(SpanFrom("Number.MIN_SAFE_INTEGER"));
EXPECT_EQ(-0x1fffffffffffff, -9007199254740991); // 53 bits for integers.
writer->HandleDouble(-9007199254740991); // Note HandleDouble here.
writer->HandleString8(SpanFrom("Number.MAX_SAFE_INTEGER"));
EXPECT_EQ(0x1fffffffffffff, 9007199254740991); // 53 bits for integers.
writer->HandleDouble(9007199254740991); // Note HandleDouble here.
writer->HandleMapEnd();
EXPECT_TRUE(status.ok());
EXPECT_EQ(
"{\"Number.MIN_SAFE_INTEGER\":-9007199254740991,"
"\"Number.MAX_SAFE_INTEGER\":9007199254740991}",
out);
}
TEST(JsonStdStringWriterTest, RepresentingNonFiniteValuesAsNull) {
// JSON can't represent +Infinity, -Infinity, or NaN.
// So in practice it's mapped to null.
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleMapBegin();
writer->HandleString8(SpanFrom("Infinity"));
writer->HandleDouble(std::numeric_limits<double>::infinity());
writer->HandleString8(SpanFrom("-Infinity"));
writer->HandleDouble(-std::numeric_limits<double>::infinity());
writer->HandleString8(SpanFrom("NaN"));
writer->HandleDouble(std::numeric_limits<double>::quiet_NaN());
writer->HandleMapEnd();
EXPECT_TRUE(status.ok());
EXPECT_EQ("{\"Infinity\":null,\"-Infinity\":null,\"NaN\":null}", out);
}
TEST(JsonStdStringWriterTest, BinaryEncodedAsJsonString) {
// The encoder emits binary submitted to ParserHandler::HandleBinary
// as base64. The following three examples are taken from
// https://en.wikipedia.org/wiki/Base64.
{
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleBinary(SpanFrom(std::vector<uint8_t>({'M', 'a', 'n'})));
EXPECT_TRUE(status.ok());
EXPECT_EQ("\"TWFu\"", out);
}
{
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleBinary(SpanFrom(std::vector<uint8_t>({'M', 'a'})));
EXPECT_TRUE(status.ok());
EXPECT_EQ("\"TWE=\"", out);
}
{
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleBinary(SpanFrom(std::vector<uint8_t>({'M'})));
EXPECT_TRUE(status.ok());
EXPECT_EQ("\"TQ==\"", out);
}
{ // "Hello, world.", verified with base64decode.org.
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleBinary(SpanFrom(std::vector<uint8_t>(
{'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '.'})));
EXPECT_TRUE(status.ok());
EXPECT_EQ("\"SGVsbG8sIHdvcmxkLg==\"", out);
}
}
TEST(JsonStdStringWriterTest, HandlesErrors) {
// When an error is sent via HandleError, it saves it in the provided
// status and clears the output.
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleMapBegin();
WriteUTF8AsUTF16(writer.get(), "msg1");
writer->HandleError(Status{Error::JSON_PARSER_VALUE_EXPECTED, 42});
EXPECT_THAT(status, StatusIs(Error::JSON_PARSER_VALUE_EXPECTED, 42u));
EXPECT_EQ("", out);
}
TEST(JsonStdStringWriterTest, DoubleToString_LeadingZero) {
// In JSON, .1 must be rendered as 0.1, and -.7 must be rendered as -0.7.
std::string out;
Status status;
std::unique_ptr<ParserHandler> writer = NewJSONEncoder(&out, &status);
writer->HandleArrayBegin();
writer->HandleDouble(.1);
writer->HandleDouble(-.7);
writer->HandleArrayEnd();
EXPECT_EQ("[0.1,-0.7]", out);
}
// =============================================================================
// json::ParseJSON - for receiving streaming parser events for JSON
// =============================================================================
class Log : public ParserHandler {
public:
void HandleMapBegin() override { log_ << "map begin\n"; }
void HandleMapEnd() override { log_ << "map end\n"; }
void HandleArrayBegin() override { log_ << "array begin\n"; }
void HandleArrayEnd() override { log_ << "array end\n"; }
void HandleString8(span<uint8_t> chars) override {
log_ << "string8: " << std::string(chars.begin(), chars.end()) << "\n";
}
void HandleString16(span<uint16_t> chars) override {
raw_log_string16_.emplace_back(chars.begin(), chars.end());
log_ << "string16: " << UTF16ToUTF8(chars) << "\n";
}
void HandleBinary(span<uint8_t> bytes) override {
// JSON doesn't have native support for arbitrary bytes, so our parser will
// never call this.
CHECK(false);
}
void HandleDouble(double value) override {
log_ << "double: " << value << "\n";
}
void HandleInt32(int32_t value) override { log_ << "int: " << value << "\n"; }
void HandleBool(bool value) override { log_ << "bool: " << value << "\n"; }
void HandleNull() override { log_ << "null\n"; }
void HandleError(Status status) override { status_ = status; }
std::string str() const { return status_.ok() ? log_.str() : ""; }
std::vector<std::vector<uint16_t>> raw_log_string16() const {
return raw_log_string16_;
}
Status status() const { return status_; }
private:
std::ostringstream log_;
std::vector<std::vector<uint16_t>> raw_log_string16_;
Status status_;
};
class JsonParserTest : public ::testing::Test {
protected:
Log log_;
};
TEST_F(JsonParserTest, SimpleDictionary) {
std::string json = "{\"foo\": 42}";
ParseJSON(SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: foo\n"
"int: 42\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, UsAsciiDelCornerCase) {
// DEL (0x7f) is a 7 bit US-ASCII character, and while it is a control
// character according to Unicode, it's not considered a control
// character in https://tools.ietf.org/html/rfc7159#section-7, so
// it can be placed directly into the JSON string, without JSON escaping.
std::string json = "{\"foo\": \"a\x7f\"}";
ParseJSON(SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: foo\n"
"string16: a\x7f\n"
"map end\n",
log_.str());
// We've seen an implementation of UTF16ToUTF8 which would replace the DEL
// character with ' ', so this simple roundtrip tests the routines in
// encoding_test_helper.h, to make test failures of the above easier to
// diagnose.
std::vector<uint16_t> utf16 = UTF8ToUTF16(SpanFrom(json));
EXPECT_EQ(json, UTF16ToUTF8(SpanFrom(utf16)));
}
TEST_F(JsonParserTest, Whitespace) {
std::string json = "\n {\n\"msg\"\n: \v\"Hello, world.\"\t\r}\t";
ParseJSON(SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: msg\n"
"string16: Hello, world.\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, NestedDictionary) {
std::string json = "{\"foo\": {\"bar\": {\"baz\": 1}, \"bar2\": 2}}";
ParseJSON(SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: foo\n"
"map begin\n"
"string16: bar\n"
"map begin\n"
"string16: baz\n"
"int: 1\n"
"map end\n"
"string16: bar2\n"
"int: 2\n"
"map end\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, Doubles) {
std::string json = "{\"foo\": 3.1415, \"bar\": 31415e-4}";
ParseJSON(SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: foo\n"
"double: 3.1415\n"
"string16: bar\n"
"double: 3.1415\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, Unicode) {
// Globe character. 0xF0 0x9F 0x8C 0x8E in utf8, 0xD83C 0xDF0E in utf16.
std::string json = "{\"msg\": \"Hello, \\uD83C\\uDF0E.\"}";
ParseJSON(SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: msg\n"
"string16: Hello, 🌎.\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, Unicode_ParseUtf16) {
// Globe character. utf8: 0xF0 0x9F 0x8C 0x8E; utf16: 0xD83C 0xDF0E.
// Crescent moon character. utf8: 0xF0 0x9F 0x8C 0x99; utf16: 0xD83C 0xDF19.
// We provide the moon with json escape, but the earth as utf16 input.
// Either way they arrive as utf8 (after decoding in log_.str()).
std::vector<uint16_t> json =
UTF8ToUTF16(SpanFrom("{\"space\": \"🌎 \\uD83C\\uDF19.\"}"));
ParseJSON(SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: space\n"
"string16: 🌎 🌙.\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, Unicode_ParseUtf16_SingleEscapeUpToFFFF) {
// 0xFFFF is the max codepoint that can be represented as a single \u escape.
// One way to write this is \uffff, another way is to encode it as a 3 byte
// UTF-8 sequence (0xef 0xbf 0xbf). Both are equivalent.
// Example with both ways of encoding code point 0xFFFF in a JSON string.
std::string json = "{\"escape\": \"\xef\xbf\xbf or \\uffff\"}";
ParseJSON(SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
// Shows both inputs result in equivalent output once converted to UTF-8.
EXPECT_EQ(
"map begin\n"
"string16: escape\n"
"string16: \xEF\xBF\xBF or \xEF\xBF\xBF\n"
"map end\n",
log_.str());
// Make an even stronger assertion: The parser represents \xffff as a single
// UTF-16 char.
ASSERT_EQ(2u, log_.raw_log_string16().size());
std::vector<uint16_t> expected = {0xffff, ' ', 'o', 'r', ' ', 0xffff};
EXPECT_EQ(expected, log_.raw_log_string16()[1]);
}
TEST_F(JsonParserTest, Unicode_ParseUtf8) {
// Used below:
// гласность - example for 2 byte utf8, Russian word "glasnost"
// 屋 - example for 3 byte utf8, Chinese word for "house"
// 🌎 - example for 4 byte utf8: 0xF0 0x9F 0x8C 0x8E; utf16: 0xD83C 0xDF0E.
// 🌙 - example for escapes: utf8: 0xF0 0x9F 0x8C 0x99; utf16: 0xD83C 0xDF19.
// We provide the moon with json escape, but the earth as utf8 input.
// Either way they arrive as utf8 (after decoding in log_.str()).
std::string json =
"{"
"\"escapes\": \"\\uD83C\\uDF19\","
"\"2 byte\":\"гласность\","
"\"3 byte\":\"屋\","
"\"4 byte\":\"🌎\""
"}";
ParseJSON(SpanFrom(json), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: escapes\n"
"string16: 🌙\n"
"string16: 2 byte\n"
"string16: гласность\n"
"string16: 3 byte\n"
"string16: 屋\n"
"string16: 4 byte\n"
"string16: 🌎\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, UnprocessedInputRemainsError) {
// Trailing junk after the valid JSON.
std::string json = "{\"foo\": 3.1415} junk";
size_t junk_idx = json.find("junk");
EXPECT_NE(junk_idx, std::string::npos);
ParseJSON(SpanFrom(json), &log_);
EXPECT_THAT(log_.status(),
StatusIs(Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS, junk_idx));
EXPECT_EQ("", log_.str());
}
std::string MakeNestedJson(int depth) {
std::string json;
for (int ii = 0; ii < depth; ++ii)
json += "{\"foo\":";
json += "42";
for (int ii = 0; ii < depth; ++ii)
json += "}";
return json;
}
TEST_F(JsonParserTest, StackLimitExceededError_BelowLimit) {
// kStackLimit is 300 (see json_parser.cc). First let's
// try with a small nested example.
std::string json_3 = MakeNestedJson(3);
ParseJSON(SpanFrom(json_3), &log_);
EXPECT_TRUE(log_.status().ok());
EXPECT_EQ(
"map begin\n"
"string16: foo\n"
"map begin\n"
"string16: foo\n"
"map begin\n"
"string16: foo\n"
"int: 42\n"
"map end\n"
"map end\n"
"map end\n",
log_.str());
}
TEST_F(JsonParserTest, StackLimitExceededError_AtLimit) {
// Now with kStackLimit (300).
std::string json_limit = MakeNestedJson(300);
ParseJSON(span<uint8_t>(reinterpret_cast<const uint8_t*>(json_limit.data()),
json_limit.size()),
&log_);
EXPECT_THAT(log_.status(), StatusIsOk());
}
TEST_F(JsonParserTest, StackLimitExceededError_AboveLimit) {
// Now with kStackLimit + 1 (301) - it exceeds in the innermost instance.
std::string exceeded = MakeNestedJson(301);
ParseJSON(SpanFrom(exceeded), &log_);
EXPECT_THAT(log_.status(), StatusIs(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED,
strlen("{\"foo\":") * 301));
}
TEST_F(JsonParserTest, StackLimitExceededError_WayAboveLimit) {
// Now way past the limit. Still, the point of exceeding is 301.
std::string far_out = MakeNestedJson(320);
ParseJSON(SpanFrom(far_out), &log_);
EXPECT_THAT(log_.status(), StatusIs(Error::JSON_PARSER_STACK_LIMIT_EXCEEDED,
strlen("{\"foo\":") * 301));
}
TEST_F(JsonParserTest, NoInputError) {
std::string json = "";
ParseJSON(SpanFrom(json), &log_);
EXPECT_THAT(log_.status(), StatusIs(Error::JSON_PARSER_NO_INPUT, 0u));
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, InvalidTokenError) {
std::string json = "|";
ParseJSON(SpanFrom(json), &log_);
EXPECT_THAT(log_.status(), StatusIs(Error::JSON_PARSER_INVALID_TOKEN, 0u));
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, InvalidNumberError) {
// Mantissa exceeds max (the constant used here is int64_t max).
std::string json = "1E9223372036854775807";
ParseJSON(SpanFrom(json), &log_);
EXPECT_THAT(log_.status(), StatusIs(Error::JSON_PARSER_INVALID_NUMBER, 0u));
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, InvalidStringError) {
// \x22 is an unsupported escape sequence
std::string json = "\"foo\\x22\"";
ParseJSON(SpanFrom(json), &log_);
EXPECT_THAT(log_.status(), StatusIs(Error::JSON_PARSER_INVALID_STRING, 0u));
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, UnexpectedArrayEndError) {
std::string json = "[1,2,]";
ParseJSON(SpanFrom(json), &log_);
EXPECT_THAT(log_.status(),
StatusIs(Error::JSON_PARSER_UNEXPECTED_ARRAY_END, 5u));
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, CommaOrArrayEndExpectedError) {
std::string json = "[1,2 2";
ParseJSON(SpanFrom(json), &log_);
EXPECT_THAT(log_.status(),
StatusIs(Error::JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED, 5u));
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, StringLiteralExpectedError) {
// There's an error because the key bar, a string, is not terminated.
std::string json = "{\"foo\": 3.1415, \"bar: 31415e-4}";
ParseJSON(SpanFrom(json), &log_);
EXPECT_THAT(log_.status(),
StatusIs(Error::JSON_PARSER_STRING_LITERAL_EXPECTED, 16u));
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, ColonExpectedError) {
std::string json = "{\"foo\", 42}";
ParseJSON(SpanFrom(json), &log_);
EXPECT_THAT(log_.status(), StatusIs(Error::JSON_PARSER_COLON_EXPECTED, 6u));
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, UnexpectedMapEndError) {
std::string json = "{\"foo\": 42, }";
ParseJSON(SpanFrom(json), &log_);
EXPECT_THAT(log_.status(),
StatusIs(Error::JSON_PARSER_UNEXPECTED_MAP_END, 12u));
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, CommaOrMapEndExpectedError) {
// The second separator should be a comma.
std::string json = "{\"foo\": 3.1415: \"bar\": 0}";
ParseJSON(SpanFrom(json), &log_);
EXPECT_THAT(log_.status(),
StatusIs(Error::JSON_PARSER_COMMA_OR_MAP_END_EXPECTED, 14u));
EXPECT_EQ("", log_.str());
}
TEST_F(JsonParserTest, ValueExpectedError) {
std::string json = "}";
ParseJSON(SpanFrom(json), &log_);
EXPECT_THAT(log_.status(), StatusIs(Error::JSON_PARSER_VALUE_EXPECTED, 0u));
EXPECT_EQ("", log_.str());
}
template <typename T>
class ConvertJSONToCBORTest : public ::testing::Test {};
using ContainerTestTypes = ::testing::Types<std::vector<uint8_t>, std::string>;
TYPED_TEST_SUITE(ConvertJSONToCBORTest, ContainerTestTypes);
TYPED_TEST(ConvertJSONToCBORTest, RoundTripValidJson) {
for (const std::string& json_in : {
"{\"msg\":\"Hello, world.\",\"lst\":[1,2,3]}",
"3.1415",
"false",
"true",
"\"Hello, world.\"",
"[1,2,3]",
"[]",
}) {
SCOPED_TRACE(json_in);
TypeParam json(json_in.begin(), json_in.end());
std::vector<uint8_t> cbor;
{
Status status = ConvertJSONToCBOR(SpanFrom(json), &cbor);
EXPECT_THAT(status, StatusIsOk());
}
TypeParam roundtrip_json;
{
Status status = ConvertCBORToJSON(SpanFrom(cbor), &roundtrip_json);
EXPECT_THAT(status, StatusIsOk());
}
EXPECT_EQ(json, roundtrip_json);
}
}
TYPED_TEST(ConvertJSONToCBORTest, RoundTripValidJson16) {
std::vector<uint16_t> json16 = {
'{', '"', 'm', 's', 'g', '"', ':', '"', 'H', 'e', 'l', 'l',
'o', ',', ' ', 0xd83c, 0xdf0e, '.', '"', ',', '"', 'l', 's', 't',
'"', ':', '[', '1', ',', '2', ',', '3', ']', '}'};
std::vector<uint8_t> cbor;
{
Status status =
ConvertJSONToCBOR(span<uint16_t>(json16.data(), json16.size()), &cbor);
EXPECT_THAT(status, StatusIsOk());
}
TypeParam roundtrip_json;
{
Status status = ConvertCBORToJSON(SpanFrom(cbor), &roundtrip_json);
EXPECT_THAT(status, StatusIsOk());
}
std::string json = "{\"msg\":\"Hello, \\ud83c\\udf0e.\",\"lst\":[1,2,3]}";
TypeParam expected_json(json.begin(), json.end());
EXPECT_EQ(expected_json, roundtrip_json);
}
} // namespace json
} // namespace crdtp

154
deps/inspector_protocol/crdtp/maybe.h vendored Normal file
View File

@ -0,0 +1,154 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CRDTP_MAYBE_H_
#define CRDTP_MAYBE_H_
#include <cassert>
#include <memory>
namespace crdtp {
// =============================================================================
// detail::PtrMaybe, detail::ValueMaybe, templates for optional
// pointers / values which are used in ../lib/Forward_h.template.
// =============================================================================
namespace detail {
template <typename T>
class PtrMaybe {
public:
PtrMaybe() = default;
PtrMaybe(std::unique_ptr<T> value) : value_(std::move(value)) {}
PtrMaybe(PtrMaybe&& other) noexcept : value_(std::move(other.value_)) {}
void operator=(std::unique_ptr<T> value) { value_ = std::move(value); }
// std::optional<>-compatible accessors (preferred).
bool has_value() const { return !!value_; }
operator bool() const { return has_value(); }
const T& value() const& {
assert(has_value());
return *value_;
}
T& value() & {
assert(has_value());
return *value_;
}
T&& value() && {
assert(has_value());
return std::move(*value_);
}
const T& value_or(const T& default_value) const {
return has_value() ? *value_ : default_value;
}
T* operator->() { return &value(); }
const T* operator->() const { return &value(); }
T& operator*() & { return value(); }
const T& operator*() const& { return value(); }
T&& operator*() && { return std::move(value()); }
// Legacy Maybe<> accessors (deprecated).
T* fromJust() const {
assert(value_);
return value_.get();
}
T* fromMaybe(T* default_value) const {
return value_ ? value_.get() : default_value;
}
bool isJust() const { return value_ != nullptr; }
private:
std::unique_ptr<T> value_;
};
template <typename T>
class ValueMaybe {
public:
ValueMaybe() : is_just_(false), value_() {}
ValueMaybe(T value) : is_just_(true), value_(std::move(value)) {}
ValueMaybe(ValueMaybe&& other) noexcept
: is_just_(other.is_just_), value_(std::move(other.value_)) {}
void operator=(T value) {
value_ = std::move(value);
is_just_ = true;
}
// std::optional<>-compatible accessors (preferred).
bool has_value() const { return is_just_; }
operator bool() const { return has_value(); }
const T& value() const& {
assert(is_just_);
return value_;
}
T& value() & {
assert(is_just_);
return value_;
}
T&& value() && {
assert(is_just_);
return *std::move(value_);
}
template <typename U>
T value_or(U&& default_value) const& {
return is_just_ ? value_ : std::forward<U>(default_value);
}
template <typename U>
T value_or(U&& default_value) && {
return is_just_ ? std::move(value_) : std::forward<U>(default_value);
}
T* operator->() { return &value(); }
const T* operator->() const { return &value(); }
T& operator*() & { return value(); }
const T& operator*() const& { return value(); }
T&& operator*() && { return std::move(value()); }
// Legacy Maybe<> accessors (deprecated).
const T& fromJust() const {
assert(is_just_);
return value_;
}
const T& fromMaybe(const T& default_value) const {
return is_just_ ? value_ : default_value;
}
bool isJust() const { return is_just_; }
private:
bool is_just_;
T value_;
};
template <typename T>
struct MaybeTypedef {
typedef PtrMaybe<T> type;
};
template <>
struct MaybeTypedef<bool> {
typedef ValueMaybe<bool> type;
};
template <>
struct MaybeTypedef<int> {
typedef ValueMaybe<int> type;
};
template <>
struct MaybeTypedef<double> {
typedef ValueMaybe<double> type;
};
template <>
struct MaybeTypedef<std::string> {
typedef ValueMaybe<std::string> type;
};
} // namespace detail
template <typename T>
using Maybe = typename detail::MaybeTypedef<T>::type;
} // namespace crdtp
#endif // CRDTP_MAYBE_H_

View File

@ -0,0 +1,44 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "maybe.h"
#include <string>
#include <vector>
#include "test_platform.h"
namespace crdtp {
// =============================================================================
// detail::PtrMaybe, detail::ValueMaybe, templates for optional
// pointers / values which are used in ../lib/Forward_h.template.
// =============================================================================
TEST(PtrMaybeTest, SmokeTest) {
detail::PtrMaybe<std::vector<uint32_t>> example;
EXPECT_FALSE(example.has_value());
std::unique_ptr<std::vector<uint32_t>> v(new std::vector<uint32_t>);
v->push_back(42);
v->push_back(21);
example = std::move(v);
EXPECT_TRUE(example.has_value());
EXPECT_THAT(example.value(), testing::ElementsAre(42, 21));
std::vector<uint32_t> out = *std::move(example);
EXPECT_TRUE(example.has_value());
EXPECT_THAT(*example, testing::IsEmpty());
EXPECT_THAT(out, testing::ElementsAre(42, 21));
}
TEST(ValueMaybeTest, SmokeTest) {
detail::ValueMaybe<int32_t> example;
EXPECT_FALSE(example.has_value());
EXPECT_EQ(-1, example.value_or(-1));
example = 42;
EXPECT_TRUE(example.has_value());
EXPECT_EQ(42, example.value());
int32_t out = *std::move(example);
EXPECT_EQ(out, 42);
}
} // namespace crdtp

View File

@ -0,0 +1,39 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CRDTP_PARSER_HANDLER_H_
#define CRDTP_PARSER_HANDLER_H_
#include <cstdint>
#include "span.h"
#include "status.h"
namespace crdtp {
// Handler interface for parser events emitted by a streaming parser.
// See cbor::NewCBOREncoder, cbor::ParseCBOR, json::NewJSONEncoder,
// json::ParseJSON.
class ParserHandler {
public:
virtual ~ParserHandler() = default;
virtual void HandleMapBegin() = 0;
virtual void HandleMapEnd() = 0;
virtual void HandleArrayBegin() = 0;
virtual void HandleArrayEnd() = 0;
virtual void HandleString8(span<uint8_t> chars) = 0;
virtual void HandleString16(span<uint16_t> chars) = 0;
virtual void HandleBinary(span<uint8_t> bytes) = 0;
virtual void HandleDouble(double value) = 0;
virtual void HandleInt32(int32_t value) = 0;
virtual void HandleBool(bool value) = 0;
virtual void HandleNull() = 0;
// The parser may send one error even after other events have already
// been received. Client code is reponsible to then discard the
// already processed events.
// |error| must be an eror, as in, |error.is_ok()| can't be true.
virtual void HandleError(Status error) = 0;
};
} // namespace crdtp
#endif // CRDTP_PARSER_HANDLER_H_

View File

@ -0,0 +1,289 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "protocol_core.h"
#include <algorithm>
#include <cassert>
#include <string>
namespace crdtp {
DeserializerState::DeserializerState(std::vector<uint8_t> bytes)
: storage_(new std::vector<uint8_t>(std::move(bytes))),
tokenizer_(span<uint8_t>(storage_->data(), storage_->size())) {}
DeserializerState::DeserializerState(Storage storage, span<uint8_t> span)
: storage_(std::move(storage)), tokenizer_(span) {}
void DeserializerState::RegisterError(Error error) {
assert(Error::OK != error);
if (tokenizer_.Status().ok())
status_ = Status{error, tokenizer_.Status().pos};
}
void DeserializerState::RegisterFieldPath(span<char> name) {
field_path_.push_back(name);
}
std::string DeserializerState::ErrorMessage(span<char> message_name) const {
std::string msg = "Failed to deserialize ";
msg.append(message_name.begin(), message_name.end());
for (int field = static_cast<int>(field_path_.size()) - 1; field >= 0;
--field) {
msg.append(".");
msg.append(field_path_[field].begin(), field_path_[field].end());
}
Status s = status();
if (!s.ok())
msg += " - " + s.ToASCIIString();
return msg;
}
Status DeserializerState::status() const {
if (!tokenizer_.Status().ok())
return tokenizer_.Status();
return status_;
}
namespace {
constexpr int32_t GetMandatoryFieldMask(
const DeserializerDescriptor::Field* fields,
size_t count) {
int32_t mask = 0;
for (size_t i = 0; i < count; ++i) {
if (!fields[i].is_optional)
mask |= (1 << i);
}
return mask;
}
} // namespace
DeserializerDescriptor::DeserializerDescriptor(const Field* fields,
size_t field_count)
: fields_(fields),
field_count_(field_count),
mandatory_field_mask_(GetMandatoryFieldMask(fields, field_count)) {}
bool DeserializerDescriptor::Deserialize(DeserializerState* state,
void* obj) const {
auto* tokenizer = state->tokenizer();
// As a special compatibility quirk, allow empty objects if
// no mandatory fields are required.
if (tokenizer->TokenTag() == cbor::CBORTokenTag::DONE &&
!mandatory_field_mask_) {
return true;
}
if (tokenizer->TokenTag() == cbor::CBORTokenTag::ENVELOPE)
tokenizer->EnterEnvelope();
if (tokenizer->TokenTag() != cbor::CBORTokenTag::MAP_START) {
state->RegisterError(Error::CBOR_MAP_START_EXPECTED);
return false;
}
tokenizer->Next();
int32_t seen_mandatory_fields = 0;
for (; tokenizer->TokenTag() != cbor::CBORTokenTag::STOP; tokenizer->Next()) {
if (tokenizer->TokenTag() != cbor::CBORTokenTag::STRING8) {
state->RegisterError(Error::CBOR_INVALID_MAP_KEY);
return false;
}
span<uint8_t> u_key = tokenizer->GetString8();
span<char> key(reinterpret_cast<const char*>(u_key.data()), u_key.size());
tokenizer->Next();
if (!DeserializeField(state, key, &seen_mandatory_fields, obj))
return false;
}
// Only compute mandatory fields once per type.
int32_t missing_fields = seen_mandatory_fields ^ mandatory_field_mask_;
if (missing_fields) {
int32_t idx = 0;
while ((missing_fields & 1) == 0) {
missing_fields >>= 1;
++idx;
}
state->RegisterError(Error::BINDINGS_MANDATORY_FIELD_MISSING);
state->RegisterFieldPath(fields_[idx].name);
return false;
}
return true;
}
bool DeserializerDescriptor::DeserializeField(DeserializerState* state,
span<char> name,
int* seen_mandatory_fields,
void* obj) const {
// TODO(caseq): consider checking if the sought field is the one
// after the last deserialized.
const auto* begin = fields_;
const auto* end = fields_ + field_count_;
auto entry = std::lower_bound(
begin, end, name, [](const Field& field_desc, span<char> field_name) {
return SpanLessThan(field_desc.name, field_name);
});
// Unknown field is not an error -- we may be working against an
// implementation of a later version of the protocol.
// TODO(caseq): support unknown arrays and maps not enclosed by an envelope.
if (entry == end || !SpanEquals(entry->name, name))
return true;
if (!entry->deserializer(state, obj)) {
state->RegisterFieldPath(name);
return false;
}
if (!entry->is_optional)
*seen_mandatory_fields |= 1 << (entry - begin);
return true;
}
bool ProtocolTypeTraits<bool>::Deserialize(DeserializerState* state,
bool* value) {
const auto tag = state->tokenizer()->TokenTag();
if (tag == cbor::CBORTokenTag::TRUE_VALUE) {
*value = true;
return true;
}
if (tag == cbor::CBORTokenTag::FALSE_VALUE) {
*value = false;
return true;
}
state->RegisterError(Error::BINDINGS_BOOL_VALUE_EXPECTED);
return false;
}
void ProtocolTypeTraits<bool>::Serialize(bool value,
std::vector<uint8_t>* bytes) {
bytes->push_back(value ? cbor::EncodeTrue() : cbor::EncodeFalse());
}
bool ProtocolTypeTraits<int32_t>::Deserialize(DeserializerState* state,
int32_t* value) {
if (state->tokenizer()->TokenTag() != cbor::CBORTokenTag::INT32) {
state->RegisterError(Error::BINDINGS_INT32_VALUE_EXPECTED);
return false;
}
*value = state->tokenizer()->GetInt32();
return true;
}
void ProtocolTypeTraits<int32_t>::Serialize(int32_t value,
std::vector<uint8_t>* bytes) {
cbor::EncodeInt32(value, bytes);
}
ContainerSerializer::ContainerSerializer(std::vector<uint8_t>* bytes,
uint8_t tag)
: bytes_(bytes) {
envelope_.EncodeStart(bytes_);
bytes_->push_back(tag);
}
void ContainerSerializer::EncodeStop() {
bytes_->push_back(cbor::EncodeStop());
envelope_.EncodeStop(bytes_);
}
ObjectSerializer::ObjectSerializer()
: serializer_(&owned_bytes_, cbor::EncodeIndefiniteLengthMapStart()) {}
ObjectSerializer::~ObjectSerializer() = default;
std::unique_ptr<Serializable> ObjectSerializer::Finish() {
serializer_.EncodeStop();
return Serializable::From(std::move(owned_bytes_));
}
bool ProtocolTypeTraits<double>::Deserialize(DeserializerState* state,
double* value) {
// Double values that round-trip through JSON may end up getting represented
// as an int32 (SIGNED, UNSIGNED) on the wire in CBOR. Therefore, we also
// accept an INT32 here.
if (state->tokenizer()->TokenTag() == cbor::CBORTokenTag::INT32) {
*value = state->tokenizer()->GetInt32();
return true;
}
if (state->tokenizer()->TokenTag() != cbor::CBORTokenTag::DOUBLE) {
state->RegisterError(Error::BINDINGS_DOUBLE_VALUE_EXPECTED);
return false;
}
*value = state->tokenizer()->GetDouble();
return true;
}
void ProtocolTypeTraits<double>::Serialize(double value,
std::vector<uint8_t>* bytes) {
cbor::EncodeDouble(value, bytes);
}
class IncomingDeferredMessage : public DeferredMessage {
public:
// Creates the state from the part of another message.
// Note storage is opaque and is mostly to retain ownership.
// It may be null in case caller owns the memory and will dispose
// of the message synchronously.
IncomingDeferredMessage(DeserializerState::Storage storage,
span<uint8_t> span)
: storage_(storage), span_(span) {}
private:
DeserializerState MakeDeserializer() const override {
return DeserializerState(storage_, span_);
}
void AppendSerialized(std::vector<uint8_t>* out) const override {
out->insert(out->end(), span_.begin(), span_.end());
}
DeserializerState::Storage storage_;
span<uint8_t> span_;
};
class OutgoingDeferredMessage : public DeferredMessage {
public:
OutgoingDeferredMessage() = default;
explicit OutgoingDeferredMessage(std::unique_ptr<Serializable> serializable)
: serializable_(std::move(serializable)) {
assert(!!serializable_);
}
private:
DeserializerState MakeDeserializer() const override {
return DeserializerState(serializable_->Serialize());
}
void AppendSerialized(std::vector<uint8_t>* out) const override {
serializable_->AppendSerialized(out);
}
std::unique_ptr<Serializable> serializable_;
};
// static
std::unique_ptr<DeferredMessage> DeferredMessage::FromSerializable(
std::unique_ptr<Serializable> serializeable) {
return std::make_unique<OutgoingDeferredMessage>(std::move(serializeable));
}
// static
std::unique_ptr<DeferredMessage> DeferredMessage::FromSpan(
span<uint8_t> bytes) {
return std::make_unique<IncomingDeferredMessage>(nullptr, bytes);
}
bool ProtocolTypeTraits<std::unique_ptr<DeferredMessage>>::Deserialize(
DeserializerState* state,
std::unique_ptr<DeferredMessage>* value) {
if (state->tokenizer()->TokenTag() != cbor::CBORTokenTag::ENVELOPE) {
state->RegisterError(Error::CBOR_INVALID_ENVELOPE);
return false;
}
*value = std::make_unique<IncomingDeferredMessage>(
state->storage(), state->tokenizer()->GetEnvelope());
return true;
}
void ProtocolTypeTraits<std::unique_ptr<DeferredMessage>>::Serialize(
const std::unique_ptr<DeferredMessage>& value,
std::vector<uint8_t>* bytes) {
value->AppendSerialized(bytes);
}
} // namespace crdtp

View File

@ -0,0 +1,418 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CRDTP_PROTOCOL_CORE_H_
#define CRDTP_PROTOCOL_CORE_H_
#include <sys/types.h>
#include <memory>
#include <string>
#include <tuple>
#include <vector>
#include "cbor.h"
#include "maybe.h"
#include "serializable.h"
#include "span.h"
#include "status.h"
namespace crdtp {
class CRDTP_EXPORT DeserializerState {
public:
using Storage = std::shared_ptr<const std::vector<uint8_t>>;
// Creates a state from the raw bytes received from the peer.
explicit DeserializerState(std::vector<uint8_t> bytes);
// Creates the state from the part of another message.
DeserializerState(Storage storage, span<uint8_t> span);
DeserializerState(const DeserializerState& r) = delete;
DeserializerState(DeserializerState&& r) = default;
// Registers |error|, unless the tokenizer's status is already an error.
void RegisterError(Error error);
// Registers |name| as a segment of the field path.
void RegisterFieldPath(span<char> name);
// Produces an error message considering |tokenizer.Status()|,
// status_, and field_path_.
std::string ErrorMessage(span<char> message_name) const;
Status status() const;
const Storage& storage() const { return storage_; }
cbor::CBORTokenizer* tokenizer() { return &tokenizer_; }
private:
const Storage storage_;
cbor::CBORTokenizer tokenizer_;
Status status_;
std::vector<span<char>> field_path_;
};
template <typename T, typename = void>
struct ProtocolTypeTraits {};
template <>
struct CRDTP_EXPORT ProtocolTypeTraits<bool> {
static bool Deserialize(DeserializerState* state, bool* value);
static void Serialize(bool value, std::vector<uint8_t>* bytes);
};
template <>
struct CRDTP_EXPORT ProtocolTypeTraits<int32_t> {
static bool Deserialize(DeserializerState* state, int* value);
static void Serialize(int value, std::vector<uint8_t>* bytes);
};
template <>
struct CRDTP_EXPORT ProtocolTypeTraits<double> {
static bool Deserialize(DeserializerState* state, double* value);
static void Serialize(double value, std::vector<uint8_t>* bytes);
};
class CRDTP_EXPORT ContainerSerializer {
public:
ContainerSerializer(std::vector<uint8_t>* bytes, uint8_t tag);
template <typename T>
void AddField(span<char> field_name, const T& value) {
cbor::EncodeString8(
span<uint8_t>(reinterpret_cast<const uint8_t*>(field_name.data()),
field_name.size()),
bytes_);
ProtocolTypeTraits<T>::Serialize(value, bytes_);
}
template <typename T>
void AddField(span<char> field_name, const detail::ValueMaybe<T>& value) {
if (!value.has_value()) {
return;
}
AddField(field_name, value.value());
}
template <typename T>
void AddField(span<char> field_name, const detail::PtrMaybe<T>& value) {
if (!value.has_value()) {
return;
}
AddField(field_name, value.value());
}
void EncodeStop();
private:
std::vector<uint8_t>* const bytes_;
cbor::EnvelopeEncoder envelope_;
};
class CRDTP_EXPORT ObjectSerializer {
public:
ObjectSerializer();
~ObjectSerializer();
template <typename T>
void AddField(span<char> name, const T& field) {
serializer_.AddField(name, field);
}
std::unique_ptr<Serializable> Finish();
private:
std::vector<uint8_t> owned_bytes_;
ContainerSerializer serializer_;
};
class CRDTP_EXPORT DeserializerDescriptor {
public:
struct CRDTP_EXPORT Field {
span<char> name;
bool is_optional;
bool (*deserializer)(DeserializerState* state, void* obj);
};
DeserializerDescriptor(const Field* fields, size_t field_count);
bool Deserialize(DeserializerState* state, void* obj) const;
private:
bool DeserializeField(DeserializerState* state,
span<char> name,
int* seen_mandatory_fields,
void* obj) const;
const Field* const fields_;
const size_t field_count_;
const int mandatory_field_mask_;
};
template <typename T>
struct ProtocolTypeTraits<std::vector<T>> {
static bool Deserialize(DeserializerState* state, std::vector<T>* value) {
auto* tokenizer = state->tokenizer();
if (tokenizer->TokenTag() == cbor::CBORTokenTag::ENVELOPE)
tokenizer->EnterEnvelope();
if (tokenizer->TokenTag() != cbor::CBORTokenTag::ARRAY_START) {
state->RegisterError(Error::CBOR_ARRAY_START_EXPECTED);
return false;
}
assert(value->empty());
tokenizer->Next();
for (; tokenizer->TokenTag() != cbor::CBORTokenTag::STOP;
tokenizer->Next()) {
value->emplace_back();
if (!ProtocolTypeTraits<T>::Deserialize(state, &value->back()))
return false;
}
return true;
}
static void Serialize(const std::vector<T>& value,
std::vector<uint8_t>* bytes) {
ContainerSerializer container_serializer(
bytes, cbor::EncodeIndefiniteLengthArrayStart());
for (const auto& item : value)
ProtocolTypeTraits<T>::Serialize(item, bytes);
container_serializer.EncodeStop();
}
};
template <typename T>
struct ProtocolTypeTraits<std::unique_ptr<std::vector<T>>> {
static bool Deserialize(DeserializerState* state,
std::unique_ptr<std::vector<T>>* value) {
auto res = std::make_unique<std::vector<T>>();
if (!ProtocolTypeTraits<std::vector<T>>::Deserialize(state, res.get()))
return false;
*value = std::move(res);
return true;
}
static void Serialize(const std::unique_ptr<std::vector<T>>& value,
std::vector<uint8_t>* bytes) {
ProtocolTypeTraits<std::vector<T>>::Serialize(*value, bytes);
}
};
class CRDTP_EXPORT DeferredMessage : public Serializable {
public:
static std::unique_ptr<DeferredMessage> FromSerializable(
std::unique_ptr<Serializable> serializeable);
static std::unique_ptr<DeferredMessage> FromSpan(span<uint8_t> bytes);
~DeferredMessage() override = default;
virtual DeserializerState MakeDeserializer() const = 0;
protected:
DeferredMessage() = default;
};
template <>
struct CRDTP_EXPORT ProtocolTypeTraits<std::unique_ptr<DeferredMessage>> {
static bool Deserialize(DeserializerState* state,
std::unique_ptr<DeferredMessage>* value);
static void Serialize(const std::unique_ptr<DeferredMessage>& value,
std::vector<uint8_t>* bytes);
};
template <typename T>
struct ProtocolTypeTraits<detail::ValueMaybe<T>> {
static bool Deserialize(DeserializerState* state,
detail::ValueMaybe<T>* value) {
T res;
if (!ProtocolTypeTraits<T>::Deserialize(state, &res))
return false;
*value = std::move(res);
return true;
}
static void Serialize(const detail::ValueMaybe<T>& value,
std::vector<uint8_t>* bytes) {
ProtocolTypeTraits<T>::Serialize(value.value(), bytes);
}
};
template <typename T>
struct ProtocolTypeTraits<detail::PtrMaybe<T>> {
static bool Deserialize(DeserializerState* state,
detail::PtrMaybe<T>* value) {
std::unique_ptr<T> res;
if (!ProtocolTypeTraits<std::unique_ptr<T>>::Deserialize(state, &res))
return false;
*value = std::move(res);
return true;
}
static void Serialize(const detail::PtrMaybe<T>& value,
std::vector<uint8_t>* bytes) {
ProtocolTypeTraits<T>::Serialize(value.value(), bytes);
}
};
template <typename T>
class DeserializableProtocolObject {
public:
static StatusOr<std::unique_ptr<T>> ReadFrom(
const DeferredMessage& deferred_message) {
auto state = deferred_message.MakeDeserializer();
if (auto res = Deserialize(&state))
return StatusOr<std::unique_ptr<T>>(std::move(res));
return StatusOr<std::unique_ptr<T>>(state.status());
}
static StatusOr<std::unique_ptr<T>> ReadFrom(std::vector<uint8_t> bytes) {
auto state = DeserializerState(std::move(bytes));
if (auto res = Deserialize(&state))
return StatusOr<std::unique_ptr<T>>(std::move(res));
return StatusOr<std::unique_ptr<T>>(state.status());
}
// Short-hand for legacy clients. This would swallow any errors, consider
// using ReadFrom.
static std::unique_ptr<T> FromBinary(const uint8_t* bytes, size_t size) {
std::unique_ptr<T> value(new T());
auto deserializer = DeferredMessage::FromSpan(span<uint8_t>(bytes, size))
->MakeDeserializer();
std::ignore = Deserialize(&deserializer, value.get());
return value;
}
[[nodiscard]] static bool Deserialize(DeserializerState* state, T* value) {
return T::deserializer_descriptor().Deserialize(state, value);
}
protected:
// This is for the sake of the macros used by derived classes thay may be in
// a different namespace;
using ProtocolType = T;
using DeserializerDescriptorType = DeserializerDescriptor;
template <typename U>
using DeserializableBase = DeserializableProtocolObject<U>;
DeserializableProtocolObject() = default;
~DeserializableProtocolObject() = default;
private:
friend struct ProtocolTypeTraits<std::unique_ptr<T>>;
static std::unique_ptr<T> Deserialize(DeserializerState* state) {
std::unique_ptr<T> value(new T());
if (Deserialize(state, value.get()))
return value;
return nullptr;
}
};
template <typename T>
class ProtocolObject : public Serializable,
public DeserializableProtocolObject<T> {
public:
std::unique_ptr<T> Clone() const {
std::vector<uint8_t> serialized;
AppendSerialized(&serialized);
return T::ReadFrom(std::move(serialized)).value();
}
protected:
using ProtocolType = T;
ProtocolObject() = default;
};
template <typename T>
struct ProtocolTypeTraits<
T,
typename std::enable_if<
std::is_base_of<ProtocolObject<T>, T>::value>::type> {
static bool Deserialize(DeserializerState* state, T* value) {
return T::Deserialize(state, value);
}
static void Serialize(const T& value, std::vector<uint8_t>* bytes) {
value.AppendSerialized(bytes);
}
};
template <typename T>
struct ProtocolTypeTraits<
std::unique_ptr<T>,
typename std::enable_if<
std::is_base_of<ProtocolObject<T>, T>::value>::type> {
static bool Deserialize(DeserializerState* state, std::unique_ptr<T>* value) {
std::unique_ptr<T> res = T::Deserialize(state);
if (!res)
return false;
*value = std::move(res);
return true;
}
static void Serialize(const std::unique_ptr<T>& value,
std::vector<uint8_t>* bytes) {
ProtocolTypeTraits<T>::Serialize(*value, bytes);
}
};
template <typename T, typename F>
bool ConvertProtocolValue(const F& from, T* to) {
std::vector<uint8_t> bytes;
ProtocolTypeTraits<F>::Serialize(from, &bytes);
auto deserializer =
DeferredMessage::FromSpan(span<uint8_t>(bytes.data(), bytes.size()))
->MakeDeserializer();
return ProtocolTypeTraits<T>::Deserialize(&deserializer, to);
}
#define DECLARE_DESERIALIZATION_SUPPORT() \
friend DeserializableBase<ProtocolType>; \
static const DeserializerDescriptorType& deserializer_descriptor()
#define DECLARE_SERIALIZATION_SUPPORT() \
public: \
void AppendSerialized(std::vector<uint8_t>* bytes) const override; \
\
private: \
friend DeserializableBase<ProtocolType>; \
static const DeserializerDescriptorType& deserializer_descriptor()
#define CRDTP_DESERIALIZE_FILED_IMPL(name, field, is_optional) \
{ \
MakeSpan(name), is_optional, \
[](DeserializerState* __state, void* __obj) -> bool { \
return ProtocolTypeTraits<decltype(field)>::Deserialize( \
__state, &static_cast<ProtocolType*>(__obj)->field); \
} \
}
// clang-format off
#define CRDTP_BEGIN_DESERIALIZER(type) \
const type::DeserializerDescriptorType& type::deserializer_descriptor() { \
using namespace crdtp; \
static const DeserializerDescriptorType::Field fields[] = {
#define CRDTP_END_DESERIALIZER() \
}; \
static const DeserializerDescriptorType s_desc( \
fields, sizeof fields / sizeof fields[0]); \
return s_desc; \
}
#define CRDTP_DESERIALIZE_FIELD(name, field) \
CRDTP_DESERIALIZE_FILED_IMPL(name, field, false)
#define CRDTP_DESERIALIZE_FIELD_OPT(name, field) \
CRDTP_DESERIALIZE_FILED_IMPL(name, field, true)
#define CRDTP_BEGIN_SERIALIZER(type) \
void type::AppendSerialized(std::vector<uint8_t>* bytes) const { \
using namespace crdtp; \
ContainerSerializer __serializer(bytes, \
cbor::EncodeIndefiniteLengthMapStart());
#define CRDTP_SERIALIZE_FIELD(name, field) \
__serializer.AddField(MakeSpan(name), field)
#define CRDTP_END_SERIALIZER() \
__serializer.EncodeStop(); \
} class __cddtp_dummy_name
// clang-format on
} // namespace crdtp
#endif // CRDTP_PROTOCOL_CORE_H_

View File

@ -0,0 +1,476 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "protocol_core.h"
#include <memory>
#include "cbor.h"
#include "maybe.h"
#include "status_test_support.h"
#include "test_platform.h"
#include "test_string_traits.h"
namespace crdtp {
namespace {
using ::testing::Eq;
template <typename TResult, typename TArg>
std::unique_ptr<TResult> RoundtripToType(const TArg& obj) {
std::vector<uint8_t> bytes;
obj.AppendSerialized(&bytes);
StatusOr<std::unique_ptr<TResult>> result =
TResult::ReadFrom(std::move(bytes));
return std::move(result).value();
}
template <typename T>
std::unique_ptr<T> Roundtrip(const T& obj) {
return RoundtripToType<T, T>(obj);
}
// These TestTypeFOO classes below would normally be generated
// by the protocol generator.
class TestTypeBasic : public ProtocolObject<TestTypeBasic> {
public:
TestTypeBasic() = default;
const std::string& GetValue() const { return value_; }
void SetValue(std::string value) { value_ = std::move(value); }
private:
DECLARE_SERIALIZATION_SUPPORT();
std::string value_;
};
// clang-format off
CRDTP_BEGIN_DESERIALIZER(TestTypeBasic)
CRDTP_DESERIALIZE_FIELD("value", value_)
CRDTP_END_DESERIALIZER()
CRDTP_BEGIN_SERIALIZER(TestTypeBasic)
CRDTP_SERIALIZE_FIELD("value", value_);
CRDTP_END_SERIALIZER();
// clang-format on
TEST(ProtocolCoreTest, Basic) {
TestTypeBasic obj1;
obj1.SetValue("foo");
auto obj2 = Roundtrip(obj1);
ASSERT_THAT(obj2, Not(testing::IsNull()));
EXPECT_THAT(obj2->GetValue(), Eq("foo"));
}
TEST(ProtocolCoreTest, FailedToDeserializeTestTypeBasic) {
std::vector<uint8_t> garbage = {'g', 'a', 'r', 'b', 'a', 'g', 'e'};
StatusOr<std::unique_ptr<TestTypeBasic>> result =
TestTypeBasic::ReadFrom(std::move(garbage));
EXPECT_THAT(result.status(), StatusIs(Error::CBOR_INVALID_STRING8, 0));
}
class TestTypeBasicDouble : public ProtocolObject<TestTypeBasicDouble> {
public:
TestTypeBasicDouble() = default;
double GetValue() const { return value_; }
void SetValue(double value) { value_ = value; }
private:
DECLARE_SERIALIZATION_SUPPORT();
double value_;
};
// clang-format off
CRDTP_BEGIN_DESERIALIZER(TestTypeBasicDouble)
CRDTP_DESERIALIZE_FIELD("value", value_)
CRDTP_END_DESERIALIZER()
CRDTP_BEGIN_SERIALIZER(TestTypeBasicDouble)
CRDTP_SERIALIZE_FIELD("value", value_);
CRDTP_END_SERIALIZER();
// clang-format on
TEST(TestBasicDouble, ParserAllowsAllowsDoubleEncodedAsInt) {
// We allow double's encoded as INT32, because this is what a roundtrip via
// JSON would produce.
std::vector<uint8_t> encoded;
crdtp::cbor::EnvelopeEncoder envelope;
envelope.EncodeStart(&encoded);
encoded.push_back(crdtp::cbor::EncodeIndefiniteLengthMapStart());
crdtp::cbor::EncodeString8(crdtp::SpanFrom("value"), &encoded);
crdtp::cbor::EncodeInt32(
42, &encoded); // It's a double field, but we encode an int.
encoded.push_back(crdtp::cbor::EncodeStop());
envelope.EncodeStop(&encoded);
auto obj = TestTypeBasicDouble::ReadFrom(encoded).value();
ASSERT_THAT(obj, Not(testing::IsNull()));
EXPECT_THAT(obj->GetValue(), Eq(42));
}
class TestTypeComposite : public ProtocolObject<TestTypeComposite> {
public:
bool GetBoolField() const { return bool_field_; }
void SetBoolField(bool value) { bool_field_ = value; }
int GetIntField() const { return int_field_; }
void SetIntField(int value) { int_field_ = value; }
double GetDoubleField() const { return double_field_; }
void SetDoubleField(double value) { double_field_ = value; }
const std::string& GetStrField() const { return str_field_; }
void SetStrField(std::string value) { str_field_ = std::move(value); }
const TestTypeBasic* GetTestTypeBasicField() {
return test_type1_field_.get();
}
void SetTestTypeBasicField(std::unique_ptr<TestTypeBasic> value) {
test_type1_field_ = std::move(value);
}
private:
DECLARE_SERIALIZATION_SUPPORT();
bool bool_field_ = false;
int int_field_ = 0;
double double_field_ = 0.0;
std::string str_field_;
std::unique_ptr<TestTypeBasic> test_type1_field_;
};
// clang-format off
CRDTP_BEGIN_DESERIALIZER(TestTypeComposite)
CRDTP_DESERIALIZE_FIELD("bool_field", bool_field_),
CRDTP_DESERIALIZE_FIELD("double_field", double_field_),
CRDTP_DESERIALIZE_FIELD("int_field", int_field_),
CRDTP_DESERIALIZE_FIELD("str_field", str_field_),
CRDTP_DESERIALIZE_FIELD("test_type1_field", test_type1_field_),
CRDTP_END_DESERIALIZER()
CRDTP_BEGIN_SERIALIZER(TestTypeComposite)
CRDTP_SERIALIZE_FIELD("bool_field", bool_field_),
CRDTP_SERIALIZE_FIELD("double_field", double_field_),
CRDTP_SERIALIZE_FIELD("int_field", int_field_),
CRDTP_SERIALIZE_FIELD("str_field", str_field_),
CRDTP_SERIALIZE_FIELD("test_type1_field", test_type1_field_),
CRDTP_END_SERIALIZER();
// clang-format on
TEST(ProtocolCoreTest, Composite) {
TestTypeComposite obj1;
obj1.SetBoolField(true);
obj1.SetIntField(42);
obj1.SetDoubleField(2.718281828);
obj1.SetStrField("bar");
auto val1 = std::make_unique<TestTypeBasic>();
val1->SetValue("bazzzz");
obj1.SetTestTypeBasicField(std::move(val1));
auto obj2 = Roundtrip(obj1);
ASSERT_THAT(obj2, Not(testing::IsNull()));
EXPECT_THAT(obj2->GetBoolField(), Eq(true));
EXPECT_THAT(obj2->GetIntField(), Eq(42));
EXPECT_THAT(obj2->GetDoubleField(), Eq(2.718281828));
EXPECT_THAT(obj2->GetStrField(), Eq("bar"));
EXPECT_THAT(obj2->GetTestTypeBasicField()->GetValue(), Eq("bazzzz"));
}
class CompositeParsingTest : public testing::Test {
public:
CompositeParsingTest() {
TestTypeComposite top;
top.SetIntField(42);
top.SetBoolField(true);
top.SetIntField(42);
top.SetDoubleField(2.718281828);
top.SetStrField("junk");
auto child = std::make_unique<TestTypeBasic>();
child->SetValue("child_value");
top.SetTestTypeBasicField(std::move(child));
// Let's establish that |serialized_| is a properly serialized
// representation of |top|, by checking that it deserializes ok.
top.AppendSerialized(&serialized_);
TestTypeComposite::ReadFrom(serialized_).value();
}
protected:
std::vector<uint8_t> serialized_;
};
TEST_F(CompositeParsingTest, DecodingFailure_CBORTokenizer) {
// Mutates |serialized_| so that it won't parse correctly. In this case,
// we're changing a string value so that it's invalid, making CBORTokenizer
// unhappy.
size_t position =
std::string(reinterpret_cast<const char*>(serialized_.data()),
serialized_.size())
.find("child_value");
EXPECT_GT(position, 0ul);
// We override the byte just before so that it's still a string
// (3 << 5), but the length is encoded in the bytes that follows.
// So, we override that with 0xff (255), which exceeds the length
// of the message and thereby makes the string8 invalid.
--position;
serialized_[position] = 3 << 5 | // major type: STRING
25; // length in encoded in byte that follows.
serialized_[position + 1] = 0xff; // length
auto result = TestTypeComposite::ReadFrom(serialized_);
EXPECT_THAT(result.status(), StatusIs(Error::CBOR_INVALID_STRING8, position));
}
TEST_F(CompositeParsingTest, DecodingFailure_MandatoryFieldMissingShallow) {
// We're changing the string key "int_field" to something else ("lnt_field"),
// so that the mandatory field value won't be found. Unknown fields are
// ignored for compatibility, so that's why this simple technique works here.
size_t position =
std::string(reinterpret_cast<const char*>(serialized_.data()),
serialized_.size())
.find("int_field");
serialized_[position] = 'l'; // Change 'i' to 'l'.
// serialized_.size() - 1 is the STOP character for the entire message,
size_t expected_error_pos = serialized_.size() - 1;
auto result = TestTypeComposite::ReadFrom(serialized_);
EXPECT_THAT(result.status(), StatusIs(Error::BINDINGS_MANDATORY_FIELD_MISSING,
expected_error_pos));
}
TEST_F(CompositeParsingTest, DecodingFailure_MandatoryFieldMissingNested) {
// We're changing the string key "value" to something else ("falue"), so that
// the mandatory field value in TestTypeBasic in the child won't be found.
size_t position =
std::string(reinterpret_cast<const char*>(serialized_.data()),
serialized_.size())
.find("value");
serialized_[position] = 'f'; // Change 'v' to 'f'.
// serialized_.size() - 1 is the STOP character for the enclosing message,
// and serialized_.size() - 2 is the STOP character for TestTypeBasic.
size_t expected_error_pos = serialized_.size() - 2;
auto result = TestTypeComposite::ReadFrom(serialized_);
EXPECT_THAT(result.status(), StatusIs(Error::BINDINGS_MANDATORY_FIELD_MISSING,
expected_error_pos));
}
TEST_F(CompositeParsingTest, DecodingFailure_BoolValueExpected) {
// We're changing the bool value (true) to null; we do this by looking
// for bool_field, and searching from there for TRUE; both TRUE and null
// are just one byte in the serialized buffer, so this swap is convenient.
std::string serialized_view(reinterpret_cast<const char*>(serialized_.data()),
serialized_.size());
size_t position = serialized_view.find("bool_field");
for (; position < serialized_.size(); ++position) {
if (serialized_[position] == crdtp::cbor::EncodeTrue()) {
serialized_[position] = crdtp::cbor::EncodeNull();
break;
}
}
auto result = TestTypeComposite::ReadFrom(serialized_);
EXPECT_THAT(result.status(),
StatusIs(Error::BINDINGS_BOOL_VALUE_EXPECTED, position));
}
class TestTypeArrays : public ProtocolObject<TestTypeArrays> {
public:
const std::vector<int>* GetIntArray() const { return &int_array_; }
void SetIntArray(std::vector<int> value) { int_array_ = std::move(value); }
const std::vector<double>* GetDoubleArray() const { return &double_array_; }
void SetDoubleArray(std::vector<double> value) {
double_array_ = std::move(value);
}
const std::vector<std::string>* GetStrArray() const { return &str_array_; }
void SetStrArray(std::vector<std::string> value) {
str_array_ = std::move(value);
}
const std::vector<std::unique_ptr<TestTypeBasic>>* GetTestTypeBasicArray()
const {
return &test_type_basic_array_;
}
void SetTestTypeBasicArray(
std::vector<std::unique_ptr<TestTypeBasic>> value) {
test_type_basic_array_ = std::move(value);
}
private:
DECLARE_SERIALIZATION_SUPPORT();
std::vector<int> int_array_;
std::vector<double> double_array_;
std::vector<std::string> str_array_;
std::vector<std::unique_ptr<TestTypeBasic>> test_type_basic_array_;
};
// clang-format off
CRDTP_BEGIN_DESERIALIZER(TestTypeArrays)
CRDTP_DESERIALIZE_FIELD("int_array", int_array_),
CRDTP_DESERIALIZE_FIELD("str_array", str_array_),
CRDTP_DESERIALIZE_FIELD("test_type_basic_array", test_type_basic_array_),
CRDTP_END_DESERIALIZER()
CRDTP_BEGIN_SERIALIZER(TestTypeArrays)
CRDTP_SERIALIZE_FIELD("int_array", int_array_),
CRDTP_SERIALIZE_FIELD("str_array", str_array_),
CRDTP_SERIALIZE_FIELD("test_type_basic_array", test_type_basic_array_),
CRDTP_END_SERIALIZER();
// clang-format on
TEST_F(CompositeParsingTest, Arrays) {
TestTypeArrays obj1;
obj1.SetIntArray(std::vector<int>{1, 3, 5, 7});
std::vector<std::string> strs;
strs.emplace_back("foo");
strs.emplace_back(std::string("bar"));
obj1.SetStrArray(std::move(strs));
auto val1 = std::make_unique<TestTypeBasic>();
val1->SetValue("bazzzz");
std::vector<std::unique_ptr<TestTypeBasic>> vec1;
vec1.emplace_back(std::move(val1));
obj1.SetTestTypeBasicArray(std::move(vec1));
auto obj2 = Roundtrip(obj1);
ASSERT_THAT(obj2, Not(testing::IsNull()));
EXPECT_THAT(*obj2->GetIntArray(), testing::ElementsAre(1, 3, 5, 7));
EXPECT_THAT(*obj2->GetStrArray(), testing::ElementsAre("foo", "bar"));
EXPECT_THAT(obj2->GetDoubleArray()->size(), Eq(0ul));
EXPECT_THAT(obj2->GetTestTypeBasicArray()->size(), Eq(1ul));
EXPECT_THAT(obj2->GetTestTypeBasicArray()->front()->GetValue(), Eq("bazzzz"));
}
class TestTypeOptional : public ProtocolObject<TestTypeOptional> {
public:
TestTypeOptional() = default;
bool HasIntField() const { return int_field_.has_value(); }
int GetIntField() const { return int_field_.value(); }
void SetIntField(int value) { int_field_ = value; }
bool HasStrField() { return str_field_.has_value(); }
const std::string& GetStrField() const { return str_field_.value(); }
void SetStrField(std::string value) { str_field_ = std::move(value); }
bool HasTestTypeBasicField() { return test_type_basic_field_.has_value(); }
const TestTypeBasic* GetTestTypeBasicField() const {
return test_type_basic_field_.has_value() ? &test_type_basic_field_.value()
: nullptr;
}
void SetTestTypeBasicField(std::unique_ptr<TestTypeBasic> value) {
test_type_basic_field_ = std::move(value);
}
private:
DECLARE_SERIALIZATION_SUPPORT();
Maybe<int> int_field_;
Maybe<std::string> str_field_;
Maybe<TestTypeBasic> test_type_basic_field_;
};
// clang-format off
CRDTP_BEGIN_DESERIALIZER(TestTypeOptional)
CRDTP_DESERIALIZE_FIELD_OPT("int_field", int_field_),
CRDTP_DESERIALIZE_FIELD_OPT("str_field", str_field_),
CRDTP_DESERIALIZE_FIELD_OPT("test_type_basic_field", test_type_basic_field_),
CRDTP_END_DESERIALIZER()
CRDTP_BEGIN_SERIALIZER(TestTypeOptional)
CRDTP_SERIALIZE_FIELD("int_field", int_field_),
CRDTP_SERIALIZE_FIELD("str_field", str_field_),
CRDTP_SERIALIZE_FIELD("test_type_basic_field", test_type_basic_field_),
CRDTP_END_SERIALIZER();
// clang-format on
TEST(ProtocolCoreTest, OptionalAbsent) {
TestTypeOptional obj1;
auto obj2 = Roundtrip(obj1);
ASSERT_THAT(obj2, Not(testing::IsNull()));
EXPECT_THAT(obj2->HasIntField(), Eq(false));
EXPECT_THAT(obj2->HasStrField(), Eq(false));
EXPECT_THAT(obj2->HasTestTypeBasicField(), Eq(false));
}
TEST(ProtocolCoreTest, OptionalPresent) {
TestTypeOptional obj1;
obj1.SetIntField(42);
obj1.SetStrField("foo");
auto val1 = std::make_unique<TestTypeBasic>();
val1->SetValue("bar");
obj1.SetTestTypeBasicField(std::move(val1));
auto obj2 = Roundtrip(obj1);
ASSERT_THAT(obj2, Not(testing::IsNull()));
EXPECT_THAT(obj2->HasIntField(), Eq(true));
EXPECT_THAT(obj2->GetIntField(), Eq(42));
EXPECT_THAT(obj2->HasStrField(), Eq(true));
EXPECT_THAT(obj2->GetStrField(), Eq("foo"));
EXPECT_THAT(obj2->HasTestTypeBasicField(), Eq(true));
EXPECT_THAT(obj2->GetTestTypeBasicField()->GetValue(), Eq("bar"));
}
class TestTypeLazy : public ProtocolObject<TestTypeLazy> {
public:
TestTypeLazy() = default;
const std::string& GetStrField() const { return str_field_; }
void SetStrField(std::string value) { str_field_ = std::move(value); }
const DeferredMessage* deferred_test_type1_field() const {
return test_type1_field_.get();
}
private:
DECLARE_SERIALIZATION_SUPPORT();
std::string str_field_;
std::unique_ptr<DeferredMessage> test_type1_field_;
};
// clang-format off
CRDTP_BEGIN_DESERIALIZER(TestTypeLazy)
CRDTP_DESERIALIZE_FIELD("str_field", str_field_),
CRDTP_DESERIALIZE_FIELD_OPT("test_type1_field", test_type1_field_),
CRDTP_END_DESERIALIZER()
CRDTP_BEGIN_SERIALIZER(TestTypeLazy)
CRDTP_SERIALIZE_FIELD("str_field", str_field_),
CRDTP_SERIALIZE_FIELD("test_type1_field", test_type1_field_),
CRDTP_END_SERIALIZER();
// clang-format on
TEST(ProtocolCoreTest, TestDeferredMessage) {
TestTypeComposite obj1;
obj1.SetStrField("bar");
auto val1 = std::make_unique<TestTypeBasic>();
val1->SetValue("bazzzz");
obj1.SetTestTypeBasicField(std::move(val1));
auto obj2 = RoundtripToType<TestTypeLazy>(obj1);
EXPECT_THAT(obj2->GetStrField(), Eq("bar"));
TestTypeBasic basic_val;
auto deserializer = obj2->deferred_test_type1_field()->MakeDeserializer();
EXPECT_THAT(TestTypeBasic::Deserialize(&deserializer, &basic_val), Eq(true));
EXPECT_THAT(basic_val.GetValue(), Eq("bazzzz"));
StatusOr<std::unique_ptr<TestTypeBasic>> maybe_parsed =
TestTypeBasic::ReadFrom(*obj2->deferred_test_type1_field());
ASSERT_THAT(maybe_parsed.status(), StatusIsOk());
ASSERT_THAT((*maybe_parsed), Not(testing::IsNull()));
ASSERT_EQ((*maybe_parsed)->GetValue(), "bazzzz");
}
} // namespace
} // namespace crdtp

View File

@ -0,0 +1,39 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "serializable.h"
#include <utility>
namespace crdtp {
// =============================================================================
// Serializable - An object to be emitted as a sequence of bytes.
// =============================================================================
std::vector<uint8_t> Serializable::Serialize() const {
std::vector<uint8_t> out;
AppendSerialized(&out);
return out;
}
namespace {
class PreSerialized : public Serializable {
public:
explicit PreSerialized(std::vector<uint8_t> bytes)
: bytes_(std::move(bytes)) {}
void AppendSerialized(std::vector<uint8_t>* out) const override {
out->insert(out->end(), bytes_.begin(), bytes_.end());
}
private:
std::vector<uint8_t> bytes_;
};
} // namespace
// static
std::unique_ptr<Serializable> Serializable::From(std::vector<uint8_t> bytes) {
return std::make_unique<PreSerialized>(std::move(bytes));
}
} // namespace crdtp

View File

@ -0,0 +1,32 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CRDTP_SERIALIZABLE_H_
#define CRDTP_SERIALIZABLE_H_
#include <cstdint>
#include <memory>
#include <vector>
#include "export.h"
namespace crdtp {
// =============================================================================
// Serializable - An object to be emitted as a sequence of bytes.
// =============================================================================
class CRDTP_EXPORT Serializable {
public:
// Convenience: Invokes |AppendSerialized| on an empty vector.
std::vector<uint8_t> Serialize() const;
virtual void AppendSerialized(std::vector<uint8_t>* out) const = 0;
virtual ~Serializable() = default;
// Wraps a vector of |bytes| into a Serializable for situations in which we
// eagerly serialize a structure.
static std::unique_ptr<Serializable> From(std::vector<uint8_t> bytes);
};
} // namespace crdtp
#endif // CRDTP_SERIALIZABLE_H_

View File

@ -0,0 +1,40 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cstdlib>
#include <string>
#include "serializable.h"
#include "test_platform.h"
namespace crdtp {
// =============================================================================
// Serializable - An object to be emitted as a sequence of bytes.
// =============================================================================
namespace {
// Tests ::Serialize (which invokes ::AppendSerialized).
class SimpleExample : public Serializable {
public:
explicit SimpleExample(const std::vector<uint8_t>& contents)
: contents_(contents) {}
void AppendSerialized(std::vector<uint8_t>* out) const override {
out->insert(out->end(), contents_.begin(), contents_.end());
}
private:
std::vector<uint8_t> contents_;
};
} // namespace
TEST(SerializableTest, YieldsContents) {
std::vector<uint8_t> contents = {1, 2, 3};
SimpleExample foo(contents);
foo.AppendSerialized(&contents); // Yields contents by appending.
EXPECT_THAT(contents, testing::ElementsAre(1, 2, 3, 1, 2, 3));
// Yields contents by returning.
EXPECT_THAT(foo.Serialize(), testing::ElementsAre(1, 2, 3));
}
} // namespace crdtp

39
deps/inspector_protocol/crdtp/span.cc vendored Normal file
View File

@ -0,0 +1,39 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "span.h"
#include <algorithm>
namespace crdtp {
bool SpanLessThan(span<uint8_t> x, span<uint8_t> y) noexcept {
auto min_size = std::min(x.size(), y.size());
const int r = min_size == 0 ? 0 : memcmp(x.data(), y.data(), min_size);
return (r < 0) || (r == 0 && x.size() < y.size());
}
bool SpanEquals(span<uint8_t> x, span<uint8_t> y) noexcept {
auto len = x.size();
if (len != y.size())
return false;
return x.data() == y.data() || len == 0 ||
std::memcmp(x.data(), y.data(), len) == 0;
}
bool SpanLessThan(span<char> x, span<char> y) noexcept {
auto min_size = std::min(x.size(), y.size());
const int r = min_size == 0 ? 0 : memcmp(x.data(), y.data(), min_size);
return (r < 0) || (r == 0 && x.size() < y.size());
}
bool SpanEquals(span<char> x, span<char> y) noexcept {
auto len = x.size();
if (len != y.size())
return false;
return x.data() == y.data() || len == 0 ||
std::memcmp(x.data(), y.data(), len) == 0;
}
} // namespace crdtp

100
deps/inspector_protocol/crdtp/span.h vendored Normal file
View File

@ -0,0 +1,100 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CRDTP_SPAN_H_
#define CRDTP_SPAN_H_
#include <cstdint>
#include <cstring>
#include <type_traits>
#include <string>
#include "export.h"
namespace crdtp {
// =============================================================================
// span - sequence of bytes
// =============================================================================
// This template is similar to std::span, which will be included in C++20.
template <typename T>
class span {
public:
using index_type = size_t;
constexpr span() : data_(nullptr), size_(0) {}
constexpr span(const T* data, index_type size) : data_(data), size_(size) {}
constexpr const T* data() const { return data_; }
constexpr const T* begin() const { return data_; }
constexpr const T* end() const { return data_ + size_; }
constexpr const T& operator[](index_type idx) const { return data_[idx]; }
constexpr span<T> subspan(index_type offset, index_type count) const {
return span(data_ + offset, count);
}
constexpr span<T> subspan(index_type offset) const {
return span(data_ + offset, size_ - offset);
}
constexpr bool empty() const { return size_ == 0; }
constexpr index_type size() const { return size_; }
constexpr index_type size_bytes() const { return size_ * sizeof(T); }
private:
const T* data_;
index_type size_;
};
template <size_t N>
constexpr span<char> MakeSpan(const char (&str)[N]) {
return span<char>(str, N - 1);
}
template <size_t N>
constexpr span<uint8_t> SpanFrom(const char (&str)[N]) {
return span<uint8_t>(reinterpret_cast<const uint8_t*>(str), N - 1);
}
constexpr inline span<uint8_t> SpanFrom(const char* str) {
return str ? span<uint8_t>(reinterpret_cast<const uint8_t*>(str), strlen(str))
: span<uint8_t>();
}
inline span<uint8_t> SpanFrom(const std::string& v) {
return span<uint8_t>(reinterpret_cast<const uint8_t*>(v.data()), v.size());
}
// This SpanFrom routine works for std::vector<uint8_t> and
// std::vector<uint16_t>, but also for base::span<const uint8_t> in Chromium.
template <typename C,
typename = std::enable_if_t<
std::is_unsigned<typename C::value_type>{} &&
std::is_member_function_pointer<decltype(&C::size)>{}>>
inline span<typename C::value_type> SpanFrom(const C& v) {
return span<typename C::value_type>(v.data(), v.size());
}
// Less than / equality comparison functions for sorting / searching for byte
// spans.
CRDTP_EXPORT bool SpanLessThan(span<uint8_t> x, span<uint8_t> y) noexcept;
CRDTP_EXPORT bool SpanEquals(span<uint8_t> x, span<uint8_t> y) noexcept;
// Less than / equality comparison functions for sorting / searching for byte
// spans.
CRDTP_EXPORT bool SpanLessThan(span<char> x, span<char> y) noexcept;
CRDTP_EXPORT bool SpanEquals(span<char> x, span<char> y) noexcept;
struct SpanLt {
bool operator()(span<uint8_t> l, span<uint8_t> r) const {
return SpanLessThan(l, r);
}
};
} // namespace crdtp
#endif // CRDTP_SPAN_H_

View File

@ -0,0 +1,109 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cstdlib>
#include <string>
#include "span.h"
#include "test_platform.h"
namespace crdtp {
// =============================================================================
// span - sequence of bytes
// =============================================================================
template <typename T>
class SpanTest : public ::testing::Test {};
using TestTypes = ::testing::Types<uint8_t, uint16_t>;
TYPED_TEST_SUITE(SpanTest, TestTypes);
TYPED_TEST(SpanTest, Empty) {
span<TypeParam> empty;
EXPECT_TRUE(empty.empty());
EXPECT_EQ(0u, empty.size());
EXPECT_EQ(0u, empty.size_bytes());
EXPECT_EQ(empty.begin(), empty.end());
}
TYPED_TEST(SpanTest, SingleItem) {
TypeParam single_item = 42;
span<TypeParam> singular(&single_item, 1);
EXPECT_FALSE(singular.empty());
EXPECT_EQ(1u, singular.size());
EXPECT_EQ(sizeof(TypeParam), singular.size_bytes());
EXPECT_EQ(singular.begin() + 1, singular.end());
EXPECT_EQ(42, singular[0]);
}
TYPED_TEST(SpanTest, FiveItems) {
std::vector<TypeParam> test_input = {31, 32, 33, 34, 35};
span<TypeParam> five_items(test_input.data(), 5);
EXPECT_FALSE(five_items.empty());
EXPECT_EQ(5u, five_items.size());
EXPECT_EQ(sizeof(TypeParam) * 5, five_items.size_bytes());
EXPECT_EQ(five_items.begin() + 5, five_items.end());
EXPECT_EQ(31, five_items[0]);
EXPECT_EQ(32, five_items[1]);
EXPECT_EQ(33, five_items[2]);
EXPECT_EQ(34, five_items[3]);
EXPECT_EQ(35, five_items[4]);
span<TypeParam> three_items = five_items.subspan(2);
EXPECT_EQ(3u, three_items.size());
EXPECT_EQ(33, three_items[0]);
EXPECT_EQ(34, three_items[1]);
EXPECT_EQ(35, three_items[2]);
span<TypeParam> two_items = five_items.subspan(2, 2);
EXPECT_EQ(2u, two_items.size());
EXPECT_EQ(33, two_items[0]);
EXPECT_EQ(34, two_items[1]);
}
TEST(SpanFromTest, FromConstCharAndLiteral) {
// Testing this is useful because strlen(nullptr) is undefined.
EXPECT_EQ(nullptr, SpanFrom(nullptr).data());
EXPECT_EQ(0u, SpanFrom(nullptr).size());
const char* kEmpty = "";
EXPECT_EQ(kEmpty, reinterpret_cast<const char*>(SpanFrom(kEmpty).data()));
EXPECT_EQ(0u, SpanFrom(kEmpty).size());
const char* kFoo = "foo";
EXPECT_EQ(kFoo, reinterpret_cast<const char*>(SpanFrom(kFoo).data()));
EXPECT_EQ(3u, SpanFrom(kFoo).size());
EXPECT_EQ(3u, SpanFrom("foo").size());
}
TEST(SpanFromTest, FromVectorUint8AndUint16) {
std::vector<uint8_t> foo = {'f', 'o', 'o'};
span<uint8_t> foo_span = SpanFrom(foo);
EXPECT_EQ(foo.size(), foo_span.size());
std::vector<uint16_t> bar = {0xff, 0xef, 0xeb};
span<uint16_t> bar_span = SpanFrom(bar);
EXPECT_EQ(bar.size(), bar_span.size());
}
TEST(SpanComparisons, ByteWiseLexicographicalOrder) {
// Compare the empty span.
EXPECT_FALSE(SpanLessThan(span<uint8_t>(), span<uint8_t>()));
EXPECT_TRUE(SpanEquals(span<uint8_t>(), span<uint8_t>()));
// Compare message with itself.
std::string msg = "Hello, world";
EXPECT_FALSE(SpanLessThan(SpanFrom(msg), SpanFrom(msg)));
EXPECT_TRUE(SpanEquals(SpanFrom(msg), SpanFrom(msg)));
// Compare message and copy.
EXPECT_FALSE(SpanLessThan(SpanFrom(msg), SpanFrom(std::string(msg))));
EXPECT_TRUE(SpanEquals(SpanFrom(msg), SpanFrom(std::string(msg))));
// Compare two messages. |lesser_msg| < |msg| because of the first
// byte ('A' < 'H').
std::string lesser_msg = "A lesser message.";
EXPECT_TRUE(SpanLessThan(SpanFrom(lesser_msg), SpanFrom(msg)));
EXPECT_FALSE(SpanLessThan(SpanFrom(msg), SpanFrom(lesser_msg)));
EXPECT_FALSE(SpanEquals(SpanFrom(msg), SpanFrom(lesser_msg)));
}
} // namespace crdtp

130
deps/inspector_protocol/crdtp/status.cc vendored Normal file
View File

@ -0,0 +1,130 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "status.h"
namespace crdtp {
// =============================================================================
// Status and Error codes
// =============================================================================
std::string Status::Message() const {
switch (error) {
case Error::OK:
return "OK";
case Error::JSON_PARSER_UNPROCESSED_INPUT_REMAINS:
return "JSON: unprocessed input remains";
case Error::JSON_PARSER_STACK_LIMIT_EXCEEDED:
return "JSON: stack limit exceeded";
case Error::JSON_PARSER_NO_INPUT:
return "JSON: no input";
case Error::JSON_PARSER_INVALID_TOKEN:
return "JSON: invalid token";
case Error::JSON_PARSER_INVALID_NUMBER:
return "JSON: invalid number";
case Error::JSON_PARSER_INVALID_STRING:
return "JSON: invalid string";
case Error::JSON_PARSER_UNEXPECTED_ARRAY_END:
return "JSON: unexpected array end";
case Error::JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED:
return "JSON: comma or array end expected";
case Error::JSON_PARSER_STRING_LITERAL_EXPECTED:
return "JSON: string literal expected";
case Error::JSON_PARSER_COLON_EXPECTED:
return "JSON: colon expected";
case Error::JSON_PARSER_UNEXPECTED_MAP_END:
return "JSON: unexpected map end";
case Error::JSON_PARSER_COMMA_OR_MAP_END_EXPECTED:
return "JSON: comma or map end expected";
case Error::JSON_PARSER_VALUE_EXPECTED:
return "JSON: value expected";
case Error::CBOR_INVALID_INT32:
return "CBOR: invalid int32";
case Error::CBOR_INVALID_DOUBLE:
return "CBOR: invalid double";
case Error::CBOR_INVALID_ENVELOPE:
return "CBOR: invalid envelope";
case Error::CBOR_ENVELOPE_CONTENTS_LENGTH_MISMATCH:
return "CBOR: envelope contents length mismatch";
case Error::CBOR_MAP_OR_ARRAY_EXPECTED_IN_ENVELOPE:
return "CBOR: map or array expected in envelope";
case Error::CBOR_INVALID_STRING8:
return "CBOR: invalid string8";
case Error::CBOR_INVALID_STRING16:
return "CBOR: invalid string16";
case Error::CBOR_INVALID_BINARY:
return "CBOR: invalid binary";
case Error::CBOR_UNSUPPORTED_VALUE:
return "CBOR: unsupported value";
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";
case Error::CBOR_UNEXPECTED_EOF_IN_ARRAY:
return "CBOR: unexpected EOF in array";
case Error::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:
return "CBOR: duplicate map key";
case Error::CBOR_STACK_LIMIT_EXCEEDED:
return "CBOR: stack limit exceeded";
case Error::CBOR_TRAILING_JUNK:
return "CBOR: trailing junk";
case Error::CBOR_MAP_START_EXPECTED:
return "CBOR: map start expected";
case Error::CBOR_MAP_STOP_EXPECTED:
return "CBOR: map stop expected";
case Error::CBOR_ARRAY_START_EXPECTED:
return "CBOR: array start expected";
case Error::CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED:
return "CBOR: envelope size limit exceeded";
case Error::MESSAGE_MUST_BE_AN_OBJECT:
return "Message must be an object";
case Error::MESSAGE_MUST_HAVE_INTEGER_ID_PROPERTY:
return "Message must have integer 'id' property";
case Error::MESSAGE_MUST_HAVE_STRING_METHOD_PROPERTY:
return "Message must have string 'method' property";
case Error::MESSAGE_MAY_HAVE_STRING_SESSION_ID_PROPERTY:
return "Message may have string 'sessionId' property";
case Error::MESSAGE_MAY_HAVE_OBJECT_PARAMS_PROPERTY:
return "Message may have object 'params' property";
case Error::MESSAGE_HAS_UNKNOWN_PROPERTY:
return "Message has property other than "
"'id', 'method', 'sessionId', 'params'";
case Error::BINDINGS_MANDATORY_FIELD_MISSING:
return "BINDINGS: mandatory field missing";
case Error::BINDINGS_BOOL_VALUE_EXPECTED:
return "BINDINGS: bool value expected";
case Error::BINDINGS_INT32_VALUE_EXPECTED:
return "BINDINGS: int32 value expected";
case Error::BINDINGS_DOUBLE_VALUE_EXPECTED:
return "BINDINGS: double value expected";
case Error::BINDINGS_STRING_VALUE_EXPECTED:
return "BINDINGS: string value expected";
case Error::BINDINGS_STRING8_VALUE_EXPECTED:
return "BINDINGS: string8 value expected";
case Error::BINDINGS_BINARY_VALUE_EXPECTED:
return "BINDINGS: binary value expected";
case Error::BINDINGS_DICTIONARY_VALUE_EXPECTED:
return "BINDINGS: dictionary value expected";
case Error::BINDINGS_INVALID_BASE64_STRING:
return "BINDINGS: invalid base64 string";
}
// Some compilers can't figure out that we can't get here.
return "INVALID ERROR CODE";
}
std::string Status::ToASCIIString() const {
if (ok())
return "OK";
return Message() + " at position " + std::to_string(pos);
}
} // namespace crdtp

143
deps/inspector_protocol/crdtp/status.h vendored Normal file
View File

@ -0,0 +1,143 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CRDTP_STATUS_H_
#define CRDTP_STATUS_H_
#include <cassert>
#include <cstddef>
#include <limits>
#include <string>
#include "export.h"
namespace crdtp {
// =============================================================================
// Status and Error codes
// =============================================================================
enum class Error {
OK = 0,
// JSON parsing errors; checked when parsing / converting from JSON.
// See json.{h,cc}.
JSON_PARSER_UNPROCESSED_INPUT_REMAINS = 0x01,
JSON_PARSER_STACK_LIMIT_EXCEEDED = 0x02,
JSON_PARSER_NO_INPUT = 0x03,
JSON_PARSER_INVALID_TOKEN = 0x04,
JSON_PARSER_INVALID_NUMBER = 0x05,
JSON_PARSER_INVALID_STRING = 0x06,
JSON_PARSER_UNEXPECTED_ARRAY_END = 0x07,
JSON_PARSER_COMMA_OR_ARRAY_END_EXPECTED = 0x08,
JSON_PARSER_STRING_LITERAL_EXPECTED = 0x09,
JSON_PARSER_COLON_EXPECTED = 0x0a,
JSON_PARSER_UNEXPECTED_MAP_END = 0x0b,
JSON_PARSER_COMMA_OR_MAP_END_EXPECTED = 0x0c,
JSON_PARSER_VALUE_EXPECTED = 0x0d,
// CBOR parsing errors; checked when parsing / converting from CBOR.
CBOR_INVALID_INT32 = 0x0e,
CBOR_INVALID_DOUBLE = 0x0f,
CBOR_INVALID_ENVELOPE = 0x10,
CBOR_ENVELOPE_CONTENTS_LENGTH_MISMATCH = 0x11,
CBOR_MAP_OR_ARRAY_EXPECTED_IN_ENVELOPE = 0x12,
CBOR_INVALID_STRING8 = 0x13,
CBOR_INVALID_STRING16 = 0x14,
CBOR_INVALID_BINARY = 0x15,
CBOR_UNSUPPORTED_VALUE = 0x16,
CBOR_UNEXPECTED_EOF_IN_ENVELOPE = 0x17,
CBOR_INVALID_START_BYTE = 0x18,
CBOR_UNEXPECTED_EOF_EXPECTED_VALUE = 0x19,
CBOR_UNEXPECTED_EOF_IN_ARRAY = 0x1a,
CBOR_UNEXPECTED_EOF_IN_MAP = 0x1b,
CBOR_INVALID_MAP_KEY = 0x1c,
CBOR_DUPLICATE_MAP_KEY = 0x1d,
CBOR_STACK_LIMIT_EXCEEDED = 0x1e,
CBOR_TRAILING_JUNK = 0x1f,
CBOR_MAP_START_EXPECTED = 0x20,
CBOR_MAP_STOP_EXPECTED = 0x21,
CBOR_ARRAY_START_EXPECTED = 0x22,
CBOR_ENVELOPE_SIZE_LIMIT_EXCEEDED = 0x23,
// Message errors are constraints we place on protocol messages coming
// from a protocol client; these are checked in crdtp::Dispatchable
// (see dispatch.h) as it performs a shallow parse.
MESSAGE_MUST_BE_AN_OBJECT = 0x24,
MESSAGE_MUST_HAVE_INTEGER_ID_PROPERTY = 0x25,
MESSAGE_MUST_HAVE_STRING_METHOD_PROPERTY = 0x26,
MESSAGE_MAY_HAVE_STRING_SESSION_ID_PROPERTY = 0x27,
MESSAGE_MAY_HAVE_OBJECT_PARAMS_PROPERTY = 0x28,
MESSAGE_HAS_UNKNOWN_PROPERTY = 0x29,
BINDINGS_MANDATORY_FIELD_MISSING = 0x30,
BINDINGS_BOOL_VALUE_EXPECTED = 0x31,
BINDINGS_INT32_VALUE_EXPECTED = 0x32,
BINDINGS_DOUBLE_VALUE_EXPECTED = 0x33,
BINDINGS_STRING_VALUE_EXPECTED = 0x34,
BINDINGS_STRING8_VALUE_EXPECTED = 0x35,
BINDINGS_BINARY_VALUE_EXPECTED = 0x36,
BINDINGS_DICTIONARY_VALUE_EXPECTED = 0x37,
BINDINGS_INVALID_BASE64_STRING = 0x38,
};
// A status value with position that can be copied. The default status
// is OK. Usually, error status values should come with a valid position.
struct CRDTP_EXPORT Status {
static constexpr size_t npos() { return std::numeric_limits<size_t>::max(); }
bool ok() const { return error == Error::OK; }
Error error = Error::OK;
size_t pos = npos();
Status(Error error, size_t pos) : error(error), pos(pos) {}
Status() = default;
bool IsMessageError() const {
return error >= Error::MESSAGE_MUST_BE_AN_OBJECT &&
error <= Error::MESSAGE_HAS_UNKNOWN_PROPERTY;
}
// Returns 7 bit US-ASCII string, either "OK" or an error message without
// position.
std::string Message() const;
// Returns a 7 bit US-ASCII string, either "OK" or an error message that
// includes the position.
std::string ToASCIIString() const;
};
template <typename T>
class StatusOr {
public:
explicit StatusOr(const T& value) : value_(value) {}
explicit StatusOr(T&& value) : value_(std::move(value)) {}
explicit StatusOr(const Status& status) : status_(status) {}
bool ok() const { return status_.ok(); }
const Status& status() const { return status_; }
T& operator*() & { return value(); }
const T& operator*() const& { return value(); }
T&& operator*() && { return value(); }
T& value() & {
assert(ok());
return value_;
}
T&& value() && {
assert(ok());
return std::move(value_);
}
const T& value() const& {
assert(ok());
return value_;
}
private:
Status status_;
T value_;
};
} // namespace crdtp
#endif // CRDTP_STATUS_H_

View File

@ -0,0 +1,29 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "status.h"
#include "status_test_support.h"
#include "test_platform.h"
namespace crdtp {
// =============================================================================
// Status and Error codes
// =============================================================================
TEST(StatusTest, StatusToASCIIString) {
Status ok_status;
EXPECT_EQ("OK", ok_status.ToASCIIString());
Status json_error(Error::JSON_PARSER_COLON_EXPECTED, 42);
EXPECT_EQ("JSON: colon expected at position 42", json_error.ToASCIIString());
Status cbor_error(Error::CBOR_TRAILING_JUNK, 21);
EXPECT_EQ("CBOR: trailing junk at position 21", cbor_error.ToASCIIString());
}
TEST(StatusTest, StatusTestSupport) {
Status ok_status;
EXPECT_THAT(ok_status, StatusIsOk());
Status json_error(Error::JSON_PARSER_COLON_EXPECTED, 42);
EXPECT_THAT(json_error, StatusIs(Error::JSON_PARSER_COLON_EXPECTED, 42));
}
} // namespace crdtp

View File

@ -0,0 +1,50 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "status_test_support.h"
namespace crdtp {
void PrintTo(const Status& status, std::ostream* os) {
*os << status.ToASCIIString() << " (error: 0x" << std::hex
<< static_cast<int>(status.error) << ", "
<< "pos: " << std::dec << status.pos << ")";
}
namespace {
class StatusIsMatcher : public testing::MatcherInterface<Status> {
public:
explicit StatusIsMatcher(Status status) : expected_(status) {}
bool MatchAndExplain(Status status,
testing::MatchResultListener* listener) const override {
return status.error == expected_.error && status.pos == expected_.pos;
}
void DescribeTo(std::ostream* os) const override {
*os << "equals to ";
PrintTo(expected_, os);
}
private:
Status expected_;
};
class StatusIsOkMatcher : public testing::MatcherInterface<Status> {
bool MatchAndExplain(Status status,
testing::MatchResultListener* listener) const override {
return status.ok();
}
void DescribeTo(std::ostream* os) const override { *os << "is ok"; }
};
} // namespace
testing::Matcher<Status> StatusIsOk() {
return MakeMatcher(new StatusIsOkMatcher());
}
testing::Matcher<Status> StatusIs(Error error, size_t pos) {
return MakeMatcher(new StatusIsMatcher(Status(error, pos)));
}
} // namespace crdtp

View File

@ -0,0 +1,32 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CRDTP_STATUS_TEST_SUPPORT_H_
#define CRDTP_STATUS_TEST_SUPPORT_H_
#include <ostream>
#include "status.h"
#include "test_platform.h"
namespace crdtp {
// Supports gtest, to conveniently match Status objects and
// get useful error messages when tests fail.
// Typically used with EXPECT_THAT, e.g.
//
// EXPECT_THAT(status, StatusIs(Error::JSON_PARSER_COLON_EXPECTED, 42));
//
// EXPECT_THAT(status, StatusIsOk());
// Prints a |status|, including its generated error message, error code, and
// position. This is used by gtest for pretty printing actual vs. expected.
void PrintTo(const Status& status, std::ostream* os);
// Matches any status with |status.ok()|.
testing::Matcher<Status> StatusIsOk();
// Matches any status with |error| and |pos|.
testing::Matcher<Status> StatusIs(Error error, size_t pos);
} // namespace crdtp
#endif // CRDTP_STATUS_TEST_SUPPORT_H_

View File

@ -0,0 +1,33 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file is Chromium specific, to make the tests work. It will work
// in the standalone (upstream) build, as well as in Chromium. In other code
// bases (e.g. v8), a custom file with these two functions and with appropriate
// includes may need to be provided, so it isn't necessarily part of a roll.
#include "test_platform.h"
#include <cstdint>
#include <string>
#include <vector>
#include "base/strings/utf_string_conversions.h"
namespace crdtp {
std::string UTF16ToUTF8(span<uint16_t> in) {
std::string out;
bool success = base::UTF16ToUTF8(reinterpret_cast<const char16_t*>(in.data()),
in.size(), &out);
CHECK(success);
return out;
}
std::vector<uint16_t> UTF8ToUTF16(span<uint8_t> in) {
std::u16string tmp;
bool success = base::UTF8ToUTF16(reinterpret_cast<const char*>(in.data()),
in.size(), &tmp);
CHECK(success);
return std::vector<uint16_t>(tmp.begin(), tmp.end());
}
} // namespace crdtp

View File

@ -0,0 +1,31 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file is Chromium specific, to make the tests work. It will work
// in the standalone (upstream) build, as well as in Chromium. In other code
// bases (e.g. v8), a custom file with these two functions and with appropriate
// includes may need to be provided, so it isn't necessarily part of a roll.
//
// Put another way: The tests, e.g. json_test.cc include *only* test_platform.h,
// which provides CHECK and gunit functionality, and UTF8<->UTF16 conversion
// functions.
#ifndef CRDTP_TEST_PLATFORM_H_
#define CRDTP_TEST_PLATFORM_H_
#include <cstdint>
#include <string>
#include <vector>
#include "base/check_op.h" // Provides CHECK and CHECK_EQ, etc.
#include "span.h"
#include "testing/gmock/include/gmock/gmock.h" // Provides Gunit
#include "testing/gtest/include/gtest/gtest.h" // Provides Gmock
// Provides UTF8<->UTF16 conversion routines (implemented in .cc file).
namespace crdtp {
std::string UTF16ToUTF8(span<uint16_t> in);
std::vector<uint16_t> UTF8ToUTF16(span<uint8_t> in);
} // namespace crdtp
#endif // CRDTP_TEST_PLATFORM_H_

View File

@ -0,0 +1,31 @@
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "test_string_traits.h"
namespace crdtp {
// Test-only. Real-life bindings use UTF8/16 conversions as needed.
bool ProtocolTypeTraits<std::string>::Deserialize(DeserializerState* state,
std::string* value) {
if (state->tokenizer()->TokenTag() == cbor::CBORTokenTag::STRING8) {
auto cbor_span = state->tokenizer()->GetString8();
value->assign(reinterpret_cast<const char*>(cbor_span.data()),
cbor_span.size());
return true;
}
state->RegisterError(Error::BINDINGS_STRING8_VALUE_EXPECTED);
return false;
}
// static
void ProtocolTypeTraits<std::string>::Serialize(const std::string& value,
std::vector<uint8_t>* bytes) {
cbor::EncodeString8(
span<uint8_t>(reinterpret_cast<const uint8_t*>(value.data()),
value.size()),
bytes);
}
} // namespace crdtp

View File

@ -0,0 +1,21 @@
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CRDTP_TEST_STRING_TRAITS_H_
#include "protocol_core.h"
namespace crdtp {
// Either real string traits or dummy string traits are going to be used
// depending on whether this is built standalone or with embedder.
template <>
struct ProtocolTypeTraits<std::string> {
static bool Deserialize(DeserializerState* state, std::string* value);
static void Serialize(const std::string& value, std::vector<uint8_t>* bytes);
};
} // namespace crdtp
#endif // CRDTP_TEST_STRING_TRAITS_H_

View File

@ -0,0 +1,61 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include "json.h"
namespace crdtp {
namespace {
int Transcode(const std::string& cmd,
const std::string& input_file_name,
const std::string& output_file_name) {
std::ifstream input_file(input_file_name, std::ios::binary);
if (!input_file.is_open()) {
std::cerr << "failed to open " << input_file_name << "\n";
return 1;
}
std::string in;
while (input_file) {
std::string buffer(1024, '\0');
input_file.read(&buffer.front(), buffer.size());
in += buffer.substr(0, input_file.gcount());
}
Status status;
std::vector<uint8_t> out;
if (cmd == "--json-to-cbor") {
status = json::ConvertJSONToCBOR(SpanFrom(in), &out);
} else if (cmd == "--cbor-to-json") {
status = json::ConvertCBORToJSON(SpanFrom(in), &out);
} else {
std::cerr << "unknown command " << cmd << "\n";
return 1;
}
if (!status.ok()) {
std::cerr << "transcoding error: " << status.ToASCIIString() << "\n";
return 1;
}
std::ofstream output_file(output_file_name, std::ios::binary);
if (!output_file.is_open()) {
std::cerr << "failed to open " << output_file_name << "\n";
return 1;
}
output_file.write(reinterpret_cast<const char*>(out.data()), out.size());
return 0;
}
} // namespace
} // namespace crdtp
int main(int argc, char** argv) {
if (argc == 4)
return ::crdtp::Transcode(argv[1], argv[2], argv[3]);
std::cerr << "usage: " << argv[0]
<< " --json-to-cbor <input-file> <output-file>\n"
<< " or " << argv[0]
<< " --cbor-to-json <input-file> <output-file>\n";
return 1;
}