shill: Initial support for loading and saving of services.

BUG=chromium-os:17255
TEST=unit tests

Change-Id: Ibf2721f72bfd44afbe6be9ac2cce0a2e978465eb
Reviewed-on: http://gerrit.chromium.org/gerrit/3954
Reviewed-by: Chris Masone <cmasone@chromium.org>
Tested-by: Darin Petkov <petkov@chromium.org>
diff --git a/mock_store.h b/mock_store.h
new file mode 100644
index 0000000..5e79a90
--- /dev/null
+++ b/mock_store.h
@@ -0,0 +1,57 @@
+// 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_STORE_
+#define SHILL_MOCK_STORE_
+
+#include <gmock/gmock.h>
+
+#include "shill/store_interface.h"
+
+namespace shill {
+
+class MockStore : public StoreInterface {
+ public:
+  MOCK_METHOD0(Open, bool());
+  MOCK_METHOD0(Close, bool());
+  MOCK_METHOD0(GetGroups, std::set<std::string>());
+  MOCK_METHOD1(ContainsGroup, bool(const std::string &group));
+  MOCK_METHOD2(DeleteKey,
+               bool(const std::string &group, const std::string &key));
+  MOCK_METHOD1(DeleteGroup, bool(const std::string &group));
+  MOCK_METHOD3(GetString, bool(const std::string &group,
+                               const std::string &key,
+                               std::string *value));
+  MOCK_METHOD3(SetString, bool(const std::string &group,
+                               const std::string &key,
+                               const std::string &value));
+  MOCK_METHOD3(GetBool, bool(const std::string &group,
+                             const std::string &key,
+                             bool *value));
+  MOCK_METHOD3(SetBool, bool(const std::string &group,
+                             const std::string &key,
+                             bool value));
+  MOCK_METHOD3(GetInt, bool(const std::string &group,
+                            const std::string &key,
+                            int *value));
+  MOCK_METHOD3(SetInt, bool(const std::string &group,
+                            const std::string &key,
+                            int value));
+  MOCK_METHOD3(GetStringList, bool(const std::string &group,
+                                   const std::string &key,
+                                   std::vector<std::string> *value));
+  MOCK_METHOD3(SetStringList, bool(const std::string &group,
+                                   const std::string &key,
+                                   const std::vector<std::string> &value));
+  MOCK_METHOD3(GetCryptedString, bool(const std::string &group,
+                                      const std::string &key,
+                                      std::string *value));
+  MOCK_METHOD3(SetCryptedString, bool(const std::string &group,
+                                      const std::string &key,
+                                      const std::string &value));
+};
+
+}  // namespace shill
+
+#endif  // SHILL_MOCK_STORE_
diff --git a/service.cc b/service.cc
index 0ce233d..395af35 100644
--- a/service.cc
+++ b/service.cc
@@ -21,21 +21,51 @@
 #include "shill/property_accessor.h"
 #include "shill/refptr_types.h"
 #include "shill/service_dbus_adaptor.h"
