wire-up WiFi class to D-Bus, implement rudimentary scanning support

BUG=chromium-os:14886
TEST=emerge, run wifi_integrationtest on device

Change-Id: I7ff0799d198b4eed0bf92afbef2c5591ca2a388e
Reviewed-on: http://gerrit.chromium.org/gerrit/1585
Reviewed-by: Chris Masone <cmasone@chromium.org>
Tested-by: mukesh agrawal <quiche@chromium.org>
diff --git a/Makefile b/Makefile
index 995efce..db63c09 100644
--- a/Makefile
+++ b/Makefile
@@ -35,7 +35,7 @@
 	supplicant-bss.h \
 	supplicant-interface.h \
 	supplicant-network.h \
-	supplicant-supplicant.h
+	supplicant-process.h
 
 DBUS_HEADERS = $(DBUS_ADAPTOR_HEADERS) $(DBUS_PROXY_HEADERS)
 
@@ -73,6 +73,7 @@
 	testrunner.o
 
 all: $(SHILL_BIN) $(TEST_BIN)
+integration_tests: wifi_integrationtest
 
 $(DBUS_PROXY_HEADERS): %.h: %.xml
 	$(DBUSXX_XML2CPP) $< --proxy=$@
@@ -97,5 +98,11 @@
 	$(CXX) $(CXXFLAGS) $(TEST_INCLUDE_DIRS) $(TEST_LIB_DIRS) $(LDFLAGS) $^ \
 		$(TEST_LIBS) -o $@
 
+# NB(quiche): statically link gmock, gtest, as test device will not have them
+wifi_integrationtest: CXXFLAGS += -DUNIT_TEST
+wifi_integrationtest: wifi_integrationtest.o $(SHILL_LIB)
+	$(CXX) $(CXXFLAGS) $(TEST_INCLUDE_DIRS) $(TEST_LIB_DIRS) $(LDFLAGS) $^ \
+		$(BASE_LIBS) -Wl,-Bstatic -lgmock -lgtest -Wl,-Bdynamic -o $@
+
 clean:
 	rm -rf *.o $(DBUS_HEADERS) $(SHILL_BIN) $(SHILL_LIB) $(TEST_BIN)
diff --git a/README b/README
new file mode 100644
index 0000000..a934c3c
--- /dev/null
+++ b/README
@@ -0,0 +1,5 @@
+wifi_integrationtest:
+- should be run as root
+- should be run on a system with wpa_supplicant running
+- will either report scan results, or fail by timing out after 10 seconds
+- to specify device name, use --device-name=<name>
\ No newline at end of file
diff --git a/supplicant-supplicant.xml b/supplicant-process.xml
similarity index 100%
rename from supplicant-supplicant.xml
rename to supplicant-process.xml
diff --git a/wifi.cc b/wifi.cc
index 7cd7ae3..43cbfc5 100644
--- a/wifi.cc
+++ b/wifi.cc
@@ -5,7 +5,9 @@
 #include <time.h>
 #include <stdio.h>
 
+#include <map>
 #include <string>
+#include <vector>
 
 #include <base/logging.h>
 
@@ -15,20 +17,158 @@
 
 #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";
