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