shill: WiFiProvider: Move Service vector to WiFiProvider

Remove the services_ vector from the WiFi Device and move it
to the WiFiProvider.  Also remove the WiFi device initializer
from the WiFiService, so it doesn't have an early or permanent
binding to a particular device.  This allows WiFi services to
be loaded immediately as a profile loads, so that operations
that operate on services (like Manager::ConfigureService
and Manager::FindMatchingService) do not need to use a separate
API to find and modify services that are not visible but have
stored configuration associated with them.

This also allows Chrome a somewhat more stabilized service
path to remembered services as they appear and disappear from
view.  Another advantage is that this completely regularizes
the relationship between the presence of the service in the
provider's services_ list and its registration with the
manager.

In order to perform late-binding to a WiFi device, we provide
two methods for WiFi services to find a device to call
WiFi::ConnectTo on when the time comes: Firstly, visible
WiFi services (ones with endpoints) can select the device
associated with the most "promising" endpoint.  In the case
where we try to connect to a hidden WiFi service before
endpoints appear for it, there is a new method for selecting
a WiFi device from the Manager.  In both of these cases only
one WiFi device is selected for the connect request, so this
method is no worse than before in the unlikely case where
there are two WiFi devices, except for the fact that now
there won't be duplicate WiFi services registered in the
Manager.

CQ-DEPEND=Ic8af4999b25503c3b002504edd12405dc91cc824
BUG=chromium-os:38017
TEST=Unit tests; manual operation; manager unit tests, WiFiManager
autotests (profile tests failing due to crosbug.com/35374)

Change-Id: I904df8a983ba6e7e76e20159622c652675eb6a7d
Reviewed-on: https://gerrit.chromium.org/gerrit/41664
Commit-Queue: Paul Stewart <pstew@chromium.org>
Reviewed-by: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/wifi_provider.cc b/wifi_provider.cc
index 8f0a0cd..6e7602b 100644
--- a/wifi_provider.cc
+++ b/wifi_provider.cc
@@ -4,16 +4,45 @@
 
 #include "shill/wifi_provider.h"
 
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/string_number_conversions.h>
+
 #include "shill/error.h"
 #include "shill/event_dispatcher.h"
+#include "shill/ieee80211.h"
 #include "shill/key_value_store.h"
+#include "shill/logging.h"
 #include "shill/manager.h"
 #include "shill/metrics.h"
+#include "shill/profile.h"
+#include "shill/store_interface.h"
+#include "shill/technology.h"
 #include "shill/wifi_endpoint.h"
 #include "shill/wifi_service.h"
 
