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 
23 using testing::_;
24 using testing::AtLeast;
25 using testing::AtMost;
26 using testing::HasSubstr;
27 using testing::InSequence;
28 using testing::Invoke;
29 using testing::InvokeWithoutArgs;
30 using testing::MatchesRegex;
31 using testing::Mock;
32 using testing::Return;
33 using testing::ReturnRefOfCopy;
34 using testing::StartsWith;
35 using testing::StrictMock;
36 using testing::WithArgs;
37 
38 namespace weave {
39 
40 namespace {
41 
42 using provider::HttpClient;
43 using provider::Network;
44 using provider::test::MockHttpClientResponse;
45 using test::CreateDictionaryValue;
46 using test::ValueToString;
47 
48 const 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 
71 const 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 
135 const 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 
145 const 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 
157 const char kAuthTokenResponse[] = R"({
158   "access_token" : "ACCESS_TOKEN",
159   "token_type" : "Bearer",
160   "expires_in" : 3599,
161   "refresh_token" : "REFRESH_TOKEN"
162 })";
163 
164 MATCHER_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 
172 template <class Map>
GetKeys(const Map & map)173 std::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 
182 class WeaveTest : public ::testing::Test {
183  protected:
SetUp()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>
ExpectRequest(HttpClient::Method method,const UrlMatcher & url_matcher,const std::string & json_response)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 
InitNetwork()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 
InitDnsSd()220   void InitDnsSd() {
221     EXPECT_CALL(dns_sd_, PublishService(_, _, _)).WillRepeatedly(Return());
222     EXPECT_CALL(dns_sd_, StopPublishing("_privet._tcp")).WillOnce(Return());
223   }
224 
InitDnsSdPublishing(bool registered,const std::string & flags)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 
InitHttpServer()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 
InitDefaultExpectations()268   void InitDefaultExpectations() {
269     InitNetwork();
270     EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv")))
271         .WillOnce(Return());
272     InitHttpServer();
273     InitDnsSd();
274   }
275 
StartDevice()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 
NotifyNetworkChanged(provider::Network::State state,base::TimeDelta delay)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 
TEST_F(WeaveTest,Mocks)353 TEST_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 
TEST_F(WeaveTest,StartMinimal)360 TEST_F(WeaveTest, StartMinimal) {
361   device_ = weave::Device::Create(&config_store_, &task_runner_, &http_client_,
362                                   &network_, nullptr, nullptr, &wifi_, nullptr);
363 }
364 
TEST_F(WeaveTest,StartNoWifi)365 TEST_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 
381 class WeaveBasicTest : public WeaveTest {
382  public:
SetUp()383   void SetUp() override {
384     WeaveTest::SetUp();
385 
386     InitDefaultExpectations();
387     InitDnsSdPublishing(false, "DB");
388   }
389 };
390 
TEST_F(WeaveBasicTest,Start)391 TEST_F(WeaveBasicTest, Start) {
392   StartDevice();
393 }
394 
TEST_F(WeaveBasicTest,Register)395 TEST_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 
444 class WeaveWiFiSetupTest : public WeaveTest {
445  public:
SetUp()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 
TEST_F(WeaveWiFiSetupTest,StartOnlineNoPrevSsid)458 TEST_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.
TEST_F(WeaveWiFiSetupTest,StartOnlineWithPrevSsid)481 TEST_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 
TEST_F(WeaveWiFiSetupTest,StartOfflineWithSsid)515 TEST_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 
TEST_F(WeaveWiFiSetupTest,OfflineLongTimeWithNoSsid)532 TEST_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 
TEST_F(WeaveWiFiSetupTest,OfflineLongTimeWithSsid)561 TEST_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