blob: a5d1f08ab2ca2e01d4d2601dfbe58da928d67467 [file] [log] [blame]
//
// Copyright (C) 2012 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/cellular_service.h"
#include <string>
#include <base/stl_util.h>
#include <base/strings/stringprintf.h>
#include <chromeos/dbus/service_constants.h>
#include "shill/adaptor_interfaces.h"
#include "shill/cellular/cellular.h"
#include "shill/property_accessor.h"
#include "shill/store_interface.h"
using std::set;
using std::string;
namespace shill {
namespace Logging {
static auto kModuleLogScope = ScopeLogger::kCellular;
static string ObjectID(CellularService* c) { return c->GetRpcIdentifier(); }
}
// statics
const char CellularService::kAutoConnActivating[] = "activating";
const char CellularService::kAutoConnBadPPPCredentials[] =
"bad PPP credentials";
const char CellularService::kAutoConnDeviceDisabled[] = "device disabled";
const char CellularService::kAutoConnOutOfCredits[] = "device out of credits";
const char CellularService::kAutoConnOutOfCreditsDetectionInProgress[] =
"device detecting out-of-credits";
const char CellularService::kStorageIccid[] = "Cellular.Iccid";
const char CellularService::kStorageImei[] = "Cellular.Imei";
const char CellularService::kStorageImsi[] = "Cellular.Imsi";
const char CellularService::kStorageMeid[] = "Cellular.Meid";
const char CellularService::kStoragePPPUsername[] = "Cellular.PPP.Username";
const char CellularService::kStoragePPPPassword[] = "Cellular.PPP.Password";
// TODO(petkov): Add these to system_api/dbus/service_constants.h
namespace {
const char kCellularPPPUsernameProperty[] = "Cellular.PPP.Username";
const char kCellularPPPPasswordProperty[] = "Cellular.PPP.Password";
} // namespace
namespace {
const char kStorageAPN[] = "Cellular.APN";
const char kStorageLastGoodAPN[] = "Cellular.LastGoodAPN";
} // namespace
static bool GetNonEmptyField(const Stringmap& stringmap,
const string& fieldname,
string* value) {
Stringmap::const_iterator it = stringmap.find(fieldname);
if (it != stringmap.end() && !it->second.empty()) {
*value = it->second;
return true;
}
return false;
}
CellularService::CellularService(ModemInfo* modem_info,
const CellularRefPtr& device)
: Service(modem_info->control_interface(), modem_info->dispatcher(),
modem_info->metrics(), modem_info->manager(),
Technology::kCellular),
activation_type_(kActivationTypeUnknown),
cellular_(device),
is_auto_connecting_(false) {
SetConnectable(true);
PropertyStore* store = this->mutable_store();
HelpRegisterDerivedString(kActivationTypeProperty,
&CellularService::CalculateActivationType,
nullptr);
store->RegisterConstString(kActivationStateProperty, &activation_state_);
HelpRegisterDerivedStringmap(kCellularApnProperty,
&CellularService::GetApn,
&CellularService::SetApn);
store->RegisterConstStringmap(kCellularLastGoodApnProperty,
&last_good_apn_info_);
store->RegisterConstString(kNetworkTechnologyProperty, &network_technology_);
HelpRegisterDerivedBool(kOutOfCreditsProperty,
&CellularService::IsOutOfCredits,
nullptr);
store->RegisterConstStringmap(kPaymentPortalProperty, &olp_);
store->RegisterConstString(kRoamingStateProperty, &roaming_state_);
store->RegisterConstStringmap(kServingOperatorProperty, &serving_operator_);
store->RegisterConstString(kUsageURLProperty, &usage_url_);
store->RegisterString(kCellularPPPUsernameProperty, &ppp_username_);
store->RegisterWriteOnlyString(kCellularPPPPasswordProperty, &ppp_password_);
set_friendly_name(cellular_->CreateDefaultFriendlyServiceName());
string service_id;
if (!device->home_provider_info()->uuid().empty()) {
service_id = device->home_provider_info()->uuid();
} else if (!device->serving_operator_info()->uuid().empty()) {
service_id = device->serving_operator_info()->uuid();
} else if (!device->sim_identifier().empty()) {
service_id = device->sim_identifier();
} else if (!device->meid().empty()) {
service_id = device->meid();
} else {
service_id = friendly_name();
}
storage_identifier_ = SanitizeStorageIdentifier(
base::StringPrintf("%s_%s_%s",
kTypeCellular,
device->GetEquipmentIdentifier().c_str(),
service_id.c_str()));
// Assume we are not performing any out-of-credits detection.
// The capability can reinitialize with the appropriate type later.
InitOutOfCreditsDetection(OutOfCreditsDetector::OOCTypeNone);
}
CellularService::~CellularService() { }
bool CellularService::IsAutoConnectable(const char** reason) const {
if (!cellular_->running()) {
*reason = kAutoConnDeviceDisabled;
return false;
}
if (cellular_->IsActivating()) {
*reason = kAutoConnActivating;
return false;
}
if (failure() == kFailurePPPAuth) {
*reason = kAutoConnBadPPPCredentials;
return false;
}
if (out_of_credits_detector_->IsDetecting()) {
*reason = kAutoConnOutOfCreditsDetectionInProgress;
return false;
}
if (out_of_credits_detector_->out_of_credits()) {
*reason = kAutoConnOutOfCredits;
return false;
}
return Service::IsAutoConnectable(reason);
}
uint64_t CellularService::GetMaxAutoConnectCooldownTimeMilliseconds() const {
return 30 * 60 * 1000; // 30 minutes
}
void CellularService::HelpRegisterDerivedString(
const string& name,
string(CellularService::*get)(Error* error),
bool(CellularService::*set)(const string& value, Error* error)) {
mutable_store()->RegisterDerivedString(
name,
StringAccessor(
new CustomAccessor<CellularService, string>(this, get, set)));
}
void CellularService::HelpRegisterDerivedStringmap(
const string& name,
Stringmap(CellularService::*get)(Error* error),
bool(CellularService::*set)(
const Stringmap& value, Error* error)) {
mutable_store()->RegisterDerivedStringmap(
name,
StringmapAccessor(
new CustomAccessor<CellularService, Stringmap>(this, get, set)));
}
void CellularService::HelpRegisterDerivedBool(
const string& name,
bool(CellularService::*get)(Error* error),
bool(CellularService::*set)(const bool&, Error*)) {
mutable_store()->RegisterDerivedBool(
name,
BoolAccessor(new CustomAccessor<CellularService, bool>(this, get, set)));
}
Stringmap* CellularService::GetUserSpecifiedApn() {
Stringmap::iterator it = apn_info_.find(kApnProperty);
if (it == apn_info_.end() || it->second.empty())
return nullptr;
return &apn_info_;
}
Stringmap* CellularService::GetLastGoodApn() {
Stringmap::iterator it = last_good_apn_info_.find(kApnProperty);
if (it == last_good_apn_info_.end() || it->second.empty())
return nullptr;
return &last_good_apn_info_;
}
string CellularService::CalculateActivationType(Error* error) {
return GetActivationTypeString();
}
Stringmap CellularService::GetApn(Error* /*error*/) {
return apn_info_;
}
bool CellularService::SetApn(const Stringmap& value, Error* error) {
// Only copy in the fields we care about, and validate the contents.
// If the "apn" field is missing or empty, the APN is cleared.
string str;
Stringmap new_apn_info;
if (GetNonEmptyField(value, kApnProperty, &str)) {
new_apn_info[kApnProperty] = str;
if (GetNonEmptyField(value, kApnUsernameProperty, &str))
new_apn_info[kApnUsernameProperty] = str;
if (GetNonEmptyField(value, kApnPasswordProperty, &str))
new_apn_info[kApnPasswordProperty] = str;
}
if (apn_info_ == new_apn_info) {
return false;
}
apn_info_ = new_apn_info;
adaptor()->EmitStringmapChanged(kCellularApnProperty, apn_info_);
return true;
}
void CellularService::SetLastGoodApn(const Stringmap& apn_info) {
last_good_apn_info_ = apn_info;
adaptor()->EmitStringmapChanged(kCellularLastGoodApnProperty,
last_good_apn_info_);
}
void CellularService::ClearLastGoodApn() {
last_good_apn_info_.clear();
adaptor()->EmitStringmapChanged(kCellularLastGoodApnProperty,
last_good_apn_info_);
}
void CellularService::InitOutOfCreditsDetection(
OutOfCreditsDetector::OOCType ooc_type) {
out_of_credits_detector_ =
OutOfCreditsDetector::CreateDetector(ooc_type, this);
}
bool CellularService::Load(StoreInterface* storage) {
// The initial storage identifier contains the MAC address of the cellular
// device. However, the MAC address of a cellular device may not be constant
// (e.g. the kernel driver may pick a random MAC address for a modem when the
// driver can't obtain that information from the modem). As a remedy, we
// first try to locate a profile with other service related properties (IMSI,
// MEID, etc).
string id = GetLoadableStorageIdentifier(*storage);
if (id.empty()) {
// The default storage identifier is still used for backward compatibility
// as an older profile doesn't have other service related properties
// stored.
//
// TODO(benchan): We can probably later switch to match profiles solely
// based on service properties, instead of storage identifier.
id = GetStorageIdentifier();
SLOG(this, 2) << __func__ << ": No service with matching properties found; "
"try storage identifier instead";
if (!storage->ContainsGroup(id)) {
LOG(WARNING) << "Service is not available in the persistent store: "
<< id;
return false;
}
} else {
SLOG(this, 2) << __func__
<< ": Service with matching properties found: " << id;
// Set our storage identifier to match the storage name in the Profile.
storage_identifier_ = id;
}
// Load properties common to all Services.
if (!Service::Load(storage))
return false;
LoadApn(storage, id, kStorageAPN, &apn_info_);
LoadApn(storage, id, kStorageLastGoodAPN, &last_good_apn_info_);
const string old_username = ppp_username_;
const string old_password = ppp_password_;
storage->GetString(id, kStoragePPPUsername, &ppp_username_);
storage->GetString(id, kStoragePPPPassword, &ppp_password_);
if (IsFailed() && failure() == kFailurePPPAuth &&
(old_username != ppp_username_ || old_password != ppp_password_)) {
SetState(kStateIdle);
}
return true;
}
void CellularService::LoadApn(StoreInterface* storage,
const string& storage_group,
const string& keytag,
Stringmap* apn_info) {
if (!LoadApnField(storage, storage_group, keytag, kApnProperty, apn_info))
return;
LoadApnField(storage, storage_group, keytag, kApnUsernameProperty, apn_info);
LoadApnField(storage, storage_group, keytag, kApnPasswordProperty, apn_info);
}
bool CellularService::LoadApnField(StoreInterface* storage,
const string& storage_group,
const string& keytag,
const string& apntag,
Stringmap* apn_info) {
string value;
if (storage->GetString(storage_group, keytag + "." + apntag, &value) &&
!value.empty()) {
(*apn_info)[apntag] = value;
return true;
}
return false;
}
bool CellularService::Save(StoreInterface* storage) {
// Save properties common to all Services.
if (!Service::Save(storage))
return false;
const string id = GetStorageIdentifier();
SaveApn(storage, id, GetUserSpecifiedApn(), kStorageAPN);
SaveApn(storage, id, GetLastGoodApn(), kStorageLastGoodAPN);
SaveString(
storage, id, kStorageIccid, cellular_->sim_identifier(), false, true);
SaveString(storage, id, kStorageImei, cellular_->imei(), false, true);
SaveString(storage, id, kStorageImsi, cellular_->imsi(), false, true);
SaveString(storage, id, kStorageMeid, cellular_->meid(), false, true);
SaveString(storage, id, kStoragePPPUsername, ppp_username_, false, true);
SaveString(storage, id, kStoragePPPPassword, ppp_password_, false, true);
return true;
}
void CellularService::SaveApn(StoreInterface* storage,
const string& storage_group,
const Stringmap* apn_info,
const string& keytag) {
SaveApnField(storage, storage_group, apn_info, keytag, kApnProperty);
SaveApnField(storage, storage_group, apn_info, keytag, kApnUsernameProperty);
SaveApnField(storage, storage_group, apn_info, keytag, kApnPasswordProperty);
}
void CellularService::SaveApnField(StoreInterface* storage,
const string& storage_group,
const Stringmap* apn_info,
const string& keytag,
const string& apntag) {
const string key = keytag + "." + apntag;
string str;
if (apn_info && GetNonEmptyField(*apn_info, apntag, &str))
storage->SetString(storage_group, key, str);
else
storage->DeleteKey(storage_group, key);
}
bool CellularService::IsOutOfCredits(Error* /*error*/) {
return out_of_credits_detector_->out_of_credits();
}
void CellularService::SignalOutOfCreditsChanged(bool state) const {
adaptor()->EmitBoolChanged(kOutOfCreditsProperty, state);
}
void CellularService::AutoConnect() {
is_auto_connecting_ = true;
Service::AutoConnect();
is_auto_connecting_ = false;
}
void CellularService::Connect(Error* error, const char* reason) {
Service::Connect(error, reason);
cellular_->Connect(error);
if (error->IsFailure())
out_of_credits_detector_->ResetDetector();
}
void CellularService::Disconnect(Error* error, const char* reason) {
Service::Disconnect(error, reason);
cellular_->Disconnect(error, reason);
}
void CellularService::ActivateCellularModem(const string& carrier,
Error* error,
const ResultCallback& callback) {
cellular_->Activate(carrier, error, callback);
}
void CellularService::CompleteCellularActivation(Error* error) {
cellular_->CompleteActivation(error);
}
void CellularService::SetState(ConnectState new_state) {
out_of_credits_detector_->NotifyServiceStateChanged(state(), new_state);
Service::SetState(new_state);
}
string CellularService::GetStorageIdentifier() const {
return storage_identifier_;
}
string CellularService::GetDeviceRpcId(Error* /*error*/) const {
return cellular_->GetRpcIdentifier();
}
set<string> CellularService::GetStorageGroupsWithProperty(
const StoreInterface& storage,
const std::string& key,
const std::string& value) const {
KeyValueStore properties;
properties.SetString(kStorageType, kTypeCellular);
properties.SetString(key, value);
return storage.GetGroupsWithProperties(properties);
}
string CellularService::GetLoadableStorageIdentifier(
const StoreInterface& storage) const {
// We try the following service related identifiers in order:
// - IMSI
// - MEID
//
// TODO(benchan): IMSI / MEID is associated with the subscriber but not
// necessarily with the currently registered network. In case of roaming and
// MVNO, we may need to consider the home provider or serving operator UUID,
// which requires further investigations.
set<string> groups;
if (!cellular_->imsi().empty()) {
groups =
GetStorageGroupsWithProperty(storage, kStorageImsi, cellular_->imsi());
}
if (groups.empty() && !cellular_->meid().empty()) {
groups =
GetStorageGroupsWithProperty(storage, kStorageMeid, cellular_->meid());
}
if (groups.empty()) {
LOG(WARNING) << "Configuration for service " << unique_name()
<< " is not available in the persistent store";
return std::string();
}
if (groups.size() > 1) {
LOG(WARNING) << "More than one configuration for service " << unique_name()
<< " is available; choosing the first.";
}
return *groups.begin();
}
bool CellularService::IsLoadableFrom(const StoreInterface& storage) const {
// TODO(benchan): Remove `|| Service::IsLoadableFrom(storage)` once we no
// longer locate a profile based on storage identifier.
return !GetLoadableStorageIdentifier(storage).empty() ||
Service::IsLoadableFrom(storage);
}
void CellularService::SetActivationType(ActivationType type) {
if (type == activation_type_) {
return;
}
activation_type_ = type;
adaptor()->EmitStringChanged(kActivationTypeProperty,
GetActivationTypeString());
}
string CellularService::GetActivationTypeString() const {
switch (activation_type_) {
case kActivationTypeNonCellular:
return shill::kActivationTypeNonCellular;
case kActivationTypeOMADM:
return shill::kActivationTypeOMADM;
case kActivationTypeOTA:
return shill::kActivationTypeOTA;
case kActivationTypeOTASP:
return shill::kActivationTypeOTASP;
case kActivationTypeUnknown:
return "";
default:
NOTREACHED();
return ""; // Make compiler happy.
}
}
void CellularService::SetActivationState(const string& state) {
if (state == activation_state_) {
return;
}
activation_state_ = state;
adaptor()->EmitStringChanged(kActivationStateProperty, state);
SetConnectableFull(state != kActivationStateNotActivated);
}
void CellularService::SetOLP(const string& url,
const string& method,
const string& post_data) {
Stringmap olp;
olp[kPaymentPortalURL] = url;
olp[kPaymentPortalMethod] = method;
olp[kPaymentPortalPostData] = post_data;
if (olp_ == olp) {
return;
}
olp_ = olp;
adaptor()->EmitStringmapChanged(kPaymentPortalProperty, olp);
}
void CellularService::SetUsageURL(const string& url) {
if (url == usage_url_) {
return;
}
usage_url_ = url;
adaptor()->EmitStringChanged(kUsageURLProperty, url);
}
void CellularService::SetNetworkTechnology(const string& technology) {
if (technology == network_technology_) {
return;
}
network_technology_ = technology;
adaptor()->EmitStringChanged(kNetworkTechnologyProperty,
technology);
}
void CellularService::SetRoamingState(const string& state) {
if (state == roaming_state_) {
return;
}
roaming_state_ = state;
adaptor()->EmitStringChanged(kRoamingStateProperty, state);
}
void CellularService::set_serving_operator(const Stringmap& serving_operator) {
if (serving_operator_ == serving_operator)
return;
serving_operator_ = serving_operator;
adaptor()->EmitStringmapChanged(kServingOperatorProperty, serving_operator_);
}
} // namespace shill