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