// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "PacketData.hpp" #include #include #include #include #include namespace at { namespace response { bool parseQICSGP(const at::Result &resp, std::shared_ptr retAPN) { /// AT+QICSGP=[,,[,,)[,[,]]]] /// +QICSGP: ,,,, /// +QICSGP: 1,"","","",0 constexpr auto AT_QICSGP = "+QICSGP:"; if (auto tokens = at::response::getTokensForATCommand(resp, AT_QICSGP); tokens) { constexpr int QICSGP_TokensCount = 5; /// Could be depend ? on firmware version, assume that always have /// ,,,, if ((*tokens).size() == QICSGP_TokensCount) { retAPN->contextType = static_cast(utils::getNumericValue((*tokens)[0])); retAPN->apn = (*tokens)[1]; utils::findAndReplaceAll(retAPN->apn, at::response::StringDelimiter, ""); retAPN->username = (*tokens)[2]; utils::findAndReplaceAll(retAPN->username, at::response::StringDelimiter, ""); retAPN->password = (*tokens)[3]; utils::findAndReplaceAll(retAPN->password, at::response::StringDelimiter, ""); retAPN->authMethod = static_cast(utils::getNumericValue((*tokens)[4])); return true; } } return false; } bool parseQIACT(const at::Result &resp, std::vector> &ret) { /** * In case of AT+QIACT? * +QIACT:1,,[,] * [..... * +QIACT:16,,[,]] * * also could be empty list */ constexpr int QIACT_TokensCount = 3; constexpr int QIACT_WithIP_TokensCount = 4; constexpr auto AT_QIACT = "+QIACT:"; if (auto mtokens = at::response::getTokensForATResults(resp, AT_QIACT); mtokens) { for (const auto &tokens : *mtokens) { if (tokens.size() < QIACT_TokensCount) { continue; } std::shared_ptr retAPN = std::make_shared(); retAPN->contextId = utils::getNumericValue(tokens[0]); retAPN->state = static_cast(utils::getNumericValue(tokens[1])); retAPN->contextType = static_cast(utils::getNumericValue(tokens[2])); if (tokens.size() >= QIACT_WithIP_TokensCount) { retAPN->ip = tokens[3]; utils::findAndReplaceAll(retAPN->ip, at::response::StringDelimiter, ""); } ret.push_back(retAPN); } return true; /// empty list is also good result } return false; } } // namespace response namespace query { std::string prepareQICSGP(std::shared_ptr apn, bool setEmpty) { /// AT+QICSGP=[,,[,,)[,]]] if (setEmpty) { LOG_DEBUG("Set empty APN"); return at::factory(at::AT::QICSGP) + "=" + utils::to_string(static_cast(apn->contextId)) + "," + "1,\"\",\"\",\"\",1"; } return at::factory(at::AT::QICSGP) + "=" + utils::to_string(static_cast(apn->contextId)) + "," + utils::to_string(static_cast(apn->contextType)) + ",\"" + apn->apn + "\",\"" + apn->username + "\",\"" + apn->password + "\"," + utils::to_string(static_cast(apn->authMethod)); } } // namespace query } // namespace at namespace packet_data { PDPContext::PDPContext(ServiceCellular &cellularService) : cellularService(cellularService) { channel = cellularService.cmux->get(TS0710::Channel::Commands); } std::shared_ptr PDPContext::getConfiguration(std::uint8_t contextId) { if (channel) { auto resp = channel->cmd(at::factory(at::AT::QICSGP) + "=" + utils::to_string(static_cast(contextId))); if (resp.code == at::Result::Code::OK) { std::shared_ptr ret = std::make_shared(); ret->contextId = contextId; if (at::response::parseQICSGP(resp, ret)) { return ret; } } } return nullptr; } at::Result::Code PDPContext::setConfiguration(std::shared_ptr apn, bool setEmpty) { if (channel) { auto resp = channel->cmd(at::query::prepareQICSGP(apn, setEmpty)); return resp.code; } return at::Result::Code::ERROR; } at::Result::Code PDPContext::activate(std::uint8_t contextId) { if (channel) { auto resp = channel->cmd(at::factory(at::AT::QIACT) + "=" + utils::to_string(static_cast(contextId))); /** * From quectel documentation: * 1. Reboot the module if there is no response in 150s. * 2. If failed to deactivate the PDP context for 3 times continuously, then reboot the module. */ return resp.code; } return at::Result::Code::ERROR; } at::Result::Code PDPContext::deactivate(std::uint8_t contextId) { if (channel) { /// this command could generate URC, deactivate auto resp = channel->cmd(at::factory(at::AT::QIDEACT) + "=" + utils::to_string(static_cast(contextId))); return resp.code; } return at::Result::Code::ERROR; } std::optional>> PDPContext::getActive() { if (channel) { auto resp = channel->cmd(at::factory(at::AT::QIACT) + "?"); if (resp.code == at::Result::Code::OK) { std::vector> ret; if (at::response::parseQIACT(resp, ret)) { return ret; } } } return std::nullopt; } PacketData::PacketData(ServiceCellular &cellularService) : cellularService(cellularService){}; void PacketData::loadAPNSettings() { std::shared_ptr apnConfig = std::make_shared(); apnConfig->contextId = 1; apnConfig->apnType = APN::APNType::Default; apnConfig->apn = "internet"; apnConfig->authMethod = APN::AuthMethod::NONE; contextMap[apnConfig->contextId] = apnConfig; LOG_ERROR("loadAPNSettings"); } void PacketData::saveAPNSettings() { /// Save in phone memory } at::Result::Code PacketData::updateAPNSettings(std::uint8_t ctxId) { LOG_DEBUG("updateAPNSettings %d", ctxId); PDPContext pdpCtx(cellularService); std::shared_ptr apnConfig; std::shared_ptr modemApn; if ((modemApn = pdpCtx.getConfiguration(ctxId)) && (modemApn != nullptr)) { auto phoneApn = contextMap.find(ctxId); if (phoneApn != contextMap.end()) { LOG_DEBUG("Phone context exists"); if (dataTransfer == DataTransfer::Off) { /// set null configuration, solution based on lack of quectel documentation return pdpCtx.setConfiguration(phoneApn->second, true); } else { if (!phoneApn->second->compare(modemApn)) { /// rebuild configuratio LOG_DEBUG("Update modem context %d", ctxId); return pdpCtx.setConfiguration(phoneApn->second); } } } else { LOG_ERROR("Phone context not exists"); if (!modemApn->isEmpty()) { /** update phone configuration base on modem conf (eg. ims) * * Comment from quectel (2020.12): * As I know, only VZW MBN would use IMS on CID 1 to register IMS service directly. * So we usually ask customers to set up data connection on CID3 for VZW sim card. * Regarding other MBN files, the CID 1 shouldn’t be IMS because the CID1 is used * to activate default bearer for LTE network. So we usually ask customers to * configure their own APN on CID1. With this rule, it’s easy for customer * to configure their APN no matter which MBN file is activated */ LOG_ERROR("Update modem context %d", ctxId); return pdpCtx.setConfiguration(modemApn, true); } } } return at::Result::Code::OK; } void PacketData::setupAPNSettings() { for (std::uint8_t ctxId = MINContextId; ctxId <= MAXContextId; ctxId++) { updateAPNSettings(ctxId); } } std::optional> PacketData::getAPN(std::uint8_t ctxid) { auto apn = std::find_if(contextMap.begin(), contextMap.end(), [&ctxid](const ContextPair &pair) { return pair.second->contextId == ctxid; }); if (apn != contextMap.end()) { return apn->second; } return std::nullopt; } const std::vector> PacketData::getAPNs() const { std::vector> vconf; std::transform( contextMap.begin(), contextMap.end(), std::back_inserter(vconf), [](auto &cm) { return cm.second; }); return vconf; } std::optional> PacketData::getAPNFirst(APN::APNType type) { auto apn = std::find_if(contextMap.begin(), contextMap.end(), [&type](const ContextPair &pair) -> bool { return pair.second->apnType == type; }); if (apn != contextMap.end()) { return apn->second; } return std::nullopt; } at::Result::Code PacketData::setAPN(std::shared_ptr apn) { contextMap[apn->contextId] = apn; return updateAPNSettings(apn->contextId); } bool PacketData::setDataTransfer(DataTransfer dt) { dataTransfer = dt; setupAPNSettings(); return true; } DataTransfer PacketData::getDataTransfer() const { return dataTransfer; } std::optional>> PacketData::getActiveContexts() { PDPContext pdpCtx(cellularService); return pdpCtx.getActive(); } at::Result::Code PacketData::activateContext(std::uint8_t contextId) { PDPContext pdpCtx(cellularService); return pdpCtx.activate(contextId); } at::Result::Code PacketData::deactivateContext(std::uint8_t contextId) { PDPContext pdpCtx(cellularService); return pdpCtx.deactivate(contextId); } } // namespace packet_data