| // 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/dms_cmd.h" |
| #include "hermes/euicc_manager_interface.h" |
| #include "hermes/sgp_22.h" |
| #include "hermes/socket_qrtr.h" |
| #include "hermes/type_traits.h" |
| #include "hermes/uim_cmd.h" |
| |
| namespace { |
| |
| // 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; |
| |
| constexpr int kEidLen = 16; |
| constexpr char bcd_chars[] = "0123456789\0\0\0\0\0\0"; |
| |
| // 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(UimCmd 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); |
| |
| // DMS callbacks |
| qmi_rx_callbacks_[{QmiCmdInterface::Service::kDms, |
| DmsCmd::QmiType::kGetDeviceSerialNumbers}] = |
| base::Bind(&ModemQrtr::ReceiveQmiGetSerialNumbers, |
| base::Unretained(this)); |
| |
| // UIM callbacks |
| qmi_rx_callbacks_[{QmiCmdInterface::Service::kUim, UimCmd::QmiType::kReset}] = |
| base::Bind(&ModemQrtr::ReceiveQmiReset, base::Unretained(this)); |
| qmi_rx_callbacks_[{QmiCmdInterface::Service::kUim, |
| UimCmd::QmiType::kSendApdu}] = |
| base::Bind(&ModemQrtr::ReceiveQmiSendApdu, base::Unretained(this)); |
| qmi_rx_callbacks_[{QmiCmdInterface::Service::kUim, |
| UimCmd::QmiType::kSwitchSlot}] = |
| base::Bind(&ModemQrtr::ReceiveQmiSwitchSlot, base::Unretained(this)); |
| qmi_rx_callbacks_[{QmiCmdInterface::Service::kUim, |
| UimCmd::QmiType::kGetSlots}] = |
| base::Bind(&ModemQrtr::ReceiveQmiGetSlots, base::Unretained(this)); |
| qmi_rx_callbacks_[{QmiCmdInterface::Service::kUim, |
| UimCmd::QmiType::kOpenLogicalChannel}] = |
| base::Bind(&ModemQrtr::ReceiveQmiOpenLogicalChannel, |
| base::Unretained(this)); |
| } |
| |
| 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(), std::make_unique<UimCmd>(UimCmd::QmiType::kSwitchSlot)}); |
| ReacquireChannel(); |
| } |
| |
| void ModemQrtr::StoreAndSetActiveSlot(const uint32_t physical_slot) { |
| tx_queue_.push_back({std::make_unique<TxInfo>(), AllocateId(), |
| std::make_unique<UimCmd>(UimCmd::QmiType::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(), std::make_unique<UimCmd>(UimCmd::QmiType::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(), std::make_unique<UimCmd>(UimCmd::QmiType::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; |
| if (!socket_->StartService(QmiCmdInterface::Service::kDms, 1, 0)) { |
| LOG(ERROR) << "Failed starting DMS service during ModemQrtr initialization"; |
| RetryInitialization(); |
| return; |
| } |
| current_state_.Transition(State::kInitializeStarted); |
| } |
| void ModemQrtr::InitializeUim() { |
| // StartService should result in a received QRTR_TYPE_NEW_SERVER |
| // packet. Don't send other packets until that occurs. |
| if (!socket_->StartService(QmiCmdInterface::Service::kUim, 1, 0)) { |
| LOG(ERROR) << "Failed starting UIM service during ModemQrtr initialization"; |
| RetryInitialization(); |
| return; |
| } |
| } |
| |
| 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(), |
| std::make_unique<UimCmd>(UimCmd::QmiType::kReset)}); |
| tx_queue_.push_back( |
| {std::unique_ptr<TxInfo>(), AllocateId(), |
| std::make_unique<UimCmd>(UimCmd::QmiType::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(to_underlying(QmiCmdInterface::Service::kUim), 1, 0); |
| socket_->StopService(to_underlying(QmiCmdInterface::Service::kDms), 1, 0); |
| } |
| qrtr_table_.clear(); |
| 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; |
| } |
| |
| switch (tx_queue_[0].qmi_msg_->service()) { |
| case QmiCmdInterface::Service::kUim: |
| TransmitUimCmdFromQueue(); |
| break; |
| case QmiCmdInterface::Service::kDms: |
| TransmitDmsCmdFromQueue(); |
| break; |
| } |
| } |
| |
| void ModemQrtr::TransmitDmsCmdFromQueue() { |
| auto qmi_cmd = tx_queue_[0].qmi_msg_.get(); |
| CHECK(qmi_cmd->service() == QmiCmdInterface::Service::kDms) |
| << "Attempted to send non-DMS command in " << __func__; |
| switch (qmi_cmd->qmi_type()) { |
| case DmsCmd::QmiType::kGetDeviceSerialNumbers: |
| dms_get_device_serial_numbers_req imei_request; |
| SendCommand(tx_queue_[0].qmi_msg_.get(), tx_queue_[0].id_, &imei_request, |
| dms_get_device_serial_numbers_req_ei); |
| break; |
| default: |
| LOG(ERROR) << "Unexpected QMI DMS type in ModemQrtr tx queue"; |
| } |
| tx_queue_.pop_front(); |
| } |
| |
| void ModemQrtr::TransmitUimCmdFromQueue() { |
| auto qmi_cmd = tx_queue_[0].qmi_msg_.get(); |
| CHECK(qmi_cmd->service() == QmiCmdInterface::Service::kUim) |
| << "Attempted to send non-UIM command in " << __func__; |
| bool should_pop = true; |
| switch (qmi_cmd->qmi_type()) { |
| case UimCmd::QmiType::kReset: |
| uim_reset_req reset_request; |
| SendCommand(tx_queue_[0].qmi_msg_.get(), tx_queue_[0].id_, &reset_request, |
| uim_reset_req_ei); |
| break; |
| case UimCmd::QmiType::kSwitchSlot: |
| // Don't pop since we need to update the inactive euicc if SwitchSlot |
| // succeeds |
| should_pop = false; |
| TransmitQmiSwitchSlot(&tx_queue_[0]); |
| break; |
| case UimCmd::QmiType::kGetSlots: |
| uim_get_slots_req slots_request; |
| SendCommand(tx_queue_[0].qmi_msg_.get(), tx_queue_[0].id_, &slots_request, |
| uim_get_slots_req_ei); |
| break; |
| case UimCmd::QmiType::kOpenLogicalChannel: |
| TransmitQmiOpenLogicalChannel(&tx_queue_[0]); |
| current_state_.Transition(State::kLogicalChannelPending); |
| break; |
| case UimCmd::QmiType::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(tx_queue_[0].qmi_msg_.get(), 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); |
| DCHECK(tx_element->qmi_msg_->qmi_type() == |
| UimCmd::QmiType::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(tx_element->qmi_msg_.get(), tx_element->id_, &request, |
| uim_open_logical_channel_req_ei); |
| } |
| |
| void ModemQrtr::TransmitQmiSendApdu(TxElement* tx_element) { |
| DCHECK(tx_element); |
| DCHECK(tx_element->qmi_msg_->qmi_type() == UimCmd::QmiType::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 = to_underlying(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(tx_element->qmi_msg_.get(), tx_element->id_, &request, |
| uim_send_apdu_req_ei); |
| } |
| |
| bool ModemQrtr::SendCommand(QmiCmdInterface* qmi_command, |
| 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 (!qrtr_table_.ContainsService(qmi_command->service())) { |
| LOG(ERROR) << "Tried sending to unknown service:" << qmi_command->service(); |
| return false; |
| } |
| if (qmi_command->service() == QmiCmdInterface::Service::kUim && |
| (qmi_command->qmi_type() == UimCmd::QmiType::kSendApdu) && |
| current_state_ != State::kSendApduReady) { |
| LOG(ERROR) << "QRTR tried to send apdu in state: " << current_state_; |
| return false; |
| } |
| |
| 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, qmi_command->qmi_type(), |
| id, c_struct, ei); |
| if (len < 0) { |
| LOG(ERROR) << "Failed to encode QMI UIM request: " |
| << qmi_command->qmi_type(); |
| return false; |
| } |
| |
| LOG(INFO) << "ModemQrtr sending transaction type " << qmi_command->qmi_type() |
| << " with data (size : " << packet.data_len |
| << ") : " << base::HexEncode(packet.data, packet.data_len); |
| |
| int success = -1; |
| success = |
| socket_->Send(packet.data, packet.data_len, |
| reinterpret_cast<const void*>( |
| &qrtr_table_.GetMetadata(qmi_command->service()))); |
| if (success < 0) { |
| LOG(ERROR) << "qrtr_sendto failed"; |
| return false; |
| } |
| |
| switch (qmi_command->service()) { |
| case QmiCmdInterface::Service::kDms: |
| pending_response_type_ = std::make_unique<DmsCmd>( |
| static_cast<DmsCmd::QmiType>(qmi_command->qmi_type())); |
| break; |
| case QmiCmdInterface::Service::kUim: |
| pending_response_type_ = std::make_unique<UimCmd>( |
| static_cast<UimCmd::QmiType>(qmi_command->qmi_type())); |
| break; |
| default: |
| CHECK(false) << "Unknown service: " << qmi_command->service(); |
| 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(2) << "Received NEW_SERVER QRTR packet"; |
| if (pkt.service == QmiCmdInterface::Service::kUim && |
| channel_ == kInvalidChannel) { |
| current_state_.Transition(State::kUimStarted); |
| qrtr_table_.Insert(QmiCmdInterface::Service::kUim, |
| {pkt.port, pkt.node}); |
| VLOG(2) << "Stored UIM metadata"; |
| // 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(), |
| std::make_unique<UimCmd>(UimCmd::QmiType::kGetSlots)}); |
| tx_queue_.push_front( |
| {std::make_unique<TxInfo>(), AllocateId(), |
| std::make_unique<UimCmd>(UimCmd::QmiType::kReset)}); |
| } |
| if (pkt.service == QmiCmdInterface::Service::kDms) { |
| current_state_.Transition(State::kDmsStarted); |
| qrtr_table_.Insert(QmiCmdInterface::Service::kDms, |
| {pkt.port, pkt.node}); |
| VLOG(2) << "Stored DMS metadata"; |
| tx_queue_.push_front({std::make_unique<TxInfo>(), AllocateId(), |
| std::make_unique<DmsCmd>( |
| DmsCmd::QmiType::kGetDeviceSerialNumbers)}); |
| } |
| 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; |
| } |
| QmiCmdInterface::Service service = |
| qrtr_table_.GetService({packet.port, packet.node}); |
| VLOG(2) << "Received QMI message of type: " << qmi_type |
| << " from service: " << service; |
| |
| if (!pending_response_type_) { |
| LOG(ERROR) << "Received unexpected QMI response. No pending response."; |
| return; |
| } |
| |
| if (qmi_rx_callbacks_.find({service, qmi_type}) == qmi_rx_callbacks_.end()) { |
| LOG(WARNING) << "Unknown QMI message of type: " << qmi_type |
| << " from service: " << service; |
| return; |
| } |
| |
| qmi_rx_callbacks_[{service, qmi_type}].Run(packet); |
| |
| if (pending_response_type_->service() != service) |
| LOG(ERROR) << "Received unexpected QMI response. Expected service: " |
| << pending_response_type_->service() |
| << " Actual service: " << service; |
| if (pending_response_type_->qmi_type() != qmi_type) |
| LOG(ERROR) << "Received unexpected QMI response. Expected type: " |
| << pending_response_type_->qmi_type() |
| << " Actual type:" << qmi_type; |
| pending_response_type_.reset(); |
| } |
| |
| void ModemQrtr::ReceiveQmiGetSlots(const qrtr_packet& packet) { |
| UimCmd cmd(UimCmd::QmiType::kGetSlots); |
| uim_get_slots_resp resp; |
| unsigned int id; |
| if (qmi_decode_message(&resp, &id, &packet, QMI_RESPONSE, cmd.qmi_type(), |
| 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.slot_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 min_len = std::min(std::min(resp.status_len, resp.slot_info_len), |
| resp.eid_info_len); |
| if (resp.status_len != resp.slot_info_len || |
| resp.status_len != resp.eid_info_len) { |
| LOG(ERROR) << "Lengths of status, slot_info and eid_info differ," |
| << " slot_info_len:" << resp.slot_info_len |
| << " status_len:" << resp.status_len |
| << " eid_info_len:" << resp.eid_info_len; |
| } |
| for (uint8_t i = 0; i < min_len; ++i) { |
| bool is_present = (resp.status[i].physical_card_status == |
| uim_physical_slot_status::kCardPresent); |
| bool is_euicc = resp.slot_info[i].is_euicc; |
| |
| bool is_active = (resp.status[i].physical_slot_state == |
| uim_physical_slot_status::kSlotActive); |
| |
| VLOG(2) << "Slot:" << i + 1 << " is_present:" << is_present |
| << " is_euicc:" << is_euicc << " is_active:" << is_active; |
| 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); |
| continue; |
| } |
| |
| std::string eid; |
| if (resp.eid_info[i].eid_len != kEidLen) |
| LOG(ERROR) << "Expected eid_len=" << kEidLen << ", eid_len is " |
| << resp.eid_info[i].eid_len; |
| for (int j = 0; j < resp.eid_info[i].eid_len; j++) { |
| eid += bcd_chars[(resp.eid_info[i].eid[j] >> 4) & 0xF]; |
| eid += bcd_chars[resp.eid_info[i].eid[j] & 0xF]; |
| if (j == 0) { |
| CHECK(eid == "89") << "Expected eid to begin with 89, eid begins with " |
| << eid; |
| } |
| } |
| |
| VLOG(2) << "EID for slot " << i + 1 << " is " << eid; |
| euicc_manager_->OnEuiccUpdated( |
| i + 1, is_active |
| ? EuiccSlotInfo(resp.status[i].logical_slot, std::move(eid)) |
| : EuiccSlotInfo(std::move(eid))); |
| } |
| } |
| |
| void ModemQrtr::ReceiveQmiSwitchSlot(const qrtr_packet& packet) { |
| UimCmd cmd(UimCmd::QmiType::kSwitchSlot); |
| uim_switch_slot_resp resp; |
| unsigned int id; |
| |
| if (qmi_decode_message(&resp, &id, &packet, QMI_RESPONSE, cmd.qmi_type(), |
| 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_->OnEuiccLogicalSlotUpdated(switch_slot_tx_info->physical_slot_, |
| switch_slot_tx_info->logical_slot_); |
| if (stored_active_slot_) |
| euicc_manager_->OnEuiccLogicalSlotUpdated(stored_active_slot_.value(), |
| base::nullopt); |
| |
| 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::ReceiveQmiGetSerialNumbers(const qrtr_packet& packet) { |
| DmsCmd cmd(DmsCmd::QmiType::kGetDeviceSerialNumbers); |
| if (current_state_ != State::kDmsStarted) { |
| LOG(ERROR) << "Received unexpected QMI DMS response: " << cmd.ToString() |
| << " in state " << current_state_; |
| return; |
| } |
| |
| dms_get_device_serial_numbers_resp resp; |
| unsigned int id; |
| if (qmi_decode_message(&resp, &id, &packet, QMI_RESPONSE, cmd.qmi_type(), |
| dms_get_device_serial_numbers_resp_ei) < 0) { |
| LOG(ERROR) << "Failed to decode QMI UIM response: " << cmd.ToString(); |
| } |
| |
| if (resp.result.result != 0) { |
| LOG(ERROR) << cmd.ToString() << " Could not decode imei" |
| << resp.result.error; |
| } |
| |
| if (!resp.imei_valid) { |
| LOG(ERROR) << "QMI UIM response for " << cmd.ToString() |
| << " contained an invalid imei"; |
| } |
| |
| imei_ = resp.imei; |
| VLOG(2) << "IMEI: " << imei_; |
| InitializeUim(); |
| } |
| |
| void ModemQrtr::ReceiveQmiReset(const qrtr_packet& packet) { |
| current_state_.Transition(State::kUimStarted); |
| VLOG(1) << "Ignoring received RESET packet"; |
| } |
| |
| void ModemQrtr::ReceiveQmiOpenLogicalChannel(const qrtr_packet& packet) { |
| ParseQmiOpenLogicalChannel(packet); |
| if (!current_state_.IsInitialized()) { |
| FinalizeInitialization(); |
| } |
| } |
| |
| void ModemQrtr::ParseQmiOpenLogicalChannel(const qrtr_packet& packet) { |
| UimCmd cmd(UimCmd::QmiType::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.qmi_type(), |
| 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) { |
| UimCmd cmd(UimCmd::QmiType::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.qmi_type(), |
| 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_ == kDmsStarted); |
| 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(); |
| } |
| |
| void ModemQrtr::QrtrTable::Insert(QmiCmdInterface::Service service, |
| SocketQrtr::PacketMetadata metadata) { |
| qrtr_metadata_[service] = metadata; |
| service_from_metadata_[metadata] = service; |
| } |
| |
| void ModemQrtr::QrtrTable::clear() { |
| qrtr_metadata_.clear(); |
| service_from_metadata_.clear(); |
| } |
| |
| const SocketQrtr::PacketMetadata& ModemQrtr::QrtrTable::GetMetadata( |
| QmiCmdInterface::Service service) { |
| return qrtr_metadata_[service]; |
| } |
| |
| const QmiCmdInterface::Service& ModemQrtr::QrtrTable::GetService( |
| SocketQrtr::PacketMetadata metadata) { |
| auto it = service_from_metadata_.find(metadata); |
| CHECK(it != service_from_metadata_.end()) |
| << "Metadata not found in qrtr_table"; |
| return it->second; |
| } |
| |
| bool ModemQrtr::QrtrTable::ContainsService(QmiCmdInterface::Service service) { |
| return (qrtr_metadata_.find(service) != qrtr_metadata_.end()); |
| } |
| |
| } // namespace hermes |