shill: implement Manager.GetService (error-case only)

BUG=chromium-os:20254
TEST=unittests, WiFiManager/000_SSID_Length_Limit

this gives us enough to pass the autotest for
network_WiFiManager/000_SSID_Length_Limit.

Change-Id: Ib0305e707d2203327d846be3e0b206033d6a884a
Reviewed-on: http://gerrit.chromium.org/gerrit/7567
Commit-Ready: mukesh agrawal <quiche@chromium.org>
Reviewed-by: mukesh agrawal <quiche@chromium.org>
Tested-by: mukesh agrawal <quiche@chromium.org>
diff --git a/Makefile b/Makefile
index 050658d..05a2f6d 100644
--- a/Makefile
+++ b/Makefile
@@ -101,6 +101,7 @@
 	ipconfig.o \
 	ipconfig_dbus_adaptor.o \
 	key_file_store.o \
+	key_value_store.o \
 	manager.o \
 	manager_dbus_adaptor.o \
 	modem.o \
@@ -183,6 +184,7 @@
 	mock_store.o \
 	mock_supplicant_interface_proxy.o \
 	mock_supplicant_process_proxy.o \
+	mock_wifi.o \
 	modem_info_unittest.o \
 	modem_manager_unittest.o \
 	modem_unittest.o \
diff --git a/cellular_service.cc b/cellular_service.cc
index 26556d3..f2c2b10 100644
--- a/cellular_service.cc
+++ b/cellular_service.cc
@@ -23,10 +23,9 @@
                                  EventDispatcher *dispatcher,
                                  Manager *manager,
                                  const CellularRefPtr &device)
-    : Service(control_interface, dispatcher, manager),
+    : Service(control_interface, dispatcher, manager, flimflam::kTypeCellular),
       strength_(0),
-      cellular_(device),
-      type_(flimflam::kTypeCellular) {
+      cellular_(device) {
   PropertyStore *store = this->mutable_store();
   store->RegisterConstString(flimflam::kActivationStateProperty,
                              &activation_state_);
@@ -40,7 +39,6 @@
   store->RegisterConstStringmap(flimflam::kServingOperatorProperty,
                                 &serving_operator_.ToDict());
   store->RegisterConstUint8(flimflam::kSignalStrengthProperty, &strength_);
-  store->RegisterConstString(flimflam::kTypeProperty, &type_);
   store->RegisterConstString(flimflam::kUsageURLProperty, &usage_url_);
 }
 
diff --git a/cellular_service.h b/cellular_service.h
index b2ab054..072a08e 100644
--- a/cellular_service.h
+++ b/cellular_service.h
@@ -78,7 +78,6 @@
   std::map<std::string, std::string> last_good_apn_info_;
 
   CellularRefPtr cellular_;
-  const std::string type_;
 
   DISALLOW_COPY_AND_ASSIGN(CellularService);
 };
diff --git a/dbus_adaptor.cc b/dbus_adaptor.cc
index a8cbc45..808b325 100644
--- a/dbus_adaptor.cc
+++ b/dbus_adaptor.cc
@@ -12,6 +12,7 @@
 #include "shill/accessor_interface.h"
 #include "shill/dbus_adaptor.h"
 #include "shill/error.h"
+#include "shill/key_value_store.h"
 #include "shill/property_store.h"
 
 using std::map;
@@ -133,6 +134,28 @@
 }
 
 // static
+void DBusAdaptor::ArgsToKeyValueStore(
+    const map<string, ::DBus::Variant> &args,
+    KeyValueStore *out,
+    Error *error) {  // XXX should be ::DBus::Error?
+  for (map<string, ::DBus::Variant>::const_iterator it = args.begin();
+       it != args.end();
+       ++it) {
+    DBus::type<string> string_type;
+    DBus::type<bool> bool_type;
+
+    if (it->second.signature() == string_type.sig()) {
+      out->SetString(it->first, it->second.reader().get_string());
+    } else if (it->second.signature() == bool_type.sig()) {
+      out->SetBool(it->first, it->second.reader().get_bool());
+    } else {
+      error->Populate(Error::kInternalError);
+      return;  // skip remaining args after error
+    }
+  }
+}
+
+// static
 ::DBus::Variant DBusAdaptor::BoolToVariant(bool value) {
   ::DBus::Variant v;
   v.writer().append_bool(value);
diff --git a/dbus_adaptor.h b/dbus_adaptor.h
index 6676984..6523f05 100644
--- a/dbus_adaptor.h
+++ b/dbus_adaptor.h
@@ -19,6 +19,8 @@
 #define SHILL_INTERFACE "org.chromium.flimflam"
 #define SHILL_PATH "/org/chromium/flimflam"
 
+class Error;
+class KeyValueStore;
 class PropertyStore;
 
 // Superclass for all DBus-backed Adaptor objects
@@ -35,6 +37,10 @@
   static bool GetProperties(const PropertyStore &store,
                             std::map<std::string, ::DBus::Variant> *out,
                             ::DBus::Error *error);
+  static void ArgsToKeyValueStore(
+      const std::map<std::string, ::DBus::Variant> &args,
+      KeyValueStore *out,
+      Error *error);
 
   static ::DBus::Variant BoolToVariant(bool value);
   static ::DBus::Variant ByteToVariant(uint8 value);
diff --git a/dbus_adaptor_unittest.cc b/dbus_adaptor_unittest.cc
index 36b68b3..c93acfe 100644
--- a/dbus_adaptor_unittest.cc
+++ b/dbus_adaptor_unittest.cc
@@ -12,6 +12,7 @@
 #include <gtest/gtest.h>
 #include <gmock/gmock.h>
 
+#include "shill/key_value_store.h"
 #include "shill/manager.h"
 #include "shill/mock_control.h"
 #include "shill/mock_device.h"
@@ -186,4 +187,17 @@
   EXPECT_TRUE(DBusAdaptor::DispatchOnType(&store, "", byte_v_, &e10));
 }
 
