3573d5e0fa
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}
315 lines
12 KiB
C++
315 lines
12 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.
|
|
|
|
#ifndef V8_CRDTP_DISPATCH_H_
|
|
#define V8_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 v8_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/master/Lib/xmlrpc/client.py).
|
|
PARSE_ERROR = -32700,
|
|
INVALID_REQUEST = -32600,
|
|
METHOD_NOT_FOUND = -32601,
|
|
INVALID_PARAMS = -32602,
|
|
INTERNAL_ERROR = -32603,
|
|
SERVER_ERROR = -32000,
|
|
};
|
|
|
|
// Information returned by command handlers. Usually returned after command
|
|
// execution attempts.
|
|
class 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);
|
|
|
|
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 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).
|
|
|
|
std::unique_ptr<Serializable> CreateErrorResponse(
|
|
int callId,
|
|
DispatchResponse dispatch_response,
|
|
const ErrorSupport* errors = nullptr);
|
|
|
|
std::unique_ptr<Serializable> CreateErrorNotification(
|
|
DispatchResponse dispatch_response);
|
|
|
|
std::unique_ptr<Serializable> CreateResponse(
|
|
int callId,
|
|
std::unique_ptr<Serializable> params);
|
|
|
|
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 DomainDispatcher {
|
|
public:
|
|
class WeakPtr {
|
|
public:
|
|
explicit WeakPtr(DomainDispatcher*);
|
|
~WeakPtr();
|
|
DomainDispatcher* get() { return dispatcher_; }
|
|
void dispose() { dispatcher_ = nullptr; }
|
|
|
|
private:
|
|
DomainDispatcher* dispatcher_;
|
|
};
|
|
|
|
class 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);
|
|
|
|
// Returns true if |errors| contains errors *and* reports these errors
|
|
// as a response on the frontend channel. Called from generated code,
|
|
// optimized for code size of the callee.
|
|
bool MaybeReportInvalidParams(const Dispatchable& dispatchable,
|
|
const ErrorSupport& errors);
|
|
bool MaybeReportInvalidParams(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 UberDispatcher {
|
|
public:
|
|
// Return type for ::Dispatch.
|
|
class 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 v8_crdtp
|
|
|
|
#endif // V8_CRDTP_DISPATCH_H_
|