~aleteoryx/muditaos

253ff8685c0102c7fc2ee1dc6e462cd16186dd19 — Hubert Chrzaniuk 5 years ago 50f28e8
[EGD-4403][EGD-4412] MMI support (#1021)

* cleaned up namespace and class naming in MMI framework
* extended MMI framework to support call forwarding and
  password change MMIs
* added support for requests that have different
  number of regex capture groups
* added MMI framework tests
31 files changed, 1131 insertions(+), 200 deletions(-)

M changelog.md
M module-cellular/at/Commands.hpp
M module-services/service-cellular/CMakeLists.txt
D module-services/service-cellular/CallRequestFactory.cpp
R module-services/service-cellular/{CellularCallRequestHandler => CellularRequestHandler}.cpp
A module-services/service-cellular/RequestFactory.cpp
M module-services/service-cellular/ServiceCellular.cpp
A module-services/service-cellular/requests/CallForwardingRequest.cpp
A module-services/service-cellular/requests/CallRequest.cpp
A module-services/service-cellular/requests/ImeiRequest.cpp
A module-services/service-cellular/requests/PasswordRegistrationRequest.cpp
A module-services/service-cellular/requests/PinChangeRequest.cpp
R module-services/service-cellular/{CallRequest => requests/Request}.cpp
A module-services/service-cellular/requests/SupplementaryServicesRequest.cpp
A module-services/service-cellular/requests/UssdRequest.cpp
D module-services/service-cellular/service-cellular/CallRequest.hpp
D module-services/service-cellular/service-cellular/CallRequestHandler.hpp
R module-services/service-cellular/service-cellular/{CellularCallRequestHandler => CellularRequestHandler}.hpp
R module-services/service-cellular/service-cellular/{CallRequestFactory => RequestFactory}.hpp
A module-services/service-cellular/service-cellular/RequestHandler.hpp
M module-services/service-cellular/service-cellular/ServiceCellular.hpp
A module-services/service-cellular/service-cellular/requests/CallForwardingRequest.hpp
A module-services/service-cellular/service-cellular/requests/CallRequest.hpp
A module-services/service-cellular/service-cellular/requests/ImeiRequest.hpp
A module-services/service-cellular/service-cellular/requests/PasswordRegistrationRequest.hpp
A module-services/service-cellular/service-cellular/requests/PinChangeRequest.hpp
A module-services/service-cellular/service-cellular/requests/Request.hpp
A module-services/service-cellular/service-cellular/requests/SupplementaryServicesRequest.hpp
A module-services/service-cellular/service-cellular/requests/UssdRequest.hpp
A module-services/service-cellular/tests/CMakeLists.txt
A module-services/service-cellular/tests/unittest_mmi.cpp
M changelog.md => changelog.md +1 -0
@@ 30,6 30,7 @@
* `[audio]` Added support for Bluetooth audio profiles
* `[filesystem]` Added support for standard file IO library.
* [`[messages]`] Added fetching text messages at phone startup.
* `[cellular]` Support for MMI Call Forwarding call

## Changed


M module-cellular/at/Commands.hpp => module-cellular/at/Commands.hpp +12 -1
@@ 135,6 135,12 @@ namespace at
        CFUN_DISABLE_TRANSMITTING, /// Disable the ME from both transmitting and receiving RF signals
        LIST_MESSAGES,             /// List all messages from message storage
        GET_IMEI,
        CCFC, /// Supplementary Services - Call Forwarding Number and Conditions Control
        CCWA, /// Supplementary Services - Call Waiting Control
        CHLD, /// Supplementary Services - Call Related Supplementary Services
        CLIR, /// Supplementary Services - Calling Line Identification Restriction
        COLP, /// Supplementary Services - Connected Line Identification Presentation
        CSSN, /// Supplementary Services - Supplementary Service Notifications
    };

    // below timeouts are defined in Quectel_EC25&EC21_AT_Commands_Manual_V1.3.pdf


