/* * Copyright (C) 2025 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. */ #define FAILURE_DEBUG_PREFIX "AtResponse" #include #include #include #include #include #include "atCmds.h" #include "AtResponse.h" #include "Parser.h" #include "debug.h" #include "hexbin.h" namespace aidl { namespace android { namespace hardware { namespace radio { namespace implementation { using namespace std::literals; namespace { constexpr char kCR = '\r'; constexpr std::string_view krOKr = "\rOK\r"sv; struct ValueParser { std::string_view cmd; AtResponsePtr (*parse)(std::string_view str); bool multiline; }; struct CmdIdVisitor { std::string_view operator()(const AtResponse::OK&) const { return "OK"sv; } std::string_view operator()(const AtResponse::ERROR&) const { return "ERROR"sv; } std::string_view operator()(const AtResponse::RING&) const { return "RING"sv; } std::string_view operator()(const AtResponse::SmsPrompt&) const { return "SmsPrompt"sv; } std::string_view operator()(const AtResponse::ParseError&) const { return "ParseError"sv; } std::string_view operator()(const std::string&) const { return "string"sv; } template std::string_view operator()(const T&) const { return T::id(); } }; std::string_view ltrim(std::string_view s) { while (!s.empty()) { if (s.front() <= 0x20) { s.remove_prefix(1); } else { break; } } return s; } std::string toString(std::string_view s) { return std::string(s.data(), s.size()); } AtResponse::ParseResult parseCmds(const std::string_view str, const ValueParser* vp, const ValueParser* const vpEnd) { const std::string_view str1 = str.substr(1); // skip + or % if (str1.empty()) { return { 0, nullptr }; } bool maybeIncomplete = false; for (; vp != vpEnd; ++vp) { const std::string_view& cmd = vp->cmd; if (str1.starts_with(cmd.substr(0, str1.size()))) { size_t skipSize; std::string_view payload; if (str1.size() <= cmd.size()) { maybeIncomplete = true; continue; } else if (str1[cmd.size()] == ':') { skipSize = 1 + cmd.size() + 1; // `+CMD:` } else if (str1[cmd.size()] == '\r') { skipSize = 1 + cmd.size(); // `+CMD` } else { continue; } int consumed; if (vp->multiline) { const size_t payloadEnd = str.find(krOKr, skipSize); if (payloadEnd != str.npos) { // keep '+CMD:' and add extra '\r' to keep lines consistent payload = str.substr(0, payloadEnd + 1); consumed = payloadEnd + krOKr.size(); } else { return { 0, nullptr }; } } else { const size_t payloadEnd = str.find(kCR, skipSize); if (payloadEnd != str.npos) { payload = ltrim(str.substr(skipSize, payloadEnd - skipSize)); consumed = payloadEnd + 1; } else { return { 0, nullptr }; } } return { consumed, (*vp->parse)(payload) }; } } if (maybeIncomplete) { return { 0, nullptr }; } else { return { -1, FAILURE(nullptr) }; } } } // namespace AtResponse::ParseResult AtResponse::parse(const std::string_view str) { #define CMD(C) AtResponse::C::id(), &AtResponse::C::parse static const ValueParser plusValueParsers[] = { { CMD(CPIN), false }, { CMD(CPINR), false }, { CMD(CRSM), false }, { CMD(CFUN), false }, { CMD(CREG), false }, { CMD(CEREG), false }, { CMD(CGREG), false }, { CMD(CTEC), false }, { CMD(COPS), true }, { CMD(WRMP), false }, { CMD(CCSS), false }, { CMD(CSQ), false }, { CMD(CLCC), true }, { CMD(CCFCU), true }, { CMD(CCWA), false }, { CMD(CUSATD), false }, { CMD(CUSATP), false }, { CMD(CUSATE), false }, { CMD(CUSATT), false }, { CMD(CUSATEND), false }, { CMD(CGDCONT), true }, { CMD(CGCONTRDP), false }, { CMD(CLCK), false }, { CMD(CSIM), false }, { CMD(CGLA), false }, { CMD(CCHC), false }, { CMD(CLIP), false }, { CMD(CLIR), false }, { CMD(CMUT), false }, { CMD(WSOS), false }, { CMD(CSCA), false }, { CMD(CSCB), false }, { CMD(CMGS), false }, { CMD(CMGW), false }, { CMD(CmeError), false }, { CMD(CmsError), false }, }; static const ValueParser percentValueParsers[] = { { CMD(CTZV), false }, { CMD(CGFPCCFG), false }, }; static const ValueParser caretValueParsers[] = { { CMD(MBAU), false }, }; #undef CMD static constexpr std::string_view kRING = "RING\r"sv; if (str.starts_with(kRING)) { return { int(kRING.size()), AtResponse::make(RING()) }; } static constexpr std::string_view kCMT = "+CMT:"sv; if (str.starts_with(kCMT)) { const std::string_view trimmed = ltrim(str.substr(kCMT.size())); const auto [consumed, response] = AtResponse::CMT::parse(trimmed); if (consumed > 0) { return { int(consumed + str.size() - trimmed.size()), std::move(response) }; } else { return { consumed, nullptr }; } } static constexpr std::string_view kCDS = "+CDS:"sv; if (str.starts_with(kCDS)) { const std::string_view trimmed = ltrim(str.substr(kCDS.size())); const auto [consumed, response] = AtResponse::CDS::parse(trimmed); if (consumed > 0) { return { int(consumed + str.size() - trimmed.size()), std::move(response) }; } else { return { consumed, nullptr }; } } switch (str.front()) { case '+': return parseCmds(str, std::begin(plusValueParsers), std::end(plusValueParsers)); case '%': return parseCmds(str, std::begin(percentValueParsers), std::end(percentValueParsers)); case '^': return parseCmds(str, std::begin(caretValueParsers), std::end(caretValueParsers)); } static constexpr std::string_view kSmsPrompt = "> \r"sv; if (str.starts_with(kSmsPrompt)) { return { int(kSmsPrompt.size()), AtResponse::make(SmsPrompt()) }; } static constexpr std::string_view kOKr = "OK\r"sv; if (str.starts_with(kOKr)) { return { int(kOKr.size()), AtResponse::make(OK()) }; } static constexpr std::string_view kERRORr = "ERROR\r"sv; if (str.starts_with(kERRORr)) { return { int(kERRORr.size()), AtResponse::make(ERROR()) }; } const size_t pos = str.find(krOKr); if (pos != str.npos) { std::string value(str.begin(), str.begin() + pos); return { int(pos + krOKr.size()), make(std::move(value)) }; } return { 0, nullptr }; } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CmeError" AtResponsePtr AtResponse::CmeError::parse(const std::string_view str) { RadioError err; if (str.compare(atCmds::kCmeErrorOperationNotAllowed) == 0) { err = RadioError::OPERATION_NOT_ALLOWED; } else if (str.compare(atCmds::kCmeErrorOperationNotSupported) == 0) { err = RadioError::REQUEST_NOT_SUPPORTED; } else if (str.compare(atCmds::kCmeErrorSimNotInserted) == 0) { err = RadioError::SIM_ABSENT; } else if (str.compare(atCmds::kCmeErrorSimPinRequired) == 0) { err = RadioError::SIM_PIN2; } else if (str.compare(atCmds::kCmeErrorSimPukRequired) == 0) { err = RadioError::SIM_PUK2; } else if (str.compare(atCmds::kCmeErrorSimBusy) == 0) { err = RadioError::SIM_BUSY; } else if (str.compare(atCmds::kCmeErrorIncorrectPassword) == 0) { err = RadioError::PASSWORD_INCORRECT; } else if (str.compare(atCmds::kCmeErrorMemoryFull) == 0) { err = RadioError::SIM_FULL; } else if (str.compare(atCmds::kCmeErrorInvalidIndex) == 0) { err = RadioError::INVALID_ARGUMENTS; } else if (str.compare(atCmds::kCmeErrorNotFound) == 0) { err = RadioError::NO_SUCH_ELEMENT; } else if (str.compare(atCmds::kCmeErrorInvalidCharactersInTextString) == 0) { err = RadioError::GENERIC_FAILURE; } else if (str.compare(atCmds::kCmeErrorNoNetworkService) == 0) { err = RadioError::NO_NETWORK_FOUND; } else if (str.compare(atCmds::kCmeErrorNetworkNotAllowedEmergencyCallsOnly) == 0) { err = RadioError::NETWORK_REJECT; } else if (str.compare(atCmds::kCmeErrorInCorrectParameters) == 0) { err = RadioError::INVALID_ARGUMENTS; } else if (str.compare(atCmds::kCmeErrorNetworkNotAttachedDueToMTFunctionalRestrictions) == 0) { err = RadioError::NETWORK_REJECT; } else if (str.compare(atCmds::kCmeErrorFixedDialNumberOnlyAllowed) == 0) { err = RadioError::GENERIC_FAILURE; } else { err = RadioError::GENERIC_FAILURE; } CmeError cmeErr = { .error = err, }; return make(std::move(cmeErr)); } RadioError AtResponse::CmeError::getErrorAndLog( const char* klass, const char* func, int line) const { RLOGE("%s:%s:%d failure: %s", klass, func, line, toString(error).c_str()); return error; } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CmsError" AtResponsePtr AtResponse::CmsError::parse(const std::string_view str) { CmsError cmsErr = { .message = toString(str), }; return make(std::move(cmsErr)); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CPIN" AtResponsePtr AtResponse::CPIN::parse(const std::string_view str) { CPIN cpin; if (str == "READY"sv) { cpin.state = CPIN::State::READY; } else if (str == "SIM PIN"sv) { cpin.state = CPIN::State::PIN; } else if (str == "SIM PUK"sv) { cpin.state = CPIN::State::PUK; } else { return FAILURE_V(makeParseErrorFor(), "Can't parse: '%*.*s'", int(str.size()), int(str.size()), str.data()); } return make(std::move(cpin)); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CPINR" AtResponsePtr AtResponse::CPINR::parse(const std::string_view str) { CPINR cpinr; Parser parser(str); std::string_view unused; if (!parser(&unused, ',') (&cpinr.remainingRetryTimes).skip(',') (&cpinr.maxRetryTimes).fullMatch()) { return FAILURE_V(makeParseErrorFor(), "Can't parse: '%*.*s'", int(str.size()), int(str.size()), str.data()); } return make(std::move(cpinr)); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CRSM" AtResponsePtr AtResponse::CRSM::parse(const std::string_view str) { CRSM crsm; Parser parser(str); if (parser(&crsm.sw1).skip(',')(&crsm.sw2).hasMore()) { if (parser.skip(',').matchSoFar()) { crsm.response = toString(parser.remaining()); } else { return FAILURE_V(makeParseErrorFor(), "Can't parse: '%*.*s'", int(str.size()), int(str.size()), str.data()); } } else if (!parser.fullMatch()) { return FAILURE_V(makeParseErrorFor(), "Can't parse: '%*.*s'", int(str.size()), int(str.size()), str.data()); } return make(std::move(crsm)); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CFUN" AtResponsePtr AtResponse::CFUN::parse(const std::string_view str) { using modem::RadioState; int state; Parser parser(str); if (parser(&state).fullMatch()) { CFUN cfun = { .state = state ? RadioState::ON : RadioState::OFF, }; return make(std::move(cfun)); } else { return FAILURE_V(makeParseErrorFor(), "Can't parse: '%*.*s'", int(str.size()), int(str.size()), str.data()); } } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CREG" AtResponsePtr AtResponse::CREG::parse(const std::string_view str) { CREG creg; Parser parser(str); std::string_view areaCodeHex; std::string_view cellIdHex; int unsolMode; int state; switch (std::count(str.begin(), str.end(), ',')) { case 0: // state if (parser(&state).fullMatch()) { creg.state = static_cast(state); return make(std::move(creg)); } break; case 1: // unsolMode,state if (parser(&unsolMode).skip(',')(&state).fullMatch()) { creg.state = static_cast(state); return make(std::move(creg)); } break; case 3: // state,areaCode,cellId,networkType if (parser(&state).skip(',').skip('"')(&areaCodeHex, '"') .skip(',').skip('"')(&cellIdHex, '"').skip(',') (&creg.networkType).fullMatch()) { std::from_chars(areaCodeHex.begin(), areaCodeHex.end(), creg.areaCode, 16); std::from_chars(cellIdHex.begin(), cellIdHex.end(), creg.cellId, 16); creg.state = static_cast(state); return make(std::move(creg)); } break; case 4: // unsolMode,state,areaCode,cellId,networkType if (parser(&unsolMode).skip(',') (&state).skip(',') .skip('"')(&areaCodeHex, '"').skip(',') .skip('"')(&cellIdHex, '"').skip(',') (&creg.networkType).fullMatch()) { std::from_chars(areaCodeHex.begin(), areaCodeHex.end(), creg.areaCode, 16); std::from_chars(cellIdHex.begin(), cellIdHex.end(), creg.cellId, 16); creg.state = static_cast(state); return make(std::move(creg)); } break; } return FAILURE_V(makeParseErrorFor(), "Can't parse: '%*.*s'", int(str.size()), int(str.size()), str.data()); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CGREG" AtResponsePtr AtResponse::CGREG::parse(const std::string_view str) { CGREG cgreg; Parser parser(str); std::string_view areaCodeHex; std::string_view cellIdHex; int unsolMode; int state; switch (std::count(str.begin(), str.end(), ',')) { case 0: // state if (parser(&state).fullMatch()) { cgreg.state = static_cast(state); return make(std::move(cgreg)); } break; case 1: // unsolMode,state if (parser(&unsolMode).skip(',')(&state).fullMatch()) { cgreg.state = static_cast(state); return make(std::move(cgreg)); } break; case 3: // state,areaCode,cellId,networkType if (parser(&state).skip(',').skip('"')(&areaCodeHex, '"') .skip(',').skip('"')(&cellIdHex, '"').skip(',') (&cgreg.networkType).fullMatch()) { std::from_chars(areaCodeHex.begin(), areaCodeHex.end(), cgreg.areaCode, 16); std::from_chars(cellIdHex.begin(), cellIdHex.end(), cgreg.cellId, 16); cgreg.state = static_cast(state); return make(std::move(cgreg)); } break; case 4: // unsolMode,state,areaCode,cellId,networkType if (parser(&unsolMode).skip(',') (&state).skip(',') .skip('"')(&areaCodeHex, '"').skip(',') .skip('"')(&cellIdHex, '"').skip(',') (&cgreg.networkType).fullMatch()) { std::from_chars(areaCodeHex.begin(), areaCodeHex.end(), cgreg.areaCode, 16); std::from_chars(cellIdHex.begin(), cellIdHex.end(), cgreg.cellId, 16); cgreg.state = static_cast(state); return make(std::move(cgreg)); } break; } return FAILURE_V(makeParseErrorFor(), "Can't parse: '%*.*s'", int(str.size()), int(str.size()), str.data()); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CEREG" AtResponsePtr AtResponse::CEREG::parse(const std::string_view str) { CEREG cereg; Parser parser(str); std::string_view areaCodeHex; std::string_view cellIdHex; int unsolMode; int state; switch (std::count(str.begin(), str.end(), ',')) { case 0: // state if (parser(&state).fullMatch()) { cereg.state = static_cast(state); return make(std::move(cereg)); } break; case 1: // unsolMode,state if (parser(&unsolMode).skip(',')(&state).fullMatch()) { cereg.state = static_cast(state); return make(std::move(cereg)); } break; case 3: // state,areaCode,cellId,networkType if (parser(&state).skip(',').skip('"')(&areaCodeHex, '"') .skip(',').skip('"')(&cellIdHex, '"').skip(',') (&cereg.networkType).fullMatch()) { std::from_chars(areaCodeHex.begin(), areaCodeHex.end(), cereg.areaCode, 16); std::from_chars(cellIdHex.begin(), cellIdHex.end(), cereg.cellId, 16); cereg.state = static_cast(state); return make(std::move(cereg)); } break; case 4: // unsolMode,state,areaCode,cellId,networkType if (parser(&unsolMode).skip(',') (&state).skip(',') .skip('"')(&areaCodeHex, '"').skip(',') .skip('"')(&cellIdHex, '"').skip(',') (&cereg.networkType).fullMatch()) { std::from_chars(areaCodeHex.begin(), areaCodeHex.end(), cereg.areaCode, 16); std::from_chars(cellIdHex.begin(), cellIdHex.end(), cereg.cellId, 16); cereg.state = static_cast(state); return make(std::move(cereg)); } break; } return FAILURE_V(makeParseErrorFor(), "Can't parse: '%*.*s'", int(str.size()), int(str.size()), str.data()); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CTEC" /* +CTEC: current (decimal),preferred_bitmask (hex) * OR * +CTEC: comma_separated_list_of_supported (decimal) * OR * +CTEC: current (decimal) * OR * +CTEC: DONE */ AtResponsePtr AtResponse::CTEC::parse(const std::string_view str) { CTEC ctec; size_t i = 0; while (true) { const size_t comma = str.find(',', i); if (comma != std::string_view::npos) { ctec.values.push_back(std::string(str.substr(i, comma - i))); i = comma + 1; } else { ctec.values.push_back(std::string(str.substr(i))); break; } } return make(std::move(ctec)); } std::optional AtResponse::CTEC::getCurrentModemTechnology() const { using ratUtils::ModemTechnology; if ((values.size() == 0) || (values.size() > 2) || ((values.size() == 1) && (values[0] == "DONE"))) { return std::nullopt; } int mtech; std::from_chars_result r = std::from_chars(&*values[0].begin(), &*values[0].end(), mtech, 10); if ((r.ec != std::errc()) || (r.ptr != &*values[0].end())) { return FAILURE(std::nullopt); } for (unsigned i = static_cast(ModemTechnology::GSM); i <= static_cast(ModemTechnology::NR); ++i) { if (mtech & (1U << i)) { return static_cast(i); } } return FAILURE(std::nullopt); } bool AtResponse::CTEC::isDONE() const { return (values.size() == 1) && (values[0] == "DONE"); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "COPS" /* "+COPS: 0,0,longName\r+COPS: 0,1,shortName\r+COPS: 0,2,numeric\r" * OR * "+COPS: (state,longName,shortName,numeric),(...),...\r" * OR * "+COPS: selectionMode,2,numeric\r" * OR * "+COPS: selectionMode,0,0\r" * OR * "+COPS: 0,0,0\r" */ AtResponsePtr AtResponse::COPS::parse(const std::string_view str) { COPS cops; Parser parser(str); if (!parser.skip("+COPS:").skip(' ').hasMore()) { goto err; } if (parser.front() == '(') { while (parser.matchSoFar()) { COPS::OperatorInfo operatorInfo; int state; if (parser.skip('(')(&state).skip(',') (&operatorInfo.longName, ',') (&operatorInfo.shortName, ',') (&operatorInfo.numeric, ')').matchSoFar()) { operatorInfo.state = static_cast(state); cops.operators.push_back(std::move(operatorInfo)); if (parser.front() == ',') { parser.skip(','); } else { break; } } else { goto err; } } return make(std::move(cops)); } else { std::string str; int networkSelectionMode, n; if (!parser(&networkSelectionMode).skip(',')(&n).skip(',')(&str, kCR).matchSoFar()) { goto err; } if ((n == 2) && parser.fullMatch()) { cops.networkSelectionMode = static_cast(networkSelectionMode); cops.numeric = std::move(str); return make(std::move(cops)); } else if (n != 0) { goto err; } else if ((str == "0") && parser.fullMatch()) { cops.networkSelectionMode = static_cast(networkSelectionMode); return make(std::move(cops)); } COPS::OperatorInfo operatorInfo; operatorInfo.state = COPS::OperatorInfo::State::CURRENT; operatorInfo.longName = std::move(str); if (!parser.skip("+COPS:").skip(' ').skip("0,1,")(&operatorInfo.shortName, kCR) .skip("+COPS:").skip(' ').skip("0,2,")(&operatorInfo.numeric, kCR) .fullMatch()) { goto err; } cops.operators.push_back(std::move(operatorInfo)); return make(std::move(cops)); } err: return FAILURE_V(makeParseErrorFor(), "Can't parse: '%*.*s'", int(str.size()), int(str.size()), str.data()); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "WRMP" AtResponsePtr AtResponse::WRMP::parse(const std::string_view str) { int cdmaRoamingPreference; Parser parser(str); if (!parser(&cdmaRoamingPreference).fullMatch()) { return FAILURE_V(makeParseErrorFor(), "Can't parse: '%*.*s'", int(str.size()), int(str.size()), str.data()); } WRMP wrmp = { .cdmaRoamingPreference = static_cast(cdmaRoamingPreference), }; return make(std::move(wrmp)); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CCSS" AtResponsePtr AtResponse::CCSS::parse(const std::string_view str) { CCSS ccss; int source; Parser parser(str); if (parser(&source).fullMatch()) { ccss.source = static_cast(source); return make(std::move(ccss)); } else { return FAILURE_V(makeParseErrorFor(), "Can't parse: '%*.*s'", int(str.size()), int(str.size()), str.data()); } } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CSQ" AtResponsePtr AtResponse::CSQ::parse(const std::string_view str) { constexpr size_t kMaxSize = 22; int values[kMaxSize]; Parser parser(str); if (!parser(&values[0]).matchSoFar()) { return FAILURE_V(makeParseErrorFor(), "Can't parse: '%*.*s'", int(str.size()), int(str.size()), str.data()); } size_t n; for (n = 1; parser.hasMore() && (n < kMaxSize); ++n) { if (!parser.skip(',')(&values[n]).matchSoFar()) { return FAILURE_V(makeParseErrorFor(), "Can't parse: '%*.*s'", int(str.size()), int(str.size()), str.data()); } } if (!parser.fullMatch()) { return FAILURE_V(makeParseErrorFor(), "Can't parse: '%*.*s'", int(str.size()), int(str.size()), str.data()); } CSQ csq; switch (n) { case 22: csq.wcdma_signalStrength = values[14]; if (csq.wcdma_signalStrength != kUnknown) { csq.wcdma_rscp = 42; csq.wcdma_ecno = 19; } csq.wcdma_bitErrorRate = values[15]; csq.nr_ssRsrp = values[16]; csq.nr_ssRsrq = values[17]; csq.nr_ssSinr = values[18]; csq.nr_csiRsrp = values[19]; csq.nr_csiRsrq = values[20]; csq.nr_csiSinr = values[21]; [[fallthrough]]; case 14: csq.tdscdma_rscp = values[13]; [[fallthrough]]; case 13: csq.lte_timingAdvance = values[12]; [[fallthrough]]; case 12: csq.gsm_signalStrength = values[0]; csq.gsm_bitErrorRate = values[1]; csq.cdma_dbm = values[2]; csq.cdma_ecio = values[3]; csq.evdo_dbm = values[4]; csq.evdo_ecio = values[5]; csq.evdo_signalNoiseRatio = values[6]; csq.lte_signalStrength = values[7]; csq.lte_rsrp = values[8]; csq.lte_rsrq = values[9]; csq.lte_rssnr = values[10]; csq.lte_cqi = values[11]; break; default: return FAILURE_V(makeParseErrorFor(), "Unexpected size: %zu", n); } return make(std::move(csq)); } network::SignalStrength AtResponse::CSQ::toSignalStrength() const { return { .gsm = { .signalStrength = gsm_signalStrength, .bitErrorRate = gsm_bitErrorRate, .timingAdvance = gsm_timingAdvance, }, .cdma = { .dbm = cdma_dbm, .ecio = cdma_ecio, }, .evdo = { .dbm = evdo_dbm, .ecio = evdo_ecio, .signalNoiseRatio = evdo_signalNoiseRatio, }, .lte = { .signalStrength = lte_signalStrength, .rsrp = lte_rsrp, .rsrq = lte_rsrq, .rssnr = lte_rssnr, .cqi = lte_cqi, .timingAdvance = lte_timingAdvance, .cqiTableIndex = lte_cqiTableIndex, }, .tdscdma = { .signalStrength = tdscdma_signalStrength, .bitErrorRate = tdscdma_bitErrorRate, .rscp = tdscdma_rscp, }, .wcdma = { .signalStrength = wcdma_signalStrength, .bitErrorRate = wcdma_bitErrorRate, .rscp = wcdma_rscp, .ecno = wcdma_ecno, }, .nr = { .ssRsrp = nr_ssRsrp, .ssRsrq = nr_ssRsrq, .ssSinr = nr_ssSinr, .csiRsrp = nr_csiRsrp, .csiRsrq = nr_csiRsrq, .csiSinr = nr_csiSinr, .csiCqiTableIndex = nr_csiCqiTableIndex, .timingAdvance = nr_timingAdvance, }, }; } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CLCC" AtResponsePtr AtResponse::CLCC::parse(const std::string_view str) { CLCC clcc; Parser parser(str); while (parser.hasMore()) { int index; int dir; int state; int mode; int mpty; int type; std::string number; // +CLCC: ,,,,,,\r if (parser.skip("+CLCC:").skip(' ')(&index).skip(',') (&dir).skip(',')(&state).skip(',') (&mode).skip(',')(&mpty).skip(',') (&number, ',')(&type).skip(kCR).matchSoFar()) { voice::Call call = { .state = state, .index = index, .toa = type, .isMpty = (mpty != 0), .isMT = (dir != 0), .isVoice = (mode == 0), .number = std::move(number), }; clcc.calls.push_back(std::move(call)); } else { return FAILURE_V(makeParseErrorFor(), "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); } } return make(std::move(clcc)); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CCFCU" AtResponsePtr AtResponse::CCFCU::parse(const std::string_view str) { CCFCU ccfcu; Parser parser(str); while (parser.hasMore()) { voice::CallForwardInfo cfi; int numberType; std::string_view ignore; if (parser.skip("+CCFCU:").skip(' ')(&cfi.status).skip(',') (&cfi.serviceClass).skip(',')(&numberType).skip(',') (&cfi.toa).skip(',').skip('"')(&cfi.number, '"').matchSoFar()) { switch (parser.front()) { case ',': if (!parser.skip(',')(&ignore, ',')(&ignore, ',') (&cfi.timeSeconds).skip(kCR).matchSoFar()) { return FAILURE_V(makeParseErrorFor(), "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); } break; case kCR: parser.skip(kCR); break; default: return FAILURE_V(makeParseErrorFor(), "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); } ccfcu.callForwardInfos.push_back(std::move(cfi)); } else { return FAILURE_V(makeParseErrorFor(), "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); } } return make(std::move(ccfcu)); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CCWA" AtResponsePtr AtResponse::CCWA::parse(const std::string_view str) { CCWA ccwa; int mode; Parser parser(str); if (parser(&mode).skip(',')(&ccwa.serviceClass).fullMatch()) { ccwa.enable = (mode == 1); return make(std::move(ccwa)); } else { return FAILURE_V(makeParseErrorFor(), "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); } } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CUSATD" AtResponsePtr AtResponse::CUSATD::parse(const std::string_view str) { CUSATD cusatd; Parser parser(str); if (parser(&cusatd.a).skip(',').skip(' ')(&cusatd.a).fullMatch()) { return make(std::move(cusatd)); } else { return FAILURE_V(makeParseErrorFor(), "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); } } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CUSATP" AtResponsePtr AtResponse::CUSATP::parse(const std::string_view str) { CUSATP cusatp = { .cmd = toString(str), }; return make(std::move(cusatp)); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CUSATE" AtResponsePtr AtResponse::CUSATE::parse(const std::string_view str) { CUSATE cusate = { .response = toString(str), }; return make(std::move(cusate)); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CUSATT" AtResponsePtr AtResponse::CUSATT::parse(const std::string_view str) { CUSATT cusatt; Parser parser(str); if (parser(&cusatt.value).fullMatch()) { return make(std::move(cusatt)); } else { return FAILURE_V(makeParseErrorFor(), "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); } } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CUSATEND" AtResponsePtr AtResponse::CUSATEND::parse(const std::string_view) { return make(CUSATEND()); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CLCK" AtResponsePtr AtResponse::CLCK::parse(const std::string_view str) { CLCK clck; switch (str.front()) { case '0': clck.locked = false; break; case '1': clck.locked = true; break; default: return FAILURE_V(makeParseErrorFor(), "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); } return make(std::move(clck)); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CSIM" AtResponsePtr AtResponse::CSIM::parse(const std::string_view str) { Parser parser(str); int len; if (parser(&len).skip(',').matchSoFar()) { const std::string_view response = parser.remaining(); if (len == response.size()) { CSIM csim = { .response = toString(response), }; return make(std::move(csim)); } } return FAILURE_V(makeParseErrorFor(), "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CGLA" AtResponsePtr AtResponse::CGLA::parse(const std::string_view str) { Parser parser(str); int len; if (parser(&len).skip(',').matchSoFar()) { const std::string_view response = parser.remaining(); if (len == response.size()) { CGLA cgla = { .response = toString(response), }; return make(std::move(cgla)); } } return FAILURE_V(makeParseErrorFor(), "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CCHC" AtResponsePtr AtResponse::CCHC::parse(const std::string_view) { return make(CCHC()); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CLIP" AtResponsePtr AtResponse::CLIP::parse(const std::string_view str) { int enable; int status; Parser parser(str); if (parser(&enable).skip(',')(&status).fullMatch()) { CLIP clip; clip.enable = enable != 0; clip.status = static_cast(status); return make(std::move(clip)); } else { return FAILURE_V(makeParseErrorFor(), "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); } } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CLIR" AtResponsePtr AtResponse::CLIR::parse(const std::string_view str) { CLIR clir; Parser parser(str); if (parser(&clir.n).skip(',')(&clir.m).fullMatch()) { return make(std::move(clir)); } else { return FAILURE_V(makeParseErrorFor(), "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); } } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CMUT" AtResponsePtr AtResponse::CMUT::parse(const std::string_view str) { int on; Parser parser(str); if (parser(&on).fullMatch()) { CMUT cmut; cmut.on = (on != 0); return make(std::move(cmut)); } else { return FAILURE_V(makeParseErrorFor(), "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); } } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "WSOS" AtResponsePtr AtResponse::WSOS::parse(const std::string_view str) { int isEmergencyMode; Parser parser(str); if (parser(&isEmergencyMode).fullMatch()) { WSOS wsos; wsos.isEmergencyMode = (isEmergencyMode != 0); return make(std::move(wsos)); } else { return FAILURE_V(makeParseErrorFor(), "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); } } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CSCA" AtResponsePtr AtResponse::CSCA::parse(const std::string_view str) { CSCA csca; Parser parser(str); if (!parser(&csca.sca, ',')(&csca.tosca).fullMatch()) { return FAILURE_V(makeParseErrorFor(), "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); } return make(std::move(csca)); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CSCB" AtResponsePtr AtResponse::CSCB::parse(const std::string_view str) { CSCB cscb; std::string_view serviceId; std::string_view codeScheme; Parser parser(str); if (!parser(&cscb.mode).skip(',') .skip('"')(&serviceId, '"').skip(',') .skip('"')(&codeScheme, '"').fullMatch()) { fail: return FAILURE_V(makeParseErrorFor(), "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); } auto maybeIds = parseIds(serviceId); if (!maybeIds) { goto fail; } cscb.serviceId = std::move(maybeIds.value()); maybeIds = parseIds(codeScheme); if (!maybeIds) { goto fail; } cscb.codeScheme = std::move(maybeIds.value()); return make(std::move(cscb)); } std::optional> AtResponse::CSCB::parseIds(const std::string_view str) { std::vector ids; Parser parser(str); while (parser.hasMore()) { Association a; if (!parser(&a.from).matchSoFar()) { return std::nullopt; } if (parser.fullMatch()) { a.to = a.from; ids.push_back(a); break; } switch (parser.front()) { case '-': if (!parser.skip('-')(&a.to).matchSoFar()) { return std::nullopt; } ids.push_back(a); if (parser.fullMatch()) { break; } else if (parser.front() == ',') { parser.skip(','); } else { return std::nullopt; } break; case ',': parser.skip(','); break; default: return std::nullopt; } } return ids; } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CMGS" AtResponsePtr AtResponse::CMGS::parse(const std::string_view str) { CMGS cmgs; Parser parser(str); if (!parser(&cmgs.messageRef).fullMatch()) { return FAILURE_V(makeParseErrorFor(), "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); } return make(std::move(cmgs)); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CMGW" AtResponsePtr AtResponse::CMGW::parse(const std::string_view str) { CMGW cmgw; Parser parser(str); if (!parser(&cmgw.messageRef).fullMatch()) { return FAILURE_V(makeParseErrorFor(), "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); } return make(std::move(cmgw)); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CMT" std::pair AtResponse::CMT::parse(const std::string_view str) { CMT cmt; std::string strPdu; Parser parser(str); if (parser(&cmt.something).skip(kCR).matchSoFar()) { if (parser(&strPdu, kCR).matchSoFar()) { if (hex2bin(strPdu, &cmt.pdu)) { return { parser.consumed(), make(std::move(cmt)) }; } } else { return { 0, nullptr }; } } auto err = std::make_pair(-1, makeParseErrorFor()); return FAILURE_V(err, "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CDS" std::pair AtResponse::CDS::parse(const std::string_view str) { CDS cds; std::string strPdu; Parser parser(str); if (parser(&cds.pduSize).skip(kCR).matchSoFar()) { if (parser(&strPdu, kCR).matchSoFar()) { if (hex2bin(strPdu, &cds.pdu)) { return { parser.consumed(), make(std::move(cds)) }; } } else { return { 0, nullptr }; } } auto err = std::make_pair(-1, makeParseErrorFor()); return FAILURE_V(err, "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CGDCONT" // +CGDCONT: ,,,,,\r // 1,"IPV6","fast.t-mobile.com",,0,0 AtResponsePtr AtResponse::CGDCONT::parse(const std::string_view str) { CGDCONT cgdcont; Parser parser(str); while (parser.hasMore()) { CGDCONT::PdpContext pdpContext; if (parser.skip("+CGDCONT:").skip(' ')(&pdpContext.index).skip(',') .skip('"')(&pdpContext.type, '"').skip(',') .skip('"')(&pdpContext.apn, '"').skip(',') (&pdpContext.addr, ',')(&pdpContext.dComp) .skip(',')(&pdpContext.hComp).skip(' ').matchSoFar()) { cgdcont.contexts.push_back(std::move(pdpContext)); } else { return FAILURE_V(makeParseErrorFor(), "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); } } return make(std::move(cgdcont)); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CGCONTRDP" // 1,5,"epc.tmobile.com",10.0.2.15/24,10.0.2.2,10.0.2.3 // 1,5,"epc.tmobile.com",10.0.2.15,10.0.2.2,10.0.2.3 AtResponsePtr AtResponse::CGCONTRDP::parse(const std::string_view str) { CGCONTRDP cgcontrdp; std::string_view unused; std::string_view localAddr; Parser parser(str); if (parser(&cgcontrdp.cid).skip(',')(&cgcontrdp.bearer).skip(',') .skip('"')(&cgcontrdp.apn, '"').skip(',') (&localAddr, ',') (&cgcontrdp.gwAddr, ',').matchSoFar()) { cgcontrdp.dns1 = parser.remainingAsString(); } else { return FAILURE_V(makeParseErrorFor(), "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); } Parser localAddrParser(localAddr); if (!localAddrParser(&cgcontrdp.localAddr, '/') (&cgcontrdp.localAddrSize).fullMatch()) { cgcontrdp.localAddr = std::string(localAddr.data(), localAddr.size()); cgcontrdp.localAddrSize = 0; } return make(std::move(cgcontrdp)); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CGFPCCFG" // 1,5000,32,0,1 AtResponsePtr AtResponse::CGFPCCFG::parse(const std::string_view str) { CGFPCCFG cgfpccfg; int status; int mtech; Parser parser(str); if (!parser(&status).skip(',') (&cgfpccfg.bandwidth).skip(',') (&mtech).skip(',') (&cgfpccfg.freq).skip(',') (&cgfpccfg.contextId).fullMatch()) { return FAILURE_V(makeParseErrorFor(), "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); } cgfpccfg.status = static_cast(status); cgfpccfg.mtech = static_cast(mtech); return make(std::move(cgfpccfg)); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "MBAU" // ,, // ,,, AtResponsePtr AtResponse::MBAU::parse(const std::string_view str) { MBAU mbau; std::string_view kc; std::string_view sres; std::string_view ck; std::string_view ik; std::string_view resAuts; Parser parser(str); switch (std::count(str.begin(), str.end(), ',')) { default: failed: return FAILURE_V(makeParseErrorFor(), "Can't parse '%*.*s'", int(str.size()), int(str.size()), str.data()); case 0: if (!parser(&mbau.status).fullMatch()) { goto failed; } break; case 2: if (parser(&mbau.status).skip(',')(&kc, ',').matchSoFar()) { sres = parser.remaining(); } else { goto failed; } break; case 3: if (parser(&mbau.status).skip(',')(&ck, ',')(&ik, ',').matchSoFar()) { resAuts = parser.remaining(); } else { goto failed; } break; } if (!hex2bin(kc, &mbau.kc) || !hex2bin(sres, &mbau.sres) || !hex2bin(ck, &mbau.ck) || !hex2bin(ik, &mbau.ik) || !hex2bin(resAuts, &mbau.resAuts)) { goto failed; } return make(std::move(mbau)); } #undef FAILURE_DEBUG_PREFIX #define FAILURE_DEBUG_PREFIX "CTZV" // 24/11/05:17:01:32-32:0:America!Los_Angeles AtResponsePtr AtResponse::CTZV::parse(const std::string_view str) { int yy, month, day, hh, mm, ss, tzOffset15m; char tzSign, daylight; Parser parser(str); parser.skip(' ')(&yy).skip('/')(&month).skip('/')(&day).skip(':') (&hh).skip(':')(&mm).skip(':')(&ss) (&tzSign)(&tzOffset15m).skip(':')(&daylight).skip(':'); if (!parser.matchSoFar()) { return FAILURE_V(makeParseErrorFor(), "Can't parse: '%*.*s'", int(str.size()), int(str.size()), str.data()); } switch (tzSign) { case '+': break; case '-': tzOffset15m = -tzOffset15m; break; default: return FAILURE_V(makeParseErrorFor(), "Unexpected timezone offset sign: '%*.*s'", int(str.size()), int(str.size()), str.data()); } CTZV ctzv = { .tzName = toString(parser.remaining()), .year = uint16_t(yy + 2000), .month = uint8_t(month), .day = uint8_t(day), .hour = uint8_t(hh), .isDaylightSaving = uint8_t(daylight != '0'), .minute = uint8_t(mm), .second = uint8_t(ss), .tzOffset15m = int8_t(tzOffset15m), }; return make(std::move(ctzv)); } std::string AtResponse::CTZV::nitzString() const { return std::format("{:02d}/{:02d}/{:02d}:{:02d}:{:02d}:{:02d}{:+d}:{:d}:{:s}", year % 100, month, day, hour, minute, second, tzOffset15m, isDaylightSaving, tzName.c_str()); } std::string_view AtResponse::what() const { return visitR(CmdIdVisitor()); } void AtResponse::unexpected(const char* klass, const char* request, const std::source_location location) const { const std::string_view r = what(); const int rl = r.size(); LOG_ALWAYS_FATAL("Unexpected response: '%*.*s' in %s:%s at %s:%d in %s", rl, rl, r.data(), klass, request, location.function_name(), location.line(), location.file_name()); } } // namespace implementation } // namespace radio } // namespace hardware } // namespace android } // namespace aidl