/*
 * Copyright (C) 2019 by Multi-Tech Systems
 *
 * This file is part of libmts-io.
 *
 * libmts-io is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * libmts-io is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with libmts-io.  If not, see .
 *
 */
#include "mts/MTS_IO_CellularRadio.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace MTS::IO;
namespace {
    typedef struct
    {
        const char    *name;
        int32_t        low;
        int32_t        high;
    } *pNameRangeMap, nameRangeMap;
    const unsigned int NUM_GSM_BANDS = 7;
    const unsigned int NUM_WCDMA_BANDS = 6;
    const unsigned int NUM_LTE_BANDS = 42;
    // http://niviuk.free.fr/gsm_band.php
    const nameRangeMap GSMband[] =
    {
                {"GSM 450", 259, 293}, {"GSM 480", 306, 340},
                {"GSM 750", 438, 511}, {"GSM 850", 128, 251},
                {"GSM 900 P", 1, 124}, {"GSM 900 E/R", 955, 1023},
                {"GSM DCS 1800/1900", 512, 885},
    };
    // http://niviuk.free.fr/umts_band.php
    const nameRangeMap WCDMAband[] =
    {
                {"BAND I",   10592, 10838}, {"BAND II", 9662, 9938},
                {"BAND III",  1162,  1513}, {"BAND IV", 1537, 1738},
                {"BAND V",    4357,  4458}, {"BAND VI", 4387, 4413}
    };
    // http://niviuk.free.fr/lte_band.php
    const nameRangeMap EULTRAband[] =
    {
                {"EUTRAN BAND1",      0,   599}, {"EUTRAN BAND2",    600,  1199},
                {"EUTRAN BAND3",   1200,  1949}, {"EUTRAN BAND4",   1950,  2399},
                {"EUTRAN BAND5",   2400,  2649}, {"EUTRAN BAND6",   2650,  2749},
                {"EUTRAN BAND7",   2750,  3449}, {"EUTRAN BAND8",   3450,  3799},
                {"EUTRAN BAND9",   3800,  4149}, {"EUTRAN BAND10",  4150,  4749},
                {"EUTRAN BAND11",  4750,  4999}, {"EUTRAN BAND12",  5000,  5179},
                {"EUTRAN BAND13",  5180,  5279}, {"EUTRAN BAND14",  5280,  5379},
                {"EUTRAN BAND17",  5730,  5849}, {"EUTRAN BAND18",  5850,  5999},
                {"EUTRAN BAND19",  6000,  6149}, {"EUTRAN BAND20",  6150,  6449},
                {"EUTRAN BAND21",  6450,  6525}, {"EUTRAN BAND22",  6600,  7399},
                {"EUTRAN BAND23",  7500,  7699}, {"EUTRAN BAND24",  7700,  8039},
                {"EUTRAN BAND25",  8040,  8689}, {"EUTRAN BAND26",  8690,  9039},
                {"EUTRAN BAND27",  9040,  9209}, {"EUTRAN BAND28",  9210,  9659},
                {"EUTRAN BAND29",  9660,  9769}, {"EUTRAN BAND30",  9770,  9869},
                {"EUTRAN BAND31",  9870,  9919}, {"EUTRAN BAND32",  9920, 10359},
                {"EUTRAN BAND33", 36000, 36199}, {"EUTRAN BAND34", 36200, 36349},
                {"EUTRAN BAND35", 36350, 36949}, {"EUTRAN BAND36", 36950, 37549},
                {"EUTRAN BAND37", 37550, 37749}, {"EUTRAN BAND38", 37750, 38249},
                {"EUTRAN BAND39", 38250, 38649}, {"EUTRAN BAND40", 38650, 39649},
                {"EUTRAN BAND41", 39650, 41589}, {"EUTRAN BAND42", 41590, 43589},
                {"EUTRAN BAND43", 43590, 45589}, {"EUTRAN BAND44", 45590, 46589}
    };
}
CellularRadio::CellularRadio(const std::string& sName, const std::string& sRadioPort)
: m_sName(sName)
, m_sRadioPort(sRadioPort)
, m_bEchoEnabled(false)
, m_bEnableEchoOnClose(false)
{
    m_apIo.reset(new MTS::IO::SerialConnection(
                             MTS::IO::SerialConnection::Builder(m_sRadioPort)
                                .baudRate(115200)
                                .useLockFile()
                                .build()));
}
CellularRadio::~CellularRadio() {
    shutdown();
    m_apIo.reset();
}
bool CellularRadio::initialize(uint32_t iTimeoutMillis) {
    if(!m_apIo->open(iTimeoutMillis)) {
        printError("%s| Failed to open radio port [%s]", m_sName.c_str(), m_sRadioPort.c_str());
        return false;
    }
    bool bEnabled;
    ICellularRadio::CODE eCode = getEcho(bEnabled);
    if(eCode == SUCCESS && bEnabled) {
        printDebug("%s| Disabling 'echo'", m_sName.c_str());
        setEcho(false);
        m_bEnableEchoOnClose = true;
    }
    return true;
}
bool CellularRadio::resetConnection(uint32_t iTimeoutMillis) {
    //Close Current Connection
    if(!m_apIo.isNull()) {
        m_apIo->close();
    }
    m_apIo.reset(new MTS::IO::SerialConnection(
                     MTS::IO::SerialConnection::Builder(m_sRadioPort)
                        .baudRate(115200)
                        .useLockFile()
                        .build()));
    //Try to obtain the device port over the given period of time
    MTS::Timer oTimer;
    oTimer.start();
    uint64_t iCurrentTime = 0;
    while(iCurrentTime < iTimeoutMillis) {
        if(!m_apIo->open(iTimeoutMillis - iCurrentTime)) {
            printWarning("%s| Failed to re-open radio port [%s]", m_sName.c_str(), m_sRadioPort.c_str());
        } else {
            printInfo("%s| Successfully re-opened radio port [%s]", m_sName.c_str(), m_sRadioPort.c_str());
            printDebug("%s| Recovering 'echo' after connection reset", m_sName.c_str()); // see CellularRadio::initialize
            if (setEcho(m_bEchoEnabled) != SUCCESS) {
                printWarning("%s| Failed to recover 'echo' after connection reset", m_sName.c_str());
            }
            break;
        }
        ::usleep(500000); //500 millis
        iCurrentTime = oTimer.getMillis();
    }
    oTimer.stop();
    return !m_apIo->isClosed();
}
void CellularRadio::shutdown() {
    if(!m_apIo.isNull()) {
        if(m_bEnableEchoOnClose) {
            printDebug("%s| Enabling 'echo'", m_sName.c_str());
            setEcho(true);
            m_bEnableEchoOnClose = false;
        }
        m_apIo->close();
    }
}
const std::string& CellularRadio::getName() const {
    return m_sName;
}
ICellularRadio::CODE CellularRadio::getFirmware(std::string& sFirmware) {
    printTrace("%s| Get Firmware", m_sName.c_str());
    sFirmware = ICellularRadio::VALUE_NOT_SUPPORTED;
    std::string sCmd("AT+CGMR");
    std::string sResult = sendCommand(sCmd);
    size_t pos = sResult.find(ICellularRadio::RSP_OK);
    if (pos == std::string::npos) {
        printWarning("%s| Unable to get firmware from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }
    sFirmware = MTS::Text::trim(sResult.substr(0, pos));
    if(sFirmware.size() == 0) {
        printWarning("%s| Unable to get firmware from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }
    m_sFirmware = sFirmware;
    return SUCCESS;
}
ICellularRadio::CODE CellularRadio::getFirmwareBuild(std::string& sFirmwareBuild) {
    sFirmwareBuild = ICellularRadio::VALUE_NOT_SUPPORTED;
    return FAILURE;
}
ICellularRadio::CODE CellularRadio::getVendorFirmware(std::string& sVendorFirmware) {
    sVendorFirmware = ICellularRadio::VALUE_NOT_SUPPORTED;
    return FAILURE;
}
ICellularRadio::CODE CellularRadio::getHardware(std::string& sHardware) {
    printTrace("%s| Get Hardware", m_sName.c_str());
    sHardware = ICellularRadio::VALUE_NOT_SUPPORTED;
    if(m_sFirmware.size() == 0) {
        getFirmware(m_sFirmware);
    }
    if(getHardwareVersionFromFirmware(m_sFirmware, sHardware)) {
        return SUCCESS;
    }
    return FAILURE;
}
ICellularRadio::CODE CellularRadio::getManufacturer(std::string& sManufacturer) {
    printTrace("%s| Get Manufacturer", m_sName.c_str());
    sManufacturer = ICellularRadio::VALUE_NOT_SUPPORTED;
    std::string sCmd("AT+GMI");
    std::string sResult = sendCommand(sCmd);
    size_t pos = sResult.find(ICellularRadio::RSP_OK);
    if (pos == std::string::npos) {
        printWarning("%s| Unable to get manufacturer from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }
    sManufacturer = MTS::Text::trim(sResult.substr(0, pos));
    if(sManufacturer.size() == 0) {
        printWarning("%s| Unable to get manufacturer from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }
    return SUCCESS;
}
ICellularRadio::CODE CellularRadio::getImei(std::string& sImei) {
    printTrace("%s| Get IMEI", m_sName.c_str());
    sImei = ICellularRadio::VALUE_NOT_SUPPORTED;
    // AT+CGSN execution can take up to 300ms according to the Quectel datasheet. Setting timeout to 500ms just for sure.
    std::string sCmd("AT+CGSN");
    std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 500);
    size_t pos = sResult.find(ICellularRadio::RSP_OK);
    if (pos == std::string::npos) {
        printWarning("%s| Unable to get IMEI from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }
    sImei = MTS::Text::trim(sResult.substr(0, pos));
    if(sImei.size() == 0) {
        printWarning("%s| Unable to get IMEI from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }
    return SUCCESS;
}
ICellularRadio::CODE CellularRadio::getMeid(std::string& sMeid) {
    printTrace("%s| Get MEID", m_sName.c_str());
    return getImei(sMeid);
}
ICellularRadio::CODE CellularRadio::getImsi(std::string& sImsi) {
    printTrace("%s| Get IMSI", m_sName.c_str());
    sImsi = ICellularRadio::VALUE_NOT_SUPPORTED;
    // AT+CIMI execution can take up to 300ms according to the Quectel datasheet. Setting timeout to 500ms just for sure.
    std::string sCmd("AT+CIMI");
    std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 500);
    size_t pos = sResult.find(ICellularRadio::RSP_OK);
    if (pos == std::string::npos) {
        printWarning("%s| Unable to get IMSI from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }
    sImsi = MTS::Text::trim(sResult.substr(0, pos));
    if(sImsi.size() == 0) {
        printWarning("%s| Unable to get IMSI from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }
    return SUCCESS;
}
ICellularRadio::CODE CellularRadio::getSimStatus(std::string& sSimStatus) {
    printTrace("%s| Get SIM Status", getName().c_str());
    sSimStatus = ICellularRadio::VALUE_UNKNOWN;
    return FAILURE;
}
ICellularRadio::CODE CellularRadio::getSimStatusSummary(Json::Value& jData) {
    bool bIsSimInserted = false;
    bool bIsSimLocked = true;
    int iAttemptsPin = 0;
    int iAttemptsPuk = 0;
    std::string sSimLockStatus;
    ICellularRadio::CODE retCode;
    do {
        retCode = getIsSimInserted(bIsSimInserted);
        if (retCode != SUCCESS) {
            break;
        }
        if (!bIsSimInserted) {
            // There is not much left to do. Return one field only.
            jData[KEY_IS_SIM_INSERTED] = bIsSimInserted;
            break;
        }
        // The following code assumes that the SIM card is inserted
        retCode = getSimLockStatus(sSimLockStatus);
        if (retCode != SUCCESS) {
            /* IN:4033:
             *
             * On some devices #SIMDET reports "inserted" but +CPIN? returns ERROR when there is
             * no SIM card in the slot. It's also the case when only plastic holder is inserted
             * instead of the SIM itself.
             *
             * Interpret this error as "SIM card not detected" for such cases.
             */
            jData[KEY_IS_SIM_INSERTED] = false;
            retCode = SUCCESS;
            break;
        }
        bIsSimLocked = (sSimLockStatus != "READY");  // SIM PIN, SIM PUK or other values
        retCode = getSimLockAttempts(iAttemptsPin, iAttemptsPuk);
        if (retCode != SUCCESS) {
            break;
        }
        // Everything fetched successfully. Populate the jData object
        jData[KEY_IS_SIM_INSERTED] = bIsSimInserted;
        jData[KEY_IS_SIM_LOCKED] = bIsSimLocked;
        jData[KEY_SIM_LOCK_STATUS] = sSimLockStatus;
        jData[KEY_ATTEMPTS_PIN] = iAttemptsPin;
        jData[KEY_ATTEMPTS_PUK] = iAttemptsPuk;
    } while (false);
    return retCode;
}
ICellularRadio::CODE CellularRadio::getLac(std::string& sLac) {
    Json::Value jData;
    printTrace("%s| Get LAC", m_sName.c_str());
    sLac = ICellularRadio::VALUE_NOT_SUPPORTED;
    if(getNetworkStatus(jData) == SUCCESS) {
        if(jData.isMember(ICellularRadio::KEY_LAC)) {
            sLac = jData[ICellularRadio::KEY_LAC].asString();
            return SUCCESS;
        }
    }
    return FAILURE;
}
ICellularRadio::CODE CellularRadio::getMdn(std::string& sMdn) {
    printTrace("%s| Get MDN", m_sName.c_str());
    sMdn = ICellularRadio::VALUE_NOT_SUPPORTED;
    // AT+CNUM execution can take up to 300ms according to the Quectel datasheet. Setting timeout to 500ms just for sure.
    std::string sCmd("AT+CNUM");
    std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 500);
    size_t end = sResult.find(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printWarning("%s| Unable to get MDN from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }
    size_t start = sResult.find("CNUM:");
    if(start != std::string::npos) {
        start += sizeof("CNUM:");
        std::vector vParts = MTS::Text::split(sResult.substr(start, end - start), ',');
        if(vParts.size() < 3) {
            printWarning("%s| Unable to parse MDN from response [%s]", m_sName.c_str(), sResult.c_str());
            return FAILURE;
        }
        sMdn = MTS::Text::strip(vParts[1], '"');
        if(sMdn.size() == 0) {
            printWarning("%s| Unable to get MDN from radio using command [%s].  MDN may not be set.", m_sName.c_str(), sCmd.c_str());
        }
    } else {
        sMdn = "";
        printWarning("%s| Unable to get MDN from radio using command [%s].  MDN may not be set.", m_sName.c_str(), sCmd.c_str());
    }
    return SUCCESS;
}
ICellularRadio::CODE CellularRadio::getMsid(std::string& sMsid) {
    printTrace("%s| Get MSID", m_sName.c_str());
    sMsid = "";
    std::string sImsi;
    if(getImsi(sImsi) == SUCCESS) {
        if(sImsi.size() >= 10) {
            sMsid = sImsi.substr(sImsi.size() - 10);
            printTrace("IMSI: [%s] MEID [%s]", sImsi.c_str(), sMsid.c_str());
            return SUCCESS;
        }
    }
    printWarning("%s| Unable to get MSID from radio", m_sName.c_str());
    return FAILURE;
}
ICellularRadio::CODE CellularRadio::getType(std::string& sType) {
    printTrace("%s| Get Type", m_sName.c_str());
    sType = ICellularRadio::VALUE_NOT_SUPPORTED;
    return FAILURE;
}
ICellularRadio::CODE CellularRadio::getCarrier(std::string& sCarrier) {
    printTrace("%s| Get Carrier", m_sName.c_str());
    CODE rc = SUCCESS;
    std::string t_sCarrier;
    do {
        if (m_sCarrier == "") {
            rc = getCarrierFromSimSpn(t_sCarrier);
            if (rc == SUCCESS) {
                m_sCarrier = t_sCarrier;
                break;
            }
            rc = getCarrierFromSimMccMnc(t_sCarrier);
            if (rc == SUCCESS) {
                m_sCarrier = t_sCarrier;
                break;
            }
        }
    } while (false);
    sCarrier = m_sCarrier;
    return rc;
}
ICellularRadio::CODE CellularRadio::getCarrierFromSimMccMnc(std::string& sCarrier) {
    printTrace("%s| Get Carrier from SIM MCC/MNC", m_sName.c_str());
    std::string sMcc;
    std::string sMnc;
    CODE rc;
    rc = getSimMccMnc(sMcc, sMnc);
    if (rc == SUCCESS) {
        Json::Value jLookup = MccMncTable::getInstance()->lookup(sMcc, sMnc);
        printTrace("%s| MCC-MNC Lookup: [%s][%s][%s]", m_sName.c_str(),
                    sMcc.c_str(), sMnc.c_str(), jLookup.toStyledString().c_str());
        if (jLookup.isMember(ICellularRadio::KEY_CARRIER)) {
            sCarrier = jLookup[ICellularRadio::KEY_CARRIER].asString();
        } else {
            printWarning("%s| MCC-MNC Lookup does not contain the carrier", m_sName.c_str());
            return FAILURE;
        }
    } else {
        printWarning("%s| SIM did no contain MCC or MNC", m_sName.c_str());
        return rc;
    }
    return SUCCESS;
}
ICellularRadio::CODE CellularRadio::getCarrierFromSimSpn(std::string& sCarrier) {
    printTrace("%s| Get Carrier from SIM SPN", m_sName.c_str());
    const int iEfspnId = 0x6F46;
    const uint8_t iOffsetHigh = 1;
    const uint8_t iOffsetLow = 0;
    const uint8_t iNumBytes = 16;
    CODE rc;
    std::string sEFspnContent;
    rc = simAccessReadBinary(iEfspnId, iOffsetLow, iOffsetHigh, iNumBytes, sEFspnContent);
    if (rc != SUCCESS) {
        printError("%s| Failed to determine the service provider name", m_sName.c_str());
        return rc;
    }
    if (sEFspnContent.length() != 32) {
        printError("%s| Invalid length of the service provider name: expected [32], actual: [%d]", m_sName.c_str(), sEFspnContent.length());
        return FAILURE;
    }
    uint8_t iSpnPart;
    for (size_t i = 0; i < sEFspnContent.length(); i += 2) {
        std::string sPart = sEFspnContent.substr(i, 2);
        // parse hex to unsigned byte
        if (!MTS::Text::parseHex(iSpnPart, sPart)) {
            printError("%s| Unexpected SIM EFspn content: [%s]", m_sName.c_str(), sEFspnContent.c_str());
            return FAILURE;
        }
        // skip 0xFF bytes
        if (iSpnPart == 0xFF) {
            continue;
        }
        sCarrier.push_back(iSpnPart);
    }
    /**
     * Example of radio response when SIM card does not contain the service provider name:
     * Raw response from the radio: [
     *   +CRSM: 144,0,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
     *   OK
     * ]
     */
    if (sCarrier.empty()) {
        printError("%s| SIM EFspn does not contain the service provider name", m_sName.c_str());
        return FAILURE;
    }
    return SUCCESS;
}
ICellularRadio::CODE CellularRadio::getTower(std::string& sTower) {
    Json::Value jData;
    printTrace("%s| Get Tower", m_sName.c_str());
    sTower = ICellularRadio::VALUE_NOT_SUPPORTED;
    if(getNetworkStatus(jData) == SUCCESS) {
        if(jData.isMember(ICellularRadio::KEY_CID)) {
            sTower = jData[ICellularRadio::KEY_CID].asString();
            return SUCCESS;
        }
    }
    return FAILURE;
}
ICellularRadio::CODE CellularRadio::getTime(std::string& sDate, std::string& sTime, std::string& sTimeZone) {
    Json::Value jData;
    printTrace("%s| Get Time", m_sName.c_str());
    sDate = "";
    sTime = "";
    sTimeZone = "";
    std::string sCmd("AT+CCLK?");
    std::string sResult = sendCommand(sCmd);
    size_t end = sResult.find(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printWarning("%s| Unable to get Time from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }
    size_t start = sResult.find("CCLK: ");
    if(start != std::string::npos) {
        start += sizeof("CCLK: ");
        std::string sValue = MTS::Text::trim(sResult.substr(start, end - start));
        sValue = MTS::Text::strip(sValue, '"');
        std::vector vParts = MTS::Text::split(sValue, ',');
        if(vParts.size() != 2) {
            printWarning("%s| Unable to parse Date from response [%s]", m_sName.c_str(), sResult.c_str());
            return FAILURE;
        }
        std::vector vDateParts = MTS::Text::split(vParts[0], '/');
        if(vDateParts.size() != 3) {
            printWarning("%s| Unable to parse Date from response [%s]", m_sName.c_str(), sResult.c_str());
            return FAILURE;
        }
        //The Date format is YY/MM/DD -> Change to MM/DD/YY
        sDate = vDateParts[1] + "/" + vDateParts[2] + "/" + vDateParts[0];
        vParts = MTS::Text::split(vParts[1], '-');
        if(vParts.size() != 2) {
            printWarning("%s| Unable to parse Time from response [%s]", m_sName.c_str(), sResult.c_str());
            return FAILURE;
        }
        sTime = vParts[0];
        int32_t iZoneUnits; //the difference, expressed in quarters of an hour, between the local time and GMT
        if(!MTS::Text::parse(iZoneUnits, MTS::Text::strip(vParts[1], '+'))) {
            printWarning("%s| Unable to parse Time Zone from response [%s]", m_sName.c_str(), sResult.c_str());
            return FAILURE;
        }
        int32_t iZone = iZoneUnits/4;  //Divide by 4 to get hours difference
        int32_t iZonePartial = (iZoneUnits % 4) * 15;  //Remainder in minutes
        std::string sPlusSign = "+";
        if(iZonePartial < 0) {
            //Remove negative sign from partial and clear plus sign component
            iZonePartial *= -1;
            sPlusSign = "";
        }
        std::stringstream ss;
        ss << sPlusSign << iZone;
        if(iZonePartial != 0) {
            ss << ":" << iZonePartial;
        }
        sTimeZone = ss.str();
        return SUCCESS;
    } else {
        printWarning("%s| Unable to get Time from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
    }
    return FAILURE;
}
ICellularRadio::CODE CellularRadio::getRoaming(bool& bRoaming) {
    Json::Value jData;
    printTrace("%s| Get Roaming", m_sName.c_str());
    bRoaming = false;
    REGISTRATION eReg;
    if(getRegistration(eReg) == SUCCESS) {
        bRoaming = (eReg == ROAMING);
        return SUCCESS;
    }
    return FAILURE;
}
ICellularRadio::CODE CellularRadio::getSignalStrength(int32_t& rssi) {
    printTrace("%s| Get Signal Strength", m_sName.c_str());
    // AT+CSQ execution can take up to 300ms according to the Quectel datasheet. Setting timeout to 500ms just for sure.
    std::string sCmd("AT+CSQ");
    std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 500);
    if (sResult.find("+CSQ: ") == std::string::npos) {
        printDebug("%s| Signal Strength command returned unexpected response: [%s]", m_sName.c_str(), sResult.c_str());
        return FAILURE;
    }
    size_t start = sResult.find(':');
    size_t stop = sResult.find(',', start);
    if(start == std::string::npos || stop == std::string::npos) {
        printDebug("%s| Signal Strength command returned malformed response: [%s]", m_sName.c_str(), sResult.c_str());
        return FAILURE;
    }
    std::string signal = sResult.substr(start + 2, stop - start - 2);
    sscanf(signal.c_str(), "%d", &rssi);
    printDebug("%s| Signal Strength: [%d]", m_sName.c_str(), rssi);
    return SUCCESS;
}
ICellularRadio::CODE CellularRadio::getModemLocation(std::string&) {
    printTrace("%s|CellularRadio getModemLocation - not supported", m_sName.c_str());
    return FAILURE;
}
ICellularRadio::CODE CellularRadio::getEcho(bool& bEnabled) {
    printTrace("%s| Echo Test", m_sName.c_str());
    std::string sResult = sendCommand("AT");
    if(sResult.size() == 0) {
        return NO_RESPONSE;
    }
    if(sResult.find("AT") != std::string::npos) {
        bEnabled = true;
    } else {
        bEnabled = false;
    }
    m_bEchoEnabled = bEnabled;
    return SUCCESS;
}
ICellularRadio::CODE CellularRadio::setEcho(bool bEnabled) {
    ICellularRadio::CODE eCode = FAILURE;
    if(bEnabled) {
        eCode = sendBasicCommand("ATE1");
        m_bEchoEnabled = (eCode == SUCCESS ) ? true : m_bEchoEnabled;
    } else {
        eCode = sendBasicCommand("ATE0");
        m_bEchoEnabled = (eCode == SUCCESS ) ? false : m_bEchoEnabled;
    }
    return eCode;
}
ICellularRadio::CODE CellularRadio::getStaticInformation(Json::Value& jData) {
    printTrace("%s| Get Static Information", m_sName.c_str());
    printTrace("%s| Static Information:\n%s\n", m_sName.c_str(), jData.toStyledString().c_str());
    return FAILURE;
}
// Get the LAC for the LTE radio that's not in the #RFSTS or +QENG response
std::string CellularRadio::queryLteLac() {
    std::string CGREGstring;
    std::string originalCGREG;
    std::string result;
    CGREGstring = queryCGREGstring();
    if (CGREGstring == ICellularRadio::RSP_ERROR) {
        originalCGREG = "0";
    } else {
        originalCGREG = CGREGstring.at(CGREGstring.find(",") - 1); //Position right before first comma ("+CGREG: 0,1")
    }
    // Temporarily set CGREG=2 to get more info
    setCGREG("2");
    CGREGstring = queryCGREGstring();
    if (CGREGstring == ICellularRadio::RSP_ERROR) {
        result = ICellularRadio::VALUE_UNKNOWN;
    } else {
        size_t start = CGREGstring.find(":") + 1; //Position right after "+CGREG:"
        std::vector vParts = MTS::Text::split(MTS::Text::trim(CGREGstring.substr(start)), ",");
        if(vParts.size() < 3) {
            result = ICellularRadio::VALUE_UNAVAILABLE;
        } else {
            result = MTS::Text::strip(vParts[2], '"');
        }
    }
    setCGREG(originalCGREG);
    return result;
}
void CellularRadio::setCGREG(std::string value) {
    std::string sCmd("AT+CGREG=" + value);
    std::string cmdResult(sendCommand(sCmd));
    if (cmdResult.find("OK") == std::string::npos) {
        printDebug("%s| AT+CGREG=%s returned unexpected response: [%s][%s]", m_sName.c_str(), value.c_str(), sCmd.c_str(), cmdResult.c_str());
    }
}
std::string CellularRadio::queryCGREGstring() {
    std::string sCmd("AT+CGREG?");
    std::string cmdResult(sendCommand(sCmd));
    if (cmdResult.find("+CGREG:") == std::string::npos) {
        printDebug("%s| AT+CGREG? returned unexpected response: [%s][%s]", m_sName.c_str(), sCmd.c_str(), cmdResult.c_str());
        return ICellularRadio::RSP_ERROR;
    }
    return cmdResult;
}
void CellularRadio::getCommonNetworkStats(Json::Value& jData) {
    bool bRoaming = false;
    if(getRoaming(bRoaming) == SUCCESS) {
        jData[ICellularRadio::KEY_ROAMING] = bRoaming;
    }
    int32_t iRssi;
    if(getSignalStrength(iRssi) == SUCCESS) {
        jData[ICellularRadio::KEY_RSSI] = iRssi;
        int32_t dBm;
        if(!jData.isMember(ICellularRadio::KEY_RSSIDBM) && convertSignalStrengthTodBm(iRssi, dBm) == SUCCESS) {
            //Add RSSI in dBm format
            jData[ICellularRadio::KEY_RSSIDBM] = MTS::Text::format(dBm);
        }
    }
    std::string sService;
    if(getService(sService) == SUCCESS) {
        jData[ICellularRadio::KEY_SERVICE] = sService;
    }
    std::string sDate, sTime, sTimeZone;
    if(getTime(sDate, sTime, sTimeZone) == SUCCESS) {
        jData[ICellularRadio::KEY_DATETIME] = sDate + " " + sTime + " GMT" + sTimeZone;
    }
    std::string sNetworkReg;
    REGISTRATION eReg;
    if (getRegistration(eReg) == SUCCESS) {
        if (convertRegistrationToString(eReg, sNetworkReg) == SUCCESS) {
            jData[ICellularRadio::KEY_NETWORK_REG] = sNetworkReg;
        }
    }
    std::string sCurrentCellMode;
    CELLULAR_MODES eModes;
    if (getCellularMode(eModes) == SUCCESS) {
        if (convertCellModesToString(eModes, sCurrentCellMode) == SUCCESS) {
            jData[ICellularRadio::KEY_CELL_MODE] = sCurrentCellMode;
        }
    }
}
ICellularRadio::CODE CellularRadio::getSimLockStatus(std::string& sData)
{
    printTrace("%s| Get SIM lock status", m_sName.c_str());
    // SIM card may introduce a delay to AT+CPIN? execution. Setting timeout to 1s as set in PPP chat patches.
    std::string sCmd("AT+CPIN?");
    std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 1000);
    const std::string sPrefix = "+CPIN: ";
    size_t start = sResult.find(sPrefix);
    size_t end = sResult.rfind(ICellularRadio::RSP_OK);
    if (start == std::string::npos || end == std::string::npos) {
        printWarning("%s| Unable to get SIM lock status from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }
    start += sPrefix.size();
    sData = MTS::Text::trim(sResult.substr(start, end-start));
    if(sData.size() == 0) {
        printWarning("%s| Unable to get SIM lock status from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }
    return SUCCESS;
}
void CellularRadio::initMipProfile(Json::Value& jData) {
    jData[ICellularRadio::KEY_MIP_ID] = 0;
    jData[ICellularRadio::KEY_MIP_ENABLED] = false;
    jData[ICellularRadio::KEY_MIP_NAI] = ICellularRadio::VALUE_UNKNOWN;
    jData[ICellularRadio::KEY_MIP_HOMEADDRESS] = ICellularRadio::VALUE_UNKNOWN;
    jData[ICellularRadio::KEY_MIP_PRIMARYHA] = ICellularRadio::VALUE_UNKNOWN;
    jData[ICellularRadio::KEY_MIP_SECONDARYHA] = ICellularRadio::VALUE_UNKNOWN;
    jData[ICellularRadio::KEY_MIP_MNAAASPI] = ICellularRadio::VALUE_UNKNOWN;
    jData[ICellularRadio::KEY_MIP_MNHASPI] = ICellularRadio::VALUE_UNKNOWN;
    jData[ICellularRadio::KEY_MIP_MNAAASS] = false;
    jData[ICellularRadio::KEY_MIP_MNHASS] = false;
}
const std::vector CellularRadio::getRegistrationCommands() {
    std::string sType;
    convertModelToType(getName(), sType);
    if (sType == VALUE_TYPE_LTE) {
        return { "CREG", "CGREG", "CEREG" };
    } else {
        return { "CREG", "CGREG" };
    }
}
ICellularRadio::REGISTRATION CellularRadio::parseRegResponse(std::string sResult) {
    size_t start = sResult.find(',');
    size_t stop = sResult.find(' ', start);
    std::string sRegStat = sResult.substr(start + 1, stop - start - 1);
    int32_t value;
    sscanf(sRegStat.c_str(), "%d", &value);
    return (ICellularRadio::REGISTRATION)value;
}
ICellularRadio::CODE CellularRadio::getRegistration(REGISTRATION& eRegistration, const std::string& sType) {
    std::string sCmd = "AT+" + sType + "?";
    std::string sResp = "+" + sType + ": ";
    std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 5000);
    if (sResult.find(sResp) == std::string::npos) {
        if(sResult.size() == 0) {
            printDebug("%s| Registration command returned no response", m_sName.c_str());
            return NO_RESPONSE;
        }
        printDebug("%s| Registration command returned unexpected response: [%s]", m_sName.c_str(), sResult.c_str());
        return FAILURE;
    }
    eRegistration = parseRegResponse(sResult);
    return SUCCESS;
}
ICellularRadio::CODE CellularRadio::getRegistration(REGISTRATION& eRegistration) {
    /* REGISTRATION_PRIORITY:
     *   REGISTERED     = 0
     *   ROAMING        = 1
     *   DENIED         = 2
     *   SEARCHING      = 3
     *   NOT_REGISTERED = 4
     *   UNKNOWN        = 5
     */
    uint8_t uRegPriority[] = { 4, 0, 3, 2, 5, 1 };
    ICellularRadio::CODE ret = ERROR;
    REGISTRATION eReg = UNKNOWN;
    eRegistration = UNKNOWN;
    // We need to check CREG, CGREG, and CEREG for possible success.
    // Depending on the radio, carrier, roaming, sim card, cellular mode and some other factors the registration
    // will come back differently depending on the type of connection that is possible.
    const auto & commands = getRegistrationCommands();
    for (const auto & cmd : commands) {
        ret = getRegistration(eReg, cmd);
        if (ret != SUCCESS) {
            break;
        }
        if (eReg == REGISTERED || eReg == ROAMING) {
            eRegistration = eReg;
            break;
        }
        eRegistration = (uRegPriority[eRegistration] > uRegPriority[eReg]) ? eReg : eRegistration;
    }
    return ret;
}
ICellularRadio::CODE CellularRadio::getCellularMode(CELLULAR_MODES &networks) {
    networks = CELLULAR_MODE_NA;
    std::string cmdResult = sendCommand("AT+COPS?");
    if (cmdResult.find(ICellularRadio::RSP_OK) == std::string::npos) {
        printError("%s| AT+COPS returned unexpected response: AT+COPS? [%s]", getName().c_str(), cmdResult.c_str());
        return FAILURE;
    }
    size_t cursor = 0;
    const std::vector &reply = MTS::Text::split(MTS::Text::getLine(MTS::Text::trim(cmdResult), cursor, cursor), ',');
    uint8_t op;
    if (reply.size() < 4 || !MTS::Text::parse(op, reply[3])) {
        printError("%s| AT+COPS Error parsing reply [AT+COPS?][%s]", getName().c_str(), cmdResult.c_str());
        return FAILURE;
    }
    switch (op) {
        case 0:  // GSM
        case 1:  // GSM Compact
        case 3:  // GSM w/EGPRS
            networks = CELLULAR_MODE_2G;
            break;
        case 2:  // UTRAN
        case 4:  // UTRAN w/HSDPA
        case 5:  // UTRAN w/HSUPA
        case 6:  // UTRAN w/HSDPA and HSUPA
            networks = CELLULAR_MODE_3G;
            break;
        case 7:  // E-UTRAN, LTE
        case 8:  // CAT M1, EC-GSM-IoT (A/Gb mode), LTE
        case 9:  // NB IoT, E-UTRAN (NB-S1 mode), LTE
            networks = CELLULAR_MODE_4G;
            break;
        default:
            printError("%s| AT+COPS unknown Radio Access Technology [AT+COPS?][%s]", getName().c_str(), cmdResult.c_str());
            return FAILURE;
    }
    return SUCCESS;
}
ICellularRadio::CODE CellularRadio::convertRegistrationToString(REGISTRATION eRegistration, std::string& sRegistration) {
    ICellularRadio::CODE eCode = FAILURE;
    switch (eRegistration) {
        case NOT_REGISTERED: sRegistration = ICellularRadio::VALUE_NOT_REGISTERED; eCode = SUCCESS; break;
        case REGISTERED: sRegistration = ICellularRadio::VALUE_REGISTERED; eCode = SUCCESS; break;
        case SEARCHING: sRegistration = ICellularRadio::VALUE_SEARCHING; eCode = SUCCESS; break;
        case DENIED: sRegistration = ICellularRadio::VALUE_DENIED; eCode = SUCCESS; break;
        case UNKNOWN: sRegistration = ICellularRadio::VALUE_UNKNOWN; eCode = SUCCESS; break;
        case ROAMING: sRegistration = ICellularRadio::VALUE_ROAMING; eCode = SUCCESS; break;
    }
    return eCode;
}
ICellularRadio::CODE CellularRadio::convertCellModesToString(ICellularRadio::CELLULAR_MODES eCellModes, std::string& sCellModes) {
    std::string sResult;
    if (eCellModes & CELLULAR_MODE_2G) {
        sResult += "2g,";
    }
    if (eCellModes & CELLULAR_MODE_3G) {
        sResult += "3g,";
    }
    if (eCellModes & CELLULAR_MODE_4G) {
        sResult += "4g,";
    }
    if (eCellModes & CELLULAR_MODE_5G) {
        sResult += "5g,";
    }
    if (!sResult.empty()) {
        sResult.pop_back();  // remove trailing comma
    }
    sCellModes = sResult;
    return SUCCESS;
}
ICellularRadio::CODE CellularRadio::unlockSimCard(const Json::Value& jArgs) {
    printTrace("%s| Unlock the SIM card using PIN code", m_sName.c_str());
    if(!jArgs["pin"].isString()) {
        return INVALID_ARGS;
    }
    std::string sCmd = "AT+CPIN=" + jArgs["pin"].asString();
    std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 5000);
    size_t pos = sResult.find(ICellularRadio::RSP_OK);
    if (pos == std::string::npos) {
        printWarning("%s| Failed to unlock the SIM card using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }
    return SUCCESS;
}
ICellularRadio::CODE CellularRadio::getSimCarrierCode(std::string& sCarrierCode) {
    CODE rc;
    printTrace("%s| Get carrier code from the SIM card installed", m_sName.c_str());
    do {
        // Try to detect based on the ICCID
        std::string sIccid;
        rc = getIccid(sIccid);
        if (rc != SUCCESS) {
            printError("%s| Unable to determine SIM carrier: Failed to fetch SIM identifier", m_sName.c_str());
            break;
        }
        printTrace("%s| Fetched ICCID: [%s]", m_sName.c_str(), sIccid.c_str());
        rc = getSimCarrierCode(sIccid, sCarrierCode);
        if (rc != SUCCESS) {
            printError("%s| Unable to determine SIM carrier: Unable to extract carrier from the SIM identifier", m_sName.c_str());
            break;
        }
        if (sCarrierCode != VALUE_UNKNOWN) {
            rc = SUCCESS;
            break;
        }
        // Fallback to the MCC/MNC detection
        std::string sMcc;
        std::string sMnc;
        rc = getSimMccMnc(sMcc, sMnc);
        if (rc != SUCCESS) {
            printError("%s| Unable to determine SIM carrier: Failed to fetch MCC/MNC from the SIM", m_sName.c_str());
            break;
        }
        rc = getSimCarrierCode(sMcc, sMnc, sCarrierCode);
        if (rc != SUCCESS) {
            printError("%s| Unable to determine SIM carrier: Unable to extract carrier from MCC/MNC of the SIM", m_sName.c_str());
            break;
        }
    } while(false);
    printTrace("%s| Detected carrier code: [%s]", m_sName.c_str(), sCarrierCode.c_str());
    return rc;
}
ICellularRadio::CODE CellularRadio::getSimCarrierCode(const std::string& sIccid, std::string& sCarrierCode) {
    const char* ICCID_PREFIX_VZW = "891480";
    const char* ICCID_PREFIX_ATT = "8901410";
    if (sIccid.find(ICCID_PREFIX_VZW) == 0) {
        printTrace("%s| Verizon SIM detected", m_sName.c_str());
        sCarrierCode = VALUE_CARRIER_CODE_VERIZON;
    } else if (sIccid.find(ICCID_PREFIX_ATT) == 0) {
        printTrace("%s| AT&T SIM detected", m_sName.c_str());
        sCarrierCode = VALUE_CARRIER_CODE_ATT;
    } else {
        // All other carriers for which ICCID prefixes are not defined
        printWarning("%s| Carrier code is unknown for this SIM ID: [%s]", m_sName.c_str(), sIccid.c_str());
        sCarrierCode = VALUE_UNKNOWN;
    }
    return SUCCESS;  // no error cases for now
}
ICellularRadio::CODE CellularRadio::getSimCarrierCode(const std::string& sMcc, const std::string& sMnc, std::string& sCarrierCode) {
    const Json::Value& jLookup = MccMncTable::getInstance()->lookup(sMcc, sMnc);
    do {
        printTrace("%s| MCC-MNC Lookup: [%s][%s][%s]", m_sName.c_str(),
                   sMcc.c_str(), sMnc.c_str(), jLookup.toStyledString().c_str());
        if (jLookup.isNull()) {
            printWarning("%s| Carrier code is unknown for this MCC/NNC combination: [%s][%s]", m_sName.c_str(), sMcc.c_str(), sMnc.c_str());
            sCarrierCode = VALUE_UNKNOWN;
            break;
        }
        if (jLookup["carrierCode"].asString().empty()) {
            printWarning("%s| Carrier code is unknown for this MCC/MNC combination: [%s][%s]", m_sName.c_str(), sMcc.c_str(), sMnc.c_str());
            sCarrierCode = VALUE_UNKNOWN;
            break;
        }
        sCarrierCode = jLookup["carrierCode"].asString();
        printTrace("%s| Detected carrier code by MCC/MNC: [%s]", m_sName.c_str(), sCarrierCode.c_str());
    } while (false);
    return CODE::SUCCESS;
}
ICellularRadio::CODE CellularRadio::simAccessReadBinary(uint16_t iFileId, uint8_t iP1, uint8_t iP2, uint8_t iLe, std::string& sResult) {
    printTrace("%s| Read binary from the SIM Elementary File", m_sName.c_str());
    // +CRSM=176,,,,[,[,]]
    std::string sCmd = "AT+CRSM=176,";
    sCmd += MTS::Text::format(iFileId);
    sCmd += ',';
    sCmd += MTS::Text::format(iP1);
    sCmd += ',';
    sCmd += MTS::Text::format(iP2);
    sCmd += ',';
    sCmd += MTS::Text::format(iLe);
    std::string sRawResponse = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 3000);
    printTrace("%s| Raw response from the radio: [%s]", m_sName.c_str(), sRawResponse.c_str());
    if (sRawResponse.empty()) {
        printError("%s| No response from the radio in 3 seconds.", m_sName.c_str());
        return CODE::NO_RESPONSE;
    }
    if (sRawResponse.rfind(RSP_ERROR) != std::string::npos) {
        printError("%s| Failed to read from the SIM Elementary File: [%s]", m_sName.c_str(), sRawResponse.c_str());
        return CODE::ERROR;
    }
    // Trim the output to remove excess whitespaces and line separators.
    sRawResponse = MTS::Text::trim(sRawResponse);
    // The response should start with "+CRSM: ".
    const std::string sResponsePrefix = "+CRSM: ";
    if (sRawResponse.rfind(sResponsePrefix, 0) != 0) {
        printError("%s| Unexpected response from the radio: [%s]", m_sName.c_str(), sRawResponse.c_str());
        return CODE::FAILURE;
    }
    // Select eveything between the prefix and the next line.
    auto eolPos = sRawResponse.find(CR, sResponsePrefix.size());
    sRawResponse = sRawResponse.substr(sResponsePrefix.size(), eolPos - sResponsePrefix.size());
    // Split the output by commas. Example: 144,0,"00FFFF02"
    auto vOutput = MTS::Text::split(sRawResponse, ',', 3);
    if (vOutput.size() < 3) {
        printError("%s| Unexpected response from the radio: [%s]", m_sName.c_str(), sRawResponse.c_str());
        return CODE::FAILURE;
    }
    // Two unquoted integers
    const std::string& sSw1 = vOutput[0];
    const std::string& sSw2 = vOutput[1];
    // Check if the SIM indicates any errors
    if (sSw1 != "144" || sSw2 != "0") {
        printError("%s| Unexpected response from the SIM: [%s]", m_sName.c_str(), sRawResponse.c_str());
        return CODE::FAILURE;
    }
    // Quectel radios quote the third element of the output. Remove the quoting.
    const std::string& sResponse = MTS::Text::trim(vOutput[2], '"');
    sResult = sResponse;
    return CODE::SUCCESS;
}
ICellularRadio::CODE CellularRadio::getSimMncLength(uint8_t& iLength) {
    printTrace("%s| Get SIM MNC length", m_sName.c_str());
    const int iEfadId = 0x6FAD;
    const uint8_t iOffsetHigh = 0;
    const uint8_t iOffsetLow = 0;
    const uint8_t iNumBytes = 0;
    CODE rc;
    std::string sEFadContent;
    rc = simAccessReadBinary(iEfadId, iOffsetLow, iOffsetHigh, iNumBytes, sEFadContent);
    if (rc != CODE::SUCCESS) {
        printError("%s| Failed to determine the SIM MNC length", m_sName.c_str());
        return rc;
    }
    // length of MNC in the IMSI is stored in byte 4 of EFad (indexing from 1)
    const uint8_t iMncLengthEfadIdx = 4;
    const uint8_t iCharsPerByte = 2;
    const uint8_t iMinEFadLength = iMncLengthEfadIdx * iCharsPerByte;
    if (sEFadContent.size() < iMinEFadLength) {
        printError("%s| SIM EFad does not contain an MNC length byte: [%s]", m_sName.c_str(), sEFadContent.c_str());
        return CODE::FAILURE;
    }
    // read byte 4 of EFad (indexing from 1) with the MNC length
    const size_t iMncStartPosition = (iMncLengthEfadIdx - 1) * iCharsPerByte;
    const std::string sMncLength = sEFadContent.substr(iMncStartPosition, iCharsPerByte);
    uint8_t iMncLength;
    // parse hex to unsigned byte
    if (!MTS::Text::parseHex(iMncLength, sMncLength)) {
        printError("%s| Unexpected SIM EFad content: [%s]", m_sName.c_str(), sEFadContent.c_str());
        return CODE::FAILURE;
    }
    // Only the lower 4 bits are used for MNC length, others are reserved for future use.
    iMncLength &= 0x0F;
    // Done
    iLength = iMncLength;
    printDebug("%s| Got MNC length of [%u]", m_sName.c_str(), iLength);
    return CODE::SUCCESS;
}
ICellularRadio::CODE CellularRadio::getSimMccMnc(std::string& sMccMnc) {
    printTrace("%s| Get MCC/MNC of the home network from the SIM", m_sName.c_str());
    CODE rc;
    std::string sImsi;
    uint8_t iMncLength;
    do {
        rc = getImsi(sImsi);
        if (rc != CODE::SUCCESS) {
            printError("%s| Failed to get SIM IMSI", m_sName.c_str());
            break;
        }
        if (sImsi.size() < 5) {
            printError("%s| Unexpected IMSI value: [%s]", m_sName.c_str(), sImsi.c_str());
            rc = CODE::FAILURE;
            break;
        }
        rc = getSimMncLength(iMncLength);
        if (rc != CODE::SUCCESS) {
            printError("%s| Failed to determine the MNC length", m_sName.c_str());
            break;
        }
        // MNC shall be 2 or 3 characters long
        if (iMncLength < 2 || iMncLength > 3) {
            printError("%s| Unexpected MNC length: [%u]", m_sName.c_str(), iMncLength);
            rc = CODE::FAILURE;
            break;
        }
        // PLMN code shall be 5 or 6 characters long
        const size_t mncLength = 3;
        const size_t plmnCodeLength = mncLength + iMncLength;
        sMccMnc = sImsi.substr(0, plmnCodeLength);
        // Done
        printDebug("%s| Got MCC/MNC of the home network from the SIM: [%s]", m_sName.c_str(), sMccMnc.c_str());
        rc = CODE::SUCCESS;
    } while (false);
    return rc;
}
ICellularRadio::CODE CellularRadio::getSimMccMnc(std::string& sMcc, std::string& sMnc) {
    CODE rc;
    std::string sPlmnCode;
    rc = getSimMccMnc(sPlmnCode);
    if (rc == CODE::SUCCESS) {
        // PLMN code consists of MCC (first 3 digits) and MNC (second 2 or 3 digits)
        sMcc = sPlmnCode.substr(0, 3);
        sMnc = sPlmnCode.substr(3);
    }
    return rc;
}
ICellularRadio::CODE CellularRadio::validateMsl(const Json::Value&) {
    printTrace("%s| Validate MSL", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::setMsid(const Json::Value&) {
    printTrace("%s| Set MSID", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::getMipProfile(Json::Value&) {
    printTrace("%s| Get MIP Active Profile", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::setMipActiveProfile(const Json::Value&) {
    printTrace("%s| Set MIP Active Profile", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::setMipNai(const Json::Value&) {
    printTrace("%s| Set MIP NAI", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::setMipHomeIp(const Json::Value&) {
    printTrace("%s| Set MIP Home IP", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::setMipPrimaryHa(const Json::Value&) {
    printTrace("%s| Set MIP Primary HA", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::setMipSecondaryHa(const Json::Value&) {
    printTrace("%s| Set MIP Secondary HA", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::setMipMnAaaSpi(const Json::Value&) {
    printTrace("%s| Set MIP MN-AAA SPI", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::setMipMnHaSpi(const Json::Value&) {
    printTrace("%s| Set MIP MN-HA SPI", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::setMipRevTun(const Json::Value&) {
    printTrace("%s| Set MIP Rev Tun", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::setMipMnAaaSs(const Json::Value&) {
    printTrace("%s| Set MIP MN-AAA SS", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::setMipMnHaSs(const Json::Value&) {
    printTrace("%s| Set MIP MN-HA SS", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::updateDc(const Json::Value&, UpdateCb&) {
    printTrace("%s| Update Device Configuration", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::updatePrl(const Json::Value&, UpdateCb&) {
    printTrace("%s| Update Preferred Roaming List", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::updateFumo(const Json::Value&, UpdateCb&) {
    printTrace("%s| Update Firmware Update Management Object", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::updateFumoLocal(int, ICellularRadio::UpdateCb&) {
    printTrace("%s| Update Local Firmware Update Management Object", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::fumoLocalInject(int, ICellularRadio::UpdateCb&) {
    printTrace("%s| Inject Delta Firmware Image File: not applicable", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::fumoLocalApply(ICellularRadio::UpdateCb&) {
    printTrace("%s| Apply Delta Firmware Image File: not applicable", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::fumoLocalCleanup() {
    printTrace("%s| Cleanup Delta Firmware Image File: not applicable", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::resetHfa(const Json::Value&, UpdateCb&) {
    printTrace("%s| HFA Reset", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::activate(const Json::Value&, UpdateCb&) {
    printTrace("%s| Activation", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::startOmaDm(ICellularRadio::UpdateCb&) {
    printTrace("%s| Start OMA DM procedure: not applicable", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::setActiveFirmware(const Json::Value&) {
    printTrace("%s| Set Active Firmware Image Number: not applicable", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::getActiveFirmware(std::string& sFwId) {
    printTrace("%s| Get Active Firmware Image Number: not applicable", m_sName.c_str());
    sFwId = ICellularRadio::VALUE_NOT_SUPPORTED;
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::sendBasicCommand(const std::string& sCmd, int32_t iTimeoutMillis, const char& ESC) {
    std::string response = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, iTimeoutMillis, ESC);
    if (response.size() == 0) {
        return NO_RESPONSE;
    } else if (response.find(ICellularRadio::RSP_OK) != std::string::npos) {
        return SUCCESS;
    } else if (response.find(ICellularRadio::RSP_ERROR) != std::string::npos) {
        return ERROR;
    } else {
        return FAILURE;
    }
}
std::string CellularRadio::sendCommand(const std::string& sCmd, const std::vector& vBail, int32_t timeoutMillis, const char& ESC) {
    return ICellularRadio::sendCommand(m_apIo, sCmd, vBail, timeoutMillis, ESC);
}
std::string CellularRadio::sendCommand(const std::string& sCmd, MTS::IO::CellularRadio::IsNeedMoreData& isNeedMoreData, int32_t timeoutMillis, const char& ESC) {
    return ICellularRadio::sendCommand(m_apIo, sCmd, isNeedMoreData, timeoutMillis, ESC);
}
std::string CellularRadio::waitResponse(const std::vector& vBail, int32_t timeoutMillis) {
    return ICellularRadio::waitResponse(m_apIo, vBail, timeoutMillis);
}
std::string CellularRadio::waitResponse(ICellularRadio::IsNeedMoreData& isNeedMoreData, int32_t timeoutMillis) {
    return ICellularRadio::waitResponse(m_apIo, isNeedMoreData, timeoutMillis);
}
ICellularRadio::CODE CellularRadio::sendData(const char* pData, size_t nBytes) {
    if(m_apIo.isNull()) {
        printError("RADIO| IO is not set in sendData");
        return ERROR;
    }
    // This limit comes from the Connection::write implementation. Otherwise we can get overflows.
    const size_t maxInt32 = INT32_MAX;    // iSize parameter type in Connection::write
    const size_t maxUInt32 = UINT32_MAX;  // return value type in Connection::write
    const size_t nSizeLimit = std::min(maxInt32, maxUInt32);
    if (nBytes > nSizeLimit) {
        printError("RADIO| Chunks larger than %d bytes are not supported", nSizeLimit);
        return INVALID_ARGS;
    }
    // Now we can ignore conversion and overflow warnings emitted by compiler.
    int32_t iResult;
    iResult = m_apIo->write(pData, nBytes);
    if(iResult != static_cast(nBytes)) {
        printError("RADIO| Failed to send data to radio");
        return ERROR;
    }
    return SUCCESS;
}
bool CellularRadio::splitAndAssign(const std::string& sLine, const std::string& sKey, Json::Value& jParent, const std::string& sJsonKey,  Json::ValueType eType) {
    std::vector vParts = MTS::Text::split(sLine, ":", 2);
    if(vParts.size() == 2 && vParts[0] == sKey) {
        if(eType == Json::ValueType::stringValue) {
            jParent[sJsonKey] = MTS::Text::trim(vParts[1]);
        } else if (eType == Json::ValueType::intValue || eType == Json::ValueType::uintValue) {
            //TODO:
            printWarning("%s| Unable to parse requested type from line [%s]", getName().c_str(), sKey.c_str(), sLine.c_str());
            return false;
        } else if(eType == Json::ValueType::realValue) {
            //TODO:
            printWarning("%s| Unable to parse requested type from line [%s]", getName().c_str(), sKey.c_str(), sLine.c_str());
            return false;
        } else if(eType == Json::ValueType::booleanValue) {
            //TODO:
            printWarning("%s| Unable to parse requested type from line [%s]", getName().c_str(), sKey.c_str(), sLine.c_str());
            return false;
        } else {
            printWarning("%s| Unable to parse requested type from line [%s]", getName().c_str(), sKey.c_str(), sLine.c_str());
            return false;
        }
    } else {
        printWarning("%s| Unable to parse %s from line [%s]", getName().c_str(), sKey.c_str(), sLine.c_str());
        return false;
    }
    return true;
}
bool CellularRadio::getCarrierFromFirmware(const std::string& sFirmware, std::string& sCarrier) {
    // Assuming that this function is not supported by the modem until overriden.
    return false;
}
bool CellularRadio::getHardwareVersionFromFirmware(const std::string& sFirmware, std::string& sHardware) {
    // Assuming that this function is not supported by the modem until overriden.
    return false;
}
const char *CellularRadio::RadioBandMap::getLTEBand(const int32_t channel)
{
    for (unsigned int ii = 0; ii < NUM_LTE_BANDS; ii++)
    {
        if (EULTRAband[ii].low <= channel && EULTRAband[ii].high >= channel)
        {
            return EULTRAband[ii].name;
        }
    }
    return ICellularRadio::VALUE_UNKNOWN;
}
const char *CellularRadio::RadioBandMap::getCDMABand(const int channel)
{
    for (unsigned int ii = 0; ii < NUM_WCDMA_BANDS; ii++)
    {
        if (WCDMAband[ii].low <= channel && WCDMAband[ii].high >= channel)
        {
            return WCDMAband[ii].name;
        }
    }
    return ICellularRadio::VALUE_UNKNOWN;
}
const char *CellularRadio::RadioBandMap::getGSMBand(const int channel)
{
    for (unsigned int ii = 0; ii < NUM_GSM_BANDS; ii++)
    {
        if (GSMband[ii].low <= channel && GSMband[ii].high >= channel)
        {
            return GSMband[ii].name;
        }
    }
    return ICellularRadio::VALUE_UNKNOWN;
}
const char *CellularRadio::RadioBandMap::getRadioBandName()
{
    const char *band = ICellularRadio::VALUE_UNKNOWN;
    if (m_sRadioType == ICellularRadio::VALUE_TYPE_LTE)
    {
        band = getLTEBand(m_iChannel);
    }
    else if (m_sRadioType == ICellularRadio::VALUE_TYPE_CDMA)
    {
        band = getCDMABand(m_iChannel);
    }
    else if (m_sRadioType == ICellularRadio::VALUE_TYPE_GSM)
    {
        band = getGSMBand(m_iChannel);
    }
    return band;
}
const char *CellularRadio::RadioBandMap::getRadioBandName(const std::string &channel, const std::string &radioType)
{
    const char *band = ICellularRadio::VALUE_UNKNOWN;
    int32_t chan = strtol(channel.c_str(), NULL, 10);
    if (radioType == ICellularRadio::VALUE_TYPE_LTE)
    {
        band = getLTEBand(chan);
    }
    else if (radioType == ICellularRadio::VALUE_TYPE_CDMA)
    {
        band = getCDMABand(chan);
    }
    else if (radioType == ICellularRadio::VALUE_TYPE_GSM)
    {
        band = getGSMBand(chan);
    }
    return band;
}
ICellularRadio::CODE CellularRadio::getFileSize(int fd, size_t& nBytes) {
    CODE rc = FAILURE;
    do {
        struct stat fileStatus;
        // On success, zero is returned. On error, -1 is returned, and errno is set appropriately.
        if (fstat(fd, &fileStatus) < 0) {
            printError("Failed to determine file size: %d", errno);
            break;
        }
        if (fileStatus.st_size < 0) {
            printError("Failed to determine file size, file size is negative: %d", fileStatus.st_size);
            break;
        }
        nBytes = static_cast(fileStatus.st_size);
        rc = SUCCESS;
    } while (false);
    lseek(fd, 0, SEEK_SET);
    return rc;
}
ICellularRadio::CODE CellularRadio::sizeToChunks(const size_t nBytes, const size_t chunkSize, size_t& nChunks) {
    nChunks = (nBytes + chunkSize - 1) / chunkSize;
    return SUCCESS;
}
ICellularRadio::CODE CellularRadio::readChunk(int fd, char* pChunk, size_t dChunkSize, size_t& nReadBytes) {
    size_t nUsedBuffer = 0;
    CODE rc = FAILURE;
    while (true) {
        if (nUsedBuffer > dChunkSize) {
            printError("Internal pointer error, abort upload: %d", nUsedBuffer);
            rc = ERROR;
            break;
        }
        if (nUsedBuffer == dChunkSize) {
            // full chunk received
            rc = SUCCESS;
            nReadBytes = dChunkSize;
            break;
        }
        char* pData = pChunk + nUsedBuffer;
        size_t nFreeBuffer = dChunkSize - nUsedBuffer;
        ssize_t dReadCount = read(fd, pData, nFreeBuffer);
        if (dReadCount < 0) {
            printError("Failed to read from the source file: %d", errno);
            rc = ERROR;
            break;
        }
        size_t duReadCount = static_cast(dReadCount);
        if (duReadCount == 0) {
            // EOF. Return what was already read
            nReadBytes = nUsedBuffer;
            rc = SUCCESS;
            break;
        }
        nUsedBuffer += duReadCount;
    }
    return rc;
}
ICellularRadio::CODE CellularRadio::setUeModeOfOperation(ICellularRadio::UE_MODES_OF_OPERATION mode) {
    printTrace("%s| Set UE Mode Of Operation: not applicable", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::getUeModeOfOperation(ICellularRadio::UE_MODES_OF_OPERATION& mode) {
    printTrace("%s| Get UE Mode Of Operation: not applicable", m_sName.c_str());
    mode = ICellularRadio::UE_MODES_OF_OPERATION::UNKNOWN_MODE;
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::disableVoiceSupport() {
    printTrace("%s| Disable Voice Support: not applicable", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::getVoiceSupport(Json::Value& jData) {
    bool bVoiceEnabled, bSmsOnly;
    CODE rc;
    rc = getVoiceSupport(bVoiceEnabled, bSmsOnly);
    if (rc == SUCCESS) {
        jData[KEY_VOICE_ENABLED] = bVoiceEnabled;
        jData[KEY_SMS_ONLY] = bSmsOnly;
    }
    return rc;
}
ICellularRadio::CODE CellularRadio::getVoiceSupport(bool&, bool&) {
    printTrace("%s| Get Voice Support: not applicable", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::sendBasicQuery(const std::string& sCmd, const std::string& sLabel, std::string& sResult, int32_t iTimeoutMillis, const char& ESC) {
    sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, iTimeoutMillis, ESC);
    printTrace("%s| Got response from the radio: [%s]", getName().c_str(), sResult.c_str());
    if (sResult.empty()) {
        return NO_RESPONSE;
    }
    if (sResult.rfind(ICellularRadio::RSP_ERROR) != std::string::npos) {
        return ERROR;
    }
    size_t end = sResult.rfind(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printError("%s| \"OK\" not found in the response: [%s]", getName().c_str(), sResult.c_str());
        return FAILURE;
    }
    size_t start = sResult.find(sLabel);
    if (start == std::string::npos) {
        printError("%s| Unexpected radio response: [%s]", getName().c_str(), sResult.c_str());
        return FAILURE;
    }
    start += sLabel.length();
    sResult = MTS::Text::trim(sResult.substr(start, end-start));
    return SUCCESS;
}
ICellularRadio::CODE CellularRadio::getSelectedBandsRaw(std::string& sRawBands)
{
    printTrace("%s| Acquiring selected bands: not applicable", m_sName.c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE CellularRadio::getPdpContexts(Json::Value& jData) {
    printTrace("%s| Fetching the list of PDP contexts from the radio", getName().c_str());
    CODE rc;
    const std::string sCommand = "AT+CGDCONT?";
    const int dTimeout = 1000;
    std::string sResult;
    rc = sendBasicQuery(sCommand, "", sResult, dTimeout);
    if (rc != SUCCESS) {
        return rc;
    }
    std::vector vContexts = MTS::Text::split(sResult, "+CGDCONT: ");
    std::vector vContextParams;
    for (size_t i = 0; i < vContexts.size(); i++) {
        vContexts[i] = MTS::Text::trim(vContexts[i]);
        if (vContexts[i].empty()) {
            continue;
        }
        vContextParams = MTS::Text::split(vContexts[i], ",", 4);
        if (vContextParams.size() < 3) {
            return FAILURE;
        }
        std::string sContextId = vContextParams[0];
        std::string sIpMode = MTS::Text::trim(vContextParams[1], '"'); // Remove double quotes from the start and end of the value
        std::string sApn = MTS::Text::trim(vContextParams[2], '"');    // Remove double quotes from the start and end of the value
        jData[sContextId][ICellularRadio::KEY_PDP_CONTEXT_IPMODE] = sIpMode;
        jData[sContextId][ICellularRadio::KEY_PDP_CONTEXT_APN] = sApn;
    }
    return SUCCESS;
}
ICellularRadio::CODE CellularRadio::setPdpContext(const std::string& sId, const Json::Value& jConfig) {
    printTrace("%s| Setting context to the radio", getName().c_str());
    CODE rc;
    if (sId.empty()) {
        printError("%s| PDP Context ID is not specified", getName().c_str());
        return FAILURE;
    }
    std::string sCommand = "AT+CGDCONT=" + sId;
    const int dTimeout = 1000;
    Json::Value jAllContexts;
    rc = getPdpContexts(jAllContexts);
    if (rc != SUCCESS) {
        printError("%s| Failed to retrieve the current PDP context configuration: [%d]", getName().c_str(), rc);
        return rc;
    }
    // Remove the context if no parameters defined
    if (!jConfig.isMember(ICellularRadio::KEY_PDP_CONTEXT_IPMODE) && !jConfig.isMember(ICellularRadio::KEY_PDP_CONTEXT_APN)) {
        if (jAllContexts.isMember(sId)) {
            rc = sendBasicCommand(sCommand, dTimeout);
            return rc;
        } else {
            printError("%s| PDP Context [%s] does not exist", getName().c_str(), sId.c_str());
            return FAILURE;
        }
    }
    std::string sIpMode;
    if (jConfig.isMember(ICellularRadio::KEY_PDP_CONTEXT_IPMODE)) {
        if (jConfig[ICellularRadio::KEY_PDP_CONTEXT_IPMODE] == "IP" || jConfig[ICellularRadio::KEY_PDP_CONTEXT_IPMODE] == "PPP" ||
            jConfig[ICellularRadio::KEY_PDP_CONTEXT_IPMODE] == "IPV6" || jConfig[ICellularRadio::KEY_PDP_CONTEXT_IPMODE] == "IPV4V6") {
                sIpMode = jConfig[ICellularRadio::KEY_PDP_CONTEXT_IPMODE].asString();
        } else {
            printError("%s| Invalid IP Mode defined: [%s]", getName().c_str(), jConfig[ICellularRadio::KEY_PDP_CONTEXT_IPMODE].asString().c_str());
            return FAILURE;
        }
    } else if (jAllContexts.isMember(sId)) {
        printInfo("%s| Re-using IP Mode [%s] for PDP context [%s]", getName().c_str(), jAllContexts[sId][ICellularRadio::KEY_PDP_CONTEXT_IPMODE].asString().c_str(), sId.c_str());
        sIpMode = jAllContexts[sId][ICellularRadio::KEY_PDP_CONTEXT_IPMODE].asString();
    } else {
        printError("%s| Failed to edit PDP context [%s] - no such context defined", getName().c_str(), sId.c_str());
        return FAILURE;
    }
    sCommand += ",\"";
    sCommand += sIpMode;
    sCommand += "\"";
    std::string sApn;
    if (jConfig.isMember(ICellularRadio::KEY_PDP_CONTEXT_APN)) {
        sApn = jConfig[ICellularRadio::KEY_PDP_CONTEXT_APN].asString();
    } else if (jAllContexts.isMember(sId)) {
        printInfo("%s| Re-using APN [%s] for PDP context [%s]", getName().c_str(), jAllContexts[sId][ICellularRadio::KEY_PDP_CONTEXT_APN].asString().c_str(), sId.c_str());
        sApn = jAllContexts[sId][ICellularRadio::KEY_PDP_CONTEXT_APN].asString();
    } else {
        printError("%s| Failed to edit PDP context [%s] - no such context defined", getName().c_str(), sId.c_str());
        return FAILURE;
    }
    sCommand += ",\"";
    sCommand += sApn;
    sCommand += "\"";
    rc = sendBasicCommand(sCommand, dTimeout);
    return rc;
}