+#include "shill/store_interface.h"
 
 using std::map;
 using std::string;
 using std::vector;
 
 namespace shill {
+
+const char Service::kCheckPortalAuto[] = "auto";
+const char Service::kCheckPortalFalse[] = "false";
+const char Service::kCheckPortalTrue[] = "true";
+
+const char Service::kStorageAutoConnect[] = "AutoConnect";
+const char Service::kStorageCheckPortal[] = "CheckPortal";
+const char Service::kStorageEapAnonymousIdentity[] = "EAP.AnonymousIdentity";
+const char Service::kStorageEapCACert[] = "EAP.CACert";
+const char Service::kStorageEapCACertID[] = "EAP.CACertID";
+const char Service::kStorageEapCertID[] = "EAP.CertID";
+const char Service::kStorageEapClientCert[] = "EAP.ClientCert";
+const char Service::kStorageEapEap[] = "EAP.EAP";
+const char Service::kStorageEapIdentity[] = "EAP.Identity";
+const char Service::kStorageEapInnerEap[] = "EAP.InnerEAP";
+const char Service::kStorageEapKeyID[] = "EAP.KeyID";
+const char Service::kStorageEapKeyManagement[] = "EAP.KeyMgmt";
+const char Service::kStorageEapPIN[] = "EAP.PIN";
+const char Service::kStorageEapPassword[] = "EAP.Password";
+const char Service::kStorageEapPrivateKey[] = "EAP.PrivateKey";
+const char Service::kStorageEapPrivateKeyPassword[] = "EAP.PrivateKeyPassword";
+const char Service::kStorageEapUseSystemCAs[] = "EAP.UseSystemCAs";
+const char Service::kStorageFavorite[] = "Favorite";
+const char Service::kStorageName[] = "Name";
+const char Service::kStoragePriority[] = "Priority";
+const char Service::kStorageProxyConfig[] = "ProxyConfig";
+const char Service::kStorageSaveCredentials[] = "SaveCredentials";
+
 Service::Service(ControlInterface *control_interface,
                  EventDispatcher *dispatcher,
                  const ProfileRefPtr &profile,
                  const string& name)
     : auto_connect_(false),
+      check_portal_(kCheckPortalAuto),
       connectable_(false),
       favorite_(false),
-      priority_(0),
-      save_credentials_(false),
+      priority_(kPriorityNone),
+      save_credentials_(true),
       profile_(profile),
       dispatcher_(dispatcher),
       name_(name),
@@ -118,6 +148,10 @@
   return adaptor_->GetRpcIdentifier();
 }
 
+string Service::GetStorageIdentifier() {
+  return UniqueName();
+}
+
 void Service::HelpRegisterDerivedBool(const string &name,
                                   bool(Service::*get)(void),
                                   bool(Service::*set)(const bool&)) {
@@ -134,4 +168,149 @@
       StringAccessor(new CustomAccessor<Service, string>(this, get, set)));
 }
 
