shill: support association with open wifi access points

with this patch, shill organizes scan results into Endpoints and
Services. although this patch does not communicate information about
Services to the UI, one can connect to an open AP by sending the
appropriate message to shill over D-Bus.

known limitations:
- does not communicate to UI
- creates a Service for every Endpoint (note, however, that this
  does not provide the ability to connect to a specific AP)
- only supports open networks

note: a fix to memory management in wifi_integrationtest slipped
into this patch.

BUG=chromium-os:16065
TEST=manual: start shill, use dbus-send to tell shill to connect

Change-Id: I26737f5e61b56497beffb9639f3e347a21ad76d7
Reviewed-on: http://gerrit.chromium.org/gerrit/2910
Reviewed-by: Chris Masone <cmasone@chromium.org>
Tested-by: mukesh agrawal <quiche@chromium.org>
diff --git a/Makefile b/Makefile
index df9da75..7027005 100644
--- a/Makefile
+++ b/Makefile
@@ -50,6 +50,7 @@
 	dhcp_config.o \
 	dhcp_provider.o \
 	dhcpcd_proxy.o \
+	endpoint.o \
 	error.o \
 	ethernet.o \
 	ethernet_service.o \
@@ -66,7 +67,9 @@
 	shill_config.o \
 	shill_daemon.o \
 	shill_event.o \
-	wifi.o
+	wifi.o \
+	wifi_endpoint.o \
+	wifi_service.o
 
 SHILL_BIN = shill
 SHILL_MAIN_OBJ = shill_main.o
diff --git a/endpoint.cc b/endpoint.cc
new file mode 100644
index 0000000..39cce5e
--- /dev/null
+++ b/endpoint.cc
@@ -0,0 +1,12 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "shill/endpoint.h"
+
+namespace shill {
+
+Endpoint::Endpoint() {}
+Endpoint::~Endpoint() {}
+
+}  // namespace shill
diff --git a/endpoint.h b/endpoint.h
new file mode 100644
index 0000000..e235354
--- /dev/null
+++ b/endpoint.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHILL_ENDPOINT_
+#define SHILL_ENDPOINT_
+
+#include <base/memory/ref_counted.h>
+
+namespace shill {
+
+class Endpoint;
+
+class Endpoint : public base::RefCounted<Endpoint> {
+ public:
+  Endpoint();
+  virtual ~Endpoint();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Endpoint);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_ENDPOINT_
diff --git a/service.cc b/service.cc
index 5be9d35..60e5ae4 100644
--- a/service.cc
+++ b/service.cc
@@ -23,10 +23,11 @@
 
 namespace shill {
 Service::Service(ControlInterface *control_interface,
-                 EventDispatcher */* dispatcher */,
+                 EventDispatcher *dispatcher,
                  DeviceConfigInterfaceRefPtr config_interface,
                  const string& name)
-    : name_(name),
+    : dispatcher_(dispatcher),
+      name_(name),
       available_(false),
       configured_(false),
       auto_connect_(false),
diff --git a/service.h b/service.h
index 23af70a..61e2efc 100644
--- a/service.h
+++ b/service.h
@@ -78,6 +78,9 @@
   // Service instance.
   virtual const std::string& UniqueName() { return name_; }
 
+ protected:
+  EventDispatcher *dispatcher_;
+
  private:
   const std::string name_;
   bool available_;
@@ -89,6 +92,7 @@
   Endpoint *endpoint_;
   scoped_ptr<ServiceAdaptorInterface> adaptor_;
   friend class ServiceAdaptorInterface;
+  DISALLOW_COPY_AND_ASSIGN(Service);
 };
 
 }  // namespace shill
