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