+void Service::SaveString(StoreInterface *storage,
+                         const string &key,
+                         const string &value,
+                         bool crypted,
+                         bool save) {
+  if (value.empty() || !save) {
+    storage->DeleteKey(GetStorageIdentifier(), key);
+    return;
+  }
+  if (crypted) {
+    storage->SetCryptedString(GetStorageIdentifier(), key, value);
+    return;
+  }
+  storage->SetString(GetStorageIdentifier(), key, value);
+}
+
+void Service::LoadEapCredentials(StoreInterface *storage) {
+  const string id = GetStorageIdentifier();
+  storage->GetCryptedString(id, kStorageEapIdentity, &eap_.identity);
+  storage->GetString(id, kStorageEapEap, &eap_.eap);
+  storage->GetString(id, kStorageEapInnerEap, &eap_.inner_eap);
+  storage->GetCryptedString(id,
+                            kStorageEapAnonymousIdentity,
+                            &eap_.anonymous_identity);
+  storage->GetString(id, kStorageEapClientCert, &eap_.client_cert);
+  storage->GetString(id, kStorageEapCertID, &eap_.cert_id);
+  storage->GetString(id, kStorageEapPrivateKey, &eap_.private_key);
+  storage->GetCryptedString(id,
+                            kStorageEapPrivateKeyPassword,
+                            &eap_.private_key_password);
+  storage->GetString(id, kStorageEapKeyID, &eap_.key_id);
+  storage->GetString(id, kStorageEapCACert, &eap_.ca_cert);
+  storage->GetString(id, kStorageEapCACertID, &eap_.ca_cert_id);
+  storage->GetBool(id, kStorageEapUseSystemCAs, &eap_.use_system_cas);
+  storage->GetString(id, kStorageEapPIN, &eap_.pin);
+  storage->GetCryptedString(id, kStorageEapPassword, &eap_.password);
+  storage->GetString(id, kStorageEapKeyManagement, &eap_.key_management);
+}
+
+void Service::SaveEapCredentials(StoreInterface *storage) {
+  bool save = save_credentials_;
+  SaveString(storage, kStorageEapIdentity, eap_.identity, true, save);
+  SaveString(storage, kStorageEapEap, eap_.eap, false, true);
+  SaveString(storage, kStorageEapInnerEap, eap_.inner_eap, false, true);
+  SaveString(storage,
+             kStorageEapAnonymousIdentity,
+             eap_.anonymous_identity,
+             true,
+             save);
+  SaveString(storage, kStorageEapClientCert, eap_.client_cert, false, save);
+  SaveString(storage, kStorageEapCertID, eap_.cert_id, false, save);
+  SaveString(storage, kStorageEapPrivateKey, eap_.private_key, false, save);
+  SaveString(storage,
+             kStorageEapPrivateKeyPassword,
+             eap_.private_key_password,
+             true,
+             save);
+  SaveString(storage, kStorageEapKeyID, eap_.key_id, false, save);
+  SaveString(storage, kStorageEapCACert, eap_.ca_cert, false, true);
+  SaveString(storage, kStorageEapCACertID, eap_.ca_cert_id, false, true);
+  storage->SetBool(GetStorageIdentifier(),
+                   kStorageEapUseSystemCAs,
+                   eap_.use_system_cas);
+  SaveString(storage, kStorageEapPIN, eap_.pin, false, save);
+  SaveString(storage, kStorageEapPassword, eap_.password, true, save);
+  SaveString(storage,
+             kStorageEapKeyManagement,
+             eap_.key_management,
+             false,
+             true);
+}
+
+bool Service::Load(StoreInterface *storage) {
+  const string id = GetStorageIdentifier();
+  if (!storage->ContainsGroup(id)) {
+    LOG(WARNING) << "Service is not available in the persistent store: " << id;
+    return false;
+  }
+  storage->GetBool(id, kStorageAutoConnect, &auto_connect_);
+  storage->GetString(id, kStorageCheckPortal, &check_portal_);
+  storage->GetBool(id, kStorageFavorite, &favorite_);
+  storage->GetInt(id, kStoragePriority, &priority_);
+  storage->GetString(id, kStorageProxyConfig, &proxy_config_);
+  storage->GetBool(id, kStorageSaveCredentials, &save_credentials_);
+
+  LoadEapCredentials(storage);
+
+  // TODO(petkov): Load these:
+
+  // "Name"
+  // "WiFi.HiddenSSID"
+  // "SSID"
+  // "Failure"
+  // "Modified"
+  // "LastAttempt"
+  // WiFiService: "Passphrase"
+  // "APN"
+  // "LastGoodAPN"
+
+  return true;
+}
+
+bool Service::Save(StoreInterface *storage) {
+  const string id = GetStorageIdentifier();
+
+  // TODO(petkov): We could choose to simplify the saving code by removing most
+  // conditionals thus saving even default values.
+  if (favorite_) {
+    storage->SetBool(id, kStorageAutoConnect, auto_connect_);
+  }
+  if (check_portal_ == kCheckPortalAuto) {
+    storage->DeleteKey(id, kStorageCheckPortal);
+  } else {
+    storage->SetString(id, kStorageCheckPortal, check_portal_);
+  }
+  storage->SetBool(id, kStorageFavorite, favorite_);
+  storage->SetString(id, kStorageName, name_);
+  SaveString(storage, kStorageProxyConfig, proxy_config_, false, true);
+  if (priority_ != kPriorityNone) {
+    storage->SetInt(id, kStoragePriority, priority_);
+  } else {
+    storage->DeleteKey(id, kStoragePriority);
+  }
+  if (save_credentials_) {
+    storage->DeleteKey(id, kStorageSaveCredentials);
+  } else {
+    storage->SetBool(id, kStorageSaveCredentials, false);
+  }
+
+  SaveEapCredentials(storage);
+
+  // TODO(petkov): Save these:
+
+  // "WiFi.HiddenSSID"
+  // "SSID"
+  // "Failure"
+  // "Modified"
+  // "LastAttempt"
+  // WiFiService: "Passphrase"
+  // "APN"
+  // "LastGoodAPN"
+
+  return true;
+}
+
 }  // namespace shill
diff --git a/service.h b/service.h
index f6f6418..f22e646 100644
--- a/service.h
+++ b/service.h
@@ -11,6 +11,7 @@
 
 #include <base/memory/ref_counted.h>
 #include <base/memory/scoped_ptr.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
 
 #include "shill/accessor_interface.h"
 #include "shill/property_store.h"
@@ -25,6 +26,7 @@
 class Error;
 class EventDispatcher;
 class ServiceAdaptorInterface;
+class StoreInterface;
 
 // A Service is a uniquely named entity, which the system can
 // connect in order to begin sending and receiving network traffic.
