/src/node/deps/inspector_protocol/crdtp/dispatch.h
Line | Count | Source |
1 | | // Copyright 2020 The Chromium Authors |
2 | | // Use of this source code is governed by a BSD-style license that can be |
3 | | // found in the LICENSE file. |
4 | | |
5 | | #ifndef CRDTP_DISPATCH_H_ |
6 | | #define CRDTP_DISPATCH_H_ |
7 | | |
8 | | #include <cassert> |
9 | | #include <cstdint> |
10 | | #include <functional> |
11 | | #include <string> |
12 | | #include <unordered_set> |
13 | | #include "export.h" |
14 | | #include "serializable.h" |
15 | | #include "span.h" |
16 | | #include "status.h" |
17 | | |
18 | | namespace crdtp { |
19 | | class DeserializerState; |
20 | | class ErrorSupport; |
21 | | class FrontendChannel; |
22 | | namespace cbor { |
23 | | class CBORTokenizer; |
24 | | } // namespace cbor |
25 | | |
26 | | // ============================================================================= |
27 | | // DispatchResponse - Error status and chaining / fall through |
28 | | // ============================================================================= |
29 | | enum class DispatchCode { |
30 | | SUCCESS = 1, |
31 | | FALL_THROUGH = 2, |
32 | | // For historical reasons, these error codes correspond to commonly used |
33 | | // XMLRPC codes (e.g. see METHOD_NOT_FOUND in |
34 | | // https://github.com/python/cpython/blob/main/Lib/xmlrpc/client.py). |
35 | | PARSE_ERROR = -32700, |
36 | | INVALID_REQUEST = -32600, |
37 | | METHOD_NOT_FOUND = -32601, |
38 | | INVALID_PARAMS = -32602, |
39 | | INTERNAL_ERROR = -32603, |
40 | | SERVER_ERROR = -32000, |
41 | | SESSION_NOT_FOUND = SERVER_ERROR - 1, |
42 | | }; |
43 | | |
44 | | // Information returned by command handlers. Usually returned after command |
45 | | // execution attempts. |
46 | | class CRDTP_EXPORT DispatchResponse { |
47 | | public: |
48 | 0 | const std::string& Message() const { return message_; } |
49 | | |
50 | 0 | DispatchCode Code() const { return code_; } |
51 | | |
52 | 0 | bool IsSuccess() const { return code_ == DispatchCode::SUCCESS; } |
53 | 0 | bool IsFallThrough() const { return code_ == DispatchCode::FALL_THROUGH; } |
54 | 0 | bool IsError() const { return code_ < DispatchCode::SUCCESS; } |
55 | | |
56 | | static DispatchResponse Success(); |
57 | | static DispatchResponse FallThrough(); |
58 | | |
59 | | // Indicates that a message could not be parsed. E.g., malformed JSON. |
60 | | static DispatchResponse ParseError(std::string message); |
61 | | |
62 | | // Indicates that a request is lacking required top-level properties |
63 | | // ('id', 'method'), has top-level properties of the wrong type, or has |
64 | | // unknown top-level properties. |
65 | | static DispatchResponse InvalidRequest(std::string message); |
66 | | |
67 | | // Indicates that a protocol method such as "Page.bringToFront" could not be |
68 | | // dispatched because it's not known to the (domain) dispatcher. |
69 | | static DispatchResponse MethodNotFound(std::string message); |
70 | | |
71 | | // Indicates that the params sent to a domain handler are invalid. |
72 | | static DispatchResponse InvalidParams(std::string message); |
73 | | |
74 | | // Used for application level errors, e.g. within protocol agents. |
75 | | static DispatchResponse InternalError(); |
76 | | |
77 | | // Used for application level errors, e.g. within protocol agents. |
78 | | static DispatchResponse ServerError(std::string message); |
79 | | |
80 | | // Indicate that session with the id specified in the protocol message |
81 | | // was not found (e.g. because it has already been detached). |
82 | | static DispatchResponse SessionNotFound(std::string message); |
83 | | |
84 | | private: |
85 | | DispatchResponse() = default; |
86 | | DispatchCode code_; |
87 | | std::string message_; |
88 | | }; |
89 | | |
90 | | // ============================================================================= |
91 | | // Dispatchable - a shallow parser for CBOR encoded DevTools messages |
92 | | // ============================================================================= |
93 | | |
94 | | // This parser extracts only the known top-level fields from a CBOR encoded map; |
95 | | // method, id, sessionId, and params. |
96 | | class CRDTP_EXPORT Dispatchable { |
97 | | public: |
98 | | // This constructor parses the |serialized| message. If successful, |
99 | | // |ok()| will yield |true|, and |Method()|, |SessionId()|, |CallId()|, |
100 | | // |Params()| can be used to access, the extracted contents. Otherwise, |
101 | | // |ok()| will yield |false|, and |DispatchError()| can be |
102 | | // used to send a response or notification to the client. |
103 | | explicit Dispatchable(span<uint8_t> serialized); |
104 | | |
105 | | // The serialized message that we just parsed. |
106 | 0 | span<uint8_t> Serialized() const { return serialized_; } |
107 | | |
108 | | // Yields true if parsing was successful. This is cheaper than calling |
109 | | // ::DispatchError(). |
110 | | bool ok() const; |
111 | | |
112 | | // If !ok(), returns a DispatchResponse with appropriate code and error |
113 | | // which can be sent to the client as a response or notification. |
114 | | DispatchResponse DispatchError() const; |
115 | | |
116 | | // Top level field: the command to be executed, fully qualified by |
117 | | // domain. E.g. "Page.createIsolatedWorld". |
118 | 0 | span<uint8_t> Method() const { return method_; } |
119 | | // Used to identify protocol connections attached to a specific |
120 | | // target. See Target.attachToTarget, Target.setAutoAttach. |
121 | 0 | span<uint8_t> SessionId() const { return session_id_; } |
122 | | // The call id, a sequence number that's used in responses to indicate |
123 | | // the request to which the response belongs. |
124 | 0 | int32_t CallId() const { return call_id_; } |
125 | 0 | bool HasCallId() const { return has_call_id_; } |
126 | | // The payload of the request in CBOR format. The |Dispatchable| parser does |
127 | | // not parse into this; it only provides access to its raw contents here. |
128 | 0 | span<uint8_t> Params() const { return params_; } |
129 | | |
130 | | private: |
131 | | bool MaybeParseProperty(cbor::CBORTokenizer* tokenizer); |
132 | | bool MaybeParseCallId(cbor::CBORTokenizer* tokenizer); |
133 | | bool MaybeParseMethod(cbor::CBORTokenizer* tokenizer); |
134 | | bool MaybeParseParams(cbor::CBORTokenizer* tokenizer); |
135 | | bool MaybeParseSessionId(cbor::CBORTokenizer* tokenizer); |
136 | | |
137 | | span<uint8_t> serialized_; |
138 | | |
139 | | Status status_; |
140 | | |
141 | | bool has_call_id_ = false; |
142 | | int32_t call_id_; |
143 | | span<uint8_t> method_; |
144 | | bool params_seen_ = false; |
145 | | span<uint8_t> params_; |
146 | | span<uint8_t> session_id_; |
147 | | }; |
148 | | |
149 | | // ============================================================================= |
150 | | // Helpers for creating protocol cresponses and notifications. |
151 | | // ============================================================================= |
152 | | |
153 | | // The resulting notifications can be sent to a protocol client, |
154 | | // usually via a FrontendChannel (see frontend_channel.h). |
155 | | |
156 | | CRDTP_EXPORT std::unique_ptr<Serializable> CreateErrorResponse( |
157 | | int callId, |
158 | | DispatchResponse dispatch_response); |
159 | | |
160 | | CRDTP_EXPORT std::unique_ptr<Serializable> CreateErrorNotification( |
161 | | DispatchResponse dispatch_response); |
162 | | |
163 | | CRDTP_EXPORT std::unique_ptr<Serializable> CreateResponse( |
164 | | int callId, |
165 | | std::unique_ptr<Serializable> params); |
166 | | |
167 | | CRDTP_EXPORT std::unique_ptr<Serializable> CreateNotification( |
168 | | const char* method, |
169 | | std::unique_ptr<Serializable> params = nullptr); |
170 | | |
171 | | // ============================================================================= |
172 | | // DomainDispatcher - Dispatching betwen protocol methods within a domain. |
173 | | // ============================================================================= |
174 | | |
175 | | // This class is subclassed by |DomainDispatcherImpl|, which we generate per |
176 | | // DevTools domain. It contains routines called from the generated code, |
177 | | // e.g. ::MaybeReportInvalidParams, which are optimized for small code size. |
178 | | // The most important method is ::Dispatch, which implements method dispatch |
179 | | // by command name lookup. |
180 | | class CRDTP_EXPORT DomainDispatcher { |
181 | | public: |
182 | | class CRDTP_EXPORT WeakPtr { |
183 | | public: |
184 | | explicit WeakPtr(DomainDispatcher*); |
185 | | ~WeakPtr(); |
186 | 0 | DomainDispatcher* get() { return dispatcher_; } |
187 | 0 | void dispose() { dispatcher_ = nullptr; } |
188 | | |
189 | | private: |
190 | | DomainDispatcher* dispatcher_; |
191 | | }; |
192 | | |
193 | | class CRDTP_EXPORT Callback { |
194 | | public: |
195 | | virtual ~Callback(); |
196 | | void dispose(); |
197 | | |
198 | | protected: |
199 | | // |method| must point at static storage (a C++ string literal in practice). |
200 | | Callback(std::unique_ptr<WeakPtr> backend_impl, |
201 | | int call_id, |
202 | | span<uint8_t> method, |
203 | | span<uint8_t> message); |
204 | | |
205 | | void sendIfActive(std::unique_ptr<Serializable> partialMessage, |
206 | | const DispatchResponse& response); |
207 | | void fallThroughIfActive(); |
208 | | |
209 | | private: |
210 | | std::unique_ptr<WeakPtr> backend_impl_; |
211 | | int call_id_; |
212 | | // Subclasses of this class are instantiated from generated code which |
213 | | // passes a string literal for the method name to the constructor. So the |
214 | | // storage for |method| is the binary of the running process. |
215 | | span<uint8_t> method_; |
216 | | std::vector<uint8_t> message_; |
217 | | }; |
218 | | |
219 | | explicit DomainDispatcher(FrontendChannel*); |
220 | | virtual ~DomainDispatcher(); |
221 | | |
222 | | // Given a |command_name| without domain qualification, looks up the |
223 | | // corresponding method. If the method is not found, returns nullptr. |
224 | | // Otherwise, Returns a closure that will parse the provided |
225 | | // Dispatchable.params() to a protocol object and execute the |
226 | | // apprpropriate method. If the parsing fails it will issue an |
227 | | // error response on the frontend channel, otherwise it will execute the |
228 | | // command. |
229 | | virtual std::function<void(const Dispatchable&)> Dispatch( |
230 | | span<uint8_t> command_name) = 0; |
231 | | |
232 | | // Sends a response to the client via the channel. |
233 | | void sendResponse(int call_id, |
234 | | const DispatchResponse&, |
235 | | std::unique_ptr<Serializable> result = nullptr); |
236 | | |
237 | | void ReportInvalidParams(const Dispatchable& dispatchable, |
238 | | const DeserializerState& state); |
239 | | |
240 | 0 | FrontendChannel* channel() { return frontend_channel_; } |
241 | | |
242 | | void clearFrontend(); |
243 | | |
244 | | std::unique_ptr<WeakPtr> weakPtr(); |
245 | | |
246 | | private: |
247 | | FrontendChannel* frontend_channel_; |
248 | | std::unordered_set<WeakPtr*> weak_ptrs_; |
249 | | }; |
250 | | |
251 | | // ============================================================================= |
252 | | // UberDispatcher - dispatches between domains (backends). |
253 | | // ============================================================================= |
254 | | class CRDTP_EXPORT UberDispatcher { |
255 | | public: |
256 | | // Return type for ::Dispatch. |
257 | | class CRDTP_EXPORT DispatchResult { |
258 | | public: |
259 | | DispatchResult(bool method_found, std::function<void()> runnable); |
260 | | |
261 | | // Indicates whether the method was found, that is, it could be dispatched |
262 | | // to a backend registered with this dispatcher. |
263 | 0 | bool MethodFound() const { return method_found_; } |
264 | | |
265 | | // Runs the dispatched result. This will send the appropriate error |
266 | | // responses if the method wasn't found or if something went wrong during |
267 | | // parameter parsing. |
268 | | void Run(); |
269 | | |
270 | | private: |
271 | | bool method_found_; |
272 | | std::function<void()> runnable_; |
273 | | }; |
274 | | |
275 | | // |frontend_hannel| can't be nullptr. |
276 | | explicit UberDispatcher(FrontendChannel* frontend_channel); |
277 | | virtual ~UberDispatcher(); |
278 | | |
279 | | // Dispatches the provided |dispatchable| considering all redirects and domain |
280 | | // handlers registered with this uber dispatcher. Also see |DispatchResult|. |
281 | | // |dispatchable.ok()| must hold - callers must check this separately and |
282 | | // deal with errors. |
283 | | DispatchResult Dispatch(const Dispatchable& dispatchable) const; |
284 | | |
285 | | // Invoked from generated code for wiring domain backends; that is, |
286 | | // connecting domain handlers to an uber dispatcher. |
287 | | // See <domain-namespace>::Dispatcher::Wire(UberDispatcher*,Backend*). |
288 | 0 | FrontendChannel* channel() const { |
289 | | assert(frontend_channel_); |
290 | 0 | return frontend_channel_; |
291 | 0 | } |
292 | | |
293 | | // Invoked from generated code for wiring domain backends; that is, |
294 | | // connecting domain handlers to an uber dispatcher. |
295 | | // See <domain-namespace>::Dispatcher::Wire(UberDispatcher*,Backend*). |
296 | | void WireBackend(span<uint8_t> domain, |
297 | | const std::vector<std::pair<span<uint8_t>, span<uint8_t>>>&, |
298 | | std::unique_ptr<DomainDispatcher> dispatcher); |
299 | | |
300 | | private: |
301 | | DomainDispatcher* findDispatcher(span<uint8_t> method); |
302 | | FrontendChannel* const frontend_channel_; |
303 | | // Pairs of ascii strings of the form ("Domain1.method1","Domain2.method2") |
304 | | // indicating that the first element of each pair redirects to the second. |
305 | | // Sorted by first element. |
306 | | std::vector<std::pair<span<uint8_t>, span<uint8_t>>> redirects_; |
307 | | // Domain dispatcher instances, sorted by their domain name. |
308 | | std::vector<std::pair<span<uint8_t>, std::unique_ptr<DomainDispatcher>>> |
309 | | dispatchers_; |
310 | | }; |
311 | | } // namespace crdtp |
312 | | |
313 | | #endif // CRDTP_DISPATCH_H_ |