shill: Add extended mobile-broadband-provider-info parser to shill.

This CL extends the CellularOperatorInfo class to contain more
mobile-broadband-provider-info data in the following ways:

  - data/cellular_operator_info is modified to contain more info than
    just OLP. The format has been changed to an extended version of
    /usr/share/mobile-broadband-provider-info/serviceproviders.bfd. For
    now, it only contains information on one carrier.

  - The CellularOperatorInfo class provides the functionality to parse
    the contents of the file format described in
    data/cellular_operator_info. It provides the functionalities of the
    parser in third_party/mobile-broadband-provider-info/src/
    (implemented in C) in C++, by exposing similar content in the form
    of CellularOperatorInfo::CellularOperator.

  - Similar to third_party/mobile-broadband-provider-info/src/,
    CellularOperatorInfo can be used to look up a cellular carrier by
    name, MCCMNC, but also SID. The output, in addition, contains data
    regarding OLP.

BUG=chromium-os:37670
TEST=Build and run unittests.

Change-Id: I4975c79c828d6386517c5c91c1ce9567cd063a15
Reviewed-on: https://gerrit.chromium.org/gerrit/41101
Reviewed-by: Ben Chan <benchan@chromium.org>
Commit-Queue: Arman Uguray <armansito@chromium.org>
Tested-by: Arman Uguray <armansito@chromium.org>
diff --git a/Makefile b/Makefile
index 07a15c6..7835d1d 100644
--- a/Makefile
+++ b/Makefile
@@ -208,6 +208,8 @@
 	ethernet.o \
 	ethernet_service.o \
 	event_dispatcher.o \
+	file_reader.o \
+	file_reader_unittest.o \
 	geolocation_info.o \
 	glib.o \
 	glib_io_ready_handler.o \
diff --git a/cellular_capability_universal.cc b/cellular_capability_universal.cc
index 29c7cc2..9bbbef2 100644
--- a/cellular_capability_universal.cc
+++ b/cellular_capability_universal.cc
@@ -578,10 +578,13 @@
   if (!cellular()->cellular_operator_info())
     return;
 
-  CellularService::OLP olp;
-  if (!cellular()->cellular_operator_info()->GetOLP(operator_id_, &olp))
+  const CellularService::OLP *result =
+      cellular()->cellular_operator_info()->GetOLPByMCCMNC(operator_id_);
+  if (!result)
     return;
 
+  CellularService::OLP olp;
+  olp.CopyFrom(*result);
   string post_data = olp.GetPostData();
   ReplaceSubstringsAfterOffset(&post_data, 0, "${esn}", esn_);
   ReplaceSubstringsAfterOffset(&post_data, 0, "${iccid}", sim_identifier_);
@@ -732,8 +735,9 @@
   if (!cellular()->cellular_operator_info())
     return false;
 
-  CellularService::OLP olp;
-  if (!cellular()->cellular_operator_info()->GetOLP(operator_id_, &olp))
+  const CellularService::OLP *olp =
+      cellular()->cellular_operator_info()->GetOLPByMCCMNC(operator_id_);
+  if (!olp)
     return false;
 
   // To avoid false positives, it's safer to assume the service does not
diff --git a/cellular_capability_universal_unittest.cc b/cellular_capability_universal_unittest.cc
index 8a7ac73..ec592bc 100644
--- a/cellular_capability_universal_unittest.cc
+++ b/cellular_capability_universal_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Copyright (c) 2013 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.
 
@@ -66,23 +66,6 @@
           apn == expected_apn);
 }
 
