1// Copyright 2015 The Weave 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 <weave/device.h>
6
7#include <gmock/gmock.h>
8#include <gtest/gtest.h>
9#include <weave/provider/test/fake_task_runner.h>
10#include <weave/provider/test/mock_bluetooth.h>
11#include <weave/provider/test/mock_config_store.h>
12#include <weave/provider/test/mock_dns_service_discovery.h>
13#include <weave/provider/test/mock_http_client.h>
14#include <weave/provider/test/mock_http_server.h>
15#include <weave/provider/test/mock_network.h>
16#include <weave/provider/test/mock_wifi.h>
17#include <weave/test/mock_command.h>
18#include <weave/test/mock_device.h>
19#include <weave/test/unittest_utils.h>
20
21#include "src/bind_lambda.h"
22
23using testing::_;
24using testing::AtLeast;
25using testing::AtMost;
26using testing::HasSubstr;
27using testing::InSequence;
28using testing::Invoke;
29using testing::InvokeWithoutArgs;
30using testing::MatchesRegex;
31using testing::Mock;
32using testing::Return;
33using testing::ReturnRefOfCopy;
34using testing::StartsWith;
35using testing::StrictMock;
36using testing::WithArgs;
37
38namespace weave {
39
40namespace {
41
42using provider::HttpClient;
43using provider::Network;
44using provider::test::MockHttpClientResponse;
45using test::CreateDictionaryValue;
46using test::ValueToString;
47
48const char kTraitDefs[] = R"({
49  "trait1": {
50    "commands": {
51      "reboot": {
52        "minimalRole": "user"
53      },
54      "shutdown": {
55        "minimalRole": "user",
56        "parameters": {},
57        "results": {}
58      }
59    },
60    "state": {
61      "firmwareVersion": {"type": "string"}
62    }
63  },
64  "trait2": {
65    "state": {
66      "battery_level": {"type": "integer"}
67    }
68  }
69})";
70
71const char kDeviceResource[] = R"({
72  "kind": "weave#device",
73  "id": "CLOUD_ID",
74  "channel": {
75    "supportedType": "pull"
76  },
77  "deviceKind": "vendor",
78  "modelManifestId": "ABCDE",
79  "systemName": "",
80  "name": "TEST_NAME",
81  "displayName": "",
82  "description": "Developer device",
83  "stateValidationEnabled": true,
84  "commandDefs":{
85    "trait1": {
86      "reboot": {
87        "minimalRole": "user",
88        "parameters": {"delay": {"type": "integer"}},
89        "results": {}
90      },
91      "shutdown": {
92        "minimalRole": "user",
93        "parameters": {},
94        "results": {}
95      }
96    }
97  },
98  "state":{
99    "trait1": {"firmwareVersion":"FIRMWARE_VERSION"},
100    "trait2": {"battery_level":44}
101  },
102  "traits": {
103    "trait1": {
104      "commands": {
105        "reboot": {
106          "minimalRole": "user"
107        },
108        "shutdown": {
109          "minimalRole": "user",
110          "parameters": {},
111          "results": {}
112        }
113      },
114      "state": {
115        "firmwareVersion": {"type": "string"}
116      }
117    },
118    "trait2": {
119      "state": {
120        "battery_level": {"type": "integer"}
121      }
122    }
123  },
124  "components": {
125    "myComponent": {
126      "traits": ["trait1", "trait2"],
127      "state": {
128        "trait1": {"firmwareVersion":"FIRMWARE_VERSION"},
129        "trait2": {"battery_level":44}
130      }
131    }
132  }
133})";
134
135const char kRegistrationResponse[] = R"({
136  "kind": "weave#registrationTicket",
137  "id": "TICKET_ID",
138  "deviceId": "CLOUD_ID",
139  "oauthClientId": "CLIENT_ID",
140  "userEmail": "USER@gmail.com",
141  "creationTimeMs": "1440087183738",
142  "expirationTimeMs": "1440087423738"
143})";
144
145const char kRegistrationFinalResponse[] = R"({
146  "kind": "weave#registrationTicket",
147  "id": "TICKET_ID",
148  "deviceId": "CLOUD_ID",
149  "oauthClientId": "CLIENT_ID",
150  "userEmail": "USER@gmail.com",
151  "robotAccountEmail": "ROBO@gmail.com",
152  "robotAccountAuthorizationCode": "AUTH_CODE",
153  "creationTimeMs": "1440087183738",
154  "expirationTimeMs": "1440087423738"
155})";
156
157const char kAuthTokenResponse[] = R"({
158  "access_token" : "ACCESS_TOKEN",
159  "token_type" : "Bearer",
160  "expires_in" : 3599,
161  "refresh_token" : "REFRESH_TOKEN"
162})";
163
164MATCHER_P(MatchTxt, txt, "") {
165  std::vector<std::string> txt_copy = txt;
166  std::sort(txt_copy.begin(), txt_copy.end());
167  std::vector<std::string> arg_copy = arg;
168  std::sort(arg_copy.begin(), arg_copy.end());
169  return (arg_copy == txt_copy);
170}
171
172template <class Map>
173std::set<typename Map::key_type> GetKeys(const Map& map) {
174  std::set<typename Map::key_type> result;
175  for (const auto& pair : map)
176    result.insert(pair.first);
177  return result;
178}
179
180}  // namespace
181
182class WeaveTest : public ::testing::Test {
183 protected:
184  void SetUp() override {
185    EXPECT_CALL(wifi_, IsWifi24Supported()).WillRepeatedly(Return(true));
186    EXPECT_CALL(wifi_, IsWifi50Supported()).WillRepeatedly(Return(false));
187  }
188
189  template <class UrlMatcher>
190  void ExpectRequest(HttpClient::Method method,
191                     const UrlMatcher& url_matcher,
192                     const std::string& json_response) {
193    EXPECT_CALL(http_client_, SendRequest(method, url_matcher, _, _, _))
194        .WillOnce(WithArgs<4>(Invoke(
195            [json_response](const HttpClient::SendRequestCallback& callback) {
196              std::unique_ptr<provider::test::MockHttpClientResponse> response{
197                  new StrictMock<provider::test::MockHttpClientResponse>};
198              EXPECT_CALL(*response, GetStatusCode())
199                  .Times(AtLeast(1))
200                  .WillRepeatedly(Return(200));
201              EXPECT_CALL(*response, GetContentType())
202                  .Times(AtLeast(1))
203                  .WillRepeatedly(Return("application/json; charset=utf-8"));
204              EXPECT_CALL(*response, GetData())
205                  .WillRepeatedly(Return(json_response));
206              callback.Run(std::move(response), nullptr);
207            })));
208  }
209
210  void InitNetwork() {
211    EXPECT_CALL(network_, AddConnectionChangedCallback(_))
212        .WillRepeatedly(Invoke(
213            [this](const provider::Network::ConnectionChangedCallback& cb) {
214              network_callbacks_.push_back(cb);
215            }));
216    EXPECT_CALL(network_, GetConnectionState())
217        .WillRepeatedly(Return(Network::State::kOffline));
218  }
219
220  void InitDnsSd() {
221    EXPECT_CALL(dns_sd_, PublishService(_, _, _)).WillRepeatedly(Return());
222    EXPECT_CALL(dns_sd_, StopPublishing("_privet._tcp")).WillOnce(Return());
223  }
224
225  void InitDnsSdPublishing(bool registered, const std::string& flags) {
226    std::vector<std::string> txt{
227        {"id=TEST_DEVICE_ID"},         {"flags=" + flags}, {"mmid=ABCDE"},
228        {"services=developmentBoard"}, {"txtvers=3"},      {"ty=TEST_NAME"}};
229    if (registered) {
230      txt.push_back("gcd_id=CLOUD_ID");
231
232      // During registration device may announce itself twice:
233      // 1. with GCD ID but not connected (DB)
234      // 2. with GCD ID and connected (BB)
235      EXPECT_CALL(dns_sd_, PublishService("_privet._tcp", 11, MatchTxt(txt)))
236          .Times(AtMost(1))
237          .WillOnce(Return());
238
239      txt[1] = "flags=BB";
240    }
241
242    EXPECT_CALL(dns_sd_, PublishService("_privet._tcp", 11, MatchTxt(txt)))
243        .Times(AtMost(1))
244        .WillOnce(Return());
245  }
246
247  void InitHttpServer() {
248    EXPECT_CALL(http_server_, GetHttpPort()).WillRepeatedly(Return(11));
249    EXPECT_CALL(http_server_, GetHttpsPort()).WillRepeatedly(Return(12));
250    EXPECT_CALL(http_server_, GetRequestTimeout())
251        .WillRepeatedly(Return(base::TimeDelta::Max()));
252    EXPECT_CALL(http_server_, GetHttpsCertificateFingerprint())
253        .WillRepeatedly(Return(std::vector<uint8_t>{1, 2, 3}));
254    EXPECT_CALL(http_server_, AddHttpRequestHandler(_, _))
255        .WillRepeatedly(Invoke(
256            [this](const std::string& path_prefix,
257                   const provider::HttpServer::RequestHandlerCallback& cb) {
258              http_handlers_[path_prefix] = cb;
259            }));
260    EXPECT_CALL(http_server_, AddHttpsRequestHandler(_, _))
261        .WillRepeatedly(Invoke(
262            [this](const std::string& path_prefix,
263                   const provider::HttpServer::RequestHandlerCallback& cb) {
264              https_handlers_[path_prefix] = cb;
265            }));
266  }
267
268  void InitDefaultExpectations() {
269    InitNetwork();
270    EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv")))
271        .WillOnce(Return());
272    InitHttpServer();
273    InitDnsSd();
274  }
275
276  void StartDevice() {
277    device_ = weave::Device::Create(&config_store_, &task_runner_,
278                                    &http_client_, &network_, &dns_sd_,
279                                    &http_server_, &wifi_, &bluetooth_);
280
281    EXPECT_EQ((std::set<std::string>{
282                  // clang-format off
283                  "/privet/info",
284                  "/privet/v3/pairing/cancel",
285                  "/privet/v3/pairing/confirm",
286                  "/privet/v3/pairing/start",
287                  // clang-format on
288              }),
289              GetKeys(http_handlers_));
290    EXPECT_EQ((std::set<std::string>{
291                  // clang-format off
292                  "/privet/info",
293                  "/privet/v3/accessControl/claim",
294                  "/privet/v3/accessControl/confirm",
295                  "/privet/v3/auth",
296                  "/privet/v3/checkForUpdates",
297                  "/privet/v3/commandDefs",
298                  "/privet/v3/commands/cancel",
299                  "/privet/v3/commands/execute",
300                  "/privet/v3/commands/list",
301                  "/privet/v3/commands/status",
302                  "/privet/v3/components",
303                  "/privet/v3/pairing/cancel",
304                  "/privet/v3/pairing/confirm",
305                  "/privet/v3/pairing/start",
306                  "/privet/v3/setup/start",
307                  "/privet/v3/setup/status",
308                  "/privet/v3/state",
309                  "/privet/v3/traits",
310                  // clang-format on
311              }),
312              GetKeys(https_handlers_));
313
314    device_->AddTraitDefinitionsFromJson(kTraitDefs);
315    EXPECT_TRUE(
316        device_->AddComponent("myComponent", {"trait1", "trait2"}, nullptr));
317    EXPECT_TRUE(device_->SetStatePropertiesFromJson(
318        "myComponent", R"({"trait2": {"battery_level":44}})", nullptr));
319
320    task_runner_.Run();
321  }
322
323  void NotifyNetworkChanged(provider::Network::State state,
324                            base::TimeDelta delay) {
325    auto task = [this, state] {
326      EXPECT_CALL(network_, GetConnectionState()).WillRepeatedly(Return(state));
327      for (const auto& cb : network_callbacks_)
328        cb.Run();
329    };
330
331    task_runner_.PostDelayedTask(FROM_HERE, base::Bind(task), delay);
332  }
333
334  std::map<std::string, provider::HttpServer::RequestHandlerCallback>
335      http_handlers_;
336  std::map<std::string, provider::HttpServer::RequestHandlerCallback>
337      https_handlers_;
338
339  StrictMock<provider::test::MockConfigStore> config_store_;
340  StrictMock<provider::test::FakeTaskRunner> task_runner_;
341  StrictMock<provider::test::MockHttpClient> http_client_;
342  StrictMock<provider::test::MockNetwork> network_;
343  StrictMock<provider::test::MockDnsServiceDiscovery> dns_sd_;
344  StrictMock<provider::test::MockHttpServer> http_server_;
345  StrictMock<provider::test::MockWifi> wifi_;
346  StrictMock<provider::test::MockBluetooth> bluetooth_;
347
348  std::vector<provider::Network::ConnectionChangedCallback> network_callbacks_;
349
350  std::unique_ptr<weave::Device> device_;
351};
352
353TEST_F(WeaveTest, Mocks) {
354  // Test checks if mock implements entire interface and mock can be
355  // instantiated.
356  test::MockDevice device;
357  test::MockCommand command;
358}
359
360TEST_F(WeaveTest, StartMinimal) {
361  device_ = weave::Device::Create(&config_store_, &task_runner_, &http_client_,
362                                  &network_, nullptr, nullptr, &wifi_, nullptr);
363}
364
365TEST_F(WeaveTest, StartNoWifi) {
366  InitNetwork();
367  InitHttpServer();
368  InitDnsSd();
369  InitDnsSdPublishing(false, "CB");
370
371  device_ = weave::Device::Create(&config_store_, &task_runner_, &http_client_,
372                                  &network_, &dns_sd_, &http_server_, nullptr,
373                                  &bluetooth_);
374  device_->AddTraitDefinitionsFromJson(kTraitDefs);
375  EXPECT_TRUE(
376      device_->AddComponent("myComponent", {"trait1", "trait2"}, nullptr));
377
378  task_runner_.Run();
379}
380
381class WeaveBasicTest : public WeaveTest {
382 public:
383  void SetUp() override {
384    WeaveTest::SetUp();
385
386    InitDefaultExpectations();
387    InitDnsSdPublishing(false, "DB");
388  }
389};
390
391TEST_F(WeaveBasicTest, Start) {
392  StartDevice();
393}
394
395TEST_F(WeaveBasicTest, Register) {
396  EXPECT_CALL(network_, OpenSslSocket(_, _, _)).WillRepeatedly(Return());
397  StartDevice();
398
399  auto draft = CreateDictionaryValue(kDeviceResource);
400  auto response = CreateDictionaryValue(kRegistrationResponse);
401  response->Set("deviceDraft", draft->DeepCopy());
402  ExpectRequest(HttpClient::Method::kPatch,
403                "https://www.googleapis.com/weave/v1/registrationTickets/"
404                "TICKET_ID?key=TEST_API_KEY",
405                ValueToString(*response));
406
407  response = CreateDictionaryValue(kRegistrationFinalResponse);
408  response->Set("deviceDraft", draft->DeepCopy());
409  ExpectRequest(HttpClient::Method::kPost,
410                "https://www.googleapis.com/weave/v1/registrationTickets/"
411                "TICKET_ID/finalize?key=TEST_API_KEY",
412                ValueToString(*response));
413
414  ExpectRequest(HttpClient::Method::kPost,
415                "https://accounts.google.com/o/oauth2/token",
416                kAuthTokenResponse);
417
418  ExpectRequest(HttpClient::Method::kPost, HasSubstr("upsertLocalAuthInfo"),
419                {});
420
421  InitDnsSdPublishing(true, "DB");
422
423  bool done = false;
424  device_->Register("TICKET_ID", base::Bind([this, &done](ErrorPtr error) {
425                      EXPECT_FALSE(error);
426                      done = true;
427                      task_runner_.Break();
428                      EXPECT_EQ("CLOUD_ID", device_->GetSettings().cloud_id);
429                    }));
430  task_runner_.Run();
431  EXPECT_TRUE(done);
432
433  done = false;
434  device_->Register("TICKET_ID2", base::Bind([this, &done](ErrorPtr error) {
435                      EXPECT_TRUE(error->HasError("already_registered"));
436                      done = true;
437                      task_runner_.Break();
438                      EXPECT_EQ("CLOUD_ID", device_->GetSettings().cloud_id);
439                    }));
440  task_runner_.Run();
441  EXPECT_TRUE(done);
442}
443
444class WeaveWiFiSetupTest : public WeaveTest {
445 public:
446  void SetUp() override {
447    WeaveTest::SetUp();
448
449    InitHttpServer();
450    InitNetwork();
451    InitDnsSd();
452
453    EXPECT_CALL(network_, GetConnectionState())
454        .WillRepeatedly(Return(provider::Network::State::kOnline));
455  }
456};
457
458TEST_F(WeaveWiFiSetupTest, StartOnlineNoPrevSsid) {
459  StartDevice();
460
461  // Short disconnect.
462  NotifyNetworkChanged(provider::Network::State::kOffline, {});
463  NotifyNetworkChanged(provider::Network::State::kOnline,
464                       base::TimeDelta::FromSeconds(10));
465  task_runner_.Run();
466
467  // Long disconnect.
468  NotifyNetworkChanged(Network::State::kOffline, {});
469  auto offline_from = task_runner_.GetClock()->Now();
470  EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv")))
471      .WillOnce(InvokeWithoutArgs([this, offline_from]() {
472        EXPECT_GT(task_runner_.GetClock()->Now() - offline_from,
473                  base::TimeDelta::FromMinutes(1));
474        task_runner_.Break();
475      }));
476  task_runner_.Run();
477}
478
479// If device has previously configured WiFi it will run AP for limited time
480// after which it will try to re-connect.
481TEST_F(WeaveWiFiSetupTest, StartOnlineWithPrevSsid) {
482  EXPECT_CALL(config_store_, LoadSettings())
483      .WillRepeatedly(Return(R"({"last_configured_ssid": "TEST_ssid"})"));
484  StartDevice();
485
486  // Long disconnect.
487  NotifyNetworkChanged(Network::State::kOffline, {});
488
489  for (int i = 0; i < 5; ++i) {
490    auto offline_from = task_runner_.GetClock()->Now();
491    // Temporarily offline mode.
492    EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv")))
493        .WillOnce(InvokeWithoutArgs([this, &offline_from]() {
494          EXPECT_GT(task_runner_.GetClock()->Now() - offline_from,
495                    base::TimeDelta::FromMinutes(1));
496          task_runner_.Break();
497        }));
498    task_runner_.Run();
499
500    // Try to reconnect again.
501    offline_from = task_runner_.GetClock()->Now();
502    EXPECT_CALL(wifi_, StopAccessPoint())
503        .WillOnce(InvokeWithoutArgs([this, offline_from]() {
504          EXPECT_GT(task_runner_.GetClock()->Now() - offline_from,
505                    base::TimeDelta::FromMinutes(5));
506          task_runner_.Break();
507        }));
508    task_runner_.Run();
509  }
510
511  NotifyNetworkChanged(Network::State::kOnline, {});
512  task_runner_.Run();
513}
514
515TEST_F(WeaveWiFiSetupTest, StartOfflineWithSsid) {
516  EXPECT_CALL(config_store_, LoadSettings())
517      .WillRepeatedly(Return(R"({"last_configured_ssid": "TEST_ssid"})"));
518  EXPECT_CALL(network_, GetConnectionState())
519      .WillRepeatedly(Return(Network::State::kOffline));
520
521  auto offline_from = task_runner_.GetClock()->Now();
522  EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv")))
523      .WillOnce(InvokeWithoutArgs([this, &offline_from]() {
524        EXPECT_GT(task_runner_.GetClock()->Now() - offline_from,
525                  base::TimeDelta::FromMinutes(1));
526        task_runner_.Break();
527      }));
528
529  StartDevice();
530}
531
532TEST_F(WeaveWiFiSetupTest, OfflineLongTimeWithNoSsid) {
533  EXPECT_CALL(network_, GetConnectionState())
534      .WillRepeatedly(Return(Network::State::kOffline));
535  NotifyNetworkChanged(provider::Network::State::kOnline,
536                       base::TimeDelta::FromHours(15));
537
538  {
539    InSequence s;
540    auto time_stamp = task_runner_.GetClock()->Now();
541
542    EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv")))
543        .WillOnce(InvokeWithoutArgs([this, &time_stamp]() {
544          EXPECT_LE(task_runner_.GetClock()->Now() - time_stamp,
545                    base::TimeDelta::FromMinutes(1));
546          time_stamp = task_runner_.GetClock()->Now();
547        }));
548
549    EXPECT_CALL(wifi_, StopAccessPoint())
550        .WillOnce(InvokeWithoutArgs([this, &time_stamp]() {
551          EXPECT_GT(task_runner_.GetClock()->Now() - time_stamp,
552                    base::TimeDelta::FromMinutes(5));
553          time_stamp = task_runner_.GetClock()->Now();
554          task_runner_.Break();
555        }));
556  }
557
558  StartDevice();
559}
560
561TEST_F(WeaveWiFiSetupTest, OfflineLongTimeWithSsid) {
562  EXPECT_CALL(config_store_, LoadSettings())
563      .WillRepeatedly(Return(R"({"last_configured_ssid": "TEST_ssid"})"));
564  EXPECT_CALL(network_, GetConnectionState())
565      .WillRepeatedly(Return(Network::State::kOffline));
566  NotifyNetworkChanged(provider::Network::State::kOnline,
567                       base::TimeDelta::FromHours(15));
568
569  {
570    InSequence s;
571    auto time_stamp = task_runner_.GetClock()->Now();
572    for (size_t i = 0; i < 10; ++i) {
573      EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv")))
574          .WillOnce(InvokeWithoutArgs([this, &time_stamp]() {
575            EXPECT_GT(task_runner_.GetClock()->Now() - time_stamp,
576                      base::TimeDelta::FromMinutes(1));
577            time_stamp = task_runner_.GetClock()->Now();
578          }));
579
580      EXPECT_CALL(wifi_, StopAccessPoint())
581          .WillOnce(InvokeWithoutArgs([this, &time_stamp]() {
582            EXPECT_GT(task_runner_.GetClock()->Now() - time_stamp,
583                      base::TimeDelta::FromMinutes(5));
584            time_stamp = task_runner_.GetClock()->Now();
585          }));
586    }
587
588    EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv")))
589        .WillOnce(InvokeWithoutArgs([this]() { task_runner_.Break(); }));
590  }
591
592  StartDevice();
593}
594
595}  // namespace weave
596