~aleteoryx/muditaos

2443cb6ab156ff1a86b8d83fb4eff7acaedef1b0 — Kuba 4 years ago fb279f1
[EGD-7614] Add Enable Rndis mode

Adds enabling Rndis mode in modem, so tethering is possible
on Linux, Mac and Windows.
M module-cellular/CMakeLists.txt => module-cellular/CMakeLists.txt +1 -0
@@ 39,6 39,7 @@ set(SOURCES
        ${CMAKE_CURRENT_SOURCE_DIR}/at/cmd/src/CPBR.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/cmd/src/QNWINFO.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/cmd/src/QSIMSTAT.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/at/cmd/src/QCFGUsbnet.cpp
        )

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

M module-cellular/at/Commands.hpp => module-cellular/at/Commands.hpp +1 -0
@@ 134,6 134,7 @@ namespace at
        SMS_URC_ON,
        ACT_URC_OFF,
        ACT_URC_ON,
        SET_RNDIS,
    };

    enum class commadsSet

A module-cellular/at/cmd/QCFGUsbnet.hpp => module-cellular/at/cmd/QCFGUsbnet.hpp +47 -0
@@ 0,0 1,47 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "Result.hpp"
#include <at/Cmd.hpp>
#include <functional>
#include <SimInsertedState.hpp>

namespace at
{
    namespace qcfg::usbnet
    {
        constexpr auto tokensCount = 1;
        enum class Net
        {
            RMNET = 0,
            ECM,
            MBIM,
            RNDIS
        };
    } // namespace qcfg::usbnet

    namespace result
    {
        struct QCFGUsbnet : public Result
        {
            qcfg::usbnet::Net net;
            explicit QCFGUsbnet(const Result &that);
        };
    } // namespace result

    namespace cmd
    {
        class QCFGUsbnet : public Cmd
        {
          public:
            QCFGUsbnet() noexcept;
            explicit QCFGUsbnet(at::cmd::Modifier mod) noexcept;

            [[nodiscard]] auto parseQCFGUsbnet(const Result &base_result) -> result::QCFGUsbnet;
            void set(qcfg::usbnet::Net net);
        };
    } // namespace cmd

} // namespace at

A module-cellular/at/cmd/src/QCFGUsbnet.cpp => module-cellular/at/cmd/src/QCFGUsbnet.cpp +74 -0
@@ 0,0 1,74 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <at/cmd/QCFGUsbnet.hpp>

#include <log/log.hpp>
#include <memory>
#include <string>

namespace at
{
    namespace cmd
    {
        QCFGUsbnet::QCFGUsbnet(at::cmd::Modifier mod) noexcept : Cmd("AT+QCFG=\"usbnet\"", mod, at::default_timeout)
        {}

        QCFGUsbnet::QCFGUsbnet() noexcept : QCFGUsbnet(at::cmd::Modifier::None)
        {}

        auto QCFGUsbnet::parseQCFGUsbnet(const Result &base_result) -> result::QCFGUsbnet
        {

            auto constexpr responseHeader = "+QCFG: \"usbnet\",";

            result::QCFGUsbnet parsed{base_result};

            if (parsed) {
                if (parsed.response.empty()) {
                    LOG_ERROR("Can't parse - empty response");
                    parsed.code = result::QCFGUsbnet::Code::PARSING_ERROR;
                    return parsed;
                }

                std::string str = parsed.response[0];
                if (str.find(responseHeader) == std::string::npos) {
                    LOG_ERROR("Can't parse - bad header");
                    parsed.code = result::QCFGUsbnet::Code::PARSING_ERROR;
                    return parsed;
                }

                utils::findAndReplaceAll(str, responseHeader, "");
                utils::trim(str);
                std::vector<std::string> tokens = utils::split(str, ',');
                if (tokens.size() != qcfg::usbnet::tokensCount) {
                    LOG_ERROR("Can't parse - invalid tokens count");
                    parsed.code = result::QCFGUsbnet::Code::PARSING_ERROR;
                    return parsed;
                }

                auto net = 0;
                if (utils::toNumeric(tokens[0], net) && magic_enum::enum_contains<qcfg::usbnet::Net>(net)) {
                    parsed.net = static_cast<qcfg::usbnet::Net>(net);
                }
                else {
                    LOG_ERROR("Can't parse - bad value");
                    parsed.code = result::QCFGUsbnet::Code::PARSING_ERROR;
                    return parsed;
                }
            }
            return parsed;
        }