+
+WiFi::SupplicantProcessProxy::SupplicantProcessProxy(DBus::Connection *bus)
+    : DBus::ObjectProxy(*bus, kSupplicantPath, kSupplicantDBusAddr) {}
+
+void WiFi::SupplicantProcessProxy::InterfaceAdded(
+    const ::DBus::Path& path,
+    const std::map<string, ::DBus::Variant> &properties) {
+  LOG(INFO) << __func__;
+  // XXX
+}
+
+void WiFi::SupplicantProcessProxy::InterfaceRemoved(const ::DBus::Path& path) {
+  LOG(INFO) << __func__;
+  // XXX
+}
+
+void WiFi::SupplicantProcessProxy::PropertiesChanged(
+    const std::map<string, ::DBus::Variant>& properties) {
+  LOG(INFO) << __func__;
+  // XXX
+}
+
+WiFi::SupplicantInterfaceProxy::SupplicantInterfaceProxy(
+    WiFi *wifi,
+    DBus::Connection *bus,
+    const ::DBus::Path &object_path)
+    : wifi_(*wifi),
+      DBus::ObjectProxy(*bus, object_path, kSupplicantDBusAddr) {}
+
+void WiFi::SupplicantInterfaceProxy::ScanDone(const bool& success) {
+  LOG(INFO) << __func__ << " " << success;
+  if (success) {
+    wifi_.ScanDone();
+  }
+}
+
+void WiFi::SupplicantInterfaceProxy::BSSAdded(
+    const ::DBus::Path &BSS,
+    const std::map<string, ::DBus::Variant> &properties) {
+  LOG(INFO) << __func__;
+  wifi_.BSSAdded(BSS, properties);
+}
+
+void WiFi::SupplicantInterfaceProxy::BSSRemoved(const ::DBus::Path &BSS) {
+  LOG(INFO) << __func__;
+  // XXX
+}
+
+void WiFi::SupplicantInterfaceProxy::BlobAdded(const string &blobname) {
+  LOG(INFO) << __func__;
+  // XXX
+}
+
+void WiFi::SupplicantInterfaceProxy::BlobRemoved(const string &blobname) {
+  LOG(INFO) << __func__;
+  // XXX
+}
+
+void WiFi::SupplicantInterfaceProxy::NetworkAdded(
+    const ::DBus::Path &network,
+    const std::map<string, ::DBus::Variant> &properties) {
+  LOG(INFO) << __func__;
+  // XXX
+}
+
+void WiFi::SupplicantInterfaceProxy::NetworkRemoved(
+    const ::DBus::Path &network) {
+  LOG(INFO) << __func__;
+  // XXX
+}
+
+void WiFi::SupplicantInterfaceProxy::NetworkSelected(
+    const ::DBus::Path &network) {
+  LOG(INFO) << __func__;
+  // XXX
+}
+
+void WiFi::SupplicantInterfaceProxy::PropertiesChanged(
+    const std::map<string, ::DBus::Variant> &properties) {
+  LOG(INFO) << __func__;
+  // XXX
+}
+
+// NB: we assume supplicant is already running. [quiche.20110518]
 WiFi::WiFi(ControlInterface *control_interface,
            EventDispatcher *dispatcher,
            Manager *manager,
            const std::string& link_name,
            int interface_index)
     : Device(control_interface, dispatcher, manager, link_name,
-             interface_index) {
+             interface_index),
+      dbus_(DBus::Connection::SystemBus()), scan_pending_(false) {
   VLOG(2) << "WiFi device " << link_name << " initialized.";
 }
 
 WiFi::~WiFi() {
 }
 
+void WiFi::Start() {
+  std::map<string, DBus::Variant> create_interface_args;
+  std::map<string, DBus::Variant> scan_args;
+  ::DBus::Path interface_path;
+
+  supplicant_process_proxy_.reset(new SupplicantProcessProxy(&dbus_));
+  create_interface_args["Ifname"].writer().append_string(link_name_.c_str());
+  create_interface_args["Driver"].writer().append_string(kSupplicantWiFiDriver);
+  // TODO(quiche) create_interface_args["ConfigFile"].writer().append_string
+  // (file with pkcs config info)
+  interface_path =
+      supplicant_process_proxy_->CreateInterface(create_interface_args);
+  // TODO(quiche) if CreateInterface failed: call GetInterface instead
+  LOG(INFO) << "creating SupplicantInterfaceProxy.";
+  supplicant_interface_proxy_.reset(
+      new SupplicantInterfaceProxy(this, &dbus_, interface_path));
+  // TODO(quiche) set ApScan=1 and BSSExpireAge=190, like flimflam does?
+  scan_args["Type"].writer().append_string("active");
+  LOG(INFO) << "initiating Scan.";
+  // TODO(quiche) indicate scanning in UI
+  supplicant_interface_proxy_->Scan(scan_args);
+  scan_pending_ = true;
+  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
+  Device::Stop();
+}
+
 bool WiFi::TechnologyIs(const Device::Technology type) {
   return type == Device::kWifi;
 }
