blob: b63c2c3aaf2f604c4ab025ed9635b9a5bbc563fe [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 <algorithm>
#include <array>
#include <utility>
#include <base/bind.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <libqrtr.h>
#include "hermes/apdu.h"
#include "hermes/euicc_manager_interface.h"
#include "hermes/qmi_uim.h"
#include "hermes/sgp_22.h"
#include "hermes/socket_qrtr.h"
namespace {
// As per QMI UIM spec section 2.2
constexpr uint8_t kQmiUimService = 0xB;
// This represents the default logical slot that we want our eSIM to be
// assigned. For dual sim - single standby modems, this will always work. For
// other multi-sim modems, get the first active slot and store it as a ModemQrtr
// field.
constexpr uint8_t kDefaultLogicalSlot = 0x01;
constexpr uint8_t kInvalidChannel = -1;
// A profile enable/disable results in an automatic refresh.
// Put Hermes to sleep during this refresh. If the refresh
// takes any longer, Hermes will retry channel acquisition
// after kInitRetryDelay
constexpr auto kSimRefreshDelay = base::TimeDelta::FromSeconds(2);
constexpr auto kInitRetryDelay = base::TimeDelta::FromSeconds(10);
bool CheckMessageSuccess(QmiUimCommand cmd, const uim_qmi_result& qmi_result) {
if (qmi_result.result == 0) {
return true;
}
LOG(ERROR) << cmd.ToString()
<< " response contained error: " << qmi_result.error;
return false;
}
} // namespace
namespace hermes {
struct ApduTxInfo : public ModemQrtr::TxInfo {
explicit ApduTxInfo(CommandApdu apdu,
ModemQrtr::ResponseCallback cb = nullptr)
: apdu_(std::move(apdu)), callback_(cb) {}
CommandApdu apdu_;
ModemQrtr::ResponseCallback callback_;
};
struct SwitchSlotTxInfo : public ModemQrtr::TxInfo {
explicit SwitchSlotTxInfo(const uint32_t physical_slot,
const uint8_t logical_slot)
: physical_slot_(physical_slot), logical_slot_(logical_slot) {}
const uint32_t physical_slot_;
const uint8_t logical_slot_;
};
std::unique_ptr<ModemQrtr> ModemQrtr::Create(
std::unique_ptr<SocketInterface> socket,
Logger* logger,
Executor* executor) {
// Open the socket prior to passing to ModemQrtr, such that it always has a
// valid socket to write to.
if (!socket || !socket->Open()) {
LOG(ERROR) << "Failed to open socket";
return nullptr;
}
return std::unique_ptr<ModemQrtr>(
new ModemQrtr(std::move(socket), logger, executor));
}
ModemQrtr::ModemQrtr(std::unique_ptr<SocketInterface> socket,
Logger* logger,
Executor* executor)
: qmi_disabled_(false),
extended_apdu_supported_(false),
current_transaction_id_(static_cast<uint16_t>(-1)),
channel_(kInvalidChannel),
logical_slot_(kDefaultLogicalSlot),
procedure_bytes_mode_(ProcedureBytesMode::EnableIntermediateBytes),
socket_(std::move(socket)),
buffer_(4096),
euicc_manager_(nullptr),
logger_(logger),
executor_(executor) {
CHECK(socket_);
CHECK(socket_->IsValid());
socket_->SetDataAvailableCallback(
base::Bind(&ModemQrtr::OnDataAvailable, base::Unretained(this)));
// Set SGP.22 specification version supported by this implementation (this is
// not currently constrained by the eUICC we use).
spec_version_.set_major(2);
spec_version_.set_minor(2);
spec_version_.set_revision(0);
}
ModemQrtr::~ModemQrtr() {
Shutdown();
socket_->Close();
}
void ModemQrtr::SetActiveSlot(const uint32_t physical_slot) {
tx_queue_.push_back(
{std::make_unique<SwitchSlotTxInfo>(physical_slot, logical_slot_),
AllocateId(), QmiUimCommand::kSwitchSlot});
ReacquireChannel();
}
void ModemQrtr::StoreAndSetActiveSlot(const uint32_t physical_slot) {
tx_queue_.push_back(
{std::make_unique<TxInfo>(), AllocateId(), QmiUimCommand::kGetSlots});
SetActiveSlot(physical_slot);
}
void ModemQrtr::RestoreActiveSlot() {
if (stored_active_slot_) {
tx_queue_.push_back({std::make_unique<SwitchSlotTxInfo>(
stored_active_slot_.value(), logical_slot_),
AllocateId(), QmiUimCommand::kSwitchSlot});
stored_active_slot_.reset();
} else {
LOG(ERROR) << "Attempted to restore active slot when none was stored";
}
}
void ModemQrtr::SendApdus(std::vector<lpa::card::Apdu> apdus,
ResponseCallback cb) {
for (size_t i = 0; i < apdus.size(); ++i) {
ResponseCallback callback =
(i == apdus.size() - 1 ? std::move(cb) : nullptr);
CommandApdu apdu(static_cast<ApduClass>(apdus[i].cla()),
static_cast<ApduInstruction>(apdus[i].ins()),
extended_apdu_supported_);
apdu.AddData(apdus[i].data());
tx_queue_.push_back(
{std::make_unique<ApduTxInfo>(std::move(apdu), std::move(callback)),
AllocateId(), QmiUimCommand::kSendApdu});
}
// Begin transmitting if we are not already processing a transaction.
if (!pending_response_type) {
TransmitFromQueue();
}
}
bool ModemQrtr::IsSimValidAfterEnable() {
// This function is called by the lpa after profile enable.
return true;
}
bool ModemQrtr::IsSimValidAfterDisable() {
// This function is called by the lpa after profile disable.
return true;
}
void ModemQrtr::Initialize(EuiccManagerInterface* euicc_manager) {
CHECK(current_state_ == State::kUninitialized);
euicc_manager_ = euicc_manager;
// StartService should result in a received QRTR_TYPE_NEW_SERVER
// packet. Don't send other packets until that occurs.
if (!socket_->StartService(kQmiUimService, 1, 0)) {
LOG(ERROR) << "Failed starting UIM service during ModemQrtr initialization";
RetryInitialization();
return;
}
current_state_.Transition(State::kInitializeStarted);
// Request initial info about SIM slots.
// TODO(crbug.com/1085825) Add support for getting indications so that this
// info can get updated.
tx_queue_.push_front(
{std::make_unique<TxInfo>(), AllocateId(), QmiUimCommand::kGetSlots});
tx_queue_.push_front(
{std::unique_ptr<TxInfo>(), AllocateId(), QmiUimCommand::kReset});
}
void ModemQrtr::ReacquireChannel() {
LOG(INFO) << "Reacquiring Channel";
if (current_state_ != State::kUimStarted)
current_state_.Transition(State::kUimStarted);
channel_ = kInvalidChannel;
tx_queue_.push_back(
{std::unique_ptr<TxInfo>(), AllocateId(), QmiUimCommand::kReset});
tx_queue_.push_back({std::unique_ptr<TxInfo>(), AllocateId(),
QmiUimCommand::kOpenLogicalChannel});
}
void ModemQrtr::RetryInitialization() {
VLOG(1) << "Reprobing for eSIM in " << kInitRetryDelay.InSeconds()
<< " seconds";
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::Bind(&ModemQrtr::Initialize, base::Unretained(this),
euicc_manager_),
kInitRetryDelay);
}
void ModemQrtr::FinalizeInitialization() {
if (current_state_ != State::kLogicalChannelOpened) {
VLOG(1) << "Could not open logical channel to eSIM";
Shutdown();
RetryInitialization();
return;
}
LOG(INFO) << "ModemQrtr initialization successful. eSIM found.";
current_state_.Transition(State::kSendApduReady);
// TODO(crbug.com/1117582) Set this based on whether or not Extended Length
// APDU is supported.
extended_apdu_supported_ = false;
}
void ModemQrtr::Shutdown() {
if (current_state_ != State::kUninitialized &&
current_state_ != State::kInitializeStarted) {
socket_->StopService(kQmiUimService, 1, 0);
}
current_state_.Transition(State::kUninitialized);
}
uint16_t ModemQrtr::AllocateId() {
// transaction id cannot be 0, but when incrementing by 1, an overflow will
// result in this method at some point returning 0. Incrementing by 2 when
// transaction_id is initialized as an odd number guarantees us that this
// method will never return 0 without special-casing the overflow.
current_transaction_id_ += 2;
return current_transaction_id_;
}
/////////////////////////////////////
// Transmit method implementations //
/////////////////////////////////////
void ModemQrtr::TransmitFromQueue() {
if (tx_queue_.empty() || pending_response_type || qmi_disabled_) {
return;
}
bool should_pop = true;
switch (tx_queue_[0].uim_type_) {
case QmiUimCommand::kReset:
uim_reset_req reset_request;
SendCommand(QmiUimCommand::kReset, tx_queue_[0].id_, &reset_request,
uim_reset_req_ei);
break;
case QmiUimCommand::kSwitchSlot:
// Don't pop since we need to update the inactive euicc if SwitchSlot
// succeeds
should_pop = false;
TransmitQmiSwitchSlot(&tx_queue_[0]);
break;
case QmiUimCommand::kGetSlots:
uim_get_slots_req slots_request;
SendCommand(QmiUimCommand::kGetSlots, tx_queue_[0].id_, &slots_request,
uim_get_slots_req_ei);
break;
case QmiUimCommand::kOpenLogicalChannel:
TransmitQmiOpenLogicalChannel(&tx_queue_[0]);
current_state_.Transition(State::kLogicalChannelPending);
break;
case QmiUimCommand::kSendApdu:
// kSendApdu element will be popped off the queue after the response has
// been entirely received. This occurs within |ReceiveQmiSendApdu|.
should_pop = false;
TransmitQmiSendApdu(&tx_queue_[0]);
break;
default:
LOG(ERROR) << "Unexpected QMI UIM type in ModemQrtr tx queue";
}
if (should_pop) {
tx_queue_.pop_front();
}
}
void ModemQrtr::TransmitQmiSwitchSlot(TxElement* tx_element) {
auto switch_slot_tx_info =
dynamic_cast<SwitchSlotTxInfo*>(tx_queue_[0].info_.get());
// Slot switching takes time, thus switch slots only when absolutely necessary
if (!stored_active_slot_ ||
stored_active_slot_.value() != switch_slot_tx_info->physical_slot_) {
uim_switch_slot_req switch_slot_request;
switch_slot_request.physical_slot = switch_slot_tx_info->physical_slot_;
switch_slot_request.logical_slot = switch_slot_tx_info->logical_slot_;
SendCommand(QmiUimCommand::kSwitchSlot, tx_queue_[0].id_,
&switch_slot_request, uim_switch_slot_req_ei);
} else {
LOG(INFO) << "Requested slot is already active";
tx_queue_.pop_front();
TransmitFromQueue();
}
}
void ModemQrtr::TransmitQmiOpenLogicalChannel(TxElement* tx_element) {
DCHECK(tx_element &&
tx_element->uim_type_ == QmiUimCommand::kOpenLogicalChannel);
uim_open_logical_channel_req request;
request.slot = logical_slot_;
request.aid_valid = true;
request.aid_len = kAidIsdr.size();
std::copy(kAidIsdr.begin(), kAidIsdr.end(), request.aid);
SendCommand(QmiUimCommand::kOpenLogicalChannel, tx_element->id_, &request,
uim_open_logical_channel_req_ei);
}
void ModemQrtr::TransmitQmiSendApdu(TxElement* tx_element) {
DCHECK(tx_element && tx_element->uim_type_ == QmiUimCommand::kSendApdu);
uim_send_apdu_req request;
request.slot = logical_slot_;
request.channel_id_valid = true;
request.channel_id = channel_;
request.procedure_bytes_valid = true;
request.procedure_bytes = static_cast<uint8_t>(procedure_bytes_mode_);
uint8_t* fragment;
ApduTxInfo* apdu = static_cast<ApduTxInfo*>(tx_element->info_.get());
size_t fragment_size = apdu->apdu_.GetNextFragment(&fragment);
request.apdu_len = fragment_size;
std::copy(fragment, fragment + fragment_size, request.apdu);
SendCommand(QmiUimCommand::kSendApdu, tx_element->id_, &request,
uim_send_apdu_req_ei);
}
bool ModemQrtr::SendCommand(QmiUimCommand type,
uint16_t id,
void* c_struct,
qmi_elem_info* ei) {
if (!socket_->IsValid()) {
LOG(ERROR) << "ModemQrtr socket is invalid!";
return false;
}
if (pending_response_type) {
LOG(ERROR) << "QRTR tried to send buffer while awaiting a qmi response";
return false;
}
if (!current_state_.CanSend()) {
LOG(ERROR) << "QRTR tried to send buffer in a non-sending state: "
<< current_state_;
return false;
}
if (type == QmiUimCommand::kSendApdu &&
current_state_ != State::kSendApduReady) {
LOG(ERROR) << "QRTR tried to send apdu in state: " << current_state_;
return false;
}
// All hermes initiated qmi messages expect a response
pending_response_type = type;
std::vector<uint8_t> encoded_buffer(kBufferDataSize * 2, 0);
qrtr_packet packet;
packet.data = encoded_buffer.data();
packet.data_len = encoded_buffer.size();
size_t len = qmi_encode_message(
&packet, QMI_REQUEST, static_cast<uint16_t>(type), id, c_struct, ei);
if (len < 0) {
LOG(ERROR) << "Failed to encode QMI UIM request: "
<< static_cast<uint16_t>(type);
return false;
}
LOG(INFO) << "ModemQrtr sending transaction type "
<< static_cast<uint16_t>(type)
<< " with data (size : " << packet.data_len
<< ") : " << base::HexEncode(packet.data, packet.data_len);
int success = socket_->Send(packet.data, packet.data_len,
reinterpret_cast<void*>(&metadata_));
if (success < 0) {
LOG(ERROR) << "qrtr_sendto failed";
return false;
}
return true;
}
////////////////////////////////////
// Receive method implementations //
////////////////////////////////////
void ModemQrtr::ProcessQrtrPacket(uint32_t node, uint32_t port, int size) {
sockaddr_qrtr qrtr_sock;
qrtr_sock.sq_family = AF_QIPCRTR;
qrtr_sock.sq_node = node;
qrtr_sock.sq_port = port;
qrtr_packet pkt;
int ret = qrtr_decode(&pkt, buffer_.data(), size, &qrtr_sock);
if (ret < 0) {
LOG(ERROR) << "qrtr_decode failed";
return;
}
switch (pkt.type) {
case QRTR_TYPE_NEW_SERVER:
VLOG(1) << "Received NEW_SERVER QRTR packet";
if (pkt.service == kQmiUimService && channel_ == kInvalidChannel) {
current_state_.Transition(State::kUimStarted);
metadata_.node = pkt.node;
metadata_.port = pkt.port;
}
break;
case QRTR_TYPE_DATA:
VLOG(1) << "Received data QRTR packet";
ProcessQmiPacket(pkt);
break;
case QRTR_TYPE_DEL_SERVER:
case QRTR_TYPE_HELLO:
case QRTR_TYPE_BYE:
case QRTR_TYPE_DEL_CLIENT:
case QRTR_TYPE_RESUME_TX:
case QRTR_TYPE_EXIT:
case QRTR_TYPE_PING:
case QRTR_TYPE_NEW_LOOKUP:
case QRTR_TYPE_DEL_LOOKUP:
LOG(INFO) << "Received QRTR packet of type " << pkt.type << ". Ignoring.";
break;
default:
LOG(WARNING) << "Received QRTR packet but did not recognize packet type "
<< pkt.type << ".";
}
// If we cannot yet send another request, it is because we are waiting for a
// response. After the response is received and processed, the next request
// will be sent.
if (!pending_response_type) {
TransmitFromQueue();
}
}
void ModemQrtr::ProcessQmiPacket(const qrtr_packet& packet) {
uint32_t qmi_type;
if (qmi_decode_header(&packet, &qmi_type) < 0) {
LOG(ERROR) << "QRTR received invalid QMI packet";
return;
}
VLOG(1) << "Received QMI message of type: " << qmi_type;
switch (qmi_type) {
case QmiUimCommand::kReset:
current_state_.Transition(State::kUimStarted);
VLOG(1) << "Ignoring received RESET packet";
break;
case QmiUimCommand::kSwitchSlot:
ReceiveQmiSwitchSlot(packet);
break;
case QmiUimCommand::kGetSlots:
ReceiveQmiGetSlots(packet);
break;
case QmiUimCommand::kOpenLogicalChannel:
ReceiveQmiOpenLogicalChannel(packet);
if (!current_state_.IsInitialized()) {
FinalizeInitialization();
}
break;
case QmiUimCommand::kSendApdu:
ReceiveQmiSendApdu(packet);
break;
default:
LOG(WARNING) << "Received QMI packet of unknown type: " << qmi_type;
return;
}
if (!pending_response_type) {
LOG(ERROR) << "Received unexpected QMI response. No pending response.";
return;
}
if (pending_response_type != qmi_type)
LOG(ERROR) << "Received unexpected QMI response. Expected: "
<< pending_response_type->ToString();
pending_response_type.reset();
}
void ModemQrtr::ReceiveQmiGetSlots(const qrtr_packet& packet) {
QmiUimCommand cmd(QmiUimCommand::kGetSlots);
uim_get_slots_resp resp;
unsigned int id;
if (qmi_decode_message(&resp, &id, &packet, QMI_RESPONSE, cmd,
uim_get_slots_resp_ei) < 0) {
LOG(ERROR) << "Failed to decode QMI UIM response: " << cmd.ToString();
return;
} else if (!CheckMessageSuccess(cmd, resp.result)) {
return;
}
if (!resp.status_valid || !resp.info_valid) {
LOG(ERROR) << "QMI UIM response for " << cmd.ToString()
<< " contained invalid slot info";
return;
}
CHECK(euicc_manager_);
bool logical_slot_found = false;
uint8_t max_len = std::max(resp.status_len, resp.info_len);
for (uint8_t i = 0; i < max_len; ++i) {
bool is_present = (resp.status[i].physical_card_status ==
uim_physical_slot_status::kCardPresent);
bool is_euicc = resp.info[i].is_euicc;
bool is_active = (resp.status[i].physical_slot_state ==
uim_physical_slot_status::kSlotActive);
if (is_active) {
stored_active_slot_ = i + 1;
if (!logical_slot_found) {
// This is the logical slot we grab when we perform a switch slot
logical_slot_ = resp.status[i].logical_slot;
logical_slot_found = true;
}
}
if (!is_present || !is_euicc)
euicc_manager_->OnEuiccRemoved(i + 1);
else
euicc_manager_->OnEuiccUpdated(
i + 1, is_active ? EuiccSlotInfo(resp.status[i].logical_slot)
: EuiccSlotInfo());
}
}
void ModemQrtr::ReceiveQmiSwitchSlot(const qrtr_packet& packet) {
QmiUimCommand cmd(QmiUimCommand::kSwitchSlot);
uim_switch_slot_resp resp;
unsigned int id;
if (qmi_decode_message(&resp, &id, &packet, QMI_RESPONSE, cmd,
uim_switch_slot_resp_ei) < 0) {
LOG(ERROR) << "Failed to decode QMI UIM response: " << cmd.ToString();
return;
}
if (!CheckMessageSuccess(cmd, resp.result)) {
return;
}
auto switch_slot_tx_info =
dynamic_cast<SwitchSlotTxInfo*>(tx_queue_.front().info_.get());
euicc_manager_->OnEuiccUpdated(
switch_slot_tx_info->physical_slot_,
EuiccSlotInfo(switch_slot_tx_info->logical_slot_));
if (stored_active_slot_)
euicc_manager_->OnEuiccUpdated(stored_active_slot_.value(),
EuiccSlotInfo());
tx_queue_.pop_front();
// Sending QMI messages immediately after switch slot leads to QMI errors
// since slot switching takes time. If channel reacquisition fails despite
// this delay, we retry after kInitRetryDelay.
DisableQmi(kSwitchSlotDelay);
}
void ModemQrtr::ReceiveQmiOpenLogicalChannel(const qrtr_packet& packet) {
QmiUimCommand cmd(QmiUimCommand::kOpenLogicalChannel);
if (current_state_ != State::kLogicalChannelPending) {
LOG(ERROR) << "Received unexpected QMI UIM response: " << cmd.ToString()
<< " in state " << current_state_;
return;
}
uim_open_logical_channel_resp resp;
unsigned int id;
if (qmi_decode_message(&resp, &id, &packet, QMI_RESPONSE, cmd,
uim_open_logical_channel_resp_ei) < 0) {
LOG(ERROR) << "Failed to decode QMI UIM response: " << cmd.ToString();
return;
}
if (resp.result.result != 0) {
VLOG(1) << cmd.ToString()
<< " Could not open channel to eSIM. This is expected if the "
"active sim slot is not an eSIM. QMI response contained error: "
<< resp.result.error;
return;
}
if (!resp.channel_id_valid) {
LOG(ERROR) << "QMI UIM response for " << cmd.ToString()
<< " contained an invalid channel id";
return;
}
channel_ = resp.channel_id;
current_state_.Transition(State::kLogicalChannelOpened);
}
void ModemQrtr::ReceiveQmiSendApdu(const qrtr_packet& packet) {
QmiUimCommand cmd(QmiUimCommand::kSendApdu);
CHECK(tx_queue_.size());
// Ensure that the queued element is for a kSendApdu command
TxInfo* base_info = tx_queue_[0].info_.get();
CHECK(base_info);
CHECK(dynamic_cast<ApduTxInfo*>(base_info));
static ResponseApdu payload;
uim_send_apdu_resp resp;
unsigned int id;
ApduTxInfo* info = static_cast<ApduTxInfo*>(base_info);
if (!qmi_decode_message(&resp, &id, &packet, QMI_RESPONSE, cmd,
uim_send_apdu_resp_ei)) {
LOG(ERROR) << "Failed to decode QMI UIM response: " << cmd.ToString();
return;
}
if (!CheckMessageSuccess(cmd, resp.result)) {
if (info->callback_) {
info->callback_(responses_, lpa::card::EuiccCard::kSendApduError);
// ResponseCallback interface does not indicate a change in ownership of
// |responses_|, but all callbacks should transfer ownership. Check for
// sanity.
// TODO(pholla) : Make ResponseCallback interface accept const responses_&
// and clear responses_.
CHECK(responses_.empty());
}
// Pop the apdu that caused the error.
tx_queue_.pop_front();
ReacquireChannel();
return;
}
VLOG(2) << "Adding to payload from APDU response ("
<< resp.apdu_response_len - 2 << " bytes): "
<< base::HexEncode(resp.apdu_response, resp.apdu_response_len - 2);
payload.AddData(resp.apdu_response, resp.apdu_response_len);
if (payload.MorePayloadIncoming()) {
// Make the next transmit operation be a request for more APDU data
info->apdu_ = payload.CreateGetMoreCommand(false);
return;
} else if (info->apdu_.HasMoreFragments()) {
// Send next fragment of APDU
VLOG(1) << "Sending next APDU fragment...";
TransmitFromQueue();
return;
}
if (tx_queue_.empty() || static_cast<uint16_t>(id) != tx_queue_[0].id_) {
LOG(ERROR) << "ModemQrtr received APDU from modem with unrecognized "
<< "transaction ID";
return;
}
VLOG(1) << "Finished transaction " << tx_queue_[0].id_ / 2
<< " (id: " << tx_queue_[0].id_ << ")";
responses_.push_back(payload.Release());
if (info->callback_) {
info->callback_(responses_, lpa::card::EuiccCard::kNoError);
// ResponseCallback interface does not indicate a change in ownership of
// |responses_|, but all callbacks should transfer ownership. Check for
// sanity.
CHECK(responses_.empty());
}
tx_queue_.pop_front();
}
void ModemQrtr::OnDataAvailable(SocketInterface* socket) {
CHECK(socket == socket_.get());
void* metadata = nullptr;
SocketQrtr::PacketMetadata data = {0, 0};
if (socket->GetType() == SocketInterface::Type::kQrtr) {
metadata = reinterpret_cast<void*>(&data);
}
int bytes_received = socket->Recv(buffer_.data(), buffer_.size(), metadata);
if (bytes_received < 0) {
LOG(ERROR) << "Socket recv failed";
return;
}
LOG(INFO) << "ModemQrtr recevied raw data (" << bytes_received
<< " bytes): " << base::HexEncode(buffer_.data(), bytes_received);
ProcessQrtrPacket(data.node, data.port, bytes_received);
}
const lpa::proto::EuiccSpecVersion& ModemQrtr::GetCardVersion() {
return spec_version_;
}
void ModemQrtr::SetProcedureBytes(
const ProcedureBytesMode procedure_bytes_mode) {
procedure_bytes_mode_ = procedure_bytes_mode;
}
bool ModemQrtr::State::Transition(ModemQrtr::State::Value value) {
bool valid_transition = false;
switch (value) {
case kUninitialized:
valid_transition = true;
break;
case kUimStarted:
// we reacquire the channel from kSendApduReady after profile (en/dis)able
valid_transition =
(value_ == kSendApduReady || value_ == kInitializeStarted);
break;
default:
// Most states can only transition from the previous state.
valid_transition = (value == value_ + 1);
}
if (valid_transition) {
value_ = value;
} else {
LOG(ERROR) << "Cannot transition from state " << *this << " to state "
<< State(value);
}
return valid_transition;
}
void ModemQrtr::DisableQmi(base::TimeDelta duration) {
qmi_disabled_ = true;
VLOG(1) << "Blocking QMI messages for " << duration << "seconds";
executor_->PostDelayedTask(
FROM_HERE, base::BindOnce(&ModemQrtr::EnableQmi, base::Unretained(this)),
duration);
}
void ModemQrtr::EnableQmi() {
qmi_disabled_ = false;
TransmitFromQueue();
}
void ModemQrtr::StartProfileOp(const uint32_t physical_slot) {
StoreAndSetActiveSlot(physical_slot);
// The card triggers a refresh after profile enable. This refresh can cause
// response apdu's with intermediate bytes to be flushed during a qmi
// transaction. Since, we don't use these intermediate bytes, disable
// them to avoid qmi errors as per QC's recommendation. b/169954635
SetProcedureBytes(ProcedureBytesMode::DisableIntermediateBytes);
}
void ModemQrtr::FinishProfileOp() {
DisableQmi(kSimRefreshDelay);
SetProcedureBytes(ProcedureBytesMode::EnableIntermediateBytes);
ReacquireChannel();
}
} // namespace hermes