        void QCFGUsbnet::set(qcfg::usbnet::Net net)
        {
            body += utils::to_string(static_cast<int>(net));
        }
    } // namespace cmd
    namespace result
    {
        QCFGUsbnet::QCFGUsbnet(const Result &that) : Result(that)
        {}

    } // namespace result
} // namespace at

M module-cellular/at/src/ATFactory.cpp => module-cellular/at/src/ATFactory.cpp +1 -0
@@ 119,6 119,7 @@ namespace at
        {AT::ACT_URC_ON, {"AT+QINDCFG=\"act\",1"}},
        {AT::SMS_URC_ON, {"AT+QINDCFG=\"smsincoming\",1"}},
        {AT::SMS_URC_OFF, {"AT+QINDCFG=\"smsincoming\",0"}},
        {AT::SET_RNDIS, {"AT+QCFG=\"usbnet\",3"}},

    };


M module-cellular/test/mock/AtCommon_channel.hpp => module-cellular/test/mock/AtCommon_channel.hpp +57 -0
@@ 509,4 509,61 @@ namespace at
            return result;
        }
    };

    /// provides invalid QSIMSTAT response
    class QCGFGUsbnet_toLittleTokens : public ChannelMock
    {
      public:
        auto ResultMock() -> Result final
        {
            auto result     = Result();
            result.code     = Result::Code::OK;
            result.response = {"+QCFG: \"usbnet\",", "OK"};
            return result;
        }
    };

    /// provides proper QCFGUsbnet response
    class QCGFGUsbnet_successChannel : public ChannelMock
    {
      public:
        auto ResultMock() -> Result final
        {
            auto result     = Result();
            result.code     = Result::Code::OK;
            result.response = {"+QCFG: \"usbnet\",1", "OK"};
            return result;
        }
    };

    /// provides invalid QCFGUsbnet response
    class QCFGUsbnet_toManyTokens : public ChannelMock
    {
      public:
        const std::string net = "1";
        const std::string bad = "1";

        auto ResultMock() -> Result final
        {
            auto result     = Result();
            result.code     = Result::Code::OK;
            result.response = {"+QCFG: \"usbnet\"," + net + "," + bad, "OK"};
            return result;
        }
    };

    /// provides invalid QCFGUsbnet response
    class QCFGUsbnet_invalidToken : public ChannelMock
    {
      public:
        const std::string net = "9";

        auto ResultMock() -> Result final
        {
            auto result     = Result();
            result.code     = Result::Code::OK;
            result.response = {"+QCFG: \"usbnet\"," + net, "OK"};
            return result;
        }
    };
} // namespace at

M module-cellular/test/unittest_parse_result.cpp => module-cellular/test/unittest_parse_result.cpp +61 -0
@@ 14,6 14,7 @@
#include <at/cmd/CPBR.hpp>
#include <at/cmd/QNWINFO.hpp>
#include <at/cmd/QSIMSTAT.hpp>
#include <at/cmd/QCFGUsbnet.hpp>

#include "mock/AtCommon_channel.hpp"
#include "PhoneNumber.hpp"


@@ 754,3 755,63 @@ TEST_CASE("QSIMSTAT parser")
        REQUIRE(response.code == at::Result::Code::PARSING_ERROR);
    }
}
TEST_CASE("QCFGUsbnet parser")
{
    SECTION("Empty data")
    {
        at::cmd::QCFGUsbnet cmd;
        at::Result result;
        auto response = cmd.parseQCFGUsbnet(result);
        REQUIRE(!response);
    }

    SECTION("Failing channel")
    {
        at::cmd::QCFGUsbnet cmd;
        at::FailingChannel channel;
        auto base     = channel.cmd(cmd);
        auto response = cmd.parseQCFGUsbnet(base);
        REQUIRE(!response);
        REQUIRE(response.code == at::Result::Code::ERROR);
    }

    SECTION("success")
    {
        at::cmd::QCFGUsbnet cmd;
        at::QCGFGUsbnet_successChannel channel;
        auto base     = channel.cmd(cmd);
        auto response = cmd.parseQCFGUsbnet(base);
        REQUIRE(response);
        REQUIRE(response.net == at::qcfg::usbnet::Net::ECM);
    }

    SECTION("too few tokens")
    {
        at::cmd::QCFGUsbnet cmd;
        at::QSIMSTAT_toLittleTokens channel;
        auto base     = channel.cmd(cmd);
        auto response = cmd.parseQCFGUsbnet(base);
        REQUIRE(!response);
        REQUIRE(response.code == at::Result::Code::PARSING_ERROR);
    }

    SECTION("Failed - too many tokens")
    {
        at::cmd::QCFGUsbnet cmd;
        at::QCFGUsbnet_toManyTokens channel;
        auto base     = channel.cmd(cmd);
        auto response = cmd.parseQCFGUsbnet(base);
        REQUIRE(!response);
        REQUIRE(response.code == at::Result::Code::PARSING_ERROR);
    }

    SECTION("Failed - invalid token")
    {
        at::cmd::QCFGUsbnet cmd;
        at::QCFGUsbnet_invalidToken channel;
        auto base     = channel.cmd(cmd);
        auto response = cmd.parseQCFGUsbnet(base);
        REQUIRE(!response);
        REQUIRE(response.code == at::Result::Code::PARSING_ERROR);
    }
}

