| // Copyright 2015 The Weave Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "src/privet/wifi_bootstrap_manager.h" | 
 |  | 
 | #include <base/logging.h> | 
 | #include <base/memory/weak_ptr.h> | 
 | #include <weave/enum_to_string.h> | 
 | #include <weave/provider/network.h> | 
 | #include <weave/provider/task_runner.h> | 
 | #include <weave/provider/wifi.h> | 
 |  | 
 | #include "src/bind_lambda.h" | 
 | #include "src/privet/constants.h" | 
 | #include "src/config.h" | 
 |  | 
 | namespace weave { | 
 | namespace privet { | 
 |  | 
 | namespace { | 
 |  | 
 | const int kMonitoringWithSsidTimeoutSeconds = 15; | 
 | const int kMonitoringTimeoutSeconds = 120; | 
 | const int kBootstrapTimeoutSeconds = 600; | 
 | const int kConnectingTimeoutSeconds = 180; | 
 |  | 
 | const EnumToStringMap<WifiBootstrapManager::State>::Map kWifiSetupStateMap[] = { | 
 |     {WifiBootstrapManager::State::kDisabled, "disabled"}, | 
 |     {WifiBootstrapManager::State::kBootstrapping, "waiting"}, | 
 |     {WifiBootstrapManager::State::kMonitoring, "monitoring"}, | 
 |     {WifiBootstrapManager::State::kConnecting, "connecting"}, | 
 | }; | 
 | } | 
 |  | 
 | using provider::Network; | 
 |  | 
 | WifiBootstrapManager::WifiBootstrapManager(Config* config, | 
 |                                            provider::TaskRunner* task_runner, | 
 |                                            provider::Network* network, | 
 |                                            provider::Wifi* wifi, | 
 |                                            CloudDelegate* gcd) | 
 |     : config_{config}, | 
 |       task_runner_{task_runner}, | 
 |       network_{network}, | 
 |       wifi_{wifi}, | 
 |       ssid_generator_{gcd, this} { | 
 |   CHECK(config_); | 
 |   CHECK(network_); | 
 |   CHECK(task_runner_); | 
 |   CHECK(wifi_); | 
 | } | 
 |  | 
 | void WifiBootstrapManager::Init() { | 
 |   UpdateConnectionState(); | 
 |   network_->AddConnectionChangedCallback( | 
 |       base::Bind(&WifiBootstrapManager::OnConnectivityChange, | 
 |                  lifetime_weak_factory_.GetWeakPtr())); | 
 |   if (config_->GetSettings().last_configured_ssid.empty()) { | 
 |     // Give implementation some time to figure out state. | 
 |     StartMonitoring( | 
 |         base::TimeDelta::FromSeconds(kMonitoringWithSsidTimeoutSeconds)); | 
 |   } else { | 
 |     StartMonitoring(base::TimeDelta::FromSeconds(kMonitoringTimeoutSeconds)); | 
 |   } | 
 | } | 
 |  | 
 | void WifiBootstrapManager::StartBootstrapping() { | 
 |   if (network_->GetConnectionState() == Network::State::kOnline) { | 
 |     // If one of the devices we monitor for connectivity is online, we need not | 
 |     // start an AP.  For most devices, this is a situation which happens in | 
 |     // testing when we have an ethernet connection.  If you need to always | 
 |     // start an AP to bootstrap WiFi credentials, then add your WiFi interface | 
 |     // to the device whitelist. | 
 |     StartMonitoring(base::TimeDelta::FromSeconds(kMonitoringTimeoutSeconds)); | 
 |     return; | 
 |   } | 
 |  | 
 |   UpdateState(State::kBootstrapping); | 
 |   if (!config_->GetSettings().last_configured_ssid.empty()) { | 
 |     // If we have been configured before, we'd like to periodically take down | 
 |     // our AP and find out if we can connect again.  Many kinds of failures are | 
 |     // transient, and having an AP up prohibits us from connecting as a client. | 
 |     task_runner_->PostDelayedTask( | 
 |         FROM_HERE, base::Bind(&WifiBootstrapManager::OnBootstrapTimeout, | 
 |                               tasks_weak_factory_.GetWeakPtr()), | 
 |         base::TimeDelta::FromSeconds(kBootstrapTimeoutSeconds)); | 
 |   } | 
 |   // TODO(vitalybuka): Add SSID probing. | 
 |   privet_ssid_ = GenerateSsid(); | 
 |   CHECK(!privet_ssid_.empty()); | 
 |  | 
 |   VLOG(1) << "Starting AP with SSID: " << privet_ssid_; | 
 |   wifi_->StartAccessPoint(privet_ssid_); | 
 | } | 
 |  | 
 | void WifiBootstrapManager::EndBootstrapping() { | 
 |   VLOG(1) << "Stopping AP"; | 
 |   wifi_->StopAccessPoint(); | 
 |   privet_ssid_.clear(); | 
 | } | 
 |  | 
 | void WifiBootstrapManager::StartConnecting(const std::string& ssid, | 
 |                                            const std::string& passphrase) { | 
 |   VLOG(1) << "Attempting connect to SSID:" << ssid; | 
 |   UpdateState(State::kConnecting); | 
 |   task_runner_->PostDelayedTask( | 
 |       FROM_HERE, base::Bind(&WifiBootstrapManager::OnConnectTimeout, | 
 |                             tasks_weak_factory_.GetWeakPtr()), | 
 |       base::TimeDelta::FromSeconds(kConnectingTimeoutSeconds)); | 
 |   wifi_->Connect(ssid, passphrase, | 
 |                  base::Bind(&WifiBootstrapManager::OnConnectDone, | 
 |                             tasks_weak_factory_.GetWeakPtr(), ssid)); | 
 | } | 
 |  | 
 | void WifiBootstrapManager::EndConnecting() {} | 
 |  | 
 | void WifiBootstrapManager::StartMonitoring(const base::TimeDelta& timeout) { | 
 |   monitor_until_ = {}; | 
 |   ContinueMonitoring(timeout); | 
 | } | 
 |  | 
 | void WifiBootstrapManager::ContinueMonitoring(const base::TimeDelta& timeout) { | 
 |   VLOG(1) << "Monitoring connectivity."; | 
 |   // We already have a callback in place with |network_| to update our | 
 |   // connectivity state.  See OnConnectivityChange(). | 
 |   UpdateState(State::kMonitoring); | 
 |  | 
 |   if (network_->GetConnectionState() == Network::State::kOnline) { | 
 |     monitor_until_ = {}; | 
 |   } else { | 
 |     if (monitor_until_.is_null()) { | 
 |       monitor_until_ = base::Time::Now() + timeout; | 
 |       VLOG(2) << "Waiting for connection until: " << monitor_until_; | 
 |     } | 
 |  | 
 |     // Schedule timeout timer taking into account already offline time. | 
 |     task_runner_->PostDelayedTask( | 
 |         FROM_HERE, base::Bind(&WifiBootstrapManager::OnMonitorTimeout, | 
 |                               tasks_weak_factory_.GetWeakPtr()), | 
 |         monitor_until_ - base::Time::Now()); | 
 |   } | 
 | } | 
 |  | 
 | void WifiBootstrapManager::EndMonitoring() {} | 
 |  | 
 | void WifiBootstrapManager::UpdateState(State new_state) { | 
 |   VLOG(3) << "Switching state from " << EnumToString(state_) << " to " | 
 |           << EnumToString(new_state); | 
 |   // Abort irrelevant tasks. | 
 |   tasks_weak_factory_.InvalidateWeakPtrs(); | 
 |  | 
 |   switch (state_) { | 
 |     case State::kDisabled: | 
 |       break; | 
 |     case State::kBootstrapping: | 
 |       EndBootstrapping(); | 
 |       break; | 
 |     case State::kMonitoring: | 
 |       EndMonitoring(); | 
 |       break; | 
 |     case State::kConnecting: | 
 |       EndConnecting(); | 
 |       break; | 
 |   } | 
 |  | 
 |   state_ = new_state; | 
 | } | 
 |  | 
 | std::string WifiBootstrapManager::GenerateSsid() const { | 
 |   const std::string& ssid = config_->GetSettings().test_privet_ssid; | 
 |   return ssid.empty() ? ssid_generator_.GenerateSsid() : ssid; | 
 | } | 
 |  | 
 | const ConnectionState& WifiBootstrapManager::GetConnectionState() const { | 
 |   return connection_state_; | 
 | } | 
 |  | 
 | const SetupState& WifiBootstrapManager::GetSetupState() const { | 
 |   return setup_state_; | 
 | } | 
 |  | 
 | bool WifiBootstrapManager::ConfigureCredentials(const std::string& ssid, | 
 |                                                 const std::string& passphrase, | 
 |                                                 ErrorPtr* error) { | 
 |   setup_state_ = SetupState{SetupState::kInProgress}; | 
 |   // Since we are changing network, we need to let the web server send out the | 
 |   // response to the HTTP request leading to this action. So, we are waiting | 
 |   // a bit before mocking with network set up. | 
 |   task_runner_->PostDelayedTask( | 
 |       FROM_HERE, base::Bind(&WifiBootstrapManager::StartConnecting, | 
 |                             tasks_weak_factory_.GetWeakPtr(), ssid, passphrase), | 
 |       base::TimeDelta::FromSeconds(1)); | 
 |   return true; | 
 | } | 
 |  | 
 | std::string WifiBootstrapManager::GetCurrentlyConnectedSsid() const { | 
 |   // TODO(vitalybuka): Get from shill, if possible. | 
 |   return config_->GetSettings().last_configured_ssid; | 
 | } | 
 |  | 
 | std::string WifiBootstrapManager::GetHostedSsid() const { | 
 |   return privet_ssid_; | 
 | } | 
 |  | 
 | std::set<WifiType> WifiBootstrapManager::GetTypes() const { | 
 |   // TODO(wiley) This should do some system work to figure this out. | 
 |   return {WifiType::kWifi24}; | 
 | } | 
 |  | 
 | void WifiBootstrapManager::OnConnectDone(const std::string& ssid, | 
 |                                          ErrorPtr error) { | 
 |   if (error) { | 
 |     Error::AddTo(&error, FROM_HERE, errors::kDomain, errors::kInvalidState, | 
 |                  "Failed to connect to provided network"); | 
 |     setup_state_ = SetupState{std::move(error)}; | 
 |     return StartBootstrapping(); | 
 |   } | 
 |   VLOG(1) << "Wifi was connected successfully"; | 
 |   Config::Transaction change{config_}; | 
 |   change.set_last_configured_ssid(ssid); | 
 |   change.Commit(); | 
 |   setup_state_ = SetupState{SetupState::kSuccess}; | 
 |   StartMonitoring(base::TimeDelta::FromSeconds(kMonitoringTimeoutSeconds)); | 
 | } | 
 |  | 
 | void WifiBootstrapManager::OnConnectTimeout() { | 
 |   ErrorPtr error; | 
 |   Error::AddTo(&error, FROM_HERE, errors::kDomain, errors::kInvalidState, | 
 |                "Timeout connecting to provided network"); | 
 |   setup_state_ = SetupState{std::move(error)}; | 
 |   return StartBootstrapping(); | 
 | } | 
 |  | 
 | void WifiBootstrapManager::OnBootstrapTimeout() { | 
 |   VLOG(1) << "Bootstrapping has timed out."; | 
 |   StartMonitoring(base::TimeDelta::FromSeconds(kMonitoringTimeoutSeconds)); | 
 | } | 
 |  | 
 | void WifiBootstrapManager::OnConnectivityChange() { | 
 |   UpdateConnectionState(); | 
 |  | 
 |   if (state_ == State::kMonitoring || | 
 |       (state_ != State::kDisabled && | 
 |        network_->GetConnectionState() == Network::State::kOnline)) { | 
 |     ContinueMonitoring(base::TimeDelta::FromSeconds(kMonitoringTimeoutSeconds)); | 
 |   } | 
 | } | 
 |  | 
 | void WifiBootstrapManager::OnMonitorTimeout() { | 
 |   VLOG(1) << "Spent too long offline. Entering bootstrap mode."; | 
 |   // TODO(wiley) Retrieve relevant errors from shill. | 
 |   StartBootstrapping(); | 
 | } | 
 |  | 
 | void WifiBootstrapManager::UpdateConnectionState() { | 
 |   connection_state_ = ConnectionState{ConnectionState::kUnconfigured}; | 
 |  | 
 |   Network::State service_state{network_->GetConnectionState()}; | 
 |   VLOG(3) << "New network state: " << EnumToString(service_state); | 
 |   switch (service_state) { | 
 |     case Network::State::kOffline: | 
 |       connection_state_ = ConnectionState{ConnectionState::kOffline}; | 
 |       return; | 
 |     case Network::State::kError: { | 
 |       // TODO(wiley) Pull error information from somewhere. | 
 |       ErrorPtr error; | 
 |       Error::AddTo(&error, FROM_HERE, errors::kDomain, errors::kInvalidState, | 
 |                    "Unknown WiFi error"); | 
 |       connection_state_ = ConnectionState{std::move(error)}; | 
 |       return; | 
 |     } | 
 |     case Network::State::kConnecting: | 
 |       connection_state_ = ConnectionState{ConnectionState::kConnecting}; | 
 |       return; | 
 |     case Network::State::kOnline: | 
 |       connection_state_ = ConnectionState{ConnectionState::kOnline}; | 
 |       return; | 
 |   } | 
 |   ErrorPtr error; | 
 |   Error::AddToPrintf(&error, FROM_HERE, errors::kDomain, errors::kInvalidState, | 
 |                      "Unknown network state: %s", | 
 |                      EnumToString(service_state).c_str()); | 
 |   connection_state_ = ConnectionState{std::move(error)}; | 
 | } | 
 |  | 
 | }  // namespace privet | 
 |  | 
 | template <> | 
 | LIBWEAVE_EXPORT | 
 | EnumToStringMap<privet::WifiBootstrapManager::State>::EnumToStringMap() | 
 |     : EnumToStringMap(privet::kWifiSetupStateMap) {} | 
 |  | 
 | }  // namespace weave |