-class MockCellularOperatorInfoGetOLPHelper {
- public:
-  MockCellularOperatorInfoGetOLPHelper(const CellularService::OLP &olp) {
-    olp_.CopyFrom(olp);
-  }
-
-  ~MockCellularOperatorInfoGetOLPHelper() {}
-
-  bool GetOLP(const std::string &operator_id, CellularService::OLP *olp) {
-    olp->CopyFrom(olp_);
-    return true;
-  }
-
- private:
-  CellularService::OLP olp_;
-};
-
 class CellularCapabilityUniversalTest : public testing::Test {
  public:
   CellularCapabilityUniversalTest()
@@ -1062,8 +1045,6 @@
   test_olp.SetPostData("esn=${esn}&imei=${imei}&imsi=${imsi}&mdn=${mdn}&"
                        "meid=${meid}&min=${min}&iccid=${iccid}");
 
-  MockCellularOperatorInfoGetOLPHelper get_olp_helper(test_olp);
-
   capability_->esn_ = "0";
   capability_->imei_ = "1";
   capability_->imsi_ = "2";
@@ -1074,9 +1055,9 @@
   capability_->operator_id_ = "123456";
   cellular_->cellular_operator_info_ = &cellular_operator_info_;
 
-  EXPECT_CALL(cellular_operator_info_, GetOLP(capability_->operator_id_, _))
-      .WillRepeatedly(Invoke(&get_olp_helper,
-                             &MockCellularOperatorInfoGetOLPHelper::GetOLP));
+  EXPECT_CALL(cellular_operator_info_,
+      GetOLPByMCCMNC(capability_->operator_id_))
+      .WillRepeatedly(Return(&test_olp));
 
   SetService();
   capability_->UpdateOLP();
@@ -1121,8 +1102,8 @@
 
   // Service activation is not needed
   cellular_->cellular_operator_info_ = &cellular_operator_info_;
-  EXPECT_CALL(cellular_operator_info_, GetOLP(_, _))
-      .WillOnce(Return(false));
+  EXPECT_CALL(cellular_operator_info_, GetOLPByMCCMNC(_))
+      .WillOnce(Return((const CellularService::OLP *)NULL));
   capability_->UpdateOperatorInfo();
   EXPECT_EQ("", capability_->serving_operator_.GetName());
   EXPECT_EQ("", capability_->serving_operator_.GetCountry());
@@ -1131,8 +1112,9 @@
   // Service activation is needed
   capability_->mdn_ = "0000000000";
   cellular_->cellular_operator_info_ = &cellular_operator_info_;
-  EXPECT_CALL(cellular_operator_info_, GetOLP(_, _))
-      .WillOnce(Return(true));
+  CellularService::OLP olp;
+  EXPECT_CALL(cellular_operator_info_, GetOLPByMCCMNC(_))
+      .WillOnce(Return(&olp));
 
   capability_->UpdateOperatorInfo();
   EXPECT_EQ(kOperatorId, capability_->serving_operator_.GetCode());
@@ -1153,8 +1135,9 @@
   // Service activation is needed
   capability_->mdn_ = "0000000000";
   cellular_->cellular_operator_info_ = &cellular_operator_info_;
-  EXPECT_CALL(cellular_operator_info_, GetOLP(_, _))
-      .WillOnce(Return(true));
+  CellularService::OLP olp;
+  EXPECT_CALL(cellular_operator_info_, GetOLPByMCCMNC(_))
+      .WillOnce(Return(&olp));
   EXPECT_EQ("cellular_0123", capability_->CreateFriendlyServiceName());
   EXPECT_EQ("0123", capability_->serving_operator_.GetCode());
 
@@ -1182,9 +1165,10 @@
   EXPECT_FALSE(capability_->IsServiceActivationRequired());
 
   cellular_->cellular_operator_info_ = &cellular_operator_info_;
-  EXPECT_CALL(cellular_operator_info_, GetOLP(_, _))
-      .WillOnce(Return(false))
-      .WillRepeatedly(Return(true));
+  CellularService::OLP olp;
+  EXPECT_CALL(cellular_operator_info_, GetOLPByMCCMNC(_))
+      .WillOnce(Return((const CellularService::OLP *)NULL))
+      .WillRepeatedly(Return(&olp));
   EXPECT_FALSE(capability_->IsServiceActivationRequired());
 
   capability_->mdn_ = "";
diff --git a/cellular_operator_info.cc b/cellular_operator_info.cc
index ab6f75f..92443b9 100644
--- a/cellular_operator_info.cc
+++ b/cellular_operator_info.cc
@@ -1,10 +1,19 @@
-// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Copyright (c) 2013 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/cellular_operator_info.h"
 
+#include <base/string_number_conversions.h>
+#include <base/string_split.h>
+#include <base/string_util.h>
+
+#include "shill/file_reader.h"
+#include "shill/logging.h"
+
+using std::map;
 using std::string;
+using std::vector;
 
 namespace shill {
 
@@ -16,48 +25,407 @@
 
 }  // namespace
 
-CellularOperatorInfo::CellularOperatorInfo(GLib *glib) : info_file_(glib) {}
+CellularOperatorInfo::CellularOperator::CellularOperator()
+    : is_primary_(false),
+      requires_roaming_(false) {}
+CellularOperatorInfo::CellularOperator::~CellularOperator() {}
 
+CellularOperatorInfo::MobileAPN::MobileAPN() {}
+CellularOperatorInfo::MobileAPN::~MobileAPN() {}
+
+CellularOperatorInfo::CellularOperatorInfo() {}
 CellularOperatorInfo::~CellularOperatorInfo() {}
 
+const ScopedVector<CellularOperatorInfo::CellularOperator> &
+CellularOperatorInfo::operators() const {
+  return operators_;
+}
+
+// static
+string CellularOperatorInfo::FormattedMCCMNC(const string &mccmnc) {
+  return "[MCCMNC=" + mccmnc + "]";
+}
+
+// static
+string CellularOperatorInfo::FormattedSID(const string &sid) {
+  return "[SID=" + sid + "]";
+}
+
+const CellularOperatorInfo::CellularOperator *
+CellularOperatorInfo::GetCellularOperatorByMCCMNC(const string &mccmnc) {
+  LOG(INFO) << __func__ << "(" << FormattedMCCMNC(mccmnc) << ")";
+
+  map<string, CellularOperator *>::const_iterator it =
+      mccmnc_to_operator_.find(mccmnc);
+  if (it == mccmnc_to_operator_.end()) {
+    LOG(ERROR) << "Operator with " << FormattedMCCMNC(mccmnc)
+               << " not found.";
+    return NULL;
+  }
+
+  return it->second;
+}
+
+const CellularOperatorInfo::CellularOperator *
+CellularOperatorInfo::GetCellularOperatorBySID(const string &sid) {
+  LOG(INFO) << __func__ << "(" << FormattedSID(sid) << ")";
+
+  map<string, CellularOperator *>::const_iterator it =
+      sid_to_operator_.find(sid);
+  if (it == sid_to_operator_.end()) {
+    LOG(ERROR) << "Operator with " << FormattedSID(sid) << " not found.";
+    return NULL;
+  }
+
+  return it->second;
+}
+
+const vector<const CellularOperatorInfo::CellularOperator *> *
+CellularOperatorInfo::GetCellularOperatorsByName(const string &name) {
+  LOG(INFO) << __func__ << "(" << name << ")";
+
+  map<string, vector<const CellularOperator *> >::const_iterator it =
+      name_to_operators_.find(name);
+  if (it == name_to_operators_.end()) {
+    LOG(ERROR) << "Given name \"" << name << "\" did not match any operators.";
+    return NULL;
+  }
+
+  return &it->second;
+}
+
+const CellularService::OLP *
+CellularOperatorInfo::GetOLPByMCCMNC(const string &mccmnc) {
+  LOG(INFO) << __func__ << "(" << FormattedMCCMNC(mccmnc) << ")";
+
+  const CellularOperator *provider = GetCellularOperatorByMCCMNC(mccmnc);
+  if (!provider)
+    return NULL;
+
+  map<string, uint32>::const_iterator it =
+    provider->mccmnc_to_olp_idx_.find(mccmnc);
+
+  if (it == provider->mccmnc_to_olp_idx_.end())
+    return NULL;
+
+  uint32 index = it->second;
+  if (index >= provider->olp_list_.size()) {
+    LOG(ERROR) << "Invalid OLP index found for "
+               << FormattedMCCMNC(mccmnc) << ".";
+    return NULL;
+  }
+
+  return provider->olp_list_[index];
+}
+
+const CellularService::OLP *
+CellularOperatorInfo::GetOLPBySID(const string &sid) {
+  LOG(INFO) << __func__ << "(" << FormattedSID(sid) << ")";
+
+  const CellularOperator *provider = GetCellularOperatorBySID(sid);
+  if (!provider)
+    return NULL;
+
+  map<string, uint32>::const_iterator it =
+    provider->sid_to_olp_idx_.find(sid);
+
+  if (it == provider->sid_to_olp_idx_.end())
+    return NULL;
+
+  uint32 index = it->second;
+  if (index >= provider->olp_list_.size()) {
+    LOG(ERROR) << "Invalid OLP index found for " << FormattedSID(sid) << ".";
+    return NULL;
+  }
+
+  return provider->olp_list_[index];
+}
+
+void CellularOperatorInfo::ClearOperators() {
+  operators_.reset();
+  mccmnc_to_operator_.clear();
+  sid_to_operator_.clear();
+  name_to_operators_.clear();
+}
+
+// static
+bool CellularOperatorInfo::ParseKeyValue(const string &line,
+                                         char key_value_delimiter,
+                                         std::string *key,
+                                         std::string *value) {
+  size_t length = line.length();
+  for (size_t i = 0; i < length; ++i) {
+    if (line[i] == key_value_delimiter) {
+      key->assign(line, 0, i);
+      value->assign(line, i + 1, length-i);
+      return true;
+    }
+  }
+  return false;
+}
+
 bool CellularOperatorInfo::Load(const FilePath &info_file_path) {
-  info_file_.set_path(info_file_path);
-  if (!info_file_.Open()) {
-    LOG(ERROR) << "Could not open cellular operator info file '"
-               << info_file_path.value() << "'.";
+  LOG(INFO) << __func__ << "(" << info_file_path.value() << ")";
+
+  // Clear any previus operators.
+  ClearOperators();
+
+  FileReader file_reader;
+  if (!file_reader.Open(info_file_path)) {
+    LOG(ERROR) << "Could not open operator info file.";
     return false;
   }
+
+  // See data/cellular_operator_info for the format of file contents.
+
+  ParserState state;
+
+  typedef bool (CellularOperatorInfo::*KeyHandlerFunc)
+      (ParserState *, const string &);
+  map<string, KeyHandlerFunc> key_handler_map;
+  key_handler_map["provider"] = &CellularOperatorInfo::HandleProvider;
+  key_handler_map["mccmnc"] = &CellularOperatorInfo::HandleMCCMNC;
+  key_handler_map["name"] = &CellularOperatorInfo::HandleNameKey;
+  key_handler_map["apn"] = &CellularOperatorInfo::HandleAPN;
+  key_handler_map["sid"] = &CellularOperatorInfo::HandleSID;
+  key_handler_map["olp"] = &CellularOperatorInfo::HandleOLP;
+  key_handler_map["country"] = &CellularOperatorInfo::HandleCountry;
+
+  string line;
+  bool firstline = true;
+  while (file_reader.ReadLine(&line)) {
+    // Skip line comments.
+    TrimWhitespaceASCII(line, TRIM_ALL, &line);
+    if (line[0] == '#' || line.empty())
+      continue;
+
+    // If line consists of a single null character, skip
+    if (line.length() == 1 && line[0] == '\0')
+      continue;
+
+    string key, value;
+    if (!ParseKeyValue(line, ':', &key, &value)) {
+      LOG(ERROR) << "Badly formed line: " << line;
+      return false;
+    }
+
+    if (firstline) {
+      if (key != "serviceproviders") {
+        LOG(ERROR) << "File does not begin with \"serviceproviders\" "
+                   << "entry.";
+        return false;
+      }
+      if (value != "3.0") {
+        LOG(ERROR) << "Unrecognized serviceproviders format.";
+        return false;
+      }
+      firstline = false;
+    } else {
+      map<string, KeyHandlerFunc>::iterator it = key_handler_map.find(key);
+      if (it == key_handler_map.end()) {
+        LOG(ERROR) << "Invalid key \"" << key << "\".";
+        return false;
+      }
+      KeyHandlerFunc func = it->second;
+      if (!(this->*func)(&state, value)) {
+        LOG(ERROR) << "Failed to parse \"" << key << "\" entry.";
+        return false;
+      }
+    }
+  }
   return true;
 }
 
-bool CellularOperatorInfo::GetOLP(const string &operator_id,
-                                  CellularService::OLP *olp) {
-  if (!info_file_.ContainsGroup(operator_id)) {
-    LOG(INFO) << "Could not find cellular operator '" << operator_id << "'.";
+bool CellularOperatorInfo::HandleCountry(ParserState *state,
+                                         const string &value) {
+  state->country = value;
+  return true;
+}
+
+bool CellularOperatorInfo::HandleNameKey(ParserState *state,
+                                         const string &value) {
+  if (state->parsing_apn)
+    return HandleAPNName(state, value);
+  return HandleName(state, value);
+}
+
+bool CellularOperatorInfo::HandleProvider(ParserState *state,
+                                          const string &value) {
+  state->parsing_apn = false;
+
+  vector<string> fields;
+  base::SplitString(value, ',', &fields);
+  if (fields.size() != 4) {
+    LOG(ERROR) << "Badly formed \"provider\" entry.";
     return false;
   }
 
-  string value;
-  if (!info_file_.GetString(operator_id, kKeyOLPURL, &value)) {
-    LOG(INFO) << "Could not find online payment portal info for cellular "
-              << "operator '" << operator_id << "'.";
+  int is_primary = 0;
+  if (!base::StringToInt(fields[2], &is_primary)) {
+    LOG(ERROR) << "Badly formed value for \"is_primary\": " << fields[2];
+    return false;
+  }
+  int requires_roaming = 0;
+  if (!base::StringToInt(fields[3], &requires_roaming)) {
+    LOG(ERROR) << "Badly formed value for \"requires_roaming\": "
+               << fields[3];
+    return false;
+  }
+  state->provider = new CellularOperator();
+  state->provider->is_primary_ = is_primary != 0;
+  state->provider->requires_roaming_ = requires_roaming != 0;
+  state->provider->country_ = state->country;
+
+  operators_.push_back(state->provider);
+  return true;
+}
+
+bool CellularOperatorInfo::HandleMCCMNC(ParserState *state,
+                                        const string &value) {
+  if (value.empty()) {
+    LOG(ERROR) << "Empty \"mccmnc\" value.";
+    return false;
+  }
+  if (!state->provider) {
+    LOG(ERROR) << "Found \"mccmnc\" entry without \"provider\".";
     return false;
   }
 
-  olp->SetURL(value);
-
-  if (info_file_.GetString(operator_id, kKeyOLPMethod, &value)) {
-    olp->SetMethod(value);
-  } else {
-    olp->SetMethod("");
+  vector<string> mccmnc_list;
+  base::SplitString(value, ',', &mccmnc_list);
+  if ((mccmnc_list.size() % 2) != 0) {
+    LOG(ERROR) << "Badly formatted \"mccmnc\" entry. "
+               << "Expected even number of elements.";
+    return false;
   }
 
-  if (info_file_.GetString(operator_id, kKeyOLPPostData, &value)) {
-    olp->SetPostData(value);
-  } else {
-    olp->SetPostData("");
+  for (size_t i = 0; i < mccmnc_list.size(); i += 2) {
+    const string &mccmnc = mccmnc_list[i];
+    if (!mccmnc.empty()) {
+      state->provider->mccmnc_list_.push_back(mccmnc);
+      mccmnc_to_operator_[mccmnc] = state->provider;
+      int index = -1;
+      if (base::StringToInt(mccmnc_list[i + 1], &index))
+        state->provider->mccmnc_to_olp_idx_[mccmnc] = index;
+    }
+  }
+  return true;
+}
+
+bool CellularOperatorInfo::HandleAPN(ParserState *state,
+                                     const string &value) {
+  if (!state->provider) {
+    LOG(ERROR) << "Found \"apn\" entry without \"provider\".";
+    return false;
+  }
+  vector<string> fields;
+  base::SplitString(value, ',', &fields);
+  if (fields.size() != 4) {
+    LOG(ERROR) << "Badly formed \"apn\" entry.";
+    return false;
+  }
+  state->apn = new MobileAPN();
+  state->apn->apn_ = fields[1];
+  state->apn->username_ = fields[2];
+  state->apn->password_ = fields[3];
+  state->provider->apn_list_.push_back(state->apn);
+  state->parsing_apn = true;
+  return true;
+}
+
+bool CellularOperatorInfo::HandleAPNName(ParserState *state,
+                                         const string &value) {
+  if (!(state->parsing_apn && state->apn)) {
+    LOG(ERROR) << "APN not being parsed.";
+    return false;
+  }
+  LocalizedName name;
+  if (!ParseNameLine(value, &name))
+    return false;
+  state->apn->name_list_.push_back(name);
+  return true;
+}
+
+bool CellularOperatorInfo::HandleName(ParserState *state,
+                                      const string &value) {
+  if (!state->provider) {
+    LOG(ERROR) << "Found \"name\" entry without \"provider\".";
+    return false;
+  }
+  LocalizedName name;
+  if (!ParseNameLine(value, &name))
+    return false;
+  if (state->provider->name_list_.size() == 0) {
+    vector<const CellularOperator *> &matching_operators =
+        name_to_operators_[name.name];
+    matching_operators.push_back(state->provider);
+  }
+  state->provider->name_list_.push_back(name);
+  return true;
+}
+
+bool CellularOperatorInfo::ParseNameLine(const string &value,
+                                         LocalizedName *name) {
+  vector<string> fields;
+  base::SplitString(value, ',', &fields);
+  if (fields.size() != 2) {
+    LOG(ERROR) << "Badly formed \"name\" entry.";
+    return false;
+  }
+  name->language = fields[0];
+  name->name = fields[1];
+  return true;
+}
+
+bool CellularOperatorInfo::HandleSID(ParserState *state,
+                                     const string &value) {
+  if (value.empty()) {
+    LOG(ERROR) << "Empty \"sid\" value.";
+    return false;
+  }
+  if (!state->provider) {
+    LOG(ERROR) << "Found \"sid\" entry without \"provider\".";
+    return false;
+  }
+  vector<string> sid_list;
+  base::SplitString(value, ',', &sid_list);
+  if ((sid_list.size() % 2) != 0) {
+    LOG(ERROR) << "Badly formatted \"sid\" entry. "
+               << "Expected even number of elements. ";
+    return false;
   }
 
+  for (size_t i = 0; i < sid_list.size(); i += 2) {
+    const string &sid = sid_list[i];
+    if (!sid.empty()) {
+      state->provider->sid_list_.push_back(sid);
+      sid_to_operator_[sid] = state->provider;
+      int index = -1;
+      if (base::StringToInt(sid_list[i + 1], &index))
+        state->provider->sid_to_olp_idx_[sid] = index;
+    }
+  }
+  return true;
+}
+
+bool CellularOperatorInfo::HandleOLP(ParserState *state,
+                                     const string &value) {
+  if (!state->provider) {
+    LOG(ERROR) << "Found \"olp\" entry without \"provider\"";
+    return false;
+  }
+  vector<string> fields;
+  base::SplitString(value, ',', &fields);
+  if (fields.size() != 3) {
+    LOG(ERROR) << "Badly formed \"apn\" entry.";
+    return false;
+  }
+  CellularService::OLP *olp = new CellularService::OLP();
+  olp->SetMethod(fields[0]);
+  olp->SetURL(fields[1]);
+  olp->SetPostData(fields[2]);
+
+  state->provider->olp_list_.push_back(olp);
   return true;
 }
 
diff --git a/cellular_operator_info.h b/cellular_operator_info.h
index f09f319..8e9c0fd 100644
--- a/cellular_operator_info.h
+++ b/cellular_operator_info.h
@@ -1,37 +1,193 @@
-// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Copyright (c) 2013 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_CELLULAR_OPERATOR_INFO_H_
 #define SHILL_CELLULAR_OPERATOR_INFO_H_
 
+#include <map>
 #include <string>
+#include <vector>
 
 #include <base/basictypes.h>
 #include <base/file_path.h>
+#include <base/memory/scoped_vector.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
 
 #include "shill/cellular_service.h"
+#include "shill/file_reader.h"
 #include "shill/key_file_store.h"
 
 namespace shill {
 
-class GLib;
-
 class CellularOperatorInfo {
  public:
-  explicit CellularOperatorInfo(GLib *glib);
+  CellularOperatorInfo();
   virtual ~CellularOperatorInfo();
 
+  struct LocalizedName {
+    std::string name;
+    std::string language;
+  };
+
+  class MobileAPN {
+   public:
+    MobileAPN();
+    ~MobileAPN();
+
+    const std::string &apn() const { return apn_; }
+    const std::vector<LocalizedName> &name_list() const { return name_list_; }
+    const std::string &username() const { return username_; }
+    const std::string &password() const { return password_; }
+
+   private:
+    friend class CellularOperatorInfo;
+
+    std::string apn_;
+    std::vector<LocalizedName> name_list_;
+    std::string username_;
+    std::string password_;
+
+    DISALLOW_COPY_AND_ASSIGN(MobileAPN);
+  };
+
+  class CellularOperator {
+   public:
+    CellularOperator();
+    virtual ~CellularOperator();
+
+    const std::string &country() const { return country_; }
+    const std::vector<std::string> &mccmnc_list() const {
+        return mccmnc_list_;
+    }
+    const std::vector<std::string> &sid_list() const { return sid_list_; }
+    const std::vector<LocalizedName> &name_list() const { return name_list_; }
+    const ScopedVector<MobileAPN> &apn_list() const { return apn_list_; }
+    const ScopedVector<CellularService::OLP> &olp_list() const {
+        return olp_list_;
+    }
+    bool is_primary() const { return is_primary_; }
+    bool requires_roaming() const { return requires_roaming_; }
+
+   private:
+    friend class CellularOperatorInfo;
+    friend class CellularOperatorInfoTest;
+    FRIEND_TEST(CellularOperatorInfoTest, HandleMCCMNC);
+    FRIEND_TEST(CellularOperatorInfoTest, HandleSID);
+
+    std::string country_;
+    std::vector<std::string> mccmnc_list_;
+    std::vector<std::string> sid_list_;
+    std::vector<LocalizedName> name_list_;
+    ScopedVector<MobileAPN> apn_list_;
+    ScopedVector<CellularService::OLP> olp_list_;
+    std::map<std::string, uint32> mccmnc_to_olp_idx_;
+    std::map<std::string, uint32> sid_to_olp_idx_;
+    bool is_primary_;
+    bool requires_roaming_;
+
+    DISALLOW_COPY_AND_ASSIGN(CellularOperator);
+  };
+
   // Loads the operator info from |info_file_path|. Returns true on success.
   virtual bool Load(const FilePath &info_file_path);
 
-  // Gets the online payment portal info of the operator with ID |operator_id|.
-  // Returns true if the info is found.
-  virtual bool GetOLP(const std::string &operator_id,
-                      CellularService::OLP *olp);
+  // Gets the cellular operator info of the operator with MCCMNC |mccmnc|.
+  // If found, returns a pointer to the matching operator.
+  virtual const CellularOperator *
+      GetCellularOperatorByMCCMNC(const std::string &mccmnc);
+
+  // Gets the cellular operator info of the operator with SID |sid|.
+  // If found, returns a pointer to the matching operator.
+  virtual const CellularOperator *
+      GetCellularOperatorBySID(const std::string &sid);
+
+  // Gets the cellular operator info of the operators that match the name
+  // |name|, such that each element contains information about the operator
+  // in different countries. The given name must be the first enumerated name
+  // for the operator in the operator database.
+  // If found, returns a pointer to a vector containing the matching operators.
+  virtual const std::vector<const CellularOperator *> *
+      GetCellularOperatorsByName(const std::string &name);
+
+  // Gets the online payment portal info of the operator with MCCMNC |mccmnc|.
+  // If found, returns a pointer to the matching OLP.
+  virtual const CellularService::OLP *GetOLPByMCCMNC(const std::string &mccmnc);
+
+  // Gets the online payment portal info of the operator with SID |sid|.
+  // If found, returns a pointer to the matching OLP.
+  virtual const CellularService::OLP *GetOLPBySID(const std::string &sid);
+
+  // Returns a list of all operators.
+  virtual const ScopedVector<CellularOperator> &operators() const;
 
  private:
-  KeyFileStore info_file_;
+  friend class CellularOperatorInfoTest;
+  FRIEND_TEST(CellularOperatorInfoTest, HandleAPN);
+  FRIEND_TEST(CellularOperatorInfoTest, HandleAPNName);
+  FRIEND_TEST(CellularOperatorInfoTest, HandleMCCMNC);
+  FRIEND_TEST(CellularOperatorInfoTest, HandleName);
+  FRIEND_TEST(CellularOperatorInfoTest, HandleOLP);
+  FRIEND_TEST(CellularOperatorInfoTest, HandleProvider);
+  FRIEND_TEST(CellularOperatorInfoTest, HandleSID);
+  FRIEND_TEST(CellularOperatorInfoTest, ParseKeyValue);
+  FRIEND_TEST(CellularOperatorInfoTest, ParseNameLine);
+  FRIEND_TEST(CellularOperatorInfoTest, ParseSuccess);
+
+  struct ParserState {
+    ParserState() : provider(NULL),
+                    apn(NULL),
+                    parsing_apn(false) {}
+
+    std::string country;
+    CellularOperator *provider;
+    MobileAPN *apn;
+
+    bool parsing_apn;
+  };
+
+  bool HandleProvider(ParserState *state,
+                      const std::string &value);
+  bool HandleMCCMNC(ParserState *state,
+                    const std::string &value);
+  bool HandleNameKey(ParserState *state,
+                     const std::string &value);
+  bool HandleName(ParserState *state,
+                  const std::string &value);
+  bool HandleAPN(ParserState *state,
+                 const std::string &value);
+  bool HandleAPNName(ParserState *state,
+                     const std::string &value);
+  bool HandleSID(ParserState *state,
+                 const std::string &value);
+  bool HandleOLP(ParserState *state,
+                 const std::string &value);
+  bool HandleCountry(ParserState *state,
+                     const std::string &value);
+  bool ParseNameLine(const std::string &value,
+                     LocalizedName *name);
+
+  void ClearOperators();
+
+  // Functions that return specially formatted strings for logging
+  // MCCMNC and SID to help with scrubbing.
+  static std::string FormattedMCCMNC(const std::string &mccmnc);
+  static std::string FormattedSID(const std::string &sid);
+
+  // Splits |line| into two strings, separated with the |key_value_delimiter|.
+  // Returns |false| if line does not contain |key_value_delimiter|, otherwise
+  // returns the first substring in |key| and the second substring in |value|,
+  // and returns true.
+  static bool ParseKeyValue(const std::string &line,
+                            char key_value_delimiter,
+                            std::string *key,
+                            std::string *value);
+
+  ScopedVector<CellularOperator> operators_;
+  std::map<std::string, CellularOperator *> mccmnc_to_operator_;
+  std::map<std::string, CellularOperator *> sid_to_operator_;
+  std::map<std::string,
+           std::vector<const CellularOperator *> > name_to_operators_;
 
   DISALLOW_COPY_AND_ASSIGN(CellularOperatorInfo);
 };
diff --git a/cellular_operator_info_unittest.cc b/cellular_operator_info_unittest.cc
index 5537e97..87ec7b8 100644
--- a/cellular_operator_info_unittest.cc
+++ b/cellular_operator_info_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Copyright (c) 2013 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.
 
@@ -7,7 +7,9 @@
 #include <base/file_util.h>
 #include <gtest/gtest.h>
 
+using std::map;
 using std::string;
+using std::vector;
 
 namespace shill {
 
@@ -17,22 +19,37 @@
     "#\n"
     "# Comments\n"
     "#\n"
-    "[000001]\n"
-    "OLP.URL=https://testurl\n"
-    "OLP.Method=POST\n"
-    "OLP.PostData=imei=${imei}&iccid=${iccid}\n"
+    "serviceproviders:3.0\n"
+    "country:us\n"
     "\n"
-    "[000002]\n"
-    "OLP.URL=https://testurl2\n"
+    "# TestProvider1\n"
+    "provider:1,1,0,1\n"
+    "name:,TestProvider1\n"
+    "mccmnc:000001,0,000002,0\n"
+    "sid:1,0,2,0,3,0\n"
+    "olp:POST,https://testurl,imei=${imei}&iccid=${iccid}\n"
+    "apn:2,testprovider1apn,,\n"
+    "name:en,Test Provider 1\n"
+    "name:de,Testmobilfunkanbieter 1\n"
     "\n"
-    "[000003]\n"
-    "Name=test\n";
+    "# TestProvider2\n"
+    "provider:1,2,1,0\n"
+    "name:,TestProviderTwo\n"
+    "name:,TestProvider2\n"
+    "mccmnc:100001,1,100002,0\n"
+    "sid:4,0,5,1\n"
+    "olp:,https://testurl2,\n"
+    "olp:,https://testurl3,\n"
+    "apn:1,testprovider2apn,,\n"
+    "name:,Test Provider 2\n"
+    "apn:1,testprovider2apn2,testusername,testpassword\n"
+    "name:tr,Test Operatoru 2\n";
 
 }  // namespace
 
 class CellularOperatorInfoTest : public testing::Test {
  public:
-  CellularOperatorInfoTest() : info_(&glib_) {}
+  CellularOperatorInfoTest() {}
 
  protected:
   void SetUp() {
@@ -51,22 +68,419 @@
   CellularOperatorInfo info_;
 };
 
-TEST_F(CellularOperatorInfoTest, GetOLP) {
+TEST_F(CellularOperatorInfoTest, ParseSuccess) {
+  EXPECT_TRUE(info_.Load(info_file_path_));
+  EXPECT_EQ(info_.operators().size(), 2);
+
+  const CellularOperatorInfo::CellularOperator *provider = info_.operators()[0];
+  EXPECT_FALSE(provider->is_primary());
+  EXPECT_TRUE(provider->requires_roaming());
+  EXPECT_EQ(provider->country(), "us");
+  EXPECT_EQ(provider->name_list().size(), 1);
+  EXPECT_EQ(provider->name_list()[0].language, "");
+  EXPECT_EQ(provider->name_list()[0].name, "TestProvider1");
+  EXPECT_EQ(provider->mccmnc_list().size(), 2);
+  EXPECT_EQ(provider->mccmnc_list()[0], "000001");
+  EXPECT_EQ(provider->mccmnc_list()[1], "000002");
+  EXPECT_EQ(provider->sid_list().size(), 3);
+  EXPECT_EQ(provider->sid_list()[0], "1");
+  EXPECT_EQ(provider->sid_list()[1], "2");
+  EXPECT_EQ(provider->sid_list()[2], "3");
+  EXPECT_EQ(provider->olp_list().size(), 1);
+  EXPECT_EQ(provider->olp_list()[0]->GetURL(), "https://testurl");
+  EXPECT_EQ(provider->olp_list()[0]->GetMethod(), "POST");
+  EXPECT_EQ(provider->olp_list()[0]->GetPostData(),
+            "imei=${imei}&iccid=${iccid}");
+  EXPECT_EQ(provider->apn_list().size(), 1);
+  EXPECT_EQ(provider->apn_list()[0]->apn(), "testprovider1apn");
+  EXPECT_EQ(provider->apn_list()[0]->username(), "");
+  EXPECT_EQ(provider->apn_list()[0]->password(), "");
+  EXPECT_EQ(provider->apn_list()[0]->name_list().size(), 2);
+  EXPECT_EQ(provider->apn_list()[0]->name_list()[0].language, "en");
+  EXPECT_EQ(provider->apn_list()[0]->name_list()[0].name, "Test Provider 1");
+  EXPECT_EQ(provider->apn_list()[0]->name_list()[1].language, "de");
+  EXPECT_EQ(provider->apn_list()[0]->name_list()[1].name,
+            "Testmobilfunkanbieter 1");
+
+  const CellularOperatorInfo::CellularOperator *provider2 =
+      info_.operators()[1];
+  EXPECT_TRUE(provider2->is_primary());
+  EXPECT_FALSE(provider2->requires_roaming());
+  EXPECT_EQ(provider2->country(), "us");
+  EXPECT_EQ(provider2->name_list().size(), 2);
+  EXPECT_EQ(provider2->name_list()[0].language, "");
+  EXPECT_EQ(provider2->name_list()[0].name, "TestProviderTwo");
+  EXPECT_EQ(provider2->name_list()[1].language, "");
+  EXPECT_EQ(provider2->name_list()[1].name, "TestProvider2");
+  EXPECT_EQ(provider2->mccmnc_list().size(), 2);
+  EXPECT_EQ(provider2->mccmnc_list()[0], "100001");
+  EXPECT_EQ(provider2->mccmnc_list()[1], "100002");
+  EXPECT_EQ(provider2->sid_list().size(), 2);
+  EXPECT_EQ(provider2->sid_list()[0], "4");
+  EXPECT_EQ(provider2->sid_list()[1], "5");
+  EXPECT_EQ(provider2->olp_list().size(), 2);
+  EXPECT_EQ(provider2->olp_list()[0]->GetURL(), "https://testurl2");
+  EXPECT_EQ(provider2->olp_list()[0]->GetMethod(), "");
+  EXPECT_EQ(provider2->olp_list()[0]->GetPostData(), "");
+  EXPECT_EQ(provider2->olp_list()[1]->GetURL(), "https://testurl3");
+  EXPECT_EQ(provider2->olp_list()[1]->GetMethod(), "");
+  EXPECT_EQ(provider2->olp_list()[1]->GetPostData(), "");
+  EXPECT_EQ(provider2->apn_list().size(), 2);
+  EXPECT_EQ(provider2->apn_list()[0]->apn(), "testprovider2apn");
+  EXPECT_EQ(provider2->apn_list()[0]->username(), "");
+  EXPECT_EQ(provider2->apn_list()[0]->password(), "");
+  EXPECT_EQ(provider2->apn_list()[0]->name_list().size(), 1);
+  EXPECT_EQ(provider2->apn_list()[0]->name_list()[0].language, "");
+  EXPECT_EQ(provider2->apn_list()[0]->name_list()[0].name, "Test Provider 2");
+  EXPECT_EQ(provider2->apn_list()[1]->apn(), "testprovider2apn2");
+  EXPECT_EQ(provider2->apn_list()[1]->username(), "testusername");
+  EXPECT_EQ(provider2->apn_list()[1]->password(), "testpassword");
+  EXPECT_EQ(provider2->apn_list()[1]->name_list().size(), 1);
+  EXPECT_EQ(provider2->apn_list()[1]->name_list()[0].language, "tr");
+  EXPECT_EQ(provider2->apn_list()[1]->name_list()[0].name, "Test Operatoru 2");
+}
+
+TEST_F(CellularOperatorInfoTest, GetCellularOperatorByMCCMNC) {
   EXPECT_TRUE(info_.Load(info_file_path_));
 
-  CellularService::OLP olp;
-  EXPECT_TRUE(info_.GetOLP("000001", &olp));
-  EXPECT_EQ("https://testurl", olp.GetURL());
-  EXPECT_EQ("POST", olp.GetMethod());
-  EXPECT_EQ("imei=${imei}&iccid=${iccid}", olp.GetPostData());
+  EXPECT_FALSE(info_.GetCellularOperatorByMCCMNC("1"));
+  EXPECT_FALSE(info_.GetCellularOperatorByMCCMNC("000003"));
+  EXPECT_FALSE(info_.GetCellularOperatorByMCCMNC("bananas"));
+  EXPECT_FALSE(info_.GetCellularOperatorByMCCMNC("abcd"));
 
-  EXPECT_TRUE(info_.GetOLP("000002", &olp));
-  EXPECT_EQ("https://testurl2", olp.GetURL());
-  EXPECT_EQ("", olp.GetMethod());
-  EXPECT_EQ("", olp.GetPostData());
+  const CellularOperatorInfo::CellularOperator *provider = NULL;
+  EXPECT_TRUE((provider = info_.GetCellularOperatorByMCCMNC("000001")));
+  EXPECT_EQ(provider, info_.operators()[0]);
+  EXPECT_TRUE((provider = info_.GetCellularOperatorByMCCMNC("100001")));
+  EXPECT_EQ(provider, info_.operators()[1]);
+  EXPECT_TRUE((provider = info_.GetCellularOperatorByMCCMNC("000002")));
+  EXPECT_EQ(provider, info_.operators()[0]);
+  EXPECT_TRUE((provider = info_.GetCellularOperatorByMCCMNC("100002")));
+  EXPECT_EQ(provider, info_.operators()[1]);
+}
 
-  EXPECT_FALSE(info_.GetOLP("000003", &olp));
-  EXPECT_FALSE(info_.GetOLP("000004", &olp));
+TEST_F(CellularOperatorInfoTest, GetCellularOperatorBySID) {
+  EXPECT_TRUE(info_.Load(info_file_path_));
+
+  EXPECT_FALSE(info_.GetCellularOperatorBySID("000001"));
+  EXPECT_FALSE(info_.GetCellularOperatorBySID("000002"));
+  EXPECT_FALSE(info_.GetCellularOperatorBySID("100001"));
+  EXPECT_FALSE(info_.GetCellularOperatorBySID("100002"));
+  EXPECT_FALSE(info_.GetCellularOperatorBySID("banana"));
+
+  const CellularOperatorInfo::CellularOperator *provider = NULL;
+  EXPECT_TRUE((provider = info_.GetCellularOperatorBySID("1")));
+  EXPECT_EQ(provider, info_.operators()[0]);
+  EXPECT_TRUE((provider = info_.GetCellularOperatorBySID("4")));
+  EXPECT_EQ(provider, info_.operators()[1]);
+  EXPECT_TRUE((provider = info_.GetCellularOperatorBySID("2")));
+  EXPECT_EQ(provider, info_.operators()[0]);
+  EXPECT_TRUE((provider = info_.GetCellularOperatorBySID("5")));
+  EXPECT_EQ(provider, info_.operators()[1]);
+  EXPECT_TRUE((provider = info_.GetCellularOperatorBySID("3")));
+  EXPECT_EQ(provider, info_.operators()[0]);
+}
+
+TEST_F(CellularOperatorInfoTest, GetCellularOperatorsByName) {
+  EXPECT_TRUE(info_.Load(info_file_path_));
+
+  const vector<const CellularOperatorInfo::CellularOperator *> *list = NULL;
+  EXPECT_FALSE(info_.GetCellularOperatorsByName("banana"));
+  EXPECT_FALSE(info_.GetCellularOperatorsByName("TestProvider2"));
+
+  EXPECT_TRUE((list = info_.GetCellularOperatorsByName("TestProvider1")));
+  EXPECT_EQ(list->size(), 1);
+  EXPECT_EQ((*list)[0]->apn_list()[0]->apn(), "testprovider1apn");
+
+  EXPECT_FALSE(info_.GetCellularOperatorsByName("TestProvider2"));
+
+  EXPECT_TRUE((list = info_.GetCellularOperatorsByName("TestProviderTwo")));
+  EXPECT_EQ(list->size(), 1);
+  EXPECT_EQ((*list)[0]->apn_list()[0]->apn(), "testprovider2apn");
+}
+
+TEST_F(CellularOperatorInfoTest, GetOLPByMCCMNC) {
+  EXPECT_TRUE(info_.Load(info_file_path_));
+
+  const CellularService::OLP *olp = NULL;
+  EXPECT_TRUE((olp = info_.GetOLPByMCCMNC("000001")));
+  EXPECT_EQ("https://testurl", olp->GetURL());
+  EXPECT_EQ("POST", olp->GetMethod());
+  EXPECT_EQ("imei=${imei}&iccid=${iccid}", olp->GetPostData());
+
+  EXPECT_TRUE((olp = info_.GetOLPByMCCMNC("000002")));
+  EXPECT_EQ("https://testurl", olp->GetURL());
+  EXPECT_EQ("POST", olp->GetMethod());
+  EXPECT_EQ("imei=${imei}&iccid=${iccid}", olp->GetPostData());
+
+  EXPECT_TRUE((olp = info_.GetOLPByMCCMNC("100001")));
+  EXPECT_EQ("https://testurl3", olp->GetURL());
+  EXPECT_EQ("", olp->GetMethod());
+  EXPECT_EQ("", olp->GetPostData());
+
+  EXPECT_TRUE((olp = info_.GetOLPByMCCMNC("100002")));
+  EXPECT_EQ("https://testurl2", olp->GetURL());
+  EXPECT_EQ("", olp->GetMethod());
+  EXPECT_EQ("", olp->GetPostData());
+
+  EXPECT_FALSE(info_.GetOLPByMCCMNC("000003"));
+  EXPECT_FALSE(info_.GetOLPByMCCMNC("000004"));
+}
+
+TEST_F(CellularOperatorInfoTest, GetOLPBySID) {
+  EXPECT_TRUE(info_.Load(info_file_path_));
+
+  const CellularService::OLP *olp = NULL;
+  EXPECT_TRUE((olp = info_.GetOLPBySID("1")));
+  EXPECT_EQ("https://testurl", olp->GetURL());
+  EXPECT_EQ("POST", olp->GetMethod());
+  EXPECT_EQ("imei=${imei}&iccid=${iccid}", olp->GetPostData());
+
+  EXPECT_TRUE((olp = info_.GetOLPBySID("2")));
+  EXPECT_EQ("https://testurl", olp->GetURL());
+  EXPECT_EQ("POST", olp->GetMethod());
+  EXPECT_EQ("imei=${imei}&iccid=${iccid}", olp->GetPostData());
+
+  EXPECT_TRUE((olp = info_.GetOLPBySID("3")));
+  EXPECT_EQ("https://testurl", olp->GetURL());
+  EXPECT_EQ("POST", olp->GetMethod());
+  EXPECT_EQ("imei=${imei}&iccid=${iccid}", olp->GetPostData());
+
+  EXPECT_TRUE((olp = info_.GetOLPBySID("4")));
+  EXPECT_EQ("https://testurl2", olp->GetURL());
+  EXPECT_EQ("", olp->GetMethod());
+  EXPECT_EQ("", olp->GetPostData());
+
+  EXPECT_TRUE((olp = info_.GetOLPBySID("5")));
+  EXPECT_EQ("https://testurl3", olp->GetURL());
+  EXPECT_EQ("", olp->GetMethod());
+  EXPECT_EQ("", olp->GetPostData());
+
+  EXPECT_FALSE(info_.GetOLPBySID("6"));
+  EXPECT_FALSE(info_.GetOLPBySID("7"));
+}
+
+TEST_F(CellularOperatorInfoTest, HandleProvider) {
+  CellularOperatorInfo::ParserState state;
+  EXPECT_FALSE(info_.HandleProvider(&state, "0,0,0"));
+  EXPECT_FALSE(state.provider);
+  EXPECT_EQ(info_.operators_.size(), 0);
+
+  EXPECT_TRUE(info_.HandleProvider(&state, "0,0,0,0"));
+  EXPECT_TRUE(state.provider);
+  EXPECT_EQ(info_.operators_.size(), 1);
+  EXPECT_FALSE(state.provider->is_primary());
+  EXPECT_FALSE(state.provider->requires_roaming());
+
+  EXPECT_TRUE(info_.HandleProvider(&state, "1,1,0,1"));
+  EXPECT_EQ(info_.operators_.size(), 2);
+  EXPECT_EQ(state.provider, info_.operators_[1]);
+  EXPECT_FALSE(state.provider->is_primary());
+  EXPECT_TRUE(state.provider->requires_roaming());
+}
+
+TEST_F(CellularOperatorInfoTest, HandleMCCMNC) {
+  CellularOperatorInfo::ParserState state;
+  EXPECT_FALSE(info_.HandleMCCMNC(&state, "1,1"));
+
+  EXPECT_TRUE(info_.HandleProvider(&state, "1,1,0,0"));
+  EXPECT_TRUE(state.provider);
+
+  EXPECT_FALSE(info_.HandleMCCMNC(&state, ""));
+  EXPECT_TRUE(state.provider->mccmnc_list().empty());
+  EXPECT_TRUE(info_.mccmnc_to_operator_.empty());
+
+  EXPECT_FALSE(info_.HandleMCCMNC(&state, "000001,0,000002"));
+  EXPECT_TRUE(state.provider->mccmnc_list().empty());
+  EXPECT_TRUE(info_.mccmnc_to_operator_.empty());
+
+  EXPECT_TRUE(info_.HandleMCCMNC(&state, "000001,0,000002,3"));
+  EXPECT_EQ(state.provider->mccmnc_list().size(), 2);
+  EXPECT_EQ(info_.mccmnc_to_operator_.size(), 2);
+  EXPECT_EQ(info_.mccmnc_to_operator_["000001"], state.provider);
+  EXPECT_EQ(info_.mccmnc_to_operator_["000002"], state.provider);
+  EXPECT_EQ(state.provider->mccmnc_list()[0], "000001");
+  EXPECT_EQ(state.provider->mccmnc_list()[1], "000002");
+  EXPECT_EQ(state.provider->mccmnc_to_olp_idx_.size(), 2);
+  EXPECT_EQ(state.provider->mccmnc_to_olp_idx_["000001"], 0);
+  EXPECT_EQ(state.provider->mccmnc_to_olp_idx_["000002"], 3);
+}
+
+TEST_F(CellularOperatorInfoTest, HandleName) {
+  CellularOperatorInfo::ParserState state;
+  EXPECT_FALSE(info_.HandleName(&state, ","));
+
+  EXPECT_TRUE(info_.HandleProvider(&state, "1,1,0,0"));
+  EXPECT_TRUE(state.provider);
+  EXPECT_TRUE(state.provider->name_list().empty());
+
+  EXPECT_FALSE(info_.HandleName(&state, ",,"));
+  EXPECT_TRUE(state.provider->name_list().empty());
+
+  EXPECT_TRUE(info_.HandleName(&state, "en,Test Name"));
+  EXPECT_EQ(state.provider->name_list().size(), 1);
+
+  const CellularOperatorInfo::LocalizedName &name =
+      state.provider->name_list()[0];
+  EXPECT_EQ(name.language, "en");
+  EXPECT_EQ(name.name, "Test Name");
+
+  EXPECT_TRUE(info_.HandleName(&state, ",Other Test Name"));
+  EXPECT_EQ(state.provider->name_list().size(), 2);
+
+  const CellularOperatorInfo::LocalizedName &name2 =
+      state.provider->name_list()[1];
+  EXPECT_EQ(name2.language, "");
+  EXPECT_EQ(name2.name, "Other Test Name");
+}
+
+TEST_F(CellularOperatorInfoTest, HandleAPN) {
+  CellularOperatorInfo::ParserState state;
+  EXPECT_FALSE(info_.HandleAPN(&state, ",,,"));
+
+  EXPECT_TRUE(info_.HandleProvider(&state, "1,1,0,0"));
+  EXPECT_TRUE(state.provider);
+  EXPECT_TRUE(state.provider->apn_list().empty());
+
+  EXPECT_FALSE(info_.HandleAPN(&state, ","));
+  EXPECT_TRUE(state.provider->apn_list().empty());
+
+  EXPECT_FALSE(state.parsing_apn);
+
+  EXPECT_TRUE(info_.HandleAPN(&state, "0,testapn,testusername,testpassword"));
+  EXPECT_EQ(state.provider->apn_list().size(), 1);
+  EXPECT_TRUE(state.parsing_apn);
+
+  const CellularOperatorInfo::MobileAPN *apn = state.provider->apn_list()[0];
+  EXPECT_EQ(apn->apn(), "testapn");
+  EXPECT_EQ(apn->username(), "testusername");
+  EXPECT_EQ(apn->password(), "testpassword");
+}
+
+TEST_F(CellularOperatorInfoTest, HandleAPNName) {
+  CellularOperatorInfo::ParserState state;
+  EXPECT_FALSE(info_.HandleAPNName(&state, ","));
+  state.parsing_apn = true;
+  EXPECT_FALSE(info_.HandleAPNName(&state, ","));
+  state.parsing_apn = false;
+  state.apn = (CellularOperatorInfo::MobileAPN *)1;
+  EXPECT_FALSE(info_.HandleAPNName(&state, ","));
+
+  EXPECT_TRUE(info_.HandleProvider(&state, "1,1,0,0"));
+  EXPECT_TRUE(state.provider);
+  EXPECT_TRUE(info_.HandleAPN(&state, ",,,"));
+  EXPECT_TRUE(state.parsing_apn && state.apn);
+
+  const CellularOperatorInfo::MobileAPN *apn = state.provider->apn_list()[0];
+
+  EXPECT_FALSE(info_.HandleAPNName(&state, ",,"));
+  EXPECT_EQ(apn->name_list().size(), 0);
+
+  EXPECT_TRUE(info_.HandleAPNName(&state, "en,APN Name"));
+  EXPECT_EQ(apn->name_list().size(), 1);
+
+  const CellularOperatorInfo::LocalizedName &name = apn->name_list()[0];
+  EXPECT_EQ(name.language, "en");
+  EXPECT_EQ(name.name, "APN Name");
+
+  EXPECT_TRUE(info_.HandleAPNName(&state, ",Other APN Name"));
+  EXPECT_EQ(apn->name_list().size(), 2);
+
+  const CellularOperatorInfo::LocalizedName &name2 = apn->name_list()[1];
+  EXPECT_EQ(name2.language, "");
+  EXPECT_EQ(name2.name, "Other APN Name");
+}
+
+TEST_F(CellularOperatorInfoTest, HandleSID) {
+ CellularOperatorInfo::ParserState state;
+ EXPECT_FALSE(info_.HandleSID(&state, "1,0"));
+
+ EXPECT_TRUE(info_.HandleProvider(&state, "1,1,0,0"));
+ EXPECT_TRUE(state.provider);
+
+ EXPECT_FALSE(info_.HandleSID(&state, ""));
+ EXPECT_TRUE(state.provider->sid_list().empty());
+ EXPECT_TRUE(info_.sid_to_operator_.empty());
+
+ EXPECT_FALSE(info_.HandleSID(&state, "1,2,3"));
+ EXPECT_TRUE(state.provider->sid_list().empty());
+ EXPECT_TRUE(info_.sid_to_operator_.empty());
+
+ EXPECT_TRUE(info_.HandleSID(&state, "1,5,2,3,3,0"));
+ EXPECT_EQ(state.provider->sid_list().size(), 3);
+ EXPECT_EQ(info_.sid_to_operator_.size(), 3);
+ EXPECT_EQ(info_.sid_to_operator_["1"], state.provider);
+ EXPECT_EQ(info_.sid_to_operator_["2"], state.provider);
+ EXPECT_EQ(info_.sid_to_operator_["3"], state.provider);
+ EXPECT_EQ(state.provider->sid_list()[0], "1");
+ EXPECT_EQ(state.provider->sid_list()[1], "2");
+ EXPECT_EQ(state.provider->sid_list()[2], "3");
+ EXPECT_EQ(state.provider->sid_to_olp_idx_.size(), 3);
+ EXPECT_EQ(state.provider->sid_to_olp_idx_["1"], 5);
+ EXPECT_EQ(state.provider->sid_to_olp_idx_["2"], 3);
+ EXPECT_EQ(state.provider->sid_to_olp_idx_["3"], 0);
+}
+
+TEST_F(CellularOperatorInfoTest, HandleOLP) {
+  CellularOperatorInfo::ParserState state;
+  EXPECT_FALSE(info_.HandleOLP(&state, ",,"));
+
+  EXPECT_TRUE(info_.HandleProvider(&state, "1,1,0,0"));
+  EXPECT_TRUE(state.provider);
+  EXPECT_EQ(0, state.provider->olp_list().size());
+
+  EXPECT_FALSE(info_.HandleOLP(&state, ","));
+  EXPECT_EQ(0, state.provider->olp_list().size());
+
+  EXPECT_TRUE(info_.HandleOLP(&state, ",,"));
+  EXPECT_EQ(1, state.provider->olp_list().size());
+  EXPECT_EQ(state.provider->olp_list()[0]->GetURL(), "");
+  EXPECT_EQ(state.provider->olp_list()[0]->GetMethod(), "");
+  EXPECT_EQ(state.provider->olp_list()[0]->GetPostData(), "");
+
+  EXPECT_TRUE(info_.HandleOLP(&state, "a,b,c"));
+  EXPECT_EQ(2, state.provider->olp_list().size());
+  EXPECT_EQ(state.provider->olp_list()[0]->GetURL(), "");
+  EXPECT_EQ(state.provider->olp_list()[0]->GetMethod(), "");
+  EXPECT_EQ(state.provider->olp_list()[0]->GetPostData(), "");
+  EXPECT_EQ(state.provider->olp_list()[1]->GetMethod(), "a");
+  EXPECT_EQ(state.provider->olp_list()[1]->GetURL(), "b");
+  EXPECT_EQ(state.provider->olp_list()[1]->GetPostData(), "c");
+}
+
+TEST_F(CellularOperatorInfoTest, ParseNameLine) {
+  CellularOperatorInfo::LocalizedName name;
+  EXPECT_FALSE(info_.ParseNameLine(",,", &name));
+  EXPECT_FALSE(info_.ParseNameLine("", &name));
+  EXPECT_TRUE(info_.ParseNameLine("a,b", &name));
+  EXPECT_EQ(name.language, "a");
+  EXPECT_EQ(name.name, "b");
+}
+
+TEST_F(CellularOperatorInfoTest, ParseKeyValue) {
+  string line = "key:value";
+  string key, value;
+  EXPECT_TRUE(CellularOperatorInfo::ParseKeyValue(line, ':', &key, &value));
+  EXPECT_EQ("key", key);
+  EXPECT_EQ("value", value);
+
+  line = "key:::::";
+  EXPECT_TRUE(CellularOperatorInfo::ParseKeyValue(line, ':', &key, &value));
+  EXPECT_EQ("key", key);
+  EXPECT_EQ("::::", value);
+
+  line = ":";
+  EXPECT_TRUE(CellularOperatorInfo::ParseKeyValue(line, ':', &key, &value));
+  EXPECT_EQ("", key);
+  EXPECT_EQ("", value);
+
+  line = ":value";
+  EXPECT_TRUE(CellularOperatorInfo::ParseKeyValue(line, ':', &key, &value));
+  EXPECT_EQ("", key);
+  EXPECT_EQ("value", value);
+
+  line = "";
+  EXPECT_FALSE(CellularOperatorInfo::ParseKeyValue(line, ':', &key, &value));
 }
 
 }  // namespace shill
diff --git a/cellular_unittest.cc b/cellular_unittest.cc
index 38c2d95..2965480 100644
--- a/cellular_unittest.cc
+++ b/cellular_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Copyright (c) 2013 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.
 
@@ -709,8 +709,9 @@
   // Service activation is needed
   GetCapabilityUniversal()->mdn_ = "0000000000";
   device_->cellular_operator_info_ = &cellular_operator_info_;
-  EXPECT_CALL(cellular_operator_info_, GetOLP(_, _))
-      .WillRepeatedly(Return(true));
+  CellularService::OLP olp;
+  EXPECT_CALL(cellular_operator_info_, GetOLPByMCCMNC(_))
+      .WillRepeatedly(Return(&olp));
 
   device_->state_ = Cellular::kStateDisabled;
   device_->HandleNewRegistrationState();
diff --git a/data/cellular_operator_info b/data/cellular_operator_info
index 614d94f..3e5df7d 100644
--- a/data/cellular_operator_info
+++ b/data/cellular_operator_info
@@ -1,12 +1,38 @@
-# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+# Copyright (c) 2013 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.
 #
 # This file contains information about cellular operators.
 #
+# country:xx
+# <provider block 1>
+# <provider block 2>
+#
+# provider:<# of names>,<# of apns>,<primary>,<roaming>
+# name:<lang>,<name 1>
+# name:<lang>,<name 2>
+# ...
+# mccmnc:NNNNNN,<olp index>,NNNNNN,<olp index>...
+# sid:<sid>,<olp index>,<sid>,<olp index>...
+# olp:<method>,<url>,<postdata>
+# apn:<# of descriptive names>,<value>,<username>,<password>
+# name:<lang>,<descriptive name 1>
+# name:<lang>,<descriptive name 2>
+# ...
+# apn:<# of descriptive names>,<value>,<username>,<password>
+# ...
+#
+
+serviceproviders:3.0
+country:us
 
 # Verizon Wireless
-[311480]
-OLP.URL=https://quickaccess.verizonwireless.com/bbportal/chrome/start4g.do
-OLP.Method=POST
-OLP.PostData=oem=GOG3&iccid=${iccid}&imei=${imei}&mdn=${mdn}
+provider:1,1,0,0
+name:,Verizon Wireless
+mccmnc:310995,0,311480,0
+sid:2,1,4,1,5,1,6,1,8,1,12,1,15,1,17,1,18,1,20,1,21,1,22,1,26,1,28,1,30,1,32,1,33,1,37,1,40,1,41,1,48,1,51,1,54,1,56,1,58,1,59,1,60,1,64,1,65,1,69,1,73,1,75,1,78,1,80,1,86,1,92,1,93,1,94,1,95,1,96,1,104,1,107,1,110,1,112,1,113,1,119,1,126,1,127,1,133,1,137,1,139,1,143,1,150,1,154,1,162,1,163,1,165,1,170,1,172,1,179,1,180,1,181,1,186,1,189,1,190,1,203,1,213,1,214,1,222,1,224,1,226,1,228,1,241,1,250,1,258,1,262,1,263,1,266,1,272,1,276,1,284,1,286,1,294,1,298,1,299,1,300,1,314,1,316,1,319,1,323,1,328,1,329,1,330,1,349,1,356,1,377,1,385,1,404,1,428,1,443,1,456,1,465,1,482,1,483,1,486,1,490,1,498,1,502,1,506,1,528,1,530,1,532,1,539,1,1015,1,1026,1,1032,1,1034,1,1062,1,1072,1,1074,1,1076,1,1083,1,1086,1,1088,1,1094,1,1103,1,1129,1,1131,1,1137,1,1139,1,1145,1,1151,1,1153,1,1164,1,1166,1,1174,1,1180,1,1189,1,1193,1,1196,1,1220,1,1224,1,1227,1,1230,1,1267,1,1285,1,1330,1,1358,1,1417,1,1429,1,1476,1,1488,1,1492,1,1494,1,1506,1,1510,1,1514,1,1516,1,1517,1,1519,1,1523,1,1548,1,1552,1,1563,1,1567,1,1614,1,1626,1,1630,1,1632,1,1637,1,1639,1,1641,1,1653,1,1679,1,1736,1,1740,1,1749,1,1760,1,1776,1,1780,1,1790,1,1792,1,1824,1,1826,1,1827,1,1830,1,1832,1,1857,1,1910,1,1912,1,1940,1,1969,1,2004,1,2054,1,2058,1,2060,1,2076,1,2115,1,2119,1,2125,1,2127,1,2149,1,3004,1,3008,1,3046,1,3066,1,3216,1,3218,1,3228,1,6709,1,6711,1,7532,1,7536,1,9640,1,9642,1,9644,1
+olp:POST,https://quickaccess.verizonwireless.com/bbportal/chrome/start4g.do,oem=GOG3&iccid=${iccid}&imei=${imei}&mdn=${mdn}
+# TODO(armansito): There may be different olp's for different US sid's.
+olp:POST,https://quickaccess.verizonwireless.com/bbportal/chrome/start.do,mdn=${mdn}&oem=GOG2&meid=${meid}
+apn:1,vzwinternet,,
+name:,4G LTE Contract
diff --git a/file_reader.cc b/file_reader.cc
index 81e52a4..450d7e0 100644
--- a/file_reader.cc
+++ b/file_reader.cc
@@ -26,7 +26,7 @@
 bool FileReader::ReadLine(string *line) {
   CHECK(line) << "Invalid argument";
 
-  FILE* fp = file_.get();
+  FILE *fp = file_.get();
   if (fp == NULL)
     return false;
 
diff --git a/file_reader_unittest.cc b/file_reader_unittest.cc
new file mode 100644
index 0000000..85d51ed
--- /dev/null
+++ b/file_reader_unittest.cc
@@ -0,0 +1,80 @@
+// Copyright (c) 2013 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/file_reader.h"
+
+#include <string>
+#include <vector>
+
+#include <base/file_util.h>
+#include <base/scoped_temp_dir.h>
+#include <base/string_util.h>
+#include <gtest/gtest.h>
+
+using std::string;
+using std::vector;
+
+namespace shill {
+
+class FileReaderTest : public ::testing::Test {
+ public:
+  void VerifyReadLines(const FilePath& path, const vector<string>& lines) {
+    string line;
+    EXPECT_FALSE(reader_.ReadLine(&line));
+    EXPECT_TRUE(reader_.Open(path));
+    for (size_t i = 0; i < lines.size(); ++i) {
+      EXPECT_TRUE(reader_.ReadLine(&line));
+      EXPECT_EQ(lines[i], line);
+    }
+    EXPECT_FALSE(reader_.ReadLine(&line));
+    reader_.Close();
+    EXPECT_FALSE(reader_.ReadLine(&line));
+  }
+
+ protected:
+  FileReader reader_;
+};
+
+TEST_F(FileReaderTest, OpenNonExistentFile) {
+  EXPECT_FALSE(reader_.Open(FilePath("a_nonexistent_file")));
+}
+
+TEST_F(FileReaderTest, OpenEmptyFile) {
+  ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+  FilePath path;
+  ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir.path(), &path));
+
+  EXPECT_TRUE(reader_.Open(path));
+  string line;
+  EXPECT_FALSE(reader_.ReadLine(&line));
+  reader_.Close();
+}
+
+TEST_F(FileReaderTest, ReadLine) {
+  vector<string> lines;
+  lines.push_back("this is");
+  lines.push_back("a");
+  lines.push_back("");
+  lines.push_back("test");
+  string content = JoinString(lines, '\n');
+
+  ScopedTempDir temp_dir;
+  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+  FilePath path;
+  ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir.path(), &path));
+
+  // Test a file not ending with a new-line character
+  ASSERT_EQ(content.size(),
+      file_util::WriteFile(path, content.c_str(), content.size()));
+  VerifyReadLines(path, lines);
+
+  // Test a file ending with a new-line character
+  content.push_back('\n');
+  ASSERT_EQ(content.size(),
+      file_util::WriteFile(path, content.c_str(), content.size()));
+  VerifyReadLines(path, lines);
+}
+
+}  // namespace shill
diff --git a/mock_cellular_operator_info.cc b/mock_cellular_operator_info.cc
index f5cc5ca..8a9f336 100644
--- a/mock_cellular_operator_info.cc
+++ b/mock_cellular_operator_info.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Copyright (c) 2013 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.
 
@@ -7,7 +7,7 @@
 namespace shill {
 
 MockCellularOperatorInfo::MockCellularOperatorInfo()
-    : CellularOperatorInfo(NULL) {}
+    : CellularOperatorInfo() {}
 
 MockCellularOperatorInfo::~MockCellularOperatorInfo() {}
 
diff --git a/mock_cellular_operator_info.h b/mock_cellular_operator_info.h
index 2acb0db..9346301 100644
--- a/mock_cellular_operator_info.h
+++ b/mock_cellular_operator_info.h
@@ -19,8 +19,8 @@
   virtual ~MockCellularOperatorInfo();
 
   MOCK_METHOD1(Load, bool(const FilePath &info_file_path));
-  MOCK_METHOD2(GetOLP, bool(const std::string &operator_id,
-                            CellularService::OLP *olp));
+  MOCK_METHOD1(GetOLPByMCCMNC,
+               const CellularService::OLP *(const std::string &operator_id));
 };
 
 }  // namespace shill
diff --git a/modem_info.cc b/modem_info.cc
index e6d0139..8888e60 100644
--- a/modem_info.cc
+++ b/modem_info.cc
@@ -52,7 +52,7 @@
 }
 
 void ModemInfo::Start() {
-  cellular_operator_info_.reset(new CellularOperatorInfo(glib_));
+  cellular_operator_info_.reset(new CellularOperatorInfo());
   cellular_operator_info_->Load(FilePath(kCellularOperatorInfoPath));
 
   // TODO(petkov): Consider initializing the mobile provider database lazily