M module-services/service-cellular/CMakeLists.txt => module-services/service-cellular/CMakeLists.txt +1 -0
@@ 11,6 11,7 @@ set(SOURCES
    src/SMSPartsHandler.cpp
    src/SMSSendHandler.cpp
    src/ImeiGetHandler.cpp
    src/TerhetingHandler.cpp

    CellularCall.cpp
    CellularServiceAPI.cpp

M module-services/service-cellular/ServiceCellular.cpp => module-services/service-cellular/ServiceCellular.cpp +6 -13
@@ 102,6 102,8 @@

#include <ctime>

#include <at/cmd/QCFGUsbnet.hpp>

const char *ServiceCellular::serviceName = cellular::service::name;

inline constexpr auto cellularStack = 8000;


@@ 291,24 293,13 @@ void ServiceCellular::registerMessageHandlers()
    phoneModeObserver->subscribe(
        [this](sys::phone_modes::PhoneMode mode) { connectionManager->onPhoneModeChange(mode); });
    phoneModeObserver->subscribe([&](sys::phone_modes::Tethering tethering) {
        using bsp::cellular::USB::setPassthrough;
        using bsp::cellular::USB::PassthroughState;
        if (tethering == sys::phone_modes::Tethering::On) {
            if (!tetheringTurnOffURC()) {
                LOG_ERROR("Failed to disable URC on tethering enable");
            }
            priv->tetheringHandler->enable();
        }
        else {
            if (!tetheringTurnOnURC()) {
                LOG_ERROR("Failed to enable URC on tethering disable");
            }
            if (!receiveAllMessages()) {
                LOG_ERROR("Failed to receive all sms after tethering disabling");
            }
            priv->tetheringHandler->disable();
            logTetheringCalls();
        }
        setPassthrough(tethering == sys::phone_modes::Tethering::On ? PassthroughState::ENABLED
                                                                    : PassthroughState::DISABLED);
    });

    priv->connectSimCard();


@@ 893,6 884,8 @@ bool ServiceCellular::handle_audio_conf_procedure()
                return false;
            }

            priv->tetheringHandler->configure();

            priv->state->set(State::ST::APNConfProcedure);
            return true;
        }

M module-services/service-cellular/src/ServiceCellularPriv.cpp => module-services/service-cellular/src/ServiceCellularPriv.cpp +44 -1
@@ 15,6 15,7 @@
#include <queries/messages/sms/QuerySMSUpdate.hpp>

#include <at/ATFactory.hpp>
#include <at/cmd/QCFGUsbnet.hpp>
#include <ucs2/UCS2.hpp>
#include <service-appmgr/Constants.hpp>



@@ 28,10 29,12 @@ namespace cellular::internal
    ServiceCellularPriv::ServiceCellularPriv(ServiceCellular *owner)
        : owner{owner}, simCard{std::make_unique<SimCard>()}, state{std::make_unique<State>(owner)},
          networkTime{std::make_unique<NetworkTime>()}, simContacts{std::make_unique<SimContacts>()},
          imeiGetHandler{std::make_unique<service::ImeiGetHandler>()}
          imeiGetHandler{std::make_unique<service::ImeiGetHandler>()}, tetheringHandler{
                                                                           std::make_unique<TetheringHandler>()}
    {
        initSimCard();
        initSMSSendHandler();
        initTetheringHandler();
    }

    void ServiceCellularPriv::initSimCard()