@@ -34,6 +36,10 @@
 // becomes populated over time.
 class Service : public base::RefCounted<Service> {
  public:
+  static const char kCheckPortalAuto[];
+  static const char kCheckPortalFalse[];
+  static const char kCheckPortalTrue[];
+
   enum ConnectFailure {
     kServiceFailureUnknown,
     kServiceFailureActivationFailure,
@@ -80,23 +86,35 @@
           const ProfileRefPtr &profile,
           const std::string& name);
   virtual ~Service();
+
   virtual void Connect() = 0;
   virtual void Disconnect() = 0;
 
   virtual bool IsActive() { return false; }
 
-  // Returns a string that is guaranteed to uniquely identify this
-  // Service instance.
-  virtual const std::string &UniqueName() { return name_; }
+  // Returns a string that is guaranteed to uniquely identify this Service
+  // instance.
+  const std::string &UniqueName() { return name_; }
 
   std::string GetRpcIdentifier();
 
+  // Returns the unique persistent storage identifier for the service.
+  std::string GetStorageIdentifier();
+
+  // Loads the service from persistent |storage|. Returns true on success.
+  virtual bool Load(StoreInterface *storage);
+
+  // Saves the service to persistent |storage|. Returns true on success.
+  virtual bool Save(StoreInterface *storage);
+
   bool auto_connect() const { return auto_connect_; }
   void set_auto_connect(bool connect) { auto_connect_ = connect; }
 
   PropertyStore *store() { return &store_; }
 
  protected:
+  static const int kPriorityNone = 0;
+
   virtual std::string CalculateState() = 0;
 
   void HelpRegisterDerivedBool(const std::string &name,
@@ -106,6 +124,18 @@
                                  std::string(Service::*get)(void),
                                  bool(Service::*set)(const std::string&));
 
+  // Assigns |value| to |key| in |storage| if |value| is non-empty and |save| is
+  // true. Otherwise, removes |key| from |storage|. If |crypted| is true, the
+  // value is encrypted.
+  void SaveString(StoreInterface *storage,
+                  const std::string &key,
+                  const std::string &value,
+                  bool crypted,
+                  bool save);
+
+  void LoadEapCredentials(StoreInterface *storage);
+  void SaveEapCredentials(StoreInterface *storage);
+
   // Properties
   bool auto_connect_;
   std::string check_portal_;
@@ -123,6 +153,37 @@
   EventDispatcher *dispatcher_;
 
  private:
+  friend class ServiceAdaptorInterface;
+  FRIEND_TEST(ServiceTest, Constructor);
+  FRIEND_TEST(ServiceTest, Save);
+  FRIEND_TEST(ServiceTest, SaveString);
+  FRIEND_TEST(ServiceTest, SaveStringCrypted);
+  FRIEND_TEST(ServiceTest, SaveStringDontSave);
+  FRIEND_TEST(ServiceTest, SaveStringEmpty);
+
+  static const char kStorageAutoConnect[];
+  static const char kStorageCheckPortal[];
+  static const char kStorageEapAnonymousIdentity[];
+  static const char kStorageEapCACert[];
+  static const char kStorageEapCACertID[];
+  static const char kStorageEapCertID[];
+  static const char kStorageEapClientCert[];
+  static const char kStorageEapEap[];
+  static const char kStorageEapIdentity[];
+  static const char kStorageEapInnerEap[];
+  static const char kStorageEapKeyID[];
+  static const char kStorageEapKeyManagement[];
+  static const char kStorageEapPIN[];
+  static const char kStorageEapPassword[];
+  static const char kStorageEapPrivateKey[];
+  static const char kStorageEapPrivateKeyPassword[];
+  static const char kStorageEapUseSystemCAs[];
+  static const char kStorageFavorite[];
+  static const char kStorageName[];
+  static const char kStoragePriority[];
+  static const char kStorageProxyConfig[];
+  static const char kStorageSaveCredentials[];
+
   virtual std::string GetDeviceRpcId() = 0;
 
   std::string GetProfileRpcId() {
@@ -136,7 +197,6 @@
   Connection *connection_;
   scoped_ptr<ServiceAdaptorInterface> adaptor_;
 
-  friend class ServiceAdaptorInterface;
   DISALLOW_COPY_AND_ASSIGN(Service);
 };
 
diff --git a/service_unittest.cc b/service_unittest.cc
index e47bd2e..9b5b023 100644
--- a/service_unittest.cc
+++ b/service_unittest.cc
@@ -20,16 +20,19 @@
 #include "shill/mock_control.h"
 #include "shill/mock_profile.h"
 #include "shill/mock_service.h"
+#include "shill/mock_store.h"
 #include "shill/property_store_unittest.h"
 #include "shill/shill_event.h"
 
 using std::map;
 using std::string;
 using std::vector;
-using ::testing::_;
-using ::testing::NiceMock;
-using ::testing::Return;
-using ::testing::Test;
+using testing::_;
+using testing::AtLeast;
+using testing::NiceMock;
+using testing::Return;
+using testing::StrictMock;
+using testing::Test;
 
 namespace shill {
 
@@ -58,6 +61,11 @@
 
 const char ServiceTest::kProfileName[] = "profile";
 
+TEST_F(ServiceTest, Constructor) {
+  EXPECT_TRUE(service_->save_credentials_);
+  EXPECT_EQ(Service::kCheckPortalAuto, service_->check_portal_);
+}
+
 TEST_F(ServiceTest, GetProperties) {
   EXPECT_CALL(*service_.get(), CalculateState()).WillRepeatedly(Return(""));
   EXPECT_CALL(*service_.get(), GetDeviceRpcId())
@@ -185,4 +193,71 @@
   }
 }
 
+TEST_F(ServiceTest, GetStorageIdentifier) {
+  EXPECT_EQ(kMockServiceName, service_->GetStorageIdentifier());
+}
+
+TEST_F(ServiceTest, Load) {
+  NiceMock<MockStore> storage;
+  const string id = kMockServiceName;
+  EXPECT_CALL(storage, ContainsGroup(id)).WillOnce(Return(true));
+  EXPECT_CALL(storage, GetString(id, _, _))
+      .Times(AtLeast(1))
+      .WillRepeatedly(Return(true));
+  EXPECT_TRUE(service_->Load(&storage));
+}
+
+TEST_F(ServiceTest, LoadFail) {
+  StrictMock<MockStore> storage;
+  const string id = kMockServiceName;
+  EXPECT_CALL(storage, ContainsGroup(kMockServiceName)).WillOnce(Return(false));
+  EXPECT_FALSE(service_->Load(&storage));
+}
+
+TEST_F(ServiceTest, SaveString) {
+  MockStore storage;
+  static const char kKey[] = "test-key";
+  static const char kData[] = "test-data";
+  EXPECT_CALL(storage, SetString(kMockServiceName, kKey, kData))
+      .WillOnce(Return(true));
+  service_->SaveString(&storage, kKey, kData, false, true);
+}
+
+TEST_F(ServiceTest, SaveStringCrypted) {
+  MockStore storage;
+  static const char kKey[] = "test-key";
+  static const char kData[] = "test-data";
+  EXPECT_CALL(storage, SetCryptedString(kMockServiceName, kKey, kData))
+      .WillOnce(Return(true));
+  service_->SaveString(&storage, kKey, kData, true, true);
+}
+
+TEST_F(ServiceTest, SaveStringDontSave) {
+  MockStore storage;
+  static const char kKey[] = "test-key";
+  EXPECT_CALL(storage, DeleteKey(kMockServiceName, kKey))
+      .WillOnce(Return(true));
+  service_->SaveString(&storage, kKey, "data", false, false);
+}
+
+TEST_F(ServiceTest, SaveStringEmpty) {
+  MockStore storage;
+  static const char kKey[] = "test-key";
+  EXPECT_CALL(storage, DeleteKey(kMockServiceName, kKey))
+      .WillOnce(Return(true));
+  service_->SaveString(&storage, kKey, "", true, true);
+}
+
+TEST_F(ServiceTest, Save) {
+  NiceMock<MockStore> storage;
+  const string id = kMockServiceName;
+  EXPECT_CALL(storage, SetString(id, _, _))
+      .Times(AtLeast(1))
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(storage, DeleteKey(id, _))
+      .Times(AtLeast(1))
+      .WillRepeatedly(Return(true));
+  EXPECT_TRUE(service_->Save(&storage));
+}
+
 }  // namespace shill