1 //
2 // Copyright (C) 2014 The Android Open Source Project
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16
17 #include "shill/cellular/mobile_operator_info_impl.h"
18
19 #include <regex.h>
20
21 #include <algorithm>
22 #include <cctype>
23 #include <map>
24
25 #include <base/bind.h>
26 #include <base/strings/string_util.h>
27 #include <google/protobuf/repeated_field.h>
28
29 #include "shill/logging.h"
30 #include "shill/protobuf_lite_streams.h"
31
32 using base::Bind;
33 using base::FilePath;
34 using google::protobuf::io::CopyingInputStreamAdaptor;
35 using google::protobuf::RepeatedField;
36 using google::protobuf::RepeatedPtrField;
37 using shill::mobile_operator_db::Data;
38 using shill::mobile_operator_db::Filter;
39 using shill::mobile_operator_db::LocalizedName;
40 using shill::mobile_operator_db::MobileAPN;
41 using shill::mobile_operator_db::MobileNetworkOperator;
42 using shill::mobile_operator_db::MobileOperatorDB;
43 using shill::mobile_operator_db::MobileVirtualNetworkOperator;
44 using shill::mobile_operator_db::OnlinePortal;
45 using std::map;
46 using std::string;
47 using std::vector;
48
49 namespace shill {
50
51 namespace Logging {
52 static auto kModuleLogScope = ScopeLogger::kCellular;
ObjectID(const MobileOperatorInfoImpl * m)53 static string ObjectID(const MobileOperatorInfoImpl* m) {
54 return "(mobile_operator_info_impl)";
55 }
56 }
57
58 // static
59 const char* MobileOperatorInfoImpl::kDefaultDatabasePath =
60 "/usr/share/shill/serviceproviders.pbf";
61 const int MobileOperatorInfoImpl::kMCCMNCMinLen = 5;
62
63 namespace {
64
65 // Wrap some low level functions from the GNU regex librarly.
GetRegError(int code,const regex_t * compiled)66 string GetRegError(int code, const regex_t* compiled) {
67 size_t length = regerror(code, compiled, nullptr, 0);
68 vector<char> buffer(length);
69 DCHECK_EQ(length, regerror(code, compiled, buffer.data(), length));
70 return buffer.data();
71 }
72
73 } // namespace
74
MobileOperatorInfoImpl(EventDispatcher * dispatcher,const string & info_owner)75 MobileOperatorInfoImpl::MobileOperatorInfoImpl(EventDispatcher* dispatcher,
76 const string& info_owner)
77 : dispatcher_(dispatcher),
78 info_owner_(info_owner),
79 observers_(
80 base::ObserverList<MobileOperatorInfo::Observer, true>::NOTIFY_ALL),
81 operator_code_type_(kOperatorCodeTypeUnknown),
82 current_mno_(nullptr),
83 current_mvno_(nullptr),
84 requires_roaming_(false),
85 user_olp_empty_(true),
86 weak_ptr_factory_(this) {
87 AddDatabasePath(FilePath(kDefaultDatabasePath));
88 }
89
~MobileOperatorInfoImpl()90 MobileOperatorInfoImpl::~MobileOperatorInfoImpl() {}
91
ClearDatabasePaths()92 void MobileOperatorInfoImpl::ClearDatabasePaths() {
93 database_paths_.clear();
94 }
95
AddDatabasePath(const FilePath & absolute_path)96 void MobileOperatorInfoImpl::AddDatabasePath(const FilePath& absolute_path) {
97 database_paths_.push_back(absolute_path);
98 }
99
Init()100 bool MobileOperatorInfoImpl::Init() {
101 // |database_| is guaranteed to be set once |Init| is called.
102 database_.reset(new MobileOperatorDB());
103
104 bool found_databases = false;
105 for (const auto& database_path : database_paths_) {
106 const char* database_path_cstr = database_path.value().c_str();
107 std::unique_ptr<CopyingInputStreamAdaptor> database_stream;
108 database_stream.reset(protobuf_lite_file_input_stream(database_path_cstr));
109 if (!database_stream.get()) {
110 LOG(ERROR) << "Failed to read mobile operator database: "
111 << database_path_cstr;
112 continue;
113 }
114
115 MobileOperatorDB database;
116 if (!database.ParseFromZeroCopyStream(database_stream.get())) {
117 LOG(ERROR) << "Could not parse mobile operator database: "
118 << database_path_cstr;
119 continue;
120 }
121 LOG(INFO) << "Successfully loaded database: " << database_path_cstr;
122 // Collate loaded databases into one as they're found.
123 // TODO(pprabhu) This merge might be very costly. Determine if we need to
124 // implement move semantics / bias the merge to use the largest database
125 // as the base database and merge other databases into it.
126 database_->MergeFrom(database);
127 found_databases = true;
128 }
129
130 if (!found_databases) {
131 LOG(ERROR) << "Could not read any mobile operator database. "
132 << "Will not be able to determine MVNO.";
133 return false;
134 }
135
136 PreprocessDatabase();
137 return true;
138 }
139
AddObserver(MobileOperatorInfo::Observer * observer)140 void MobileOperatorInfoImpl::AddObserver(
141 MobileOperatorInfo::Observer* observer) {
142 observers_.AddObserver(observer);
143 }
144
RemoveObserver(MobileOperatorInfo::Observer * observer)145 void MobileOperatorInfoImpl::RemoveObserver(
146 MobileOperatorInfo::Observer* observer) {
147 observers_.RemoveObserver(observer);
148 }
149
IsMobileNetworkOperatorKnown() const150 bool MobileOperatorInfoImpl::IsMobileNetworkOperatorKnown() const {
151 return (current_mno_ != nullptr);
152 }
153
IsMobileVirtualNetworkOperatorKnown() const154 bool MobileOperatorInfoImpl::IsMobileVirtualNetworkOperatorKnown() const {
155 return (current_mvno_ != nullptr);
156 }
157
158 // ///////////////////////////////////////////////////////////////////////////
159 // Getters.
info_owner() const160 const string& MobileOperatorInfoImpl::info_owner() const {
161 return info_owner_;
162 }
163
uuid() const164 const string& MobileOperatorInfoImpl::uuid() const {
165 return uuid_;
166 }
167
operator_name() const168 const string& MobileOperatorInfoImpl::operator_name() const {
169 // TODO(pprabhu) I'm not very sure yet what is the right thing to do here.
170 // It is possible that we obtain a name OTA, and then using some other
171 // information (say the iccid range), determine that this is an MVNO. In
172 // that case, we may want to *override* |user_operator_name_| by the name
173 // obtained from the DB for the MVNO.
174 return operator_name_;
175 }
176
country() const177 const string& MobileOperatorInfoImpl::country() const {
178 return country_;
179 }
180
mccmnc() const181 const string& MobileOperatorInfoImpl::mccmnc() const {
182 return mccmnc_;
183 }
184
sid() const185 const string& MobileOperatorInfoImpl::MobileOperatorInfoImpl::sid() const {
186 return sid_;
187 }
188
nid() const189 const string& MobileOperatorInfoImpl::nid() const {
190 return (user_nid_ == "") ? nid_ : user_nid_;
191 }
192
mccmnc_list() const193 const vector<string>& MobileOperatorInfoImpl::mccmnc_list() const {
194 return mccmnc_list_;
195 }
196
sid_list() const197 const vector<string>& MobileOperatorInfoImpl::sid_list() const {
198 return sid_list_;
199 }
200
201 const vector<MobileOperatorInfo::LocalizedName> &
operator_name_list() const202 MobileOperatorInfoImpl::operator_name_list() const {
203 return operator_name_list_;
204 }
205
206 const ScopedVector<MobileOperatorInfo::MobileAPN> &
apn_list() const207 MobileOperatorInfoImpl::apn_list() const {
208 return apn_list_;
209 }
210
211 const vector<MobileOperatorInfo::OnlinePortal> &
olp_list() const212 MobileOperatorInfoImpl::olp_list() const {
213 return olp_list_;
214 }
215
activation_code() const216 const string& MobileOperatorInfoImpl::activation_code() const {
217 return activation_code_;
218 }
219
requires_roaming() const220 bool MobileOperatorInfoImpl::requires_roaming() const {
221 return requires_roaming_;
222 }
223
224 // ///////////////////////////////////////////////////////////////////////////
225 // Functions used to notify this object of operator data changes.
UpdateIMSI(const string & imsi)226 void MobileOperatorInfoImpl::UpdateIMSI(const string& imsi) {
227 bool operator_changed = false;
228 if (user_imsi_ == imsi) {
229 return;
230 }
231
232 user_imsi_ = imsi;
233
234 if (!user_mccmnc_.empty()) {
235 if (!base::StartsWith(imsi, user_mccmnc_,
236 base::CompareCase::INSENSITIVE_ASCII)) {
237 LOG(WARNING) << "MCCMNC [" << user_mccmnc_ << "] is not a substring of "
238 << "the IMSI [" << imsi << "].";
239 }
240 } else {
241 // Attempt to determine the MNO from IMSI since MCCMNC is absent.
242 AppendToCandidatesByMCCMNC(imsi.substr(0, kMCCMNCMinLen));
243 AppendToCandidatesByMCCMNC(imsi.substr(0, kMCCMNCMinLen + 1));
244 if (!candidates_by_operator_code_.empty()) {
245 // We found some candidates using IMSI.
246 operator_changed |= UpdateMNO();
247 }
248 }
249 operator_changed |= UpdateMVNO();
250
251 // No special notification should be sent for this property, since the object
252 // does not expose |imsi| as a property at all.
253 if (operator_changed) {
254 PostNotifyOperatorChanged();
255 }
256 }
257
UpdateICCID(const string & iccid)258 void MobileOperatorInfoImpl::UpdateICCID(const string& iccid) {
259 if (user_iccid_ == iccid) {
260 return;
261 }
262
263 user_iccid_ = iccid;
264 // |iccid| is not an exposed property, so don't raise event for just this
265 // property update.
266 if (UpdateMVNO()) {
267 PostNotifyOperatorChanged();
268 }
269 }
270
UpdateMCCMNC(const string & mccmnc)271 void MobileOperatorInfoImpl::UpdateMCCMNC(const string& mccmnc) {
272 if (user_mccmnc_ == mccmnc) {
273 return;
274 }
275
276 user_mccmnc_ = mccmnc;
277 HandleMCCMNCUpdate();
278 candidates_by_operator_code_.clear();
279 AppendToCandidatesByMCCMNC(mccmnc);
280
281 // Always update M[V]NO, even if we found no candidates, since we might have
282 // lost some candidates due to an incorrect MCCMNC.
283 bool operator_changed = false;
284 operator_changed |= UpdateMNO();
285 operator_changed |= UpdateMVNO();
286 if (operator_changed || ShouldNotifyPropertyUpdate()) {
287 PostNotifyOperatorChanged();
288 }
289 }
290
UpdateSID(const string & sid)291 void MobileOperatorInfoImpl::UpdateSID(const string& sid) {
292 if (user_sid_ == sid) {
293 return;
294 }
295
296 user_sid_ = sid;
297 HandleSIDUpdate();
298 candidates_by_operator_code_.clear();
299 AppendToCandidatesBySID(sid);
300
301 // Always update M[V]NO, even if we found no candidates, since we might have
302 // lost some candidates due to an incorrect SID.
303 bool operator_changed = false;
304 operator_changed |= UpdateMNO();
305 operator_changed |= UpdateMVNO();
306 if (operator_changed || ShouldNotifyPropertyUpdate()) {
307 PostNotifyOperatorChanged();
308 }
309 }
310
UpdateNID(const string & nid)311 void MobileOperatorInfoImpl::UpdateNID(const string& nid) {
312 if (user_nid_ == nid) {
313 return;
314 }
315
316 user_nid_ = nid;
317 if (UpdateMVNO() || ShouldNotifyPropertyUpdate()) {
318 PostNotifyOperatorChanged();
319 }
320 }
321
UpdateOperatorName(const string & operator_name)322 void MobileOperatorInfoImpl::UpdateOperatorName(const string& operator_name) {
323 bool operator_changed = false;
324 if (user_operator_name_ == operator_name) {
325 return;
326 }
327
328 user_operator_name_ = operator_name;
329 HandleOperatorNameUpdate();
330
331 // We must update the candidates by name anyway.
332 StringToMNOListMap::const_iterator cit = name_to_mnos_.find(
333 NormalizeOperatorName(operator_name));
334 candidates_by_name_.clear();
335 if (cit != name_to_mnos_.end()) {
336 candidates_by_name_ = cit->second;
337 // We should never have inserted an empty vector into the map.
338 DCHECK(!candidates_by_name_.empty());
339 } else {
340 LOG(INFO) << "Operator name [" << operator_name << "] "
341 << "(Normalized: [" << NormalizeOperatorName(operator_name)
342 << "]) does not match any MNO.";
343 }
344
345 operator_changed |= UpdateMNO();
346 operator_changed |= UpdateMVNO();
347 if (operator_changed || ShouldNotifyPropertyUpdate()) {
348 PostNotifyOperatorChanged();
349 }
350 }
351
UpdateOnlinePortal(const string & url,const string & method,const string & post_data)352 void MobileOperatorInfoImpl::UpdateOnlinePortal(const string& url,
353 const string& method,
354 const string& post_data) {
355 if (!user_olp_empty_ &&
356 user_olp_.url == url &&
357 user_olp_.method == method &&
358 user_olp_.post_data == post_data) {
359 return;
360 }
361
362 user_olp_empty_ = false;
363 user_olp_.url = url;
364 user_olp_.method = method;
365 user_olp_.post_data = post_data;
366 HandleOnlinePortalUpdate();
367
368 // OnlinePortal is never used in deciding M[V]NO.
369 if (ShouldNotifyPropertyUpdate()) {
370 PostNotifyOperatorChanged();
371 }
372 }
373
Reset()374 void MobileOperatorInfoImpl::Reset() {
375 bool should_notify = current_mno_ != nullptr || current_mvno_ != nullptr;
376
377 current_mno_ = nullptr;
378 current_mvno_ = nullptr;
379 operator_code_type_ = kOperatorCodeTypeUnknown;
380 candidates_by_operator_code_.clear();
381 candidates_by_name_.clear();
382
383 ClearDBInformation();
384
385 user_imsi_.clear();
386 user_iccid_.clear();
387 user_mccmnc_.clear();
388 user_sid_.clear();
389 user_nid_.clear();
390 user_operator_name_.clear();
391 user_olp_empty_ = true;
392 user_olp_.url.clear();
393 user_olp_.method.clear();
394 user_olp_.post_data.clear();
395
396 if (should_notify) {
397 PostNotifyOperatorChanged();
398 }
399 }
400
PreprocessDatabase()401 void MobileOperatorInfoImpl::PreprocessDatabase() {
402 SLOG(this, 3) << __func__;
403
404 mccmnc_to_mnos_.clear();
405 sid_to_mnos_.clear();
406 name_to_mnos_.clear();
407
408 const RepeatedPtrField<MobileNetworkOperator>& mnos = database_->mno();
409 for (const auto& mno : mnos) {
410 // MobileNetworkOperator::data is a required field.
411 DCHECK(mno.has_data());
412 const Data& data = mno.data();
413
414 const RepeatedPtrField<string>& mccmncs = data.mccmnc();
415 for (const auto& mccmnc : mccmncs) {
416 InsertIntoStringToMNOListMap(&mccmnc_to_mnos_, mccmnc, &mno);
417 }
418
419 const RepeatedPtrField<string>& sids = data.sid();
420 for (const auto& sid : sids) {
421 InsertIntoStringToMNOListMap(&sid_to_mnos_, sid, &mno);
422 }
423
424 const RepeatedPtrField<LocalizedName>& localized_names =
425 data.localized_name();
426 for (const auto& localized_name : localized_names) {
427 // LocalizedName::name is a required field.
428 DCHECK(localized_name.has_name());
429 InsertIntoStringToMNOListMap(&name_to_mnos_,
430 NormalizeOperatorName(localized_name.name()),
431 &mno);
432 }
433 }
434
435 if (database_->imvno_size() > 0) {
436 // TODO(pprabhu) Support IMVNOs.
437 LOG(ERROR) << "InternationalMobileVirtualNetworkOperators are not "
438 << "supported yet. Ignoring all IMVNOs.";
439 }
440 }
441
442 // This function assumes that duplicate |values| are never inserted for the
443 // same |key|. If you do that, the function is too dumb to deduplicate the
444 // |value|s, and two copies will get stored.
InsertIntoStringToMNOListMap(StringToMNOListMap * table,const string & key,const MobileNetworkOperator * value)445 void MobileOperatorInfoImpl::InsertIntoStringToMNOListMap(
446 StringToMNOListMap* table,
447 const string& key,
448 const MobileNetworkOperator* value) {
449 (*table)[key].push_back(value);
450 }
451
AppendToCandidatesByMCCMNC(const string & mccmnc)452 bool MobileOperatorInfoImpl::AppendToCandidatesByMCCMNC(const string& mccmnc) {
453 // First check that we haven't determined candidates using SID.
454 if (operator_code_type_ == kOperatorCodeTypeSID) {
455 LOG(WARNING) << "SID update will be overridden by the MCCMNC update for "
456 "determining MNO.";
457 candidates_by_operator_code_.clear();
458 }
459
460 operator_code_type_ = kOperatorCodeTypeMCCMNC;
461 StringToMNOListMap::const_iterator cit = mccmnc_to_mnos_.find(mccmnc);
462 if (cit == mccmnc_to_mnos_.end()) {
463 LOG(WARNING) << "Unknown MCCMNC value [" << mccmnc << "].";
464 return false;
465 }
466
467 // We should never have inserted an empty vector into the map.
468 DCHECK(!cit->second.empty());
469 for (const auto& mno : cit->second) {
470 candidates_by_operator_code_.push_back(mno);
471 }
472 return true;
473 }
474
AppendToCandidatesBySID(const string & sid)475 bool MobileOperatorInfoImpl::AppendToCandidatesBySID(const string& sid) {
476 // First check that we haven't determined candidates using MCCMNC.
477 if (operator_code_type_ == kOperatorCodeTypeMCCMNC) {
478 LOG(WARNING) << "MCCMNC update will be overriden by the SID update for "
479 "determining MNO.";
480 candidates_by_operator_code_.clear();
481 }
482
483 operator_code_type_ = kOperatorCodeTypeSID;
484 StringToMNOListMap::const_iterator cit = sid_to_mnos_.find(sid);
485 if (cit == sid_to_mnos_.end()) {
486 LOG(WARNING) << "Unknown SID value [" << sid << "].";
487 return false;
488 }
489
490 // We should never have inserted an empty vector into the map.
491 DCHECK(!cit->second.empty());
492 for (const auto& mno : cit->second) {
493 candidates_by_operator_code_.push_back(mno);
494 }
495 return true;
496 }
497
OperatorCodeString() const498 string MobileOperatorInfoImpl::OperatorCodeString() const {
499 switch (operator_code_type_) {
500 case kOperatorCodeTypeMCCMNC:
501 return "MCCMNC";
502 case kOperatorCodeTypeSID:
503 return "SID";
504 case kOperatorCodeTypeUnknown: // FALLTHROUGH
505 default:
506 return "UnknownOperatorCodeType";
507 }
508 }
509
UpdateMNO()510 bool MobileOperatorInfoImpl::UpdateMNO() {
511 SLOG(this, 3) << __func__;
512 const MobileNetworkOperator* candidate = nullptr;
513
514 // The only way |operator_code_type_| can be |kOperatorCodeTypeUnknown| is
515 // that we haven't received any operator_code updates yet.
516 DCHECK(operator_code_type_ == kOperatorCodeTypeMCCMNC ||
517 operator_code_type_ == kOperatorCodeTypeSID ||
518 (user_mccmnc_.empty() && user_sid_.empty()));
519
520 // TODO(pprabhu) Remove this despicable hack. (crosbug.com/p/30200)
521 // We currently have no principled way to handle an MVNO for which the
522 // database does not have MCCMNC data. It is possible that some other MNO
523 // matches the MCCMNC, while the MVNO matches the operator name. We special
524 // case one such operator here and override all the logic below.
525 const char kCubicUUID[] = "2de39b14-c3ba-4143-abb5-c67a390034ee";
526 for (auto candidate_by_name : candidates_by_name_) {
527 CHECK(candidate_by_name->has_data());
528 CHECK(candidate_by_name->data().has_uuid());
529 if (candidate_by_name->data().uuid() == kCubicUUID) {
530 current_mno_ = candidate_by_name;
531 RefreshDBInformation();
532 return true;
533 }
534 }
535
536 if (candidates_by_operator_code_.size() == 1) {
537 candidate = candidates_by_operator_code_[0];
538 if (candidates_by_name_.size() > 0) {
539 bool found_match = false;
540 for (auto candidate_by_name : candidates_by_name_) {
541 if (candidate_by_name == candidate) {
542 found_match = true;
543 break;
544 }
545 }
546 if (!found_match) {
547 const string& operator_code =
548 (operator_code_type_ == kOperatorCodeTypeMCCMNC) ? user_mccmnc_ :
549 user_sid_;
550 SLOG(this, 1) << "MNO determined by "
551 << OperatorCodeString() << " [" << operator_code
552 << "] does not match any suggested by name["
553 << user_operator_name_
554 << "]. "
555 << OperatorCodeString() << " overrides name!";
556 }
557 }
558 } else if (candidates_by_operator_code_.size() > 1) {
559 // Try to find an intersection of the two candidate lists. These lists
560 // should be almost always of length 1. Simply iterate.
561 for (auto candidate_by_mccmnc : candidates_by_operator_code_) {
562 for (auto candidate_by_name : candidates_by_name_) {
563 if (candidate_by_mccmnc == candidate_by_name) {
564 candidate = candidate_by_mccmnc;
565 break;
566 }
567 }
568 if (candidate != nullptr) {
569 break;
570 }
571 }
572 if (candidate == nullptr) {
573 const string& operator_code =
574 (operator_code_type_ == kOperatorCodeTypeMCCMNC) ? user_mccmnc_ :
575 user_sid_;
576 SLOG(this, 1) << "MNOs suggested by "
577 << OperatorCodeString() << " [" << operator_code
578 << "] are multiple and disjoint from those suggested "
579 << "by name["
580 << user_operator_name_
581 << "].";
582 candidate = PickOneFromDuplicates(candidates_by_operator_code_);
583 }
584 } else { // candidates_by_operator_code_.size() == 0
585 // Special case: In case we had a *wrong* operator_code update, we want
586 // to override the suggestions from |user_operator_name_|. We should not
587 // determine an MNO in this case.
588 if ((operator_code_type_ == kOperatorCodeTypeMCCMNC &&
589 !user_mccmnc_.empty()) ||
590 (operator_code_type_ == kOperatorCodeTypeSID && !user_sid_.empty())) {
591 SLOG(this, 1) << "A non-matching "
592 << OperatorCodeString() << " "
593 << "was reported by the user."
594 << "We fail the MNO match in this case.";
595 } else if (candidates_by_name_.size() == 1) {
596 candidate = candidates_by_name_[0];
597 } else if (candidates_by_name_.size() > 1) {
598 SLOG(this, 1) << "Multiple MNOs suggested by name["
599 << user_operator_name_
600 << "], and none by MCCMNC.";
601 candidate = PickOneFromDuplicates(candidates_by_name_);
602 } else { // candidates_by_name_.size() == 0
603 SLOG(this, 1) << "No candidates suggested.";
604 }
605 }
606
607 if (candidate != current_mno_) {
608 current_mno_ = candidate;
609 RefreshDBInformation();
610 return true;
611 }
612 return false;
613 }
614
UpdateMVNO()615 bool MobileOperatorInfoImpl::UpdateMVNO() {
616 SLOG(this, 3) << __func__;
617 if (current_mno_ == nullptr) {
618 return false;
619 }
620
621 for (const auto& candidate_mvno : current_mno_->mvno()) {
622 bool passed_all_filters = true;
623 for (const auto& filter : candidate_mvno.mvno_filter()) {
624 if (!FilterMatches(filter)) {
625 passed_all_filters = false;
626 break;
627 }
628 }
629 if (passed_all_filters) {
630 if (current_mvno_ == &candidate_mvno) {
631 return false;
632 }
633 current_mvno_ = &candidate_mvno;
634 RefreshDBInformation();
635 return true;
636 }
637 }
638
639 // We did not find any valid MVNO.
640 if (current_mvno_ != nullptr) {
641 current_mvno_ = nullptr;
642 RefreshDBInformation();
643 return true;
644 }
645 return false;
646 }
647
PickOneFromDuplicates(const vector<const MobileNetworkOperator * > & duplicates) const648 const MobileNetworkOperator* MobileOperatorInfoImpl::PickOneFromDuplicates(
649 const vector<const MobileNetworkOperator*>& duplicates) const {
650 if (duplicates.empty())
651 return nullptr;
652
653 for (auto candidate : duplicates) {
654 if (candidate->earmarked()) {
655 SLOG(this, 2) << "Picking earmarked candidate: "
656 << candidate->data().uuid();
657 return candidate;
658 }
659 }
660 SLOG(this, 2) << "No earmarked candidate found. Choosing the first.";
661 return duplicates[0];
662 }
663
FilterMatches(const Filter & filter)664 bool MobileOperatorInfoImpl::FilterMatches(const Filter& filter) {
665 DCHECK(filter.has_regex());
666 string to_match;
667 switch (filter.type()) {
668 case mobile_operator_db::Filter_Type_IMSI:
669 to_match = user_imsi_;
670 break;
671 case mobile_operator_db::Filter_Type_ICCID:
672 to_match = user_iccid_;
673 break;
674 case mobile_operator_db::Filter_Type_SID:
675 to_match = user_sid_;
676 break;
677 case mobile_operator_db::Filter_Type_OPERATOR_NAME:
678 to_match = user_operator_name_;
679 break;
680 case mobile_operator_db::Filter_Type_MCCMNC:
681 to_match = user_mccmnc_;
682 break;
683 default:
684 SLOG(this, 1) << "Unknown filter type [" << filter.type() << "]";
685 return false;
686 }
687 // |to_match| can be empty if we have no *user provided* information of the
688 // correct type.
689 if (to_match.empty()) {
690 SLOG(this, 2) << "Nothing to match against (filter: "
691 << filter.regex() << ").";
692 return false;
693 }
694
695 // Must use GNU regex implementation, since C++11 implementation is
696 // incomplete.
697 regex_t filter_regex;
698 string filter_regex_str = filter.regex();
699
700 // |regexec| matches the given regular expression to a substring of the
701 // given query string. Ensure that |filter_regex_str| uses anchors to
702 // accept only a full match.
703 if (filter_regex_str.front() != '^') {
704 filter_regex_str = "^" + filter_regex_str;
705 }
706 if (filter_regex_str.back() != '$') {
707 filter_regex_str = filter_regex_str + "$";
708 }
709
710 int regcomp_error = regcomp(&filter_regex,
711 filter_regex_str.c_str(),
712 REG_EXTENDED | REG_NOSUB);
713 if (regcomp_error) {
714 LOG(WARNING) << "Could not compile regex '" << filter.regex() << "'. "
715 << "Error returned: "
716 << GetRegError(regcomp_error, &filter_regex) << ". ";
717 regfree(&filter_regex);
718 return false;
719 }
720
721 int regexec_error = regexec(&filter_regex,
722 to_match.c_str(),
723 0,
724 nullptr,
725 0);
726 if (regexec_error) {
727 string error_string;
728 error_string = GetRegError(regcomp_error, &filter_regex);
729 SLOG(this, 2) << "Could not match string " << to_match << " "
730 << "against regexp " << filter.regex() << ". "
731 << "Error returned: " << error_string << ". ";
732 regfree(&filter_regex);
733 return false;
734 }
735 regfree(&filter_regex);
736 return true;
737 }
738
RefreshDBInformation()739 void MobileOperatorInfoImpl::RefreshDBInformation() {
740 ClearDBInformation();
741
742 if (current_mno_ == nullptr) {
743 return;
744 }
745
746 // |data| is a required field.
747 DCHECK(current_mno_->has_data());
748 SLOG(this, 2) << "Reloading MNO data.";
749 ReloadData(current_mno_->data());
750
751 if (current_mvno_ != nullptr) {
752 // |data| is a required field.
753 DCHECK(current_mvno_->has_data());
754 SLOG(this, 2) << "Reloading MVNO data.";
755 ReloadData(current_mvno_->data());
756 }
757 }
758
ClearDBInformation()759 void MobileOperatorInfoImpl::ClearDBInformation() {
760 uuid_.clear();
761 country_.clear();
762 nid_.clear();
763 mccmnc_list_.clear();
764 HandleMCCMNCUpdate();
765 sid_list_.clear();
766 HandleSIDUpdate();
767 operator_name_list_.clear();
768 HandleOperatorNameUpdate();
769 apn_list_.clear();
770 olp_list_.clear();
771 raw_olp_list_.clear();
772 HandleOnlinePortalUpdate();
773 activation_code_.clear();
774 requires_roaming_ = false;
775 }
776
ReloadData(const Data & data)777 void MobileOperatorInfoImpl::ReloadData(const Data& data) {
778 SLOG(this, 3) << __func__;
779 // |uuid_| is *always* overwritten. An MNO and MVNO should not share the
780 // |uuid_|.
781 CHECK(data.has_uuid());
782 uuid_ = data.uuid();
783
784 if (data.has_country()) {
785 country_ = data.country();
786 }
787
788 if (data.localized_name_size() > 0) {
789 operator_name_list_.clear();
790 for (const auto& localized_name : data.localized_name()) {
791 operator_name_list_.push_back({localized_name.name(),
792 localized_name.language()});
793 }
794 HandleOperatorNameUpdate();
795 }
796
797 if (data.has_requires_roaming()) {
798 requires_roaming_ = data.requires_roaming();
799 }
800
801 if (data.olp_size() > 0) {
802 raw_olp_list_.clear();
803 // Copy the olp list so we can mutate it.
804 for (const auto& olp : data.olp()) {
805 raw_olp_list_.push_back(olp);
806 }
807 HandleOnlinePortalUpdate();
808 }
809
810 if (data.mccmnc_size() > 0) {
811 mccmnc_list_.clear();
812 for (const auto& mccmnc : data.mccmnc()) {
813 mccmnc_list_.push_back(mccmnc);
814 }
815 HandleMCCMNCUpdate();
816 }
817
818 if (data.mobile_apn_size() > 0) {
819 apn_list_.clear();
820 for (const auto& apn_data : data.mobile_apn()) {
821 auto* apn = new MobileOperatorInfo::MobileAPN();
822 apn->apn = apn_data.apn();
823 apn->username = apn_data.username();
824 apn->password = apn_data.password();
825 for (const auto& localized_name : apn_data.localized_name()) {
826 apn->operator_name_list.push_back({localized_name.name(),
827 localized_name.language()});
828 }
829
830 // Takes ownership.
831 apn_list_.push_back(apn);
832 }
833 }
834
835 if (data.sid_size() > 0) {
836 sid_list_.clear();
837 for (const auto& sid : data.sid()) {
838 sid_list_.push_back(sid);
839 }
840 HandleSIDUpdate();
841 }
842
843 if (data.has_activation_code()) {
844 activation_code_ = data.activation_code();
845 }
846 }
847
HandleMCCMNCUpdate()848 void MobileOperatorInfoImpl::HandleMCCMNCUpdate() {
849 if (!user_mccmnc_.empty()) {
850 bool append_to_list = true;
851 for (const auto& mccmnc : mccmnc_list_) {
852 append_to_list &= (user_mccmnc_ != mccmnc);
853 }
854 if (append_to_list) {
855 mccmnc_list_.push_back(user_mccmnc_);
856 }
857 }
858
859 if (!user_mccmnc_.empty()) {
860 mccmnc_ = user_mccmnc_;
861 } else if (mccmnc_list_.size() > 0) {
862 mccmnc_ = mccmnc_list_[0];
863 } else {
864 mccmnc_.clear();
865 }
866 }
867
HandleOperatorNameUpdate()868 void MobileOperatorInfoImpl::HandleOperatorNameUpdate() {
869 if (!user_operator_name_.empty()) {
870 bool append_user_operator_name = true;
871 for (const auto& localized_name : operator_name_list_) {
872 append_user_operator_name &= (user_operator_name_ != localized_name.name);
873 }
874 if (append_user_operator_name) {
875 MobileOperatorInfo::LocalizedName localized_name {
876 user_operator_name_,
877 ""};
878 operator_name_list_.push_back(localized_name);
879 }
880 }
881
882 if (!operator_name_list_.empty()) {
883 operator_name_ = operator_name_list_[0].name;
884 } else if (!user_operator_name_.empty()) {
885 operator_name_ = user_operator_name_;
886 } else {
887 operator_name_.clear();
888 }
889 }
890
HandleSIDUpdate()891 void MobileOperatorInfoImpl::HandleSIDUpdate() {
892 if (!user_sid_.empty()) {
893 bool append_user_sid = true;
894 for (const auto& sid : sid_list_) {
895 append_user_sid &= (user_sid_ != sid);
896 }
897 if (append_user_sid) {
898 sid_list_.push_back(user_sid_);
899 }
900 }
901
902 if (!user_sid_.empty()) {
903 sid_ = user_sid_;
904 } else if (sid_list_.size() > 0) {
905 sid_ = sid_list_[0];
906 } else {
907 sid_.clear();
908 }
909 }
910
911 // Warning: Currently, an MCCMNC/SID update by itself does not result into
912 // recomputation of the |olp_list_|. This means that if the new MCCMNC/SID
913 // causes an online portal filter to match, we'll miss that.
914 // This won't be a problem if either the MNO or the MVNO changes, since data is
915 // reloaded then.
916 // This is a corner case that we don't expect to hit, since MCCMNC doesn't
917 // really change in a running system.
HandleOnlinePortalUpdate()918 void MobileOperatorInfoImpl::HandleOnlinePortalUpdate() {
919 // Always recompute |olp_list_|. We don't expect this list to be big.
920 olp_list_.clear();
921 for (const auto& raw_olp : raw_olp_list_) {
922 if (!raw_olp.has_olp_filter() || FilterMatches(raw_olp.olp_filter())) {
923 olp_list_.push_back(MobileOperatorInfo::OnlinePortal {
924 raw_olp.url(),
925 (raw_olp.method() == raw_olp.GET) ? "GET" : "POST",
926 raw_olp.post_data()});
927 }
928 }
929 if (!user_olp_empty_) {
930 bool append_user_olp = true;
931 for (const auto& olp : olp_list_) {
932 append_user_olp &= (olp.url != user_olp_.url ||
933 olp.method != user_olp_.method ||
934 olp.post_data != user_olp_.post_data);
935 }
936 if (append_user_olp) {
937 olp_list_.push_back(user_olp_);
938 }
939 }
940 }
941
PostNotifyOperatorChanged()942 void MobileOperatorInfoImpl::PostNotifyOperatorChanged() {
943 SLOG(this, 3) << __func__;
944 // If there was an outstanding task, it will get replaced.
945 notify_operator_changed_task_.Reset(
946 Bind(&MobileOperatorInfoImpl::NotifyOperatorChanged,
947 weak_ptr_factory_.GetWeakPtr()));
948 dispatcher_->PostTask(notify_operator_changed_task_.callback());
949 }
950
NotifyOperatorChanged()951 void MobileOperatorInfoImpl::NotifyOperatorChanged() {
952 FOR_EACH_OBSERVER(MobileOperatorInfo::Observer,
953 observers_,
954 OnOperatorChanged());
955 }
956
ShouldNotifyPropertyUpdate() const957 bool MobileOperatorInfoImpl::ShouldNotifyPropertyUpdate() const {
958 return IsMobileNetworkOperatorKnown() ||
959 IsMobileVirtualNetworkOperatorKnown();
960 }
961
NormalizeOperatorName(const string & name) const962 string MobileOperatorInfoImpl::NormalizeOperatorName(const string& name) const {
963 string result = base::ToLowerASCII(name);
964 base::RemoveChars(result, base::kWhitespaceASCII, &result);
965 return result;
966 }
967
968 } // namespace shill
969