// 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/config.h" #include "src/privet/constants.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 { std::set<WifiType> result; if (wifi_->IsWifi24Supported()) result.insert(WifiType::kWifi24); if (wifi_->IsWifi50Supported()) result.insert(WifiType::kWifi50); return result; } void WifiBootstrapManager::OnConnectDone(const std::string& ssid, ErrorPtr error) { if (error) { Error::AddTo(&error, FROM_HERE, 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::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); // TODO: Make it true wifi state, currently it's rather online state. if (service_state != Network::State::kOnline && config_->GetSettings().last_configured_ssid.empty()) { return; } 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::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::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