+TEST_F(DBusAdaptorTest, ArgsToKeyValueStore) {
+    map<string, ::DBus::Variant> args;
+    KeyValueStore args_kv;
+    Error error;
+
+    args["string_arg"].writer().append_string("string");
+    args["bool_arg"].writer().append_bool(true);
+    DBusAdaptor::ArgsToKeyValueStore(args, &args_kv, &error);
+    EXPECT_TRUE(error.IsSuccess());
+    EXPECT_EQ("string", args_kv.GetString("string_arg"));
+    EXPECT_EQ(true, args_kv.GetBool("bool_arg"));
+}
+
 }  // namespace shill
diff --git a/error.cc b/error.cc
index 030e598..e486bb5 100644
--- a/error.cc
+++ b/error.cc
@@ -65,9 +65,12 @@
   message_ = message;
 }
 
-void Error::ToDBusError(::DBus::Error *error) const {
+bool Error::ToDBusError(::DBus::Error *error) const {
   if (IsFailure()) {
     error->set(GetName(type_).c_str(), message_.c_str());
+    return true;
+  } else {
+    return false;
   }
 }
 
diff --git a/error.h b/error.h
index dc2ab19..c1fb2b1 100644
--- a/error.h
+++ b/error.h
@@ -49,9 +49,9 @@
   void Populate(Type type);  // Uses the default message for |type|.
   void Populate(Type type, const std::string &message);
 
-  // Sets the DBus |error| to this error if it's failure. Leaves |error|
-  // unchanged otherwise.
-  void ToDBusError(::DBus::Error *error) const;
+  // Sets the DBus |error| and returns true if Error represents failure.
+  // Leaves |error| unchanged, and returns false, otherwise.
+  bool ToDBusError(::DBus::Error *error) const;
 
   Type type() const { return type_; }
   const std::string &message() const { return message_; }
diff --git a/ethernet_service.cc b/ethernet_service.cc
index 8e1cdd6..679266c 100644
--- a/ethernet_service.cc
+++ b/ethernet_service.cc
@@ -34,12 +34,9 @@
                                  EventDispatcher *dispatcher,
                                  Manager *manager,
                                  const EthernetRefPtr &device)
-    : Service(control_interface, dispatcher, manager),
-      ethernet_(device),
-      type_(flimflam::kTypeEthernet) {
+    : Service(control_interface, dispatcher, manager, flimflam::kTypeEthernet),
+      ethernet_(device) {
   set_auto_connect(true);
-
-  mutable_store()->RegisterConstString(flimflam::kTypeProperty, &type_);
 }
 
 EthernetService::~EthernetService() { }
diff --git a/ethernet_service.h b/ethernet_service.h
index 2934c6a..e132f50 100644
--- a/ethernet_service.h
+++ b/ethernet_service.h
@@ -38,7 +38,6 @@
   std::string GetDeviceRpcId();
 
   EthernetRefPtr ethernet_;
-  const std::string type_;
   DISALLOW_COPY_AND_ASSIGN(EthernetService);
 };
 