@@ 317,5 320,45 @@ namespace cellular::internal
            return std::make_shared<cellular::GetImeiResponse>();
        });
    }
    void ServiceCellularPriv::initTetheringHandler()
    {
        tetheringHandler->onIsRndisEnabled = [this]() -> std::optional<at::qcfg::usbnet::Net> {
            auto channel = owner->cmux->get(CellularMux::Channel::Commands);
            if (channel == nullptr) {
                return std::nullopt;
            }
            auto command  = at::cmd::QCFGUsbnet(at::cmd::Modifier::None);
            auto response = channel->cmd(command);
            auto result   = command.parseQCFGUsbnet(response);
            if (result) {
                return result.net;
            }
            return std::nullopt;
        };

        tetheringHandler->onEnableRndis = [this]() -> bool {
            auto channel = owner->cmux->get(CellularMux::Channel::Commands);
            if (channel == nullptr) {
                return false;
            }
            auto response = channel->cmd(at::AT::SET_RNDIS);
            if (response) {
                return true;
            }
            return false;
        };

        tetheringHandler->onEnableUrc = [this]() -> bool { return owner->tetheringTurnOnURC(); };

        tetheringHandler->onDisableUrc = [this]() -> bool { return owner->tetheringTurnOffURC(); };

        tetheringHandler->onSetPassthrough = [](bool enable) {
            using bsp::cellular::USB::setPassthrough;
            using bsp::cellular::USB::PassthroughState;
            setPassthrough(enable ? PassthroughState::ENABLED : PassthroughState::DISABLED);
        };

        tetheringHandler->onReadMessages = [this]() -> bool { return owner->receiveAllMessages(); };
    }

} // namespace cellular::internal

M module-services/service-cellular/src/ServiceCellularPriv.hpp => module-services/service-cellular/src/ServiceCellularPriv.hpp +4 -2
@@ 11,6 11,7 @@
#include "NetworkTime.hpp"
#include "SimContacts.hpp"
#include "ImeiGetHandler.hpp"
#include "TetheringHandler.hpp"

namespace cellular::internal
{


@@ 18,7 19,7 @@ namespace cellular::internal
    using service::SimCard;
    using service::SimContacts;
    using service::State;

    using service::TetheringHandler;
    class ServiceCellularPriv
    {
        ServiceCellular *owner;


@@ 29,6 30,7 @@ namespace cellular::internal
        std::unique_ptr<NetworkTime> networkTime;
        std::unique_ptr<SimContacts> simContacts;
        std::unique_ptr<service::ImeiGetHandler> imeiGetHandler;
        std::unique_ptr<TetheringHandler> tetheringHandler;
        State::PowerState nextPowerState = State::PowerState::Off;
        std::uint8_t multiPartSMSUID     = 0;



@@ 47,7 49,7 @@ namespace cellular::internal
      private:
        void initSimCard();
        void initSMSSendHandler();

        void initTetheringHandler();
        /** Send SMS action used by the SMSSendHandler
         * \param record SMS record to send
         */

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

#include "TetheringHandler.hpp"

#include <module-cellular/at/cmd/QCFGUsbnet.hpp>

namespace cellular::service
{

    auto TetheringHandler::isRndisEnabled() -> bool
    {
        auto result = onIsRndisEnabled();
        if (result.has_value()) {
            return result == at::qcfg::usbnet::Net::RNDIS;
        }
        return false;
    }

    auto TetheringHandler::configure() -> bool
    {
        if (isRndisEnabled()) {
            return true;
        }
        onEnableRndis();
        return false;
    }

    auto TetheringHandler::disable() -> bool
    {
        onSetPassthrough(false);
        if (onEnableUrc() && onReadMessages()) {
            return true;
        }
        LOG_FATAL("Tethering disabling failed");
        return false;
    }

    auto TetheringHandler::enable() -> bool
    {
        onSetPassthrough(true);
        if (onDisableUrc()) {
            return true;
        }
        LOG_FATAL("Tethering enabling failed");
        return false;
    }
} // namespace cellular::service

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

#pragma once

#include <memory>
#include <functional>

namespace at
{
    class Cmd;
    class BaseChannel;
} // namespace at

namespace at::qcfg::usbnet
{
    enum class Net;
}
namespace cellular::service
{

    class TetheringHandler
    {
      public:
        /**
         * Reads current Rndis configuration
         * @return true if Rndis is enabled, false if Rndis is disabled
         */
        auto isRndisEnabled() -> bool;
        /**
         * It's called to configure Rndis mode
         * @return
         */
        auto configure() -> bool;
        /**
         * It's enabling tethering mode. Disables all URC and sets modem on passthrough via USB
         * @return true on succes, false on fail
         */
        auto enable() -> bool;
        /**
         * It's disabling tethering mode. Enables all URC, reads all unreaded messages and disables passtrough via USB
         * @return
         */
        auto disable() -> bool;

