v8/third_party/inspector_protocol/crdtp/dispatch.cc
Andrey Kosyakov 3573d5e0fa Roll inspector_protocol library to inculude unified (de)serialization support
Note that changes in test expectation come from a more verbose
error diagnostics for expected errors around input parameter
validation.

Original change: https://chromium-review.googlesource.com/c/deps/inspector_protocol/+/2270757

Bug: chromium:1099809

Change-Id: I4fc2efc9c89d0af645dad937d719fa36e1d33489
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2277142
Reviewed-by: Yang Guo <yangguo@chromium.org>
Commit-Queue: Andrey Kosyakov <caseq@chromium.org>
Cr-Commit-Position: refs/heads/master@{#68657}
2020-07-02 14:08:19 +00:00

605 lines
19 KiB
C++

// Copyright 2020 The Chromium Authors. All rights reserved.
// 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 v8_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;
}
// =============================================================================
// Dispatchable - a shallow parser for CBOR encoded DevTools messages
// =============================================================================
namespace {
constexpr size_t kEncodedEnvelopeHeaderSize = 1 + 1 + sizeof(uint32_t);
} // namespace
Dispatchable::Dispatchable(span<uint8_t> serialized) : serialized_(serialized) {
Status s = cbor::CheckCBORMessage(serialized);
if (!s.ok()) {
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 +
kEncodedEnvelopeHeaderSize +
tokenizer.GetEnvelopeContents().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,
const ErrorSupport* errors) {
auto protocol_error =
std::make_unique<ProtocolError>(std::move(dispatch_response));
protocol_error->SetCallId(call_id);
if (errors && !errors->Errors().empty()) {
protocol_error->SetData(
std::string(errors->Errors().begin(), errors->Errors().end()));
}
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));
}
bool DomainDispatcher::MaybeReportInvalidParams(
const Dispatchable& dispatchable,
const ErrorSupport& errors) {
if (errors.Errors().empty())
return false;
if (frontend_channel_) {
frontend_channel_->SendProtocolResponse(
dispatchable.CallId(),
CreateErrorResponse(
dispatchable.CallId(),
DispatchResponse::InvalidParams("Invalid parameters"), &errors));
}
return true;
}
bool DomainDispatcher::MaybeReportInvalidParams(
const Dispatchable& dispatchable,
const DeserializerState& state) {
if (state.status().ok())
return false;
if (frontend_channel_) {
frontend_channel_->SendProtocolResponse(
dispatchable.CallId(),
CreateErrorResponse(
dispatchable.CallId(),
DispatchResponse::InvalidParams("Invalid parameters"), state));
}
return true;
}
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 v8_crdtp