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