diff --git a/ieee80211.h b/ieee80211.h
new file mode 100644
index 0000000..5fa34bd
--- /dev/null
+++ b/ieee80211.h
@@ -0,0 +1,21 @@
+// 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_IEEE80211_H
+#define SHILL_IEEE80211_H
+
+namespace shill {
+
+namespace IEEE_80211 {
+const unsigned int kMaxSSIDLen = 32;
+
+const unsigned int kWEP40AsciiLen = 5;
+const unsigned int kWEP40HexLen = 10;
+const unsigned int kWEP104AsciiLen = 13;
+const unsigned int kWEP104HexLen = 26;
+};
+
+}  // namespace shill
+
+#endif  // SHILL_IEEE_80211_H
diff --git a/key_value_store.cc b/key_value_store.cc
new file mode 100644
index 0000000..bc0f55b
--- /dev/null
+++ b/key_value_store.cc
@@ -0,0 +1,45 @@
+// 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/key_value_store.h"
+
+#include <base/logging.h>
+#include <base/stl_util-inl.h>
+
+using std::map;
+using std::string;
+
+namespace shill {
+
+KeyValueStore::KeyValueStore() {}
+
+bool KeyValueStore::ContainsBool(const string &name) const {
+  return ContainsKey(bool_properties_, name);
+}
+
+bool KeyValueStore::ContainsString(const string &name) const {
+  return ContainsKey(string_properties_, name);
+}
+
+bool KeyValueStore::GetBool(const string &name) const {
+  map<string, bool>::const_iterator it(bool_properties_.find(name));
+  CHECK(it != bool_properties_.end());
+  return it->second;
+}
+
+const string &KeyValueStore::GetString(const string &name) const {
+  map<string, string>::const_iterator it(string_properties_.find(name));
+  CHECK(it != string_properties_.end());
+  return it->second;
+}
+
+void KeyValueStore::SetBool(const string &name, bool value) {
+  bool_properties_[name] = value;
+}
+
+void KeyValueStore::SetString(const string &name, const string &value) {
+  string_properties_[name] = value;
+}
+
+}  // namespace shill
diff --git a/key_value_store.h b/key_value_store.h
new file mode 100644
index 0000000..bf02600
--- /dev/null
+++ b/key_value_store.h
@@ -0,0 +1,51 @@
+// 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_KEY_VALUE_STORE_
+#define SHILL_KEY_VALUE_STORE_
+
+#include <map>
+#include <string>
+
+#include <base/basictypes.h>
+
+namespace shill {
+
+class KeyValueStore {
+  // A simple store for key-value pairs, which supports (a limited set of)
+  // heterogenous value types.
+  //
+  // Compare to PropertyStore, which enables a class to (selectively)
+  // expose its instance members as properties accessible via
+  // RPC. (RPC support for ProperyStore is implemented in a
+  // protocol-specific adaptor. e.g. dbus_adpator.)
+  //
+  // Implemented separately from PropertyStore, to avoid complicating
+  // the PropertyStore interface. In particular, objects implementing the
+  // PropertyStore interface always provide the storage themselves. In
+  // contrast, users of KeyValueStore expect KeyValueStore to provide
+  // storage.
+ public:
+  KeyValueStore();
+
+  bool ContainsBool(const std::string &name) const;
+  bool ContainsString(const std::string &name) const;
+
+  bool GetBool(const std::string &name) const;
+  const std::string &GetString(const std::string &name) const;
+
+  void SetBool(const std::string &name, bool value);
+  void SetString(const std::string& name,
+                         const std::string& value);
+
+ private:
+  std::map<std::string, bool> bool_properties_;
+  std::map<std::string, std::string> string_properties_;
+
+  DISALLOW_COPY_AND_ASSIGN(KeyValueStore);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_KEY_VALUE_STORE_
diff --git a/manager.cc b/manager.cc
index 996c291..3b109e2 100644
--- a/manager.cc
+++ b/manager.cc
@@ -7,6 +7,7 @@
 #include <time.h>
 #include <stdio.h>
 
+#include <map>
 #include <string>
 #include <vector>
 
@@ -29,12 +30,17 @@
 #include "shill/resolver.h"
 #include "shill/shill_event.h"
 #include "shill/service.h"
+#include "shill/wifi.h"
+#include "shill/wifi_service.h"
 
 using std::string;
 using std::vector;
 
 namespace shill {
 
+// static
+const char Manager::kManagerErrorNoDevice[] = "no wifi devices available";
+
 Manager::Manager(ControlInterface *control_interface,
                  EventDispatcher *dispatcher,
                  GLib *glib,
@@ -304,6 +310,21 @@
 }
 
 // called via RPC (e.g., from ManagerDBusAdaptor)
+WiFiServiceRefPtr Manager::GetWifiService(const KeyValueStore &args,
+                                          Error *error) {
+  std::vector<DeviceRefPtr> wifi_devices;
+  FilterByTechnology(Device::kWifi, &wifi_devices);
+  if (wifi_devices.empty()) {
+    error->Populate(Error::kInvalidArguments, kManagerErrorNoDevice);
+    return NULL;
+  } else {
+    WiFi *wifi = dynamic_cast<WiFi *>(wifi_devices.front().get());
+    CHECK(wifi);
+    return wifi->GetService(args, error);
+  }
+}
+
+// called via RPC (e.g., from ManagerDBusAdaptor)
 void Manager::RequestScan(const std::string &technology, Error *error) {
   if (technology == flimflam::kTypeWifi || technology == "") {
     vector<DeviceRefPtr> wifi_devices;
diff --git a/manager.h b/manager.h
index 6bddeb5..bd03330 100644
--- a/manager.h
+++ b/manager.h
@@ -19,6 +19,7 @@
 #include "shill/property_store.h"
 #include "shill/service.h"
 #include "shill/shill_event.h"
+#include "shill/wifi.h"
 
 namespace shill {
 
@@ -69,6 +70,7 @@
   std::vector<std::string> EnumerateAvailableServices();
 
   // called via RPC (e.g., from ManagerDBusAdaptor)
+  WiFiServiceRefPtr GetWifiService(const KeyValueStore &args, Error *error);
   void RequestScan(const std::string &technology, Error *error);
 
   virtual DeviceInfo *device_info() { return &device_info_; }
@@ -83,6 +85,8 @@
  private:
   friend class ManagerAdaptorInterface;
 
+  static const char kManagerErrorNoDevice[];
+
   std::string CalculateState();
   std::vector<std::string> AvailableTechnologies();
   std::vector<std::string> ConnectedTechnologies();
diff --git a/manager_dbus_adaptor.cc b/manager_dbus_adaptor.cc
index 36ace30..9318401 100644
--- a/manager_dbus_adaptor.cc
+++ b/manager_dbus_adaptor.cc
@@ -13,7 +13,9 @@
 
 #include "shill/device.h"
 #include "shill/error.h"
+#include "shill/key_value_store.h"
 #include "shill/manager.h"
+#include "shill/wifi_service.h"
 
 using std::map;
 using std::string;
@@ -132,16 +134,32 @@
                                            ::DBus::Error &error) {
 }
 
+// deprecated synonym for GetWifiService
 ::DBus::Path ManagerDBusAdaptor::GetService(
-    const map<string, ::DBus::Variant> &,
+    const map<string, ::DBus::Variant> &args,
     ::DBus::Error &error) {
-  return ::DBus::Path();
+  return GetWifiService(args, error);
 }
 
+// called, e.g., to get Service handle for a hidden SSID
 ::DBus::Path ManagerDBusAdaptor::GetWifiService(
-    const map<string, ::DBus::Variant> &,
+    const map<string, ::DBus::Variant> &args,
     ::DBus::Error &error) {
-  return ::DBus::Path();
+  KeyValueStore args_store;
+  Error e;
+  WiFiServiceRefPtr service;
+  string ret;
+
+  DBusAdaptor::ArgsToKeyValueStore(args, &args_store, &e);
+  if (e.IsSuccess()) {
+    service = manager_->GetWifiService(args_store, &e);
+  }
+
+  if (e.ToDBusError(&error)) {
+    return "/";  // ensure return is syntactically valid
+  } else {
+    return service->GetRpcIdentifier();
+  }
 }
 
 void ManagerDBusAdaptor::ConfigureWifiService(
diff --git a/manager_unittest.cc b/manager_unittest.cc
index 960fddb..242d6e9 100644
--- a/manager_unittest.cc
+++ b/manager_unittest.cc
@@ -16,13 +16,16 @@
 
 #include "shill/adaptor_interfaces.h"
 #include "shill/error.h"
+#include "shill/key_value_store.h"
 #include "shill/mock_adaptors.h"
 #include "shill/mock_control.h"
 #include "shill/mock_device.h"
 #include "shill/mock_glib.h"
 #include "shill/mock_profile.h"
 #include "shill/mock_service.h"
+#include "shill/mock_wifi.h"
 #include "shill/property_store_unittest.h"
+#include "shill/wifi_service.h"
 
 using std::map;
 using std::set;
@@ -55,7 +58,14 @@
                                                manager(),
                                                "null3",
                                                "addr3",
-                                               3)) {
+                                               3)),
+        mock_wifi_(new NiceMock<MockWiFi>(control_interface(),
+                                          dispatcher(),
+                                          manager(),
+                                          "wifi0",
+                                          "addr4",
+                                          4))
+  {
   }
   virtual ~ManagerTest() {}
 
@@ -69,6 +79,7 @@
   scoped_refptr<MockDevice> mock_device_;
   scoped_refptr<MockDevice> mock_device2_;
   scoped_refptr<MockDevice> mock_device3_;
+  scoped_refptr<MockWiFi> mock_wifi_;
 };
 
 TEST_F(ManagerTest, Contains) {
@@ -316,4 +327,23 @@
   }
 }
 
+TEST_F(ManagerTest, GetWifiServiceNoDevice) {
+  KeyValueStore args;
+  Error e;
+  manager()->GetWifiService(args, &e);
+  EXPECT_EQ(Error::kInvalidArguments, e.type());
+  EXPECT_EQ("no wifi devices available", e.message());
+}
+
+TEST_F(ManagerTest, GetWifiService) {
+  KeyValueStore args;
+  Error e;
+  WiFiServiceRefPtr wifi_service;
+
+  manager()->RegisterDevice(mock_wifi_);
+  EXPECT_CALL(*mock_wifi_, GetService(_, _))
+      .WillRepeatedly(Return(wifi_service));
+  manager()->GetWifiService(args, &e);
+}
+
 }  // namespace shill