@@ 215,7 221,12 @@ namespace at
            {AT::ENABLE_NETWORK_REGISTRATION_URC, {"AT+CREG=2"}},
            {AT::SET_SMS_TEXT_MODE_UCS2, {"AT+CSMP=17,167,0,8"}},
            {AT::LIST_MESSAGES, {"AT+CMGL=\"ALL\"", default_doc_timeout}},
            {AT::GET_IMEI, {"AT+GSN", default_doc_timeout}}};
            {AT::GET_IMEI, {"AT+GSN", default_doc_timeout}},
            {AT::CCFC, {"AT+CCFC=", default_doc_timeout}},
            {AT::CCWA, {"AT+CCWA=\"", default_doc_timeout}},
            {AT::CHLD, {"AT+CHLD=\"", default_doc_timeout}},
            {AT::COLP, {"AT+COLP=\"", default_doc_timeout}},
            {AT::CSSN, {"AT+CSSN=\"", default_doc_timeout}}};

        if (fact.count(at)) {
            return fact.at(at);

M module-services/service-cellular/CMakeLists.txt => module-services/service-cellular/CMakeLists.txt +15 -5
@@ 1,4 1,4 @@
project(service-cellular)
project(service-cellular)
message( "${PROJECT_NAME}  ${CMAKE_CURRENT_LIST_DIR}" )

set(SOURCES


@@ 7,10 7,17 @@ set(SOURCES
    CellularUrcHandler.cpp
    ServiceCellular.cpp
    SignalStrength.cpp
    CallRequest.cpp
    SimCard.cpp
    CallRequestFactory.cpp
    CellularCallRequestHandler.cpp
    RequestFactory.cpp
    CellularRequestHandler.cpp
    requests/Request.cpp
    requests/CallRequest.cpp
    requests/SupplementaryServicesRequest.cpp
    requests/CallForwardingRequest.cpp
    requests/PasswordRegistrationRequest.cpp
    requests/PinChangeRequest.cpp
    requests/ImeiRequest.cpp
    requests/UssdRequest.cpp
)

add_library(${PROJECT_NAME} STATIC ${SOURCES})


@@ 28,4 35,7 @@ target_link_libraries(${PROJECT_NAME}
        module-bsp
        module-cellular
    )
    

if (${ENABLE_TESTS})
    add_subdirectory(tests)
endif ()

D module-services/service-cellular/CallRequestFactory.cpp => module-services/service-cellular/CallRequestFactory.cpp +0 -41
@@ 1,41 0,0 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "service-cellular/CallRequestFactory.hpp"

#include <re2/re2.h>

#include <at/Commands.hpp>
#include <log/log.hpp>

namespace call_request
{
    constexpr inline auto IMEIRegex = "(\\*#06#)";
    constexpr inline auto USSDRegex = "^[\\*].*[\\#]$";

    Factory::Factory(const std::string &data) : request(data)
    {
        registerRequest(IMEIRegex, IMEIRequest::create);

        /*It have to be last check due to 3GPP TS 22.030*/
        registerRequest(USSDRegex, USSDRequest::create);
    }

    void Factory::registerRequest(std::string regex, CreateCallback callback)
    {
        requestMap.insert(std::make_pair(regex, callback));
    }

    std::unique_ptr<IRequest> Factory::create()
    {
        for (const auto &element : requestMap) {
            re2::StringPiece input(request);
            re2::RE2 reg(element.first);
            if (re2::RE2::FullMatch(input, reg)) {
                return element.second(request);
            }
        }
        return std::make_unique<CallRequest>(request);
    }

} // namespace call_request

R module-services/service-cellular/CellularCallRequestHandler.cpp => module-services/service-cellular/CellularRequestHandler.cpp +39 -5
@@ 1,15 1,22 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "service-cellular/CellularCallRequestHandler.hpp"
#include "service-cellular/CallRequestFactory.hpp"
#include "service-cellular/CellularRequestHandler.hpp"
#include "service-cellular/RequestFactory.hpp"
#include "service-cellular/ServiceCellular.hpp"

#include "Service/Bus.hpp"
#include "Service/Message.hpp"
#include "Service/Timer.hpp"

void CellularCallRequestHandler::handle(IMEIRequest &request, at::Result &result)
#include "service-cellular/requests/CallRequest.hpp"
#include "service-cellular/requests/SupplementaryServicesRequest.hpp"
#include "service-cellular/requests/PasswordRegistrationRequest.hpp"
#include "service-cellular/requests/PinChangeRequest.hpp"
#include "service-cellular/requests/ImeiRequest.hpp"
#include "service-cellular/requests/UssdRequest.hpp"

void CellularRequestHandler::handle(ImeiRequest &request, at::Result &result)
{
    if (!request.checkModemResponse(result)) {
        request.setHandled(false);


@@ 18,7 25,7 @@ void CellularCallRequestHandler::handle(IMEIRequest &request, at::Result &result
    request.setHandled(true);
}

void CellularCallRequestHandler::handle(USSDRequest &request, at::Result &result)
void CellularRequestHandler::handle(UssdRequest &request, at::Result &result)
{
    if (!request.checkModemResponse(result)) {
        request.setHandled(false);


@@ 29,7 36,7 @@ void CellularCallRequestHandler::handle(USSDRequest &request, at::Result &result
    request.setHandled(true);
}

void CellularCallRequestHandler::handle(CallRequest &request, at::Result &result)
void CellularRequestHandler::handle(CallRequest &request, at::Result &result)
{
    if (!request.checkModemResponse(result)) {
        request.setHandled(false);


@@ 44,3 51,30 @@ void CellularCallRequestHandler::handle(CallRequest &request, at::Result &result
        &cellular);
    request.setHandled(true);
}

void CellularRequestHandler::handle(SupplementaryServicesRequest &request, at::Result &result)
{
    if (!request.checkModemResponse(result)) {
        request.setHandled(false);
        return;
    }
    request.setHandled(true);
}

void CellularRequestHandler::handle(PasswordRegistrationRequest &request, at::Result &result)
{
    if (!request.checkModemResponse(result)) {
        request.setHandled(false);
        return;
    }
    request.setHandled(true);
}

void CellularRequestHandler::handle(PinChangeRequest &request, at::Result &result)
{
    if (!request.checkModemResponse(result)) {
        request.setHandled(false);
        return;
    }
    request.setHandled(true);
}

A module-services/service-cellular/RequestFactory.cpp => module-services/service-cellular/RequestFactory.cpp +83 -0
@@ 0,0 1,83 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "service-cellular/RequestFactory.hpp"

#include <re2/re2.h>

#include <at/Commands.hpp>
#include <log/log.hpp>

#include "service-cellular/requests/CallRequest.hpp"
#include "service-cellular/requests/SupplementaryServicesRequest.hpp"
#include "service-cellular/requests/PasswordRegistrationRequest.hpp"
#include "service-cellular/requests/PinChangeRequest.hpp"
#include "service-cellular/requests/ImeiRequest.hpp"
#include "service-cellular/requests/UssdRequest.hpp"

namespace cellular
{
    RequestFactory::RequestFactory(const std::string &data) : request(data)
    {
        registerRequest(ImeiRegex, ImeiRequest::create);
        registerRequest(PasswordRegistrationRegex, PasswordRegistrationRequest::create);
        registerRequest(PinChangeRegex, PinChangeRequest::create);
        registerRequest(SupplementaryServicesRegex, SupplementaryServicesRequest::create);
        /*It have to be last check due to 3GPP TS 22.030*/
        registerRequest(UssdRegex, UssdRequest::create);
    }

    void RequestFactory::registerRequest(std::string regex, CreateCallback callback)
    {
        requestMap.emplace_back(std::make_pair(regex, callback));
    }

    std::unique_ptr<IRequest> RequestFactory::create()
    {
        std::string groupA, groupB, groupC, groupD, groupE, groupF;
        GroupMatch matchPack = {groupA, groupB, groupC, groupD, groupE, groupF};

        std::vector<std::string> results;

        for (const auto &element : requestMap) {
            auto const &requestCreateCallback = element.second;
            re2::StringPiece input(request);
            re2::RE2 reg(element.first);

            std::vector<RE2::Arg> reArguments;
            std::vector<RE2::Arg *> reArgumentPtrs;

            std::size_t numberOfGroups = reg.NumberOfCapturingGroups();

            if (numberOfGroups > matchPack.size()) {
                LOG_ERROR("Internal error, GroupMatch has to be redefined.");
                break;
            }

            reArguments.resize(numberOfGroups);
            reArgumentPtrs.resize(numberOfGroups);
            results.resize(numberOfGroups);

            for (std::size_t i = 0; i < numberOfGroups; ++i) {
                /// Bind RE arguments to strings from vector.
                reArguments[i] = &matchPack[i].get();
                /// Assign pointers to arguments.
                reArgumentPtrs[i] = &reArguments[i];
            }

            if (re2::RE2::FullMatchN(input, reg, reArgumentPtrs.data(), numberOfGroups)) {
                try {
                    if (auto req = requestCreateCallback(request, matchPack)) {
                        return req;
                    }
                }
                catch (const std::runtime_error &except) {
                    LOG_ERROR("Failed to create MMI request. Error message:\n%s", except.what());
                }
                break;
            }
        }
        return std::make_unique<CallRequest>(request);
    }

} // namespace cellular

M module-services/service-cellular/ServiceCellular.cpp => module-services/service-cellular/ServiceCellular.cpp +5 -4
@@ 9,9 9,10 @@
#include "service-cellular/SignalStrength.hpp"
#include "service-cellular/State.hpp"
#include "service-cellular/USSD.hpp"

#include "SimCard.hpp"
#include "service-cellular/CallRequestFactory.hpp"
#include "service-cellular/CellularCallRequestHandler.hpp"
#include "service-cellular/RequestFactory.hpp"
#include "service-cellular/CellularRequestHandler.hpp"

#include <Audio/AudioCommon.hpp>
#include <BaseInterface.hpp>


@@ 728,8 729,8 @@ sys::MessagePointer ServiceCellular::DataReceivedHandler(sys::DataMessage *msgl,
            break;
        }

        call_request::Factory factory(msg->number.getEntered());
        CellularCallRequestHandler handler(*this);
        cellular::RequestFactory factory(msg->number.getEntered());
        CellularRequestHandler handler(*this);

        auto request = factory.create();
        auto result  = channel->cmd(request->command());

A module-services/service-cellular/requests/CallForwardingRequest.cpp => module-services/service-cellular/requests/CallForwardingRequest.cpp +107 -0
@@ 0,0 1,107 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <string>

#include <at/Commands.hpp>
#include <Utils.hpp>

#include "service-cellular/requests/CallForwardingRequest.hpp"

namespace
{
    // according to EC25&EC21_AT_Commands_Manual_V1.3
    const std::map<std::string, std::string> reasonCodes = {{
        {"21", "0"},  // Unconditional
        {"67", "1"},  // MobileBusy
        {"61", "2"},  // NoReply
        {"62", "3"},  // NotReachable
        {"002", "4"}, // AllCallForwarding
        {"004", "5"}, // AllConditionalCallForwarding
    }};
} // namespace

namespace cellular
{

    std::unique_ptr<SupplementaryServicesRequest> CallForwardingRequest::create(const std::string &serviceCode,
                                                                                const std::string &data,
                                                                                GroupMatch matchGroups)
    {
        if (auto it = reasonCodes.find(serviceCode); it != reasonCodes.end()) {
            return std::make_unique<CallForwardingRequest>(it->second, data, matchGroups);
        }

        return nullptr;
    }

    auto CallForwardingRequest::command() -> std::string
    {
        std::array<std::function<std::string()>, 2> commandParts = {
            [this]() { return this->getCommandMode(); },
            [this]() { return this->getCommandNumber(); },
        };

        std::string cmd(at::factory(at::AT::CCFC) + this->getCommandReason());
        for (auto &cmdPart : commandParts) {
            auto partStr = cmdPart();
            if (partStr.empty()) {
                break;
            }
            cmd.append("," + partStr);
        }

        return cmd;
    }

    auto CallForwardingRequest::getCommandReason() const -> std::string
    {
        return forwardReason;
    }

    auto CallForwardingRequest::getCommandMode() const -> std::string
    {
        return utils::to_string(magic_enum::enum_integer(procedureType));
    }

    auto CallForwardingRequest::getCommandNumber() const -> std::string
    {
        return phoneNumber.empty() ? std::string() : "\"" + phoneNumber + "\"";
    }

    auto CallForwardingRequest::getCommandType() const -> std::string
    {
        // according to EC25&EC21_AT_Commands_Manual_V1.3
        if (auto pos = phoneNumber.find("+"); pos != std::string::npos) {
            return addressFormatTypeDefault;
        }
        else {
            return addressFormatTypeInternational;
        }
    }

    auto CallForwardingRequest::getCommandClass() const -> std::string
    {
        return getCommandInformationClass(basicServiceGroup);
    }

    auto CallForwardingRequest::getCommandSubAddr() const -> std::string
    {
        return subaddrDefault;
    }

    auto CallForwardingRequest::getCommandSatype() const -> std::string
    {
        return std::string();
    }

    auto CallForwardingRequest::getCommandTime() const -> std::string
    {
        return noReplyConditionTimer;
    }

    void CallForwardingRequest::handle(RequestHandler &h, at::Result &result)
    {
        h.handle(*this, result);
    }
} // namespace cellular

A module-services/service-cellular/requests/CallRequest.cpp => module-services/service-cellular/requests/CallRequest.cpp +23 -0
@@ 0,0 1,23 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <string>
#include <memory>

#include <at/Result.hpp>
#include <at/Commands.hpp>

#include "service-cellular/requests/CallRequest.hpp"

namespace cellular
{
    std::string CallRequest::command()
    {
        return std::string(at::factory(at::AT::ATD) + request + ";");
    }

    void CallRequest::handle(RequestHandler &h, at::Result &result)
    {
        h.handle(*this, result);
    }
}; // namespace cellular

A module-services/service-cellular/requests/ImeiRequest.cpp => module-services/service-cellular/requests/ImeiRequest.cpp +29 -0
@@ 0,0 1,29 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <string>
#include <memory>

#include <at/Result.hpp>
#include <at/Commands.hpp>

#include "service-cellular/requests/ImeiRequest.hpp"

namespace cellular
{
    std::string ImeiRequest::command()
    {
        return std::string(at::factory(at::AT::GET_IMEI));
    }

    void ImeiRequest::handle(RequestHandler &h, at::Result &result)
    {
        h.handle(*this, result);
    }

    std::unique_ptr<ImeiRequest> ImeiRequest::create(const std::string &data, GroupMatch)
    {
        return std::make_unique<ImeiRequest>(data);
    }

}; // namespace cellular

A module-services/service-cellular/requests/PasswordRegistrationRequest.cpp => module-services/service-cellular/requests/PasswordRegistrationRequest.cpp +86 -0
@@ 0,0 1,86 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <string>

#include <at/Commands.hpp>
#include <Utils.hpp>

#include "service-cellular/requests/PasswordRegistrationRequest.hpp"

namespace
{
    // according to EC25&EC21_AT_Commands_Manual_V1.3
    const std::map<std::string, std::string> barringServiceToFacilityMap = {{
        {"33", "AO"},  // BAOC (Bar All Outgoing Calls)
        {"331", "OI"}, // BOIC (Bar Outgoing International Calls)
        {"332", "OX"}, // BOIC-exHC (Bar Outgoing International Calls except to home country)
        {"35", "AI"},  // BAIC (Bar All Incoming Calls)
        {"351", "IR"}, // BIC-Roam (Bar Incoming Calls when Roaming outside the home country)
        {"330", "AB"}, // All barring services
        {"333", "AG"}, // All outgoing barring services
        {"353", "AC"}, // All incoming barring services
        {"", "AB"},    // All incoming barring services (According to 3GPP TS 22.030 V16.0.0 )
    }};
} // namespace

namespace
{
    constexpr inline std::string_view changeNetworkPasswordServiceCode = "03";
}

namespace cellular
{

    std::unique_ptr<PasswordRegistrationRequest> PasswordRegistrationRequest::create(const std::string &data,
                                                                                     GroupMatch matchGroups)
    {
        return std::make_unique<PasswordRegistrationRequest>(data, matchGroups);
    }

    auto PasswordRegistrationRequest::command() -> std::string
    {
        std::array<std::function<std::string()>, 3> commandParts = {
            [this]() { return this->getCommandFacility(); },
            [this]() { return "," + this->getOldPassword(); },
            [this]() { return "," + this->getNewPassword(); },
        };

        std::string cmd(at::factory(at::AT::CPWD));
        for (auto &cmdPart : commandParts) {
            cmd.append(cmdPart());
        }

        return cmd;
    }

    auto PasswordRegistrationRequest::getCommandFacility() const noexcept -> std::string
    {
        if (auto it = barringServiceToFacilityMap.find(requestBarringService);
            it != barringServiceToFacilityMap.end()) {
            return "\"" + it->second + "\"";
        }
        return std::string();
    }

    auto PasswordRegistrationRequest::getOldPassword() const noexcept -> std::string
    {
        return "\"" + requestOldPassword + "\"";
    }

    auto PasswordRegistrationRequest::getNewPassword() const noexcept -> std::string
    {
        return "\"" + requestNewPassword + "\"";
    }

    void PasswordRegistrationRequest::handle(RequestHandler &h, at::Result &result)
    {
        h.handle(*this, result);
    }

    auto PasswordRegistrationRequest::isValid() const noexcept -> bool
    {
        return requestNewPassword == requestNewPasswordRepeat && !requestNewPasswordRepeat.empty() &&
               !requestNewPassword.empty() && !requestOldPassword.empty();
    }
} // namespace cellular

A module-services/service-cellular/requests/PinChangeRequest.cpp => module-services/service-cellular/requests/PinChangeRequest.cpp +92 -0
@@ 0,0 1,92 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <string>

#include <at/Commands.hpp>
#include <Utils.hpp>

#include "service-cellular/requests/PinChangeRequest.hpp"

namespace
{
    //(according to 3GPP TS 22.030 V16.0.0):
    const std::map<std::string, cellular::PinChangeRequest::PassChangeType> serviceCodeToChangeType = {{
        {"04", cellular::PinChangeRequest::PassChangeType::ChangePin},
        {"042", cellular::PinChangeRequest::PassChangeType::ChangePin2},
        {"05", cellular::PinChangeRequest::PassChangeType::ChangePinByPuk},
    }};

    constexpr inline std::string_view changeNetworkPasswordServiceCode = "03";
} // namespace

namespace cellular
{

    PinChangeRequest::PinChangeRequest(const std::string &data, GroupMatch matchGroups)
        : Request(data), requestOldPinOrPuk(matchGroups[magic_enum::enum_integer(PinChangeRegexGroups::OldPassword)]),
          requestNewPin(matchGroups[magic_enum::enum_integer(PinChangeRegexGroups::NewPassword)]),
          requestNewPinRepeat(matchGroups[magic_enum::enum_integer(PinChangeRegexGroups::NewPasswordRepeat)])
    {
        auto &serviceCode = matchGroups[magic_enum::enum_integer(PinChangeRegexGroups::ServiceCode)];
        if (auto it = serviceCodeToChangeType.find(serviceCode); it == serviceCodeToChangeType.end()) {
            throw std::runtime_error(std::string(__FUNCTION__) + ": input service code not supported");
        }
        else {
            passChangeType = it->second;
        }
    }

    std::unique_ptr<PinChangeRequest> PinChangeRequest::create(const std::string &data, GroupMatch matchGroups)
    {
        return std::make_unique<PinChangeRequest>(data, matchGroups);
    }

    auto PinChangeRequest::command() -> std::string
    {
        std::array<std::function<std::string()>, 2> commandParts = {
            [this]() { return this->getOldPinOrPuk(); },
            [this]() { return "," + this->getNewPin(); },
        };

        std::string cmd;
        switch (passChangeType) {
        case PassChangeType::ChangePin:
            cmd.append(at::factory(at::AT::CPWD) + "\"SC\",");
            break;
        case PassChangeType::ChangePin2:
            cmd.append(at::factory(at::AT::CPWD) + "\"P2\",");
            break;
        case PassChangeType::ChangePinByPuk:
            cmd.append(at::factory(at::AT::CPIN));
            break;
        };

        for (auto &cmdPart : commandParts) {
            cmd.append(cmdPart());
        }

        return cmd;
    }

    auto PinChangeRequest::getOldPinOrPuk() const noexcept -> std::string
    {
        return "\"" + requestOldPinOrPuk + "\"";
    }

    auto PinChangeRequest::getNewPin() const noexcept -> std::string
    {
        return "\"" + requestNewPin + "\"";
    }

    void PinChangeRequest::handle(RequestHandler &h, at::Result &result)
    {
        h.handle(*this, result);
    }

    auto PinChangeRequest::isValid() const noexcept -> bool
    {
        return requestNewPin == requestNewPinRepeat && !requestNewPin.empty() && !requestNewPinRepeat.empty() &&
               !requestOldPinOrPuk.empty();
    }
} // namespace cellular

R module-services/service-cellular/CallRequest.cpp => module-services/service-cellular/requests/Request.cpp +6 -43
@@ 1,18 1,16 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <functional>
#include <string>
#include <memory>

#include "service-cellular/CallRequest.hpp"
#include <at/Result.hpp>
#include <at/Commands.hpp>

namespace call_request
{
#include "service-cellular/requests/Request.hpp"

    Request::Request(const std::string &data) : request(data){};
namespace cellular
{
    void Request::setHandled(bool handled)
    {
        isRequestHandled = handled;


@@ 25,43 23,8 @@ namespace call_request
    {
        return result.code == at::Result::Code::OK;
    }

    std::string IMEIRequest::command()
    {
        return std::string(at::factory(at::AT::GET_IMEI));
    }
    void IMEIRequest::handle(CallRequestHandler &h, at::Result &result)
    {
        h.handle(*this, result);
    }

    std::unique_ptr<IMEIRequest> IMEIRequest::create(const std::string &data)
    {
        return std::make_unique<IMEIRequest>(data);
    }

    std::string USSDRequest::command()
    {
        return std::string(at::factory(at::AT::CUSD_SEND) + request + ",15");
    }

    std::unique_ptr<USSDRequest> USSDRequest::create(const std::string &data)
    {
        return std::make_unique<USSDRequest>(data);
    }

    void USSDRequest::handle(CallRequestHandler &h, at::Result &result)
    {
        h.handle(*this, result);
    }

    std::string CallRequest::command()
    {
        return std::string(at::factory(at::AT::ATD) + request + ";");
    }

    void CallRequest::handle(CallRequestHandler &h, at::Result &result)
    bool Request::isValid() const noexcept
    {
        h.handle(*this, result);
        return true;
    }
}; // namespace call_request
}; // namespace cellular

A module-services/service-cellular/requests/SupplementaryServicesRequest.cpp => module-services/service-cellular/requests/SupplementaryServicesRequest.cpp +80 -0
@@ 0,0 1,80 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <string>
#include <memory>

#include <at/Commands.hpp>

#include "service-cellular/requests/SupplementaryServicesRequest.hpp"
#include "service-cellular/requests/CallForwardingRequest.hpp"
#include "service-cellular/requests/PasswordRegistrationRequest.hpp"

namespace
{
    const std::map<std::string, cellular::SupplementaryServicesRequest::ProcedureType> procedureTypeMap = {
        {"*", cellular::SupplementaryServicesRequest::ProcedureType::Activation},
        {"#", cellular::SupplementaryServicesRequest::ProcedureType::Deactivation},
        {"*#", cellular::SupplementaryServicesRequest::ProcedureType::Interrogation},
        {"**", cellular::SupplementaryServicesRequest::ProcedureType::Registration},
        {"##", cellular::SupplementaryServicesRequest::ProcedureType::Erasure},
    };
}

namespace cellular
{
    SupplementaryServicesRequest::SupplementaryServicesRequest(const std::string &data, GroupMatch matchGroups)
        : Request(data),
          serviceCode(matchGroups[magic_enum::enum_integer(SupplementaryServicesRegexGroups::ServiceCode)]),
          supplementaryInfoA(matchGroups[magic_enum::enum_integer(SupplementaryServicesRegexGroups::SupInfoA)]),
          supplementaryInfoB(matchGroups[magic_enum::enum_integer(SupplementaryServicesRegexGroups::SupInfoB)]),
          supplementaryInfoC(matchGroups[magic_enum::enum_integer(SupplementaryServicesRegexGroups::SupInfoC)])
    {
        static_assert(magic_enum::enum_count<SupplementaryServicesRegexGroups>() <= maxGroupsInRequest);

        auto &procedureTypeString =
            matchGroups[magic_enum::enum_integer(SupplementaryServicesRegexGroups::ProcedureType)];

        auto pType = procedureTypeMap.find(procedureTypeString);
        if (pType == procedureTypeMap.end()) {

            throw std::runtime_error(std::string(__FUNCTION__) + ": procedure type not covered by constructor.");
        }
        procedureType = pType->second;
    }

    auto SupplementaryServicesRequest::create(const std::string &data, GroupMatch matchGroups)
        -> std::unique_ptr<SupplementaryServicesRequest>
    {
        std::string serviceCode = matchGroups[magic_enum::enum_integer(SupplementaryServicesRegexGroups::ServiceCode)];

        auto factoryList = {
            CallForwardingRequest::create,
        };

        for (auto &createCb : factoryList) {
            if (auto req = createCb(serviceCode, data, matchGroups)) {
                return req;
            }
        }

        return nullptr;
    }

    auto SupplementaryServicesRequest::getCommandInformationClass(const std::string &basicServiceGroup) const
        -> std::string
    {
        // according to EC25&EC21_AT_Commands_Manual_V1.3
        if (basicServiceGroup == basicServiceVoice) {
            return atInformationClassVoice;
        }
        else if (basicServiceGroup == basicServiceVoiceFax) {
            return atInformationClassFax;
        }
        else if (basicServiceGroup == basicServiceVoiceData) {
            return atInformationClassData;
        }
        return atInformationClassAllButSms;
    }

}; // namespace cellular

A module-services/service-cellular/requests/UssdRequest.cpp => module-services/service-cellular/requests/UssdRequest.cpp +28 -0
@@ 0,0 1,28 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <string>
#include <memory>

#include <at/Result.hpp>
#include <at/Commands.hpp>

#include "service-cellular/requests/UssdRequest.hpp"

namespace cellular
{
    std::string UssdRequest::command()
    {
        return std::string(at::factory(at::AT::CUSD_SEND) + request + ",15");
    }

    std::unique_ptr<UssdRequest> UssdRequest::create(const std::string &data, GroupMatch)
    {
        return std::make_unique<UssdRequest>(data);
    }

    void UssdRequest::handle(RequestHandler &h, at::Result &result)
    {
        h.handle(*this, result);
    }
}; // namespace cellular

D module-services/service-cellular/service-cellular/CallRequest.hpp => module-services/service-cellular/service-cellular/CallRequest.hpp +0 -66
@@ 1,66 0,0 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <at/Result.hpp>
#include "CallRequestHandler.hpp"

namespace call_request
{
    class IRequest
    {
      public:
        virtual std::string command()                                  = 0;
        virtual void handle(CallRequestHandler &h, at::Result &result) = 0;
        virtual void setHandled(bool handled)                          = 0;
        virtual bool isHandled() const noexcept                        = 0;
        virtual bool checkModemResponse(const at::Result &result)      = 0;
        virtual ~IRequest(){};
    };

    class Request : public IRequest
    {
      public:
        Request(const std::string &data);
        void setHandled(bool handled) final;
        bool isHandled() const noexcept final;
        bool checkModemResponse(const at::Result &result);

      protected:
        bool isRequestHandled = false;
        std::string request;
    };

    class IMEIRequest : public Request
    {
      public:
        IMEIRequest(const std::string &data) : Request(data){};
        std::string command() final;
        static std::unique_ptr<IMEIRequest> create(const std::string &data);
        void handle(CallRequestHandler &h, at::Result &result) final;
    };

    class USSDRequest : public Request
    {
      public:
        USSDRequest(const std::string &data) : Request(data){};
        std::string command() final;

        static std::unique_ptr<USSDRequest> create(const std::string &data);
        void handle(CallRequestHandler &h, at::Result &result) final;
    };

    class CallRequest : public Request
    {
      public:
        CallRequest(const std::string &data) : Request(data){};
        std::string command() final;
        std::string getNumber() const
        {
            return request;
        }
        static std::unique_ptr<CallRequest> create(const std::string &data);
        void handle(CallRequestHandler &h, at::Result &result) final;
    };
}; // namespace call_request

D module-services/service-cellular/service-cellular/CallRequestHandler.hpp => module-services/service-cellular/service-cellular/CallRequestHandler.hpp +0 -21
@@ 1,21 0,0 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <at/Result.hpp>

namespace call_request
{
    class IMEIRequest;
    class USSDRequest;
    class CallRequest;

    class CallRequestHandler
    {
      public:
        virtual void handle(IMEIRequest &request, at::Result &result) = 0;
        virtual void handle(USSDRequest &request, at::Result &result) = 0;
        virtual void handle(CallRequest &request, at::Result &result) = 0;
    };
} // namespace call_request

R module-services/service-cellular/service-cellular/CellularCallRequestHandler.hpp => module-services/service-cellular/service-cellular/CellularRequestHandler.hpp +9 -6
@@ 3,20 3,23 @@

#pragma once

#include "CallRequestHandler.hpp"
#include "RequestHandler.hpp"
#include "service-cellular/ServiceCellular.hpp"

using namespace call_request;
using namespace cellular;

class CellularCallRequestHandler : public CallRequestHandler
class CellularRequestHandler : public RequestHandler
{
  public:
    CellularCallRequestHandler(ServiceCellular &serviceCellular) : cellular(serviceCellular)
    CellularRequestHandler(ServiceCellular &serviceCellular) : cellular(serviceCellular)
    {}

    void handle(IMEIRequest &request, at::Result &result) final;
    void handle(USSDRequest &request, at::Result &result) final;
    void handle(ImeiRequest &request, at::Result &result) final;
    void handle(UssdRequest &request, at::Result &result) final;
    void handle(CallRequest &request, at::Result &result) final;
    void handle(PasswordRegistrationRequest &request, at::Result &result) final;
    void handle(SupplementaryServicesRequest &request, at::Result &result) final;
    void handle(PinChangeRequest &request, at::Result &result) final;

  private:
    ServiceCellular &cellular;

R module-services/service-cellular/service-cellular/CallRequestFactory.hpp => module-services/service-cellular/service-cellular/RequestFactory.hpp +7 -7
@@ 8,21 8,21 @@
#include <map>
#include <functional>

#include "CallRequest.hpp"
#include "requests/CallRequest.hpp"

namespace call_request
namespace cellular
{
    using CreateCallback = std::function<std::unique_ptr<IRequest>(const std::string &)>;
    using CreateCallback = std::function<std::unique_ptr<IRequest>(const std::string &, GroupMatch)>;

    class Factory
    class RequestFactory
    {
      public:
        Factory(const std::string &data);
        RequestFactory(const std::string &data);
        std::unique_ptr<IRequest> create();

      private:
        std::string request;
        std::map<std::string, CreateCallback> requestMap;
        std::vector<std::pair<std::string, CreateCallback>> requestMap;
        void registerRequest(std::string regex, CreateCallback);
    };
} // namespace call_request
} // namespace cellular

A module-services/service-cellular/service-cellular/RequestHandler.hpp => module-services/service-cellular/service-cellular/RequestHandler.hpp +27 -0
@@ 0,0 1,27 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <at/Result.hpp>

namespace cellular
{
    class ImeiRequest;
    class UssdRequest;
    class CallRequest;
    class SupplementaryServicesRequest;
    class PasswordRegistrationRequest;
    class PinChangeRequest;

    class RequestHandler
    {
      public:
        virtual void handle(ImeiRequest &request, at::Result &result)                  = 0;
        virtual void handle(UssdRequest &request, at::Result &result)                  = 0;
        virtual void handle(CallRequest &request, at::Result &result)                  = 0;
        virtual void handle(PasswordRegistrationRequest &request, at::Result &result)  = 0;
        virtual void handle(PinChangeRequest &request, at::Result &result)             = 0;
        virtual void handle(SupplementaryServicesRequest &request, at::Result &result) = 0;
    };
} // namespace cellular

M module-services/service-cellular/service-cellular/ServiceCellular.hpp => module-services/service-cellular/service-cellular/ServiceCellular.hpp +1 -1
@@ 253,6 253,6 @@ class ServiceCellular : public sys::Service
    bool handleSimState(at::SimState state, const std::string message);

    friend class CellularUrcHandler;
    friend class CellularCallRequestHandler;
    friend class SimCard;
    friend class CellularRequestHandler;
};

A module-services/service-cellular/service-cellular/requests/CallForwardingRequest.hpp => module-services/service-cellular/service-cellular/requests/CallForwardingRequest.hpp +44 -0
@@ 0,0 1,44 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "SupplementaryServicesRequest.hpp"

namespace cellular
{
    class CallForwardingRequest : public SupplementaryServicesRequest
    {
      public:
        CallForwardingRequest(const std::string &forwardReason, const std::string &data, GroupMatch matchGroups)
            : SupplementaryServicesRequest(data, matchGroups), forwardReason(forwardReason)
        {}

        static std::unique_ptr<SupplementaryServicesRequest> create(const std::string &serviceCode,
                                                                    const std::string &data,
                                                                    GroupMatch matchGroups);

        auto command() -> std::string final;
        void handle(RequestHandler &h, at::Result &result) final;

      private:
        static constexpr auto addressFormatTypeInternational = "145";
        static constexpr auto addressFormatTypeDefault       = "129";
        static constexpr auto subaddrDefault                 = "128";

        std::string forwardReason;
        std::string &phoneNumber           = supplementaryInfoA;
        std::string &basicServiceGroup     = supplementaryInfoB;
        std::string &noReplyConditionTimer = supplementaryInfoB;

        // command decomposition according to EC25&EC21_AT_Commands_Manual_V1.3
        auto getCommandReason() const -> std::string;
        auto getCommandMode() const -> std::string;
        auto getCommandNumber() const -> std::string;
        auto getCommandType() const -> std::string;
        auto getCommandClass() const -> std::string;
        auto getCommandSubAddr() const -> std::string;
        auto getCommandSatype() const -> std::string;
        auto getCommandTime() const -> std::string;
    };
}; // namespace cellular

A module-services/service-cellular/service-cellular/requests/CallRequest.hpp => module-services/service-cellular/service-cellular/requests/CallRequest.hpp +25 -0
@@ 0,0 1,25 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <at/Result.hpp>

#include "service-cellular/RequestHandler.hpp"
#include "service-cellular/requests/Request.hpp"

namespace cellular
{
    class CallRequest : public Request
    {
      public:
        CallRequest(const std::string &data) : Request(data){};
        std::string command() final;
        std::string getNumber() const
        {
            return request;
        }
        static std::unique_ptr<CallRequest> create(const std::string &data, GroupMatch);
        void handle(RequestHandler &h, at::Result &result) final;
    };
}; // namespace cellular

A module-services/service-cellular/service-cellular/requests/ImeiRequest.hpp => module-services/service-cellular/service-cellular/requests/ImeiRequest.hpp +24 -0
@@ 0,0 1,24 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <at/Result.hpp>

#include "service-cellular/RequestHandler.hpp"
#include "service-cellular/requests/Request.hpp"

namespace cellular
{
    constexpr inline auto ImeiRegex = "(\\*#06#)";

    class ImeiRequest : public Request
    {
      public:
        ImeiRequest(const std::string &data) : Request(data){};
        std::string command() final;

        static std::unique_ptr<ImeiRequest> create(const std::string &data, GroupMatch);
        void handle(RequestHandler &h, at::Result &result) final;
    };
} // namespace cellular

A module-services/service-cellular/service-cellular/requests/PasswordRegistrationRequest.hpp => module-services/service-cellular/service-cellular/requests/PasswordRegistrationRequest.hpp +57 -0
@@ 0,0 1,57 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "SupplementaryServicesRequest.hpp"

namespace cellular
{
    constexpr inline auto PasswordRegistrationRegex =
        R"(^(\*\*|\*)(03)(?:\*(30|31|330|331|33|332|333|35|351|353|))(?:\*([0-9]*))(?:\*([0-9]*))(?:\*([0-9]*))\#$)";

    /**
     * (according to 3GPP TS 22.030 V16.0.0):
     */
    enum class PasswordRegistrationRegexGroups
    {
        ProcedureType, //! (\*\*|\*)
        //!    * (activation/registration),
        //!    ** (registration),
        ServiceCode,        //! (03)
        BarringServiceCode, //! (?:\*(30|31|330|331|33|332|333|35|351|353|))
        OldPassword,        //! (?:\*([0-9]*)
        NewPassword,        //! (?:\*([0-9]*)
        NewPasswordRepeat   //! (?:\*([0-9]*)
    };

    class PasswordRegistrationRequest : public Request
    {
      public:
        PasswordRegistrationRequest(const std::string &data, GroupMatch matchGroups)
            : Request(data),
              requestBarringService(
                  matchGroups[magic_enum::enum_integer(PasswordRegistrationRegexGroups::BarringServiceCode)]),
              requestOldPassword(matchGroups[magic_enum::enum_integer(PasswordRegistrationRegexGroups::OldPassword)]),
              requestNewPassword(matchGroups[magic_enum::enum_integer(PasswordRegistrationRegexGroups::NewPassword)]),
              requestNewPasswordRepeat(
                  matchGroups[magic_enum::enum_integer(PasswordRegistrationRegexGroups::NewPasswordRepeat)])
        {}

        static std::unique_ptr<PasswordRegistrationRequest> create(const std::string &data, GroupMatch matchGroups);

        auto command() -> std::string final;
        void handle(RequestHandler &h, at::Result &result) final;
        auto isValid() const noexcept -> bool final;

        auto getCommandFacility() const noexcept -> std::string;
        auto getOldPassword() const noexcept -> std::string;
        auto getNewPassword() const noexcept -> std::string;

      private:
        std::string requestBarringService;
        std::string requestOldPassword;
        std::string requestNewPassword;
        std::string requestNewPasswordRepeat;
    };
}; // namespace cellular

A module-services/service-cellular/service-cellular/requests/PinChangeRequest.hpp => module-services/service-cellular/service-cellular/requests/PinChangeRequest.hpp +50 -0
@@ 0,0 1,50 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "SupplementaryServicesRequest.hpp"

namespace cellular
{
    constexpr inline auto PinChangeRegex = R"(^(\*\*)(04|042|05)(?:\*([0-9]*))(?:\*([0-9]*))(?:\*([0-9]*))\#$)";

    // (according to 3GPP TS 22.030 V16.0.0):
    enum class PinChangeRegexGroups
    {
        ProcedureType, //! (\*\*)
        //!    ** (registration),
        ServiceCode,      //! (04|042|05)
        OldPassword,      //! (?:\*([0-9]*))
        NewPassword,      //! (?:\*([0-9]*))
        NewPasswordRepeat //! (?:\*([0-9]*))
    };

    class PinChangeRequest : public Request
    {
      public:
        enum class PassChangeType
        {
            ChangePin,
            ChangePin2,
            ChangePinByPuk,
        };

        PinChangeRequest(const std::string &data, GroupMatch matchGroups);

        static std::unique_ptr<PinChangeRequest> create(const std::string &data, GroupMatch matchGroups);

        auto command() -> std::string final;
        void handle(RequestHandler &h, at::Result &result) final;
        auto isValid() const noexcept -> bool final;

        auto getOldPinOrPuk() const noexcept -> std::string;
        auto getNewPin() const noexcept -> std::string;

      private:
        PassChangeType passChangeType;
        std::string requestOldPinOrPuk;
        std::string requestNewPin;
        std::string requestNewPinRepeat;
    };
}; // namespace cellular

A module-services/service-cellular/service-cellular/requests/Request.hpp => module-services/service-cellular/service-cellular/requests/Request.hpp +40 -0
@@ 0,0 1,40 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <at/Result.hpp>
#include "service-cellular/RequestHandler.hpp"

namespace cellular
{
    constexpr inline auto maxGroupsInRequest = 6;
    using GroupMatch                         = std::array<std::reference_wrapper<std::string>, maxGroupsInRequest>;

    class IRequest
    {
      public:
        virtual std::string command()                              = 0;
        virtual void handle(RequestHandler &h, at::Result &result) = 0;
        virtual void setHandled(bool handled)                      = 0;
        virtual bool isHandled() const noexcept                    = 0;
        virtual bool checkModemResponse(const at::Result &result)  = 0;
        virtual bool isValid() const noexcept                      = 0;
        virtual ~IRequest(){};
    };

    class Request : public IRequest
    {
      public:
        Request(const std::string &data) : request(data){};

        void setHandled(bool handled) final;
        bool isHandled() const noexcept final;
        bool checkModemResponse(const at::Result &result) final;
        bool isValid() const noexcept override;

      protected:
        bool isRequestHandled = false;
        std::string request;
    };
}; // namespace cellular

A module-services/service-cellular/service-cellular/requests/SupplementaryServicesRequest.hpp => module-services/service-cellular/service-cellular/requests/SupplementaryServicesRequest.hpp +65 -0
@@ 0,0 1,65 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <at/Result.hpp>
#include "service-cellular/requests/CallRequest.hpp"

namespace cellular
{
    constexpr inline auto SupplementaryServicesRegex =
        R"(^(\*|\*\*|\*\#|\#|\#\#)(\d{2,3})(?:\*([^\*\#]*))?(?:\*([^\*\#]*))?(?:\*([^\*\#]*))?\#)";

    /**
     * (according to 3GPP TS 22.030 V16.0.0):
     */
    enum class SupplementaryServicesRegexGroups
    {
        ProcedureType, //! (\*|\*\*|\*\#|\#|\#\#)
        //!    * (activation/registration),
        //!    # (deactivation),
        //!    *# (interrogation),
        //!    ** (registration),
        //!    ## (erasure)
        ServiceCode, //! (\d{2,3})
        SupInfoA,    //! (?:\*([^\*\#]+))  - Supplementary Information A (optional)
        SupInfoB,    //! (?:\*([^\*\#]+))  - Supplementary Information B (optional)
        SupInfoC     //! (?:\*([^\*\#]+))  - Supplementary Information C (optional)
    };

    class SupplementaryServicesRequest : public Request
    {
      public:
        static std::unique_ptr<SupplementaryServicesRequest> create(const std::string &data, GroupMatch matchGroups);
        SupplementaryServicesRequest(const std::string &data, GroupMatch matchGroups);

        enum class ProcedureType
        {
            Deactivation,
            Activation,
            Interrogation,
            Registration,
            Erasure
        };

      protected:
        std::string request;
        std::string serviceCode;
        std::string supplementaryInfoA;
        std::string supplementaryInfoB;
        std::string supplementaryInfoC;
        ProcedureType procedureType;

        static constexpr inline auto atInformationClassVoice     = "1";
        static constexpr inline auto atInformationClassData      = "2";
        static constexpr inline auto atInformationClassFax       = "4";
        static constexpr inline auto atInformationClassAllButSms = "7";

        static constexpr inline auto basicServiceVoice     = "11";
        static constexpr inline auto basicServiceVoiceData = "25";
        static constexpr inline auto basicServiceVoiceFax  = "13";

        auto getCommandInformationClass(const std::string &basicServiceGroup) const -> std::string;
    };
}; // namespace cellular

A module-services/service-cellular/service-cellular/requests/UssdRequest.hpp => module-services/service-cellular/service-cellular/requests/UssdRequest.hpp +24 -0
@@ 0,0 1,24 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <at/Result.hpp>

#include "service-cellular/RequestHandler.hpp"
#include "service-cellular/requests/Request.hpp"

namespace cellular
{
    constexpr inline auto UssdRegex = "^[\\*].*[\\#]$";

    class UssdRequest : public Request
    {
      public:
        UssdRequest(const std::string &data) : Request(data){};
        std::string command() final;

        static std::unique_ptr<UssdRequest> create(const std::string &data, GroupMatch);
        void handle(RequestHandler &h, at::Result &result) final;
    };
} // namespace cellular

A module-services/service-cellular/tests/CMakeLists.txt => module-services/service-cellular/tests/CMakeLists.txt +11 -0
@@ 0,0 1,11 @@
cmake_minimum_required(VERSION 3.12)

add_catch2_executable(
        NAME
            cellular-mmi
        SRCS
            unittest_mmi.cpp
        LIBS
            module-cellular
)


A module-services/service-cellular/tests/unittest_mmi.cpp => module-services/service-cellular/tests/unittest_mmi.cpp +141 -0
@@ 0,0 1,141 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#define CATCH_CONFIG_MAIN

#include <catch2/catch.hpp>
#include <service-cellular/RequestFactory.hpp>
#include <service-cellular/requests/CallForwardingRequest.hpp>
#include <service-cellular/requests/PasswordRegistrationRequest.hpp>
#include <service-cellular/requests/PinChangeRequest.hpp>
#include <service-cellular/requests/ImeiRequest.hpp>
#include <service-cellular/requests/UssdRequest.hpp>

using namespace cellular;

TEST_CASE("MMI requests")
{
    struct TestCase
    {
        const std::string requestString;
        const std::string expectedCommandString;
        const std::type_info &expectedType;
        const bool expectedValid = true;
    };

    std::vector<TestCase> testCases = {
        /// IMEIRequest
        {R"(*#06#)", R"(AT+GSN)", typeid(ImeiRequest)},

        /// CallForwardingRequest
        // all diversions
        {R"(##002#)", R"(AT+CCFC=4,4)", typeid(CallForwardingRequest)},
        // all conditional redirections
        {R"(**004*666555444#)", R"(AT+CCFC=5,3,"666555444")", typeid(CallForwardingRequest)},
        {R"(##004#)", R"(AT+CCFC=5,4)", typeid(CallForwardingRequest)},
        // unconditional divert
        {R"(**21*666555444#)", R"(AT+CCFC=0,3,"666555444")", typeid(CallForwardingRequest)},
        {R"(*21#)", R"(AT+CCFC=0,1)", typeid(CallForwardingRequest)},
        {R"(#21#)", R"(AT+CCFC=0,0)", typeid(CallForwardingRequest)},
        {R"(##21#)", R"(AT+CCFC=0,4)", typeid(CallForwardingRequest)},
        {R"(*#21#)", R"(AT+CCFC=0,2)", typeid(CallForwardingRequest)},
        // divert when not answered
        {R"(**61*666555444#)", R"(AT+CCFC=2,3,"666555444")", typeid(CallForwardingRequest)},
        {R"(*61#)", R"(AT+CCFC=2,1)", typeid(CallForwardingRequest)},
        {R"(#61#)", R"(AT+CCFC=2,0)", typeid(CallForwardingRequest)},
        {R"(##61#)", R"(AT+CCFC=2,4)", typeid(CallForwardingRequest)},
        {R"(*#61#)", R"(AT+CCFC=2,2)", typeid(CallForwardingRequest)},
        // divert when off or not
        {R"(**62*666555444#)", R"(AT+CCFC=3,3,"666555444")", typeid(CallForwardingRequest)},
        {R"(*62#)", R"(AT+CCFC=3,1)", typeid(CallForwardingRequest)},
        {R"(#62#)", R"(AT+CCFC=3,0)", typeid(CallForwardingRequest)},
        {R"(##62#)", R"(AT+CCFC=3,4)", typeid(CallForwardingRequest)},
        {R"(*#62#)", R"(AT+CCFC=3,2)", typeid(CallForwardingRequest)},
        // divert when busy or pressing reject
        {R"(**67*666555444#)", R"(AT+CCFC=1,3,"666555444")", typeid(CallForwardingRequest)},
        {R"(*67#)", R"(AT+CCFC=1,1)", typeid(CallForwardingRequest)},
        {R"(#67#)", R"(AT+CCFC=1,0)", typeid(CallForwardingRequest)},
        {R"(##67#)", R"(AT+CCFC=1,4)", typeid(CallForwardingRequest)},
        {R"(*#67#)", R"(AT+CCFC=1,2)", typeid(CallForwardingRequest)},
        // alternative register and on
        {R"(*21*666555444#)", R"(AT+CCFC=0,1,"666555444")", typeid(CallForwardingRequest)},

        /// PasswordRegistrationRequest
        // total incoming and outgoing service barring (empty string)
        {R"(**03**23*111*111#)", R"(AT+CPWD="AB","23","111")", typeid(PasswordRegistrationRequest)},
        // outgoing call barring
        {R"(**03*33*1234*4321*4321#)", R"(AT+CPWD="AO","1234","4321")", typeid(PasswordRegistrationRequest)},
        // outgoing international call barring
        {R"(**03*331*1234*4321*4321#)", R"(AT+CPWD="OI","1234","4321")", typeid(PasswordRegistrationRequest)},
        // outgoing international call barring, excluding to home
        {R"(**03*332*1234*4321*4321#)", R"(AT+CPWD="OX","1234","4321")", typeid(PasswordRegistrationRequest)},
        // incoming call barring
        {R"(**03*35*1234*4321*4321#)", R"(AT+CPWD="AI","1234","4321")", typeid(PasswordRegistrationRequest)},
        // incoming call barring, when international roaming
        {R"(**03*351*1234*4321*4321#)", R"(AT+CPWD="IR","1234","4321")", typeid(PasswordRegistrationRequest)},
        // total incoming and outgoing service barring
        {R"(**03*330*1234*4321*4321#)", R"(AT+CPWD="AB","1234","4321")", typeid(PasswordRegistrationRequest)},
        // total outgoing service barring
        {R"(**03*333*1234*4321*4321#)", R"(AT+CPWD="AG","1234","4321")", typeid(PasswordRegistrationRequest)},
        // total incoming service barring
        {R"(**03*353*1234*4321*4321#)", R"(AT+CPWD="AC","1234","4321")", typeid(PasswordRegistrationRequest)},
        // alternative procedure type *
        {R"(*03*353*1234*4321*4321#)", R"(AT+CPWD="AC","1234","4321")", typeid(PasswordRegistrationRequest)},
        // bad procedure type * fallback to UUSD
        {R"(*#03*353*1234*4321*4321#)", R"(AT+CUSD=1,*#03*353*1234*4321*4321#,15)", typeid(UssdRequest)},
        // bad procedure type ##
        {R"(##03*353*1234*4321*4321#)", std::string(), typeid(CallRequest)},
        // bad procedure type #
        {R"(#03*353*1234*4321*4321#)", std::string(), typeid(CallRequest)},
        // no password
        {R"(**03*353***#)", std::string(), typeid(PasswordRegistrationRequest), false},
        // no password
        {R"(**03*353*24**#)", std::string(), typeid(PasswordRegistrationRequest), false},
        // no password
        {R"(**03*353**34*#)", std::string(), typeid(PasswordRegistrationRequest), false},
        // no password
        {R"(**03*353***34#)", std::string(), typeid(PasswordRegistrationRequest), false},
        // password does not match
        {R"(**03*353*56*46*74#)", std::string(), typeid(PasswordRegistrationRequest), false},

        /// PinChangeRequest
        // Change PIN
        {R"(**04*0000*1111*1111#)", R"(AT+CPWD="SC","0000","1111")", typeid(PinChangeRequest)},
        // Change PIN2
        {R"(**042*0000*1111*1111#)", R"(AT+CPWD="P2","0000","1111")", typeid(PinChangeRequest)},
        // Change PIN by PUK
        {R"(**05*0000*1111*1111#)", R"(AT+CPIN="0000","1111")", typeid(PinChangeRequest)},
        // Change PIN by PUK more than 4
        {R"(**042*00002*11113*11113#)", R"(AT+CPWD="P2","00002","11113")", typeid(PinChangeRequest)},
        // bad procedure type *
        {R"(*042*00002*11112*11112#)", std::string(), typeid(CallRequest)},
        // bad procedure type ##
        {R"(##042*00002*11112*11112#)", std::string(), typeid(CallRequest)},
        // bad procedure type #
        {R"(#042*00002*11112*11112#)", std::string(), typeid(CallRequest)},
        // bad procedure type *#
        {R"(*#042*00002*11112*11112#)", std::string(), typeid(CallRequest)},
        // no password
        {R"(**042*00002**#)", std::string(), typeid(PinChangeRequest), false},
        // no password
        {R"(**042***11112#)", std::string(), typeid(PinChangeRequest), false},
        // no password
        {R"(**042**11112*#)", std::string(), typeid(PinChangeRequest), false},
        // no password
        {R"(**042**11112*#)", std::string(), typeid(PinChangeRequest), false},
        //  password does not match
        {R"(**042*0000*1111*2222#)", std::string(), typeid(PinChangeRequest), false},
    };

    for (auto &testCase : testCases) {
        RequestFactory requestFactory(testCase.requestString);
        auto request        = requestFactory.create();
        auto requestCommand = request->command();
        INFO("Failed on testcase: " << testCase.requestString);
        REQUIRE(typeid(*request.get()).name() == testCase.expectedType.name());
        REQUIRE(request->isValid() == testCase.expectedValid);
        if (typeid(*request.get()).name() != typeid(CallRequest).name() && request->isValid()) {
            REQUIRE(requestCommand == testCase.expectedCommandString);
        }
    }
}