        /**
         * User callbacks
         */
        std::function<std::optional<at::qcfg::usbnet::Net>()> onIsRndisEnabled;
        std::function<bool()> onEnableRndis;
        std::function<bool()> onDisableUrc;
        std::function<bool()> onEnableUrc;
        std::function<bool()> onReadMessages;
        std::function<void(bool)> onSetPassthrough;
    };
} // namespace cellular::service

M module-services/service-cellular/tests/CMakeLists.txt => module-services/service-cellular/tests/CMakeLists.txt +9 -0
@@ 66,3 66,12 @@ add_gtest_executable(
        module-sys
        module-cellular
)

add_catch2_executable(
        NAME
        cellular-tethering-handler
        SRCS
        unittest_tetheringHandler.cpp
        LIBS
        module-cellular
)

A module-services/service-cellular/tests/unittest_tetheringHandler.cpp => module-services/service-cellular/tests/unittest_tetheringHandler.cpp +107 -0
@@ 0,0 1,107 @@
// Copyright (c) 2017-2021, 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 <module-services/service-cellular/src/TetheringHandler.hpp>
#include <module-cellular/at/cmd/QCFGUsbnet.hpp>

TEST_CASE("SMSSendHandler functionality")
{
    SECTION("RNDIS enabled")
    {
        auto handler             = cellular::service::TetheringHandler();
        handler.onIsRndisEnabled = []() -> at::qcfg::usbnet::Net { return at::qcfg::usbnet::Net::RNDIS; };

        auto result = handler.isRndisEnabled();
        REQUIRE(result == true);
    }

    SECTION("RNDIS disabled")
    {

        auto handler             = cellular::service::TetheringHandler();
        handler.onIsRndisEnabled = []() -> at::qcfg::usbnet::Net { return at::qcfg::usbnet::Net::ECM; };
        auto result              = handler.isRndisEnabled();

        REQUIRE(result == false);
    }

    SECTION("configure RNDIS enabled")
    {
        auto handler                = cellular::service::TetheringHandler();
        auto onIsRndisEnabledCalled = 0;
        handler.onIsRndisEnabled    = [&onIsRndisEnabledCalled]() -> at::qcfg::usbnet::Net {
            onIsRndisEnabledCalled++;
            return at::qcfg::usbnet::Net::RNDIS;
        };
        auto result = handler.configure();
        REQUIRE(result == true);
        REQUIRE(onIsRndisEnabledCalled == 1);
    }

    SECTION("configure RNDIS disabled")
    {
        auto handler                = cellular::service::TetheringHandler();
        auto onIsRndisEnabledCalled = 0;
        auto onEnableRndisCalled    = 0;
        handler.onIsRndisEnabled    = [&onIsRndisEnabledCalled]() -> at::qcfg::usbnet::Net {
            onIsRndisEnabledCalled++;
            return at::qcfg::usbnet::Net::ECM;
        };

        handler.onEnableRndis = [&onEnableRndisCalled]() -> bool {
            onEnableRndisCalled++;
            return true;
        };

        auto result = handler.configure();
        REQUIRE(result == false);
        REQUIRE(onIsRndisEnabledCalled == 1);
        REQUIRE(onEnableRndisCalled == 1);
    }

    SECTION("enable tethering")
    {
        auto handler                = cellular::service::TetheringHandler();
        auto onDisableUrcCalled     = 0;
        auto onSetPassthroughCalled = 0;
        handler.onDisableUrc        = [&onDisableUrcCalled]() -> bool {
            onDisableUrcCalled++;
            return true;
        };

        handler.onSetPassthrough = [&onSetPassthroughCalled](bool) { onSetPassthroughCalled++; };

        auto result = handler.enable();
        REQUIRE(result == true);
        REQUIRE(onDisableUrcCalled == 1);
        REQUIRE(onSetPassthroughCalled == 1);
    }

    SECTION("disable tethering")
    {
        auto handler                 = cellular::service::TetheringHandler();
        auto onEnableUrcCalled       = 0;
        auto onReadAllMessagesCalled = 0;
        auto onSetPassthroughCalled  = 0;
        handler.onEnableUrc          = [&onEnableUrcCalled]() -> bool {
            onEnableUrcCalled++;
            return true;
        };

        handler.onReadMessages = [&onReadAllMessagesCalled]() -> bool {
            onReadAllMessagesCalled++;
            return true;
        };

        handler.onSetPassthrough = [&onSetPassthroughCalled](bool) { onSetPassthroughCalled++; };

        auto result = handler.disable();
        REQUIRE(result == true);
        REQUIRE(onEnableUrcCalled == 1);
        REQUIRE(onSetPassthroughCalled == 1);
    }
}