1// Copyright 2015 The Weave Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "src/privet/wifi_bootstrap_manager.h" 6 7#include <base/logging.h> 8#include <base/memory/weak_ptr.h> 9#include <weave/enum_to_string.h> 10#include <weave/provider/network.h> 11#include <weave/provider/task_runner.h> 12#include <weave/provider/wifi.h> 13 14#include "src/bind_lambda.h" 15#include "src/config.h" 16#include "src/privet/constants.h" 17 18namespace weave { 19namespace privet { 20 21namespace { 22 23const int kMonitoringWithSsidTimeoutSeconds = 15; 24const int kMonitoringTimeoutSeconds = 120; 25const int kBootstrapTimeoutSeconds = 600; 26const int kConnectingTimeoutSeconds = 180; 27 28const EnumToStringMap<WifiBootstrapManager::State>::Map kWifiSetupStateMap[] = { 29 {WifiBootstrapManager::State::kDisabled, "disabled"}, 30 {WifiBootstrapManager::State::kBootstrapping, "waiting"}, 31 {WifiBootstrapManager::State::kMonitoring, "monitoring"}, 32 {WifiBootstrapManager::State::kConnecting, "connecting"}, 33}; 34} 35 36using provider::Network; 37 38WifiBootstrapManager::WifiBootstrapManager(Config* config, 39 provider::TaskRunner* task_runner, 40 provider::Network* network, 41 provider::Wifi* wifi, 42 CloudDelegate* gcd) 43 : config_{config}, 44 task_runner_{task_runner}, 45 network_{network}, 46 wifi_{wifi}, 47 ssid_generator_{gcd, this} { 48 CHECK(config_); 49 CHECK(network_); 50 CHECK(task_runner_); 51 CHECK(wifi_); 52} 53 54void WifiBootstrapManager::Init() { 55 UpdateConnectionState(); 56 network_->AddConnectionChangedCallback( 57 base::Bind(&WifiBootstrapManager::OnConnectivityChange, 58 lifetime_weak_factory_.GetWeakPtr())); 59 if (config_->GetSettings().last_configured_ssid.empty()) { 60 // Give implementation some time to figure out state. 61 StartMonitoring( 62 base::TimeDelta::FromSeconds(kMonitoringWithSsidTimeoutSeconds)); 63 } else { 64 StartMonitoring(base::TimeDelta::FromSeconds(kMonitoringTimeoutSeconds)); 65 } 66} 67 68void WifiBootstrapManager::StartBootstrapping() { 69 if (network_->GetConnectionState() == Network::State::kOnline) { 70 // If one of the devices we monitor for connectivity is online, we need not 71 // start an AP. For most devices, this is a situation which happens in 72 // testing when we have an ethernet connection. If you need to always 73 // start an AP to bootstrap WiFi credentials, then add your WiFi interface 74 // to the device whitelist. 75 StartMonitoring(base::TimeDelta::FromSeconds(kMonitoringTimeoutSeconds)); 76 return; 77 } 78 79 UpdateState(State::kBootstrapping); 80 if (!config_->GetSettings().last_configured_ssid.empty()) { 81 // If we have been configured before, we'd like to periodically take down 82 // our AP and find out if we can connect again. Many kinds of failures are 83 // transient, and having an AP up prohibits us from connecting as a client. 84 task_runner_->PostDelayedTask( 85 FROM_HERE, base::Bind(&WifiBootstrapManager::OnBootstrapTimeout, 86 tasks_weak_factory_.GetWeakPtr()), 87 base::TimeDelta::FromSeconds(kBootstrapTimeoutSeconds)); 88 } 89 // TODO(vitalybuka): Add SSID probing. 90 privet_ssid_ = GenerateSsid(); 91 CHECK(!privet_ssid_.empty()); 92 93 VLOG(1) << "Starting AP with SSID: " << privet_ssid_; 94 wifi_->StartAccessPoint(privet_ssid_); 95} 96 97void WifiBootstrapManager::EndBootstrapping() { 98 VLOG(1) << "Stopping AP"; 99 wifi_->StopAccessPoint(); 100 privet_ssid_.clear(); 101} 102 103void WifiBootstrapManager::StartConnecting(const std::string& ssid, 104 const std::string& passphrase) { 105 VLOG(1) << "Attempting connect to SSID:" << ssid; 106 UpdateState(State::kConnecting); 107 task_runner_->PostDelayedTask( 108 FROM_HERE, base::Bind(&WifiBootstrapManager::OnConnectTimeout, 109 tasks_weak_factory_.GetWeakPtr()), 110 base::TimeDelta::FromSeconds(kConnectingTimeoutSeconds)); 111 wifi_->Connect(ssid, passphrase, 112 base::Bind(&WifiBootstrapManager::OnConnectDone, 113 tasks_weak_factory_.GetWeakPtr(), ssid)); 114} 115 116void WifiBootstrapManager::EndConnecting() {} 117 118void WifiBootstrapManager::StartMonitoring(const base::TimeDelta& timeout) { 119 monitor_until_ = {}; 120 ContinueMonitoring(timeout); 121} 122 123void WifiBootstrapManager::ContinueMonitoring(const base::TimeDelta& timeout) { 124 VLOG(1) << "Monitoring connectivity."; 125 // We already have a callback in place with |network_| to update our 126 // connectivity state. See OnConnectivityChange(). 127 UpdateState(State::kMonitoring); 128 129 if (network_->GetConnectionState() == Network::State::kOnline) { 130 monitor_until_ = {}; 131 } else { 132 if (monitor_until_.is_null()) { 133 monitor_until_ = base::Time::Now() + timeout; 134 VLOG(2) << "Waiting for connection until: " << monitor_until_; 135 } 136 137 // Schedule timeout timer taking into account already offline time. 138 task_runner_->PostDelayedTask( 139 FROM_HERE, base::Bind(&WifiBootstrapManager::OnMonitorTimeout, 140 tasks_weak_factory_.GetWeakPtr()), 141 monitor_until_ - base::Time::Now()); 142 } 143} 144 145void WifiBootstrapManager::EndMonitoring() {} 146 147void WifiBootstrapManager::UpdateState(State new_state) { 148 VLOG(3) << "Switching state from " << EnumToString(state_) << " to " 149 << EnumToString(new_state); 150 // Abort irrelevant tasks. 151 tasks_weak_factory_.InvalidateWeakPtrs(); 152 153 switch (state_) { 154 case State::kDisabled: 155 break; 156 case State::kBootstrapping: 157 EndBootstrapping(); 158 break; 159 case State::kMonitoring: 160 EndMonitoring(); 161 break; 162 case State::kConnecting: 163 EndConnecting(); 164 break; 165 } 166 167 state_ = new_state; 168} 169 170std::string WifiBootstrapManager::GenerateSsid() const { 171 const std::string& ssid = config_->GetSettings().test_privet_ssid; 172 return ssid.empty() ? ssid_generator_.GenerateSsid() : ssid; 173} 174 175const ConnectionState& WifiBootstrapManager::GetConnectionState() const { 176 return connection_state_; 177} 178 179const SetupState& WifiBootstrapManager::GetSetupState() const { 180 return setup_state_; 181} 182 183bool WifiBootstrapManager::ConfigureCredentials(const std::string& ssid, 184 const std::string& passphrase, 185 ErrorPtr* error) { 186 setup_state_ = SetupState{SetupState::kInProgress}; 187 // Since we are changing network, we need to let the web server send out the 188 // response to the HTTP request leading to this action. So, we are waiting 189 // a bit before mocking with network set up. 190 task_runner_->PostDelayedTask( 191 FROM_HERE, base::Bind(&WifiBootstrapManager::StartConnecting, 192 tasks_weak_factory_.GetWeakPtr(), ssid, passphrase), 193 base::TimeDelta::FromSeconds(1)); 194 return true; 195} 196 197std::string WifiBootstrapManager::GetCurrentlyConnectedSsid() const { 198 // TODO(vitalybuka): Get from shill, if possible. 199 return config_->GetSettings().last_configured_ssid; 200} 201 202std::string WifiBootstrapManager::GetHostedSsid() const { 203 return privet_ssid_; 204} 205 206std::set<WifiType> WifiBootstrapManager::GetTypes() const { 207 std::set<WifiType> result; 208 if (wifi_->IsWifi24Supported()) 209 result.insert(WifiType::kWifi24); 210 if (wifi_->IsWifi50Supported()) 211 result.insert(WifiType::kWifi50); 212 return result; 213} 214 215void WifiBootstrapManager::OnConnectDone(const std::string& ssid, 216 ErrorPtr error) { 217 if (error) { 218 Error::AddTo(&error, FROM_HERE, errors::kInvalidState, 219 "Failed to connect to provided network"); 220 setup_state_ = SetupState{std::move(error)}; 221 return StartBootstrapping(); 222 } 223 VLOG(1) << "Wifi was connected successfully"; 224 Config::Transaction change{config_}; 225 change.set_last_configured_ssid(ssid); 226 change.Commit(); 227 setup_state_ = SetupState{SetupState::kSuccess}; 228 StartMonitoring(base::TimeDelta::FromSeconds(kMonitoringTimeoutSeconds)); 229} 230 231void WifiBootstrapManager::OnConnectTimeout() { 232 ErrorPtr error; 233 Error::AddTo(&error, FROM_HERE, errors::kInvalidState, 234 "Timeout connecting to provided network"); 235 setup_state_ = SetupState{std::move(error)}; 236 return StartBootstrapping(); 237} 238 239void WifiBootstrapManager::OnBootstrapTimeout() { 240 VLOG(1) << "Bootstrapping has timed out."; 241 StartMonitoring(base::TimeDelta::FromSeconds(kMonitoringTimeoutSeconds)); 242} 243 244void WifiBootstrapManager::OnConnectivityChange() { 245 UpdateConnectionState(); 246 247 if (state_ == State::kMonitoring || 248 (state_ != State::kDisabled && 249 network_->GetConnectionState() == Network::State::kOnline)) { 250 ContinueMonitoring(base::TimeDelta::FromSeconds(kMonitoringTimeoutSeconds)); 251 } 252} 253 254void WifiBootstrapManager::OnMonitorTimeout() { 255 VLOG(1) << "Spent too long offline. Entering bootstrap mode."; 256 // TODO(wiley) Retrieve relevant errors from shill. 257 StartBootstrapping(); 258} 259 260void WifiBootstrapManager::UpdateConnectionState() { 261 connection_state_ = ConnectionState{ConnectionState::kUnconfigured}; 262 Network::State service_state{network_->GetConnectionState()}; 263 VLOG(3) << "New network state: " << EnumToString(service_state); 264 265 // TODO: Make it true wifi state, currently it's rather online state. 266 if (service_state != Network::State::kOnline && 267 config_->GetSettings().last_configured_ssid.empty()) { 268 return; 269 } 270 271 switch (service_state) { 272 case Network::State::kOffline: 273 connection_state_ = ConnectionState{ConnectionState::kOffline}; 274 return; 275 case Network::State::kError: { 276 // TODO(wiley) Pull error information from somewhere. 277 ErrorPtr error; 278 Error::AddTo(&error, FROM_HERE, errors::kInvalidState, 279 "Unknown WiFi error"); 280 connection_state_ = ConnectionState{std::move(error)}; 281 return; 282 } 283 case Network::State::kConnecting: 284 connection_state_ = ConnectionState{ConnectionState::kConnecting}; 285 return; 286 case Network::State::kOnline: 287 connection_state_ = ConnectionState{ConnectionState::kOnline}; 288 return; 289 } 290 ErrorPtr error; 291 Error::AddToPrintf(&error, FROM_HERE, errors::kInvalidState, 292 "Unknown network state: %s", 293 EnumToString(service_state).c_str()); 294 connection_state_ = ConnectionState{std::move(error)}; 295} 296 297} // namespace privet 298 299template <> 300LIBWEAVE_EXPORT 301EnumToStringMap<privet::WifiBootstrapManager::State>::EnumToStringMap() 302 : EnumToStringMap(privet::kWifiSetupStateMap) {} 303 304} // namespace weave 305