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