diff --git a/mock_service.cc b/mock_service.cc
index a88fbc8..6125479 100644
--- a/mock_service.cc
+++ b/mock_service.cc
@@ -24,7 +24,7 @@
 MockService::MockService(ControlInterface *control_interface,
                          EventDispatcher *dispatcher,
                          Manager *manager)
-    : Service(control_interface, dispatcher, manager) {
+    : Service(control_interface, dispatcher, manager, "mock") {
   ON_CALL(*this, GetRpcIdentifier()).WillByDefault(Return(""));
 }
 
diff --git a/mock_wifi.cc b/mock_wifi.cc
new file mode 100644
index 0000000..68fa119
--- /dev/null
+++ b/mock_wifi.cc
@@ -0,0 +1,28 @@
+// 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/mock_wifi.h"
+
+#include <string>
+
+namespace shill {
+
+using std::string;
+
+MockWiFi::MockWiFi(ControlInterface *control_interface,
+                   EventDispatcher *dispatcher,
+                   Manager *manager,
+                   const string &link_name,
+                   const string &address,
+                   int interface_index)
+    : WiFi(control_interface,
+           dispatcher,
+           manager,
+           link_name,
+           address,
+           interface_index) {}
+
+MockWiFi::~MockWiFi() {}
+
+}  // namespace shill
diff --git a/mock_wifi.h b/mock_wifi.h
new file mode 100644
index 0000000..9dc8430
--- /dev/null
+++ b/mock_wifi.h
@@ -0,0 +1,46 @@
+// 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_MOCK_WIFI_
+#define SHILL_MOCK_WIFI_
+
+#include <string>
+
+#include <base/memory/ref_counted.h>
+#include <gmock/gmock.h>
+
+#include "shill/key_value_store.h"
+#include "shill/refptr_types.h"
+#include "shill/wifi.h"
+#include "shill/wifi_service.h"
+
+namespace shill {
+
+class ControlInterface;
+class Error;
+class EventDispatcher;
+
+class MockWiFi : public WiFi {
+ public:
+  MockWiFi(ControlInterface *control_interface,
+           EventDispatcher *dispatcher,
+           Manager *manager,
+           const std::string &link_name,
+           const std::string &address,
+           int interface_index);
+  virtual ~MockWiFi();
+
+  MOCK_METHOD0(Start, void());
+  MOCK_METHOD0(Stop, void());
+  MOCK_METHOD1(Scan, void(Error *error));
+  MOCK_METHOD2(GetService,
+               WiFiServiceRefPtr(const KeyValueStore &args, Error *error));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockWiFi);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_MOCK_WIFI_
diff --git a/service.cc b/service.cc
index 29b2f3a..f4867bc 100644
--- a/service.cc
+++ b/service.cc
@@ -65,7 +65,8 @@
 
 Service::Service(ControlInterface *control_interface,
                  EventDispatcher *dispatcher,
-                 Manager *manager)
+                 Manager *manager,
+                 const string &type)
     : state_(kStateUnknown),
       failure_(kFailureUnknown),
       auto_connect_(false),
@@ -74,6 +75,7 @@
       favorite_(false),
       priority_(kPriorityNone),
       save_credentials_(true),
+      type_(type),
       dispatcher_(dispatcher),
       name_(base::UintToString(serial_number_++)),
       available_(false),
@@ -137,6 +139,7 @@
   // store_.RegisterConstStringmap(flimflam::kProviderProperty, &provider_);
 
   store_.RegisterBool(flimflam::kSaveCredentialsProperty, &save_credentials_);
+  store_.RegisterConstString(flimflam::kTypeProperty, &type_);
   // flimflam::kSecurityProperty: Registered in WiFiService
   HelpRegisterDerivedString(flimflam::kStateProperty,
                             &Service::CalculateState,
diff --git a/service.h b/service.h
index 6f7cb8e..032693f 100644
--- a/service.h
+++ b/service.h
@@ -25,6 +25,7 @@
 class Endpoint;
 class Error;
 class EventDispatcher;
+class KeyValueStore;
 class Manager;
 class ServiceAdaptorInterface;
 class StoreInterface;
@@ -86,7 +87,8 @@
   // A constructor for the Service object
   Service(ControlInterface *control_interface,
           EventDispatcher *dispatcher,
-          Manager *manager);
+          Manager *manager,
+          const std::string &type);
   virtual ~Service();
 
   virtual void Connect(Error *error) = 0;
@@ -221,6 +223,7 @@
   std::string proxy_config_;
   bool save_credentials_;
   EapCredentials eap_;  // Only saved if |save_credentials_| is true.
+  const std::string type_;
 
   ProfileRefPtr profile_;
   PropertyStore store_;
diff --git a/service_unittest.cc b/service_unittest.cc
index 35063f0..efbd149 100644
--- a/service_unittest.cc
+++ b/service_unittest.cc
@@ -45,7 +45,7 @@
   ServiceUnderTest(ControlInterface *control_interface,
                    EventDispatcher *dispatcher,
                    Manager *manager)
-      : Service(control_interface, dispatcher, manager) {}
+      : Service(control_interface, dispatcher, manager, "stub") {}
   virtual ~ServiceUnderTest() {}
 
   virtual void Connect(Error *error) {}
diff --git a/wifi.cc b/wifi.cc
index dd2acb9..18195ec 100644
--- a/wifi.cc
+++ b/wifi.cc
@@ -15,10 +15,15 @@
 #include <vector>
 
 #include <base/logging.h>
+#include <base/string_number_conversions.h>
+#include <base/string_util.h>
 #include <chromeos/dbus/service_constants.h>
 
 #include "shill/control_interface.h"
 #include "shill/device.h"
+#include "shill/error.h"
+#include "shill/key_value_store.h"
+#include "shill/ieee80211.h"
 #include "shill/manager.h"
 #include "shill/profile.h"
 #include "shill/proxy_factory.h"
@@ -28,9 +33,28 @@
 #include "shill/wifi_endpoint.h"
 #include "shill/wifi_service.h"
 
+using std::map;
 using std::string;
+using std::vector;
 
 namespace shill {
+
+// statics
+//
+// Note that WiFi generates some manager-level errors, because it implements
+// the Manager.GetWiFiService flimflam API. The API is implemented here,
+// rather than in manager, to keep WiFi-specific logic in the right place.
+const char WiFi::kManagerErrorPassphraseRequired[] = "must specify passphrase";
+const char WiFi::kManagerErrorSSIDRequired[] = "must specify SSID";
+const char WiFi::kManagerErrorSSIDTooLong[]  = "SSID is too long";
+const char WiFi::kManagerErrorSSIDTooShort[] = "SSID is too short";
+const char WiFi::kManagerErrorTypeRequired[] = "must specify service type";
+const char WiFi::kManagerErrorUnsupportedSecurityMode[] =
+    "security mode is unsupported";
+const char WiFi::kManagerErrorUnsupportedServiceType[] =
+    "service type is unsupported";
+const char WiFi::kManagerErrorUnsupportedServiceMode[] =
+    "service mode is unsupported";
 const char WiFi::kSupplicantPath[]        = "/fi/w1/wpa_supplicant1";
 const char WiFi::kSupplicantDBusAddr[]    = "fi.w1.wpa_supplicant1";
 const char WiFi::kSupplicantWiFiDriver[]  = "nl80211";
@@ -298,4 +322,163 @@
   scan_pending_ = true;
 }
 
+// used by manager, via static WiFi::GetService method
+WiFiServiceRefPtr WiFi::GetService(const KeyValueStore &args, Error *error) {
+  if (!args.ContainsString(flimflam::kTypeProperty)) {
+    error->Populate(Error::kInvalidArguments, kManagerErrorTypeRequired);
+    return NULL;
+  }
+
+  if (args.GetString(flimflam::kTypeProperty) != flimflam::kTypeWifi) {
+    error->Populate(Error::kNotSupported, kManagerErrorUnsupportedServiceType);
+    return NULL;
+  }
+
+  if (args.ContainsString(flimflam::kModeProperty) &&
+      args.GetString(flimflam::kModeProperty) !=
+      flimflam::kModeManaged) {
+    error->Populate(Error::kNotSupported, kManagerErrorUnsupportedServiceMode);
+    return NULL;
+  }
+
+  if (!args.ContainsString(flimflam::kSSIDProperty)) {
+    error->Populate(Error::kInvalidArguments, kManagerErrorSSIDRequired);
+    return NULL;
+  }
+
+  string ssid = args.GetString(flimflam::kSSIDProperty);
+  if (ssid.length() < 1) {
+    error->Populate(Error::kInvalidNetworkName, kManagerErrorSSIDTooShort);
+    return NULL;
+  }
+
+  if (ssid.length() > IEEE_80211::kMaxSSIDLen) {
+    error->Populate(Error::kInvalidNetworkName, kManagerErrorSSIDTooLong);
+    return NULL;
+  }
+
+  string security_method;
+  if (args.ContainsString(flimflam::kSecurityProperty)) {
+    security_method = args.GetString(flimflam::kSecurityProperty);
+  } else {
+    security_method = flimflam::kSecurityNone;
+  }
+
+  if (security_method != flimflam::kSecurityNone &&
+      security_method != flimflam::kSecurityWep &&
+      security_method != flimflam::kSecurityPsk &&
+      security_method != flimflam::kSecurityWpa &&
+      security_method != flimflam::kSecurityRsn &&
+      security_method != flimflam::kSecurity8021x) {
+    error->Populate(Error::kNotSupported,
+                    kManagerErrorUnsupportedSecurityMode);
+    return NULL;
+  }
+
+  if ((security_method == flimflam::kSecurityWep ||
+       security_method == flimflam::kSecurityPsk ||
+       security_method == flimflam::kSecurityWpa ||
+       security_method == flimflam::kSecurityRsn) &&
+      !args.ContainsString(flimflam::kPassphraseProperty)) {
+    error->Populate(Error::kInvalidArguments,
+                    kManagerErrorPassphraseRequired);
+    return NULL;
+  }
+
+  if (security_method == flimflam::kSecurityWep) {
+    string passphrase = args.GetString(flimflam::kPassphraseProperty);
+    passphrase = ParseWEPPassphrase(passphrase, error);
+    if (error->IsFailure()) {
+      return NULL;
+    }
+  }
+
+  WiFiService *service = NULL;
+
+  // TODO(quiche): search for existing service
+
+  if (service == NULL) {
+    // TODO(quiche): construct a new service
+  }
+
+  // TODO(quiche): apply configuration parameters
+
+  return service;
+}
+
+// static
+string WiFi::ParseWEPPassphrase(const string &passphrase, Error *error) {
+  unsigned int length = passphrase.length();
+
+  switch (length) {
+    case IEEE_80211::kWEP40AsciiLen:
+    case IEEE_80211::kWEP104AsciiLen:
+      break;
+    case IEEE_80211::kWEP40AsciiLen + 2:
+    case IEEE_80211::kWEP104AsciiLen + 2:
+      CheckWEPKeyIndex(passphrase, error);
+      break;
+    case IEEE_80211::kWEP40HexLen:
+    case IEEE_80211::kWEP104HexLen:
+      CheckWEPIsHex(passphrase, error);
+      break;
+    case IEEE_80211::kWEP40HexLen + 2:
+    case IEEE_80211::kWEP104HexLen + 2:
+      (CheckWEPKeyIndex(passphrase, error) ||
+       CheckWEPPrefix(passphrase, error)) &&
+          CheckWEPIsHex(passphrase.substr(2), error);
+      break;
+    case IEEE_80211::kWEP40HexLen + 4:
+    case IEEE_80211::kWEP104HexLen + 4:
+      CheckWEPKeyIndex(passphrase, error) &&
+          CheckWEPPrefix(passphrase.substr(2), error) &&
+          CheckWEPIsHex(passphrase.substr(4), error);
+      break;
+    default:
+      error->Populate(Error::kInvalidPassphrase);
+      break;
+  }
+
+  // TODO(quiche): may need to normalize passphrase format
+  if (error->IsSuccess()) {
+    return passphrase;
+  } else {
+    return "";
+  }
+}
+
+// static
+bool WiFi::CheckWEPIsHex(const string &passphrase, Error *error) {
+  vector<uint8> passphrase_bytes;
+  if (base::HexStringToBytes(passphrase, &passphrase_bytes)) {
+    return true;
+  } else {
+    error->Populate(Error::kInvalidPassphrase);
+    return false;
+  }
+}
+
+// static
+bool WiFi::CheckWEPKeyIndex(const string &passphrase, Error *error) {
+  if (StartsWithASCII(passphrase, "0:", false) ||
+      StartsWithASCII(passphrase, "1:", false) ||
+      StartsWithASCII(passphrase, "2:", false) ||
+      StartsWithASCII(passphrase, "3:", false)) {
+    return true;
+  } else {
+    error->Populate(Error::kInvalidPassphrase);
+    return false;
+  }
+}
+
+// static
+bool WiFi::CheckWEPPrefix(const string &passphrase, Error *error) {
+  if (StartsWithASCII(passphrase, "0x", false)) {
+    return true;
+  } else {
+    error->Populate(Error::kInvalidPassphrase);
+    return false;
+  }
+}
+
 }  // namespace shill
