// Copyright (c) 2017-2023, 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 #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(CellularMux::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(const std::string &json) { std::string err; auto apnsObj = json11::Json::parse(json, err).array_items(); if (!err.empty()) { LOG_ERROR("Error while parsing APN configuration: %s", err.c_str()); return; } contextMap.clear(); for (auto &it : apnsObj) { std::shared_ptr apnConfig = std::make_shared(); apnConfig->from_json(it); contextMap[apnConfig->contextId] = apnConfig; } LOG_DEBUG("APN settings loaded"); } std::string PacketData::saveAPNSettings() const { /// Save as JSON string std::vector vec; std::transform(contextMap.begin(), contextMap.end(), std::back_inserter(vec), [](auto &apn) { return apn.second->to_json(); }); LOG_DEBUG("APN settings saved"); return json11::Json(vec).dump(); } at::Result::Code PacketData::updateAPNSettings(std::uint8_t ctxId) { LOG_DEBUG("Updating APN settings: %d", ctxId); PDPContext pdpCtx(cellularService); std::shared_ptr apnConfig; std::shared_ptr modemApn; if (!(modemApn = pdpCtx.getConfiguration(ctxId))) { return at::Result::Code::ERROR; } 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 configuration LOG_DEBUG("Update modem context: %d", ctxId); if (ctxId > internalAPNMaxId) { return pdpCtx.setConfiguration(phoneApn->second); } else { contextMap[ctxId] = modemApn; } } } } else { LOG_DEBUG("Phone context not exists"); if (!modemApn->isEmpty()) { /** update phone configuration base on modem conf (eg. ims) */ LOG_DEBUG("Update modem context: %d", ctxId); if (ctxId > internalAPNMaxId) { return pdpCtx.setConfiguration(modemApn, true); } else { contextMap[ctxId] = modemApn; } } } return at::Result::Code::OK; } void PacketData::setupAPNSettings() { for (std::uint8_t ctxId = MINContextId; ctxId <= MAXContextId; ctxId++) { if (updateAPNSettings(ctxId) != at::Result::Code::OK) { LOG_ERROR("Failed update APN settings for context %d", 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) { /// Only one default, save on phone configuration (not modem) if (apn->apnType == packet_data::APN::APNType::Default) { for (std::uint8_t ctxId = internalAPNMaxId + 1; ctxId <= MAXContextId; ctxId++) { auto phoneApn = contextMap.find(ctxId); if ((phoneApn != contextMap.end()) && (!phoneApn->second->isEmpty()) && (phoneApn->second->apnType == packet_data::APN::APNType::Default) && (ctxId != apn->contextId)) { contextMap[ctxId]->apnType = packet_data::APN::APNType::Internet; } } } if (apn->isEmpty()) { contextMap.erase(apn->contextId); } else { contextMap[apn->contextId] = apn; } return updateAPNSettings(apn->contextId); } at::Result::Code PacketData::newAPN(std::shared_ptr apn, std::uint8_t &newId) { for (std::uint8_t ctxId = internalAPNMaxId + 1; ctxId <= MAXContextId; ctxId++) { if (contextMap.find(ctxId) == contextMap.end()) { newId = ctxId; apn->contextId = ctxId; return setAPN(apn); } } return at::Result::Code::ERROR; } 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