//
// Copyright (C) 2014 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "shill/cellular/mobile_operator_info_impl.h"
#include <regex.h>
#include <algorithm>
#include <cctype>
#include <map>
#include <base/bind.h>
#include <base/strings/string_util.h>
#include <google/protobuf/repeated_field.h>
#include "shill/logging.h"
#include "shill/protobuf_lite_streams.h"
using base::Bind;
using base::FilePath;
using google::protobuf::io::CopyingInputStreamAdaptor;
using google::protobuf::RepeatedField;
using google::protobuf::RepeatedPtrField;
using shill::mobile_operator_db::Data;
using shill::mobile_operator_db::Filter;
using shill::mobile_operator_db::LocalizedName;
using shill::mobile_operator_db::MobileAPN;
using shill::mobile_operator_db::MobileNetworkOperator;
using shill::mobile_operator_db::MobileOperatorDB;
using shill::mobile_operator_db::MobileVirtualNetworkOperator;
using shill::mobile_operator_db::OnlinePortal;
using std::map;
using std::string;
using std::vector;
namespace shill {
namespace Logging {
static auto kModuleLogScope = ScopeLogger::kCellular;
static string ObjectID(const MobileOperatorInfoImpl* m) {
return "(mobile_operator_info_impl)";
}
}
// static
const char* MobileOperatorInfoImpl::kDefaultDatabasePath =
"/usr/share/shill/serviceproviders.pbf";
const int MobileOperatorInfoImpl::kMCCMNCMinLen = 5;
namespace {
// Wrap some low level functions from the GNU regex librarly.
string GetRegError(int code, const regex_t* compiled) {
size_t length = regerror(code, compiled, nullptr, 0);
vector<char> buffer(length);
DCHECK_EQ(length, regerror(code, compiled, buffer.data(), length));
return buffer.data();
}
} // namespace
MobileOperatorInfoImpl::MobileOperatorInfoImpl(EventDispatcher* dispatcher,
const string& info_owner)
: dispatcher_(dispatcher),
info_owner_(info_owner),
observers_(
base::ObserverList<MobileOperatorInfo::Observer, true>::NOTIFY_ALL),
operator_code_type_(kOperatorCodeTypeUnknown),
current_mno_(nullptr),
current_mvno_(nullptr),
requires_roaming_(false),
user_olp_empty_(true),
weak_ptr_factory_(this) {
AddDatabasePath(FilePath(kDefaultDatabasePath));
}
MobileOperatorInfoImpl::~MobileOperatorInfoImpl() {}
void MobileOperatorInfoImpl::ClearDatabasePaths() {
database_paths_.clear();
}
void MobileOperatorInfoImpl::AddDatabasePath(const FilePath& absolute_path) {
database_paths_.push_back(absolute_path);
}
bool MobileOperatorInfoImpl::Init() {
// |database_| is guaranteed to be set once |Init| is called.
database_.reset(new MobileOperatorDB());
bool found_databases = false;
for (const auto& database_path : database_paths_) {
const char* database_path_cstr = database_path.value().c_str();
std::unique_ptr<CopyingInputStreamAdaptor> database_stream;
database_stream.reset(protobuf_lite_file_input_stream(database_path_cstr));
if (!database_stream.get()) {
LOG(ERROR) << "Failed to read mobile operator database: "
<< database_path_cstr;
continue;
}
MobileOperatorDB database;
if (!database.ParseFromZeroCopyStream(database_stream.get())) {
LOG(ERROR) << "Could not parse mobile operator database: "
<< database_path_cstr;
continue;
}
LOG(INFO) << "Successfully loaded database: " << database_path_cstr;
// Collate loaded databases into one as they're found.
// TODO(pprabhu) This merge might be very costly. Determine if we need to
// implement move semantics / bias the merge to use the largest database
// as the base database and merge other databases into it.
database_->MergeFrom(database);
found_databases = true;
}
if (!found_databases) {
LOG(ERROR) << "Could not read any mobile operator database. "
<< "Will not be able to determine MVNO.";
return false;
}
PreprocessDatabase();
return true;
}
void MobileOperatorInfoImpl::AddObserver(
MobileOperatorInfo::Observer* observer) {
observers_.AddObserver(observer);
}
void MobileOperatorInfoImpl::RemoveObserver(
MobileOperatorInfo::Observer* observer) {
observers_.RemoveObserver(observer);
}
bool MobileOperatorInfoImpl::IsMobileNetworkOperatorKnown() const {
return (current_mno_ != nullptr);
}
bool MobileOperatorInfoImpl::IsMobileVirtualNetworkOperatorKnown() const {
return (current_mvno_ != nullptr);
}
// ///////////////////////////////////////////////////////////////////////////
// Getters.
const string& MobileOperatorInfoImpl::info_owner() const {
return info_owner_;
}
const string& MobileOperatorInfoImpl::uuid() const {
return uuid_;
}
const string& MobileOperatorInfoImpl::operator_name() const {
// TODO(pprabhu) I'm not very sure yet what is the right thing to do here.
// It is possible that we obtain a name OTA, and then using some other
// information (say the iccid range), determine that this is an MVNO. In
// that case, we may want to *override* |user_operator_name_| by the name
// obtained from the DB for the MVNO.
return operator_name_;
}
const string& MobileOperatorInfoImpl::country() const {
return country_;
}
const string& MobileOperatorInfoImpl::mccmnc() const {
return mccmnc_;
}
const string& MobileOperatorInfoImpl::MobileOperatorInfoImpl::sid() const {
return sid_;
}
const string& MobileOperatorInfoImpl::nid() const {
return (user_nid_ == "") ? nid_ : user_nid_;
}
const vector<string>& MobileOperatorInfoImpl::mccmnc_list() const {
return mccmnc_list_;
}
const vector<string>& MobileOperatorInfoImpl::sid_list() const {
return sid_list_;
}
const vector<MobileOperatorInfo::LocalizedName> &
MobileOperatorInfoImpl::operator_name_list() const {
return operator_name_list_;
}
const ScopedVector<MobileOperatorInfo::MobileAPN> &
MobileOperatorInfoImpl::apn_list() const {
return apn_list_;
}
const vector<MobileOperatorInfo::OnlinePortal> &
MobileOperatorInfoImpl::olp_list() const {
return olp_list_;
}
const string& MobileOperatorInfoImpl::activation_code() const {
return activation_code_;
}
bool MobileOperatorInfoImpl::requires_roaming() const {
return requires_roaming_;
}
// ///////////////////////////////////////////////////////////////////////////
// Functions used to notify this object of operator data changes.
void MobileOperatorInfoImpl::UpdateIMSI(const string& imsi) {
bool operator_changed = false;
if (user_imsi_ == imsi) {
return;
}
user_imsi_ = imsi;
if (!user_mccmnc_.empty()) {
if (!base::StartsWith(imsi, user_mccmnc_,
base::CompareCase::INSENSITIVE_ASCII)) {
LOG(WARNING) << "MCCMNC [" << user_mccmnc_ << "] is not a substring of "
<< "the IMSI [" << imsi << "].";
}
} else {
// Attempt to determine the MNO from IMSI since MCCMNC is absent.
AppendToCandidatesByMCCMNC(imsi.substr(0, kMCCMNCMinLen));
AppendToCandidatesByMCCMNC(imsi.substr(0, kMCCMNCMinLen + 1));
if (!candidates_by_operator_code_.empty()) {
// We found some candidates using IMSI.
operator_changed |= UpdateMNO();
}
}
operator_changed |= UpdateMVNO();
// No special notification should be sent for this property, since the object
// does not expose |imsi| as a property at all.
if (operator_changed) {
PostNotifyOperatorChanged();
}
}
void MobileOperatorInfoImpl::UpdateICCID(const string& iccid) {
if (user_iccid_ == iccid) {
return;
}
user_iccid_ = iccid;
// |iccid| is not an exposed property, so don't raise event for just this
// property update.
if (UpdateMVNO()) {
PostNotifyOperatorChanged();
}
}
void MobileOperatorInfoImpl::UpdateMCCMNC(const string& mccmnc) {
if (user_mccmnc_ == mccmnc) {
return;
}
user_mccmnc_ = mccmnc;
HandleMCCMNCUpdate();
candidates_by_operator_code_.clear();
AppendToCandidatesByMCCMNC(mccmnc);
// Always update M[V]NO, even if we found no candidates, since we might have
// lost some candidates due to an incorrect MCCMNC.
bool operator_changed = false;
operator_changed |= UpdateMNO();
operator_changed |= UpdateMVNO();
if (operator_changed || ShouldNotifyPropertyUpdate()) {
PostNotifyOperatorChanged();
}
}
void MobileOperatorInfoImpl::UpdateSID(const string& sid) {
if (user_sid_ == sid) {
return;
}
user_sid_ = sid;
HandleSIDUpdate();
candidates_by_operator_code_.clear();
AppendToCandidatesBySID(sid);
// Always update M[V]NO, even if we found no candidates, since we might have
// lost some candidates due to an incorrect SID.
bool operator_changed = false;
operator_changed |= UpdateMNO();
operator_changed |= UpdateMVNO();
if (operator_changed || ShouldNotifyPropertyUpdate()) {
PostNotifyOperatorChanged();
}
}
void MobileOperatorInfoImpl::UpdateNID(const string& nid) {
if (user_nid_ == nid) {
return;
}
user_nid_ = nid;
if (UpdateMVNO() || ShouldNotifyPropertyUpdate()) {
PostNotifyOperatorChanged();
}
}
void MobileOperatorInfoImpl::UpdateOperatorName(const string& operator_name) {
bool operator_changed = false;
if (user_operator_name_ == operator_name) {
return;
}
user_operator_name_ = operator_name;
HandleOperatorNameUpdate();
// We must update the candidates by name anyway.
StringToMNOListMap::const_iterator cit = name_to_mnos_.find(
NormalizeOperatorName(operator_name));
candidates_by_name_.clear();
if (cit != name_to_mnos_.end()) {
candidates_by_name_ = cit->second;
// We should never have inserted an empty vector into the map.
DCHECK(!candidates_by_name_.empty());
} else {
LOG(INFO) << "Operator name [" << operator_name << "] "
<< "(Normalized: [" << NormalizeOperatorName(operator_name)
<< "]) does not match any MNO.";
}
operator_changed |= UpdateMNO();
operator_changed |= UpdateMVNO();
if (operator_changed || ShouldNotifyPropertyUpdate()) {
PostNotifyOperatorChanged();
}
}
void MobileOperatorInfoImpl::UpdateOnlinePortal(const string& url,
const string& method,
const string& post_data) {
if (!user_olp_empty_ &&
user_olp_.url == url &&
user_olp_.method == method &&
user_olp_.post_data == post_data) {
return;
}
user_olp_empty_ = false;
user_olp_.url = url;
user_olp_.method = method;
user_olp_.post_data = post_data;
HandleOnlinePortalUpdate();
// OnlinePortal is never used in deciding M[V]NO.
if (ShouldNotifyPropertyUpdate()) {
PostNotifyOperatorChanged();
}
}
void MobileOperatorInfoImpl::Reset() {
bool should_notify = current_mno_ != nullptr || current_mvno_ != nullptr;
current_mno_ = nullptr;
current_mvno_ = nullptr;
operator_code_type_ = kOperatorCodeTypeUnknown;
candidates_by_operator_code_.clear();
candidates_by_name_.clear();
ClearDBInformation();
user_imsi_.clear();
user_iccid_.clear();
user_mccmnc_.clear();
user_sid_.clear();
user_nid_.clear();
user_operator_name_.clear();
user_olp_empty_ = true;
user_olp_.url.clear();
user_olp_.method.clear();
user_olp_.post_data.clear();
if (should_notify) {
PostNotifyOperatorChanged();
}
}
void MobileOperatorInfoImpl::PreprocessDatabase() {
SLOG(this, 3) << __func__;
mccmnc_to_mnos_.clear();
sid_to_mnos_.clear();
name_to_mnos_.clear();
const RepeatedPtrField<MobileNetworkOperator>& mnos = database_->mno();
for (const auto& mno : mnos) {
// MobileNetworkOperator::data is a required field.
DCHECK(mno.has_data());
const Data& data = mno.data();
const RepeatedPtrField<string>& mccmncs = data.mccmnc();
for (const auto& mccmnc : mccmncs) {
InsertIntoStringToMNOListMap(&mccmnc_to_mnos_, mccmnc, &mno);
}
const RepeatedPtrField<string>& sids = data.sid();
for (const auto& sid : sids) {
InsertIntoStringToMNOListMap(&sid_to_mnos_, sid, &mno);
}
const RepeatedPtrField<LocalizedName>& localized_names =
data.localized_name();
for (const auto& localized_name : localized_names) {
// LocalizedName::name is a required field.
DCHECK(localized_name.has_name());
InsertIntoStringToMNOListMap(&name_to_mnos_,
NormalizeOperatorName(localized_name.name()),
&mno);
}
}
if (database_->imvno_size() > 0) {
// TODO(pprabhu) Support IMVNOs.
LOG(ERROR) << "InternationalMobileVirtualNetworkOperators are not "
<< "supported yet. Ignoring all IMVNOs.";
}
}
// This function assumes that duplicate |values| are never inserted for the
// same |key|. If you do that, the function is too dumb to deduplicate the
// |value|s, and two copies will get stored.
void MobileOperatorInfoImpl::InsertIntoStringToMNOListMap(
StringToMNOListMap* table,
const string& key,
const MobileNetworkOperator* value) {
(*table)[key].push_back(value);
}
bool MobileOperatorInfoImpl::AppendToCandidatesByMCCMNC(const string& mccmnc) {
// First check that we haven't determined candidates using SID.
if (operator_code_type_ == kOperatorCodeTypeSID) {
LOG(WARNING) << "SID update will be overridden by the MCCMNC update for "
"determining MNO.";
candidates_by_operator_code_.clear();
}
operator_code_type_ = kOperatorCodeTypeMCCMNC;
StringToMNOListMap::const_iterator cit = mccmnc_to_mnos_.find(mccmnc);
if (cit == mccmnc_to_mnos_.end()) {
LOG(WARNING) << "Unknown MCCMNC value [" << mccmnc << "].";
return false;
}
// We should never have inserted an empty vector into the map.
DCHECK(!cit->second.empty());
for (const auto& mno : cit->second) {
candidates_by_operator_code_.push_back(mno);
}
return true;
}
bool MobileOperatorInfoImpl::AppendToCandidatesBySID(const string& sid) {
// First check that we haven't determined candidates using MCCMNC.
if (operator_code_type_ == kOperatorCodeTypeMCCMNC) {
LOG(WARNING) << "MCCMNC update will be overriden by the SID update for "
"determining MNO.";
candidates_by_operator_code_.clear();
}
operator_code_type_ = kOperatorCodeTypeSID;
StringToMNOListMap::const_iterator cit = sid_to_mnos_.find(sid);
if (cit == sid_to_mnos_.end()) {
LOG(WARNING) << "Unknown SID value [" << sid << "].";
return false;
}
// We should never have inserted an empty vector into the map.
DCHECK(!cit->second.empty());
for (const auto& mno : cit->second) {
candidates_by_operator_code_.push_back(mno);
}
return true;
}
string MobileOperatorInfoImpl::OperatorCodeString() const {
switch (operator_code_type_) {
case kOperatorCodeTypeMCCMNC:
return "MCCMNC";
case kOperatorCodeTypeSID:
return "SID";
case kOperatorCodeTypeUnknown: // FALLTHROUGH
default:
return "UnknownOperatorCodeType";
}
}
bool MobileOperatorInfoImpl::UpdateMNO() {
SLOG(this, 3) << __func__;
const MobileNetworkOperator* candidate = nullptr;
// The only way |operator_code_type_| can be |kOperatorCodeTypeUnknown| is
// that we haven't received any operator_code updates yet.
DCHECK(operator_code_type_ == kOperatorCodeTypeMCCMNC ||
operator_code_type_ == kOperatorCodeTypeSID ||
(user_mccmnc_.empty() && user_sid_.empty()));
// TODO(pprabhu) Remove this despicable hack. (crosbug.com/p/30200)
// We currently have no principled way to handle an MVNO for which the
// database does not have MCCMNC data. It is possible that some other MNO
// matches the MCCMNC, while the MVNO matches the operator name. We special
// case one such operator here and override all the logic below.
const char kCubicUUID[] = "2de39b14-c3ba-4143-abb5-c67a390034ee";
for (auto candidate_by_name : candidates_by_name_) {
CHECK(candidate_by_name->has_data());
CHECK(candidate_by_name->data().has_uuid());
if (candidate_by_name->data().uuid() == kCubicUUID) {
current_mno_ = candidate_by_name;
RefreshDBInformation();
return true;
}
}
if (candidates_by_operator_code_.size() == 1) {
candidate = candidates_by_operator_code_[0];
if (candidates_by_name_.size() > 0) {
bool found_match = false;
for (auto candidate_by_name : candidates_by_name_) {
if (candidate_by_name == candidate) {
found_match = true;
break;
}
}
if (!found_match) {
const string& operator_code =
(operator_code_type_ == kOperatorCodeTypeMCCMNC) ? user_mccmnc_ :
user_sid_;
SLOG(this, 1) << "MNO determined by "
<< OperatorCodeString() << " [" << operator_code
<< "] does not match any suggested by name["
<< user_operator_name_
<< "]. "
<< OperatorCodeString() << " overrides name!";
}
}
} else if (candidates_by_operator_code_.size() > 1) {
// Try to find an intersection of the two candidate lists. These lists
// should be almost always of length 1. Simply iterate.
for (auto candidate_by_mccmnc : candidates_by_operator_code_) {
for (auto candidate_by_name : candidates_by_name_) {
if (candidate_by_mccmnc == candidate_by_name) {
candidate = candidate_by_mccmnc;
break;
}
}
if (candidate != nullptr) {
break;
}
}
if (candidate == nullptr) {
const string& operator_code =
(operator_code_type_ == kOperatorCodeTypeMCCMNC) ? user_mccmnc_ :
user_sid_;
SLOG(this, 1) << "MNOs suggested by "
<< OperatorCodeString() << " [" << operator_code
<< "] are multiple and disjoint from those suggested "
<< "by name["
<< user_operator_name_
<< "].";
candidate = PickOneFromDuplicates(candidates_by_operator_code_);
}
} else { // candidates_by_operator_code_.size() == 0
// Special case: In case we had a *wrong* operator_code update, we want
// to override the suggestions from |user_operator_name_|. We should not
// determine an MNO in this case.
if ((operator_code_type_ == kOperatorCodeTypeMCCMNC &&
!user_mccmnc_.empty()) ||
(operator_code_type_ == kOperatorCodeTypeSID && !user_sid_.empty())) {
SLOG(this, 1) << "A non-matching "
<< OperatorCodeString() << " "
<< "was reported by the user."
<< "We fail the MNO match in this case.";
} else if (candidates_by_name_.size() == 1) {
candidate = candidates_by_name_[0];
} else if (candidates_by_name_.size() > 1) {
SLOG(this, 1) << "Multiple MNOs suggested by name["
<< user_operator_name_
<< "], and none by MCCMNC.";
candidate = PickOneFromDuplicates(candidates_by_name_);
} else { // candidates_by_name_.size() == 0
SLOG(this, 1) << "No candidates suggested.";
}
}
if (candidate != current_mno_) {
current_mno_ = candidate;
RefreshDBInformation();
return true;
}
return false;
}
bool MobileOperatorInfoImpl::UpdateMVNO() {
SLOG(this, 3) << __func__;
if (current_mno_ == nullptr) {
return false;
}
for (const auto& candidate_mvno : current_mno_->mvno()) {
bool passed_all_filters = true;
for (const auto& filter : candidate_mvno.mvno_filter()) {
if (!FilterMatches(filter)) {
passed_all_filters = false;
break;
}
}
if (passed_all_filters) {
if (current_mvno_ == &candidate_mvno) {
return false;
}
current_mvno_ = &candidate_mvno;
RefreshDBInformation();
return true;
}
}
// We did not find any valid MVNO.
if (current_mvno_ != nullptr) {
current_mvno_ = nullptr;
RefreshDBInformation();
return true;
}
return false;
}
const MobileNetworkOperator* MobileOperatorInfoImpl::PickOneFromDuplicates(
const vector<const MobileNetworkOperator*>& duplicates) const {
if (duplicates.empty())
return nullptr;
for (auto candidate : duplicates) {
if (candidate->earmarked()) {
SLOG(this, 2) << "Picking earmarked candidate: "
<< candidate->data().uuid();
return candidate;
}
}
SLOG(this, 2) << "No earmarked candidate found. Choosing the first.";
return duplicates[0];
}
bool MobileOperatorInfoImpl::FilterMatches(const Filter& filter) {
DCHECK(filter.has_regex());
string to_match;
switch (filter.type()) {
case mobile_operator_db::Filter_Type_IMSI:
to_match = user_imsi_;
break;
case mobile_operator_db::Filter_Type_ICCID:
to_match = user_iccid_;
break;
case mobile_operator_db::Filter_Type_SID:
to_match = user_sid_;
break;
case mobile_operator_db::Filter_Type_OPERATOR_NAME:
to_match = user_operator_name_;
break;
case mobile_operator_db::Filter_Type_MCCMNC:
to_match = user_mccmnc_;
break;
default:
SLOG(this, 1) << "Unknown filter type [" << filter.type() << "]";
return false;
}
// |to_match| can be empty if we have no *user provided* information of the
// correct type.
if (to_match.empty()) {
SLOG(this, 2) << "Nothing to match against (filter: "
<< filter.regex() << ").";
return false;
}
// Must use GNU regex implementation, since C++11 implementation is
// incomplete.
regex_t filter_regex;
string filter_regex_str = filter.regex();
// |regexec| matches the given regular expression to a substring of the
// given query string. Ensure that |filter_regex_str| uses anchors to
// accept only a full match.
if (filter_regex_str.front() != '^') {
filter_regex_str = "^" + filter_regex_str;
}
if (filter_regex_str.back() != '$') {
filter_regex_str = filter_regex_str + "$";
}
int regcomp_error = regcomp(&filter_regex,
filter_regex_str.c_str(),
REG_EXTENDED | REG_NOSUB);
if (regcomp_error) {
LOG(WARNING) << "Could not compile regex '" << filter.regex() << "'. "
<< "Error returned: "
<< GetRegError(regcomp_error, &filter_regex) << ". ";
regfree(&filter_regex);
return false;
}
int regexec_error = regexec(&filter_regex,
to_match.c_str(),
0,
nullptr,
0);
if (regexec_error) {
string error_string;
error_string = GetRegError(regcomp_error, &filter_regex);
SLOG(this, 2) << "Could not match string " << to_match << " "
<< "against regexp " << filter.regex() << ". "
<< "Error returned: " << error_string << ". ";
regfree(&filter_regex);
return false;
}
regfree(&filter_regex);
return true;
}
void MobileOperatorInfoImpl::RefreshDBInformation() {
ClearDBInformation();
if (current_mno_ == nullptr) {
return;
}
// |data| is a required field.
DCHECK(current_mno_->has_data());
SLOG(this, 2) << "Reloading MNO data.";
ReloadData(current_mno_->data());
if (current_mvno_ != nullptr) {
// |data| is a required field.
DCHECK(current_mvno_->has_data());
SLOG(this, 2) << "Reloading MVNO data.";
ReloadData(current_mvno_->data());
}
}
void MobileOperatorInfoImpl::ClearDBInformation() {
uuid_.clear();
country_.clear();
nid_.clear();
mccmnc_list_.clear();
HandleMCCMNCUpdate();
sid_list_.clear();
HandleSIDUpdate();
operator_name_list_.clear();
HandleOperatorNameUpdate();
apn_list_.clear();
olp_list_.clear();
raw_olp_list_.clear();
HandleOnlinePortalUpdate();
activation_code_.clear();
requires_roaming_ = false;
}
void MobileOperatorInfoImpl::ReloadData(const Data& data) {
SLOG(this, 3) << __func__;
// |uuid_| is *always* overwritten. An MNO and MVNO should not share the
// |uuid_|.
CHECK(data.has_uuid());
uuid_ = data.uuid();
if (data.has_country()) {
country_ = data.country();
}
if (data.localized_name_size() > 0) {
operator_name_list_.clear();
for (const auto& localized_name : data.localized_name()) {
operator_name_list_.push_back({localized_name.name(),
localized_name.language()});
}
HandleOperatorNameUpdate();
}
if (data.has_requires_roaming()) {
requires_roaming_ = data.requires_roaming();
}
if (data.olp_size() > 0) {
raw_olp_list_.clear();
// Copy the olp list so we can mutate it.
for (const auto& olp : data.olp()) {
raw_olp_list_.push_back(olp);
}
HandleOnlinePortalUpdate();
}
if (data.mccmnc_size() > 0) {
mccmnc_list_.clear();
for (const auto& mccmnc : data.mccmnc()) {
mccmnc_list_.push_back(mccmnc);
}
HandleMCCMNCUpdate();
}
if (data.mobile_apn_size() > 0) {
apn_list_.clear();
for (const auto& apn_data : data.mobile_apn()) {
auto* apn = new MobileOperatorInfo::MobileAPN();
apn->apn = apn_data.apn();
apn->username = apn_data.username();
apn->password = apn_data.password();
for (const auto& localized_name : apn_data.localized_name()) {
apn->operator_name_list.push_back({localized_name.name(),
localized_name.language()});
}
// Takes ownership.
apn_list_.push_back(apn);
}
}
if (data.sid_size() > 0) {
sid_list_.clear();
for (const auto& sid : data.sid()) {
sid_list_.push_back(sid);
}
HandleSIDUpdate();
}
if (data.has_activation_code()) {
activation_code_ = data.activation_code();
}
}
void MobileOperatorInfoImpl::HandleMCCMNCUpdate() {
if (!user_mccmnc_.empty()) {
bool append_to_list = true;
for (const auto& mccmnc : mccmnc_list_) {
append_to_list &= (user_mccmnc_ != mccmnc);
}
if (append_to_list) {
mccmnc_list_.push_back(user_mccmnc_);
}
}
if (!user_mccmnc_.empty()) {
mccmnc_ = user_mccmnc_;
} else if (mccmnc_list_.size() > 0) {
mccmnc_ = mccmnc_list_[0];
} else {
mccmnc_.clear();
}
}
void MobileOperatorInfoImpl::HandleOperatorNameUpdate() {
if (!user_operator_name_.empty()) {
bool append_user_operator_name = true;
for (const auto& localized_name : operator_name_list_) {
append_user_operator_name &= (user_operator_name_ != localized_name.name);
}
if (append_user_operator_name) {
MobileOperatorInfo::LocalizedName localized_name {
user_operator_name_,
""};
operator_name_list_.push_back(localized_name);
}
}
if (!operator_name_list_.empty()) {
operator_name_ = operator_name_list_[0].name;
} else if (!user_operator_name_.empty()) {
operator_name_ = user_operator_name_;
} else {
operator_name_.clear();
}
}
void MobileOperatorInfoImpl::HandleSIDUpdate() {
if (!user_sid_.empty()) {
bool append_user_sid = true;
for (const auto& sid : sid_list_) {
append_user_sid &= (user_sid_ != sid);
}
if (append_user_sid) {
sid_list_.push_back(user_sid_);
}
}
if (!user_sid_.empty()) {
sid_ = user_sid_;
} else if (sid_list_.size() > 0) {
sid_ = sid_list_[0];
} else {
sid_.clear();
}
}
// Warning: Currently, an MCCMNC/SID update by itself does not result into
// recomputation of the |olp_list_|. This means that if the new MCCMNC/SID
// causes an online portal filter to match, we'll miss that.
// This won't be a problem if either the MNO or the MVNO changes, since data is
// reloaded then.
// This is a corner case that we don't expect to hit, since MCCMNC doesn't
// really change in a running system.
void MobileOperatorInfoImpl::HandleOnlinePortalUpdate() {
// Always recompute |olp_list_|. We don't expect this list to be big.
olp_list_.clear();
for (const auto& raw_olp : raw_olp_list_) {
if (!raw_olp.has_olp_filter() || FilterMatches(raw_olp.olp_filter())) {
olp_list_.push_back(MobileOperatorInfo::OnlinePortal {
raw_olp.url(),
(raw_olp.method() == raw_olp.GET) ? "GET" : "POST",
raw_olp.post_data()});
}
}
if (!user_olp_empty_) {
bool append_user_olp = true;
for (const auto& olp : olp_list_) {
append_user_olp &= (olp.url != user_olp_.url ||
olp.method != user_olp_.method ||
olp.post_data != user_olp_.post_data);
}
if (append_user_olp) {
olp_list_.push_back(user_olp_);
}
}
}
void MobileOperatorInfoImpl::PostNotifyOperatorChanged() {
SLOG(this, 3) << __func__;
// If there was an outstanding task, it will get replaced.
notify_operator_changed_task_.Reset(
Bind(&MobileOperatorInfoImpl::NotifyOperatorChanged,
weak_ptr_factory_.GetWeakPtr()));
dispatcher_->PostTask(notify_operator_changed_task_.callback());
}
void MobileOperatorInfoImpl::NotifyOperatorChanged() {
FOR_EACH_OBSERVER(MobileOperatorInfo::Observer,
observers_,
OnOperatorChanged());
}
bool MobileOperatorInfoImpl::ShouldNotifyPropertyUpdate() const {
return IsMobileNetworkOperatorKnown() ||
IsMobileVirtualNetworkOperatorKnown();
}
string MobileOperatorInfoImpl::NormalizeOperatorName(const string& name) const {
string result = base::ToLowerASCII(name);
base::RemoveChars(result, base::kWhitespaceASCII, &result);
return result;
}
} // namespace shill