Coverage Report

Created: 2025-10-31 09:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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_