diff --git a/wifi.h b/wifi.h
index 04b1d09..a92174f 100644
--- a/wifi.h
+++ b/wifi.h
@@ -17,6 +17,8 @@
 
 namespace shill {
 
+class Error;
+class KeyValueStore;
 class SupplicantInterfaceProxyInterface;
 class SupplicantProcessProxyInterface;
 class WiFiService;
@@ -47,10 +49,21 @@
   // called by WiFiService
   void ConnectTo(WiFiService *service);
 
+  // called by Manager
+  virtual WiFiServiceRefPtr GetService(const KeyValueStore &args, Error *error);
+
  private:
   typedef std::map<const std::string, WiFiEndpointRefPtr> EndpointMap;
   typedef std::map<const std::string, WiFiServiceRefPtr> ServiceMap;
 
+  static const char kManagerErrorPassphraseRequired[];
+  static const char kManagerErrorSSIDTooLong[];
+  static const char kManagerErrorSSIDTooShort[];
+  static const char kManagerErrorSSIDRequired[];
+  static const char kManagerErrorTypeRequired[];
+  static const char kManagerErrorUnsupportedSecurityMode[];
+  static const char kManagerErrorUnsupportedServiceType[];
+  static const char kManagerErrorUnsupportedServiceMode[];
   static const char kSupplicantPath[];
   static const char kSupplicantDBusAddr[];
   static const char kSupplicantWiFiDriver[];
@@ -65,6 +78,12 @@
   void ScanDoneTask();
   void ScanTask();
 
+  static std::string ParseWEPPassphrase(const std::string &passphrase,
+                                        Error *error);
+  static bool CheckWEPIsHex(const std::string &passphrase, Error *error);
+  static bool CheckWEPKeyIndex(const std::string &passphrase, Error *error);
+  static bool CheckWEPPrefix(const std::string &passphrase, Error *error);
+
   ScopedRunnableMethodFactory<WiFi> task_factory_;
   scoped_ptr<SupplicantProcessProxyInterface> supplicant_process_proxy_;
   scoped_ptr<SupplicantInterfaceProxyInterface> supplicant_interface_proxy_;
diff --git a/wifi_service.cc b/wifi_service.cc
index 8b5046d..6ed8b79 100644
--- a/wifi_service.cc
+++ b/wifi_service.cc
@@ -28,9 +28,8 @@
                          const std::vector<uint8_t> ssid,
                          const std::string &mode,
                          const std::string &key_management)
