~aleteoryx/muditaos

bf2d3208bb9b12ec7350cce9f285e169fad4e5bc — Marcin Zieliński 3 years ago 5a37d3b
[MOS-812] Improved CUSD message handling

* Added handling for shorter +CUSD formats, thus got rid of a crash.
* Added handling for the "push session"+"action required" state of a
  USSD interaction with the network.
M module-cellular/CMakeLists.txt => module-cellular/CMakeLists.txt +1 -0
@@ 75,6 75,7 @@ target_link_libraries(${PROJECT_NAME}

    PRIVATE
        Microsoft.GSL::GSL
	re2::re2
        time-constants
)


M module-cellular/at/UrcCusd.hpp => module-cellular/at/UrcCusd.hpp +11 -18
@@ 3,16 3,13 @@

#pragma once

#include "Urc.hpp"

#include <optional>
#include "Urc.hpp"

namespace at::urc
{

    class Cusd : public Urc
    {

        enum Tokens
        {
            Status,


@@ 20,6 17,11 @@ namespace at::urc
            DCS
        };

        bool valid_ = true;

        auto split(const std::string &str) -> void override;
        [[nodiscard]] auto getDCS() const noexcept -> std::optional<int>;

      public:
        enum class StatusType
        {


@@ 30,26 32,17 @@ namespace at::urc
            OperationNotSupported,
            NetworkTimeOut
        };
        Cusd(const std::string &urcBody, const std::string &urcHead = std::string());

        static constexpr std::string_view head = "+CUSD";
        static auto isURC(const std::string &uHead) -> bool
        {
            return uHead.find(Cusd::head) != std::string::npos;
        }

        using Urc::Urc;
        explicit Cusd(const std::string &urcBody, const std::string &urcHead = {});

        static auto isURC(const std::string &uHead) -> bool;
        [[nodiscard]] auto isValid() const noexcept -> bool override;

        [[nodiscard]] auto isActionNeeded() const noexcept -> bool;
        [[nodiscard]] auto getMessage() const noexcept -> std::optional<std::string>;
        [[nodiscard]] auto getStatus() const noexcept -> std::optional<StatusType>;
        [[nodiscard]] auto getDCS() const noexcept -> std::optional<int>;
        void split(const std::string &str) override;
        [[nodiscard]] auto getStatus() const noexcept -> StatusType;

        void Handle(UrcHandler &h) final
        {
            h.Handle(*this);
        }
        auto Handle(UrcHandler &h) -> void final;
    };
} // namespace at::urc

M module-cellular/at/src/UrcCusd.cpp => module-cellular/at/src/UrcCusd.cpp +85 -61
@@ 1,30 1,54 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <UrcCusd.hpp>
#include <Utils.hpp>
#include "UrcCusd.hpp"
#include <magic_enum.hpp>
#include <re2/re2.h>

using namespace at::urc;

auto Cusd::isURC(const std::string &uHead) -> bool
{
    return uHead == head;
}

auto Cusd::Handle(UrcHandler &h) -> void
{
    h.Handle(*this);
}

Cusd::Cusd(const std::string &urcBody, const std::string &urcHead) : Urc(urcBody, urcHead)
{
    split(urcBody);
    try {
        split(urcBody);

        // MOS-858: decide whether to try to display anything or to bail out
        int constexpr supportedAlphabets[]{0, 15};
        if (auto dcs = getDCS();
            dcs.has_value() && std::find(std::begin(supportedAlphabets), std::end(supportedAlphabets), *dcs) ==
                                   std::end(supportedAlphabets)) {
            throw std::runtime_error("unsupported CUSD alphabet");
        }

        if (auto status = getStatus();
            status != StatusType::FurtherUserActionRequired && status != StatusType::NoFurtherUserActionRequired) {
            LOG_WARN("unsupported CUSD status: %d", magic_enum::enum_integer(status));
        }
    }
    catch (std::runtime_error const &exc) {
        valid_ = false;
        LOG_ERROR("%s", exc.what());
    }
}

auto Cusd::isValid() const noexcept -> bool
{
    return tokens.size() == magic_enum::enum_count<Tokens>();
    return valid_ && Urc::isValid();
}

auto Cusd::isActionNeeded() const noexcept -> bool
{
    if (isValid()) {
        auto status = getStatus();
        if (status) {
            return *status == StatusType::FurtherUserActionRequired;
        }
    }
    return false;
    return getStatus() == StatusType::FurtherUserActionRequired;
}

auto Cusd::getMessage() const noexcept -> std::optional<std::string>


