| // |
| // Copyright (C) 2020 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "host/commands/modem_simulator/sim_service.h" |
| |
| #include <android-base/logging.h> |
| #include <tinyxml2.h> |
| |
| #include "common/libs/utils/files.h" |
| #include "host/commands/modem_simulator/device_config.h" |
| #include "host/commands/modem_simulator/network_service.h" |
| #include "host/commands/modem_simulator/pdu_parser.h" |
| #include "host/commands/modem_simulator/nvram_config.h" |
| |
| namespace cuttlefish { |
| |
| const std::pair<int, int> kSimPinSizeRange(4, 8); |
| constexpr int kSimPukSize = 8; |
| constexpr int kSimPinMaxRetryTimes = 3; |
| constexpr int kSimPukMaxRetryTimes = 10; |
| static const std::string kDefaultPinCode = "1234"; |
| static const std::string kDefaultPukCode = "12345678"; |
| |
| static const std::string MF_SIM = "3F00"; |
| static const std::string DF_TELECOM = "7F10"; |
| static const std::string DF_PHONEBOOK = "5F3A"; |
| static const std::string DF_GRAPHICS = "5F50"; |
| static const std::string DF_GSM = "7F20"; |
| static const std::string DF_CDMA = "7F25"; |
| static const std::string DF_ADF = "7FFF"; // UICC access |
| |
| // In an ADN record, everything but the alpha identifier |
| // is in a footer that's 14 bytes |
| constexpr int kFooterSizeBytes = 14; |
| // Maximum size of the un-extended number field |
| constexpr int kMaxNumberSizeBytes = 11; |
| |
| constexpr int kMaxLogicalChannels = 3; |
| |
| const std::map<SimService::SimStatus, std::string> gSimStatusResponse = { |
| {SimService::SIM_STATUS_ABSENT, ModemService::kCmeErrorSimNotInserted}, |
| {SimService::SIM_STATUS_NOT_READY, ModemService::kCmeErrorSimBusy}, |
| {SimService::SIM_STATUS_READY, "+CPIN: READY"}, |
| {SimService::SIM_STATUS_PIN, "+CPIN: SIM PIN"}, |
| {SimService::SIM_STATUS_PUK, "+CPIN: SIM PUK"}, |
| }; |
| |
| /* SimFileSystem */ |
| XMLElement* SimService::SimFileSystem::GetRootElement() { |
| return doc.RootElement(); |
| } |
| |
| std::string SimService::SimFileSystem::GetCommonIccEFPath(EFId efid) { |
| switch (efid) { |
| case EF_ADN: |
| case EF_FDN: |
| case EF_MSISDN: |
| case EF_SDN: |
| case EF_EXT1: |
| case EF_EXT2: |
| case EF_EXT3: |
| case EF_PSI: |
| return MF_SIM + DF_TELECOM; |
| |
| case EF_ICCID: |
| case EF_PL: |
| return MF_SIM; |
| case EF_PBR: |
| // we only support global phonebook. |
| return MF_SIM + DF_TELECOM + DF_PHONEBOOK; |
| case EF_IMG: |
| return MF_SIM + DF_TELECOM + DF_GRAPHICS; |
| default: |
| return {}; |
| } |
| } |
| |
| std::string SimService::SimFileSystem::GetUsimEFPath(EFId efid) { |
| switch(efid) { |
| case EF_SMS: |
| case EF_EXT5: |
| case EF_EXT6: |
| case EF_MWIS: |
| case EF_MBI: |
| case EF_SPN: |
| case EF_AD: |
| case EF_MBDN: |
| case EF_PNN: |
| case EF_OPL: |
| case EF_SPDI: |
| case EF_SST: |
| case EF_CFIS: |
| case EF_MAILBOX_CPHS: |
| case EF_VOICE_MAIL_INDICATOR_CPHS: |
| case EF_CFF_CPHS: |
| case EF_SPN_CPHS: |
| case EF_SPN_SHORT_CPHS: |
| case EF_FDN: |
| case EF_SDN: |
| case EF_EXT3: |
| case EF_MSISDN: |
| case EF_EXT2: |
| case EF_INFO_CPHS: |
| case EF_CSP_CPHS: |
| case EF_GID1: |
| case EF_GID2: |
| case EF_LI: |
| case EF_PLMN_W_ACT: |
| case EF_OPLMN_W_ACT: |
| case EF_HPLMN_W_ACT: |
| case EF_EHPLMN: |
| case EF_FPLMN: |
| case EF_LRPLMNSI: |
| case EF_HPPLMN: |
| return MF_SIM + DF_ADF; |
| |
| case EF_PBR: |
| // we only support global phonebook. |
| return MF_SIM + DF_TELECOM + DF_PHONEBOOK; |
| default: |
| std::string path = GetCommonIccEFPath(efid); |
| if (path.empty()) { |
| // The EFids in USIM phone book entries are decided by the card manufacturer. |
| // So if we don't match any of the cases above and if it's a USIM return |
| // the phone book path. |
| return MF_SIM + DF_TELECOM + DF_PHONEBOOK; |
| } |
| return path; |
| } |
| } |
| |
| XMLElement* SimService::SimFileSystem::FindAttribute(XMLElement *parent, |
| const std::string& attr_name, |
| const std::string& attr_value) { |
| if (parent == nullptr) { |
| return nullptr; |
| } |
| |
| XMLElement* child = parent->FirstChildElement(); |
| while (child) { |
| const XMLAttribute *attr = child->FindAttribute(attr_name.c_str()); |
| if (attr && attr->Value() == attr_value) { |
| break; |
| } |
| child = child->NextSiblingElement(); |
| } |
| return child; |
| }; |
| |
| XMLElement* SimService::SimFileSystem::AppendNewElement(XMLElement* parent, |
| const char* name) { |
| auto element = doc.NewElement(name); |
| parent->InsertEndChild(element); |
| return element; |
| } |
| |
| XMLElement* SimService::SimFileSystem::AppendNewElementWithText( |
| XMLElement* parent, const char* name, const char* text) { |
| auto element = doc.NewElement(name); |
| auto xml_text = doc.NewText(text); |
| element->InsertEndChild(xml_text); |
| parent->InsertEndChild(element); |
| return element; |
| } |
| |
| /* PinStatus */ |
| bool SimService::PinStatus::CheckPasswordValid(std::string_view password) { |
| for (int i = 0; i < password.size(); i++) { |
| int c = (int)password[i]; |
| if (c >= 48 && c <= 57) { |
| continue; |
| } else { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool SimService::PinStatus::VerifyPIN(const std::string_view pin) { |
| if (pin.size() < kSimPinSizeRange.first || pin.size() > kSimPinSizeRange.second) { |
| return false; |
| } |
| |
| if (!CheckPasswordValid(pin)) { |
| return false; |
| } |
| |
| if (pin_remaining_times_ <= 0) { |
| return false; |
| } |
| |
| std::string_view temp(pin_); |
| if (pin == temp) { // C++20 remove Operator!= |
| pin_remaining_times_ = kSimPinMaxRetryTimes; |
| return true; |
| } |
| |
| pin_remaining_times_ -= 1; |
| return false; |
| } |
| |
| bool SimService::PinStatus::VerifyPUK(const std::string_view puk) { |
| if (puk.size() != kSimPukSize) { |
| return false; |
| } |
| |
| if (!CheckPasswordValid(puk)) { |
| return false; |
| } |
| |
| if (puk_remaining_times_ <= 0) { |
| return false; |
| } |
| |
| std::string_view temp(puk_); |
| if (puk == temp) { // C++20 remove Operator!= |
| pin_remaining_times_ = kSimPinMaxRetryTimes; |
| puk_remaining_times_ = kSimPukMaxRetryTimes; |
| return true; |
| } |
| |
| puk_remaining_times_ -= 1; |
| return false; |
| } |
| |
| bool SimService::PinStatus::ChangePIN(ChangeMode mode, |
| const std::string_view pin_or_puk, |
| const std::string_view new_pin) { |
| auto length = new_pin.length(); |
| if (length < kSimPinSizeRange.first || length > kSimPinSizeRange.second) { |
| LOG(ERROR) << "Invalid digit number for PIN"; |
| return false; |
| } |
| |
| bool result = false; |
| if (mode == WITH_PIN) { // using old pin to change pin |
| result = VerifyPIN(pin_or_puk); |
| } else if (mode == WITH_PUK) { // using puk to change pin |
| result = VerifyPUK(pin_or_puk); |
| } |
| |
| if (!result) { |
| LOG(ERROR) << "Incorrect PIN or PUK"; |
| return false; |
| } |
| |
| if (!CheckPasswordValid(new_pin)) { |
| return false; |
| } |
| |
| std::string temp(new_pin); |
| pin_ = temp; |
| return true; |
| } |
| |
| bool SimService::PinStatus::ChangePUK(const std::string_view puk, |
| const std::string_view new_puk) { |
| bool result = VerifyPUK(puk); |
| if (!result) { |
| LOG(ERROR) << "Incorrect PUK or no retry times"; |
| return false; |
| } |
| |
| if (new_puk.length() != kSimPukSize) { |
| LOG(ERROR) << "Invalid digit number for PUK"; |
| return false; |
| } |
| |
| std::string temp(new_puk); |
| puk_ = temp; |
| return true; |
| }; |
| |
| SimService::SimService(int32_t service_id, ChannelMonitor* channel_monitor, |
| ThreadLooper* thread_looper) |
| : ModemService(service_id, this->InitializeCommandHandlers(), |
| channel_monitor, thread_looper) { |
| InitializeServiceState(); |
| } |
| |
| std::vector<CommandHandler> SimService::InitializeCommandHandlers() { |
| std::vector<CommandHandler> command_handlers = { |
| CommandHandler( |
| "+CPIN?", |
| [this](const Client& client) { this->HandleSIMStatusReq(client); }), |
| CommandHandler("+CPIN=", |
| [this](const Client& client, std::string& cmd) { |
| this->HandleChangeOrEnterPIN(client, cmd); |
| }), |
| CommandHandler("+CRSM=", |
| [this](const Client& client, std::string& cmd) { |
| this->HandleSIM_IO(client, cmd); |
| }), |
| CommandHandler("+CSIM=", |
| [this](const Client& client, std::string& cmd) { |
| this->HandleCSIM_IO(client, cmd); |
| }), |
| CommandHandler( |
| "+CIMI", |
| [this](const Client& client) { this->HandleGetIMSI(client); }), |
| CommandHandler( |
| "+CICCID", |
| [this](const Client& client) { this->HandleGetIccId(client); }), |
| CommandHandler("+CLCK=", |
| [this](const Client& client, std::string& cmd) { |
| this->HandleFacilityLock(client, cmd); |
| }), |
| CommandHandler("+CCHO=", |
| [this](const Client& client, std::string& cmd) { |
| this->HandleOpenLogicalChannel(client, cmd); |
| }), |
| CommandHandler("+CCHC=", |
| [this](const Client& client, std::string& cmd) { |
| this->HandleCloseLogicalChannel(client, cmd); |
| }), |
| CommandHandler("+CGLA=", |
| [this](const Client& client, std::string& cmd) { |
| this->HandleTransmitLogicalChannel(client, cmd); |
| }), |
| CommandHandler("+CPWD=", |
| [this](const Client& client, std::string& cmd) { |
| this->HandleChangePassword(client, cmd); |
| }), |
| CommandHandler("+CPINR=", |
| [this](const Client& client, std::string& cmd) { |
| this->HandleQueryRemainTimes(client, cmd); |
| }), |
| CommandHandler("+CCSS", |
| [this](const Client& client, std::string& cmd) { |
| this->HandleCdmaSubscriptionSource(client, cmd); |
| }), |
| CommandHandler("+WRMP", |
| [this](const Client& client, std::string& cmd) { |
| this->HandleCdmaRoamingPreference(client, cmd); |
| }), |
| CommandHandler("^MBAU=", |
| [this](const Client& client, std::string& cmd) { |
| this->HandleSimAuthentication(client, cmd); |
| }), |
| }; |
| return (command_handlers); |
| } |
| |
| void SimService::InitializeServiceState() { |
| InitializeSimFileSystemAndSimState(); |
| |
| InitializeFacilityLock(); |
| |
| // Max logical channels: 3 |
| logical_channels_ = { |
| LogicalChannel(1), LogicalChannel(2), LogicalChannel(kMaxLogicalChannels), |
| }; |
| } |
| |
| void SimService::InitializeSimFileSystemAndSimState() { |
| auto nvram_config = NvramConfig::Get(); |
| auto sim_type = nvram_config->sim_type(); |
| std::stringstream ss; |
| if (sim_type == 2) { // Special sim card for CtsCarrierApiTestCases |
| ss << "iccprofile_for_sim" << service_id_ << "_for_CtsCarrierApiTestCases.xml"; |
| } else { |
| ss << "iccprofile_for_sim" << service_id_ << ".xml"; |
| } |
| auto icc_profile_name = ss.str(); |
| |
| auto icc_profile_path = cuttlefish::modem::DeviceConfig::PerInstancePath( |
| icc_profile_name.c_str()); |
| std::string file = icc_profile_path; |
| |
| if (!cuttlefish::FileExists(icc_profile_path) || |
| !cuttlefish::FileHasContent(icc_profile_path.c_str())) { |
| ss.clear(); |
| ss.str(""); |
| |
| if (sim_type == 2) { // Special sim card for CtsCarrierApiTestCases |
| ss << "etc/modem_simulator/files/iccprofile_for_sim" << service_id_ |
| << "_for_CtsCarrierApiTestCases.xml"; |
| } else { |
| ss << "etc/modem_simulator/files/iccprofile_for_sim" << service_id_ << ".xml"; |
| } |
| |
| auto etc_file_path = |
| cuttlefish::modem::DeviceConfig::DefaultHostArtifactsPath(ss.str()); |
| if (!cuttlefish::FileExists(etc_file_path) || !cuttlefish::FileHasContent(etc_file_path)) { |
| sim_status_ = SIM_STATUS_ABSENT; |
| return; |
| } |
| file = etc_file_path; |
| } |
| |
| sim_file_system_.file_path = icc_profile_path; |
| auto err = sim_file_system_.doc.LoadFile(file.c_str()); |
| if (err != tinyxml2::XML_SUCCESS) { |
| LOG(ERROR) << "Unable to load XML file '" << file << " ', error " << err; |
| sim_status_ = SIM_STATUS_ABSENT; |
| return; |
| } |
| |
| XMLElement *root = sim_file_system_.GetRootElement(); |
| if (!root) { |
| LOG(ERROR) << "Unable to find root element: IccProfile"; |
| sim_status_ = SIM_STATUS_ABSENT; |
| return; |
| } |
| |
| // Default value if iccprofile not configure pin state |
| sim_status_ = SIM_STATUS_READY; |
| pin1_status_.pin_ = kDefaultPinCode; |
| pin1_status_.puk_ = kDefaultPukCode; |
| pin1_status_.pin_remaining_times_ = kSimPinMaxRetryTimes; |
| pin1_status_.puk_remaining_times_ = kSimPukMaxRetryTimes; |
| pin2_status_.pin_ = kDefaultPinCode; |
| pin2_status_.puk_ = kDefaultPukCode; |
| pin2_status_.pin_remaining_times_ = kSimPinMaxRetryTimes; |
| pin2_status_.puk_remaining_times_ = kSimPukMaxRetryTimes; |
| |
| XMLElement *pin_profile = root->FirstChildElement("PinProfile"); |
| if (pin_profile) { |
| // Pin1 status |
| auto pin_state = pin_profile->FirstChildElement("PINSTATE"); |
| if (pin_state) { |
| std::string state = pin_state->GetText(); |
| if (state == "PINSTATE_ENABLED_NOT_VERIFIED") { |
| sim_status_ = SIM_STATUS_PIN; |
| } else if (state == "PINSTATE_ENABLED_BLOCKED") { |
| sim_status_ = SIM_STATUS_PUK; |
| } |
| } |
| auto pin_code = pin_profile->FirstChildElement("PINCODE"); |
| if (pin_code) pin1_status_.pin_ = pin_code->GetText(); |
| |
| auto puk_code = pin_profile->FirstChildElement("PUKCODE"); |
| if (puk_code) pin1_status_.puk_ = puk_code->GetText(); |
| |
| auto pin_remaining_times = pin_profile->FirstChildElement("PINREMAINTIMES"); |
| if (pin_remaining_times) { |
| pin1_status_.pin_remaining_times_ = std::stoi(pin_remaining_times->GetText()); |
| } |
| |
| auto puk_remaining_times = pin_profile->FirstChildElement("PUKREMAINTIMES"); |
| if (puk_remaining_times) { |
| pin1_status_.puk_remaining_times_ = std::stoi(puk_remaining_times->GetText()); |
| } |
| |
| // Pin2 status |
| auto pin2_code = pin_profile->FirstChildElement("PIN2CODE"); |
| if (pin2_code) pin2_status_.pin_ = pin2_code->GetText(); |
| |
| auto puk2_code = pin_profile->FirstChildElement("PUK2CODE"); |
| if (puk2_code) pin2_status_.puk_ = puk2_code->GetText(); |
| |
| auto pin2_remaining_times = pin_profile->FirstChildElement("PIN2REMAINTIMES"); |
| if (pin2_remaining_times) { |
| pin2_status_.pin_remaining_times_ = std::stoi(pin2_remaining_times->GetText()); |
| } |
| |
| auto puk2_remaining_times = pin_profile->FirstChildElement("PUK2REMAINTIMES"); |
| if (puk2_remaining_times) { |
| pin2_status_.puk_remaining_times_ = std::stoi(puk2_remaining_times->GetText()); |
| } |
| } |
| } |
| |
| void SimService::InitializeFacilityLock() { |
| /* Default disable */ |
| facility_lock_ = { |
| {"SC", FacilityLock(FacilityLock::LockStatus::DISABLE)}, |
| {"FD", FacilityLock(FacilityLock::LockStatus::DISABLE)}, |
| {"AO", FacilityLock(FacilityLock::LockStatus::DISABLE)}, |
| {"OI", FacilityLock(FacilityLock::LockStatus::DISABLE)}, |
| {"OX", FacilityLock(FacilityLock::LockStatus::DISABLE)}, |
| {"AI", FacilityLock(FacilityLock::LockStatus::DISABLE)}, |
| {"IR", FacilityLock(FacilityLock::LockStatus::DISABLE)}, |
| {"AB", FacilityLock(FacilityLock::LockStatus::DISABLE)}, |
| {"AG", FacilityLock(FacilityLock::LockStatus::DISABLE)}, |
| {"AC", FacilityLock(FacilityLock::LockStatus::DISABLE)}, |
| }; |
| |
| XMLElement *root = sim_file_system_.GetRootElement(); |
| if (!root) { |
| LOG(ERROR) << "Unable to find root element: IccProfile"; |
| sim_status_ = SIM_STATUS_ABSENT; |
| return; |
| } |
| |
| XMLElement *facility_lock = root->FirstChildElement("FacilityLock"); |
| if (!facility_lock) { |
| LOG(ERROR) << "Unable to find element: FacilityLock"; |
| return; |
| } |
| |
| for (auto iter = facility_lock_.begin(); iter != facility_lock_.end(); ++iter) { |
| auto lock_status = facility_lock->FirstChildElement(iter->first.c_str()); |
| if (lock_status) { |
| std::string state = lock_status->GetText(); |
| if (state == "ENABLE") { |
| iter->second.lock_status = FacilityLock::LockStatus::ENABLE; |
| } |
| } |
| } |
| } |
| |
| void SimService::SavePinStateToIccProfile() { |
| XMLElement *root = sim_file_system_.GetRootElement(); |
| if (!root) { |
| LOG(ERROR) << "Unable to find root element: IccProfile"; |
| sim_status_ = SIM_STATUS_ABSENT; |
| return; |
| } |
| |
| XMLElement *pin_profile = root->FirstChildElement("PinProfile"); |
| if (!pin_profile) { |
| pin_profile = sim_file_system_.AppendNewElement(root, "PinProfile"); |
| } |
| |
| const char* text = "PINSTATE_UNKNOWN"; |
| |
| if (sim_status_ == SIM_STATUS_PUK) { |
| text = "PINSTATE_ENABLED_BLOCKED"; |
| } else { |
| auto iter = facility_lock_.find("SC"); |
| if (iter != facility_lock_.end()) { |
| if (iter->second.lock_status == FacilityLock::ENABLE) { |
| text = "PINSTATE_ENABLED_NOT_VERIFIED"; |
| } |
| } |
| } |
| |
| // Pin1 status |
| auto pin_state = pin_profile->FirstChildElement("PINSTATE"); |
| if (!pin_state) { |
| pin_state = sim_file_system_.AppendNewElementWithText(pin_profile, "PINSTATE", text); |
| } else { |
| pin_state->SetText(text); |
| } |
| |
| auto pin_code = pin_profile->FirstChildElement("PINCODE"); |
| if (!pin_code) { |
| pin_code = sim_file_system_.AppendNewElementWithText(pin_profile, "PINCODE", |
| pin1_status_.pin_.c_str()); |
| } else { |
| pin_code->SetText(pin1_status_.pin_.c_str()); |
| } |
| |
| auto puk_code = pin_profile->FirstChildElement("PUKCODE"); |
| if (!puk_code) { |
| puk_code = sim_file_system_.AppendNewElementWithText(pin_profile, "PUKCODE", |
| pin1_status_.puk_.c_str()); |
| } else { |
| puk_code->SetText(pin1_status_.puk_.c_str()); |
| } |
| |
| std::stringstream ss; |
| ss << pin1_status_.pin_remaining_times_; |
| |
| auto pin_remaining_times = pin_profile->FirstChildElement("PINREMAINTIMES"); |
| if (!pin_remaining_times) { |
| pin_remaining_times = sim_file_system_.AppendNewElementWithText(pin_profile, |
| "PINREMAINTIMES", ss.str().c_str()); |
| } else { |
| pin_remaining_times->SetText(ss.str().c_str()); |
| } |
| ss.clear(); |
| ss.str(""); |
| ss << pin1_status_.puk_remaining_times_; |
| |
| auto puk_remaining_times = pin_profile->FirstChildElement("PUKREMAINTIMES"); |
| if (!puk_remaining_times) { |
| puk_remaining_times = sim_file_system_.AppendNewElementWithText(pin_profile, |
| "PUKREMAINTIMES", ss.str().c_str()); |
| } else { |
| puk_remaining_times->SetText(ss.str().c_str()); |
| } |
| |
| // Pin2 status |
| auto pin2_code = pin_profile->FirstChildElement("PIN2CODE"); |
| if (!pin2_code) { |
| pin2_code = sim_file_system_.AppendNewElementWithText(pin_profile, "PIN2CODE", |
| pin2_status_.pin_.c_str()); |
| } else { |
| pin2_code->SetText(pin2_status_.pin_.c_str()); |
| } |
| |
| auto puk2_code = pin_profile->FirstChildElement("PUK2CODE"); |
| if (!puk2_code) { |
| puk2_code = sim_file_system_.AppendNewElementWithText(pin_profile, "PUK2CODE", |
| pin2_status_.puk_.c_str()); |
| } else { |
| puk2_code->SetText(pin2_status_.puk_.c_str()); |
| } |
| |
| ss.clear(); |
| ss.str(""); |
| ss << pin2_status_.pin_remaining_times_; |
| |
| auto pin2_remaining_times = pin_profile->FirstChildElement("PIN2REMAINTIMES"); |
| if (!pin2_remaining_times) { |
| pin2_remaining_times = sim_file_system_.AppendNewElementWithText(pin_profile, |
| "PINREMAINTIMES", ss.str().c_str()); |
| } else { |
| pin2_remaining_times->SetText(ss.str().c_str()); |
| } |
| ss.clear(); |
| ss.str(""); |
| ss << pin2_status_.puk_remaining_times_; |
| |
| auto puk2_remaining_times = pin_profile->FirstChildElement("PUK2REMAINTIMES"); |
| if (!puk2_remaining_times) { |
| puk2_remaining_times = sim_file_system_.AppendNewElementWithText(pin_profile, |
| "PUK2REMAINTIMES", ss.str().c_str()); |
| } else { |
| puk2_remaining_times->SetText(ss.str().c_str()); |
| } |
| |
| // Save file |
| sim_file_system_.doc.SaveFile(sim_file_system_.file_path.c_str()); |
| } |
| |
| void SimService::SaveFacilityLockToIccProfile() { |
| XMLElement *root = sim_file_system_.GetRootElement(); |
| if (!root) { |
| LOG(ERROR) << "Unable to find root element: IccProfile"; |
| sim_status_ = SIM_STATUS_ABSENT; |
| return; |
| } |
| |
| XMLElement *facility_lock = root->FirstChildElement("FacilityLock"); |
| if (!facility_lock) { |
| facility_lock = sim_file_system_.AppendNewElement(root, "FacilityLock"); |
| } |
| |
| const char* text = "DISABLE"; |
| |
| for (auto iter = facility_lock_.begin(); iter != facility_lock_.end(); ++iter) { |
| if (iter->second.lock_status == FacilityLock::LockStatus::ENABLE) { |
| text = "ENABLE"; |
| } else { |
| text = "DISABLE"; |
| } |
| auto element = facility_lock->FirstChildElement(iter->first.c_str()); |
| if (!element) { |
| element = sim_file_system_.AppendNewElementWithText(facility_lock, |
| iter->first.c_str(), text); |
| } else { |
| element->SetText(text); |
| } |
| } |
| |
| sim_file_system_.doc.SaveFile(sim_file_system_.file_path.c_str()); |
| |
| InitializeSimFileSystemAndSimState(); |
| InitializeFacilityLock(); |
| } |
| |
| bool SimService::IsFDNEnabled() { |
| auto iter = facility_lock_.find("FD"); |
| if (iter != facility_lock_.end() && |
| iter->second.lock_status == FacilityLock::LockStatus::ENABLE) { |
| return true; |
| } |
| return false; |
| } |
| |
| bool SimService::IsFixedDialNumber(std::string_view number) { |
| XMLElement *root = sim_file_system_.GetRootElement(); |
| if (!root) return false; |
| |
| auto path = SimFileSystem::GetUsimEFPath(SimFileSystem::EFId::EF_FDN); |
| |
| size_t pos = 0; |
| auto parent = root; |
| while (pos < path.length()) { |
| std::string sub_path(path.substr(pos, 4)); |
| auto app = SimFileSystem::FindAttribute(parent, "path", sub_path); |
| if (!app) return false; |
| pos += 4; |
| parent = app; |
| } |
| |
| XMLElement* ef = SimFileSystem::FindAttribute(parent, "id", "6F3B"); |
| if (!ef) return false; |
| |
| XMLElement *final = ef->FirstChildElement("SIMIO"); |
| while (final) { |
| std::string record = final->GetText(); |
| int footerOffset = record.length() - kFooterSizeBytes * 2; |
| int numberLength = (record[footerOffset] - '0') * 16 + |
| record[footerOffset + 1] - '0'; |
| if (numberLength > kMaxNumberSizeBytes) { // Invalid number length |
| final = final->NextSiblingElement("SIMIO"); |
| continue; |
| } |
| |
| std::string bcd_fdn = ""; |
| if (numberLength * 2 == 16) { // Skip Type(91) and Country Code(68) |
| bcd_fdn = record.substr(footerOffset + 6, numberLength * 2 - 4); |
| } else { // Skip Type(81) |
| bcd_fdn = record.substr(footerOffset + 4, numberLength * 2 - 2); |
| } |
| |
| std::string fdn = PDUParser::BCDToString(bcd_fdn); |
| if (fdn == number) { |
| return true; |
| } |
| final = final->NextSiblingElement("SIMIO"); |
| } |
| |
| return false; |
| } |
| |
| XMLElement* SimService::GetIccProfile() { |
| return sim_file_system_.GetRootElement(); |
| } |
| |
| std::string SimService::GetPhoneNumber() { |
| XMLElement *root = sim_file_system_.GetRootElement(); |
| if (!root) return ""; |
| |
| auto path = SimFileSystem::GetUsimEFPath(SimFileSystem::EFId::EF_MSISDN); |
| |
| size_t pos = 0; |
| auto parent = root; |
| while (pos < path.length()) { |
| std::string sub_path(path.substr(pos, 4)); |
| auto app = SimFileSystem::FindAttribute(parent, "path", sub_path); |
| if (!app) return ""; |
| pos += 4; |
| parent = app; |
| } |
| |
| XMLElement* ef = SimFileSystem::FindAttribute(parent, "id", "6F40"); |
| if (!ef) return ""; |
| |
| XMLElement *final = SimFileSystem::FindAttribute(ef, "cmd", "B2");; |
| if (!final) return ""; |
| |
| std::string record = final->GetText(); |
| int footerOffset = record.length() - kFooterSizeBytes * 2; |
| int numberLength = (record[footerOffset] - '0') * 16 + |
| record[footerOffset + 1] - '0'; |
| if (numberLength > kMaxNumberSizeBytes) { // Invalid number length |
| return ""; |
| } |
| |
| std::string bcd_number = ""; |
| if (numberLength * 2 == 16) { // Skip Type(91) and Country Code(68) |
| bcd_number = record.substr(footerOffset + 6, numberLength * 2 - 4); |
| } else { // Skip Type(81) |
| bcd_number = record.substr(footerOffset + 4, numberLength * 2 - 2); |
| } |
| |
| return PDUParser::BCDToString(bcd_number); |
| } |
| |
| SimService::SimStatus SimService::GetSimStatus() const { |
| return sim_status_; |
| } |
| |
| std::string SimService::GetSimOperator() { |
| XMLElement *root = sim_file_system_.GetRootElement(); |
| if (!root) return ""; |
| |
| XMLElement* mf = SimFileSystem::FindAttribute(root, "path", MF_SIM); |
| if (!mf) return ""; |
| |
| XMLElement* df = SimFileSystem::FindAttribute(mf, "path", DF_ADF); |
| if (!df) return ""; |
| |
| XMLElement* ef = SimFileSystem::FindAttribute(df, "id", "6F07"); |
| if (!ef) return ""; |
| |
| XMLElement *cimi = ef->FirstChildElement("CIMI"); |
| if (!cimi) return ""; |
| std::string imsi = cimi->GetText(); |
| |
| ef = SimFileSystem::FindAttribute(df, "id", "6FAD"); |
| if (!ef) return ""; |
| |
| XMLElement *sim_io = ef->FirstChildElement("SIMIO"); |
| while (sim_io) { |
| const XMLAttribute *attr_cmd = sim_io->FindAttribute("cmd"); |
| std::string attr_value = attr_cmd ? attr_cmd->Value() : ""; |
| if (attr_cmd && attr_value == "B0") { |
| break; |
| } |
| |
| sim_io = sim_io->NextSiblingElement("SIMIO"); |
| } |
| |
| if (!sim_io) return ""; |
| |
| std::string length = sim_io->GetText(); |
| int mnc_size = std::stoi(length.substr(length.size() -2)); |
| |
| return imsi.substr(0, 3 + mnc_size); |
| } |
| |
| void SimService::SetupDependency(NetworkService* net) { |
| network_service_ = net; |
| } |
| |
| /** |
| * AT+CPIN |
| * Set command sends to the MT a password which is necessary before it can be |
| * operated. |
| * Read command returns an alphanumeric string indicating whether some |
| * password is required or not. |
| * |
| * Command Possible response(s) |
| * +CPIN=<pin>[,<newpin>] +CME ERROR: <err> |
| * +CPIN? +CPIN: <code> |
| * +CME ERROR: <err> |
| * <pin>, <newpin>: string type values. |
| * <code> values reserved by the present document: |
| * READY MT is not pending for any password |
| * SIM PIN MT is waiting SIM PIN to be given |
| * SIM PUK MT is waiting SIM PUK to be given |
| * |
| * see RIL_REQUEST_GET_SIM_STATUS in RIL |
| */ |
| void SimService::HandleSIMStatusReq(const Client& client) { |
| std::vector<std::string> responses; |
| auto iter = gSimStatusResponse.find(sim_status_); |
| if (iter != gSimStatusResponse.end()) { |
| responses.push_back(iter->second); |
| responses.push_back("OK"); |
| } else { |
| sim_status_ = SIM_STATUS_ABSENT; |
| responses.push_back(kCmeErrorSimNotInserted); |
| } |
| client.SendCommandResponse(responses); |
| } |
| |
| /** |
| * AT+CRSM |
| * By using this command instead of Generic SIM Access +CSIM TE application |
| * has easier but more limited access to the SIM database. |
| * |
| * Command Possible response(s) |
| * +CRSM=<command>[,<fileid> +CRSM: <sw1>,<sw2>[,<response>] |
| * [,<P1>,<P2>,<P3>[,<data>[,<pathid>]]]] +CME ERROR: <err> |
| * |
| * <command>: (command passed on by the MT to the SIM; refer 3GPP TS 51.011 [28]): |
| * 176 READ BINARY |
| * 178 READ RECORD |
| * 192 GET RESPONSE |
| * 214 UPDATE BINARY |
| * 220 UPDATE RECORD |
| * 242 STATUS |
| * 203 RETRIEVE DATA |
| * 219 SET DATA |
| * |
| * <fileid>: integer type; this is the identifier of a elementary datafile on SIM. |
| * Mandatory for every command except STATUS. |
| * |
| * <P1>, <P2>, <P3>: integer type; parameters passed on by the MT to the SIM. |
| * These parameters are mandatory for every command, |
| * except GET RESPONSE and STATUS. |
| * |
| * <data>: information which shall be written to the SIM (hexadecimal character format). |
| * |
| * <pathid>: string type; contains the path of an elementary file on the SIM/UICC |
| * in hexadecimal format. |
| * |
| * <sw1>, <sw2>: integer type; information from the SIM about the execution of |
| * the actual command. |
| * |
| * <response>: response of a successful completion of the command previously issued |
| * (hexadecimal character format; refer +CSCS). |
| */ |
| void SimService::HandleSIM_IO(const Client& client, |
| const std::string& command) { |
| std::vector<std::string> kFileNotFoud = {"+CRSM: 106,130", "OK"}; |
| std::vector<std::string> responses; |
| |
| CommandParser cmd(command); |
| cmd.SkipPrefix(); // skip "AT+CRSM=" |
| |
| if (*cmd == "242,0,0,0,0") { // for cts teset |
| responses.push_back("+CRSM: 144,0,62338202782183023F00A50C80016187010183040007DBF08A01058B062F0601020002C60C90016083010183010A83010D8102FFFF"); |
| responses.push_back("OK"); |
| client.SendCommandResponse(responses); |
| return; |
| } |
| |
| auto c = cmd.GetNextStrDeciToHex(); |
| auto id = cmd.GetNextStrDeciToHex(); |
| auto p1 = cmd.GetNextStrDeciToHex(); |
| auto p2 = cmd.GetNextStrDeciToHex(); |
| auto p3 = cmd.GetNextStrDeciToHex(); |
| |
| auto data = cmd.GetNextStr(','); |
| std::string path(cmd.GetNextStr()); |
| |
| XMLElement *root = sim_file_system_.GetRootElement(); |
| if (!root) { |
| LOG(ERROR) << "Unable to find root element: IccProfile"; |
| client.SendCommandResponse(kCmeErrorOperationNotAllowed); |
| return; |
| } |
| |
| SimFileSystem::EFId fileid = (SimFileSystem::EFId)std::stoi(id, nullptr, 16); |
| if (path == "") { |
| path = SimFileSystem::GetUsimEFPath(fileid); |
| } |
| // EF_ADN under DF_PHONEBOOK is mapped to EF_ADN under DF_TELECOM per |
| // 3GPP TS 31.102 4.4.2 |
| if (fileid == SimFileSystem::EF_ADN && |
| path == SimFileSystem::GetUsimEFPath(fileid)) { |
| id = "4F3A"; |
| path = MF_SIM + DF_TELECOM + DF_PHONEBOOK; |
| } |
| |
| size_t pos = 0; |
| auto parent = root; |
| while (pos < path.length()) { |
| std::string sub_path(path.substr(pos, 4)); |
| auto app = SimFileSystem::FindAttribute(parent, "path", sub_path); |
| if (!app) { |
| client.SendCommandResponse(kFileNotFoud); |
| return; |
| } |
| pos += 4; |
| parent = app; |
| } |
| |
| XMLElement* ef = SimFileSystem::FindAttribute(parent, "id", id); |
| if (!ef) { |
| client.SendCommandResponse(kFileNotFoud); |
| return; |
| } |
| |
| XMLElement *final = ef->FirstChildElement("SIMIO"); |
| while (final) { |
| const XMLAttribute *attr_cmd = final->FindAttribute("cmd"); |
| const XMLAttribute *attr_p1 = final->FindAttribute("p1"); |
| const XMLAttribute *attr_p2 = final->FindAttribute("p2"); |
| const XMLAttribute *attr_p3 = final->FindAttribute("p3"); |
| const XMLAttribute *attr_data = final->FindAttribute("data"); |
| |
| if (c != "DC" && c != "D6") { // Except UPDATE RECORD or UPDATE BINARY |
| if ((attr_cmd && attr_cmd->Value() != c) || |
| (attr_data && attr_data->Value() != data)) { |
| final = final->NextSiblingElement("SIMIO"); |
| continue; |
| } |
| } |
| if (attr_p1 && attr_p1->Value() == p1 && |
| attr_p2 && attr_p2->Value() == p2 && |
| attr_p3 && attr_p3->Value() == p3) { |
| break; |
| } |
| final = final->NextSiblingElement("SIMIO"); |
| } |
| |
| if (!final) { |
| client.SendCommandResponse(kFileNotFoud); |
| return; |
| } |
| |
| std::string response = "+CRSM: "; |
| if (c == "DC" || c == "D6") { |
| std::string temp = "144,0,"; |
| temp += data; |
| final->SetText(temp.c_str()); |
| sim_file_system_.doc.SaveFile(sim_file_system_.file_path.c_str()); |
| response.append("144,0"); |
| } else { |
| response.append(final->GetText()); |
| } |
| |
| responses.push_back(response); |
| responses.push_back("OK"); |
| client.SendCommandResponse(responses); |
| } |
| |
| void SimService::OnSimStatusChanged() { |
| auto ptr = network_service_; |
| if (ptr) { |
| ptr->OnSimStatusChanged(sim_status_); |
| } |
| } |
| |
| bool SimService::checkPin1AndAdjustSimStatus(std::string_view pin) { |
| if (pin1_status_.VerifyPIN(pin) == true) { |
| sim_status_ = SIM_STATUS_READY; |
| OnSimStatusChanged(); |
| return true; |
| } |
| |
| if (pin1_status_.pin_remaining_times_ <= 0) { |
| sim_status_ = SIM_STATUS_PUK; |
| OnSimStatusChanged(); |
| } |
| |
| return false; |
| } |
| |
| /* AT+CSIM */ |
| void SimService::HandleCSIM_IO(const Client& client, |
| const std::string& command) { |
| std::vector<std::string> responses; |
| |
| CommandParser cmd(command); |
| cmd.SkipPrefix(); // skip "AT+CSIM=" |
| |
| cmd.SkipComma(); |
| auto data = cmd.GetNextStr(); |
| |
| XMLElement *root = sim_file_system_.GetRootElement(); |
| if (!root) { |
| LOG(ERROR) << "Unable to find root element: IccProfile"; |
| client.SendCommandResponse(kCmeErrorOperationNotAllowed); |
| return; |
| } |
| // Get aid |
| XMLElement* df = SimFileSystem::FindAttribute(root, "aid", "CSIM"); |
| if (!df) { |
| client.SendCommandResponse(kCmeErrorNotFound); |
| return; |
| } |
| |
| std::string data_value(data); |
| if (data_value.length() > 10) { // for open channel with csim |
| responses.push_back("+CSIM: 4,9000"); |
| responses.push_back("OK"); |
| client.SendCommandResponse(responses); |
| return; |
| } |
| XMLElement* final = SimFileSystem::FindAttribute(df, "cmd", data_value); |
| if (!final) { |
| client.SendCommandResponse(kCmeErrorNotFound); |
| return; |
| } |
| |
| auto id = data_value.substr(data_value.length() - 2, 2); |
| |
| std::vector<LogicalChannel>::iterator iter = logical_channels_.begin(); |
| for (; iter != logical_channels_.end(); ++iter) { |
| if (!iter->is_open) break; |
| } |
| |
| if (iter != logical_channels_.end() && iter->session_id ==stoi(id)) { |
| iter->is_open = true; |
| iter->df_name = "CSIM"; |
| } |
| |
| std::stringstream ss; |
| ss << "+CSIM: " << final->GetText(); |
| |
| responses.push_back(ss.str()); |
| responses.push_back("OK"); |
| client.SendCommandResponse(responses); |
| } |
| |
| bool SimService::ChangePin1AndAdjustSimStatus(PinStatus::ChangeMode mode, |
| std::string_view pin, |
| std::string_view new_pin) { |
| if (pin1_status_.ChangePIN(mode, pin, new_pin) == true) { |
| sim_status_ = SIM_STATUS_READY; |
| OnSimStatusChanged(); |
| return true; |
| } |
| if (sim_status_ == SIM_STATUS_READY && pin1_status_.pin_remaining_times_ <= 0) { |
| sim_status_ = SIM_STATUS_PIN; |
| OnSimStatusChanged(); |
| } else if (sim_status_ == SIM_STATUS_PIN && pin1_status_.puk_remaining_times_ <= 0) { |
| sim_status_ = SIM_STATUS_ABSENT; |
| OnSimStatusChanged(); |
| } |
| return false; |
| } |
| |
| void SimService::HandleChangeOrEnterPIN(const Client& client, |
| const std::string& command) { |
| std::vector<std::string> responses; |
| |
| CommandParser cmd(command); |
| cmd.SkipPrefix(); // skip "AT+CPIN=" |
| switch (sim_status_) { |
| case SIM_STATUS_ABSENT: |
| responses.push_back(kCmeErrorSimNotInserted); |
| break; |
| case SIM_STATUS_NOT_READY: |
| responses.push_back(kCmeErrorSimBusy); |
| break; |
| case SIM_STATUS_READY: { |
| /* |
| * this may be a request to change the PIN with pin and new pin: |
| * AT+CPIN=pin,newpin |
| * or a request to enter the PIN2 |
| * AT+CPIN=pin2 |
| */ |
| auto pos = cmd->find(','); |
| if (pos != std::string_view::npos) { // change pin with new pin |
| auto pin = cmd.GetNextStr(','); |
| auto new_pin = *cmd; |
| |
| if (ChangePin1AndAdjustSimStatus(PinStatus::WITH_PIN, pin, new_pin)) { |
| responses.push_back("OK"); |
| } else { |
| responses.push_back(kCmeErrorIncorrectPassword); /* incorrect PIN */ |
| } |
| } else { // verify pin2 |
| if (pin2_status_.VerifyPIN(*cmd) == true) { |
| responses.push_back("OK"); |
| } else { |
| responses.push_back(kCmeErrorIncorrectPassword); /* incorrect PIN2 */ |
| } |
| } |
| break; |
| } |
| case SIM_STATUS_PIN: { /* waiting for PIN */ |
| if (checkPin1AndAdjustSimStatus(*cmd) == true) { |
| responses.push_back("OK"); |
| } else { |
| responses.push_back(kCmeErrorIncorrectPassword); |
| } |
| break; |
| } |
| case SIM_STATUS_PUK: { |
| /* |
| * this may be a request to unlock the puk with new pin: |
| * AT+CPIN=puk,newpin |
| */ |
| auto pos = cmd->find(','); |
| if (pos != std::string_view::npos) { |
| auto puk = cmd.GetNextStr(','); |
| auto new_pin = *cmd; |
| if (ChangePin1AndAdjustSimStatus(PinStatus::WITH_PUK, puk, new_pin)) { |
| responses.push_back("OK"); |
| } else { |
| responses.push_back(kCmeErrorIncorrectPassword); |
| } |
| } else { |
| responses.push_back(kCmeErrorOperationNotAllowed); |
| } |
| break; |
| } |
| default: |
| responses.push_back(kCmeErrorOperationNotAllowed); |
| break; |
| } |
| |
| client.SendCommandResponse(responses); |
| } |
| |
| /** |
| * AT+CIMI |
| * Execution command causes the TA to return <IMSI>, which is intended to |
| * permit the TE to identify the individual SIM card or active application in |
| * the UICC (GSM or USIM) which is attached to MT. |
| * |
| * Command Possible response(s) |
| * +CIMI <IMSI> |
| * +CME ERROR: <err> |
| * |
| * <IMSI>: International Mobile Subscriber Identity (string without double quotes) |
| * |
| * see RIL_REQUEST_GET_IMSI in RIL |
| */ |
| void SimService::HandleGetIMSI(const Client& client) { |
| std::vector<std::string> responses; |
| |
| XMLElement *root = sim_file_system_.GetRootElement(); |
| if (!root) { |
| client.SendCommandResponse(kCmeErrorOperationNotAllowed); |
| return; |
| } |
| |
| XMLElement* mf = SimFileSystem::FindAttribute(root, "path", MF_SIM); |
| if (!mf) { |
| client.SendCommandResponse(kCmeErrorNotFound); |
| return; |
| } |
| |
| XMLElement* df = SimFileSystem::FindAttribute(mf, "path", DF_ADF); |
| if (!df) { |
| client.SendCommandResponse(kCmeErrorNotFound); |
| return; |
| } |
| |
| XMLElement* ef = SimFileSystem::FindAttribute(df, "id", "6F07"); |
| if (!ef) { |
| client.SendCommandResponse(kCmeErrorNotFound); |
| return; |
| } |
| |
| XMLElement *final = ef->FirstChildElement("CIMI"); |
| if (!final) { |
| client.SendCommandResponse(kCmeErrorNotFound); |
| return; |
| } |
| |
| responses.push_back(final->GetText()); |
| responses.push_back("OK"); |
| client.SendCommandResponse(responses); |
| } |
| |
| /** |
| * AT+CICCID |
| * Integrated Circuit Card IDentifier (ICCID) is Unique Identifier of the SIM CARD. |
| * File is located in the SIM card at EFiccid (0x2FE2). |
| * |
| * see RIL_REQUEST_GET_SIM_STATUS in RIL |
| */ |
| void SimService::HandleGetIccId(const Client& client) { |
| std::vector<std::string> responses; |
| |
| XMLElement *root = sim_file_system_.GetRootElement(); |
| if (!root) { |
| client.SendCommandResponse(kCmeErrorOperationNotAllowed); |
| return; |
| } |
| |
| XMLElement* mf = SimFileSystem::FindAttribute(root, "path", MF_SIM); |
| if (!mf) { |
| client.SendCommandResponse(kCmeErrorNotFound); |
| return; |
| } |
| |
| XMLElement* ef = SimFileSystem::FindAttribute(mf, "id", "2FE2"); |
| if (!ef) { |
| client.SendCommandResponse(kCmeErrorNotFound); |
| return; |
| } |
| |
| XMLElement *final = ef->FirstChildElement("CCID"); |
| if (!final) { |
| client.SendCommandResponse(kCmeErrorNotFound); |
| return; |
| } |
| |
| responses.push_back(final->GetText()); |
| responses.push_back("OK"); |
| client.SendCommandResponse(responses); |
| } |
| |
| /* |
| * AT+CLCK |
| * Execute command is used to lock, unlock or interrogate a MT or a network |
| * facility <fac>. |
| * |
| * Command Possible response(s) |
| * +CLCK=<fac>, <mode> [, <password> OK or +CME ERROR: <err> |
| * [, <class>]] +CLCK: <status>[,<class1>[<CR><LF>+CLCK: |
| * <status>,<class2>[...]](when mode=2,it’s |
| * in inquiry status.) |
| * <fac> values reserved by the present document: |
| * "SC": SIM (lock SIM/UICC card installed in the currently selected card |
| * slot) (SIM/UICC asks password in MT power‑up and when this lock |
| * command issued). |
| * "FD": SIM card or active application in the UICC (GSM or USIM) fixed |
| * dialling memory feature (if PIN2 authentication has not been done |
| * during the current session, PIN2 is required as <passwd>). |
| * <mode>: integer type |
| * 0: unlock |
| * 1: lock |
| * 2: query status |
| * <status>: integer type |
| * 0: not active |
| * 1: active |
| * <passwd>: string type; shall be the same as password specified for the |
| * facility from the MT user interface or with command |
| * Change Password +CPWD. |
| * <classx> is a sum of integers each representing a class of information |
| * (default 7 - voice, data and fax): |
| * 1 voice (telephony) |
| * 2 data |
| * 4 fax (facsimile services) |
| * 8 short message service |
| * 16 data circuit sync |
| * 32 data circuit async |
| * 64 dedicated packet access |
| * 128 dedicated PAD access |
| * |
| * see RIL_REQUEST_SET_FACILITY_LOCK in RIL |
| */ |
| void SimService::HandleFacilityLock(const Client& client, |
| const std::string& command) { |
| CommandParser cmd(command); |
| std::string lock(cmd.GetNextStr()); |
| int mode = cmd.GetNextInt(); |
| auto password = cmd.GetNextStr(); |
| // Ignore class from RIL |
| |
| auto iter = facility_lock_.find(lock); |
| if (iter == facility_lock_.end()) { |
| client.SendCommandResponse(kCmeErrorOperationNotSupported); |
| return; |
| } |
| |
| std::stringstream ss; |
| std::vector<std::string> responses; |
| switch (mode) { |
| case FacilityLock::Mode::QUERY: { |
| ss << "+CLCK: " << iter->second.lock_status; |
| responses.push_back(ss.str()); |
| responses.push_back("OK"); |
| break; |
| } |
| case FacilityLock::Mode::LOCK: |
| case FacilityLock::Mode::UNLOCK: { |
| if (lock == "SC") { |
| if (checkPin1AndAdjustSimStatus(password) == true) { |
| iter->second.lock_status = (FacilityLock::LockStatus)mode; |
| responses.push_back("OK"); |
| } else { |
| responses.push_back(kCmeErrorIncorrectPassword); |
| } |
| } else if (lock == "FD") { |
| if (pin2_status_.VerifyPIN(password) == true) { |
| iter->second.lock_status = (FacilityLock::LockStatus)mode; |
| responses.push_back("OK"); |
| } else { |
| responses.push_back(kCmeErrorIncorrectPassword); |
| } |
| } else { // Don't need password except 'SC' and 'FD' |
| iter->second.lock_status = (FacilityLock::LockStatus)mode; |
| responses.push_back("OK"); |
| } |
| break; |
| } |
| default: |
| responses.push_back(kCmeErrorInCorrectParameters); |
| break; |
| } |
| |
| client.SendCommandResponse(responses); |
| } |
| |
| /** |
| * AT+CCHO |
| * The currently selected UICC will open a new logical channel; select the |
| * application identified by the <dfname> received with this command and return |
| * a session Id as the response. |
| * |
| * Command Possible response(s) |
| * +CCHO=<dfname> <sessionid> |
| * +CME ERROR: <err> |
| * |
| * <dfname>: all selectable applications in the UICC are referenced by a DF |
| * name coded on 1 to 16 bytes. |
| * <sessionid>: integer type; a session Id to be used in order to target a |
| * specific application on the smart card (e.g. (U)SIM, WIM, ISIM) |
| * using logical channels mechanism. |
| * |
| * see RIL_REQUEST_SIM_OPEN_CHANNEL in RIL |
| */ |
| void SimService::HandleOpenLogicalChannel(const Client& client, |
| const std::string& command) { |
| std::vector<std::string> responses; |
| |
| CommandParser cmd(command); |
| cmd.SkipPrefix(); // skip AT+CCHO= |
| if (cmd->empty()) { |
| client.SendCommandResponse(kCmeErrorInCorrectParameters); |
| return; |
| } |
| |
| XMLElement *root = sim_file_system_.GetRootElement(); |
| if (!root) { |
| client.SendCommandResponse(kCmeErrorOperationNotAllowed); |
| return; |
| } |
| |
| std::string aid_value(*cmd); |
| XMLElement* df = SimFileSystem::FindAttribute(root, "aid", aid_value); |
| if (!df) { |
| client.SendCommandResponse(kCmeErrorNotFound); |
| return; |
| } |
| |
| std::vector<LogicalChannel>::iterator iter = logical_channels_.begin(); |
| for (; iter != logical_channels_.end(); ++iter) { |
| if (!iter->is_open) break; |
| } |
| |
| if (iter != logical_channels_.end()) { |
| iter->is_open = true; |
| iter->df_name = *cmd; |
| |
| std::stringstream ss; |
| ss << iter->session_id; |
| responses.push_back(ss.str()); |
| responses.push_back("OK"); |
| } else { |
| responses.push_back(kCmeErrorMemoryFull); |
| } |
| |
| client.SendCommandResponse(responses); |
| } |
| |
| /** |
| * AT+CCHC |
| * This command asks the ME to close a communication session with the active |
| * UICC. |
| * |
| * Command Possible response(s) |
| * +CCHC=<sessionid> +CCHC |
| * +CME ERROR: <err> |
| * <sessionid>: see AT+CCHO |
| * |
| * see RIL_REQUEST_SIM_CLOSE_CHANNEL in RIL |
| */ |
| void SimService::HandleCloseLogicalChannel(const Client& client, |
| const std::string& command) { |
| std::vector<std::string> responses; |
| |
| CommandParser cmd(command); |
| cmd.SkipPrefix(); // skip AT+CCHC= |
| |
| int session_id = cmd.GetNextInt(); |
| std::vector<LogicalChannel>::iterator iter = logical_channels_.begin(); |
| for (; iter != logical_channels_.end(); ++iter) { |
| if (iter->session_id == session_id) break; |
| } |
| |
| if (iter != logical_channels_.end() && iter->is_open) { |
| iter->is_open = false; |
| iter->df_name.clear(); |
| responses.push_back("+CCHC"); |
| responses.push_back("OK"); |
| } else { |
| responses.push_back(kCmeErrorNotFound); |
| } |
| client.SendCommandResponse(responses); |
| } |
| |
| /** |
| * AT+CGLA |
| * Set command transmits to the MT the <command> it then shall send as it is |
| * to the selected UICC. In the same manner the UICC <response> shall be sent |
| * back by the MT to the TA as it is. |
| * |
| * Command Possible response(s) |
| * +CGLA=<sessionid>,<length>, +CGLA: <length>,<response> |
| * +CME ERROR: <err> |
| * <sessionid>: AT+CCHO |
| * <length>: integer type; length of the characters that are sent to TE in |
| * <command> or <response> . |
| * <command>: command passed on by the MT to the UICC in the format as described |
| * in 3GPP TS 31.101 [65] (hexadecimal character format; refer +CSCS). |
| * <response>: response to the command passed on by the UICC to the MT in the |
| * format as described in 3GPP TS 31.101 [65] (hexadecimal character |
| * format; refer +CSCS). |
| * |
| * see RIL_REQUEST_SIM_TRANSMIT_APDU_CHANNEL in RIL |
| */ |
| void SimService::HandleTransmitLogicalChannel(const Client& client, |
| const std::string& command) { |
| std::vector<std::string> responses; |
| |
| CommandParser cmd(command); |
| cmd.SkipPrefix(); // skip AT+CGLA= |
| |
| int session_id = cmd.GetNextInt(); |
| int length = cmd.GetNextInt(); |
| if (cmd->length() != length) { |
| client.SendCommandResponse(kCmeErrorInCorrectParameters); |
| return; |
| } |
| |
| // Check if session id is opened |
| auto iter = logical_channels_.begin(); |
| for (; iter != logical_channels_.end(); ++iter) { |
| if (iter->session_id == session_id && iter->is_open) { |
| break; |
| } |
| } |
| |
| if (iter == logical_channels_.end()) { |
| client.SendCommandResponse(kCmeErrorInvalidIndex); |
| return; |
| } |
| |
| XMLElement *root = sim_file_system_.GetRootElement(); |
| if (!root) { |
| client.SendCommandResponse(kCmeErrorOperationNotAllowed); |
| return; |
| } |
| |
| // Get aid |
| XMLElement* df = SimFileSystem::FindAttribute(root, "aid", iter->df_name); |
| if (!df) { |
| client.SendCommandResponse(kCmeErrorNotFound); |
| return; |
| } |
| |
| if (iter->df_name != "CSIM") { |
| std::string command_vaule(*cmd); |
| if (command_vaule.substr(2, 2) == "a4") { |
| last_file_id_ = command_vaule.substr(command_vaule.length() - 4, 4); |
| } |
| df = SimFileSystem::FindAttribute(df, "id", last_file_id_); |
| if (!df) { |
| client.SendCommandResponse(kCmeErrorNotFound); |
| return; |
| } |
| } |
| |
| std::string attr_value(*cmd); |
| XMLElement* final = SimFileSystem::FindAttribute(df, "cmd", attr_value); |
| if (!final) { |
| client.SendCommandResponse(kCmeErrorNotFound); |
| return; |
| } |
| |
| std::stringstream ss; |
| ss << "+CGLA: " << final->GetText(); |
| responses.push_back(ss.str()); |
| responses.push_back("OK"); |
| client.SendCommandResponse(responses); |
| } |
| |
| /** |
| * AT+CPWD |
| * Action command sets a new password for the facility lock function defined |
| * by command Facility Lock +CLCK |
| * |
| * Command Possible response(s) |
| * +CPWD=<fac>,<oldpwd>,<newpwd> +CME ERROR: <err> |
| * |
| * <fac>: |
| * "P2" SIM PIN2 |
| * refer Facility Lock +CLCK for other values |
| * <oldpwd>, <newpwd>: |
| * string type; <oldpwd> shall be the same as password specified for the |
| * facility from the MT user interface or with command Change Password +CPWD |
| * and <newpwd> is the new password; maximum length of password can be determined |
| * with <pwdlength> |
| * <pwdlength>: integer type maximum length of the password for the facility |
| */ |
| void SimService::HandleChangePassword(const Client& client, |
| const std::string& command) { |
| std::string response = kCmeErrorIncorrectPassword; |
| |
| CommandParser cmd(command); |
| cmd.SkipPrefix(); |
| auto lock = cmd.GetNextStr(); |
| auto old_password = cmd.GetNextStr(); |
| auto new_password = cmd.GetNextStr(); |
| |
| if (lock == "SC") { |
| if (ChangePin1AndAdjustSimStatus(PinStatus::WITH_PIN, old_password, new_password)) { |
| response = "OK"; |
| } |
| } else if (lock == "P2" || lock == "FD") { |
| if (pin2_status_.ChangePIN(PinStatus::WITH_PIN, old_password, new_password)) { |
| response = "OK"; |
| } |
| } else { |
| response = kCmeErrorOperationNotSupported;; |
| } |
| |
| client.SendCommandResponse(response); |
| } |
| |
| /** |
| * AT+CPINR |
| * Execution command cause the MT to return the number of remaining PIN retries |
| * for the MT passwords with intermediate result code |
| * |
| * Command Possible response(s) |
| * +CPINR[=<sel_code>] +CPINR: <code>,<retries>[,<default_retries>] |
| * |
| * <retries>: |
| * integer type. Number of remaining retries per PIN. |
| * <default_retries>: |
| * integer type. Number of default/initial retries per PIN. |
| * <code>: |
| * Type of PIN. All values listed under the description of the AT+CPIN command |
| * <sel_code>: String type. Same values as for the <code> and <ext_code> parameters. |
| * these values are strings and shall be indicated within double quotes. |
| */ |
| void SimService::HandleQueryRemainTimes(const Client& client, |
| const std::string& command) { |
| std::vector<std::string> responses; |
| std::stringstream ss; |
| |
| CommandParser cmd(command); |
| cmd.SkipPrefix(); |
| auto lock_type = cmd.GetNextStr(); |
| |
| if (lock_type == "SIM PIN") { |
| ss << "+CPINR: SIM PIN," << pin1_status_.pin_remaining_times_ << "," |
| << kSimPinMaxRetryTimes; |
| } else if (lock_type == "SIM PUK") { |
| ss << "+CPINR: SIM PUK," << pin1_status_.puk_remaining_times_ << "," |
| << kSimPukMaxRetryTimes; |
| } else if (lock_type == "SIM PIN2") { |
| ss << "+CPINR: SIM PIN2," << pin2_status_.pin_remaining_times_ << "," |
| << kSimPinMaxRetryTimes; |
| } else if (lock_type == "SIM PUK2") { |
| ss << "+CPINR: SIM PUK2," << pin2_status_.puk_remaining_times_ << "," |
| << kSimPukMaxRetryTimes; |
| } else { |
| responses.push_back(kCmeErrorInCorrectParameters); |
| client.SendCommandResponse(responses); |
| return; |
| } |
| |
| responses.push_back(ss.str()); |
| responses.push_back("OK"); |
| client.SendCommandResponse(responses); |
| } |
| |
| /** |
| * see |
| * RIL_REQUEST_CDMA_SET_SUBSCRIPTION or |
| * RIL_REQUEST_CDMA_GET_SUBSCRIPTION in RIL |
| */ |
| void SimService::HandleCdmaSubscriptionSource(const Client& client, |
| const std::string& command) { |
| std::vector<std::string> responses; |
| |
| CommandParser cmd(command); |
| if (*cmd == "AT+CCSS?") { // Query |
| std::stringstream ss; |
| ss << "+CCSS: " << cdma_subscription_source_; |
| responses.push_back(ss.str()); |
| } else { // Set |
| cdma_subscription_source_ = cmd.GetNextInt(); |
| } |
| responses.push_back("OK"); |
| client.SendCommandResponse(responses); |
| } |
| |
| /** |
| * see |
| * RIL_REQUEST_CDMA_SET_ROAMNING_PREFERENCE or |
| * RIL_REQUEST_CDMA_GET_ROAMNING_PREFERENCE in RIL |
| */ |
| void SimService::HandleCdmaRoamingPreference(const Client& client, |
| const std::string& command) { |
| std::vector<std::string> responses; |
| |
| CommandParser cmd(command); |
| if (*cmd == "AT+WRMP?") { // Query |
| std::stringstream ss; |
| ss << "+WRMP: " << cdma_roaming_preference_; |
| responses.push_back(ss.str()); |
| } else { // Set |
| cdma_roaming_preference_ = cmd.GetNextInt(); |
| } |
| responses.push_back("OK"); |
| client.SendCommandResponse(responses); |
| } |
| |
| void SimService::HandleSimAuthentication(const Client& client, |
| const std::string& command) { |
| std::vector<std::string> responses; |
| |
| CommandParser cmd(command); |
| cmd.SkipPrefix(); |
| |
| // Input format: ^MBAU=<RAND>[,<AUTN>] |
| auto cmds = cmd.GetNextStr(); |
| // Output format: ^MBAU: <STATUS>[,<KC>,<SRES>][,<CK>,<IK>,<RES/AUTS>] |
| std::stringstream ss; |
| |
| // Authentication challenges done in CTS. |
| if (cmds == "2713AB0BA8E8E7D8F1D74545BA03F563") { |
| // CarrierApiTest#testGetIccAuthentication (base64Challenge) |
| ss << "^MBAU: 0,8F2980FC3872FF89,E9620240"; |
| } else if (cmds == "C3718EC16B3C2A66F8A7200A64069F04") { |
| // CarrierApiTest#testGetIccAuthentication (base64Challenge2) |
| ss << "^MBAU: 0,CFDA6C980502DA48,F7E53577"; |
| } else if (cmds == "11111111111111111111111111111111") { |
| // CarrierApiTest#testEapSimAuthentication |
| ss << "^MBAU: 0,0000000000000000,00000000"; |
| } else if (cmds == "11111111111111111111111111111111,12351417161900001130131215141716") { |
| // CarrierApiTest#testEapAkaAuthentication |
| // Note: the "DB" prefix gets appended where the RIL parses this response. |
| ss << "^MBAU: 0,111013121514171619181B1A1D1C1F1E,1013121514171619181B1A1D1C1F1E11," |
| "13121514171619181B1A1D1C1F1E1110"; |
| } |
| |
| responses.push_back(ss.str()); |
| responses.push_back("OK"); |
| client.SendCommandResponse(responses); |
| } |
| |
| |
| } // namespace cuttlefish |