diff --git a/service_dbus_adaptor.cc b/service_dbus_adaptor.cc
index 76cfa01..7d96812 100644
--- a/service_dbus_adaptor.cc
+++ b/service_dbus_adaptor.cc
@@ -66,6 +66,7 @@
 }
 
 void ServiceDBusAdaptor::Connect(::DBus::Error &error) {
+  service_->Connect();
 }
 
 void ServiceDBusAdaptor::Disconnect(::DBus::Error &error) {
diff --git a/wifi.cc b/wifi.cc
index 72a2269..cec2d98 100644
--- a/wifi.cc
+++ b/wifi.cc
@@ -11,21 +11,27 @@
 #include <vector>
 
 #include <base/logging.h>
+#include <base/string_number_conversions.h>
 
 #include "shill/control_interface.h"
 #include "shill/device.h"
 #include "shill/shill_event.h"
+#include "shill/wifi_endpoint.h"
+#include "shill/wifi_service.h"
 
 #include "shill/wifi.h"
 
 using std::string;
 
 namespace shill {
-const char WiFi::kSupplicantPath[]       = "/fi/w1/wpa_supplicant1";
-const char WiFi::kSupplicantDBusAddr[]   = "fi.w1.wpa_supplicant1";
-const char WiFi::kSupplicantWiFiDriver[] = "nl80211";
+const char WiFi::kSupplicantPath[]        = "/fi/w1/wpa_supplicant1";
+const char WiFi::kSupplicantDBusAddr[]    = "fi.w1.wpa_supplicant1";
+const char WiFi::kSupplicantWiFiDriver[]  = "nl80211";
 const char WiFi::kSupplicantErrorInterfaceExists[] =
     "fi.w1.wpa_supplicant1.InterfaceExists";
+const char WiFi::kSupplicantKeyModeNone[] = "NONE";
+
+unsigned int WiFi::service_id_serial_ = 0;
 
 WiFi::SupplicantProcessProxy::SupplicantProcessProxy(DBus::Connection *bus)
     : DBus::ObjectProxy(*bus, kSupplicantPath, kSupplicantDBusAddr) {}
@@ -113,14 +119,18 @@
 WiFi::WiFi(ControlInterface *control_interface,
            EventDispatcher *dispatcher,
            Manager *manager,
-           const std::string& link_name,
+           const string& link_name,
            int interface_index)
     : Device(control_interface,
              dispatcher,
              manager,
              link_name,
              interface_index),
-      dbus_(DBus::Connection::SystemBus()), scan_pending_(false) {
+      task_factory_(this),
+      control_interface_(control_interface),
+      dispatcher_(dispatcher),
+      dbus_(DBus::Connection::SystemBus()),
+      scan_pending_(false) {
   VLOG(2) << "WiFi device " << link_name_ << " initialized.";
 }
 
@@ -145,6 +155,7 @@
     if (!strcmp(e.name(), kSupplicantErrorInterfaceExists)) {
       interface_path =
           supplicant_process_proxy_->GetInterface(link_name_);
+      // XXX crash here, if device missing?
     } else {
       // XXX
     }
@@ -172,23 +183,6 @@
   Device::Start();
 }
 
-void WiFi::BSSAdded(
-    const ::DBus::Path &BSS,
-    const std::map<string, ::DBus::Variant> &properties) {
-  std::vector<uint8_t> ssid = properties.find("SSID")->second;
-  ssids_.push_back(string(ssid.begin(), ssid.end()));
-}
-
-void WiFi::ScanDone() {
-  LOG(INFO) << __func__;
-
-  scan_pending_ = false;
-  for (std::vector<string>::iterator i(ssids_.begin());
-       i != ssids_.end(); ++i) {
-    LOG(INFO) << "found SSID " << *i;
-  }
-}
-
 void WiFi::Stop() {
   LOG(INFO) << __func__;
   // XXX
@@ -199,4 +193,79 @@
   return type == Device::kWifi;
 }
 
+void WiFi::BSSAdded(
+    const ::DBus::Path &BSS,
+    const std::map<string, ::DBus::Variant> &properties) {
+  // TODO(quiche): write test to verify correct behavior in the case
+  // where we get multiple BSSAdded events for a single endpoint.
+  // (old Endpoint's refcount should fall to zero, and old Endpoint
+  // should be destroyed)
+  //
+  // note: we assume that BSSIDs are unique across endpoints. this
+  // means that if an AP reuses the same BSSID for multiple SSIDs, we
+  // lose.
+  WiFiEndpointRefPtr endpoint(new WiFiEndpoint(properties));
+  endpoint_by_bssid_[endpoint->bssid_hex()] = endpoint;
+}
+
+void WiFi::ScanDone() {
+  LOG(INFO) << __func__;
+
+  // defer handling of scan result processing, because that processing
+  // may require the the registration of new D-Bus objects. and such
+  // registration can't be done in the context of a D-Bus signal
+  // handler.
+  dispatcher_->PostTask(
+      task_factory_.NewRunnableMethod(&WiFi::RealScanDone));
+}
+
+::DBus::Path WiFi::AddNetwork(
+    const std::map<string, ::DBus::Variant> &args) {
+  return supplicant_interface_proxy_->AddNetwork(args);
+}
+
+void WiFi::SelectNetwork(const ::DBus::Path &network) {
+  supplicant_interface_proxy_->SelectNetwork(network);
+}
+
+void WiFi::RealScanDone() {
+  LOG(INFO) << __func__;
+
+  scan_pending_ = false;
+
+  // TODO(quiche): group endpoints into services, instead of creating
+  // a service for every endpoint.
+  for (EndpointMap::iterator i(endpoint_by_bssid_.begin());
+       i != endpoint_by_bssid_.end(); ++i) {
+    const WiFiEndpoint &endpoint(*(i->second));
+    string service_id_private;
+
+    service_id_private =
+        endpoint.ssid_hex() + "_" + endpoint.bssid_hex();
+    if (service_by_private_id_.find(service_id_private) ==
+        service_by_private_id_.end()) {
+      unsigned int new_service_id_serial = service_id_serial_++;
+      string service_name(base::UintToString(new_service_id_serial));
+
+      LOG(INFO) << "found new endpoint. "
+                << "ssid: " << endpoint.ssid_string() << ", "
+                << "bssid: " << endpoint.bssid_string() << ", "
+                << "signal: " << endpoint.signal_strength() << ", "
+                << "service name: " << "/service/" << service_name;
+
+      // XXX key mode should reflect endpoint params (not always use
+      // kSupplicantKeyModeNone)
+      ServiceRefPtr service(
+          new WiFiService(control_interface_, dispatcher_, this,
+                          endpoint.ssid(), endpoint.network_mode(),
+                          kSupplicantKeyModeNone, service_name));
+      services_.push_back(service);
+      service_by_private_id_[service_id_private] = service;
+    }
+  }
+
+  // TODO(quiche): register new services with manager
+  // TODO(quiche): unregister removed services from manager
+}
+
 }  // namespace shill
diff --git a/wifi.h b/wifi.h
index f11fcea..f7e851a 100644
--- a/wifi.h
+++ b/wifi.h
@@ -16,6 +16,10 @@
 
 namespace shill {
 
+class WiFiEndpoint;
+typedef scoped_refptr<const WiFiEndpoint> WiFiEndpointConstRefPtr;
+typedef scoped_refptr<WiFiEndpoint> WiFiEndpointRefPtr;
+
 // WiFi class. Specialization of Device for WiFi.
 class WiFi : public Device {
  public:
@@ -35,6 +39,11 @@
                 const std::map<std::string, ::DBus::Variant> &properties);
   void ScanDone();
 
+  // called by WiFiService, to effect changes to wpa_supplicant
+  ::DBus::Path AddNetwork(
+      const std::map<std::string, ::DBus::Variant> &args);
+  void SelectNetwork(const ::DBus::Path &network);
+
  private:
   // SupplicantProcessProxy. provides access to wpa_supplicant's
   // process-level D-Bus APIs.
@@ -89,16 +98,27 @@
     WiFi &wifi_;
   };
 
+  typedef std::map<const std::string, WiFiEndpointRefPtr> EndpointMap;
+  typedef std::map<const std::string, ServiceRefPtr> ServiceMap;
+
   static const char kSupplicantPath[];
   static const char kSupplicantDBusAddr[];
   static const char kSupplicantWiFiDriver[];
   static const char kSupplicantErrorInterfaceExists[];
+  static const char kSupplicantKeyModeNone[];
 
+  void RealScanDone();
+
+  static unsigned int service_id_serial_;
+  ScopedRunnableMethodFactory<WiFi> task_factory_;
+  ControlInterface *control_interface_;
+  EventDispatcher *dispatcher_;
   DBus::Connection dbus_;
   scoped_ptr<SupplicantProcessProxy> supplicant_process_proxy_;
   scoped_ptr<SupplicantInterfaceProxy> supplicant_interface_proxy_;
   bool scan_pending_;
-  std::vector<std::string> ssids_;
+  EndpointMap endpoint_by_bssid_;
+  ServiceMap service_by_private_id_;
 
   // provide WiFiTest access to scan_pending_, so it can determine
   // if the scan completed, or timed out.
diff --git a/wifi_endpoint.cc b/wifi_endpoint.cc
new file mode 100644
index 0000000..a153f38
--- /dev/null
+++ b/wifi_endpoint.cc
@@ -0,0 +1,96 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <base/stringprintf.h>
+#include <base/string_number_conversions.h>
+
+#include "shill/wifi_endpoint.h"
+
+using std::string;
+
+namespace shill {
+
+const char WiFiEndpoint::kSupplicantPropertySSID[]   = "SSID";
+const char WiFiEndpoint::kSupplicantPropertyBSSID[]  = "BSSID";
+const char WiFiEndpoint::kSupplicantPropertySignal[] = "Signal";
+const char WiFiEndpoint::kSupplicantPropertyMode[]   = "Mode";
+
+const char WiFiEndpoint::kSupplicantNetworkModeInfrastructure[] =
+    "infrastructure";
+const char WiFiEndpoint::kSupplicantNetworkModeAdHoc[]       = "ad-hoc";
+const char WiFiEndpoint::kSupplicantNetworkModeAccessPoint[] = "ap";
+
+const uint32_t WiFiEndpoint::kSupplicantNetworkModeInfrastructureInt = 0;
+const uint32_t WiFiEndpoint::kSupplicantNetworkModeAdHocInt          = 1;
+const uint32_t WiFiEndpoint::kSupplicantNetworkModeAccessPointInt    = 2;
+
+WiFiEndpoint::WiFiEndpoint(
+    const std::map<string, ::DBus::Variant> &properties) {
+  // XXX will segfault on missing properties
+  ssid_ =
+      properties.find(kSupplicantPropertySSID)->second.
+      operator std::vector<uint8_t>();
+  bssid_ =
+      properties.find(kSupplicantPropertyBSSID)->second.
+      operator std::vector<uint8_t>();
+  signal_strength_ =
+      properties.find(kSupplicantPropertySignal)->second;
+  network_mode_ = parse_mode(
+      properties.find(kSupplicantPropertyMode)->second);
+
+  if (network_mode_ < 0) {
+    // XXX log error?
+  }
+
+  ssid_string_ = string(ssid_.begin(), ssid_.end());
+  ssid_hex_ = base::HexEncode(&(*ssid_.begin()), ssid_.size());
+  bssid_string_ = StringPrintf("%02x:%02x:%02x:%02x:%02x:%02x",
+                               bssid_[0], bssid_[1], bssid_[2],
+                               bssid_[3], bssid_[4], bssid_[5]);
+  bssid_hex_ = base::HexEncode(&(*bssid_.begin()), bssid_.size());
+}
+
+WiFiEndpoint::~WiFiEndpoint() {}
+
+const std::vector<uint8_t> &WiFiEndpoint::ssid() const {
+  return ssid_;
+}
+
+const string &WiFiEndpoint::ssid_string() const {
+  return ssid_string_;
+}
+
+const string &WiFiEndpoint::ssid_hex() const {
+  return ssid_hex_;
+}
+
+const string &WiFiEndpoint::bssid_string() const {
+  return bssid_string_;
+}
+
+const string &WiFiEndpoint::bssid_hex() const {
+  return bssid_hex_;
+}
+
+int16_t WiFiEndpoint::signal_strength() const {
+  return signal_strength_;
+}
+
+uint32_t WiFiEndpoint::network_mode() const {
+  return network_mode_;
+}
+
+int32_t WiFiEndpoint::parse_mode(const std::string &mode_string) {
+  if (mode_string == kSupplicantNetworkModeInfrastructure) {
+    return kSupplicantNetworkModeInfrastructureInt;
+  } else if (mode_string == kSupplicantNetworkModeAdHoc) {
+    return kSupplicantNetworkModeAdHocInt;
+  } else if (mode_string == kSupplicantNetworkModeAccessPoint) {
+    return kSupplicantNetworkModeAccessPointInt;
+  } else {
+    return -1;
+  }
+}
+
+}  // namespace shill
diff --git a/wifi_endpoint.h b/wifi_endpoint.h
new file mode 100644
index 0000000..8a7e15a
--- /dev/null
+++ b/wifi_endpoint.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHILL_WIFI_ENDPOINT_
+#define SHILL_WIFI_ENDPOINT_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/memory/ref_counted.h>
+#include <dbus-c++/dbus.h>
+
+#include "shill/endpoint.h"
+#include "shill/service.h"
+#include "shill/shill_event.h"
+
+namespace shill {
+
+class WiFiEndpoint : public Endpoint {
+ public:
+  WiFiEndpoint(const std::map<std::string, ::DBus::Variant> &properties);
+  virtual ~WiFiEndpoint();
+  const std::vector<uint8_t> &ssid() const;
+  const std::string &ssid_string() const;
+  const std::string &ssid_hex() const;
+  const std::string &bssid_string() const;
+  const std::string &bssid_hex() const;
+  int16_t signal_strength() const;
+  uint32_t network_mode() const;
+
+ private:
+  static const uint32_t kSupplicantNetworkModeInfrastructureInt;
+  static const uint32_t kSupplicantNetworkModeAdHocInt;
+  static const uint32_t kSupplicantNetworkModeAccessPointInt;
+
+  static const char kSupplicantPropertySSID[];
+  static const char kSupplicantPropertyBSSID[];
+  static const char kSupplicantPropertySignal[];
+  static const char kSupplicantPropertyMode[];
+  static const char kSupplicantNetworkModeInfrastructure[];
+  static const char kSupplicantNetworkModeAdHoc[];
+  static const char kSupplicantNetworkModeAccessPoint[];
+
+  static int32_t parse_mode(const std::string &mode_string);
+
+  std::vector<uint8_t> ssid_;
+  std::vector<uint8_t> bssid_;
+  std::string ssid_string_;
+  std::string ssid_hex_;
+  std::string bssid_string_;
+  std::string bssid_hex_;
+  int16_t signal_strength_;
+  uint32_t network_mode_;
+
+  DISALLOW_COPY_AND_ASSIGN(WiFiEndpoint);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_WIFI_ENDPOINT_
diff --git a/wifi_integrationtest.cc b/wifi_integrationtest.cc
index 1afdb40..9b52921 100644
--- a/wifi_integrationtest.cc
+++ b/wifi_integrationtest.cc
@@ -55,7 +55,6 @@
   }
 
   ~WiFiTest() {
-    wifi_->Release();
   }
 
   static gboolean TimeoutHandler(void *test_instance) {
@@ -65,7 +64,7 @@
 
  protected:
   DBusControl dbus_control_;
-  WiFi *wifi_;
+  scoped_refptr<WiFi> wifi_;
   bool timed_out_;
 };
 
diff --git a/wifi_service.cc b/wifi_service.cc
new file mode 100644
index 0000000..a12b111
--- /dev/null
+++ b/wifi_service.cc
@@ -0,0 +1,76 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "shill/wifi_service.h"
+
+#include <string>
+
+#include <base/logging.h>
+
+#include "shill/control_interface.h"
+#include "shill/device.h"
+#include "shill/shill_event.h"
+#include "shill/wifi.h"
+
+using std::string;
+
+namespace shill {
+const char WiFiService::kSupplicantPropertySSID[]        = "ssid";
+const char WiFiService::kSupplicantPropertyNetworkMode[] = "mode";
+const char WiFiService::kSupplicantPropertyKeyMode[]     = "key_mgmt";
+
+WiFiService::WiFiService(ControlInterface *control_interface,
+                         EventDispatcher *dispatcher,
+                         WiFi *device,
+                         const std::vector<uint8_t> ssid,
+                         uint32_t mode,
+                         const std::string &key_management,
+                         const std::string &name)
+    : Service(control_interface, dispatcher, device, name),
+      task_factory_(this),
+      wifi_(device),
+      ssid_(ssid),
+      mode_(mode),
+      key_management_(key_management) {
+}
+
+WiFiService::~WiFiService() {
+  LOG(INFO) << __func__;
+}
+
+void WiFiService::Connect() {
+  LOG(INFO) << __func__;
+
+  // NB(quiche) defer handling, since dbus-c++ does not permit us to
+  // send an outbound request while processing an inbound one.
+  dispatcher_->PostTask(
+      task_factory_.NewRunnableMethod(&WiFiService::RealConnect));
+}
+
+void WiFiService::RealConnect() {
+  std::map<string, DBus::Variant> add_network_args;
+  DBus::MessageIter mi;
+  DBus::Path network_path;
+
+  add_network_args[kSupplicantPropertyNetworkMode].writer().
+      append_uint32(mode_);
+  add_network_args[kSupplicantPropertyKeyMode].writer().
+      append_string(key_management_.c_str());
+  // TODO(quiche): figure out why we can't use operator<< without the
+  // temporary variable.
+  mi = add_network_args[kSupplicantPropertySSID].writer();
+  mi << ssid_;
+  // TODO(quiche): set scan_ssid=1, like flimflam does?
+
+  network_path = wifi_->AddNetwork(add_network_args);
+  wifi_->SelectNetwork(network_path);
+  // XXX add to favorite networks list?
+}
+
+void WiFiService::Disconnect() {
+  // TODO(quiche) RemoveNetwork from supplicant
+  // XXX remove from favorite networks list?
+}
+
+}  // namespace shill
diff --git a/wifi_service.h b/wifi_service.h
new file mode 100644
index 0000000..fd5650a
--- /dev/null
+++ b/wifi_service.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHILL_WIFI_SERVICE_
+#define SHILL_WIFI_SERVICE_
+
+#include <string>
+#include <vector>
+
+#include "shill/wifi.h"
+#include "shill/shill_event.h"
+#include "shill/service.h"
+#include "shill/supplicant-interface.h"
+
+namespace shill {
+
+class WiFiService : public Service {
+ public:
+  WiFiService(ControlInterface *control_interface,
+              EventDispatcher *dispatcher,
+              WiFi *device,
+              const std::vector<uint8_t> ssid,
+              uint32_t mode,
+              const std::string &key_management,
+              const std::string &name);
+  ~WiFiService();
+  void Connect();
+  void Disconnect();
+
+ private:
+  static const char kSupplicantPropertySSID[];
+  static const char kSupplicantPropertyNetworkMode[];
+  static const char kSupplicantPropertyKeyMode[];
+
+  void RealConnect();
+
+  ScopedRunnableMethodFactory<WiFiService> task_factory_;
+  WiFi *wifi_;
+  const std::vector<uint8_t> ssid_;
+  uint32_t mode_;
+  const std::string key_management_;
+  DISALLOW_COPY_AND_ASSIGN(WiFiService);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_WIFI_SERVICE_