-    : Service(control_interface, dispatcher, manager),
+    : Service(control_interface, dispatcher, manager, flimflam::kTypeWifi),
       security_(flimflam::kSecurityNone),
-      type_(flimflam::kTypeWifi),
       mode_(mode),
       task_factory_(this),
       wifi_(device),
@@ -43,7 +42,6 @@
   store->RegisterBool(flimflam::kPassphraseRequiredProperty, &need_passphrase_);
   store->RegisterConstString(flimflam::kSecurityProperty, &security_);
   store->RegisterConstUint8(flimflam::kSignalStrengthProperty, &strength_);
-  store->RegisterConstString(flimflam::kTypeProperty, &type_);
 
   store->RegisterConstString(flimflam::kWifiAuthMode, &auth_mode_);
   store->RegisterConstBool(flimflam::kWifiHiddenSsid, &hidden_ssid_);
diff --git a/wifi_service.h b/wifi_service.h
index 6784f20..e0176af 100644
--- a/wifi_service.h
+++ b/wifi_service.h
@@ -51,7 +51,6 @@
   bool need_passphrase_;
   std::string security_;
   uint8 strength_;
-  const std::string type_;
   // TODO(cmasone): see if the below can be pulled from the endpoint associated
   // with this service instead.
   const std::string mode_;
