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