@@ 33,73 57,73 @@ auto Cusd::getMessage() const noexcept -> std::optional<std::string>
        return std::nullopt;
    }

    auto message = tokens[Tokens::Response];
    utils::findAndReplaceAll(message, "\"", "");
    if (auto const &messageToken = tokens[Tokens::Response]; !messageToken.empty()) {
        return std::make_optional(std::move(messageToken));
    }

    return utils::trim(message);
    return std::nullopt;
}

auto Cusd::getStatus() const noexcept -> std::optional<StatusType>
auto Cusd::getStatus() const noexcept -> StatusType
{
    if (isValid()) {
        int statusInt;
        try {
            statusInt = std::stoi(tokens[Tokens::Status]);
        }
        catch (const std::exception &e) {
            return std::nullopt;
        }
    return *magic_enum::enum_cast<StatusType>(std::stoi(tokens[Tokens::Status]));
}

        auto status = magic_enum::enum_cast<StatusType>(statusInt);
        if (status.has_value()) {
            return status.value();
        }
auto Cusd::getDCS() const noexcept -> std::optional<int>
{
    if (auto const &dcsToken = tokens[Tokens::DCS]; !dcsToken.empty()) {
        return std::make_optional(std::stoi(dcsToken));
    }

    return std::nullopt;
}

auto Cusd::getDCS() const noexcept -> std::optional<int>
auto Cusd::split(const std::string &str) -> void
{
    if (!isValid()) {
        return std::nullopt;
    }
    int dcs;
    try {
        dcs = std::stoi(tokens[Tokens::DCS]);
    }
    catch (const std::exception &e) {
        return std::nullopt;
    size_t constexpr maxNumberOfDcsTokens = 3;
    tokens.resize(maxNumberOfDcsTokens);

    using namespace re2;
    re2::StringPiece input(str);

    auto constexpr numberOfStatusTypes = magic_enum::enum_count<StatusType>();
    static_assert(numberOfStatusTypes <= 9,
                  "StatusType: too many enum entries to handle - please revise regex/algorithm");
    std::string regexForStatus(" ([0-" + std::to_string(numberOfStatusTypes) + "])");

    if (!RE2::Consume(&input, regexForStatus, &tokens[Tokens::Status])) {
        throw std::runtime_error("unrecognized CUSD status field or corrupted CUSD format");
    }

    return dcs;
}
void Cusd::split(const std::string &str)
{
    constexpr auto commaString    = ",";
    constexpr char tokenDelimiter = '\"';
    auto noFurtherInput = [&input]() { return input == "\r\n"; };

    tokens = utils::split(str, tokenDelimiter);
    for (auto &t : tokens) {
        utils::findAndReplaceAll(t, "\"", "");
        t = utils::trim(t);
    if (noFurtherInput()) {
        return;
    }

    auto dcs       = tokens[Tokens::DCS];
    auto dcsTokens = utils::split(dcs, commaString);
    size_t startQuotationMarkPosition = input.find('"');
    // must give the size to rfind() because called in the default way is broken
    size_t endQuotationMarkPosition = input.rfind('"', input.size());
    if (startQuotationMarkPosition == StringPiece::npos || endQuotationMarkPosition == StringPiece::npos) {
        throw std::runtime_error("cannot locate message - corrupted CUSD format");
    }

    if (dcsTokens.size() > 1) {
        tokens.pop_back();
        for (auto el : dcsTokens) {
            tokens.push_back(el);
        }
    if (endQuotationMarkPosition == startQuotationMarkPosition + 1) {
        LOG_WARN("empty CUSD message");
    }
    uint32_t token = 0;
    for (auto &t : tokens) {
        if (token != static_cast<uint32_t>(Tokens::Response)) {
            utils::findAndReplaceAll(t, commaString, "");
            t = utils::trim(t);
        }
        token += 1;
    else {
        auto messageStartPosition = startQuotationMarkPosition + 1;
        auto messageEndPosition   = endQuotationMarkPosition;
        auto messageLength        = messageEndPosition - messageStartPosition;
        tokens[Tokens::Response]  = input.substr(messageStartPosition, messageLength).as_string();
    }

    input.remove_prefix(endQuotationMarkPosition + 1);
    if (noFurtherInput()) {
        return;
    }

    if (!RE2::FullMatch(input, ",([0-9]+)\r\n", &tokens[Tokens::DCS])) {
        throw std::runtime_error("corrupted CUSD format");
    }
}

M module-cellular/test/unittest_URC.cpp => module-cellular/test/unittest_URC.cpp +47 -44
@@ 279,74 279,77 @@ TEST_CASE("+Qind: FOTA")
    }
}

TEST_CASE("+Cusd")
TEST_CASE("+CUSD")
{
    SECTION("Cusd action needed")
    using namespace at::urc;

    SECTION("MessageWithCorruptedHeader_IsntRecognizedAsCusd")
    {
        auto urc  = at::urc::UrcFactory::Create("+CUSD:1,\"test msg\",14");
        auto cusd = getURC<at::urc::Cusd>(urc);
        auto urc       = UrcFactory::Create("+CUSDD: 9,\"xyz\",15\r\n");
        auto maybeCusd = dynamic_cast<Cusd *>(urc.get());
        REQUIRE_FALSE(maybeCusd);
    }

        REQUIRE(cusd);
        REQUIRE(cusd->isValid());
        REQUIRE(cusd->isActionNeeded());
        REQUIRE(*cusd->getMessage() == "test msg");
        REQUIRE(*cusd->getStatus() == at::urc::Cusd::StatusType::FurtherUserActionRequired);
        REQUIRE(*cusd->getDCS() == 14);
    SECTION("MessageWithUnrecognizedStatus_RendersInvalid")
    {
        auto urc  = UrcFactory::Create("+CUSD: 9,\"xyz\",15\r\n");
        auto cusd = static_cast<Cusd *>(urc.get());
        REQUIRE_FALSE(cusd->isValid());
    }

    SECTION("MessageWithUnsupportedAlphabet_RendersInvalid")
    {
        auto urc  = UrcFactory::Create("+CUSD: 0,\"xyz\",9\r\n");
        auto cusd = static_cast<Cusd *>(urc.get());
        REQUIRE_FALSE(cusd->isValid());
    }

    SECTION("Cusd action needed with white spaces")
    SECTION("EmptyMessage_RendersValid")
    {
        auto urc  = at::urc::UrcFactory::Create("+CUSD: 0 , \"test msg\" , 15 ");
        auto cusd = getURC<at::urc::Cusd>(urc);
        REQUIRE(cusd);
        auto urc  = UrcFactory::Create("+CUSD: 0,\"\",15\r\n");
        auto cusd = static_cast<Cusd *>(urc.get());
        REQUIRE(cusd->isValid());
        REQUIRE_FALSE(cusd->isActionNeeded());
        REQUIRE(*cusd->getMessage() == "test msg");
        REQUIRE(*cusd->getStatus() == at::urc::Cusd::StatusType::NoFurtherUserActionRequired);
        REQUIRE(*cusd->getDCS() == 15);
    }

    SECTION("Cusd wrong status and DCS")
    SECTION("MessageWithNoActionRequiredAndSupportedAlphabet_RendersValid")
    {
        auto urc  = at::urc::UrcFactory::Create("+CUSD:100,\"test msg\", abc");
        auto cusd = getURC<at::urc::Cusd>(urc);
        REQUIRE(cusd);
        auto urc  = UrcFactory::Create("+CUSD: 0,\"xyz\",15\r\n");
        auto cusd = static_cast<Cusd *>(urc.get());
        REQUIRE(cusd->isValid());
        REQUIRE_FALSE(cusd->isActionNeeded());
        REQUIRE(*cusd->getMessage() == "test msg");
        REQUIRE_FALSE(cusd->getStatus());
        REQUIRE_FALSE(cusd->getDCS());
    }

    SECTION("Cusd action not needed")
    SECTION("MessageWithSpacesInside_RendersValidAndHasSpacesPreserved")
    {
        auto urc  = at::urc::UrcFactory::Create("+CUSD:2,\"test msg\",15");
        auto cusd = getURC<at::urc::Cusd>(urc);
        REQUIRE(cusd);
        std::string cusdBody(" xy    zzzz  ");
        auto urc  = UrcFactory::Create("+CUSD: 0,\"" + cusdBody + "\",15\r\n");
        auto cusd = static_cast<Cusd *>(urc.get());
        REQUIRE(cusd->isValid());
        REQUIRE_FALSE(cusd->isActionNeeded());
        REQUIRE(cusd->getMessage().has_value());
        REQUIRE(*cusd->getMessage() == cusdBody);
    }

    SECTION("no Cusd")
    SECTION("MessageWithQuotationMarksInside_RendersValidAndHasQuotationMarksPreserved")
    {
        auto urc  = at::urc::UrcFactory::Create("+CSUD:1,\"test msg\",15");
        auto cusd = getURC<at::urc::Cusd>(urc);
        REQUIRE_FALSE(cusd);
        std::string cusdBody("xy\"zz\"aa");
        auto urc  = UrcFactory::Create("+CUSD: 0,\"" + cusdBody + "\",15\r\n");
        auto cusd = static_cast<Cusd *>(urc.get());
        REQUIRE(cusd->isValid());
        REQUIRE(cusd->getMessage().has_value());
        REQUIRE(*cusd->getMessage() == cusdBody);
    }

    SECTION("too short")
    SECTION("MessageWithFurtherActionRequiredAndSupportedAlphabet_RendersValid")
    {
        auto urc  = at::urc::UrcFactory::Create("+CUSD:1,\"test msg\"");
        auto cusd = getURC<at::urc::Cusd>(urc);
        REQUIRE(cusd);
        REQUIRE_FALSE(cusd->isValid());
        auto urc  = UrcFactory::Create("+CUSD: 1,\"xyz\",15\r\n");
        auto cusd = static_cast<Cusd *>(urc.get());
        REQUIRE(cusd->isValid());
        REQUIRE(cusd->isActionNeeded());
    }

    SECTION("too long")
    SECTION("MessageWithExcessiveLineEnding_RendersInvalid")
    {
        auto urc  = at::urc::UrcFactory::Create("+CUSD:1,\"test msg\",15,15");
        auto cusd = getURC<at::urc::Cusd>(urc);
        REQUIRE(cusd);
        auto urc  = UrcFactory::Create("+CUSD: 1,\"xyz\",15\r\n\r\n");
        auto cusd = static_cast<Cusd *>(urc.get());
        REQUIRE_FALSE(cusd->isValid());
    }
}

M module-services/service-cellular/CellularUrcHandler.cpp => module-services/service-cellular/CellularUrcHandler.cpp +23 -12
@@ 76,30 76,41 @@ void CellularUrcHandler::Handle(Cusd &urc)
    response     = std::nullopt;
    auto message = urc.getMessage();
    if (!message) {
        LOG_WARN("CUSD with empty message - will be treated as not handled");
        return;
    }
    auto constexpr logLength = 16;
    auto logMessage          = message->substr(0, logLength);

    LOG_INFO("USSD body: %s", logMessage.c_str());
#if LOG_SENSITIVE_DATA_ENABLED
    LOG_INFO("USSD body: %s", message->c_str());
#else
    size_t constexpr truncatedLength = 16;
    LOG_INFO("USSD body (first %zu characters): %s", truncatedLength, message->substr(0, truncatedLength).c_str());
#endif

    urc.setHandled(true);

    if (urc.isActionNeeded()) {
        if (cellularService.ussdState == ussd::State::pullRequestSent) {
        switch (cellularService.ussdState) {
        case ussd::State::pullRequestSent:
            cellularService.ussdState = ussd::State::pullResponseReceived;
            [[fallthrough]];
        case ussd::State::pushSession: {
            cellularService.setUSSDTimer();
            auto msg = std::make_shared<cellular::MMIResponseMessage>(*message);
            cellularService.bus.sendUnicast(msg, service::name::appmgr);
            return;
        }
        default:
            LOG_WARN("unexpected URC handling state: %s", magic_enum::enum_name(cellularService.ussdState).data());
            return;
        }
    }
    else {
        CellularServiceAPI::USSDRequest(&cellularService, cellular::USSDMessage::RequestType::abortSession);
        cellularService.ussdState = ussd::State::sessionAborted;
        cellularService.setUSSDTimer();
        auto msg = std::make_shared<cellular::MMIPushMessage>(*message);
        cellularService.bus.sendUnicast(msg, service::name::appmgr);
    }

    urc.setHandled(true);
    CellularServiceAPI::USSDRequest(&cellularService, cellular::USSDMessage::RequestType::abortSession);
    cellularService.ussdState = ussd::State::sessionAborted;
    cellularService.setUSSDTimer();
    auto msg = std::make_shared<cellular::MMIPushMessage>(*message);
    cellularService.bus.sendUnicast(msg, service::name::appmgr);
}

void CellularUrcHandler::Handle(Ctze &urc)

M module-services/service-cellular/call/tests/CMakeLists.txt => module-services/service-cellular/call/tests/CMakeLists.txt +1 -0
@@ 10,6 10,7 @@ add_catch2_executable(
        sml::sml
        sml::utils::logger
        test::fakeit
	libphonenumber
)

set_source_files_properties(

M pure_changelog.md => pure_changelog.md +2 -0
@@ 3,6 3,7 @@
## Unreleased

### Changed / Improved
* Improved dialog with network via USSD

### Fixed
* Fixed disappearing "confirm" button in PIN entering screen


@@ 16,6 17,7 @@
* Fixed text not showing when adding/editing contact if text began with 'j' glyph
* Fixed VoLTE switch availability after taking out SIM card
* Fixed improper message text displaying after pasting from clipboard
* Fixed USSD handling crash

### Added