shill: Make cellular Connect work for GSM networks.

Add the APN to the properties map passed to Connect.
Set up the list of APNs to try, and step through it when
a connect attempt fails with the InvalidApn error. The
order in which APNs are tried is

  - APN which most recently resulted in a successful
    connection on the current network
  - a user-specified APN, specified by setting the Cellular.APN
    property on the service
  - the list of APNs found for the home network provider in the
    mobile broadband provider database
  - if all those fail, a null APN

BUG=chromium-os:23259
TEST=manual testing, some of which involved modifying the
mobile provider DB to create invalid APN entries. Created
two new unit tests as well, and ran all unit tests.

Change-Id: I38c869228fe1aaf7421de8a826c54e7b62c209b2
Reviewed-on: https://gerrit.chromium.org/gerrit/19122
Tested-by: Eric Shienbrood <ers@chromium.org>
Reviewed-by: Jason Glasgow <jglasgow@chromium.org>
Commit-Ready: Eric Shienbrood <ers@chromium.org>
diff --git a/cellular_service.cc b/cellular_service.cc
index 4c39a3e..74017bc 100644
--- a/cellular_service.cc
+++ b/cellular_service.cc
@@ -12,11 +12,16 @@
 
 #include "shill/adaptor_interfaces.h"
 #include "shill/cellular.h"
