blob: efc163ef584fb3c4bb60cefa69c68cb5622ffd1a [file] [log] [blame]
// Copyright 2018 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/cellular_capability_gsm.h"
#include <base/bind.h>
#include <chromeos/dbus/service_constants.h>
#include <mm/mm-modem.h>
#include "shill/cellular/cellular.h"
#include "shill/cellular/cellular_service.h"
#include "shill/cellular/mock_modem_cdma_proxy.h"
#include "shill/cellular/mock_modem_gobi_proxy.h"
#include "shill/cellular/mock_modem_gsm_card_proxy.h"
#include "shill/cellular/mock_modem_gsm_network_proxy.h"
#include "shill/cellular/mock_modem_info.h"
#include "shill/cellular/mock_modem_proxy.h"
#include "shill/cellular/mock_modem_simple_proxy.h"
#include "shill/error.h"
#include "shill/mock_adaptors.h"
#include "shill/mock_control.h"
#include "shill/mock_profile.h"
#include "shill/net/mock_rtnl_handler.h"
#include "shill/test_event_dispatcher.h"
#include "shill/testing.h"
using base::Bind;
using base::Unretained;
using std::string;
using testing::InSequence;
using testing::NiceMock;
using testing::_;
namespace shill {
class CellularCapabilityClassicTest
: public testing::TestWithParam<Cellular::Type> {
public:
CellularCapabilityClassicTest()
: control_interface_(this),
modem_info_(&control_interface_, &dispatcher_, nullptr, nullptr),
create_gsm_card_proxy_from_factory_(false),
proxy_(new MockModemProxy()),
simple_proxy_(new MockModemSimpleProxy()),
cdma_proxy_(new MockModemCdmaProxy()),
gsm_card_proxy_(new MockModemGsmCardProxy()),
gsm_network_proxy_(new MockModemGsmNetworkProxy()),
gobi_proxy_(new MockModemGobiProxy()),
capability_(nullptr),
device_adaptor_(nullptr),
cellular_(new Cellular(&modem_info_,
"",
"",
0,
GetParam(),
"",
"")) {
modem_info_.metrics()->RegisterDevice(cellular_->interface_index(),
Technology::kCellular);
}
~CellularCapabilityClassicTest() override {
cellular_->service_ = nullptr;
capability_ = nullptr;
device_adaptor_ = nullptr;
}
void SetUp() override {
static_cast<Device*>(cellular_.get())->rtnl_handler_ = &rtnl_handler_;
capability_ = static_cast<CellularCapabilityClassic*>(
cellular_->capability_.get());
device_adaptor_ =
static_cast<DeviceMockAdaptor*>(cellular_->adaptor());
ASSERT_NE(nullptr, device_adaptor_);;
}
// TODO(benchan): Instead of conditionally enabling many tests for specific
// capability types via IsCellularTypeUnderTestOneOf, migrate more tests to
// work under all capability types and proboably migrate those tests for
// specific capability types into their own test fixture subclasses.
bool IsCellularTypeUnderTestOneOf(
const std::set<Cellular::Type>& valid_types) const {
return base::ContainsKey(valid_types, GetParam());
}
void CreateService() {
// The following constants are never directly accessed by the tests.
const char kFriendlyServiceName[] = "default_test_service_name";
const char kOperatorCode[] = "10010";
const char kOperatorName[] = "default_test_operator_name";
const char kOperatorCountry[] = "us";
// Simulate all the side-effects of Cellular::CreateService
auto service = new CellularService(&modem_info_, cellular_);
service->SetFriendlyName(kFriendlyServiceName);
Stringmap serving_operator;
serving_operator[kOperatorCodeKey] = kOperatorCode;
serving_operator[kOperatorNameKey] = kOperatorName;
serving_operator[kOperatorCountryKey] = kOperatorCountry;
service->set_serving_operator(serving_operator);
cellular_->set_home_provider(serving_operator);
cellular_->service_ = service;
}
CellularCapabilityGsm* GetGsmCapability() {
return static_cast<CellularCapabilityGsm*>(cellular_->capability_.get());
}
void ReleaseCapabilityProxies() {
capability_->ReleaseProxies();
}
void InvokeEnable(bool enable, Error* error,
const ResultCallback& callback, int timeout) {
callback.Run(Error());
}
void InvokeEnableFail(bool enable, Error* error,
const ResultCallback& callback, int timeout) {
callback.Run(Error(Error::kOperationFailed));
}
void InvokeDisconnect(Error* error, const ResultCallback& callback,
int timeout) {
callback.Run(Error());
}
void InvokeDisconnectFail(Error* error, const ResultCallback& callback,
int timeout) {
callback.Run(Error(Error::kOperationFailed));
}
void InvokeGetModemStatus(Error* error,
const KeyValueStoreCallback& callback,
int timeout) {
KeyValueStore props;
props.SetString("carrier", kTestCarrier);
props.SetString("unknown-property", "irrelevant-value");
callback.Run(props, Error());
}
void InvokeGetModemInfo(Error* error, const ModemInfoCallback& callback,
int timeout) {
callback.Run(kManufacturer, kModelID, kHWRev, Error());
}
void InvokeSetCarrier(const string& carrier, Error* error,
const ResultCallback& callback, int timeout) {
callback.Run(Error());
}
MOCK_METHOD1(TestCallback, void(const Error& error));
protected:
static const char kTestMobileProviderDBPath[];
static const char kTestCarrier[];
static const char kManufacturer[];
static const char kModelID[];
static const char kHWRev[];
class TestControl : public MockControl {
public:
explicit TestControl(CellularCapabilityClassicTest* test) : test_(test) {}
std::unique_ptr<ModemProxyInterface> CreateModemProxy(
const string& /*path*/,
const string& /*service*/) override {
return std::move(test_->proxy_);
}
std::unique_ptr<ModemSimpleProxyInterface> CreateModemSimpleProxy(
const string& /*path*/,
const string& /*service*/) override {
return std::move(test_->simple_proxy_);
}
std::unique_ptr<ModemCdmaProxyInterface> CreateModemCdmaProxy(
const string& /*path*/,
const string& /*service*/) override {
return std::move(test_->cdma_proxy_);
}
std::unique_ptr<ModemGsmCardProxyInterface> CreateModemGsmCardProxy(
const string& /*path*/,
const string& /*service*/) override {
// TODO(benchan): This code conditionally returns a nullptr to avoid
// CellularCapabilityGsm::InitProperties (and thus
// CellularCapabilityGsm::GetIMSI) from being called during the
// construction. Remove this workaround after refactoring the tests.
if (test_->create_gsm_card_proxy_from_factory_) {
return std::move(test_->gsm_card_proxy_);
}
return nullptr;
}
std::unique_ptr<ModemGsmNetworkProxyInterface> CreateModemGsmNetworkProxy(
const string& /*path*/, const string& /*service*/) override {
return std::move(test_->gsm_network_proxy_);
}
std::unique_ptr<ModemGobiProxyInterface> CreateModemGobiProxy(
const string& /*path*/,
const string& /*service*/) override {
return std::move(test_->gobi_proxy_);
}
private:
CellularCapabilityClassicTest* test_;
};
void SetProxy() {
capability_->proxy_ = std::move(proxy_);
}
void SetSimpleProxy() {
capability_->simple_proxy_ = std::move(simple_proxy_);
}
void SetGsmNetworkProxy() {
CellularCapabilityGsm* gsm_capability =
static_cast<CellularCapabilityGsm*>(cellular_->capability_.get());
gsm_capability->network_proxy_ = std::move(gsm_network_proxy_);
}
void AllowCreateGsmCardProxyFromFactory() {
create_gsm_card_proxy_from_factory_ = true;
}
EventDispatcherForTest dispatcher_;
TestControl control_interface_;
MockModemInfo modem_info_;
MockRTNLHandler rtnl_handler_;
bool create_gsm_card_proxy_from_factory_;
std::unique_ptr<MockModemProxy> proxy_;
std::unique_ptr<MockModemSimpleProxy> simple_proxy_;
std::unique_ptr<MockModemCdmaProxy> cdma_proxy_;
std::unique_ptr<MockModemGsmCardProxy> gsm_card_proxy_;
std::unique_ptr<MockModemGsmNetworkProxy> gsm_network_proxy_;
std::unique_ptr<MockModemGobiProxy> gobi_proxy_;
CellularCapabilityClassic* capability_; // Owned by |cellular_|.
DeviceMockAdaptor* device_adaptor_; // Owned by |cellular_|.
CellularRefPtr cellular_;
};
const char CellularCapabilityClassicTest::kTestMobileProviderDBPath[] =
"provider_db_unittest.bfd";
const char CellularCapabilityClassicTest::kTestCarrier[] =
"The Cellular Carrier";
const char CellularCapabilityClassicTest::kManufacturer[] = "Company";
const char CellularCapabilityClassicTest::kModelID[] = "Gobi 2000";
const char CellularCapabilityClassicTest::kHWRev[] = "A00B1234";
TEST_P(CellularCapabilityClassicTest, GetModemStatus) {
EXPECT_CALL(*simple_proxy_,
GetModemStatus(_, _, CellularCapability::kTimeoutDefault))
.WillOnce(
Invoke(this, &CellularCapabilityClassicTest::InvokeGetModemStatus));
EXPECT_CALL(*this, TestCallback(IsSuccess()));
SetSimpleProxy();
ResultCallback callback =
Bind(&CellularCapabilityClassicTest::TestCallback, Unretained(this));
capability_->GetModemStatus(callback);
EXPECT_EQ(kTestCarrier, cellular_->carrier());
}
TEST_P(CellularCapabilityClassicTest, GetModemInfo) {
if (!IsCellularTypeUnderTestOneOf({Cellular::kTypeGsm})) {
return;
}
EXPECT_CALL(*proxy_, GetModemInfo(_, _, CellularCapability::kTimeoutDefault))
.WillOnce(
Invoke(this, &CellularCapabilityClassicTest::InvokeGetModemInfo));
EXPECT_CALL(*this, TestCallback(IsSuccess()));
SetProxy();
ResultCallback callback =
Bind(&CellularCapabilityClassicTest::TestCallback, Unretained(this));
capability_->GetModemInfo(callback);
EXPECT_EQ(kManufacturer, cellular_->manufacturer());
EXPECT_EQ(kModelID, cellular_->model_id());
EXPECT_EQ(kHWRev, cellular_->hardware_revision());
}
TEST_P(CellularCapabilityClassicTest, EnableModemSucceed) {
EXPECT_CALL(*proxy_, Enable(true, _, _, CellularCapability::kTimeoutEnable))
.WillOnce(Invoke(this, &CellularCapabilityClassicTest::InvokeEnable));
EXPECT_CALL(*this, TestCallback(IsSuccess()));
ResultCallback callback =
Bind(&CellularCapabilityClassicTest::TestCallback, Unretained(this));
SetProxy();
capability_->EnableModem(callback);
}
TEST_P(CellularCapabilityClassicTest, EnableModemFail) {
EXPECT_CALL(*proxy_, Enable(true, _, _, CellularCapability::kTimeoutEnable))
.WillOnce(Invoke(this, &CellularCapabilityClassicTest::InvokeEnableFail));
EXPECT_CALL(*this, TestCallback(IsFailure()));
ResultCallback callback =
Bind(&CellularCapabilityClassicTest::TestCallback, Unretained(this));
SetProxy();
capability_->EnableModem(callback);
}
TEST_P(CellularCapabilityClassicTest, FinishEnable) {
if (!IsCellularTypeUnderTestOneOf({Cellular::kTypeGsm})) {
return;
}
EXPECT_CALL(*gsm_network_proxy_,
GetRegistrationInfo(nullptr, _,
CellularCapability::kTimeoutDefault));
EXPECT_CALL(
*gsm_network_proxy_,
GetSignalQuality(nullptr, _, CellularCapability::kTimeoutDefault));
EXPECT_CALL(*this, TestCallback(IsSuccess()));
SetGsmNetworkProxy();
capability_->FinishEnable(
Bind(&CellularCapabilityClassicTest::TestCallback, Unretained(this)));
}
TEST_P(CellularCapabilityClassicTest, UnsupportedOperation) {
Error error;
EXPECT_CALL(*this, TestCallback(IsSuccess())).Times(0);
capability_->CellularCapability::Reset(
&error,
Bind(&CellularCapabilityClassicTest::TestCallback, Unretained(this)));
EXPECT_TRUE(error.IsFailure());
EXPECT_EQ(Error::kNotSupported, error.type());
}
TEST_P(CellularCapabilityClassicTest, AllowRoaming) {
if (!IsCellularTypeUnderTestOneOf({Cellular::kTypeGsm})) {
return;
}
EXPECT_FALSE(cellular_->GetAllowRoaming(nullptr));
cellular_->SetAllowRoaming(false, nullptr);
EXPECT_FALSE(cellular_->GetAllowRoaming(nullptr));
{
InSequence seq;
EXPECT_CALL(*device_adaptor_,
EmitBoolChanged(kCellularAllowRoamingProperty, true));
EXPECT_CALL(*device_adaptor_,
EmitBoolChanged(kCellularAllowRoamingProperty, false));
}
cellular_->state_ = Cellular::kStateConnected;
static_cast<CellularCapabilityGsm*>(capability_)->registration_state_ =
MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING;
cellular_->SetAllowRoaming(true, nullptr);
EXPECT_TRUE(cellular_->GetAllowRoaming(nullptr));
EXPECT_EQ(Cellular::kStateConnected, cellular_->state_);
EXPECT_CALL(*proxy_, Disconnect(_, _, CellularCapability::kTimeoutDisconnect))
.WillOnce(Invoke(this, &CellularCapabilityClassicTest::InvokeDisconnect));
SetProxy();
cellular_->state_ = Cellular::kStateConnected;
cellular_->SetAllowRoaming(false, nullptr);
EXPECT_FALSE(cellular_->GetAllowRoaming(nullptr));
EXPECT_EQ(Cellular::kStateRegistered, cellular_->state_);
}
TEST_P(CellularCapabilityClassicTest, SetCarrier) {
static const char kCarrier[] = "Generic UMTS";
EXPECT_CALL(
*gobi_proxy_,
SetCarrier(kCarrier, _, _,
CellularCapabilityClassic::kTimeoutSetCarrierMilliseconds))
.WillOnce(Invoke(this, &CellularCapabilityClassicTest::InvokeSetCarrier));
EXPECT_CALL(*this, TestCallback(IsSuccess()));
Error error;
capability_->SetCarrier(kCarrier, &error,
Bind(&CellularCapabilityClassicTest::TestCallback,
Unretained(this)));
EXPECT_TRUE(error.IsSuccess());
}
MATCHER_P(HasApn, apn, "") {
return arg.ContainsString(kApnProperty) && apn == arg.GetString(kApnProperty);
}
MATCHER(HasNoApn, "") {
return !arg.ContainsString(kApnProperty);
}
TEST_P(CellularCapabilityClassicTest, TryApns) {
if (!IsCellularTypeUnderTestOneOf({Cellular::kTypeGsm})) {
return;
}
static const string kLastGoodApn("remembered.apn");
static const string kLastGoodUsername("remembered.user");
static const string kSuppliedApn("my.apn");
static const string kTmobileApn1("epc.tmobile.com");
static const string kTmobileApn2("wap.voicestream.com");
static const string kTmobileApn3("internet2.voicestream.com");
static const string kTmobileApn4("internet3.voicestream.com");
const Stringmaps kDatabaseApnList {{{ kApnProperty, kTmobileApn1 }},
{{ kApnProperty, kTmobileApn2 }},
{{ kApnProperty, kTmobileApn3 }},
{{ kApnProperty, kTmobileApn4 }}};
CreateService();
// Supply the database APNs to |cellular_| object.
cellular_->set_apn_list(kDatabaseApnList);
ProfileRefPtr profile(new NiceMock<MockProfile>(
modem_info_.control_interface(), modem_info_.metrics(),
modem_info_.manager()));
cellular_->service()->set_profile(profile);
Error error;
Stringmap apn_info;
KeyValueStore props;
CellularCapabilityGsm* gsm_capability = GetGsmCapability();
apn_info[kApnProperty] = kLastGoodApn;
apn_info[kApnUsernameProperty] = kLastGoodUsername;
cellular_->service()->SetLastGoodApn(apn_info);
props.Clear();
EXPECT_TRUE(props.IsEmpty());
gsm_capability->SetupConnectProperties(&props);
// We expect the list to contain the last good APN, plus
// the 4 APNs from the mobile provider info database.
EXPECT_EQ(5, gsm_capability->apn_try_list_.size());
EXPECT_TRUE(props.ContainsString(kApnProperty));
EXPECT_EQ(kLastGoodApn, props.GetString(kApnProperty));
EXPECT_TRUE(props.ContainsString(kApnUsernameProperty));
EXPECT_EQ(kLastGoodUsername,
props.GetString(kApnUsernameProperty));
apn_info.clear();
props.Clear();
apn_info[kApnProperty] = kSuppliedApn;
// After setting the APN, the last good APN is perserved. We expect the try
// list to contain the user-supplied APN, the last good APN, and the 4 APNs
// from the mobile provider info database. The user-supplied APN will be
// selected before the last good APN.
cellular_->service()->SetApn(apn_info, &error);
EXPECT_TRUE(props.IsEmpty());
gsm_capability->SetupConnectProperties(&props);
EXPECT_EQ(6, gsm_capability->apn_try_list_.size());
EXPECT_TRUE(props.ContainsString(kApnProperty));
EXPECT_EQ(kSuppliedApn, props.GetString(kApnProperty));
Stringmap* last_good_apn = cellular_->service()->GetLastGoodApn();
ASSERT_NE(nullptr, last_good_apn);
EXPECT_EQ(kLastGoodApn, (*last_good_apn)[kApnProperty]);
EXPECT_EQ(kLastGoodUsername, (*last_good_apn)[kApnUsernameProperty]);
// Now try all the given APNs.
using testing::InSequence;
{
InSequence dummy;
EXPECT_CALL(*simple_proxy_, Connect(HasApn(kSuppliedApn), _, _, _));
EXPECT_CALL(*simple_proxy_, Connect(HasApn(kLastGoodApn), _, _, _));
EXPECT_CALL(*simple_proxy_, Connect(HasApn(kTmobileApn1), _, _, _));
EXPECT_CALL(*simple_proxy_, Connect(HasApn(kTmobileApn2), _, _, _));
EXPECT_CALL(*simple_proxy_, Connect(HasApn(kTmobileApn3), _, _, _));
EXPECT_CALL(*simple_proxy_, Connect(HasApn(kTmobileApn4), _, _, _));
EXPECT_CALL(*simple_proxy_, Connect(HasNoApn(), _, _, _));
}
SetSimpleProxy();
gsm_capability->Connect(props, &error, ResultCallback());
Error cerror(Error::kInvalidApn);
gsm_capability->OnConnectReply(ResultCallback(), cerror);
EXPECT_EQ(5, gsm_capability->apn_try_list_.size());
gsm_capability->OnConnectReply(ResultCallback(), cerror);
EXPECT_EQ(4, gsm_capability->apn_try_list_.size());
gsm_capability->OnConnectReply(ResultCallback(), cerror);
EXPECT_EQ(3, gsm_capability->apn_try_list_.size());
gsm_capability->OnConnectReply(ResultCallback(), cerror);
EXPECT_EQ(2, gsm_capability->apn_try_list_.size());
gsm_capability->OnConnectReply(ResultCallback(), cerror);
EXPECT_EQ(1, gsm_capability->apn_try_list_.size());
gsm_capability->OnConnectReply(ResultCallback(), cerror);
EXPECT_EQ(0, gsm_capability->apn_try_list_.size());
}
TEST_P(CellularCapabilityClassicTest, StopModemDisconnectSuccess) {
EXPECT_CALL(*proxy_, Disconnect(_, _, CellularCapability::kTimeoutDisconnect))
.WillOnce(Invoke(this,
&CellularCapabilityClassicTest::InvokeDisconnect));
EXPECT_CALL(*proxy_, Enable(_, _, _, CellularCapability::kTimeoutEnable))
.WillOnce(Invoke(this,
&CellularCapabilityClassicTest::InvokeEnable));
EXPECT_CALL(*this, TestCallback(IsSuccess()));
SetProxy();
Error error;
capability_->StopModem(
&error,
Bind(&CellularCapabilityClassicTest::TestCallback, Unretained(this)));
dispatcher_.DispatchPendingEvents();
}
TEST_P(CellularCapabilityClassicTest, StopModemDisconnectFail) {
EXPECT_CALL(*proxy_, Disconnect(_, _, CellularCapability::kTimeoutDisconnect))
.WillOnce(Invoke(this,
&CellularCapabilityClassicTest::InvokeDisconnectFail));
EXPECT_CALL(*proxy_, Enable(_, _, _, CellularCapability::kTimeoutEnable))
.WillOnce(Invoke(this,
&CellularCapabilityClassicTest::InvokeEnable));
EXPECT_CALL(*this, TestCallback(IsSuccess()));
SetProxy();
Error error;
capability_->StopModem(
&error,
Bind(&CellularCapabilityClassicTest::TestCallback, Unretained(this)));
dispatcher_.DispatchPendingEvents();
}
TEST_P(CellularCapabilityClassicTest, DisconnectNoProxy) {
Error error;
ResultCallback disconnect_callback;
EXPECT_CALL(*proxy_, Disconnect(_, _, CellularCapability::kTimeoutDisconnect))
.Times(0);
ReleaseCapabilityProxies();
capability_->Disconnect(&error, disconnect_callback);
}
INSTANTIATE_TEST_CASE_P(CellularCapabilityClassicTest,
CellularCapabilityClassicTest,
testing::Values(Cellular::kTypeGsm,
Cellular::kTypeCdma));
} // namespace shill