Home | History | Annotate | Download | only in privet
      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 
     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 
     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 
     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 
     97 void WifiBootstrapManager::EndBootstrapping() {
     98   VLOG(1) << "Stopping AP";
     99   wifi_->StopAccessPoint();
    100   privet_ssid_.clear();
    101 }
    102 
    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 
    116 void WifiBootstrapManager::EndConnecting() {}
    117 
    118 void WifiBootstrapManager::StartMonitoring(const base::TimeDelta& timeout) {
    119   monitor_until_ = {};
    120   ContinueMonitoring(timeout);
    121 }
    122 
    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 
    145 void WifiBootstrapManager::EndMonitoring() {}
    146 
    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 
    170 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 
    175 const ConnectionState& WifiBootstrapManager::GetConnectionState() const {
    176   return connection_state_;
    177 }
    178 
    179 const SetupState& WifiBootstrapManager::GetSetupState() const {
    180   return setup_state_;
    181 }
    182 
    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 
    197 std::string WifiBootstrapManager::GetCurrentlyConnectedSsid() const {
    198   // TODO(vitalybuka): Get from shill, if possible.
    199   return config_->GetSettings().last_configured_ssid;
    200 }
    201 
    202 std::string WifiBootstrapManager::GetHostedSsid() const {
    203   return privet_ssid_;
    204 }
    205 
    206 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 
    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 
    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 
    239 void WifiBootstrapManager::OnBootstrapTimeout() {
    240   VLOG(1) << "Bootstrapping has timed out.";
    241   StartMonitoring(base::TimeDelta::FromSeconds(kMonitoringTimeoutSeconds));
    242 }
    243 
    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 
    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 
    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
    301 EnumToStringMap<privet::WifiBootstrapManager::State>::EnumToStringMap()
    302     : EnumToStringMap(privet::kWifiSetupStateMap) {}
    303 
    304 }  // namespace weave
    305