+#include "shill/property_accessor.h"
+#include "shill/store_interface.h"
 
 using std::string;
 
 namespace shill {
 
+const char CellularService::kStorageAPN[] = "Cellular.APN";
+const char CellularService::kStorageLastGoodAPN[] = "Cellular.LastGoodAPN";
+
 // TODO(petkov): Add these to system_api/dbus/service_constants.h
 namespace {
 const char kKeyOLPURL[] = "url";
@@ -24,6 +29,17 @@
 const char kKeyOLPPostData[] = "postdata";
 }  // namespace {}
 
+static bool GetNonEmptyField(const Stringmap &stringmap,
+                             const string &fieldname,
+                             string *value) {
+  Stringmap::const_iterator it = stringmap.find(fieldname);
+  if (it != stringmap.end() && !it->second.empty()) {
+    *value = it->second;
+    return true;
+  }
+  return false;
+}
+
 CellularService::OLP::OLP() {
   SetURL("");
   SetMethod("");
@@ -79,7 +95,9 @@
   PropertyStore *store = this->mutable_store();
   store->RegisterConstString(flimflam::kActivationStateProperty,
                              &activation_state_);
-  store->RegisterStringmap(flimflam::kCellularApnProperty, &apn_info_);
+  HelpRegisterDerivedStringmap(flimflam::kCellularApnProperty,
+                               &CellularService::GetApn,
+                               &CellularService::SetApn);
   store->RegisterConstStringmap(flimflam::kCellularLastGoodApnProperty,
                                 &last_good_apn_info_);
   store->RegisterConstString(flimflam::kNetworkTechnologyProperty,
@@ -98,6 +116,144 @@
 
 CellularService::~CellularService() { }
 
+void CellularService::HelpRegisterDerivedStringmap(
+    const string &name,
+    Stringmap(CellularService::*get)(Error *error),
+    void(CellularService::*set)(
+        const Stringmap &value, Error *error)) {
+  mutable_store()->RegisterDerivedStringmap(
+      name,
+      StringmapAccessor(
+          new CustomAccessor<CellularService, Stringmap>(this, get, set)));
+}
+
+Stringmap *CellularService::GetUserSpecifiedApn() {
+  Stringmap::iterator it = apn_info_.find(flimflam::kApnProperty);
+  if (it == apn_info_.end() || it->second.empty())
+    return NULL;
+  return &apn_info_;
+}
+
+Stringmap *CellularService::GetLastGoodApn() {
+  Stringmap::iterator it =
+      last_good_apn_info_.find(flimflam::kApnProperty);
+  if (it == last_good_apn_info_.end() || it->second.empty())
+    return NULL;
+  return &last_good_apn_info_;
+}
+
+Stringmap CellularService::GetApn(Error */*error*/) {
+  return apn_info_;
+}
+
+void CellularService::SetApn(const Stringmap &value, Error *error) {
+  // Only copy in the fields we care about, and validate the contents.
+  // The "apn" field is mandatory.
+  string str;
+  if (!GetNonEmptyField(value, flimflam::kApnProperty, &str)) {
+    error->Populate(Error::kInvalidArguments,
+                    "supplied APN info is missing the apn");
+    return;
+  }
+  apn_info_[flimflam::kApnProperty] = str;
+  if (GetNonEmptyField(value, flimflam::kApnUsernameProperty, &str))
+    apn_info_[flimflam::kApnUsernameProperty] = str;
+  if (GetNonEmptyField(value, flimflam::kApnPasswordProperty, &str))
+    apn_info_[flimflam::kApnPasswordProperty] = str;
+
+  ClearLastGoodApn();
+  adaptor()->EmitStringmapChanged(flimflam::kCellularApnProperty, apn_info_);
+  SaveToCurrentProfile();
+}
+
+void CellularService::SetLastGoodApn(const Stringmap &apn_info) {
+  last_good_apn_info_ = apn_info;
+  adaptor()->EmitStringmapChanged(flimflam::kCellularLastGoodApnProperty,
+                                  last_good_apn_info_);
+  SaveToCurrentProfile();
+}
+
+void CellularService::ClearLastGoodApn() {
+  last_good_apn_info_.clear();
+  adaptor()->EmitStringmapChanged(flimflam::kCellularLastGoodApnProperty,
+                                  last_good_apn_info_);
+  SaveToCurrentProfile();
+}
+
+bool CellularService::Load(StoreInterface *storage) {
+  // Load properties common to all Services.
+  if (!Service::Load(storage))
+    return false;
+
+  const string id = GetStorageIdentifier();
+  LoadApn(storage, id, kStorageAPN, &apn_info_);
+  LoadApn(storage, id, kStorageLastGoodAPN, &last_good_apn_info_);
+  return true;
+}
+
+void CellularService::LoadApn(StoreInterface *storage,
+                              const string &storage_group,
+                              const string &keytag,
+                              Stringmap *apn_info) {
+  if (!LoadApnField(storage, storage_group, keytag,
+               flimflam::kApnProperty, apn_info))
+    return;
+  LoadApnField(storage, storage_group, keytag,
+               flimflam::kApnUsernameProperty, apn_info);
+  LoadApnField(storage, storage_group, keytag,
+               flimflam::kApnPasswordProperty, apn_info);
+}
+
+bool CellularService::LoadApnField(StoreInterface *storage,
+                                   const string &storage_group,
+                                   const string &keytag,
+                                   const string &apntag,
+                                   Stringmap *apn_info) {
+  string value;
+  if (storage->GetString(storage_group, keytag + "." + apntag, &value) &&
+      !value.empty()) {
+    (*apn_info)[apntag] = value;
+    return true;
+  }
+  return false;
+}
+
+bool CellularService::Save(StoreInterface *storage) {
+  // Save properties common to all Services.
+  if (!Service::Save(storage))
+    return false;
+
+  const string id = GetStorageIdentifier();
+  SaveApn(storage, id, GetUserSpecifiedApn(), kStorageAPN);
+  SaveApn(storage, id, GetLastGoodApn(), kStorageLastGoodAPN);
+  return true;
+}
+
+void CellularService::SaveApn(StoreInterface *storage,
+                              const string &storage_group,
+                              const Stringmap *apn_info,
+                              const string &keytag) {
+    SaveApnField(storage, storage_group, apn_info, keytag,
+                 flimflam::kApnProperty);
+    SaveApnField(storage, storage_group, apn_info, keytag,
+                 flimflam::kApnUsernameProperty);
+    SaveApnField(storage, storage_group, apn_info, keytag,
+                 flimflam::kApnPasswordProperty);
+}
+
+void CellularService::SaveApnField(StoreInterface *storage,
+                                   const string &storage_group,
+                                   const Stringmap *apn_info,
+                                   const string &keytag,
+                                   const string &apntag) {
+  const string key = keytag + "." + apntag;
+  string str;
+  if (apn_info && GetNonEmptyField(*apn_info, apntag, &str))
+    storage->SetString(storage_group, key, str);
+  else
+    storage->DeleteKey(storage_group, key);
+}
+
 void CellularService::Connect(Error *error) {
   Service::Connect(error);
   cellular_->Connect(error);