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