+using base::Bind;
+using std::set;
+using std::string;
+using std::vector;
+
 namespace shill {
 
+// Note that WiFiProvider generates some manager-level errors, because it
+// implements the WiFi portion of the Manager.GetService flimflam API. The
+// API is implemented here, rather than in manager, to keep WiFi-specific
+// logic in the right place.
+const char WiFiProvider::kManagerErrorSSIDRequired[] = "must specify SSID";
+const char WiFiProvider::kManagerErrorSSIDTooLong[]  = "SSID is too long";
+const char WiFiProvider::kManagerErrorSSIDTooShort[] = "SSID is too short";
+const char WiFiProvider::kManagerErrorUnsupportedSecurityMode[] =
+    "security mode is unsupported";
+const char WiFiProvider::kManagerErrorUnsupportedServiceMode[] =
+    "service mode is unsupported";
+
 WiFiProvider::WiFiProvider(ControlInterface *control_interface,
                            EventDispatcher *dispatcher,
                            Metrics *metrics,
@@ -25,28 +54,273 @@
 
 WiFiProvider::~WiFiProvider() {}
 
-void WiFiProvider::Start() {
-  NOTIMPLEMENTED();
-}
+void WiFiProvider::Start() {}
 
 void WiFiProvider::Stop() {
-  NOTIMPLEMENTED();
+  SLOG(WiFi, 2) << __func__;
+  while (!services_.empty()) {
+    WiFiServiceRefPtr service = services_.back();
+    ForgetService(service);
+    SLOG(WiFi, 3) << "WiFiProvider deregistering service "
+                  << service->unique_name();
+    manager_->DeregisterService(service);
+  }
 }
 
-void WiFiProvider::CreateServicesFromProfile(ProfileRefPtr profile) {
-  NOTIMPLEMENTED();
+void WiFiProvider::CreateServicesFromProfile(const ProfileRefPtr &profile) {
+  const StoreInterface *storage = profile->GetConstStorage();
+  KeyValueStore args;
+  args.SetString(flimflam::kTypeProperty, flimflam::kTypeWifi);
+  set<string> groups = storage->GetGroupsWithProperties(args);
+  bool created_hidden_service = false;
+  for (set<string>::const_iterator it = groups.begin(); it != groups.end();
+       ++it) {
+    string ssid_hex;
+    vector<uint8_t> ssid_bytes;
+    if (!storage->GetString(*it, WiFiService::kStorageSSID, &ssid_hex) ||
+        !base::HexStringToBytes(ssid_hex, &ssid_bytes)) {
+      SLOG(WiFi, 2) << "Storage group " << *it << " is missing valid \""
+                    << WiFiService::kStorageSSID << "\" property";
+      continue;
+    }
+    string network_mode;
+    if (!storage->GetString(*it, WiFiService::kStorageMode, &network_mode) ||
+        network_mode.empty()) {
+      SLOG(WiFi, 2) << "Storage group " << *it << " is missing \""
+                    <<  WiFiService::kStorageMode << "\" property";
+      continue;
+    }
+    string security;
+    if (!storage->GetString(*it, WiFiService::kStorageSecurity, &security) ||
+        !WiFiService::IsValidSecurityMethod(security)) {
+      SLOG(WiFi, 2) << "Storage group " << *it << " has missing or invalid \""
+                    <<  WiFiService::kStorageSecurity << "\" property";
+      continue;
+    }
+    bool is_hidden = false;
+    if (!storage->GetBool(*it, WiFiService::kStorageHiddenSSID, &is_hidden)) {
+      SLOG(WiFi, 2) << "Storage group " << *it << " is missing \""
+                    << WiFiService::kStorageHiddenSSID << "\" property";
+      continue;
+    }
+
+    if (FindService(ssid_bytes, network_mode, security)) {
+      // If service already exists, we have nothing to do, since the
+      // service has already loaded its configuration from storage.
+      // This is guaranteed to happen in the single case where
+      // CreateServicesFromProfile() is called on a WiFiProvider from
+      // Manager::PushProfile():
+      continue;
+    }
+
+    AddService(ssid_bytes, network_mode, security, is_hidden);
+
+    // By registering the service in AddService, the rest of the configuration
+    // will be loaded from the profile into the service via ConfigureService().
+
+    if (is_hidden) {
+      created_hidden_service = true;
+    }
+  }
+
+  // If WiFi is unconnected and we created a hidden service as a result
+  // of opening the profile, we should initiate a WiFi scan, which will
+  // allow us to find any hidden services that we may have created.
+  if (created_hidden_service &&
+      !manager_->IsTechnologyConnected(Technology::kWifi)) {
+    Error unused_error;
+    manager_->RequestScan(flimflam::kTypeWifi, &unused_error);
+  }
 }
 
 WiFiServiceRefPtr WiFiProvider::GetService(
     const KeyValueStore &args, Error *error) {
-  NOTIMPLEMENTED();
-  return NULL;
+  CHECK_EQ(args.LookupString(flimflam::kTypeProperty, ""), flimflam::kTypeWifi);
+
+  if (args.LookupString(flimflam::kModeProperty, "") !=
+      flimflam::kModeManaged) {
+    Error::PopulateAndLog(error, Error::kNotSupported,
+                          kManagerErrorUnsupportedServiceMode);
+    return NULL;
+  }
+
+  if (!args.ContainsString(flimflam::kSSIDProperty)) {
+    Error::PopulateAndLog(error, Error::kInvalidArguments,
+                          kManagerErrorSSIDRequired);
+    return NULL;
+  }
+
+  string ssid = args.GetString(flimflam::kSSIDProperty);
+  if (ssid.length() < 1) {
+    Error::PopulateAndLog(error, Error::kInvalidNetworkName,
+                          kManagerErrorSSIDTooShort);
+    return NULL;
+  }
+
+  if (ssid.length() > IEEE_80211::kMaxSSIDLen) {
+    Error::PopulateAndLog(error, Error::kInvalidNetworkName,
+                          kManagerErrorSSIDTooLong);
+    return NULL;
+  }
+
+  string security_method = args.LookupString(flimflam::kSecurityProperty,
+                                             flimflam::kSecurityNone);
+
+  if (!WiFiService::IsValidSecurityMethod(security_method)) {
+    Error::PopulateAndLog(error, Error::kNotSupported,
+                          kManagerErrorUnsupportedSecurityMode);
+    return NULL;
+  }
+
+  // If the service is not found, and the caller hasn't specified otherwise,
+  // we assume it is a hidden service.
+  bool hidden_ssid = args.LookupBool(flimflam::kWifiHiddenSsid, true);
+
+  vector<uint8_t> ssid_bytes(ssid.begin(), ssid.end());
+  WiFiServiceRefPtr service(FindService(ssid_bytes, flimflam::kModeManaged,
+                                        security_method));
+  if (!service) {
+    service = AddService(ssid_bytes,
+                         flimflam::kModeManaged,
+                         security_method,
+                         hidden_ssid);
+  }
+
+  return service;
 }
 
 WiFiServiceRefPtr WiFiProvider::FindServiceForEndpoint(
-    const WiFiEndpoint &endpoint) {
-  NOTIMPLEMENTED();
+    const WiFiEndpointConstRefPtr &endpoint) {
+  return FindService(endpoint->ssid(),
+                     endpoint->network_mode(),
+                     endpoint->security_mode());
+}
+
+void WiFiProvider::OnEndpointAdded(const WiFiEndpointConstRefPtr &endpoint) {
+  WiFiServiceRefPtr service = FindServiceForEndpoint(endpoint);
+  if (!service) {
+    const bool hidden_ssid = false;
+    service = AddService(endpoint->ssid(),
+                         endpoint->network_mode(),
+                         endpoint->security_mode(),
+                         hidden_ssid);
+  }
+
+  service->AddEndpoint(endpoint);
+
+  SLOG(WiFi, 1) << "Assigned endpoint " << endpoint->bssid_string()
+                << " to service " << service->unique_name() << ".";
+
+  manager_->UpdateService(service);
+}
+
+WiFiServiceRefPtr WiFiProvider::OnEndpointRemoved(
+    const WiFiEndpointConstRefPtr &endpoint) {
+  WiFiServiceRefPtr service = FindServiceForEndpoint(endpoint);
+
+  CHECK(service) << "Can't find Service for Endpoint "
+                 << "(with BSSID " << endpoint->bssid_string() << ").";
+  SLOG(WiFi, 1) << "Removing endpoint " << endpoint->bssid_string()
+                << " from Service " << service->unique_name();
+  service->RemoveEndpoint(endpoint);
+
+  if (service->HasEndpoints() || service->IsRemembered()) {
+    // Keep services around if they are in a profile or have remaining
+    // endpoints.
+    manager_->UpdateService(service);
+    return NULL;
+  }
+
+  ForgetService(service);
+  manager_->DeregisterService(service);
+
+  return service;
+}
+
+bool WiFiProvider::OnServiceUnloaded(const WiFiServiceRefPtr &service) {
+  // If the service still has endpoints, it should remain in the service list.
+  if (service->HasEndpoints()) {
+    return false;
+  }
+
+  // This is the one place where we forget the service but do not also
+  // deregister the service with the manager.  However, by returning
+  // true below, the manager will do so itself.
+  ForgetService(service);
+  return true;
+}
+
+void WiFiProvider::FixupServiceEntries(
+    StoreInterface *storage, bool is_default_profile) {
+  if (WiFiService::FixupServiceEntries(storage)) {
+    storage->Flush();
+    Metrics::ServiceFixupProfileType profile_type =
+        is_default_profile ?
+            Metrics::kMetricServiceFixupDefaultProfile :
+            Metrics::kMetricServiceFixupUserProfile;
+    metrics_->SendEnumToUMA(
+        metrics_->GetFullMetricName(Metrics::kMetricServiceFixupEntries,
+                                    Technology::kWifi),
+        profile_type,
+        Metrics::kMetricServiceFixupMax);
+  }
+}
+
+WiFiServiceRefPtr WiFiProvider::AddService(const vector<uint8_t> &ssid,
+                                           const string &mode,
+                                           const string &security,
+                                           bool is_hidden) {
+  WiFiServiceRefPtr service = new WiFiService(control_interface_,
+                                              dispatcher_,
+                                              metrics_,
+                                              manager_,
+                                              this,
+                                              ssid,
+                                              mode,
+                                              security,
+                                              is_hidden);
+
+  services_.push_back(service);
+  manager_->RegisterService(service);
+  return service;
+}
+
+WiFiServiceRefPtr WiFiProvider::FindService(const vector<uint8_t> &ssid,
+                                            const string &mode,
+                                            const string &security) const {
+  for (vector<WiFiServiceRefPtr>::const_iterator it = services_.begin();
+       it != services_.end();
+       ++it) {
+    if ((*it)->ssid() == ssid && (*it)->mode() == mode &&
+        (*it)->IsSecurityMatch(security)) {
+      return *it;
+    }
+  }
   return NULL;
 }
 
+ByteArrays WiFiProvider::GetHiddenSSIDList() {
+  // Create a unique set of hidden SSIDs.
+  set<ByteArray> hidden_ssids_set;
+  for (vector<WiFiServiceRefPtr>::const_iterator it = services_.begin();
+       it != services_.end();
+       ++it) {
+    if ((*it)->hidden_ssid() && (*it)->IsRemembered()) {
+      hidden_ssids_set.insert((*it)->ssid());
+    }
+  }
+  SLOG(WiFi, 2) << "Found " << hidden_ssids_set.size() << " hidden services";
+  return ByteArrays(hidden_ssids_set.begin(), hidden_ssids_set.end());
+}
+
+void WiFiProvider::ForgetService(const WiFiServiceRefPtr &service) {
+  vector<WiFiServiceRefPtr>::iterator it;
+  it = std::find(services_.begin(), services_.end(), service);
+  if (it == services_.end()) {
+    return;
+  }
+  (*it)->ResetWiFi();
+  services_.erase(it);
+}
+
 }  // namespace shill