blob: 11db131e450c451bd2a9dc20574c5dce95849a8a [file] [log] [blame]
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -07001// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "shill/mobile_operator_info_impl.h"
6
7#include <regex.h>
8
9#include <algorithm>
10#include <cctype>
11#include <map>
12
13#include <base/bind.h>
Prathmesh Prabhub0c44c12014-04-15 13:45:31 -070014#include <base/strings/string_util.h>
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -070015#include <google/protobuf/repeated_field.h>
16
17#include "shill/logging.h"
18#include "shill/protobuf_lite_streams.h"
19
20using base::Bind;
21using base::FilePath;
22using google::protobuf::io::CopyingInputStreamAdaptor;
23using google::protobuf::RepeatedField;
24using google::protobuf::RepeatedPtrField;
25using shill::mobile_operator_db::Data;
26using shill::mobile_operator_db::Filter;
27using shill::mobile_operator_db::LocalizedName;
28using shill::mobile_operator_db::MobileAPN;
29using shill::mobile_operator_db::MobileNetworkOperator;
30using shill::mobile_operator_db::MobileOperatorDB;
31using shill::mobile_operator_db::MobileVirtualNetworkOperator;
32using shill::mobile_operator_db::OnlinePortal;
33using std::map;
34using std::string;
35using std::vector;
36
37namespace shill {
38
39// static
Prathmesh Prabhu475c0762014-06-28 19:37:01 -070040const char *MobileOperatorInfoImpl::kDefaultDatabasePath =
41 "/usr/share/shill/serviceproviders.pbf";
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -070042const int MobileOperatorInfoImpl::kMCCMNCMinLen = 5;
43
44namespace {
45
46// Wrap some low level functions from the GNU regex librarly.
47string GetRegError(int code, const regex_t *compiled) {
48 size_t length = regerror(code, compiled, NULL, 0);
49 scoped_ptr<char[]> buffer(new char[length]);
50 DCHECK_EQ(length, regerror(code, compiled, buffer.get(), length));
51 return buffer.get();
52}
53
54} // namespace
55
Miao-chen Chou70190b32014-05-14 18:24:30 -070056MobileOperatorInfoImpl::MobileOperatorInfoImpl(EventDispatcher *dispatcher,
57 const string &info_owner)
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -070058 : dispatcher_(dispatcher),
Miao-chen Chou70190b32014-05-14 18:24:30 -070059 info_owner_(info_owner),
60 observers_(ObserverList<MobileOperatorInfo::Observer, true>::NOTIFY_ALL),
Prathmesh Prabhuc1175bf2014-04-15 16:58:05 -070061 operator_code_type_(kOperatorCodeTypeUnknown),
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -070062 current_mno_(nullptr),
63 current_mvno_(nullptr),
64 requires_roaming_(false),
65 user_olp_empty_(true),
66 weak_ptr_factory_(this) {
Prathmesh Prabhu475c0762014-06-28 19:37:01 -070067 AddDatabasePath(FilePath(kDefaultDatabasePath));
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -070068}
69
70MobileOperatorInfoImpl::~MobileOperatorInfoImpl() {}
71
72void MobileOperatorInfoImpl::ClearDatabasePaths() {
73 database_paths_.clear();
74}
75
76void MobileOperatorInfoImpl::AddDatabasePath(const FilePath &absolute_path) {
77 database_paths_.push_back(absolute_path);
78}
79
80bool MobileOperatorInfoImpl::Init() {
81 ScopedVector<MobileOperatorDB> databases;
82
83 // |database_| is guaranteed to be set once |Init| is called.
84 database_.reset(new MobileOperatorDB());
85
86 for (const auto &database_path : database_paths_) {
87 const char *database_path_cstr = database_path.value().c_str();
88 scoped_ptr<CopyingInputStreamAdaptor> database_stream;
Miao-chen Chou70190b32014-05-14 18:24:30 -070089 database_stream.reset(protobuf_lite_file_input_stream(database_path_cstr));
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -070090 if (!database_stream.get()) {
91 LOG(ERROR) << "Failed to read mobile operator database: "
Miao-chen Chou70190b32014-05-14 18:24:30 -070092 << database_path_cstr;
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -070093 continue;
94 }
95
96 scoped_ptr<MobileOperatorDB> database(new MobileOperatorDB());
97 if (!database->ParseFromZeroCopyStream(database_stream.get())) {
98 LOG(ERROR) << "Could not parse mobile operator database: "
Miao-chen Chou70190b32014-05-14 18:24:30 -070099 << database_path_cstr;
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700100 continue;
101 }
102 LOG(INFO) << "Successfully loaded database: " << database_path_cstr;
103 // Hand over ownership to the vector.
104 databases.push_back(database.release());
105 }
106
107 // Collate all loaded databases into one.
108 if (databases.size() == 0) {
109 LOG(ERROR) << "Could not read any mobile operator database. "
Miao-chen Chou70190b32014-05-14 18:24:30 -0700110 << "Will not be able to determine MVNO.";
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700111 return false;
112 }
113
114 for (const auto &database : databases) {
115 // TODO(pprabhu) This merge might be very costly. Determine if we need to
116 // implement move semantics / bias the merge to use the largest database
117 // as the base database and merge other databases into it.
118 database_->MergeFrom(*database);
119 }
120 PreprocessDatabase();
121 return true;
122}
123
124void MobileOperatorInfoImpl::AddObserver(
125 MobileOperatorInfo::Observer *observer) {
126 observers_.AddObserver(observer);
127}
128
129void MobileOperatorInfoImpl::RemoveObserver(
130 MobileOperatorInfo::Observer *observer) {
131 observers_.RemoveObserver(observer);
132}
133
134bool MobileOperatorInfoImpl::IsMobileNetworkOperatorKnown() const {
135 return (current_mno_ != nullptr);
136}
137
138bool MobileOperatorInfoImpl::IsMobileVirtualNetworkOperatorKnown() const {
139 return (current_mvno_ != nullptr);
140}
141
142// ///////////////////////////////////////////////////////////////////////////
143// Getters.
Miao-chen Chou70190b32014-05-14 18:24:30 -0700144const string &MobileOperatorInfoImpl::info_owner() const {
145 return info_owner_;
146}
147
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700148const string &MobileOperatorInfoImpl::uuid() const {
149 return uuid_;
150}
151
152const string &MobileOperatorInfoImpl::operator_name() const {
153 // TODO(pprabhu) I'm not very sure yet what is the right thing to do here.
154 // It is possible that we obtain a name OTA, and then using some other
155 // information (say the iccid range), determine that this is an MVNO. In
156 // that case, we may want to *override* |user_operator_name_| by the name
157 // obtained from the DB for the MVNO.
158 return operator_name_;
159}
160
161const string &MobileOperatorInfoImpl::country() const {
162 return country_;
163}
164
165const string &MobileOperatorInfoImpl::mccmnc() const {
166 return mccmnc_;
167}
168
169const string &MobileOperatorInfoImpl::MobileOperatorInfoImpl::sid() const {
170 return sid_;
171}
172
173const string &MobileOperatorInfoImpl::nid() const {
174 return (user_nid_ == "") ? nid_ : user_nid_;
175}
176
177const vector<string> &MobileOperatorInfoImpl::mccmnc_list() const {
178 return mccmnc_list_;
179}
180
181const vector<string> &MobileOperatorInfoImpl::sid_list() const {
182 return sid_list_;
183}
184
185const vector<MobileOperatorInfo::LocalizedName> &
186MobileOperatorInfoImpl::operator_name_list() const {
187 return operator_name_list_;
188}
189
190const ScopedVector<MobileOperatorInfo::MobileAPN> &
191MobileOperatorInfoImpl::apn_list() const {
192 return apn_list_;
193}
194
195const vector<MobileOperatorInfo::OnlinePortal> &
196MobileOperatorInfoImpl::olp_list() const {
197 return olp_list_;
198}
199
200const string &MobileOperatorInfoImpl::activation_code() const {
201 return activation_code_;
202}
203
204bool MobileOperatorInfoImpl::requires_roaming() const {
205 return requires_roaming_;
206}
207
208// ///////////////////////////////////////////////////////////////////////////
209// Functions used to notify this object of operator data changes.
210void MobileOperatorInfoImpl::UpdateIMSI(const string &imsi) {
211 bool operator_changed = false;
212 if (user_imsi_ == imsi) {
213 return;
214 }
215
216 user_imsi_ = imsi;
217
218 if (!user_mccmnc_.empty()) {
Prathmesh Prabhub0c44c12014-04-15 13:45:31 -0700219 if (!StartsWithASCII(imsi, user_mccmnc_, false)) {
220 LOG(WARNING) << "MCCMNC [" << user_mccmnc_ << "] is not a substring of "
221 << "the IMSI [" << imsi << "].";
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700222 }
223 } else {
224 // Attempt to determine the MNO from IMSI since MCCMNC is absent.
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700225 AppendToCandidatesByMCCMNC(imsi.substr(0, kMCCMNCMinLen));
226 AppendToCandidatesByMCCMNC(imsi.substr(0, kMCCMNCMinLen + 1));
Prathmesh Prabhuc1175bf2014-04-15 16:58:05 -0700227 if (!candidates_by_operator_code_.empty()) {
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700228 // We found some candidates using IMSI.
229 operator_changed |= UpdateMNO();
230 }
231 }
232 operator_changed |= UpdateMVNO();
233
234 // No special notification should be sent for this property, since the object
235 // does not expose |imsi| as a property at all.
236 if (operator_changed) {
237 PostNotifyOperatorChanged();
238 }
239}
240
241void MobileOperatorInfoImpl::UpdateICCID(const string &iccid) {
242 if (user_iccid_ == iccid) {
243 return;
244 }
245
246 user_iccid_ = iccid;
247 // |iccid| is not an exposed property, so don't raise event for just this
248 // property update.
Miao-chen Chou70190b32014-05-14 18:24:30 -0700249 if (UpdateMVNO()) {
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700250 PostNotifyOperatorChanged();
251 }
252}
253
254void MobileOperatorInfoImpl::UpdateMCCMNC(const string &mccmnc) {
255 if (user_mccmnc_ == mccmnc) {
256 return;
257 }
258
259 user_mccmnc_ = mccmnc;
260 HandleMCCMNCUpdate();
Prathmesh Prabhuc1175bf2014-04-15 16:58:05 -0700261 candidates_by_operator_code_.clear();
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700262 AppendToCandidatesByMCCMNC(mccmnc);
263
264 // Always update M[V]NO, even if we found no candidates, since we might have
265 // lost some candidates due to an incorrect MCCMNC.
266 bool operator_changed = false;
267 operator_changed |= UpdateMNO();
268 operator_changed |= UpdateMVNO();
269 if (operator_changed || ShouldNotifyPropertyUpdate()) {
270 PostNotifyOperatorChanged();
271 }
272}
273
274void MobileOperatorInfoImpl::UpdateSID(const string &sid) {
275 if (user_sid_ == sid) {
276 return;
277 }
278
279 user_sid_ = sid;
280 HandleSIDUpdate();
Prathmesh Prabhuc1175bf2014-04-15 16:58:05 -0700281 candidates_by_operator_code_.clear();
282 AppendToCandidatesBySID(sid);
283
284 // Always update M[V]NO, even if we found no candidates, since we might have
285 // lost some candidates due to an incorrect SID.
286 bool operator_changed = false;
287 operator_changed |= UpdateMNO();
288 operator_changed |= UpdateMVNO();
289 if (operator_changed || ShouldNotifyPropertyUpdate()) {
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700290 PostNotifyOperatorChanged();
291 }
292}
293
294void MobileOperatorInfoImpl::UpdateNID(const string &nid) {
295 if (user_nid_ == nid) {
296 return;
297 }
298
299 user_nid_ = nid;
300 if (UpdateMVNO() || ShouldNotifyPropertyUpdate()) {
301 PostNotifyOperatorChanged();
302 }
303}
304
305void MobileOperatorInfoImpl::UpdateOperatorName(const string &operator_name) {
306 bool operator_changed = false;
307 if (user_operator_name_ == operator_name) {
308 return;
309 }
310
311 user_operator_name_ = operator_name;
312 HandleOperatorNameUpdate();
313
314 // We must update the candidates by name anyway.
315 StringToMNOListMap::const_iterator cit = name_to_mnos_.find(operator_name);
316 candidates_by_name_.clear();
317 if (cit != name_to_mnos_.end()) {
318 candidates_by_name_ = cit->second;
319 // We should never have inserted an empty vector into the map.
320 DCHECK(!candidates_by_name_.empty());
321 } else {
322 LOG(INFO) << "Operator name [" << operator_name << "] does not match any "
Miao-chen Chou70190b32014-05-14 18:24:30 -0700323 << "MNO.";
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700324 }
325
326 operator_changed |= UpdateMNO();
327 operator_changed |= UpdateMVNO();
328 if (operator_changed || ShouldNotifyPropertyUpdate()) {
329 PostNotifyOperatorChanged();
330 }
331}
332
333void MobileOperatorInfoImpl::UpdateOnlinePortal(const string &url,
334 const string &method,
335 const string &post_data) {
336 if (!user_olp_empty_ &&
337 user_olp_.url == url &&
338 user_olp_.method == method &&
339 user_olp_.post_data == post_data) {
340 return;
341 }
342
343 user_olp_empty_ = false;
344 user_olp_.url = url;
345 user_olp_.method = method;
346 user_olp_.post_data = post_data;
347 HandleOnlinePortalUpdate();
348
349 // OnlinePortal is never used in deciding M[V]NO.
350 if (ShouldNotifyPropertyUpdate()) {
351 PostNotifyOperatorChanged();
352 }
353}
354
355void MobileOperatorInfoImpl::Reset() {
356 bool should_notify = current_mno_ != nullptr || current_mvno_ != nullptr;
357
358 current_mno_ = nullptr;
359 current_mvno_ = nullptr;
Prathmesh Prabhuc1175bf2014-04-15 16:58:05 -0700360 operator_code_type_ = kOperatorCodeTypeUnknown;
361 candidates_by_operator_code_.clear();
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700362 candidates_by_name_.clear();
363
364 ClearDBInformation();
365
366 user_imsi_.clear();
367 user_iccid_.clear();
368 user_mccmnc_.clear();
369 user_sid_.clear();
370 user_nid_.clear();
371 user_operator_name_.clear();
372 user_olp_empty_ = true;
373 user_olp_.url.clear();
374 user_olp_.method.clear();
375 user_olp_.post_data.clear();
376
377 if (should_notify) {
378 PostNotifyOperatorChanged();
379 }
380}
381
382void MobileOperatorInfoImpl::PreprocessDatabase() {
383 SLOG(Cellular, 3) << __func__;
384
385 mccmnc_to_mnos_.clear();
Prathmesh Prabhuc1175bf2014-04-15 16:58:05 -0700386 sid_to_mnos_.clear();
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700387 name_to_mnos_.clear();
388
389 const RepeatedPtrField<MobileNetworkOperator> &mnos = database_->mno();
390 for (const auto &mno : mnos) {
391 // MobileNetworkOperator::data is a required field.
392 DCHECK(mno.has_data());
393 const Data &data = mno.data();
394
395 const RepeatedPtrField<string> &mccmncs = data.mccmnc();
396 for (const auto &mccmnc : mccmncs) {
Alex Vakulenko8a532292014-06-16 17:18:44 -0700397 InsertIntoStringToMNOListMap(&mccmnc_to_mnos_, mccmnc, &mno);
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700398 }
399
Prathmesh Prabhuc1175bf2014-04-15 16:58:05 -0700400 const RepeatedPtrField<string> &sids = data.sid();
Alex Vakulenko8a532292014-06-16 17:18:44 -0700401 for (const auto &sid : sids) {
402 InsertIntoStringToMNOListMap(&sid_to_mnos_, sid, &mno);
Prathmesh Prabhuc1175bf2014-04-15 16:58:05 -0700403 }
404
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700405 const RepeatedPtrField<LocalizedName> &localized_names =
406 data.localized_name();
407 for (const auto &localized_name : localized_names) {
408 // LocalizedName::name is a required field.
409 DCHECK(localized_name.has_name());
Alex Vakulenko8a532292014-06-16 17:18:44 -0700410 InsertIntoStringToMNOListMap(&name_to_mnos_,
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700411 localized_name.name(),
412 &mno);
413 }
414 }
415
416 if (database_->imvno_size() > 0) {
417 // TODO(pprabhu) Support IMVNOs.
418 LOG(ERROR) << "InternationalMobileVirtualNetworkOperators are not "
Miao-chen Chou70190b32014-05-14 18:24:30 -0700419 << "supported yet. Ignoring all IMVNOs.";
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700420 }
421}
422
423// This function assumes that duplicate |values| are never inserted for the
424// same |key|. If you do that, the function is too dumb to deduplicate the
425// |value|s, and two copies will get stored.
426void MobileOperatorInfoImpl::InsertIntoStringToMNOListMap(
Alex Vakulenko8a532292014-06-16 17:18:44 -0700427 StringToMNOListMap *table,
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700428 const string &key,
429 const MobileNetworkOperator *value) {
Alex Vakulenko8a532292014-06-16 17:18:44 -0700430 (*table)[key].push_back(value);
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700431}
432
433bool MobileOperatorInfoImpl::AppendToCandidatesByMCCMNC(const string &mccmnc) {
Prathmesh Prabhuc1175bf2014-04-15 16:58:05 -0700434 // First check that we haven't determined candidates using SID.
435 if (operator_code_type_ == kOperatorCodeTypeSID) {
Alex Vakulenko8a532292014-06-16 17:18:44 -0700436 LOG(WARNING) << "SID update will be overridden by the MCCMNC update for "
Prathmesh Prabhuc1175bf2014-04-15 16:58:05 -0700437 "determining MNO.";
438 candidates_by_operator_code_.clear();
439 }
440
441 operator_code_type_ = kOperatorCodeTypeMCCMNC;
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700442 StringToMNOListMap::const_iterator cit = mccmnc_to_mnos_.find(mccmnc);
443 if (cit == mccmnc_to_mnos_.end()) {
444 LOG(WARNING) << "Unknown MCCMNC value [" << mccmnc << "].";
445 return false;
446 }
447
448 // We should never have inserted an empty vector into the map.
449 DCHECK(!cit->second.empty());
Prathmesh Prabhuc1175bf2014-04-15 16:58:05 -0700450 for (const auto &mno : cit->second) {
451 candidates_by_operator_code_.push_back(mno);
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700452 }
453 return true;
454}
455
Prathmesh Prabhuc1175bf2014-04-15 16:58:05 -0700456bool MobileOperatorInfoImpl::AppendToCandidatesBySID(const string &sid) {
457 // First check that we haven't determined candidates using MCCMNC.
458 if (operator_code_type_ == kOperatorCodeTypeMCCMNC) {
459 LOG(WARNING) << "MCCMNC update will be overriden by the SID update for "
460 "determining MNO.";
461 candidates_by_operator_code_.clear();
462 }
463
464 operator_code_type_ = kOperatorCodeTypeSID;
465 StringToMNOListMap::const_iterator cit = sid_to_mnos_.find(sid);
466 if (cit == sid_to_mnos_.end()) {
467 LOG(WARNING) << "Unknown SID value [" << sid << "].";
468 return false;
469 }
470
471 // We should never have inserted an empty vector into the map.
472 DCHECK(!cit->second.empty());
473 for (const auto &mno : cit->second) {
474 candidates_by_operator_code_.push_back(mno);
475 }
476 return true;
477}
478
479string MobileOperatorInfoImpl::OperatorCodeString() const {
Miao-chen Chou70190b32014-05-14 18:24:30 -0700480 switch (operator_code_type_) {
Prathmesh Prabhuc1175bf2014-04-15 16:58:05 -0700481 case kOperatorCodeTypeMCCMNC:
482 return "MCCMNC";
483 case kOperatorCodeTypeSID:
484 return "SID";
485 case kOperatorCodeTypeUnknown: // FALLTHROUGH
486 default:
487 return "UnknownOperatorCodeType";
488 }
489}
490
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700491bool MobileOperatorInfoImpl::UpdateMNO() {
492 SLOG(Cellular, 3) << __func__;
493 const MobileNetworkOperator *candidate = nullptr;
Prathmesh Prabhuc1175bf2014-04-15 16:58:05 -0700494
495 // The only way |operator_code_type_| can be |kOperatorCodeTypeUnknown| is
496 // that we haven't received any operator_code updates yet.
497 DCHECK(operator_code_type_ == kOperatorCodeTypeMCCMNC ||
498 operator_code_type_ == kOperatorCodeTypeSID ||
499 (user_mccmnc_.empty() && user_sid_.empty()));
500
Prathmesh Prabhu48a73792014-07-02 11:54:33 -0700501 // TODO(pprabhu) Remove this despicable hack. (crosbug.com/p/30200)
502 // We currently have no principled way to handle an MVNO for which the
503 // database does not have MCCMNC data. It is possible that some other MNO
504 // matches the MCCMNC, while the MVNO matches the operator name. We special
505 // case one such operator here and override all the logic below.
506 const char kCubicUUID[] = "2de39b14-c3ba-4143-abb5-c67a390034ee";
507 for (auto candidate_by_name : candidates_by_name_) {
508 CHECK(candidate_by_name->has_data());
509 CHECK(candidate_by_name->data().has_uuid());
510 if (candidate_by_name->data().uuid() == kCubicUUID) {
511 current_mno_ = candidate_by_name;
512 RefreshDBInformation();
513 return true;
514 }
515 }
516
Prathmesh Prabhuc1175bf2014-04-15 16:58:05 -0700517 if (candidates_by_operator_code_.size() == 1) {
518 candidate = candidates_by_operator_code_[0];
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700519 if (candidates_by_name_.size() > 0) {
520 bool found_match = false;
521 for (auto candidate_by_name : candidates_by_name_) {
522 if (candidate_by_name == candidate) {
523 found_match = true;
524 break;
525 }
526 }
527 if (!found_match) {
Prathmesh Prabhuc1175bf2014-04-15 16:58:05 -0700528 const string &operator_code =
529 (operator_code_type_ == kOperatorCodeTypeMCCMNC) ? user_mccmnc_ :
530 user_sid_;
531 SLOG(Cellular, 1) << "MNO determined by "
532 << OperatorCodeString() << " [" << operator_code
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700533 << "] does not match any suggested by name["
534 << user_operator_name_
Prathmesh Prabhuc1175bf2014-04-15 16:58:05 -0700535 << "]. "
536 << OperatorCodeString() << " overrides name!";
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700537 }
538 }
Prathmesh Prabhuc1175bf2014-04-15 16:58:05 -0700539 } else if (candidates_by_operator_code_.size() > 1) {
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700540 // Try to find an intersection of the two candidate lists. These lists
541 // should be almost always of length 1. Simply iterate.
Prathmesh Prabhuc1175bf2014-04-15 16:58:05 -0700542 for (auto candidate_by_mccmnc : candidates_by_operator_code_) {
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700543 for (auto candidate_by_name : candidates_by_name_) {
544 if (candidate_by_mccmnc == candidate_by_name) {
545 candidate = candidate_by_mccmnc;
546 break;
547 }
548 }
549 if (candidate != nullptr) {
550 break;
551 }
552 }
553 if (candidate == nullptr) {
Prathmesh Prabhuc1175bf2014-04-15 16:58:05 -0700554 const string &operator_code =
555 (operator_code_type_ == kOperatorCodeTypeMCCMNC) ? user_mccmnc_ :
556 user_sid_;
557 SLOG(Cellular, 1) << "MNOs suggested by "
558 << OperatorCodeString() << " [" << operator_code
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700559 << "] are multiple and disjoint from those suggested "
560 << "by name["
561 << user_operator_name_
562 << "]. Can't make a decision.";
563 }
Prathmesh Prabhuc1175bf2014-04-15 16:58:05 -0700564 } else { // candidates_by_operator_code_.size() == 0
565 // Special case: In case we had a *wrong* operator_code update, we want
566 // to override the suggestions from |user_operator_name_|. We should not
567 // determine an MNO in this case.
568 if ((operator_code_type_ == kOperatorCodeTypeMCCMNC &&
569 !user_mccmnc_.empty()) ||
570 (operator_code_type_ == kOperatorCodeTypeSID && !user_sid_.empty())) {
571 SLOG(Cellular, 1) << "A non-matching "
572 << OperatorCodeString() << " "
573 << "was reported by the user."
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700574 << "We fail the MNO match in this case.";
575 } else if (candidates_by_name_.size() == 1) {
576 candidate = candidates_by_name_[0];
577 } else if (candidates_by_name_.size() > 1) {
578 SLOG(Cellular, 1) << "Multiple MNOs suggested by name["
579 << user_operator_name_
580 << "], and none by MCCMNC. Can't make a decision.";
581 } else { // candidates_by_name_.size() == 0
582 SLOG(Cellular, 1) << "No candidates suggested.";
583 }
584 }
585
586 if (candidate != current_mno_) {
587 current_mno_ = candidate;
588 RefreshDBInformation();
589 return true;
590 }
591 return false;
592}
593
594bool MobileOperatorInfoImpl::UpdateMVNO() {
595 SLOG(Cellular, 3) << __func__;
596 if (current_mno_ == nullptr) {
597 return false;
598 }
599
600 for (const auto &candidate_mvno : current_mno_->mvno()) {
601 bool passed_all_filters = true;
602 for (const auto &filter : candidate_mvno.mvno_filter()) {
Prathmesh Prabhu0db0bf02014-05-06 15:17:44 -0700603 if (!FilterMatches(filter)) {
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700604 passed_all_filters = false;
605 break;
606 }
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700607 }
608 if (passed_all_filters) {
609 if (current_mvno_ == &candidate_mvno) {
610 return false;
611 }
612 current_mvno_ = &candidate_mvno;
613 RefreshDBInformation();
614 return true;
615 }
616 }
617
618 // We did not find any valid MVNO.
619 if (current_mvno_ != nullptr) {
620 current_mvno_ = nullptr;
621 RefreshDBInformation();
622 return true;
623 }
624 return false;
625}
626
Prathmesh Prabhu0db0bf02014-05-06 15:17:44 -0700627bool MobileOperatorInfoImpl::FilterMatches(const Filter &filter) {
628 DCHECK(filter.has_regex());
629 string to_match;
630 switch (filter.type()) {
631 case mobile_operator_db::Filter_Type_IMSI:
632 to_match = user_imsi_;
633 break;
634 case mobile_operator_db::Filter_Type_ICCID:
635 to_match = user_iccid_;
636 break;
637 case mobile_operator_db::Filter_Type_SID:
638 to_match = user_sid_;
639 break;
640 case mobile_operator_db::Filter_Type_OPERATOR_NAME:
641 to_match = user_operator_name_;
642 break;
643 case mobile_operator_db::Filter_Type_MCCMNC:
644 to_match = user_mccmnc_;
645 break;
646 default:
Miao-chen Chou70190b32014-05-14 18:24:30 -0700647 SLOG(Cellular, 1) << "Unknown filter type [" << filter.type() << "]";
Prathmesh Prabhu0db0bf02014-05-06 15:17:44 -0700648 return false;
649 }
650 // |to_match| can be empty if we have no *user provided* information of the
651 // correct type.
652 if (to_match.empty()) {
653 SLOG(Cellular, 2) << "Nothing to match against (filter: "
654 << filter.regex() << ").";
655 return false;
656 }
657
658 // Must use GNU regex implementation, since C++11 implementation is
659 // incomplete.
660 regex_t filter_regex;
661 string filter_regex_str = filter.regex();
662
663 // |regexec| matches the given regular expression to a substring of the
664 // given query string. Ensure that |filter_regex_str| uses anchors to
665 // accept only a full match.
666 if (filter_regex_str.front() != '^') {
667 filter_regex_str = "^" + filter_regex_str;
668 }
669 if (filter_regex_str.back() != '$') {
670 filter_regex_str = filter_regex_str + "$";
671 }
672
673 int regcomp_error = regcomp(&filter_regex,
674 filter_regex_str.c_str(),
675 REG_EXTENDED | REG_NOSUB);
Miao-chen Chou70190b32014-05-14 18:24:30 -0700676 if (regcomp_error) {
Prathmesh Prabhu0db0bf02014-05-06 15:17:44 -0700677 LOG(WARNING) << "Could not compile regex '" << filter.regex() << "'. "
678 << "Error returned: "
679 << GetRegError(regcomp_error, &filter_regex) << ". ";
680 regfree(&filter_regex);
681 return false;
682 }
683
684 int regexec_error = regexec(&filter_regex,
685 to_match.c_str(),
686 0,
687 NULL,
688 0);
689 if (regexec_error) {
690 string error_string;
691 error_string = GetRegError(regcomp_error, &filter_regex);
692 SLOG(Cellular, 2) << "Could not match string " << to_match << " "
693 << "against regexp " << filter.regex() << ". "
694 << "Error returned: " << error_string << ". ";
695 regfree(&filter_regex);
696 return false;
697 }
698 regfree(&filter_regex);
699 return true;
700}
701
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700702void MobileOperatorInfoImpl::RefreshDBInformation() {
703 ClearDBInformation();
704
705 if (current_mno_ == nullptr) {
706 return;
707 }
708
709 // |data| is a required field.
710 DCHECK(current_mno_->has_data());
711 SLOG(Cellular, 2) << "Reloading MNO data.";
712 ReloadData(current_mno_->data());
713
714 if (current_mvno_ != nullptr) {
715 // |data| is a required field.
716 DCHECK(current_mvno_->has_data());
717 SLOG(Cellular, 2) << "Reloading MVNO data.";
718 ReloadData(current_mvno_->data());
719 }
720}
721
722void MobileOperatorInfoImpl::ClearDBInformation() {
723 uuid_.clear();
724 country_.clear();
725 nid_.clear();
726 mccmnc_list_.clear();
727 HandleMCCMNCUpdate();
728 sid_list_.clear();
729 HandleSIDUpdate();
730 operator_name_list_.clear();
731 HandleOperatorNameUpdate();
732 apn_list_.clear();
733 olp_list_.clear();
Prathmesh Prabhu0db0bf02014-05-06 15:17:44 -0700734 raw_olp_list_.clear();
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700735 HandleOnlinePortalUpdate();
736 activation_code_.clear();
737 requires_roaming_ = false;
738}
739
740void MobileOperatorInfoImpl::ReloadData(const Data &data) {
741 SLOG(Cellular, 3) << __func__;
742 // |uuid_| is *always* overwritten. An MNO and MVNO should not share the
743 // |uuid_|.
Prathmesh Prabhuf3cbe882014-05-07 19:30:19 -0700744 CHECK(data.has_uuid());
745 uuid_ = data.uuid();
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700746
747 if (data.has_country()) {
748 country_ = data.country();
749 }
750
751 if (data.localized_name_size() > 0) {
752 operator_name_list_.clear();
753 for (const auto &localized_name : data.localized_name()) {
754 operator_name_list_.push_back({localized_name.name(),
755 localized_name.language()});
756 }
757 HandleOperatorNameUpdate();
758 }
759
760 if (data.has_requires_roaming()) {
761 requires_roaming_ = data.requires_roaming();
762 }
763
764 if (data.olp_size() > 0) {
Prathmesh Prabhu0db0bf02014-05-06 15:17:44 -0700765 raw_olp_list_.clear();
766 // Copy the olp list so we can mutate it.
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700767 for (const auto &olp : data.olp()) {
Prathmesh Prabhu0db0bf02014-05-06 15:17:44 -0700768 raw_olp_list_.push_back(olp);
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700769 }
770 HandleOnlinePortalUpdate();
771 }
772
773 if (data.mccmnc_size() > 0) {
774 mccmnc_list_.clear();
775 for (const auto &mccmnc : data.mccmnc()) {
776 mccmnc_list_.push_back(mccmnc);
777 }
778 HandleMCCMNCUpdate();
779 }
780
781 if (data.mobile_apn_size() > 0) {
782 apn_list_.clear();
783 for (const auto &apn_data : data.mobile_apn()) {
784 auto *apn = new MobileOperatorInfo::MobileAPN();
785 apn->apn = apn_data.apn();
786 apn->username = apn_data.username();
787 apn->password = apn_data.password();
788 for (const auto &localized_name : apn_data.localized_name()) {
789 apn->operator_name_list.push_back({localized_name.name(),
790 localized_name.language()});
791 }
792
793 // Takes ownership.
794 apn_list_.push_back(apn);
795 }
796 }
797
798 if (data.sid_size() > 0) {
799 sid_list_.clear();
800 for (const auto &sid : data.sid()) {
801 sid_list_.push_back(sid);
802 }
803 HandleSIDUpdate();
804 }
805
806 if (data.has_activation_code()) {
807 activation_code_ = data.activation_code();
808 }
809}
810
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700811void MobileOperatorInfoImpl::HandleMCCMNCUpdate() {
812 if (!user_mccmnc_.empty()) {
813 bool append_to_list = true;
814 for (const auto &mccmnc : mccmnc_list_) {
815 append_to_list &= (user_mccmnc_ != mccmnc);
816 }
817 if (append_to_list) {
818 mccmnc_list_.push_back(user_mccmnc_);
819 }
820 }
821
822 if (!user_mccmnc_.empty()) {
823 mccmnc_ = user_mccmnc_;
824 } else if (mccmnc_list_.size() > 0) {
825 mccmnc_ = mccmnc_list_[0];
826 } else {
827 mccmnc_.clear();
828 }
829}
830
831void MobileOperatorInfoImpl::HandleOperatorNameUpdate() {
832 if (!user_operator_name_.empty()) {
833 bool append_user_operator_name = true;
834 for (const auto &localized_name : operator_name_list_) {
835 append_user_operator_name &= (user_operator_name_ != localized_name.name);
836 }
837 if (append_user_operator_name) {
838 MobileOperatorInfo::LocalizedName localized_name {
839 user_operator_name_,
840 ""};
841 operator_name_list_.push_back(localized_name);
842 }
843 }
844
845 if (!user_operator_name_.empty()) {
846 operator_name_ = user_operator_name_;
847 } else if (operator_name_list_.size() > 0) {
848 operator_name_ = operator_name_list_[0].name;
849 } else {
850 operator_name_.clear();
851 }
852}
853
854void MobileOperatorInfoImpl::HandleSIDUpdate() {
855 if (!user_sid_.empty()) {
856 bool append_user_sid = true;
857 for (const auto &sid : sid_list_) {
858 append_user_sid &= (user_sid_ != sid);
859 }
860 if (append_user_sid) {
861 sid_list_.push_back(user_sid_);
862 }
863 }
864
865 if (!user_sid_.empty()) {
866 sid_ = user_sid_;
867 } else if (sid_list_.size() > 0) {
868 sid_ = sid_list_[0];
869 } else {
870 sid_.clear();
871 }
872}
873
Prathmesh Prabhu0db0bf02014-05-06 15:17:44 -0700874// Warning: Currently, an MCCMNC/SID update by itself does not result into
875// recomputation of the |olp_list_|. This means that if the new MCCMNC/SID
876// causes an online portal filter to match, we'll miss that.
877// This won't be a problem if either the MNO or the MVNO changes, since data is
878// reloaded then.
879// This is a corner case that we don't expect to hit, since MCCMNC doesn't
880// really change in a running system.
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700881void MobileOperatorInfoImpl::HandleOnlinePortalUpdate() {
Prathmesh Prabhu0db0bf02014-05-06 15:17:44 -0700882 // Always recompute |olp_list_|. We don't expect this list to be big.
883 olp_list_.clear();
884 for (const auto &raw_olp : raw_olp_list_) {
885 if (!raw_olp.has_olp_filter() || FilterMatches(raw_olp.olp_filter())) {
886 olp_list_.push_back(MobileOperatorInfo::OnlinePortal {
887 raw_olp.url(),
888 (raw_olp.method() == raw_olp.GET) ? "GET" : "POST",
889 raw_olp.post_data()});
890 }
891 }
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700892 if (!user_olp_empty_) {
893 bool append_user_olp = true;
894 for (const auto &olp : olp_list_) {
895 append_user_olp &= (olp.url != user_olp_.url ||
896 olp.method != user_olp_.method ||
897 olp.post_data != user_olp_.post_data);
898 }
899 if (append_user_olp) {
900 olp_list_.push_back(user_olp_);
901 }
902 }
903}
904
905void MobileOperatorInfoImpl::PostNotifyOperatorChanged() {
906 SLOG(Cellular, 3) << __func__;
Prathmesh Prabhu347ff6d2014-05-09 09:28:02 -0700907 // If there was an outstanding task, it will get replaced.
908 notify_operator_changed_task_.Reset(
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700909 Bind(&MobileOperatorInfoImpl::NotifyOperatorChanged,
910 weak_ptr_factory_.GetWeakPtr()));
Prathmesh Prabhu347ff6d2014-05-09 09:28:02 -0700911 dispatcher_->PostTask(notify_operator_changed_task_.callback());
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700912}
913
914void MobileOperatorInfoImpl::NotifyOperatorChanged() {
915 FOR_EACH_OBSERVER(MobileOperatorInfo::Observer,
916 observers_,
917 OnOperatorChanged());
918}
919
920bool MobileOperatorInfoImpl::ShouldNotifyPropertyUpdate() const {
921 return IsMobileNetworkOperatorKnown() ||
922 IsMobileVirtualNetworkOperatorKnown();
923}
924
Prathmesh Prabhu28b4a3b2014-03-28 11:52:09 -0700925} // namespace shill