|  | // 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 |