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 <vector>
6 
7 #include "cbor.h"
8 #include "dispatch.h"
9 #include "error_support.h"
10 #include "frontend_channel.h"
11 #include "json.h"
12 #include "test_platform.h"
13 
14 namespace crdtp {
15 // =============================================================================
16 // DispatchResponse - Error status and chaining / fall through
17 // =============================================================================
TEST(DispatchResponseTest,OK)18 TEST(DispatchResponseTest, OK) {
19   EXPECT_EQ(DispatchCode::SUCCESS, DispatchResponse::Success().Code());
20   EXPECT_TRUE(DispatchResponse::Success().IsSuccess());
21 }
22 
TEST(DispatchResponseTest,ServerError)23 TEST(DispatchResponseTest, ServerError) {
24   DispatchResponse error = DispatchResponse::ServerError("Oops!");
25   EXPECT_FALSE(error.IsSuccess());
26   EXPECT_EQ(DispatchCode::SERVER_ERROR, error.Code());
27   EXPECT_EQ("Oops!", error.Message());
28 }
29 
TEST(DispatchResponseTest,InternalError)30 TEST(DispatchResponseTest, InternalError) {
31   DispatchResponse error = DispatchResponse::InternalError();
32   EXPECT_FALSE(error.IsSuccess());
33   EXPECT_EQ(DispatchCode::INTERNAL_ERROR, error.Code());
34   EXPECT_EQ("Internal error", error.Message());
35 }
36 
TEST(DispatchResponseTest,InvalidParams)37 TEST(DispatchResponseTest, InvalidParams) {
38   DispatchResponse error = DispatchResponse::InvalidParams("too cool");
39   EXPECT_FALSE(error.IsSuccess());
40   EXPECT_EQ(DispatchCode::INVALID_PARAMS, error.Code());
41   EXPECT_EQ("too cool", error.Message());
42 }
43 
TEST(DispatchResponseTest,FallThrough)44 TEST(DispatchResponseTest, FallThrough) {
45   DispatchResponse error = DispatchResponse::FallThrough();
46   EXPECT_FALSE(error.IsSuccess());
47   EXPECT_TRUE(error.IsFallThrough());
48   EXPECT_EQ(DispatchCode::FALL_THROUGH, error.Code());
49 }
50 
51 // =============================================================================
52 // Dispatchable - a shallow parser for CBOR encoded DevTools messages
53 // =============================================================================
TEST(DispatchableTest,MessageMustBeAnObject)54 TEST(DispatchableTest, MessageMustBeAnObject) {
55   // Provide no input whatsoever.
56   span<uint8_t> empty_span;
57   Dispatchable empty(empty_span);
58   EXPECT_FALSE(empty.ok());
59   EXPECT_EQ(DispatchCode::INVALID_REQUEST, empty.DispatchError().Code());
60   EXPECT_EQ("Message must be an object", empty.DispatchError().Message());
61 }
62 
TEST(DispatchableTest,MessageMustHaveIntegerIdProperty)63 TEST(DispatchableTest, MessageMustHaveIntegerIdProperty) {
64   // Construct an empty map inside of an envelope.
65   std::vector<uint8_t> cbor;
66   ASSERT_TRUE(json::ConvertJSONToCBOR(SpanFrom("{}"), &cbor).ok());
67   Dispatchable dispatchable(SpanFrom(cbor));
68   EXPECT_FALSE(dispatchable.ok());
69   EXPECT_FALSE(dispatchable.HasCallId());
70   EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code());
71   EXPECT_EQ("Message must have integer 'id' property",
72             dispatchable.DispatchError().Message());
73 }
74 
TEST(DispatchableTest,MessageMustHaveIntegerIdProperty_IncorrectType)75 TEST(DispatchableTest, MessageMustHaveIntegerIdProperty_IncorrectType) {
76   // This time we set the id property, but fail to make it an int32.
77   std::vector<uint8_t> cbor;
78   ASSERT_TRUE(
79       json::ConvertJSONToCBOR(SpanFrom("{\"id\":\"foo\"}"), &cbor).ok());
80   Dispatchable dispatchable(SpanFrom(cbor));
81   EXPECT_FALSE(dispatchable.ok());
82   EXPECT_FALSE(dispatchable.HasCallId());
83   EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code());
84   EXPECT_EQ("Message must have integer 'id' property",
85             dispatchable.DispatchError().Message());
86 }
87 
TEST(DispatchableTest,MessageMustHaveStringMethodProperty)88 TEST(DispatchableTest, MessageMustHaveStringMethodProperty) {
89   // This time we set the id property, but not the method property.
90   std::vector<uint8_t> cbor;
91   ASSERT_TRUE(json::ConvertJSONToCBOR(SpanFrom("{\"id\":42}"), &cbor).ok());
92   Dispatchable dispatchable(SpanFrom(cbor));
93   EXPECT_FALSE(dispatchable.ok());
94   EXPECT_TRUE(dispatchable.HasCallId());
95   EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code());
96   EXPECT_EQ("Message must have string 'method' property",
97             dispatchable.DispatchError().Message());
98 }
99 
TEST(DispatchableTest,MessageMustHaveStringMethodProperty_IncorrectType)100 TEST(DispatchableTest, MessageMustHaveStringMethodProperty_IncorrectType) {
101   // This time we set the method property, but fail to make it a string.
102   std::vector<uint8_t> cbor;
103   ASSERT_TRUE(
104       json::ConvertJSONToCBOR(SpanFrom("{\"id\":42,\"method\":42}"), &cbor)
105           .ok());
106   Dispatchable dispatchable(SpanFrom(cbor));
107   EXPECT_FALSE(dispatchable.ok());
108   EXPECT_TRUE(dispatchable.HasCallId());
109   EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code());
110   EXPECT_EQ("Message must have string 'method' property",
111             dispatchable.DispatchError().Message());
112 }
113 
TEST(DispatchableTest,MessageMayHaveStringSessionIdProperty)114 TEST(DispatchableTest, MessageMayHaveStringSessionIdProperty) {
115   // This time, the session id is an int but it should be a string. Method and
116   // call id are present.
117   std::vector<uint8_t> cbor;
118   ASSERT_TRUE(json::ConvertJSONToCBOR(
119                   SpanFrom("{\"id\":42,\"method\":\"Foo.executeBar\","
120                            "\"sessionId\":42"  // int32 is wrong type
121                            "}"),
122                   &cbor)
123                   .ok());
124   Dispatchable dispatchable(SpanFrom(cbor));
125   EXPECT_FALSE(dispatchable.ok());
126   EXPECT_TRUE(dispatchable.HasCallId());
127   EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code());
128   EXPECT_EQ("Message may have string 'sessionId' property",
129             dispatchable.DispatchError().Message());
130 }
131 
TEST(DispatchableTest,MessageMayHaveObjectParamsProperty)132 TEST(DispatchableTest, MessageMayHaveObjectParamsProperty) {
133   // This time, we fail to use the correct type for the params property.
134   std::vector<uint8_t> cbor;
135   ASSERT_TRUE(json::ConvertJSONToCBOR(
136                   SpanFrom("{\"id\":42,\"method\":\"Foo.executeBar\","
137                            "\"params\":42"  // int32 is wrong type
138                            "}"),
139                   &cbor)
140                   .ok());
141   Dispatchable dispatchable(SpanFrom(cbor));
142   EXPECT_FALSE(dispatchable.ok());
143   EXPECT_TRUE(dispatchable.HasCallId());
144   EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code());
145   EXPECT_EQ("Message may have object 'params' property",
146             dispatchable.DispatchError().Message());
147 }
148 
TEST(DispatchableTest,MessageWithUnknownProperty)149 TEST(DispatchableTest, MessageWithUnknownProperty) {
150   // This time we set the 'unknown' property, so we are told what's allowed.
151   std::vector<uint8_t> cbor;
152   ASSERT_TRUE(
153       json::ConvertJSONToCBOR(SpanFrom("{\"id\":42,\"unknown\":42}"), &cbor)
154           .ok());
155   Dispatchable dispatchable(SpanFrom(cbor));
156   EXPECT_FALSE(dispatchable.ok());
157   EXPECT_TRUE(dispatchable.HasCallId());
158   EXPECT_EQ(DispatchCode::INVALID_REQUEST, dispatchable.DispatchError().Code());
159   EXPECT_EQ(
160       "Message has property other than 'id', 'method', 'sessionId', 'params'",
161       dispatchable.DispatchError().Message());
162 }
163 
TEST(DispatchableTest,DuplicateMapKey)164 TEST(DispatchableTest, DuplicateMapKey) {
165   for (const std::string& json :
166        {"{\"id\":42,\"id\":42}", "{\"params\":null,\"params\":null}",
167         "{\"method\":\"foo\",\"method\":\"foo\"}",
168         "{\"sessionId\":\"42\",\"sessionId\":\"42\"}"}) {
169     SCOPED_TRACE("json = " + json);
170     std::vector<uint8_t> cbor;
171     ASSERT_TRUE(json::ConvertJSONToCBOR(SpanFrom(json), &cbor).ok());
172     Dispatchable dispatchable(SpanFrom(cbor));
173     EXPECT_FALSE(dispatchable.ok());
174     EXPECT_EQ(DispatchCode::PARSE_ERROR, dispatchable.DispatchError().Code());
175     EXPECT_THAT(dispatchable.DispatchError().Message(),
176                 testing::StartsWith("CBOR: duplicate map key at position "));
177   }
178 }
179 
TEST(DispatchableTest,ValidMessageParsesOK_NoParams)180 TEST(DispatchableTest, ValidMessageParsesOK_NoParams) {
181   for (const std::string& json :
182        {"{\"id\":42,\"method\":\"Foo.executeBar\",\"sessionId\":"
183         "\"f421ssvaz4\"}",
184         "{\"id\":42,\"method\":\"Foo.executeBar\",\"sessionId\":\"f421ssvaz4\","
185         "\"params\":null}"}) {
186     SCOPED_TRACE("json = " + json);
187     std::vector<uint8_t> cbor;
188     ASSERT_TRUE(json::ConvertJSONToCBOR(SpanFrom(json), &cbor).ok());
189     Dispatchable dispatchable(SpanFrom(cbor));
190     EXPECT_TRUE(dispatchable.ok());
191     EXPECT_TRUE(dispatchable.HasCallId());
192     EXPECT_EQ(42, dispatchable.CallId());
193     EXPECT_EQ("Foo.executeBar", std::string(dispatchable.Method().begin(),
194                                             dispatchable.Method().end()));
195     EXPECT_EQ("f421ssvaz4", std::string(dispatchable.SessionId().begin(),
196                                         dispatchable.SessionId().end()));
197     EXPECT_TRUE(dispatchable.Params().empty());
198   }
199 }
200 
TEST(DispatchableTest,ValidMessageParsesOK_WithParams)201 TEST(DispatchableTest, ValidMessageParsesOK_WithParams) {
202   std::vector<uint8_t> cbor;
203   cbor::EnvelopeEncoder envelope;
204   envelope.EncodeStart(&cbor);
205   cbor.push_back(cbor::EncodeIndefiniteLengthMapStart());
206   cbor::EncodeString8(SpanFrom("id"), &cbor);
207   cbor::EncodeInt32(42, &cbor);
208   cbor::EncodeString8(SpanFrom("method"), &cbor);
209   cbor::EncodeString8(SpanFrom("Foo.executeBar"), &cbor);
210   cbor::EncodeString8(SpanFrom("params"), &cbor);
211   cbor::EnvelopeEncoder params_envelope;
212   params_envelope.EncodeStart(&cbor);
213   // The |Dispatchable| class does not parse into the "params" envelope,
214   // so we can stick anything into there for the purpose of this test.
215   // For convenience, we use a String8.
216   cbor::EncodeString8(SpanFrom("params payload"), &cbor);
217   params_envelope.EncodeStop(&cbor);
218   cbor::EncodeString8(SpanFrom("sessionId"), &cbor);
219   cbor::EncodeString8(SpanFrom("f421ssvaz4"), &cbor);
220   cbor.push_back(cbor::EncodeStop());
221   envelope.EncodeStop(&cbor);
222   Dispatchable dispatchable(SpanFrom(cbor));
223   EXPECT_TRUE(dispatchable.ok());
224   EXPECT_TRUE(dispatchable.HasCallId());
225   EXPECT_EQ(42, dispatchable.CallId());
226   EXPECT_EQ("Foo.executeBar", std::string(dispatchable.Method().begin(),
227                                           dispatchable.Method().end()));
228   EXPECT_EQ("f421ssvaz4", std::string(dispatchable.SessionId().begin(),
229                                       dispatchable.SessionId().end()));
230   cbor::CBORTokenizer params_tokenizer(dispatchable.Params());
231   ASSERT_EQ(cbor::CBORTokenTag::ENVELOPE, params_tokenizer.TokenTag());
232   params_tokenizer.EnterEnvelope();
233   ASSERT_EQ(cbor::CBORTokenTag::STRING8, params_tokenizer.TokenTag());
234   EXPECT_EQ("params payload", std::string(params_tokenizer.GetString8().begin(),
235                                           params_tokenizer.GetString8().end()));
236 }
237 
TEST(DispatchableTest,FaultyCBORTrailingJunk)238 TEST(DispatchableTest, FaultyCBORTrailingJunk) {
239   // In addition to the higher level parsing errors, we also catch CBOR
240   // structural corruption. E.g., in this case, the message would be
241   // OK but has some extra trailing bytes.
242   std::vector<uint8_t> cbor;
243   cbor::EnvelopeEncoder envelope;
244   envelope.EncodeStart(&cbor);
245   cbor.push_back(cbor::EncodeIndefiniteLengthMapStart());
246   cbor::EncodeString8(SpanFrom("id"), &cbor);
247   cbor::EncodeInt32(42, &cbor);
248   cbor::EncodeString8(SpanFrom("method"), &cbor);
249   cbor::EncodeString8(SpanFrom("Foo.executeBar"), &cbor);
250   cbor::EncodeString8(SpanFrom("sessionId"), &cbor);
251   cbor::EncodeString8(SpanFrom("f421ssvaz4"), &cbor);
252   cbor.push_back(cbor::EncodeStop());
253   envelope.EncodeStop(&cbor);
254   size_t trailing_junk_pos = cbor.size();
255   cbor.push_back('t');
256   cbor.push_back('r');
257   cbor.push_back('a');
258   cbor.push_back('i');
259   cbor.push_back('l');
260   Dispatchable dispatchable(SpanFrom(cbor));
261   EXPECT_FALSE(dispatchable.ok());
262   EXPECT_EQ(DispatchCode::PARSE_ERROR, dispatchable.DispatchError().Code());
263   EXPECT_EQ(56u, trailing_junk_pos);
264   EXPECT_EQ("CBOR: trailing junk at position 56",
265             dispatchable.DispatchError().Message());
266 }
267 
268 // =============================================================================
269 // Helpers for creating protocol cresponses and notifications.
270 // =============================================================================
TEST(CreateErrorResponseTest,SmokeTest)271 TEST(CreateErrorResponseTest, SmokeTest) {
272   ErrorSupport errors;
273   errors.Push();
274   errors.SetName("foo");
275   errors.Push();
276   errors.SetName("bar");
277   errors.AddError("expected a string");
278   errors.SetName("baz");
279   errors.AddError("expected a surprise");
280   auto serializable = CreateErrorResponse(
281       42, DispatchResponse::InvalidParams("invalid params message"), &errors);
282   std::string json;
283   auto status =
284       json::ConvertCBORToJSON(SpanFrom(serializable->Serialize()), &json);
285   ASSERT_TRUE(status.ok());
286   EXPECT_EQ(
287       "{\"id\":42,\"error\":"
288       "{\"code\":-32602,"
289       "\"message\":\"invalid params message\","
290       "\"data\":\"foo.bar: expected a string; "
291       "foo.baz: expected a surprise\"}}",
292       json);
293 }
294 
TEST(CreateErrorNotificationTest,SmokeTest)295 TEST(CreateErrorNotificationTest, SmokeTest) {
296   auto serializable =
297       CreateErrorNotification(DispatchResponse::InvalidRequest("oops!"));
298   std::string json;
299   auto status =
300       json::ConvertCBORToJSON(SpanFrom(serializable->Serialize()), &json);
301   ASSERT_TRUE(status.ok());
302   EXPECT_EQ("{\"error\":{\"code\":-32600,\"message\":\"oops!\"}}", json);
303 }
304 
TEST(CreateResponseTest,SmokeTest)305 TEST(CreateResponseTest, SmokeTest) {
306   auto serializable = CreateResponse(42, nullptr);
307   std::string json;
308   auto status =
309       json::ConvertCBORToJSON(SpanFrom(serializable->Serialize()), &json);
310   ASSERT_TRUE(status.ok());
311   EXPECT_EQ("{\"id\":42,\"result\":{}}", json);
312 }
313 
TEST(CreateNotificationTest,SmokeTest)314 TEST(CreateNotificationTest, SmokeTest) {
315   auto serializable = CreateNotification("Foo.bar");
316   std::string json;
317   auto status =
318       json::ConvertCBORToJSON(SpanFrom(serializable->Serialize()), &json);
319   ASSERT_TRUE(status.ok());
320   EXPECT_EQ("{\"method\":\"Foo.bar\",\"params\":{}}", json);
321 }
322 
323 // =============================================================================
324 // UberDispatcher - dispatches between domains (backends).
325 // =============================================================================
326 class TestChannel : public FrontendChannel {
327  public:
JSON() const328   std::string JSON() const {
329     std::string json;
330     json::ConvertCBORToJSON(SpanFrom(cbor_), &json);
331     return json;
332   }
333 
334  private:
SendProtocolResponse(int call_id,std::unique_ptr<Serializable> message)335   void SendProtocolResponse(int call_id,
336                             std::unique_ptr<Serializable> message) override {
337     cbor_ = message->Serialize();
338   }
339 
SendProtocolNotification(std::unique_ptr<Serializable> message)340   void SendProtocolNotification(
341       std::unique_ptr<Serializable> message) override {
342     cbor_ = message->Serialize();
343   }
344 
FallThrough(int call_id,span<uint8_t> method,span<uint8_t> message)345   void FallThrough(int call_id,
346                    span<uint8_t> method,
347                    span<uint8_t> message) override {}
348 
FlushProtocolNotifications()349   void FlushProtocolNotifications() override {}
350 
351   std::vector<uint8_t> cbor_;
352 };
353 
TEST(UberDispatcherTest,MethodNotFound)354 TEST(UberDispatcherTest, MethodNotFound) {
355   // No domain dispatchers are registered, so unsuprisingly, we'll get a method
356   // not found error and can see that DispatchResult::MethodFound() yields
357   // false.
358   TestChannel channel;
359   UberDispatcher dispatcher(&channel);
360   std::vector<uint8_t> message;
361   json::ConvertJSONToCBOR(SpanFrom("{\"id\":42,\"method\":\"Foo.bar\"}"),
362                           &message);
363   Dispatchable dispatchable(SpanFrom(message));
364   ASSERT_TRUE(dispatchable.ok());
365   UberDispatcher::DispatchResult dispatched = dispatcher.Dispatch(dispatchable);
366   EXPECT_FALSE(dispatched.MethodFound());
367   dispatched.Run();
368   EXPECT_EQ(
369       "{\"id\":42,\"error\":"
370       "{\"code\":-32601,\"message\":\"'Foo.bar' wasn't found\"}}",
371       channel.JSON());
372 }
373 
374 // A domain dispatcher which captured dispatched and executed commands in fields
375 // for testing.
376 class TestDomain : public DomainDispatcher {
377  public:
TestDomain(FrontendChannel * channel)378   explicit TestDomain(FrontendChannel* channel) : DomainDispatcher(channel) {}
379 
Dispatch(span<uint8_t> command_name)380   std::function<void(const Dispatchable&)> Dispatch(
381       span<uint8_t> command_name) override {
382     dispatched_commands_.push_back(
383         std::string(command_name.begin(), command_name.end()));
384     return [this](const Dispatchable& dispatchable) {
385       executed_commands_.push_back(dispatchable.CallId());
386     };
387   }
388 
389   // Command names of the dispatched commands.
DispatchedCommands() const390   std::vector<std::string> DispatchedCommands() const {
391     return dispatched_commands_;
392   }
393 
394   // Call ids of the executed commands.
ExecutedCommands() const395   std::vector<int32_t> ExecutedCommands() const { return executed_commands_; }
396 
397  private:
398   std::vector<std::string> dispatched_commands_;
399   std::vector<int32_t> executed_commands_;
400 };
401 
TEST(UberDispatcherTest,DispatchingToDomainWithRedirects)402 TEST(UberDispatcherTest, DispatchingToDomainWithRedirects) {
403   // This time, we register two domain dispatchers (Foo and Bar) and issue one
404   // command 'Foo.execute' which executes on Foo and one command 'Foo.redirect'
405   // which executes as 'Bar.redirected'.
406   TestChannel channel;
407   UberDispatcher dispatcher(&channel);
408   auto foo_dispatcher = std::make_unique<TestDomain>(&channel);
409   TestDomain* foo = foo_dispatcher.get();
410   auto bar_dispatcher = std::make_unique<TestDomain>(&channel);
411   TestDomain* bar = bar_dispatcher.get();
412 
413   dispatcher.WireBackend(
414       SpanFrom("Foo"), {{SpanFrom("Foo.redirect"), SpanFrom("Bar.redirected")}},
415       std::move(foo_dispatcher));
416   dispatcher.WireBackend(SpanFrom("Bar"), {}, std::move(bar_dispatcher));
417 
418   {
419     std::vector<uint8_t> message;
420     json::ConvertJSONToCBOR(SpanFrom("{\"id\":42,\"method\":\"Foo.execute\"}"),
421                             &message);
422     Dispatchable dispatchable(SpanFrom(message));
423     ASSERT_TRUE(dispatchable.ok());
424     UberDispatcher::DispatchResult dispatched =
425         dispatcher.Dispatch(dispatchable);
426     EXPECT_TRUE(dispatched.MethodFound());
427     dispatched.Run();
428   }
429   {
430     std::vector<uint8_t> message;
431     json::ConvertJSONToCBOR(SpanFrom("{\"id\":43,\"method\":\"Foo.redirect\"}"),
432                             &message);
433     Dispatchable dispatchable(SpanFrom(message));
434     ASSERT_TRUE(dispatchable.ok());
435     UberDispatcher::DispatchResult dispatched =
436         dispatcher.Dispatch(dispatchable);
437     EXPECT_TRUE(dispatched.MethodFound());
438     dispatched.Run();
439   }
440   EXPECT_THAT(foo->DispatchedCommands(), testing::ElementsAre("execute"));
441   EXPECT_THAT(foo->ExecutedCommands(), testing::ElementsAre(42));
442   EXPECT_THAT(bar->DispatchedCommands(), testing::ElementsAre("redirected"));
443   EXPECT_THAT(bar->ExecutedCommands(), testing::ElementsAre(43));
444 }
445 }  // namespace crdtp
446