blob: 515c4dbbb809105cf8b4c9a4f74b0cd5d2228d74 [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 "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 in_seq;
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, ResultCallback());
}
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_, ResultCallback());
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 in_seq;
// Expect RESET and GET_SLOTS request after
// receiving UIM NEW_SERVER.
EXPECT_SEND(*socket_, kQrtrGetSlotsReq);
EXPECT_SEND(*socket_, kQrtrResetReq);
}
ModemReceiveData(kQrtrNewUimServerResp.begin(), kQrtrNewUimServerResp.end(),
QRTR_PORT_CTRL);
// Receive slot info from GET_SLOTS request.
ModemReceiveData(kQrtrGetSlotsResp.begin(), kQrtrGetSlotsResp.end(),
kUimPort);
// Receive RESET response from RESET request.
ModemReceiveData(kQrtrResetResp.begin(), kQrtrResetResp.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 in_seq;
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