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