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 #include "dispatch.h"
6 
7 #include <cassert>
8 #include "cbor.h"
9 #include "error_support.h"
10 #include "find_by_first.h"
11 #include "frontend_channel.h"
12 #include "protocol_core.h"
13 
14 namespace v8_crdtp {
15 // =============================================================================
16 // DispatchResponse - Error status and chaining / fall through
17 // =============================================================================
18 
19 // static
Success()20 DispatchResponse DispatchResponse::Success() {
21   DispatchResponse result;
22   result.code_ = DispatchCode::SUCCESS;
23   return result;
24 }
25 
26 // static
FallThrough()27 DispatchResponse DispatchResponse::FallThrough() {
28   DispatchResponse result;
29   result.code_ = DispatchCode::FALL_THROUGH;
30   return result;
31 }
32 
33 // static
ParseError(std::string message)34 DispatchResponse DispatchResponse::ParseError(std::string message) {
35   DispatchResponse result;
36   result.code_ = DispatchCode::PARSE_ERROR;
37   result.message_ = std::move(message);
38   return result;
39 }
40 
41 // static
InvalidRequest(std::string message)42 DispatchResponse DispatchResponse::InvalidRequest(std::string message) {
43   DispatchResponse result;
44   result.code_ = DispatchCode::INVALID_REQUEST;
45   result.message_ = std::move(message);
46   return result;
47 }
48 
49 // static
MethodNotFound(std::string message)50 DispatchResponse DispatchResponse::MethodNotFound(std::string message) {
51   DispatchResponse result;
52   result.code_ = DispatchCode::METHOD_NOT_FOUND;
53   result.message_ = std::move(message);
54   return result;
55 }
56 
57 // static
InvalidParams(std::string message)58 DispatchResponse DispatchResponse::InvalidParams(std::string message) {
59   DispatchResponse result;
60   result.code_ = DispatchCode::INVALID_PARAMS;
61   result.message_ = std::move(message);
62   return result;
63 }
64 
65 // static
InternalError()66 DispatchResponse DispatchResponse::InternalError() {
67   DispatchResponse result;
68   result.code_ = DispatchCode::INTERNAL_ERROR;
69   result.message_ = "Internal error";
70   return result;
71 }
72 
73 // static
ServerError(std::string message)74 DispatchResponse DispatchResponse::ServerError(std::string message) {
75   DispatchResponse result;
76   result.code_ = DispatchCode::SERVER_ERROR;
77   result.message_ = std::move(message);
78   return result;
79 }
80 
81 // =============================================================================
82 // Dispatchable - a shallow parser for CBOR encoded DevTools messages
83 // =============================================================================
84 namespace {
85 constexpr size_t kEncodedEnvelopeHeaderSize = 1 + 1 + sizeof(uint32_t);
86 }  // namespace
87 
Dispatchable(span<uint8_t> serialized)88 Dispatchable::Dispatchable(span<uint8_t> serialized) : serialized_(serialized) {
89   Status s = cbor::CheckCBORMessage(serialized);
90   if (!s.ok()) {
91     status_ = {Error::MESSAGE_MUST_BE_AN_OBJECT, s.pos};
92     return;
93   }
94   cbor::CBORTokenizer tokenizer(serialized);
95   if (tokenizer.TokenTag() == cbor::CBORTokenTag::ERROR_VALUE) {
96     status_ = tokenizer.Status();
97     return;
98   }
99 
100   // We checked for the envelope start byte above, so the tokenizer
101   // must agree here, since it's not an error.
102   assert(tokenizer.TokenTag() == cbor::CBORTokenTag::ENVELOPE);
103 
104   // Before we enter the envelope, we save the position that we
105   // expect to see after we're done parsing the envelope contents.
106   // This way we can compare and produce an error if the contents
107   // didn't fit exactly into the envelope length.
108   const size_t pos_past_envelope = tokenizer.Status().pos +
109                                    kEncodedEnvelopeHeaderSize +
110                                    tokenizer.GetEnvelopeContents().size();
111   tokenizer.EnterEnvelope();
112   if (tokenizer.TokenTag() == cbor::CBORTokenTag::ERROR_VALUE) {
113     status_ = tokenizer.Status();
114     return;
115   }
116   if (tokenizer.TokenTag() != cbor::CBORTokenTag::MAP_START) {
117     status_ = {Error::MESSAGE_MUST_BE_AN_OBJECT, tokenizer.Status().pos};
118     return;
119   }
120   assert(tokenizer.TokenTag() == cbor::CBORTokenTag::MAP_START);
121   tokenizer.Next();  // Now we should be pointed at the map key.
122   while (tokenizer.TokenTag() != cbor::CBORTokenTag::STOP) {
123     switch (tokenizer.TokenTag()) {
124       case cbor::CBORTokenTag::DONE:
125         status_ =
126             Status{Error::CBOR_UNEXPECTED_EOF_IN_MAP, tokenizer.Status().pos};
127         return;
128       case cbor::CBORTokenTag::ERROR_VALUE:
129         status_ = tokenizer.Status();
130         return;
131       case cbor::CBORTokenTag::STRING8:
132         if (!MaybeParseProperty(&tokenizer))
133           return;
134         break;
135       default:
136         // We require the top-level keys to be UTF8 (US-ASCII in practice).
137         status_ = Status{Error::CBOR_INVALID_MAP_KEY, tokenizer.Status().pos};
138         return;
139     }
140   }
141   tokenizer.Next();
142   if (!has_call_id_) {
143     status_ = Status{Error::MESSAGE_MUST_HAVE_INTEGER_ID_PROPERTY,
144                      tokenizer.Status().pos};
145     return;
146   }
147   if (method_.empty()) {
148     status_ = Status{Error::MESSAGE_MUST_HAVE_STRING_METHOD_PROPERTY,
149                      tokenizer.Status().pos};
150     return;
151   }
152   // The contents of the envelope parsed OK, now check that we're at
153   // the expected position.
154   if (pos_past_envelope != tokenizer.Status().pos) {
155     status_ = Status{Error::CBOR_ENVELOPE_CONTENTS_LENGTH_MISMATCH,
156                      tokenizer.Status().pos};
157     return;
158   }
159   if (tokenizer.TokenTag() != cbor::CBORTokenTag::DONE) {
160     status_ = Status{Error::CBOR_TRAILING_JUNK, tokenizer.Status().pos};
161     return;
162   }
163 }
164 
ok() const165 bool Dispatchable::ok() const {
166   return status_.ok();
167 }
168 
DispatchError() const169 DispatchResponse Dispatchable::DispatchError() const {
170   // TODO(johannes): Replace with DCHECK / similar?
171   if (status_.ok())
172     return DispatchResponse::Success();
173 
174   if (status_.IsMessageError())
175     return DispatchResponse::InvalidRequest(status_.Message());
176   return DispatchResponse::ParseError(status_.ToASCIIString());
177 }
178 
MaybeParseProperty(cbor::CBORTokenizer * tokenizer)179 bool Dispatchable::MaybeParseProperty(cbor::CBORTokenizer* tokenizer) {
180   span<uint8_t> property_name = tokenizer->GetString8();
181   if (SpanEquals(SpanFrom("id"), property_name))
182     return MaybeParseCallId(tokenizer);
183   if (SpanEquals(SpanFrom("method"), property_name))
184     return MaybeParseMethod(tokenizer);
185   if (SpanEquals(SpanFrom("params"), property_name))
186     return MaybeParseParams(tokenizer);
187   if (SpanEquals(SpanFrom("sessionId"), property_name))
188     return MaybeParseSessionId(tokenizer);
189   status_ =
190       Status{Error::MESSAGE_HAS_UNKNOWN_PROPERTY, tokenizer->Status().pos};
191   return false;
192 }
193 
MaybeParseCallId(cbor::CBORTokenizer * tokenizer)194 bool Dispatchable::MaybeParseCallId(cbor::CBORTokenizer* tokenizer) {
195   if (has_call_id_) {
196     status_ = Status{Error::CBOR_DUPLICATE_MAP_KEY, tokenizer->Status().pos};
197     return false;
198   }
199   tokenizer->Next();
200   if (tokenizer->TokenTag() != cbor::CBORTokenTag::INT32) {
201     status_ = Status{Error::MESSAGE_MUST_HAVE_INTEGER_ID_PROPERTY,
202                      tokenizer->Status().pos};
203     return false;
204   }
205   call_id_ = tokenizer->GetInt32();
206   has_call_id_ = true;
207   tokenizer->Next();
208   return true;
209 }
210 
MaybeParseMethod(cbor::CBORTokenizer * tokenizer)211 bool Dispatchable::MaybeParseMethod(cbor::CBORTokenizer* tokenizer) {
212   if (!method_.empty()) {
213     status_ = Status{Error::CBOR_DUPLICATE_MAP_KEY, tokenizer->Status().pos};
214     return false;
215   }
216   tokenizer->Next();
217   if (tokenizer->TokenTag() != cbor::CBORTokenTag::STRING8) {
218     status_ = Status{Error::MESSAGE_MUST_HAVE_STRING_METHOD_PROPERTY,
219                      tokenizer->Status().pos};
220     return false;
221   }
222   method_ = tokenizer->GetString8();
223   tokenizer->Next();
224   return true;
225 }
226 
MaybeParseParams(cbor::CBORTokenizer * tokenizer)227 bool Dispatchable::MaybeParseParams(cbor::CBORTokenizer* tokenizer) {
228   if (params_seen_) {
229     status_ = Status{Error::CBOR_DUPLICATE_MAP_KEY, tokenizer->Status().pos};
230     return false;
231   }
232   params_seen_ = true;
233   tokenizer->Next();
234   if (tokenizer->TokenTag() == cbor::CBORTokenTag::NULL_VALUE) {
235     tokenizer->Next();
236     return true;
237   }
238   if (tokenizer->TokenTag() != cbor::CBORTokenTag::ENVELOPE) {
239     status_ = Status{Error::MESSAGE_MAY_HAVE_OBJECT_PARAMS_PROPERTY,
240                      tokenizer->Status().pos};
241     return false;
242   }
243   params_ = tokenizer->GetEnvelope();
244   tokenizer->Next();
245   return true;
246 }
247 
MaybeParseSessionId(cbor::CBORTokenizer * tokenizer)248 bool Dispatchable::MaybeParseSessionId(cbor::CBORTokenizer* tokenizer) {
249   if (!session_id_.empty()) {
250     status_ = Status{Error::CBOR_DUPLICATE_MAP_KEY, tokenizer->Status().pos};
251     return false;
252   }
253   tokenizer->Next();
254   if (tokenizer->TokenTag() != cbor::CBORTokenTag::STRING8) {
255     status_ = Status{Error::MESSAGE_MAY_HAVE_STRING_SESSION_ID_PROPERTY,
256                      tokenizer->Status().pos};
257     return false;
258   }
259   session_id_ = tokenizer->GetString8();
260   tokenizer->Next();
261   return true;
262 }
263 
264 namespace {
265 class ProtocolError : public Serializable {
266  public:
ProtocolError(DispatchResponse dispatch_response)267   explicit ProtocolError(DispatchResponse dispatch_response)
268       : dispatch_response_(std::move(dispatch_response)) {}
269 
AppendSerialized(std::vector<uint8_t> * out) const270   void AppendSerialized(std::vector<uint8_t>* out) const override {
271     Status status;
272     std::unique_ptr<ParserHandler> encoder = cbor::NewCBOREncoder(out, &status);
273     encoder->HandleMapBegin();
274     if (has_call_id_) {
275       encoder->HandleString8(SpanFrom("id"));
276       encoder->HandleInt32(call_id_);
277     }
278     encoder->HandleString8(SpanFrom("error"));
279     encoder->HandleMapBegin();
280     encoder->HandleString8(SpanFrom("code"));
281     encoder->HandleInt32(static_cast<int32_t>(dispatch_response_.Code()));
282     encoder->HandleString8(SpanFrom("message"));
283     encoder->HandleString8(SpanFrom(dispatch_response_.Message()));
284     if (!data_.empty()) {
285       encoder->HandleString8(SpanFrom("data"));
286       encoder->HandleString8(SpanFrom(data_));
287     }
288     encoder->HandleMapEnd();
289     encoder->HandleMapEnd();
290     assert(status.ok());
291   }
292 
SetCallId(int call_id)293   void SetCallId(int call_id) {
294     has_call_id_ = true;
295     call_id_ = call_id;
296   }
SetData(std::string data)297   void SetData(std::string data) { data_ = std::move(data); }
298 
299  private:
300   const DispatchResponse dispatch_response_;
301   std::string data_;
302   int call_id_ = 0;
303   bool has_call_id_ = false;
304 };
305 }  // namespace
306 
307 // =============================================================================
308 // Helpers for creating protocol cresponses and notifications.
309 // =============================================================================
310 
CreateErrorResponse(int call_id,DispatchResponse dispatch_response,const ErrorSupport * errors)311 std::unique_ptr<Serializable> CreateErrorResponse(
312     int call_id,
313     DispatchResponse dispatch_response,
314     const ErrorSupport* errors) {
315   auto protocol_error =
316       std::make_unique<ProtocolError>(std::move(dispatch_response));
317   protocol_error->SetCallId(call_id);
318   if (errors && !errors->Errors().empty()) {
319     protocol_error->SetData(
320         std::string(errors->Errors().begin(), errors->Errors().end()));
321   }
322   return protocol_error;
323 }
324 
CreateErrorResponse(int call_id,DispatchResponse dispatch_response,const DeserializerState & state)325 std::unique_ptr<Serializable> CreateErrorResponse(
326     int call_id,
327     DispatchResponse dispatch_response,
328     const DeserializerState& state) {
329   auto protocol_error =
330       std::make_unique<ProtocolError>(std::move(dispatch_response));
331   protocol_error->SetCallId(call_id);
332   // TODO(caseq): should we plumb the call name here?
333   protocol_error->SetData(state.ErrorMessage(MakeSpan("params")));
334   return protocol_error;
335 }
336 
CreateErrorNotification(DispatchResponse dispatch_response)337 std::unique_ptr<Serializable> CreateErrorNotification(
338     DispatchResponse dispatch_response) {
339   return std::make_unique<ProtocolError>(std::move(dispatch_response));
340 }
341 
342 namespace {
343 class Response : public Serializable {
344  public:
Response(int call_id,std::unique_ptr<Serializable> params)345   Response(int call_id, std::unique_ptr<Serializable> params)
346       : call_id_(call_id), params_(std::move(params)) {}
347 
AppendSerialized(std::vector<uint8_t> * out) const348   void AppendSerialized(std::vector<uint8_t>* out) const override {
349     Status status;
350     std::unique_ptr<ParserHandler> encoder = cbor::NewCBOREncoder(out, &status);
351     encoder->HandleMapBegin();
352     encoder->HandleString8(SpanFrom("id"));
353     encoder->HandleInt32(call_id_);
354     encoder->HandleString8(SpanFrom("result"));
355     if (params_) {
356       params_->AppendSerialized(out);
357     } else {
358       encoder->HandleMapBegin();
359       encoder->HandleMapEnd();
360     }
361     encoder->HandleMapEnd();
362     assert(status.ok());
363   }
364 
365  private:
366   const int call_id_;
367   std::unique_ptr<Serializable> params_;
368 };
369 
370 class Notification : public Serializable {
371  public:
Notification(const char * method,std::unique_ptr<Serializable> params)372   Notification(const char* method, std::unique_ptr<Serializable> params)
373       : method_(method), params_(std::move(params)) {}
374 
AppendSerialized(std::vector<uint8_t> * out) const375   void AppendSerialized(std::vector<uint8_t>* out) const override {
376     Status status;
377     std::unique_ptr<ParserHandler> encoder = cbor::NewCBOREncoder(out, &status);
378     encoder->HandleMapBegin();
379     encoder->HandleString8(SpanFrom("method"));
380     encoder->HandleString8(SpanFrom(method_));
381     encoder->HandleString8(SpanFrom("params"));
382     if (params_) {
383       params_->AppendSerialized(out);
384     } else {
385       encoder->HandleMapBegin();
386       encoder->HandleMapEnd();
387     }
388     encoder->HandleMapEnd();
389     assert(status.ok());
390   }
391 
392  private:
393   const char* method_;
394   std::unique_ptr<Serializable> params_;
395 };
396 }  // namespace
397 
CreateResponse(int call_id,std::unique_ptr<Serializable> params)398 std::unique_ptr<Serializable> CreateResponse(
399     int call_id,
400     std::unique_ptr<Serializable> params) {
401   return std::make_unique<Response>(call_id, std::move(params));
402 }
403 
CreateNotification(const char * method,std::unique_ptr<Serializable> params)404 std::unique_ptr<Serializable> CreateNotification(
405     const char* method,
406     std::unique_ptr<Serializable> params) {
407   return std::make_unique<Notification>(method, std::move(params));
408 }
409 
410 // =============================================================================
411 // DomainDispatcher - Dispatching betwen protocol methods within a domain.
412 // =============================================================================
WeakPtr(DomainDispatcher * dispatcher)413 DomainDispatcher::WeakPtr::WeakPtr(DomainDispatcher* dispatcher)
414     : dispatcher_(dispatcher) {}
415 
~WeakPtr()416 DomainDispatcher::WeakPtr::~WeakPtr() {
417   if (dispatcher_)
418     dispatcher_->weak_ptrs_.erase(this);
419 }
420 
421 DomainDispatcher::Callback::~Callback() = default;
422 
dispose()423 void DomainDispatcher::Callback::dispose() {
424   backend_impl_ = nullptr;
425 }
426 
Callback(std::unique_ptr<DomainDispatcher::WeakPtr> backend_impl,int call_id,span<uint8_t> method,span<uint8_t> message)427 DomainDispatcher::Callback::Callback(
428     std::unique_ptr<DomainDispatcher::WeakPtr> backend_impl,
429     int call_id,
430     span<uint8_t> method,
431     span<uint8_t> message)
432     : backend_impl_(std::move(backend_impl)),
433       call_id_(call_id),
434       method_(method),
435       message_(message.begin(), message.end()) {}
436 
sendIfActive(std::unique_ptr<Serializable> partialMessage,const DispatchResponse & response)437 void DomainDispatcher::Callback::sendIfActive(
438     std::unique_ptr<Serializable> partialMessage,
439     const DispatchResponse& response) {
440   if (!backend_impl_ || !backend_impl_->get())
441     return;
442   backend_impl_->get()->sendResponse(call_id_, response,
443                                      std::move(partialMessage));
444   backend_impl_ = nullptr;
445 }
446 
fallThroughIfActive()447 void DomainDispatcher::Callback::fallThroughIfActive() {
448   if (!backend_impl_ || !backend_impl_->get())
449     return;
450   backend_impl_->get()->channel()->FallThrough(call_id_, method_,
451                                                SpanFrom(message_));
452   backend_impl_ = nullptr;
453 }
454 
DomainDispatcher(FrontendChannel * frontendChannel)455 DomainDispatcher::DomainDispatcher(FrontendChannel* frontendChannel)
456     : frontend_channel_(frontendChannel) {}
457 
~DomainDispatcher()458 DomainDispatcher::~DomainDispatcher() {
459   clearFrontend();
460 }
461 
sendResponse(int call_id,const DispatchResponse & response,std::unique_ptr<Serializable> result)462 void DomainDispatcher::sendResponse(int call_id,
463                                     const DispatchResponse& response,
464                                     std::unique_ptr<Serializable> result) {
465   if (!frontend_channel_)
466     return;
467   std::unique_ptr<Serializable> serializable;
468   if (response.IsError()) {
469     serializable = CreateErrorResponse(call_id, response);
470   } else {
471     serializable = CreateResponse(call_id, std::move(result));
472   }
473   frontend_channel_->SendProtocolResponse(call_id, std::move(serializable));
474 }
475 
MaybeReportInvalidParams(const Dispatchable & dispatchable,const ErrorSupport & errors)476 bool DomainDispatcher::MaybeReportInvalidParams(
477     const Dispatchable& dispatchable,
478     const ErrorSupport& errors) {
479   if (errors.Errors().empty())
480     return false;
481   if (frontend_channel_) {
482     frontend_channel_->SendProtocolResponse(
483         dispatchable.CallId(),
484         CreateErrorResponse(
485             dispatchable.CallId(),
486             DispatchResponse::InvalidParams("Invalid parameters"), &errors));
487   }
488   return true;
489 }
490 
MaybeReportInvalidParams(const Dispatchable & dispatchable,const DeserializerState & state)491 bool DomainDispatcher::MaybeReportInvalidParams(
492     const Dispatchable& dispatchable,
493     const DeserializerState& state) {
494   if (state.status().ok())
495     return false;
496   if (frontend_channel_) {
497     frontend_channel_->SendProtocolResponse(
498         dispatchable.CallId(),
499         CreateErrorResponse(
500             dispatchable.CallId(),
501             DispatchResponse::InvalidParams("Invalid parameters"), state));
502   }
503   return true;
504 }
505 
clearFrontend()506 void DomainDispatcher::clearFrontend() {
507   frontend_channel_ = nullptr;
508   for (auto& weak : weak_ptrs_)
509     weak->dispose();
510   weak_ptrs_.clear();
511 }
512 
weakPtr()513 std::unique_ptr<DomainDispatcher::WeakPtr> DomainDispatcher::weakPtr() {
514   auto weak = std::make_unique<DomainDispatcher::WeakPtr>(this);
515   weak_ptrs_.insert(weak.get());
516   return weak;
517 }
518 
519 // =============================================================================
520 // UberDispatcher - dispatches between domains (backends).
521 // =============================================================================
DispatchResult(bool method_found,std::function<void ()> runnable)522 UberDispatcher::DispatchResult::DispatchResult(bool method_found,
523                                                std::function<void()> runnable)
524     : method_found_(method_found), runnable_(runnable) {}
525 
Run()526 void UberDispatcher::DispatchResult::Run() {
527   if (!runnable_)
528     return;
529   runnable_();
530   runnable_ = nullptr;
531 }
532 
UberDispatcher(FrontendChannel * frontend_channel)533 UberDispatcher::UberDispatcher(FrontendChannel* frontend_channel)
534     : frontend_channel_(frontend_channel) {
535   assert(frontend_channel);
536 }
537 
538 UberDispatcher::~UberDispatcher() = default;
539 
540 constexpr size_t kNotFound = std::numeric_limits<size_t>::max();
541 
542 namespace {
DotIdx(span<uint8_t> method)543 size_t DotIdx(span<uint8_t> method) {
544   const void* p = memchr(method.data(), '.', method.size());
545   return p ? reinterpret_cast<const uint8_t*>(p) - method.data() : kNotFound;
546 }
547 }  // namespace
548 
Dispatch(const Dispatchable & dispatchable) const549 UberDispatcher::DispatchResult UberDispatcher::Dispatch(
550     const Dispatchable& dispatchable) const {
551   span<uint8_t> method = FindByFirst(redirects_, dispatchable.Method(),
552                                      /*default_value=*/dispatchable.Method());
553   size_t dot_idx = DotIdx(method);
554   if (dot_idx != kNotFound) {
555     span<uint8_t> domain = method.subspan(0, dot_idx);
556     span<uint8_t> command = method.subspan(dot_idx + 1);
557     DomainDispatcher* dispatcher = FindByFirst(dispatchers_, domain);
558     if (dispatcher) {
559       std::function<void(const Dispatchable&)> dispatched =
560           dispatcher->Dispatch(command);
561       if (dispatched) {
562         return DispatchResult(
563             true, [dispatchable, dispatched = std::move(dispatched)]() {
564               dispatched(dispatchable);
565             });
566       }
567     }
568   }
569   return DispatchResult(false, [this, dispatchable]() {
570     frontend_channel_->SendProtocolResponse(
571         dispatchable.CallId(),
572         CreateErrorResponse(dispatchable.CallId(),
573                             DispatchResponse::MethodNotFound(
574                                 "'" +
575                                 std::string(dispatchable.Method().begin(),
576                                             dispatchable.Method().end()) +
577                                 "' wasn't found")));
578   });
579 }
580 
581 template <typename T>
582 struct FirstLessThan {
operator ()v8_crdtp::FirstLessThan583   bool operator()(const std::pair<span<uint8_t>, T>& left,
584                   const std::pair<span<uint8_t>, T>& right) {
585     return SpanLessThan(left.first, right.first);
586   }
587 };
588 
WireBackend(span<uint8_t> domain,const std::vector<std::pair<span<uint8_t>,span<uint8_t>>> & sorted_redirects,std::unique_ptr<DomainDispatcher> dispatcher)589 void UberDispatcher::WireBackend(
590     span<uint8_t> domain,
591     const std::vector<std::pair<span<uint8_t>, span<uint8_t>>>&
592         sorted_redirects,
593     std::unique_ptr<DomainDispatcher> dispatcher) {
594   auto it = redirects_.insert(redirects_.end(), sorted_redirects.begin(),
595                               sorted_redirects.end());
596   std::inplace_merge(redirects_.begin(), it, redirects_.end(),
597                      FirstLessThan<span<uint8_t>>());
598   auto jt = dispatchers_.insert(dispatchers_.end(),
599                                 std::make_pair(domain, std::move(dispatcher)));
600   std::inplace_merge(dispatchers_.begin(), jt, dispatchers_.end(),
601                      FirstLessThan<std::unique_ptr<DomainDispatcher>>());
602 }
603 
604 }  // namespace v8_crdtp
605