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
18 namespace weave {
19 namespace privet {
20
21 namespace {
22
23 const int kMonitoringWithSsidTimeoutSeconds = 15;
24 const int kMonitoringTimeoutSeconds = 120;
25 const int kBootstrapTimeoutSeconds = 600;
26 const int kConnectingTimeoutSeconds = 180;
27
28 const 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
36 using provider::Network;
37
WifiBootstrapManager(Config * config,provider::TaskRunner * task_runner,provider::Network * network,provider::Wifi * wifi,CloudDelegate * gcd)38 WifiBootstrapManager::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
Init()54 void 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
StartBootstrapping()68 void 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
EndBootstrapping()97 void WifiBootstrapManager::EndBootstrapping() {
98 VLOG(1) << "Stopping AP";
99 wifi_->StopAccessPoint();
100 privet_ssid_.clear();
101 }
102
StartConnecting(const std::string & ssid,const std::string & passphrase)103 void 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
EndConnecting()116 void WifiBootstrapManager::EndConnecting() {}
117
StartMonitoring(const base::TimeDelta & timeout)118 void WifiBootstrapManager::StartMonitoring(const base::TimeDelta& timeout) {
119 monitor_until_ = {};
120 ContinueMonitoring(timeout);
121 }
122
ContinueMonitoring(const base::TimeDelta & timeout)123 void 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
EndMonitoring()145 void WifiBootstrapManager::EndMonitoring() {}
146
UpdateState(State new_state)147 void 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
GenerateSsid() const170 std::string WifiBootstrapManager::GenerateSsid() const {
171 const std::string& ssid = config_->GetSettings().test_privet_ssid;
172 return ssid.empty() ? ssid_generator_.GenerateSsid() : ssid;
173 }
174
GetConnectionState() const175 const ConnectionState& WifiBootstrapManager::GetConnectionState() const {
176 return connection_state_;
177 }
178
GetSetupState() const179 const SetupState& WifiBootstrapManager::GetSetupState() const {
180 return setup_state_;
181 }
182
ConfigureCredentials(const std::string & ssid,const std::string & passphrase,ErrorPtr * error)183 bool 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
GetCurrentlyConnectedSsid() const197 std::string WifiBootstrapManager::GetCurrentlyConnectedSsid() const {
198 // TODO(vitalybuka): Get from shill, if possible.
199 return config_->GetSettings().last_configured_ssid;
200 }
201
GetHostedSsid() const202 std::string WifiBootstrapManager::GetHostedSsid() const {
203 return privet_ssid_;
204 }
205
GetTypes() const206 std::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
OnConnectDone(const std::string & ssid,ErrorPtr error)215 void 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
OnConnectTimeout()231 void 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
OnBootstrapTimeout()239 void WifiBootstrapManager::OnBootstrapTimeout() {
240 VLOG(1) << "Bootstrapping has timed out.";
241 StartMonitoring(base::TimeDelta::FromSeconds(kMonitoringTimeoutSeconds));
242 }
243
OnConnectivityChange()244 void 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
OnMonitorTimeout()254 void WifiBootstrapManager::OnMonitorTimeout() {
255 VLOG(1) << "Spent too long offline. Entering bootstrap mode.";
256 // TODO(wiley) Retrieve relevant errors from shill.
257 StartBootstrapping();
258 }
259
UpdateConnectionState()260 void 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
299 template <>
300 LIBWEAVE_EXPORT
EnumToStringMap()301 EnumToStringMap<privet::WifiBootstrapManager::State>::EnumToStringMap()
302 : EnumToStringMap(privet::kWifiSetupStateMap) {}
303
304 } // namespace weave
305