1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 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_
|