| // Copyright 2022 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "hermes/modem_mbim.h" |
| |
| #include <memory> |
| |
| #include <base/logging.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "hermes/fake_euicc_manager.h" |
| #include "hermes/mock_executor.h" |
| #include "hermes/sgp_22.h" |
| |
| using ::testing::_; |
| |
| namespace { |
| constexpr int kEuiccSlot = 0; |
| constexpr int kNonEuiccSlot = 1; |
| constexpr int kChannel = 10; |
| constexpr guint8 kEidApdu[9] = {0xBF, 0x3E, 0x12, 0x5A, 0x10, |
| 0x11, 0x11, 0x11, 0x11}; |
| constexpr int kEidApduLen = sizeof(kEidApdu) / sizeof(kEidApdu[0]); |
| } // namespace |
| |
| namespace hermes { |
| |
| class MockModemManagerProxy : public ModemManagerProxyInterface { |
| public: |
| MOCK_METHOD(void, |
| RegisterModemAppearedCallback, |
| (base::OnceClosure cb), |
| (override)); |
| MOCK_METHOD(void, WaitForModem, (base::OnceClosure cb), (override)); |
| |
| MOCK_METHOD(std::string, GetMbimPort, (), (const, override)); |
| |
| MOCK_METHOD(void, ScheduleUninhibit, (base::TimeDelta timeout), (override)); |
| MOCK_METHOD(void, WaitForModemAndInhibit, (ResultCallback cb), (override)); |
| MockModemManagerProxy() { |
| ON_CALL(*this, WaitForModem).WillByDefault([](base::OnceClosure cb) { |
| std::move(cb).Run(); |
| }); |
| ON_CALL(*this, WaitForModemAndInhibit).WillByDefault([](ResultCallback cb) { |
| std::move(cb).Run(kModemSuccess); |
| }); |
| ON_CALL(*this, GetMbimPort).WillByDefault([]() { return "wwan0"; }); |
| } |
| }; |
| |
| class FakeLibmbim : public LibmbimInterface { |
| public: |
| FakeLibmbim() { |
| ON_CALL(*this, MbimMessageMsUiccLowLevelAccessOpenChannelResponseParse) |
| .WillByDefault(testing::Invoke( |
| this, |
| &FakeLibmbim:: |
| FakeMbimMessageMsUiccLowLevelAccessOpenChannelResponseParse)); |
| ON_CALL(*this, MbimMessageMsUiccLowLevelAccessCloseChannelResponseParse) |
| .WillByDefault(testing::Invoke( |
| this, |
| &FakeLibmbim:: |
| FakeMbimMessageMsUiccLowLevelAccessCloseChannelResponseParse)); |
| } |
| void MbimDeviceNew(GFile* file, |
| GCancellable* cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) override { |
| LOG(INFO) << __func__; |
| callback(nullptr, nullptr, user_data); |
| }; |
| |
| MbimDevice* MbimDeviceNewFinish(GAsyncResult* res, GError** error) override { |
| return new MbimDevice(); |
| }; |
| |
| void MbimDeviceOpenFull(MbimDevice* self, |
| MbimDeviceOpenFlags flags, |
| guint timeout, |
| GCancellable* cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) override { |
| GTask* task; |
| task = g_task_new(self, cancellable, nullptr, nullptr); |
| g_task_return_boolean(task, TRUE); |
| callback(reinterpret_cast<GObject*>(self), |
| reinterpret_cast<GAsyncResult*>(task), user_data); |
| } |
| |
| void MbimDeviceCommand(MbimDevice* self, |
| MbimMessage* message, |
| guint timeout, |
| GCancellable* cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) override { |
| callback(nullptr, nullptr, user_data); |
| } |
| |
| MbimMessage* MbimDeviceCommandFinish(MbimDevice* self, |
| GAsyncResult* res, |
| GError** error) override { |
| return mbim_message_new(nullptr, 0); |
| } |
| |
| gboolean MbimMessageResponseGetResult(const MbimMessage* self, |
| MbimMessageType expected, |
| GError** error) override { |
| return TRUE; |
| } |
| |
| gboolean MbimMessageDeviceCapsResponseParse( |
| const MbimMessage* message, |
| MbimDeviceType* out_device_type, |
| MbimCellularClass* out_cellular_class, |
| MbimVoiceClass* out_voice_class, |
| MbimSimClass* out_sim_class, |
| MbimDataClass* out_data_class, |
| MbimSmsCaps* out_sms_caps, |
| MbimCtrlCaps* out_control_caps, |
| guint32* out_max_sessions, |
| gchar** out_custom_data_class, |
| gchar** out_device_id, |
| gchar** out_firmware_info, |
| gchar** out_hardware_info, |
| GError** error) override { |
| *out_device_id = g_strdup_printf("123"); |
| return TRUE; |
| } |
| |
| gboolean MbimDeviceCheckMsMbimexVersion( |
| MbimDevice* self, |
| guint8 ms_mbimex_version_major, |
| guint8 ms_mbimex_version_minor) override { |
| return TRUE; |
| } |
| |
| bool GetReadyState(MbimDevice* device, |
| bool is_notification, |
| MbimMessage* notification, |
| MbimSubscriberReadyState* ready_state) override { |
| *ready_state = MBIM_SUBSCRIBER_READY_STATE_INITIALIZED; |
| return TRUE; |
| } |
| |
| gboolean MbimMessageMsBasicConnectExtensionsSysCapsResponseParse( |
| const MbimMessage* message, |
| guint32* out_number_of_executors, |
| guint32* out_number_of_slots, |
| guint32* out_concurrency, |
| guint64* out_modem_id, |
| GError** error) override { |
| *out_number_of_slots = 2; |
| *out_number_of_executors = 1; |
| return TRUE; |
| } |
| |
| gboolean MbimMessageMsBasicConnectExtensionsDeviceSlotMappingsResponseParse( |
| const MbimMessage* message, |
| guint32* out_map_count, |
| MbimSlotArray** out_slot_map, |
| GError** error) override { |
| *out_map_count = 1; |
| MbimSlotArray* out; |
| out = g_new0(MbimSlot*, 2); |
| MbimSlot* mbim_slot = g_new0(MbimSlot, 1); |
| mbim_slot->slot = active_slot_; |
| out[0] = mbim_slot; |
| *out_slot_map = out; |
| return TRUE; |
| } |
| |
| gboolean MbimMessageMsBasicConnectExtensionsSlotInfoStatusResponseParse( |
| const MbimMessage* message, |
| guint32* out_slot_index, |
| MbimUiccSlotState* out_state, |
| GError** error) override { |
| static guint32 slot_index = 0; |
| *out_slot_index = slot_index; |
| *out_state = slot_index == kNonEuiccSlot ? MBIM_UICC_SLOT_STATE_EMPTY |
| : MBIM_UICC_SLOT_STATE_ACTIVE_ESIM; |
| slot_index++; |
| slot_index %= 2; |
| return TRUE; |
| } |
| |
| MOCK_METHOD(gboolean, |
| MbimMessageMsUiccLowLevelAccessOpenChannelResponseParse, |
| (const MbimMessage* message, |
| guint32* out_status, |
| guint32* out_channel, |
| guint32* out_response_size, |
| const guint8** out_response, |
| GError** error), |
| (override)); |
| |
| gboolean FakeMbimMessageMsUiccLowLevelAccessOpenChannelResponseParse( |
| const MbimMessage* message, |
| guint32* out_status, |
| guint32* out_channel, |
| guint32* out_response_size, |
| const guint8** out_response, |
| GError** error) { |
| *out_status = ModemMbim::kMbimMessageSuccess; |
| *out_channel = kChannel; |
| *out_response_size = 0; |
| return TRUE; |
| } |
| |
| gboolean MbimMessageMsUiccLowLevelAccessApduResponseParse( |
| const MbimMessage* message, |
| guint32* out_status, |
| guint32* out_response_size, |
| const guint8** out_response, |
| GError** error) override { |
| *out_status = ModemMbim::kMbimMessageSuccess; |
| *out_response = kEidApdu; |
| *out_response_size = kEidApduLen; |
| return TRUE; |
| } |
| MOCK_METHOD(gboolean, |
| MbimMessageMsUiccLowLevelAccessCloseChannelResponseParse, |
| (const MbimMessage* message, guint32* out_status, GError** error), |
| (override)); |
| |
| gboolean FakeMbimMessageMsUiccLowLevelAccessCloseChannelResponseParse( |
| const MbimMessage* message, guint32* out_status, GError** error) { |
| *out_status = ModemMbim::kMbimMessageSuccess; |
| return TRUE; |
| } |
| |
| guint32 active_slot_ = kEuiccSlot; |
| }; |
| |
| class ModemMbimTest : public testing::Test { |
| protected: |
| void SetUp() override { |
| modem_manager_proxy_ = std::make_unique<MockModemManagerProxy>(); |
| libmbim_ = std::make_unique<FakeLibmbim>(); |
| } |
| |
| void TearDown() override { modem_.reset(); } |
| |
| void CreateModem() { |
| modem_ = ModemMbim::Create(nullptr, &executor_, std::move(libmbim_), |
| std::move(modem_manager_proxy_)); |
| ASSERT_NE(modem_, nullptr); |
| } |
| |
| base::OnceCallback<void(int)> GetResultCallback(int* test_result) { |
| return base::BindOnce([](int* test_result, int err) { *test_result = err; }, |
| test_result); |
| } |
| |
| MockExecutor executor_; |
| std::unique_ptr<ModemMbim> modem_; |
| FakeEuiccManager euicc_manager_; |
| std::unique_ptr<FakeLibmbim> libmbim_; |
| std::unique_ptr<MockModemManagerProxy> modem_manager_proxy_; |
| }; |
| |
| // Initializes the modem on a non eSIM slot and expects the initialization to be |
| // successful. |
| TEST_F(ModemMbimTest, SmokeNonEuicc) { |
| libmbim_->active_slot_ = kNonEuiccSlot; |
| EXPECT_CALL(euicc_manager_, OnEuiccUpdated(_, _)); |
| EXPECT_CALL(*modem_manager_proxy_, WaitForModem(_)); |
| CreateModem(); |
| int init_result; |
| modem_->Initialize(&euicc_manager_, GetResultCallback(&init_result)); |
| EXPECT_EQ(init_result, kModemSuccess); |
| } |
| |
| // Initializes the modem on an eSIM slot and expects the initialization to be |
| // successful. |
| TEST_F(ModemMbimTest, SmokeEuicc) { |
| EXPECT_CALL(euicc_manager_, OnEuiccUpdated(1, EuiccSlotInfo("11111111"))); |
| EXPECT_CALL(*modem_manager_proxy_, WaitForModem(_)); |
| CreateModem(); |
| int init_result; |
| modem_->Initialize(&euicc_manager_, GetResultCallback(&init_result)); |
| EXPECT_EQ(init_result, kModemSuccess); |
| } |
| |
| TEST_F(ModemMbimTest, UninhibitDuringDestruction) { |
| EXPECT_CALL(*modem_manager_proxy_, ScheduleUninhibit(_)); |
| CreateModem(); |
| } |
| |
| TEST_F(ModemMbimTest, ProcessEuiccEvent) { |
| EXPECT_CALL(*modem_manager_proxy_, WaitForModemAndInhibit(_)); |
| EXPECT_CALL(*libmbim_, |
| MbimMessageMsUiccLowLevelAccessOpenChannelResponseParse) |
| .Times(3); // Once for reading eid during initialize, once for |
| // EuiccStep::START, and once for PENDING_NOTIFICATIONS. |
| EXPECT_CALL(*libmbim_, |
| MbimMessageMsUiccLowLevelAccessCloseChannelResponseParse) |
| .Times(4); // once before reading eid, once after eid, once before |
| // EuiccStep::START, once after EuiccStep::END. |
| EXPECT_CALL(*modem_manager_proxy_, ScheduleUninhibit(_)) |
| .Times(2); // Once during ProcessEuiccEvent(EuiccStep::END) and once |
| // during modem_ destruction. |
| CreateModem(); |
| modem_->Initialize(&euicc_manager_, base::DoNothing()); |
| |
| int start_result; |
| modem_->ProcessEuiccEvent({kEuiccSlot + 1, EuiccStep::START}, |
| GetResultCallback(&start_result)); |
| EXPECT_EQ(start_result, kModemSuccess); |
| |
| modem_->SetIsReadyStateValidForTesting( |
| TRUE); // The modem sends this as a notification before |
| // PENDING_NOTIFICATIONS. |
| int notifications_result; |
| modem_->ProcessEuiccEvent({kEuiccSlot + 1, EuiccStep::PENDING_NOTIFICATIONS}, |
| GetResultCallback(¬ifications_result)); |
| EXPECT_EQ(notifications_result, kModemSuccess); |
| |
| int end_result; |
| modem_->ProcessEuiccEvent({kEuiccSlot + 1, EuiccStep::END}, |
| GetResultCallback(&end_result)); |
| EXPECT_EQ(end_result, kModemSuccess); |
| } |
| |
| TEST_F(ModemMbimTest, TransmitApdu) { |
| CreateModem(); |
| modem_->Initialize(&euicc_manager_, base::DoNothing()); |
| modem_->ProcessEuiccEvent({kEuiccSlot + 1, EuiccStep::START}, |
| base::DoNothing()); |
| std::vector<uint8_t> raw_eid; |
| auto cb = base::BindOnce([](std::vector<uint8_t>* raw_eid, |
| std::vector<uint8_t> resp) { *raw_eid = resp; }, |
| &raw_eid); |
| modem_->TransmitApdu( |
| {0x80, 0xCA, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00}, |
| std::move(cb)); // The contents of this APDU don't matter, the libmbim_ |
| // fake has been set up to send the eid as the response. |
| ASSERT_EQ(raw_eid.size(), kEidApduLen + 2); // the status word is 2 bytes |
| |
| for (int i = 0; i < kEidApduLen; ++i) { |
| EXPECT_EQ(raw_eid[i], kEidApdu[i]) |
| << "Apdu response differs at index " << i; |
| } |
| EXPECT_EQ((raw_eid[raw_eid.size() - 1] << 8 | raw_eid[raw_eid.size() - 2]), |
| ModemMbim::kMbimMessageSuccess); |
| } |
| |
| } // namespace hermes |