diff --git a/wifi.h b/wifi.h
index e61e2b2..d962de4 100644
--- a/wifi.h
+++ b/wifi.h
@@ -5,15 +5,18 @@
 #ifndef SHILL_WIFI_
 #define SHILL_WIFI_
 
+#include <map>
 #include <string>
+#include <vector>
 
 #include "shill/device.h"
 #include "shill/shill_event.h"
+#include "shill/supplicant-process.h"
+#include "shill/supplicant-interface.h"
 
 namespace shill {
 
-// Device superclass.  Individual network interfaces types will inherit from
-// this class.
+// WiFi class. Specialization of Device for WiFi.
 class WiFi : public Device {
  public:
   WiFi(ControlInterface *control_interface,
@@ -21,9 +24,85 @@
        Manager *manager,
        const std::string& link_name,
        int interface_index);
-  ~WiFi();
-  bool TechnologyIs(Device::Technology type);
+  virtual ~WiFi();
+  virtual void Start();
+  virtual void Stop();
+  virtual bool TechnologyIs(const Technology type);
+
+  // called by SupplicantInterfaceProxy, in response to events from
+  // wpa_supplicant.
+  void BSSAdded(const ::DBus::Path &BSS,
+                const std::map<std::string, ::DBus::Variant>
+                &properties);
+  void ScanDone();
+
  private:
+  // SupplicantProcessProxy. provides access to wpa_supplicant's
+  // process-level D-Bus APIs.
+  class SupplicantProcessProxy :
+      public fi::w1::wpa_supplicant1_proxy,
+        private ::DBus::ObjectProxy  // used by dbus-c++, not WiFi
+  {
+   public:
+    explicit SupplicantProcessProxy(DBus::Connection *bus);
+
+   private:
+    // called by dbus-c++, via wpa_supplicant1_proxy interface,
+    // in response to signals from wpa_supplicant. not exposed
+    // to WiFi.
+    virtual void InterfaceAdded(
+        const ::DBus::Path &path,
+        const std::map<std::string, ::DBus::Variant> &properties);
+    virtual void InterfaceRemoved(const ::DBus::Path &path);
+    virtual void PropertiesChanged(
+        const std::map<std::string, ::DBus::Variant> &properties);
+  };
+
+  // SupplicantInterfaceProxy. provides access to wpa_supplicant's
+  // network-interface D-Bus APIs.
+  class SupplicantInterfaceProxy :
+      public fi::w1::wpa_supplicant1::Interface_proxy,
+      private ::DBus::ObjectProxy  // used by dbus-c++, not WiFi
+  {
+   public:
+    SupplicantInterfaceProxy(WiFi *wifi, DBus::Connection *bus,
+                             const ::DBus::Path &object_path);
+
+   private:
+    // called by dbus-c++, via Interface_proxy interface,
+    // in response to signals from wpa_supplicant. not exposed
+    // to WiFi.
+    virtual void ScanDone(const bool &success);
+    virtual void BSSAdded(const ::DBus::Path &BSS,
+                          const std::map<std::string, ::DBus::Variant>
+                          &properties);
+    virtual void BSSRemoved(const ::DBus::Path &BSS);
+    virtual void BlobAdded(const std::string &blobname);
+    virtual void BlobRemoved(const std::string &blobname);
+    virtual void NetworkAdded(const ::DBus::Path &network,
+                              const std::map<std::string, ::DBus::Variant>
+                              &properties);
+    virtual void NetworkRemoved(const ::DBus::Path &network);
+    virtual void NetworkSelected(const ::DBus::Path &network);
+    virtual void PropertiesChanged(const std::map<std::string, ::DBus::Variant>
+                                   &properties);
+
+    WiFi &wifi_;
+  };
+
+  static const char kSupplicantPath[];
+  static const char kSupplicantDBusAddr[];
+  static const char kSupplicantWiFiDriver[];
+
+  DBus::Connection dbus_;
+  scoped_ptr<SupplicantProcessProxy> supplicant_process_proxy_;
+  scoped_ptr<SupplicantInterfaceProxy> supplicant_interface_proxy_;
+  bool scan_pending_;
+  std::vector<std::string> ssids_;
+
+  // provide WiFiTest access to scan_pending_, so it can determine
+  // if the scan completed, or timed out.
+  friend class WiFiTest;
   DISALLOW_COPY_AND_ASSIGN(WiFi);
 };
 
diff --git a/wifi_integrationtest.cc b/wifi_integrationtest.cc
new file mode 100644
index 0000000..969d845
--- /dev/null
+++ b/wifi_integrationtest.cc
@@ -0,0 +1,110 @@
+// 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 <iostream>
+#include <string>
+
+#include <base/at_exit.h>
+#include <base/command_line.h>
+#include <gtest/gtest.h>
+
+#include <glib.h>
+#include <dbus-c++/dbus.h>
+#include <dbus-c++/glib-integration.h>
+
+#include "shill/dbus_control.h"
+#include "shill/device_info.h"
+#include "shill/wifi.h"
+
+namespace switches {
+// wi-fi device name
+static const char kDeviceName[] = "device-name";
+// Flag that causes shill to show the help message and exit.
+static const char kHelp[] = "help";
+// The help message shown if help flag is passed to the program.
+static const char kHelpMessage[] = "\n"
+    "Switches for " __FILE__ "\n"
+    "  --device-name\n"
+    "    name of wi-fi device (e.g. wlan0).\n";
+}  // namespace switches
+
+namespace shill {
+using ::testing::Test;
+
+const int kInterfaceIndexUnknown = -1;
+const unsigned int kScanTimeoutSecs = 60;
+const char kDefaultDeviceName[] = "wlan0";
+DBus::Glib::BusDispatcher dbus_glib_dispatcher;
+std::string device_name;
+
+class WiFiTest : public Test {
+ public:
+  WiFiTest() : timed_out_(false) {
+    wifi_ = new WiFi(&dbus_control_, NULL, NULL, device_name,
+                     kInterfaceIndexUnknown);
+  }
+
+  bool ScanPending() {
+    return wifi_->scan_pending_;
+  }
+
+  void TimeOut() {
+    timed_out_ = true;
+  }
+
+  ~WiFiTest() {
+    wifi_->Release();
+  }
+
+  static gboolean TimeoutHandler(void *test_instance) {
+    static_cast<WiFiTest *>(test_instance)->TimeOut();
+    return false;
+  }
+
+ protected:
+  DBusControl dbus_control_;
+  WiFi *wifi_;
+  bool timed_out_;
+};
+
+
+TEST_F(WiFiTest, SSIDScanning) {
+  wifi_->Start();
+  g_timeout_add_seconds(10, WiFiTest::TimeoutHandler, this);
+
+  // Crank the glib main loop
+  while (ScanPending() && !timed_out_) {
+    LOG(INFO) << "cranking event loop";
+    g_main_context_iteration(NULL, TRUE);
+  }
+
+  ASSERT_FALSE(timed_out_);
+}
+
+}  // namespace shill
+
+int main(int argc, char **argv) {
+  base::AtExitManager exit_manager;
+  ::testing::InitGoogleTest(&argc, argv);
+  CommandLine::Init(argc, argv);
+  CommandLine *cl = CommandLine::ForCurrentProcess();
+
+  if (cl->HasSwitch(switches::kHelp)) {
+    // NB(quiche): google test prints test framework help message
+    // at InitGoogleTest, above.
+    std::cout << switches::kHelpMessage;
+    return 0;
+  }
+
+  if (cl->HasSwitch(switches::kDeviceName)) {
+    shill::device_name =
+        cl->GetSwitchValueASCII(switches::kDeviceName);
+  } else {
+    shill::device_name = shill::kDefaultDeviceName;
+  }
+
+  shill::dbus_glib_dispatcher.attach(NULL);  // NULL => default context
+  DBus::default_dispatcher = &shill::dbus_glib_dispatcher;
+  return RUN_ALL_TESTS();
+}