| // 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 |