blob: 7b233b03cdb07b242a9e6c9e3b9c3aaac28e172f [file] [log] [blame]
// 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_