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