blob: 03b22c1368911a692718be56e5563c4f82531bcd [file] [log] [blame]
// Copyright 2020 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_service_provider.h"
#include <memory>
#include <set>
#include <gtest/gtest.h>
#include "shill/cellular/cellular.h"
#include "shill/cellular/cellular_capability_3gpp.h"
#include "shill/cellular/mock_modem_info.h"
#include "shill/dbus/dbus_properties_proxy.h"
#include "shill/dbus/fake_properties_proxy.h"
#include "shill/fake_store.h"
#include "shill/mock_control.h"
#include "shill/mock_device_info.h"
#include "shill/mock_manager.h"
#include "shill/mock_metrics.h"
#include "shill/mock_profile.h"
#include "shill/test_event_dispatcher.h"
using testing::NiceMock;
using testing::Return;
namespace shill {
namespace {
const char kTestDeviceName[] = "usb0";
const char kTestDeviceAddress[] = "000102030405";
const int kTestInterfaceIndex = 1;
const char kDBusService[] = "org.freedesktop.ModemManager1";
const RpcIdentifier kDBusPath("/org/freedesktop/ModemManager1/Modem/0");
// EID must be 32 chars
const char kEid1[] = "eid1_678901234567890123456789012";
const char kEid2[] = "eid2_678901234567890123456789012";
} // namespace
class CellularServiceProviderTest : public testing::Test {
public:
CellularServiceProviderTest()
: manager_(&control_, &dispatcher_, &metrics_),
modem_info_(&control_, &manager_),
device_info_(&manager_) {}
~CellularServiceProviderTest() override = default;
void SetUp() override {
EXPECT_CALL(manager_, modem_info()).WillRepeatedly(Return(&modem_info_));
provider_ = std::make_unique<CellularServiceProvider>(&manager_);
provider_->Start();
profile_ = new NiceMock<MockProfile>(&manager_);
provider_->set_profile_for_testing(profile_);
EXPECT_CALL(*profile_, GetConstStorage()).WillRepeatedly(Return(&storage_));
EXPECT_CALL(*profile_, GetStorage()).WillRepeatedly(Return(&storage_));
EXPECT_CALL(manager_, cellular_service_provider())
.WillRepeatedly(Return(provider_.get()));
}
void TearDown() override {
provider_->Stop();
provider_.reset();
CHECK(profile_->HasOneRef());
profile_ = nullptr;
}
// TODO(b/154014577): Provide eID for identifying sim cards once supported.
CellularRefPtr CreateDevice(const std::string& imsi,
const std::string& iccid) {
CellularRefPtr cellular = new Cellular(
&manager_, kTestDeviceName, kTestDeviceAddress, kTestInterfaceIndex,
Cellular::kType3gpp, kDBusService, kDBusPath);
if (!iccid.empty()) {
Cellular::SimProperties sim_properties;
sim_properties.iccid = iccid;
sim_properties.imsi = imsi;
cellular->SetPrimarySimProperties(sim_properties);
}
return cellular;
}
CellularRefPtr CreateDeviceWithEid(const std::string& imsi,
const std::string& iccid,
const std::string& eid) {
CellularRefPtr cellular = CreateDevice(imsi, iccid);
cellular->set_eid_for_testing(eid);
return cellular;
}
// TODO(b/154014577): Provide eID once supported.
void SetupCellularStore(const std::string& identifier,
const std::string& imsi,
const std::string& iccid,
const std::string& sim_card_id) {
storage_.SetString(identifier, kTypeProperty, kTypeCellular);
storage_.SetString(identifier, CellularService::kStorageImsi, imsi);
storage_.SetString(identifier, CellularService::kStorageIccid, iccid);
storage_.SetString(identifier, CellularService::kStorageSimCardId,
sim_card_id);
}
void StoreCellularProperty(const std::string& identifier,
const std::string& key,
const std::string& value) {
storage_.SetString(identifier, key, value);
}
std::set<std::string> GetStorageGroups() { return storage_.GetGroups(); }
const std::vector<CellularServiceRefPtr>& GetProviderServices() const {
return provider_->services_;
}
CellularServiceProvider* provider() { return provider_.get(); }
private:
EventDispatcherForTest dispatcher_;
NiceMock<MockControl> control_;
NiceMock<MockMetrics> metrics_;
NiceMock<MockManager> manager_;
MockModemInfo modem_info_;
NiceMock<MockDeviceInfo> device_info_;
FakeStore storage_;
scoped_refptr<NiceMock<MockProfile>> profile_;
std::unique_ptr<CellularServiceProvider> provider_;
};
TEST_F(CellularServiceProviderTest, LoadService) {
CellularRefPtr device = CreateDevice("imsi1", "iccid1");
CellularServiceRefPtr service =
provider()->LoadServicesForDevice(device.get());
ASSERT_TRUE(service);
EXPECT_EQ(1u, GetProviderServices().size());
EXPECT_EQ("imsi1", service->imsi());
EXPECT_EQ("iccid1", service->iccid());
EXPECT_EQ("", service->eid());
EXPECT_TRUE(service->IsVisible());
EXPECT_TRUE(service->connectable());
// Stopping should remove all services.
provider()->Stop();
EXPECT_EQ(0u, GetProviderServices().size());
}
TEST_F(CellularServiceProviderTest, RemoveServices) {
CellularRefPtr device = CreateDevice("imsi1", "iccid1");
CellularServiceRefPtr service =
provider()->LoadServicesForDevice(device.get());
ASSERT_TRUE(service);
EXPECT_EQ(1u, GetProviderServices().size());
provider()->RemoveServices();
EXPECT_EQ(0u, GetProviderServices().size());
}
TEST_F(CellularServiceProviderTest, LoadServiceFromProfile) {
CellularRefPtr device = CreateDevice("imsi1", "iccid1");
std::string identifier = device->GetStorageIdentifier();
// Add an entry in the storage with a saved property (ppp_username).
SetupCellularStore(identifier, "imsi1", "iccid1", "iccid1");
StoreCellularProperty(identifier, CellularService::kStoragePPPUsername,
"user1");
// Ensure that the service is loaded from storage.
CellularServiceRefPtr service =
provider()->LoadServicesForDevice(device.get());
ASSERT_TRUE(service);
EXPECT_EQ("imsi1", service->imsi());
EXPECT_EQ("iccid1", service->iccid());
EXPECT_EQ("user1", service->ppp_username());
}
TEST_F(CellularServiceProviderTest, LoadMultipleServicesFromProfile) {
// Set up two cellular services with the same SIM Card Id.
SetupCellularStore("cellular_1a", "imsi1a", "iccid1a", kEid1);
SetupCellularStore("cellular_1b", "imsi1b", "iccid1b", kEid1);
// Set up a third cellular service with a different SIM Card Id.
SetupCellularStore("cellular_2", "imsi2", "iccid2", kEid2);
CellularRefPtr device = CreateDeviceWithEid("imsi1a", "iccid1a", kEid1);
CellularServiceRefPtr service =
provider()->LoadServicesForDevice(device.get());
ASSERT_TRUE(service);
// cellular_1a should be returned.
EXPECT_EQ("imsi1a", service->imsi());
EXPECT_EQ("iccid1a", service->iccid());
// Both cellular_1a and cellular_1b services should be created.
const std::vector<CellularServiceRefPtr>& provider_services =
GetProviderServices();
ASSERT_EQ(2u, provider_services.size());
CellularServiceRefPtr service1a = provider_services[0];
EXPECT_EQ("iccid1a", service1a->iccid());
EXPECT_TRUE(service1a->connectable());
CellularServiceRefPtr service1b = provider_services[1];
EXPECT_EQ("iccid1b", service1b->iccid());
EXPECT_FALSE(service1b->connectable());
}
// When a SIM is switched (e.g. after a hotswap), LoadServicesForDevice will be
// called with a different primary ICCID. This should create a new Service, and
// destroy the old Service when RemoveNonDeviceServices is called.
TEST_F(CellularServiceProviderTest, SwitchDeviceIccid) {
CellularRefPtr device = CreateDevice("imsi1", "iccid1");
CellularServiceRefPtr service =
provider()->LoadServicesForDevice(device.get());
ASSERT_TRUE(service);
EXPECT_EQ("iccid1", service->iccid());
EXPECT_EQ(1u, GetProviderServices().size());
unsigned int serial_number1 = service->serial_number();
// Adding a device with a new ICCID should create a new service with a
// different serial number.
Cellular::SimProperties sim_properties;
sim_properties.iccid = "iccid2";
sim_properties.imsi = "imsi2";
std::vector<Cellular::SimProperties> slot_properties;
slot_properties.push_back(sim_properties);
device->SetSimProperties(slot_properties, 0u);
service = provider()->LoadServicesForDevice(device.get());
ASSERT_TRUE(service);
EXPECT_EQ("iccid2", service->iccid());
provider()->RemoveNonDeviceServices(device.get());
EXPECT_EQ(1u, GetProviderServices().size());
EXPECT_NE(serial_number1, service->serial_number());
// Stopping should remove all services.
provider()->Stop();
EXPECT_EQ(0u, GetProviderServices().size());
}
// When the active SIM slot is switched, UpdateServices() should update
// the State and Strength properties of the inactive Service.
TEST_F(CellularServiceProviderTest, SwitchSimSlot) {
CellularRefPtr cellular = CreateDevice("", "");
// Set the Cellular State to Enabled so that UpdateServices() behaves as
// expected. This requires creating a DBusPropertiesProxy for the Capability.
static_cast<CellularCapability3gpp*>(cellular->capability_for_testing())
->SetDBusPropertiesProxyForTesting(
DBusPropertiesProxy::CreateDBusPropertiesProxyForTesting(
std::make_unique<FakePropertiesProxy>()));
cellular->set_state_for_testing(Cellular::State::kEnabled);
Cellular::SimProperties sim1_properties;
sim1_properties.iccid = "iccid1";
sim1_properties.imsi = "imsi1";
Cellular::SimProperties sim2_properties;
sim2_properties.eid = kEid1;
sim2_properties.iccid = "iccid2";
sim2_properties.imsi = "imsi2";
std::vector<Cellular::SimProperties> slot_properties;
slot_properties.push_back(sim1_properties);
slot_properties.push_back(sim2_properties);
cellular->SetSimProperties(slot_properties, /*primary=*/0);
CellularServiceRefPtr service1 =
provider()->LoadServicesForDevice(cellular.get());
ASSERT_TRUE(service1);
EXPECT_EQ("iccid1", service1->iccid());
// Set the Service to connected with a non 0 signal strength.
service1->SetConnectable(true);
service1->SetState(Service::kStateConnected);
service1->SetStrength(50);
// Setting the other SIM to primary should clear the |service1| properties
// associated with being connected.
cellular->SetSimProperties(slot_properties, /*primary=*/1);
EXPECT_EQ("iccid2", cellular->iccid());
CellularServiceRefPtr service2 =
provider()->LoadServicesForDevice(cellular.get());
ASSERT_TRUE(service2);
EXPECT_EQ("iccid2", service2->iccid());
provider()->UpdateServices(cellular.get());
// |service1| is still connectable since it is an available SIM.
EXPECT_TRUE(service1->connectable());
// |service1| State is set to Idle and Strength is set to 0.
EXPECT_EQ(Service::kStateIdle, service1->state());
EXPECT_EQ(0u, service1->strength());
provider()->Stop();
cellular->SetServiceForTesting(nullptr);
service1->SetDevice(nullptr);
service2->SetDevice(nullptr);
ASSERT_TRUE(cellular->HasOneRef());
cellular = nullptr;
}
TEST_F(CellularServiceProviderTest, RemoveObsoleteServiceFromProfile) {
CellularRefPtr device = CreateDevice("imsi1", "iccid1");
std::string identifier = device->GetStorageIdentifier();
// Add two entries in the storage with the same ICCID, one with an empty IMSI.
// Set a property on both.
SetupCellularStore(identifier, "", "iccid1", "iccid1");
StoreCellularProperty(identifier, CellularService::kStoragePPPUsername,
"user1");
SetupCellularStore(identifier, "imsi1", "iccid1", "iccid1");
StoreCellularProperty(identifier, CellularService::kStoragePPPUsername,
"user2");
// Ensure that the service with a non empty imsi loaded from storage.
CellularServiceRefPtr service =
provider()->LoadServicesForDevice(device.get());
provider()->RemoveNonDeviceServices(device.get());
ASSERT_TRUE(service);
EXPECT_EQ("imsi1", service->imsi());
EXPECT_EQ("iccid1", service->iccid());
EXPECT_EQ("user2", service->ppp_username());
// Only one provider service should exist.
EXPECT_EQ(1u, GetProviderServices().size());
}
TEST_F(CellularServiceProviderTest, OnServiceUnloaded) {
CellularRefPtr device = CreateDeviceWithEid("imsi1", "iccid1", kEid1);
std::string identifier = device->GetStorageIdentifier();
SetupCellularStore(identifier, "imsi1", "iccid1", kEid1);
SetupCellularStore(identifier, "imsi2", "iccid2", kEid1);
provider()->LoadServicesForSecondarySim(kEid1, "iccid1", "imsi1",
device.get());
const std::vector<CellularServiceRefPtr>& services = GetProviderServices();
EXPECT_EQ(2u, services.size());
for (const auto& service : services) {
if (service->iccid() == "iccid2") {
service->Unload();
break;
}
}
EXPECT_EQ(1u, GetProviderServices().size());
}
} // namespace shill