| // 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 "hermes/modem_qrtr.h" |
| |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include <base/bind.h> |
| #include <base/files/scoped_file.h> |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/test/test_mock_time_task_runner.h> |
| #include <brillo/array_utils.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "hermes/apdu.h" |
| #include "hermes/fake_euicc_manager.h" |
| #include "hermes/sgp_22.h" |
| #include "hermes/socket_qrtr.h" |
| #include "hermes/type_traits.h" |
| |
| // |
| // General testing structure |
| // ------------------------- |
| // The ModemQrtr implementation sends and receives data from a qrtr socket, |
| // whose other end is a modem. In order to fake communication with the modem, |
| // the qrtr socket is replaced with a regular file descriptor, with the modem |
| // itself being faked by the ModemQrtrTest testing framework. |
| // |
| // For each TEST_F(ModemQrtrTest, ...) test, sending data from modem -> |
| // ModemQrtr can be faked with ModemQrtrTest::ModemReceiveData(...). The |
| // ModemQrtr -> modem messages are obviously not faked, as it is what we are |
| // testing, but ModemQrtr::SendApdus is now wrapped by ModemQrtrTest::SendApdus. |
| // The EXPECT_SEND macro is used to verify that the sent data is as we expected. |
| // In both cases, the transaction IDs of provided data is ignored, and the |
| // proper transaction ID values from the calls made to ModemQrtr::AllocateIds |
| // are used instead. This means that tests will not break if the implementation |
| // of AllocateIds is changed. |
| // |
| |
| using ::testing::_; |
| using ::testing::ElementsAreArray; |
| using ::testing::Invoke; |
| using ::testing::WithArgs; |
| using ::testing::WithoutArgs; |
| |
| namespace { |
| |
| constexpr uint32_t kTestNode = 0; |
| constexpr uint32_t kUimPort = 49; |
| constexpr uint32_t kDmsPort = 56; |
| const char* kQrtrFilename = "/tmp/hermes_qrtr_test"; |
| |
| // clang-format off |
| constexpr auto kQrtrNewUimServerResp = brillo::make_array<uint8_t>( |
| 0x04, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00 |
| ); |
| |
| constexpr auto kQrtrNewDmsServerResp = brillo::make_array<uint8_t>( |
| 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00 |
| ); |
| |
| constexpr auto kQrtrGetSerialNumbersReq = brillo::make_array<uint8_t>( |
| 0x00, 0x01, 0x00, 0x25, 0x00, 0x00, 0x00 |
| ); |
| |
| constexpr auto kQrtrGetSerialNumbersResp = brillo::make_array<uint8_t>( |
| 0x02, 0x01, 0x00, 0x25, 0x00, 0x1D, 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x10, 0x01, 0x00, 0x30, 0x11, 0x0F, 0x00, 0x30, 0x31, 0x35, 0x37, 0x36, |
| 0x39, 0x30, 0x30, 0x30, 0x30, 0x31, 0x31, 0x37, 0x38, 0x36 |
| ); |
| |
| constexpr auto kQrtrResetReq = brillo::make_array<uint8_t>( |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 |
| ); |
| |
| constexpr auto kQrtrResetResp = brillo::make_array<uint8_t>( |
| 0x02, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, |
| 0x00 |
| ); |
| |
| constexpr auto kQrtrGetSlotsReq = brillo::make_array<uint8_t>( |
| 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00 |
| ); |
| |
| // 2 eUICC's present, Slot 2 active |
| constexpr auto kQrtrGetSlotsResp = brillo::make_array<uint8_t>( |
| 0x02, 0x01, 0x00, 0x47, 0x00, 0x8F, 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x12, 0x23, 0x00, 0x02, 0x10, 0x89, 0x03, 0x30, 0x23, 0x42, 0x51, 0x20, |
| 0x00, 0x00, 0x00, 0x00, 0x09, 0x71, 0x04, 0x17, 0x04, 0x10, 0x89, 0x03, 0x30, |
| 0x23, 0x42, 0x51, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x64, 0x68, 0x11, |
| 0x13, 0x05, 0x00, 0x02, 0x01, 0x00, 0x01, 0x00, 0x11, 0x3F, 0x00, 0x02, 0x02, |
| 0x00, 0x00, 0x00, 0x00, 0x18, 0x3B, 0x9F, 0x97, 0xC0, 0x0A, 0x3F, 0xC6, 0x82, |
| 0x80, 0x31, 0xE0, 0x73, 0xFE, 0x21, 0x1B, 0x65, 0xD0, 0x02, 0x33, 0x14, 0xA5, |
| 0x81, 0x0F, 0xE4, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3B, 0x9F, 0x97, |
| 0xC0, 0x0A, 0x3F, 0xC6, 0x82, 0x80, 0x31, 0xE0, 0x73, 0xFE, 0x21, 0x1B, 0x65, |
| 0xD0, 0x02, 0x33, 0x14, 0xA5, 0x81, 0x0F, 0xE4, 0x01, 0x10, 0x15, 0x00, 0x02, |
| 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, |
| 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00 |
| ); |
| |
| // Switch to slot 1 |
| constexpr auto kQrtrSwitchSlotReq = brillo::make_array<uint8_t>( |
| 0x00, 0x07, 0x00, 0x46, 0x00, 0x0B, 0x00, 0x01, 0x01, 0x00, 0x01, 0x02, 0x04, |
| 0x00, 0x01, 0x00, 0x00, 0x00 |
| ); |
| |
| constexpr auto kQrtrSwitchSlotResp = brillo::make_array<uint8_t>( |
| 0x02, 0x07, 0x00, 0x46, 0x00, 0x07, 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, |
| 0x00 |
| ); |
| |
| constexpr auto kQrtrOpenLogicalChannelReq = brillo::make_array<uint8_t>( |
| 0x00, 0x00, 0x00, 0x42, 0x00, 0x18, 0x00, 0x01, 0x01, 0x00, 0x01, 0x10, 0x11, |
| 0x00, 0x10, 0xA0, 0x00, 0x00, 0x05, 0x59, 0x10, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, |
| 0x89, 0x00, 0x00, 0x01, 0x00 |
| ); |
| |
| constexpr auto kQrtrOpenLogicalChannelResp = brillo::make_array<uint8_t>( |
| 0x02, 0x00, 0x00, 0x42, 0x00, 0x35, 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x12, 0x22, 0x00, 0x21, 0x6F, 0x1F, 0x84, 0x10, 0xA0, 0x00, 0x00, 0x05, |
| 0x59, 0x10, 0x10, 0xFF, 0xFF, 0xFF, 0xFF, 0x89, 0x00, 0x00, 0x01, 0x00, 0xA5, |
| 0x04, 0x9F, 0x65, 0x01, 0xFF, 0xE0, 0x05, 0x82, 0x03, 0x02, 0x00, 0x00, 0x11, |
| 0x02, 0x00, 0x90, 0x00, 0x10, 0x01, 0x00, 0x01 |
| ); |
| |
| constexpr auto kApduPrefix = brillo::make_array<uint8_t>( |
| 0x00, 0x00, 0x00, 0x3B, 0x00, 0x13, 0x00, 0x01, 0x01, 0x00, 0x01, 0x02, 0x08, |
| 0x00, 0x06, 0x00, 0x80, 0xE2, 0x91, 0x00, 0x00 |
| ); |
| |
| // kApduSuffix consists of channel_id and procedure_bytes_tlvs |
| constexpr auto kApduSuffix = brillo::make_array<uint8_t>( |
| 0x10, 0x01, 0x00, 0x01, 0x11, 0x01, 0x00, 0x00 |
| ); |
| |
| constexpr auto kGetChallengeApdu = brillo::make_array<uint8_t>( |
| 0xBF, 0x2E, 0x00 |
| ); |
| |
| constexpr auto kGetChallengeResp = brillo::make_array<uint8_t>( |
| 0x02, 0x00, 0x00, 0x3B, 0x00, 0x23, 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x10, 0x19, 0x00, 0x17, 0x00, 0xBF, 0x2E, 0x12, 0x80, 0x10, 0x5A, 0x6C, |
| 0x23, 0x71, 0x94, 0xBE, 0xAB, 0x24, 0xF4, 0xEF, 0xAB, 0x54, 0xB7, 0x3A, 0x59, |
| 0xCF, 0x90, 0x00 |
| ); |
| // clang-format on |
| |
| void NullResponseCallback( |
| std::vector<std::vector<uint8_t>>& responses, // NOLINT(runtime/references) |
| int err) {} |
| |
| // Create a full QRTR packet given the data of an APDU message. The current |
| // implementation only works for non-fragmented APDUs. |
| template <typename Iterator> |
| hermes::EnableIfIterator_t<Iterator, std::vector<uint8_t>> CreateQrtrFromApdu( |
| Iterator first, Iterator last) { |
| std::vector<uint8_t> result; |
| result.insert(result.end(), kApduPrefix.begin(), kApduPrefix.end()); |
| result.insert(result.end(), first, last); |
| result.insert(result.end(), kApduSuffix.begin(), kApduSuffix.end()); |
| constexpr int kControlBytesSize = 1; |
| constexpr int kTxnIdSize = 2; |
| constexpr int kMsgIdSize = 2; |
| constexpr int kMsgLenSize = 2; |
| constexpr int kMsgLenIndex = kControlBytesSize + kTxnIdSize + |
| kMsgIdSize; // Length of the QMI message sans |
| // header is stored at this index |
| constexpr int kQmiHeaderSize = |
| kControlBytesSize + kTxnIdSize + kMsgIdSize + kMsgLenSize; |
| result[kMsgLenIndex] = result.size() - kQmiHeaderSize; |
| |
| constexpr int kApduLenIndex = 14; // Length of APDU is stored at this index |
| constexpr int kApduLenSize = 2; // 2 bytes to store the length of the APDU |
| constexpr int kApduHeaderSize = 5; // CLA + INS + P1 +P2 + Lc |
| result[kApduLenIndex] = |
| std::distance(first, last) + |
| kApduHeaderSize; // len(CLA + INS + P1 +P2 + Lc + CMD_DATA) |
| constexpr int kLcIndex = 20; |
| result[kLcIndex] = std::distance(first, last); // Lc = len(CMD_DATA) |
| |
| constexpr int kApduTlvLenIndex = |
| 12; // Length of TLV with tag=0x02 is stored at this index. |
| result[kApduTlvLenIndex] = |
| result[kApduLenIndex] + |
| kApduLenSize; // length of TLV with tag=0x02 is length(APDU) + (2 bytes |
| // that store length(APDU)) |
| |
| return result; |
| } |
| |
| } // namespace |
| |
| // Expect ModemQrtr instance to send the provided vector of data to the modem. |
| // This macro should only be called within a ModemQrtrTest. |
| // |
| // Note that the transaction ID in the provided vector will be ignored and the |
| // earliest unused ID from ModemQrtr::AllocateId will be used instead. This |
| // allows for changes in message ordering to not invalidate the data passed |
| // to this macro. |
| #define EXPECT_SEND(socket_obj, data) \ |
| EXPECT_CALL(socket_obj, Send(_, data.size(), _)) \ |
| .Times(1) \ |
| .WillOnce( \ |
| WithArgs<0, 1>(Invoke([this, d = data](const void* arr, size_t l) { \ |
| const uint8_t* array = reinterpret_cast<const uint8_t*>(arr); \ |
| auto expected = d; \ |
| expected[1] = array[1]; \ |
| this->receive_ids_.push_back(expected[1]); \ |
| EXPECT_THAT(expected, ElementsAreArray(array, l)); \ |
| return 0; \ |
| }))) |
| |
| namespace hermes { |
| |
| class MockExecutor : public Executor { |
| public: |
| MockExecutor() : Executor(new base::TestMockTimeTaskRunner()) {} |
| void FastForwardBy(base::TimeDelta duration) { |
| scoped_refptr<base::TestMockTimeTaskRunner> mock_task_runner_( |
| dynamic_cast<base::TestMockTimeTaskRunner*>(task_runner().get())); |
| mock_task_runner_->FastForwardBy(duration); |
| } |
| }; |
| |
| // Socket class which mocks the outgoing (host -> modem) socket calls and |
| // provides implementations for incoming (modem -> host) socket calls that reads |
| // data from kQrtrFilename rather than from an actual QRTR socket. |
| class MockSocketQrtr : public SocketInterface { |
| public: |
| void SetDataAvailableCallback(DataAvailableCallback cb) override { cb_ = cb; } |
| void SetPort(uint32_t port) { port_ = port; } |
| |
| bool Open() override { |
| socket_ = base::ScopedFD(open(kQrtrFilename, O_RDWR)); |
| if (!socket_.is_valid()) { |
| return false; |
| } |
| int off = lseek(socket_.get(), 0, SEEK_SET); |
| EXPECT_EQ(off, 0); |
| // Return without setting up a MessageLoop::WatchFileDescriptor. The epoll |
| // syscall does not always support regular file descriptors. Libevent could |
| // be configured not to use epoll, but this would require modifying or |
| // substituting base::MessagePumpLibevent. Instead, ModemQrtrTest will |
| // manually call the DataAvailableCallback as needed. |
| return true; |
| } |
| |
| bool IsValid() const override { return socket_.is_valid(); } |
| Type GetType() const override { return Type::kQrtr; } |
| |
| int Recv(void* buf, size_t size, void* metadata) override { |
| int bytes_read = read(socket_.get(), buf, size); |
| EXPECT_EQ(bytes_read, size); |
| LOG(INFO) << "Mock ModemQrtr receiving data (" << size |
| << " bytes): " << base::HexEncode(buf, size); |
| |
| if (metadata) { |
| auto data = reinterpret_cast<SocketQrtr::PacketMetadata*>(metadata); |
| data->node = kTestNode; |
| data->port = port_; |
| } |
| return bytes_read; |
| } |
| |
| MOCK_METHOD(void, Close, (), (override)); |
| MOCK_METHOD(bool, StartService, (uint32_t, uint16_t, uint16_t), (override)); |
| MOCK_METHOD(bool, StopService, (uint32_t, uint16_t, uint16_t), (override)); |
| MOCK_METHOD(int, Send, (const void*, size_t, const void*), (override)); |
| |
| private: |
| friend class ModemQrtrTest; |
| |
| base::ScopedFD socket_; |
| uint32_t port_; |
| DataAvailableCallback cb_; |
| }; |
| |
| // Test framework for ModemQrtr tests. Allows for the faking of modem -> cpu |
| // responses with the use of ModemReceiveData. |
| class ModemQrtrTest : public testing::Test { |
| protected: |
| // Fake modem initialization such that tests may jump right to sending QMI |
| // commands |
| void SetUp() override { |
| fd_.reset(open(kQrtrFilename, O_RDWR | O_CREAT | O_TRUNC, 0777)); |
| ASSERT_TRUE(fd_.is_valid()); |
| |
| auto socket = std::make_unique<MockSocketQrtr>(); |
| socket_ = socket.get(); |
| modem_ = ModemQrtr::Create(std::move(socket), nullptr, &executor_); |
| ASSERT_NE(modem_, nullptr); |
| |
| receive_ids_.clear(); |
| |
| SimulateInitialization(); |
| } |
| |
| void TearDown() override { |
| EXPECT_CALL( |
| *socket_, |
| StopService(to_underlying(QmiCmdInterface::Service::kDms), _, _)); |
| EXPECT_CALL( |
| *socket_, |
| StopService(to_underlying(QmiCmdInterface::Service::kUim), _, _)); |
| EXPECT_CALL(*socket_, Close()); |
| modem_.reset(nullptr); |
| fd_.reset(); |
| } |
| |
| // Set's up expectations for messages that go out when a slot switch happens |
| void InitSlot(uint8_t physical_slot) { |
| { |
| ::testing::InSequence dummy; |
| |
| EXPECT_SEND(*socket_, kQrtrGetSlotsReq); |
| // Slot 2 is the active slot after test initialization. If slot 1 is |
| // requested, a SwitchSlot message is expected |
| if (physical_slot == 1) |
| EXPECT_SEND(*socket_, kQrtrSwitchSlotReq); |
| EXPECT_SEND(*socket_, kQrtrResetReq); |
| EXPECT_SEND(*socket_, kQrtrOpenLogicalChannelReq); |
| } |
| modem_->StoreAndSetActiveSlot(physical_slot); |
| } |
| |
| void ModemReceiveInitSlot(uint8_t physical_slot) { |
| ModemReceiveData(kQrtrGetSlotsResp.begin(), kQrtrGetSlotsResp.end(), |
| kUimPort); |
| if (physical_slot == 1) { |
| ModemReceiveData(kQrtrSwitchSlotResp.begin(), kQrtrSwitchSlotResp.end(), |
| kUimPort); |
| EXPECT_EQ(modem_->qmi_disabled_, true); |
| executor_.FastForwardBy(ModemQrtr::kSwitchSlotDelay); |
| EXPECT_EQ(modem_->qmi_disabled_, false); |
| } |
| ModemReceiveData(kQrtrResetResp.begin(), kQrtrResetResp.end(), kUimPort); |
| ModemReceiveData(kQrtrOpenLogicalChannelResp.begin(), |
| kQrtrOpenLogicalChannelResp.end(), kUimPort); |
| } |
| |
| // Wrapper for ModemQrtr::SendApdus. Tests should use this rather than |
| // ModemQrtr::SendApdus. |
| void SendApdus(std::vector<lpa::card::Apdu> commands, |
| ModemQrtr::ResponseCallback cb) { |
| modem_->SendApdus(std::move(commands), std::move(cb)); |
| } |
| |
| // Cause |modem_| to receive the provided data. |
| template <typename Iterator> |
| EnableIfIterator_t<Iterator, void> ModemReceiveData(Iterator first, |
| Iterator last, |
| uint32_t port) { |
| std::vector<uint8_t> receive_data(first, last); |
| receive_data[1] = receive_ids_[0]; |
| receive_ids_.pop_front(); |
| |
| int ret = write(fd_.get(), receive_data.data(), receive_data.size()); |
| EXPECT_EQ(ret, receive_data.size()); |
| // Set modem buffer size so that the proper amount of data is read from fd. |
| modem_->buffer_.resize(receive_data.size()); |
| socket_->SetPort(port); |
| socket_->cb_.Run(modem_->socket_.get()); |
| } |
| |
| void SimulateInitialization() { |
| // Start DMS service and populate IMEI |
| EXPECT_CALL( |
| *socket_, |
| StartService(to_underlying(QmiCmdInterface::Service::kDms), _, _)) |
| .WillOnce(WithoutArgs(Invoke([this]() { |
| this->receive_ids_.push_back(0); |
| return true; |
| }))); |
| modem_->Initialize(&euicc_manager_); |
| EXPECT_EQ(euicc_manager_.valid_slots().size(), 0); |
| EXPECT_SEND(*socket_, kQrtrGetSerialNumbersReq); |
| ModemReceiveData(kQrtrNewDmsServerResp.begin(), kQrtrNewDmsServerResp.end(), |
| QRTR_PORT_CTRL); |
| |
| EXPECT_CALL( |
| *socket_, |
| StartService(to_underlying(QmiCmdInterface::Service::kUim), _, _)) |
| .WillOnce(WithoutArgs(Invoke([this]() { |
| this->receive_ids_.push_back(0); |
| return true; |
| }))); |
| ModemReceiveData(kQrtrGetSerialNumbersResp.begin(), |
| kQrtrGetSerialNumbersResp.end(), kDmsPort); |
| |
| { |
| ::testing::InSequence dummy; |
| // Expect RESET and GET_SLOTS request after |
| // receiving UIM NEW_SERVER. |
| EXPECT_SEND(*socket_, kQrtrResetReq); |
| EXPECT_SEND(*socket_, kQrtrGetSlotsReq); |
| } |
| ModemReceiveData(kQrtrNewUimServerResp.begin(), kQrtrNewUimServerResp.end(), |
| QRTR_PORT_CTRL); |
| // Receive RESET response from RESET request. |
| ModemReceiveData(kQrtrResetResp.begin(), kQrtrResetResp.end(), kUimPort); |
| // Receive slot info from GET_SLOTS request. |
| ModemReceiveData(kQrtrGetSlotsResp.begin(), kQrtrGetSlotsResp.end(), |
| kUimPort); |
| EXPECT_EQ(euicc_manager_.valid_slots().size(), 2); |
| EXPECT_EQ(euicc_manager_.valid_slots().at(1), |
| EuiccSlotInfo("89033023425120000000000971041704")); |
| EXPECT_EQ(euicc_manager_.valid_slots().at(2), |
| EuiccSlotInfo(1, "89033023425120000000000011646811")); |
| EXPECT_EQ(1, modem_->logical_slot_); |
| } |
| |
| base::ScopedFD fd_; |
| // Queue of transaction ids created by the ModemQrtr instance in question. |
| // This is used such that AllocateId implementations may change without |
| // breaking the unit tests (which should not be affected by changes in id |
| // allocation strategy). Send ids are ids to use when sending commands. |
| // Likewise for receive ids. |
| std::deque<uint16_t> receive_ids_; |
| MockSocketQrtr* socket_; |
| MockExecutor executor_; |
| std::unique_ptr<ModemQrtr> modem_; |
| FakeEuiccManager euicc_manager_; |
| }; |
| |
| /////////// |
| // TESTS // |
| /////////// |
| |
| // Sends an apdu on slot 2. Since Slot 2 is active by default, the following |
| // qmi messages are expected: GetSlots,Reset,OpenLogicalChannel,SendApdu |
| TEST_F(ModemQrtrTest, EmptyApduSlot2) { |
| InitSlot(2); |
| auto v = std::vector<uint8_t>(); |
| EXPECT_SEND(*socket_, CreateQrtrFromApdu(v.begin(), v.end())); |
| std::vector<lpa::card::Apdu> commands = {lpa::card::Apdu::NewStoreData({})}; |
| SendApdus(std::move(commands), NullResponseCallback); |
| ModemReceiveInitSlot(2); |
| } |
| |
| // Sends an apdu on slot 1. Since Slot 1 is not active by default, the following |
| // qmi messages are expected: GetSlots, SwitchSlot, Reset, OpenLogicalChannel, |
| // and SendApdu |
| TEST_F(ModemQrtrTest, EmptyApduSlot1) { |
| InitSlot(1); |
| auto v = std::vector<uint8_t>(); |
| EXPECT_SEND(*socket_, CreateQrtrFromApdu(v.begin(), v.end())); |
| std::vector<lpa::card::Apdu> commands = {lpa::card::Apdu::NewStoreData({})}; |
| SendApdus(std::move(commands), NullResponseCallback); |
| ModemReceiveInitSlot(1); |
| } |
| |
| TEST_F(ModemQrtrTest, RequestGetEid) { |
| InitSlot(2); |
| EXPECT_SEND(*socket_, CreateQrtrFromApdu(kGetChallengeApdu.begin(), |
| kGetChallengeApdu.end())); |
| std::vector<lpa::card::Apdu> commands = { |
| lpa::card::Apdu::NewStoreData(std::vector<uint8_t>( |
| kGetChallengeApdu.begin(), kGetChallengeApdu.end()))}; |
| SendApdus(std::move(commands), NullResponseCallback); |
| ModemReceiveInitSlot(2); |
| } |
| |
| TEST_F(ModemQrtrTest, SendTwoApdus) { |
| InitSlot(2); |
| auto v = std::vector<uint8_t>(); |
| { |
| ::testing::InSequence dummy; |
| |
| EXPECT_SEND(*socket_, CreateQrtrFromApdu(kGetChallengeApdu.begin(), |
| kGetChallengeApdu.end())); |
| // Do not expect to reinitialize the modem in between APDUs. |
| EXPECT_SEND(*socket_, CreateQrtrFromApdu(v.begin(), v.end())); |
| } |
| |
| std::vector<lpa::card::Apdu> commands = { |
| lpa::card::Apdu::NewStoreData(std::vector<uint8_t>( |
| kGetChallengeApdu.begin(), kGetChallengeApdu.end())), |
| lpa::card::Apdu::NewStoreData({})}; |
| SendApdus(std::move(commands), NullResponseCallback); |
| ModemReceiveInitSlot(2); |
| ModemReceiveData(kGetChallengeResp.begin(), kGetChallengeResp.end(), |
| kUimPort); |
| } |
| |
| } // namespace hermes |