blob: d0be5c05f3e2f169b16260196f0697023615203e [file] [log] [blame]
// Copyright 2014 The Chromium OS 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 "privetd/wifi_bootstrap_manager.h"
#include <base/logging.h>
#include <base/memory/weak_ptr.h>
#include <base/message_loop/message_loop.h>
#include <chromeos/bind_lambda.h>
#include <chromeos/key_value_store.h>
#include "privetd/ap_manager_client.h"
#include "privetd/constants.h"
#include "privetd/shill_client.h"
namespace privetd {
WifiBootstrapManager::WifiBootstrapManager(DaemonState* state_store,
ShillClient* shill_client,
ApManagerClient* ap_manager_client,
CloudDelegate* gcd,
uint32_t connect_timeout_seconds,
uint32_t bootstrap_timeout_seconds,
uint32_t monitor_timeout_seconds)
: state_store_(state_store),
shill_client_(shill_client),
ap_manager_client_(ap_manager_client),
ssid_generator_(gcd, this),
connect_timeout_seconds_(connect_timeout_seconds),
bootstrap_timeout_seconds_(bootstrap_timeout_seconds),
monitor_timeout_seconds_(monitor_timeout_seconds) {
cloud_observer_.Add(gcd);
}
void WifiBootstrapManager::Init() {
CHECK(!is_initialized_);
std::string ssid = ssid_generator_.GenerateSsid();
if (ssid.empty())
return; // Delay initialization until ssid_generator_ is ready.
chromeos::KeyValueStore state_store;
if (!state_store_->GetBoolean(state_key::kWifiHasBeenBootstrapped,
&have_ever_been_bootstrapped_) ||
!state_store_->GetString(state_key::kWifiLastConfiguredSSID,
&last_configured_ssid_)) {
have_ever_been_bootstrapped_ = false;
}
UpdateConnectionState();
shill_client_->RegisterConnectivityListener(
base::Bind(&WifiBootstrapManager::OnConnectivityChange,
lifetime_weak_factory_.GetWeakPtr()));
if (!have_ever_been_bootstrapped_) {
StartBootstrapping();
} else {
StartMonitoring();
}
is_initialized_ = true;
}
void WifiBootstrapManager::RegisterStateListener(
const StateListener& listener) {
state_listeners_.push_back(listener);
}
void WifiBootstrapManager::StartBootstrapping() {
if (shill_client_->AmOnline()) {
// 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();
return;
}
UpdateState(kBootstrapping);
if (have_ever_been_bootstrapped_) {
// 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.
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE, base::Bind(&WifiBootstrapManager::OnBootstrapTimeout,
tasks_weak_factory_.GetWeakPtr()),
base::TimeDelta::FromSeconds(bootstrap_timeout_seconds_));
}
// TODO(vitalybuka): Add SSID probing.
std::string ssid = ssid_generator_.GenerateSsid();
CHECK(!ssid.empty());
ap_manager_client_->Start(ssid);
}
void WifiBootstrapManager::EndBootstrapping() {
ap_manager_client_->Stop();
}
void WifiBootstrapManager::StartConnecting(const std::string& ssid,
const std::string& passphrase) {
VLOG(1) << "WiFi is attempting to connect. (ssid=" << ssid
<< ", pass=" << passphrase << ").";
UpdateState(kConnecting);
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE, base::Bind(&WifiBootstrapManager::OnConnectTimeout,
tasks_weak_factory_.GetWeakPtr()),
base::TimeDelta::FromSeconds(connect_timeout_seconds_));
shill_client_->ConnectToService(
ssid, passphrase, base::Bind(&WifiBootstrapManager::OnConnectSuccess,
tasks_weak_factory_.GetWeakPtr(), ssid),
nullptr);
}
void WifiBootstrapManager::EndConnecting() {
}
void WifiBootstrapManager::StartMonitoring() {
VLOG(1) << "Monitoring connectivity.";
// We already have a callback in place with |shill_client_| to update our
// connectivity state. See OnConnectivityChange().
UpdateState(kMonitoring);
}
void WifiBootstrapManager::EndMonitoring() {
}
void WifiBootstrapManager::UpdateState(State new_state) {
VLOG(3) << "Switching state from " << state_ << " to " << new_state;
// Abort irrelevant tasks.
tasks_weak_factory_.InvalidateWeakPtrs();
switch (state_) {
case kDisabled:
break;
case kBootstrapping:
EndBootstrapping();
break;
case kMonitoring:
EndMonitoring();
break;
case kConnecting:
EndConnecting();
break;
}
if (new_state != state_) {
state_ = new_state;
// Post with weak ptr to avoid notification after this object destroyed.
base::MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(&WifiBootstrapManager::NotifyStateListeners,
lifetime_weak_factory_.GetWeakPtr(), new_state));
} else {
VLOG(3) << "Not notifying listeners of state change, "
<< "because the states are the same.";
}
}
void WifiBootstrapManager::NotifyStateListeners(State new_state) const {
for (const StateListener& listener : state_listeners_)
listener.Run(new_state);
}
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,
chromeos::ErrorPtr* error) {
setup_state_ = SetupState{SetupState::kInProgress};
// TODO(vitalybuka): Find more reliable way to finish request or move delay
// into PrivetHandler as it's very HTTP specific.
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE, base::Bind(&WifiBootstrapManager::StartConnecting,
tasks_weak_factory_.GetWeakPtr(), ssid, passphrase),
base::TimeDelta::FromSeconds(kSetupDelaySeconds));
return true;
}
std::string WifiBootstrapManager::GetCurrentlyConnectedSsid() const {
// TODO(vitalybuka): Get from shill, if possible.
return last_configured_ssid_;
}
std::string WifiBootstrapManager::GetHostedSsid() const {
return ap_manager_client_->GetSsid();
}
std::set<WifiType> WifiBootstrapManager::GetTypes() const {
// TODO(wiley) This should do some system work to figure this out.
return {WifiType::kWifi24};
}
void WifiBootstrapManager::OnDeviceInfoChanged() {
// Initialization was delayed until buffet is ready.
if (!is_initialized_)
Init();
}
void WifiBootstrapManager::OnConnectSuccess(const std::string& ssid) {
VLOG(1) << "Wifi was connected successfully";
have_ever_been_bootstrapped_ = true;
last_configured_ssid_ = ssid;
state_store_->SetBoolean(state_key::kWifiHasBeenBootstrapped,
have_ever_been_bootstrapped_);
state_store_->SetString(state_key::kWifiLastConfiguredSSID,
last_configured_ssid_);
state_store_->Save();
setup_state_ = SetupState{SetupState::kSuccess};
StartMonitoring();
}
void WifiBootstrapManager::OnBootstrapTimeout() {
VLOG(1) << "Bootstrapping has timed out.";
StartMonitoring();
}
void WifiBootstrapManager::OnConnectTimeout() {
VLOG(1) << "Wifi timed out while connecting";
chromeos::ErrorPtr error;
chromeos::Error::AddTo(&error, FROM_HERE, errors::kDomain,
errors::kInvalidState,
"Failed to connect to provided network");
setup_state_ = SetupState{std::move(error)};
StartBootstrapping();
}
void WifiBootstrapManager::OnConnectivityChange(bool is_connected) {
VLOG(3) << "ConnectivityChanged: " << is_connected;
UpdateConnectionState();
if (state_ == kBootstrapping) {
StartMonitoring();
return;
}
if (state_ == kMonitoring) {
if (is_connected) {
tasks_weak_factory_.InvalidateWeakPtrs();
} else {
// Tasks queue may have more than one OnMonitorTimeout enqueued. The
// first one could be executed as it would change the state and abort the
// rest.
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE, base::Bind(&WifiBootstrapManager::OnMonitorTimeout,
tasks_weak_factory_.GetWeakPtr()),
base::TimeDelta::FromSeconds(monitor_timeout_seconds_));
}
}
}
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};
if (!have_ever_been_bootstrapped_) {
return;
}
ServiceState service_state{shill_client_->GetConnectionState()};
switch (service_state) {
case ServiceState::kOffline:
connection_state_ = ConnectionState{ConnectionState::kOffline};
return;
case ServiceState::kFailure: {
// TODO(wiley) Pull error information from somewhere.
chromeos::ErrorPtr error;
chromeos::Error::AddTo(&error, FROM_HERE, errors::kDomain,
errors::kInvalidState, "Unknown WiFi error");
connection_state_ = ConnectionState{std::move(error)};
return;
}
case ServiceState::kConnecting:
connection_state_ = ConnectionState{ConnectionState::kConnecting};
return;
case ServiceState::kConnected:
connection_state_ = ConnectionState{ConnectionState::kOnline};
return;
}
chromeos::ErrorPtr error;
chromeos::Error::AddToPrintf(
&error, FROM_HERE, errors::kDomain, errors::kInvalidState,
"Unknown state returned from ShillClient: %s",
ServiceStateToString(service_state).c_str());
connection_state_ = ConnectionState{std::move(error)};
}
} // namespace privetd