blob: 7cd274d9433b89d045dfedbbc75e9408d39add55 [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shill/cellular.h"
#include <netinet/in.h>
#include <linux/if.h> // Needs definitions from netinet/in.h
#include <string>
#include <utility>
#include <vector>
#include <base/logging.h>
#include <base/stringprintf.h>
#include <chromeos/dbus/service_constants.h>
#include <mm/mm-modem.h>
#include <mobile_provider.h>
#include "shill/adaptor_interfaces.h"
#include "shill/cellular_capability_cdma.h"
#include "shill/cellular_capability_gsm.h"
#include "shill/cellular_service.h"
#include "shill/control_interface.h"
#include "shill/device.h"
#include "shill/device_info.h"
#include "shill/error.h"
#include "shill/event_dispatcher.h"
#include "shill/manager.h"
#include "shill/profile.h"
#include "shill/property_accessor.h"
#include "shill/proxy_factory.h"
#include "shill/rtnl_handler.h"
#include "shill/technology.h"
using std::map;
using std::string;
using std::vector;
namespace shill {
Cellular::Operator::Operator() {
SetName("");
SetCode("");
SetCountry("");
}
Cellular::Operator::~Operator() {}
void Cellular::Operator::CopyFrom(const Operator &oper) {
dict_ = oper.dict_;
}
const string &Cellular::Operator::GetName() const {
return dict_.find(flimflam::kOperatorNameKey)->second;
}
void Cellular::Operator::SetName(const string &name) {
dict_[flimflam::kOperatorNameKey] = name;
}
const string &Cellular::Operator::GetCode() const {
return dict_.find(flimflam::kOperatorCodeKey)->second;
}
void Cellular::Operator::SetCode(const string &code) {
dict_[flimflam::kOperatorCodeKey] = code;
}
const string &Cellular::Operator::GetCountry() const {
return dict_.find(flimflam::kOperatorCountryKey)->second;
}
void Cellular::Operator::SetCountry(const string &country) {
dict_[flimflam::kOperatorCountryKey] = country;
}
const Stringmap &Cellular::Operator::ToDict() const {
return dict_;
}
Cellular::Cellular(ControlInterface *control_interface,
EventDispatcher *dispatcher,
Metrics *metrics,
Manager *manager,
const string &link_name,
const string &address,
int interface_index,
Type type,
const string &owner,
const string &path,
mobile_provider_db *provider_db)
: Device(control_interface,
dispatcher,
metrics,
manager,
link_name,
address,
interface_index,
Technology::kCellular),
state_(kStateDisabled),
modem_state_(kModemStateUnknown),
dbus_owner_(owner),
dbus_path_(path),
provider_db_(provider_db),
task_factory_(this) {
PropertyStore *store = this->mutable_store();
store->RegisterConstString(flimflam::kDBusConnectionProperty, &dbus_owner_);
store->RegisterConstString(flimflam::kDBusObjectProperty, &dbus_path_);
store->RegisterConstStringmap(flimflam::kHomeProviderProperty,
&home_provider_.ToDict());
// For now, only a single capability is supported.
InitCapability(type, ProxyFactory::GetInstance());
VLOG(2) << "Cellular device " << this->link_name() << " initialized.";
}
Cellular::~Cellular() {
}
// static
string Cellular::GetStateString(State state) {
switch (state) {
case kStateDisabled: return "CellularStateDisabled";
case kStateEnabled: return "CellularStateEnabled";
case kStateRegistered: return "CellularStateRegistered";
case kStateConnected: return "CellularStateConnected";
case kStateLinked: return "CellularStateLinked";
default: NOTREACHED();
}
return StringPrintf("CellularStateUnknown-%d", state);
}
void Cellular::SetState(State state) {
VLOG(2) << GetStateString(state_) << " -> " << GetStateString(state);
state_ = state;
}
void Cellular::Start() {
LOG(INFO) << __func__ << ": " << GetStateString(state_);
if (state_ != kStateDisabled) {
return;
}
Device::Start();
if (modem_state_ == kModemStateEnabled) {
// Modem already enabled.
OnModemEnabled();
return;
}
// TODO(ers): this should not be done automatically. It should
// require an explicit Enable request to start the modem.
capability_->StartModem();
}
void Cellular::Stop() {
DestroyService();
DisconnectModem();
if (state_ != kStateDisabled)
capability_->DisableModem(NULL);
capability_->StopModem();
Device::Stop();
}
void Cellular::InitCapability(Type type, ProxyFactory *proxy_factory) {
// TODO(petkov): Consider moving capability construction into a factory that's
// external to the Cellular class.
VLOG(2) << __func__ << "(" << type << ")";
switch (type) {
case kTypeGSM:
capability_.reset(new CellularCapabilityGSM(this, proxy_factory));
break;
case kTypeCDMA:
capability_.reset(new CellularCapabilityCDMA(this, proxy_factory));
break;
default: NOTREACHED();
}
}
void Cellular::OnModemEnabled() {
VLOG(2) << __func__ << ": " << GetStateString(state_);
if (state_ == kStateDisabled) {
SetState(kStateEnabled);
}
}
void Cellular::OnModemDisabled() {
VLOG(2) << __func__ << ": " << GetStateString(state_);
if (state_ != kStateDisabled) {
SetState(kStateDisabled);
}
}
void Cellular::Activate(const std::string &carrier,
ReturnerInterface *returner) {
capability_->Activate(carrier, new AsyncCallHandler(returner));
}
void Cellular::RegisterOnNetwork(const string &network_id,
ReturnerInterface *returner) {
capability_->RegisterOnNetwork(network_id, new AsyncCallHandler(returner));
}
void Cellular::RequirePIN(
const string &pin, bool require, ReturnerInterface *returner) {
VLOG(2) << __func__ << "(" << returner << ")";
capability_->RequirePIN(pin, require, new AsyncCallHandler(returner));
}
void Cellular::EnterPIN(const string &pin, ReturnerInterface *returner) {
VLOG(2) << __func__ << "(" << returner << ")";
capability_->EnterPIN(pin, new AsyncCallHandler(returner));
}
void Cellular::UnblockPIN(const string &unblock_code,
const string &pin,
ReturnerInterface *returner) {
VLOG(2) << __func__ << "(" << returner << ")";
capability_->UnblockPIN(unblock_code, pin, new AsyncCallHandler(returner));
}
void Cellular::ChangePIN(
const string &old_pin, const string &new_pin,
ReturnerInterface *returner) {
VLOG(2) << __func__ << "(" << returner << ")";
capability_->ChangePIN(old_pin, new_pin, new AsyncCallHandler(returner));
}
void Cellular::Scan(Error * /*error*/) {
// TODO(ers): for now report immediate success.
capability_->Scan(NULL);
}
void Cellular::HandleNewRegistrationState() {
dispatcher()->PostTask(
task_factory_.NewRunnableMethod(
&Cellular::HandleNewRegistrationStateTask));
}
void Cellular::HandleNewRegistrationStateTask() {
VLOG(2) << __func__;
if (!capability_->IsRegistered()) {
DestroyService();
if (state_ == kStateLinked ||
state_ == kStateConnected ||
state_ == kStateRegistered) {
SetState(kStateEnabled);
}
return;
}
if (state_ == kStateEnabled) {
SetState(kStateRegistered);
}
if (!service_.get()) {
CreateService();
}
capability_->GetSignalQuality();
if (state_ == kStateRegistered && modem_state_ == kModemStateConnected)
OnConnected();
service_->SetNetworkTechnology(capability_->GetNetworkTechnologyString());
service_->SetRoamingState(capability_->GetRoamingStateString());
}
void Cellular::HandleNewSignalQuality(uint32 strength) {
VLOG(2) << "Signal strength: " << strength;
if (service_) {
service_->SetStrength(strength);
}
}
void Cellular::CreateService() {
VLOG(2) << __func__;
CHECK(!service_.get());
service_ =
new CellularService(control_interface(), dispatcher(), metrics(),
manager(), this);
capability_->OnServiceCreated();
manager()->RegisterService(service_);
}
void Cellular::DestroyService() {
VLOG(2) << __func__;
DestroyIPConfig();
if (service_) {
manager()->DeregisterService(service_);
service_ = NULL;
}
SelectService(NULL);
}
bool Cellular::TechnologyIs(const Technology::Identifier type) const {
return type == Technology::kCellular;
}
void Cellular::Connect(Error *error) {
VLOG(2) << __func__;
if (state_ == kStateConnected ||
state_ == kStateLinked) {
Error::PopulateAndLog(error, Error::kAlreadyConnected,
"Already connected; connection request ignored.");
return;
}
CHECK_EQ(kStateRegistered, state_);
if (!capability_->allow_roaming() &&
service_->roaming_state() == flimflam::kRoamingStateRoaming) {
Error::PopulateAndLog(error, Error::kNotOnHomeNetwork,
"Roaming disallowed; connection request ignored.");
return;
}
DBusPropertiesMap properties;
capability_->SetupConnectProperties(&properties);
capability_->Connect(properties);
}
void Cellular::OnConnected() {
VLOG(2) << __func__;
SetState(kStateConnected);
EstablishLink();
}
void Cellular::OnConnectFailed() {
// TODO(ers): Signal failure.
}
void Cellular::Disconnect(Error *error) {
VLOG(2) << __func__;
if (state_ != kStateConnected &&
state_ != kStateLinked) {
Error::PopulateAndLog(
error, Error::kInProgress, "Not connected; request ignored.");
return;
}
capability_->Disconnect();
}
void Cellular::OnDisconnected() {
VLOG(2) << __func__;
SetState(kStateRegistered);
}
void Cellular::DisconnectModem() {
VLOG(2) << __func__;
if (state_ != kStateConnected &&
state_ != kStateLinked) {
return;
}
// TODO(petkov): Switch to asynchronous calls (crosbug.com/17583). Note,
// however, that this is invoked by the Stop method, so some extra refactoring
// may be needed to prevent the device instance from being destroyed before
// the modem manager calls are complete. Maybe Disconnect and Disable need to
// be handled by the parent Modem class.
capability_->Disconnect();
}
void Cellular::OnDisconnectFailed() {
// TODO(ers): Signal failure.
}
void Cellular::EstablishLink() {
VLOG(2) << __func__;
CHECK_EQ(kStateConnected, state_);
unsigned int flags = 0;
if (manager()->device_info()->GetFlags(interface_index(), &flags) &&
(flags & IFF_UP) != 0) {
LinkEvent(flags, IFF_UP);
return;
}
// TODO(petkov): Provide a timeout for a failed link-up request.
rtnl_handler()->SetInterfaceFlags(interface_index(), IFF_UP, IFF_UP);
}
void Cellular::LinkEvent(unsigned int flags, unsigned int change) {
Device::LinkEvent(flags, change);
if ((flags & IFF_UP) != 0 && state_ == kStateConnected) {
LOG(INFO) << link_name() << " is up.";
SetState(kStateLinked);
// TODO(petkov): For GSM, remember the APN.
if (AcquireIPConfig()) {
SelectService(service_);
SetServiceState(Service::kStateConfiguring);
} else {
LOG(ERROR) << "Unable to acquire DHCP config.";
}
} else if ((flags & IFF_UP) == 0 && state_ == kStateLinked) {
SetState(kStateConnected);
DestroyService();
}
}
void Cellular::OnModemManagerPropertiesChanged(
const DBusPropertiesMap &properties) {
capability_->OnModemManagerPropertiesChanged(properties);
}
void Cellular::set_home_provider(const Operator &oper) {
home_provider_.CopyFrom(oper);
}
string Cellular::CreateFriendlyServiceName() {
VLOG(2) << __func__;
return capability_.get() ? capability_->CreateFriendlyServiceName() : "";
}
} // namespace shill