diff --git a/wifi_unittest.cc b/wifi_unittest.cc
index c2d9a1e..2751eb8 100644
--- a/wifi_unittest.cc
+++ b/wifi_unittest.cc
@@ -22,6 +22,7 @@
 #include <gtest/gtest.h>
 
 #include "shill/dbus_adaptor.h"
+#include "shill/key_value_store.h"
 #include "shill/manager.h"
 #include "shill/mock_device.h"
 #include "shill/mock_dhcp_config.h"
@@ -197,6 +198,40 @@
   void StopWiFi() {
     wifi_->Stop();
   }
+  void GetOpenService(const char *service_type,
+                      const char *ssid,
+                      const char *mode,
+                      Error &result) {
+    return GetService(service_type, ssid, mode, NULL, NULL, result);
+  }
+  void GetService(const char *service_type,
+                  const char *ssid,
+                  const char *mode,
+                  const char *security,
+                  const char *passphrase,
+                  Error &result) {
+    map<string, ::DBus::Variant> args;
+    Error e;
+    KeyValueStore args_kv;
+
+    // in general, we want to avoid D-Bus specific code for any RPCs
+    // that come in via adaptors. we make an exception here, because
+    // calls to GetWifiService are rerouted from the Manager object to
+    // the Wifi class.
+    if (service_type != NULL)
+      args[flimflam::kTypeProperty].writer().append_string(service_type);
+    if (ssid != NULL)
+      args[flimflam::kSSIDProperty].writer().append_string(ssid);
+    if (mode != NULL)
+      args[flimflam::kModeProperty].writer().append_string(mode);
+    if (security != NULL)
+      args[flimflam::kSecurityProperty].writer().append_string(security);
+    if (passphrase != NULL)
+      args[flimflam::kPassphraseProperty].writer().append_string(passphrase);
+
+    DBusAdaptor::ArgsToKeyValueStore(args, &args_kv, &e);
+    wifi_->GetService(args_kv, &result);
+  }
   MockManager *manager() {
     return &manager_;
   }
@@ -390,4 +425,161 @@
   }
 }
 
+TEST_F(WiFiMainTest, GetWifiServiceOpen) {
+  Error e;
+  GetOpenService("wifi", "an_ssid", "managed", e);
+  EXPECT_TRUE(e.IsSuccess());
+}
+
+TEST_F(WiFiMainTest, GetWifiServiceOpenNoType) {
+  Error e;
+  GetOpenService(NULL, "an_ssid", "managed", e);
+  EXPECT_EQ(Error::kInvalidArguments, e.type());
+  EXPECT_EQ("must specify service type", e.message());
+}
+
+TEST_F(WiFiMainTest, GetWifiServiceOpenNoSSID) {
+  Error e;
+  GetOpenService("wifi", NULL, "managed", e);
+  EXPECT_EQ(Error::kInvalidArguments, e.type());
+  EXPECT_EQ("must specify SSID", e.message());
+}
+
+TEST_F(WiFiMainTest, GetWifiServiceOpenLongSSID) {
+  Error e;
+  GetOpenService(
+      "wifi", "123456789012345678901234567890123", "managed", e);
+  EXPECT_EQ(Error::kInvalidNetworkName, e.type());
+  EXPECT_EQ("SSID is too long", e.message());
+}
+
+TEST_F(WiFiMainTest, GetWifiServiceOpenShortSSID) {
+  Error e;
+  GetOpenService("wifi", "", "managed", e);
+  EXPECT_EQ(Error::kInvalidNetworkName, e.type());
+  EXPECT_EQ("SSID is too short", e.message());
+}
+
+TEST_F(WiFiMainTest, GetWifiServiceOpenBadMode) {
+  Error e;
+  GetOpenService("wifi", "an_ssid", "ad-hoc", e);
+  EXPECT_EQ(Error::kNotSupported, e.type());
+  EXPECT_EQ("service mode is unsupported", e.message());
+}
+
+TEST_F(WiFiMainTest, GetWifiServiceOpenNoMode) {
+  Error e;
+  GetOpenService("wifi", "an_ssid", NULL, e);
+  EXPECT_TRUE(e.IsSuccess());
+}
+
+TEST_F(WiFiMainTest, GetWifiServiceRSN) {
+  Error e;
+  GetService("wifi", "an_ssid", "managed", "rsn", "secure password", e);
+  EXPECT_TRUE(e.IsSuccess());
+}
+
+TEST_F(WiFiMainTest, GetWifiServiceRSNNoPassword) {
+  Error e;
+  GetService("wifi", "an_ssid", "managed", "rsn", NULL, e);
+  EXPECT_EQ(Error::kInvalidArguments, e.type());
+  EXPECT_EQ("must specify passphrase", e.message());
+}
+
+TEST_F(WiFiMainTest, GetWifiServiceBadSecurity) {
+  Error e;
+  GetService("wifi", "an_ssid", "managed", "rot-13", NULL, e);
+  EXPECT_EQ(Error::kNotSupported, e.type());
+  EXPECT_EQ("security mode is unsupported", e.message());
+}
+
+TEST_F(WiFiMainTest, GetWifiServiceWEPNoPassword) {
+  Error e;
+  GetService("wifi", "an_ssid", "managed", "wep", NULL, e);
+  EXPECT_EQ(Error::kInvalidArguments, e.type());
+  EXPECT_EQ("must specify passphrase", e.message());
+}
+
+TEST_F(WiFiMainTest, GetWifiServiceWEPEmptyPassword) {
+  Error e;
+  GetService("wifi", "an_ssid", "managed", "wep", "", e);
+  EXPECT_EQ(Error::kInvalidPassphrase, e.type());
+}
+
+TEST_F(WiFiMainTest, GetWifiServiceWEP40ASCII) {
+  Error e;
+  GetService("wifi", "an_ssid", "managed", "wep", "abcde", e);
+  EXPECT_TRUE(e.IsSuccess());
+}
+
+TEST_F(WiFiMainTest, GetWifiServiceWEP104ASCII) {
+  Error e;
+  GetService("wifi", "an_ssid", "managed", "wep", "abcdefghijklm", e);
+  EXPECT_TRUE(e.IsSuccess());
+}
+
+TEST_F(WiFiMainTest, GetWifiServiceWEP40ASCIIWithKeyIndex) {
+  Error e;
+  GetService("wifi", "an_ssid", "managed", "wep", "0:abcdefghijklm", e);
+  EXPECT_TRUE(e.IsSuccess());
+}
+
+TEST_F(WiFiMainTest, GetWifiServiceWEP40Hex) {
+  Error e;
+  GetService("wifi", "an_ssid", "managed", "wep", "0102030405", e);
+  EXPECT_TRUE(e.IsSuccess());
+}
+
+TEST_F(WiFiMainTest, GetWifiServiceWEP40HexBadPassphrase) {
+  Error e;
+  GetService("wifi", "an_ssid", "managed", "wep", "O102030405", e);
+  EXPECT_EQ(Error::kInvalidPassphrase, e.type());
+}
+
+TEST_F(WiFiMainTest, GetWifiServiceWEP40HexWithKeyIndexBadPassphrase) {
+  Error e;
+  GetService("wifi", "an_ssid", "managed", "wep", "1:O102030405", e);
+  EXPECT_EQ(Error::kInvalidPassphrase, e.type());
+}
+
+TEST_F(WiFiMainTest, GetWifiServiceWEP40HexWithKeyIndexAndBaseBadPassphrase) {
+  Error e;
+  GetService("wifi", "an_ssid", "managed", "wep", "1:0xO102030405", e);
+  EXPECT_EQ(Error::kInvalidPassphrase, e.type());
+}
+
+TEST_F(WiFiMainTest, GetWifiServiceWEP40HexWithBaseBadPassphrase) {
+  Error e;
+  GetService("wifi", "an_ssid", "managed", "wep", "0xO102030405", e);
+  EXPECT_EQ(Error::kInvalidPassphrase, e.type());
+}
+
+TEST_F(WiFiMainTest, GetWifiServiceWEP104Hex) {
+  Error e;
+  GetService("wifi", "an_ssid", "managed", "wep",
+             "0102030405060708090a0b0c0d", e);
+  EXPECT_TRUE(e.IsSuccess());
+}
+
+TEST_F(WiFiMainTest, GetWifiServiceWEP104HexUppercase) {
+  Error e;
+  GetService("wifi", "an_ssid", "managed", "wep",
+             "0102030405060708090A0B0C0D", e);
+  EXPECT_TRUE(e.IsSuccess());
+}
+
+TEST_F(WiFiMainTest, GetWifiServiceWEP104HexWithKeyIndex) {
+  Error e;
+  GetService("wifi", "an_ssid", "managed", "wep",
+             "0:0102030405060708090a0b0c0d", e);
+  EXPECT_TRUE(e.IsSuccess());
+}
+
+TEST_F(WiFiMainTest, GetWifiServiceWEP104HexWithKeyIndexAndBase) {
+  Error e;
+  GetService("wifi", "an_ssid", "managed", "wep",
+             "0:0x0102030405060708090a0b0c0d", e);
+  EXPECT_TRUE(e.IsSuccess());
+}
+
 }  // namespace shill