From e312eba2ac4042e0f271933c53828f4889520934 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sat, 13 Apr 2024 14:03:16 +1000 Subject: [PATCH 001/106] Updating next release version to be v1.0.9 --- client/include/cc_mqttsn_client/common.h | 2 +- gateway/include/cc_mqttsn_gateway/version.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/include/cc_mqttsn_client/common.h b/client/include/cc_mqttsn_client/common.h index 5298fe53..73754b75 100644 --- a/client/include/cc_mqttsn_client/common.h +++ b/client/include/cc_mqttsn_client/common.h @@ -40,7 +40,7 @@ extern "C" { #define CC_MQTTSN_CLIENT_MINOR_VERSION 0U /// @brief Patch level of the library -#define CC_MQTTSN_CLIENT_PATCH_VERSION 8U +#define CC_MQTTSN_CLIENT_PATCH_VERSION 9U /// @brief Macro to create numeric version as single unsigned number #define CC_MQTTSN_CLIENT_MAKE_VERSION(major_, minor_, patch_) \ diff --git a/gateway/include/cc_mqttsn_gateway/version.h b/gateway/include/cc_mqttsn_gateway/version.h index a3db3bb3..edd8469c 100644 --- a/gateway/include/cc_mqttsn_gateway/version.h +++ b/gateway/include/cc_mqttsn_gateway/version.h @@ -17,7 +17,7 @@ #define CC_MQTTSN_GW_MINOR_VERSION 0U /// @brief Patch level of the library -#define CC_MQTTSN_GW_PATCH_VERSION 8U +#define CC_MQTTSN_GW_PATCH_VERSION 9U /// @brief Macro to create numeric version as single unsigned number #define CC_MQTTSN_GW_MAKE_VERSION(major_, minor_, patch_) \ From e4f240758a33fdb8fc7b72dc6c5bb3732f35f965 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 1 May 2024 08:24:47 +1000 Subject: [PATCH 002/106] Removed code handling DISCONNECT and PINGREQ from the broker. --- gateway/src/lib/SessionImpl.cpp | 8 +-- gateway/src/lib/SessionImpl.h | 4 +- gateway/src/lib/messages.h | 74 ++++++----------------- gateway/src/lib/session_op/Disconnect.cpp | 15 ----- gateway/src/lib/session_op/Disconnect.h | 1 - gateway/src/lib/session_op/Forward.cpp | 10 --- gateway/src/lib/session_op/Forward.h | 1 - 7 files changed, 24 insertions(+), 89 deletions(-) diff --git a/gateway/src/lib/SessionImpl.cpp b/gateway/src/lib/SessionImpl.cpp index 6ad25596..63359360 100644 --- a/gateway/src/lib/SessionImpl.cpp +++ b/gateway/src/lib/SessionImpl.cpp @@ -155,12 +155,12 @@ void SessionImpl::tick() std::size_t SessionImpl::dataFromClient(const std::uint8_t* buf, std::size_t len) { - return processInputData(buf, len, m_mqttsnStack); + return processInputData(buf, len, m_mqttsnFrame); } std::size_t SessionImpl::dataFromBroker(const std::uint8_t* buf, std::size_t len) { - return processInputData(buf, len, m_mqttStack); + return processInputData(buf, len, m_mqttFrame); } void SessionImpl::setBrokerConnected(bool connected) @@ -245,12 +245,12 @@ void SessionImpl::handle(MqttMessage& msg) void SessionImpl::sendToClient(const MqttsnMessage& msg) { - sendMessage(msg, m_mqttsnStack, m_sendToClientCb, m_mqttsnMsgData); + sendMessage(msg, m_mqttsnFrame, m_sendToClientCb, m_mqttsnMsgData); } void SessionImpl::sendToBroker(const MqttMessage& msg) { - sendMessage(msg, m_mqttStack, m_sendToBrokerCb, m_mqttMsgData); + sendMessage(msg, m_mqttFrame, m_sendToBrokerCb, m_mqttMsgData); } void SessionImpl::startOp(SessionOp& op) diff --git a/gateway/src/lib/SessionImpl.h b/gateway/src/lib/SessionImpl.h index 6b5fc7cc..ed20d5fe 100644 --- a/gateway/src/lib/SessionImpl.h +++ b/gateway/src/lib/SessionImpl.h @@ -200,8 +200,8 @@ class SessionImpl : public MsgHandler ClientConnectedReportCb m_clientConnectedCb; AuthInfoReqCb m_authInfoReqCb; - MqttsnProtStack m_mqttsnStack; - MqttProtStack m_mqttStack; + MqttsnFrame m_mqttsnFrame; + MqttFrame m_mqttFrame; DataBuf m_mqttsnMsgData; DataBuf m_mqttMsgData; diff --git a/gateway/src/lib/messages.h b/gateway/src/lib/messages.h index 75b06a98..2f25f280 100644 --- a/gateway/src/lib/messages.h +++ b/gateway/src/lib/messages.h @@ -9,11 +9,13 @@ #include "cc_mqttsn/Message.h" #include "cc_mqttsn/frame/Frame.h" +#include "cc_mqttsn/input/AllMessages.h" #include "cc_mqttsn/input/ServerInputMessages.h" #include "cc_mqttsn/options/ServerDefaultOptions.h" #include "cc_mqttsn/options/DataViewDefaultOptions.h" #include "cc_mqtt311/Message.h" #include "cc_mqtt311/frame/Frame.h" +#include "cc_mqtt311/input/AllMessages.h" #include "cc_mqtt311/input/ClientInputMessages.h" #include "cc_mqtt311/options/ClientDefaultOptions.h" #include "cc_mqtt311/options/DataViewDefaultOptions.h" @@ -45,72 +47,32 @@ using MqttsnGwOptions = cc_mqttsn::options::DataViewDefaultOptionsT< cc_mqttsn::options::ServerDefaultOptions >; - -typedef cc_mqttsn::message::Advertise AdvertiseMsg_SN; -typedef cc_mqttsn::message::Searchgw SearchgwMsg_SN; -typedef cc_mqttsn::message::Gwinfo GwinfoMsg_SN; -typedef cc_mqttsn::message::Connect ConnectMsg_SN; -typedef cc_mqttsn::message::Connack ConnackMsg_SN; -typedef cc_mqttsn::message::Willtopicreq WilltopicreqMsg_SN; -typedef cc_mqttsn::message::Willtopic WilltopicMsg_SN; -typedef cc_mqttsn::message::Willmsgreq WillmsgreqMsg_SN; -typedef cc_mqttsn::message::Willmsg WillmsgMsg_SN; -typedef cc_mqttsn::message::Register RegisterMsg_SN; -typedef cc_mqttsn::message::Regack RegackMsg_SN; -typedef cc_mqttsn::message::Publish PublishMsg_SN; -typedef cc_mqttsn::message::Puback PubackMsg_SN; -typedef cc_mqttsn::message::Pubrec PubrecMsg_SN; -typedef cc_mqttsn::message::Pubrel PubrelMsg_SN; -typedef cc_mqttsn::message::Pubcomp PubcompMsg_SN; -typedef cc_mqttsn::message::Subscribe SubscribeMsg_SN; -typedef cc_mqttsn::message::Suback SubackMsg_SN; -typedef cc_mqttsn::message::Unsubscribe UnsubscribeMsg_SN; -typedef cc_mqttsn::message::Unsuback UnsubackMsg_SN; -typedef cc_mqttsn::message::Pingreq PingreqMsg_SN; -typedef cc_mqttsn::message::Pingresp PingrespMsg_SN; -typedef cc_mqttsn::message::Disconnect DisconnectMsg_SN; -typedef cc_mqttsn::message::Willtopicupd WilltopicupdMsg_SN; -typedef cc_mqttsn::message::Willtopicresp WilltopicrespMsg_SN; -typedef cc_mqttsn::message::Willmsgupd WillmsgupdMsg_SN; -typedef cc_mqttsn::message::Willmsgresp WillmsgrespMsg_SN; + +// Aliases to all MQTT-SN messages: +/// using AdvertiseMsg_SN = cc_mqttsn::message::Advertise; +/// using SearchgwMsg_SN = cc_mqttsn::message::Searchgw; +/// ... +CC_MQTTSN_ALIASES_FOR_ALL_MESSAGES(, Msg_SN, MqttsnMessage, MqttsnGwOptions) template using InputMqttsnMessages = cc_mqttsn::input::ServerInputMessages; -using MqttsnProtStack = +using MqttsnFrame = cc_mqttsn::frame::Frame >; -//using Mqtt311GwOptions = cc_mqtt311::options::ClientDefaultOptions; - -// TODO: Currently gateway is implemented to receive PING and DISCONNECT from server, -// According to protocol spec these are client only messages. Consider using ClientDefaultOptions in the future -using Mqtt311GwOptions = cc_mqtt311::options::DefaultOptions; - -typedef cc_mqtt311::message::Connect ConnectMsg; -typedef cc_mqtt311::message::Connack ConnackMsg; -typedef cc_mqtt311::message::Publish PublishMsg; -typedef cc_mqtt311::message::Puback PubackMsg; -typedef cc_mqtt311::message::Pubrec PubrecMsg; -typedef cc_mqtt311::message::Pubrel PubrelMsg; -typedef cc_mqtt311::message::Pubcomp PubcompMsg; -typedef cc_mqtt311::message::Subscribe SubscribeMsg; -typedef cc_mqtt311::message::Suback SubackMsg; -typedef cc_mqtt311::message::Unsubscribe UnsubscribeMsg; -typedef cc_mqtt311::message::Unsuback UnsubackMsg; -typedef cc_mqtt311::message::Pingreq PingreqMsg; -typedef cc_mqtt311::message::Pingresp PingrespMsg; -typedef cc_mqtt311::message::Disconnect DisconnectMsg; +using Mqtt311GwOptions = cc_mqtt311::options::ClientDefaultOptions; -// template -// using InputMqtt311Messages = cc_mqtt311::input::ClientInputMessages; +// Aliases to all MQTT-SN messages: +/// using ConnectMsg = cc_mqtt311::message::Connect; +/// using ConnackMsg = cc_mqtt311::message::Connack; +/// ... +CC_MQTT311_ALIASES_FOR_ALL_MESSAGES(, Msg, MqttMessage, Mqtt311GwOptions) -// TODO: Currently gateway is implemented to receive PING and DISCONNECT from server, -// According to protocol spec these are client only messages. Consider using ClientInputMessages in the future template -using InputMqtt311Messages = cc_mqtt311::input::AllMessages; +using InputMqtt311Messages = cc_mqtt311::input::ClientInputMessages; -using MqttProtStack = - cc_mqtt311::frame::Frame > ; +using MqttFrame = + cc_mqtt311::frame::Frame, Mqtt311GwOptions> ; } // namespace cc_mqttsn_gateway diff --git a/gateway/src/lib/session_op/Disconnect.cpp b/gateway/src/lib/session_op/Disconnect.cpp index d3baadd6..24203ffb 100644 --- a/gateway/src/lib/session_op/Disconnect.cpp +++ b/gateway/src/lib/session_op/Disconnect.cpp @@ -53,21 +53,6 @@ void Disconnect::handle(DisconnectMsg_SN& msg) termRequest(); } -void Disconnect::handle(DisconnectMsg& msg) -{ - static_cast(msg); - if (state().m_connStatus == ConnectionStatus::Connected) { - sendDisconnectSn(); - } - - if (state().m_connStatus != ConnectionStatus::Asleep) { - termRequest(); - return; - } - - state().m_pendingClientDisconnect = true; -} - void Disconnect::sendDisconnectSn() { Base::sendDisconnectToClient(); diff --git a/gateway/src/lib/session_op/Disconnect.h b/gateway/src/lib/session_op/Disconnect.h index c7ff6ffb..d5455838 100644 --- a/gateway/src/lib/session_op/Disconnect.h +++ b/gateway/src/lib/session_op/Disconnect.h @@ -30,7 +30,6 @@ class Disconnect : public SessionOp private: using Base::handle; virtual void handle(DisconnectMsg_SN& msg) override; - virtual void handle(DisconnectMsg& msg) override; void sendDisconnectSn(); }; diff --git a/gateway/src/lib/session_op/Forward.cpp b/gateway/src/lib/session_op/Forward.cpp index 3db25268..447f66b3 100644 --- a/gateway/src/lib/session_op/Forward.cpp +++ b/gateway/src/lib/session_op/Forward.cpp @@ -323,16 +323,6 @@ void Forward::handle(PubcompMsg& msg) sendToClient(respMsg); } -void Forward::handle(PingreqMsg& msg) -{ - static_cast(msg); - if (state().m_connStatus != ConnectionStatus::Connected) { - return; - } - - sendToClient(PingreqMsg_SN()); -} - void Forward::handle(PingrespMsg& msg) { static_cast(msg); diff --git a/gateway/src/lib/session_op/Forward.h b/gateway/src/lib/session_op/Forward.h index be52b947..80bce8c9 100644 --- a/gateway/src/lib/session_op/Forward.h +++ b/gateway/src/lib/session_op/Forward.h @@ -43,7 +43,6 @@ class Forward : public SessionOp virtual void handle(PubackMsg& msg) override; virtual void handle(PubrecMsg& msg) override; virtual void handle(PubcompMsg& msg) override; - virtual void handle(PingreqMsg& msg) override; virtual void handle(PingrespMsg& msg) override; virtual void handle(SubackMsg& msg) override; virtual void handle(UnsubackMsg& msg) override; From 3a64b7f998b5cfa17e70f640161a18ff876bfc73 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 1 May 2024 08:37:39 +1000 Subject: [PATCH 003/106] Requiring min C++17. --- .appveyor.yml | 10 +- .github/workflows/actions_build.yml | 195 +------------------ CMakeLists.txt | 2 +- gateway/src/app/udp/Mgr.cpp | 21 +- gateway/src/app/udp/SessionWrapper.cpp | 3 +- gateway/src/lib/GatewayImpl.cpp | 3 +- gateway/src/lib/SessionImpl.cpp | 6 +- gateway/src/lib/session_op/Asleep.cpp | 9 +- gateway/src/lib/session_op/AsleepMonitor.cpp | 12 +- gateway/src/lib/session_op/Forward.cpp | 9 +- gateway/src/lib/session_op/PubSend.cpp | 6 +- gateway/src/lib/session_op/WillUpdate.cpp | 6 +- gateway/test/Session.th | 3 +- gateway/test/TestMsgHandler.cpp | 12 +- 14 files changed, 41 insertions(+), 256 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 003cc298..e7f6d129 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,6 +1,4 @@ image: - - Visual Studio 2015 - - Visual Studio 2017 - Visual Studio 2019 - Visual Studio 2022 @@ -24,19 +22,13 @@ environment: CC_MQTT311_TAG: v2.7 matrix: - - CPP_STD: 11 - - CPP_STD: 14 - CPP_STD: 17 - CPP_STD: 20 matrix: fast_finish: false exclude: - - image: Visual Studio 2015 - CPP_STD: 17 - - image: Visual Studio 2015 - CPP_STD: 20 - - image: Visual Studio 2017 + - image: Visual Studio 2019 CPP_STD: 20 install: diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index 45bbb945..68d25119 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -8,129 +8,6 @@ env: CC_MQTT311_TAG: v2.7 jobs: - build_gcc_old_ubuntu_20_04: - runs-on: ubuntu-20.04 - strategy: - fail-fast: false - matrix: - type: [Debug, Release, MinSizeRel] - cc_ver: [5, 6, 7] - cpp: [11, 14] - - steps: - - uses: actions/checkout@v4 - - - name: Add repositories - run: | - sudo add-apt-repository 'deb http://archive.ubuntu.com/ubuntu/ bionic main'; \ - sudo add-apt-repository 'deb http://archive.ubuntu.com/ubuntu/ bionic universe' - - - name: Prepare Install - run: sudo apt-get update --fix-missing - - - name: Install Packages - run: sudo apt install qtbase5-dev valgrind gcc-${{matrix.cc_ver}} g++-${{matrix.cc_ver}} - - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Prepare externals - shell: bash - run: $GITHUB_WORKSPACE/script/prepare_externals.sh - env: - BUILD_DIR: ${{runner.workspace}}/build - CC: gcc-${{matrix.cc_ver}} - CXX: g++-${{matrix.cc_ver}} - EXTERNALS_DIR: ${{runner.workspace}}/externals - COMMON_INSTALL_DIR: ${{runner.workspace}}/build/install - COMMON_BUILD_TYPE: ${{matrix.type}} - COMMON_CXX_STANDARD: ${{matrix.cpp}} - COMMS_TAG: ${{env.COMMS_TAG}} - CC_MQTTSN_TAG: ${{env.CC_MQTTSN_TAG}} - CC_MQTT311_TAG: ${{env.CC_MQTT311_TAG}} - - - name: Configure CMake - shell: bash - working-directory: ${{runner.workspace}}/build - run: | - cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{matrix.type}} -DCMAKE_CXX_STANDARD=${{matrix.cpp}} \ - -DCMAKE_INSTALL_PREFIX=install -DCMAKE_EXE_LINKER_FLAGS=-fuse-ld=gold -DCMAKE_PREFIX_PATH=${{runner.workspace}}/build/install \ - -DCC_MQTTSN_BUILD_UNIT_TESTS=ON -DCC_MQTTSN_UNIT_TEST_WITH_VALGRIND=ON - env: - CC: gcc-${{matrix.cc_ver}} - CXX: g++-${{matrix.cc_ver}} - - - name: Build - working-directory: ${{runner.workspace}}/build - shell: bash - run: cmake --build . --config ${{matrix.type}} --target install - env: - VERBOSE: 1 - - - name: Test - working-directory: ${{runner.workspace}}/build - shell: bash - run: ctest -V - - build_gcc_ubuntu_20_04: - runs-on: ubuntu-20.04 - strategy: - fail-fast: false - matrix: - type: [Debug, Release, MinSizeRel] - cc_ver: [8] - cpp: [11, 14, 17] - - steps: - - uses: actions/checkout@v4 - - - name: Prepare Install - run: sudo apt-get update --fix-missing - - - name: Install Packages - run: sudo apt install qtbase5-dev gcc-${{matrix.cc_ver}} g++-${{matrix.cc_ver}} - - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Prepare externals - shell: bash - run: $GITHUB_WORKSPACE/script/prepare_externals.sh - env: - BUILD_DIR: ${{runner.workspace}}/build - CC: gcc-${{matrix.cc_ver}} - CXX: g++-${{matrix.cc_ver}} - EXTERNALS_DIR: ${{runner.workspace}}/externals - COMMON_INSTALL_DIR: ${{runner.workspace}}/build/install - COMMON_BUILD_TYPE: ${{matrix.type}} - COMMON_CXX_STANDARD: ${{matrix.cpp}} - COMMS_TAG: ${{env.COMMS_TAG}} - CC_MQTTSN_TAG: ${{env.CC_MQTTSN_TAG}} - CC_MQTT311_TAG: ${{env.CC_MQTT311_TAG}} - - - name: Configure CMake - shell: bash - working-directory: ${{runner.workspace}}/build - run: | - cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{matrix.type}} -DCMAKE_CXX_STANDARD=${{matrix.cpp}} \ - -DCMAKE_INSTALL_PREFIX=install -DCMAKE_PREFIX_PATH=${{runner.workspace}}/build/install \ - -DCC_MQTTSN_BUILD_UNIT_TESTS=ON -DCC_MQTTSN_WITH_SANITIZERS=ON - env: - CC: gcc-${{matrix.cc_ver}} - CXX: g++-${{matrix.cc_ver}} - - - name: Build - working-directory: ${{runner.workspace}}/build - shell: bash - run: cmake --build . --config ${{matrix.type}} --target install - env: - VERBOSE: 1 - - - name: Test - working-directory: ${{runner.workspace}}/build - shell: bash - run: ctest -V - build_gcc_ubuntu_22_04: runs-on: ubuntu-22.04 strategy: @@ -138,7 +15,7 @@ jobs: matrix: type: [Debug, Release, MinSizeRel] cc_ver: [9, 10, 11, 12] - cpp: [11, 14, 17, 20] + cpp: [17, 20] steps: - uses: actions/checkout@v4 @@ -190,75 +67,17 @@ jobs: shell: bash run: ctest -V -# - clang crashes up to version 9 - build_clang_ubuntu_20_04: - runs-on: ubuntu-20.04 - strategy: - fail-fast: false - matrix: - type: [Debug, Release, MinSizeRel] - cc_ver: [9, 10, 11] - cpp: [11, 14, 17, 20] - - steps: - - uses: actions/checkout@v4 - - - name: Prepare Install - run: sudo apt-get update --fix-missing - - - name: Install Packages - run: sudo apt install qtbase5-dev clang-${{matrix.cc_ver}} - - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Prepare externals - shell: bash - run: $GITHUB_WORKSPACE/script/prepare_externals.sh - env: - BUILD_DIR: ${{runner.workspace}}/build - CC: clang-${{matrix.cc_ver}} - CXX: clang++-${{matrix.cc_ver}} - EXTERNALS_DIR: ${{runner.workspace}}/externals - COMMON_INSTALL_DIR: ${{runner.workspace}}/build/install - COMMON_BUILD_TYPE: ${{matrix.type}} - COMMON_CXX_STANDARD: ${{matrix.cpp}} - COMMS_TAG: ${{env.COMMS_TAG}} - CC_MQTTSN_TAG: ${{env.CC_MQTTSN_TAG}} - CC_MQTT311_TAG: ${{env.CC_MQTT311_TAG}} - - - name: Configure CMake - shell: bash - working-directory: ${{runner.workspace}}/build - run: | - cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{matrix.type}} -DCMAKE_CXX_STANDARD=${{matrix.cpp}} \ - -DCMAKE_INSTALL_PREFIX=install -DCMAKE_PREFIX_PATH=${{runner.workspace}}/build/install \ - -DCC_MQTTSN_BUILD_UNIT_TESTS=ON -DCC_MQTTSN_WITH_SANITIZERS=ON - env: - CC: clang-${{matrix.cc_ver}} - CXX: clang++-${{matrix.cc_ver}} - - - name: Build - working-directory: ${{runner.workspace}}/build - shell: bash - run: cmake --build . --config ${{matrix.type}} --target install - env: - VERBOSE: 1 - - - name: Test - working-directory: ${{runner.workspace}}/build - shell: bash - run: ctest -V - build_clang_ubuntu_22_04: runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: type: [Debug, Release, MinSizeRel] - cc_ver: [12, 13, 14, 15] - cpp: [11, 14, 17, 20] + cc_ver: [11, 12, 13, 14, 15] + cpp: [17, 20] exclude: + - cc_ver: 11 + cpp: 20 - cc_ver: 12 cpp: 20 - cc_ver: 13 @@ -323,7 +142,7 @@ jobs: matrix: type: [Debug, Release, MinSizeRel] arch: [Win32, x64] - cpp: [11, 14, 17] + cpp: [17] steps: - uses: actions/checkout@v4 @@ -374,7 +193,7 @@ jobs: matrix: type: [Debug, Release, MinSizeRel] arch: [Win32, x64] - cpp: [11, 14, 17, 20] + cpp: [17, 20] steps: - uses: actions/checkout@v4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 3289e44f..8c244082 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ option (CC_MQTTSN_WITH_SANITIZERS "Build with sanitizers" OFF) ########################################################################## if ("${CMAKE_CXX_STANDARD}" STREQUAL "") - set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD 17) endif () find_package(LibComms REQUIRED) diff --git a/gateway/src/app/udp/Mgr.cpp b/gateway/src/app/udp/Mgr.cpp index 7d481fdd..1007dafb 100644 --- a/gateway/src/app/udp/Mgr.cpp +++ b/gateway/src/app/udp/Mgr.cpp @@ -111,13 +111,13 @@ void Mgr::readClientData() while (m_socket.hasPendingDatagrams()) { data.resize(m_socket.pendingDatagramSize()); - auto readBytes = m_socket.readDatagram( - reinterpret_cast(&data[0]), - data.size(), - &senderAddress, - &senderPort); + [[maybe_unused]] auto readBytes = + m_socket.readDatagram( + reinterpret_cast(&data[0]), + data.size(), + &senderAddress, + &senderPort); assert(readBytes == static_cast(data.size())); - static_cast(readBytes); if (data == m_lastAdvertise) { continue; @@ -149,8 +149,7 @@ void Mgr::readClientData() auto key = QString("%1:%2").arg(s.getClientAddr()).arg(s.getClientPort()); auto it = m_sessions.find(key); if (it == m_sessions.end()) { - constexpr bool Session_not_found = false; - static_cast(Session_not_found); + [[maybe_unused]] constexpr bool Session_not_found = false; assert(Session_not_found); return; } @@ -163,8 +162,7 @@ void Mgr::readClientData() auto sessionPtr = session.release(); if (!sessionPtr->start()) { - constexpr bool Should_not_happen = false; - static_cast(Should_not_happen); + [[maybe_unused]] constexpr bool Should_not_happen = false; assert(Should_not_happen); continue; } @@ -173,9 +171,8 @@ void Mgr::readClientData() } } -void Mgr::socketErrorOccurred(QAbstractSocket::SocketError err) +void Mgr::socketErrorOccurred([[maybe_unused]] QAbstractSocket::SocketError err) { - static_cast(err); std::cerr << "ERROR: UDP Socket: " << m_socket.errorString().toStdString() << std::endl; } diff --git a/gateway/src/app/udp/SessionWrapper.cpp b/gateway/src/app/udp/SessionWrapper.cpp index 11cb5256..4a725dfc 100644 --- a/gateway/src/app/udp/SessionWrapper.cpp +++ b/gateway/src/app/udp/SessionWrapper.cpp @@ -177,9 +177,8 @@ void SessionWrapper::readFromBrokerSocket() m_brokerData.assign(buf + consumed, buf + bufSize); } -void SessionWrapper::brokerSocketErrorOccurred(QAbstractSocket::SocketError err) +void SessionWrapper::brokerSocketErrorOccurred([[maybe_unused]] QAbstractSocket::SocketError err) { - static_cast(err); std::cerr << "ERROR: TCP Socket: " << m_brokerSocket.errorString().toStdString() << std::endl; } diff --git a/gateway/src/lib/GatewayImpl.cpp b/gateway/src/lib/GatewayImpl.cpp index 25af057a..505baa5e 100644 --- a/gateway/src/lib/GatewayImpl.cpp +++ b/gateway/src/lib/GatewayImpl.cpp @@ -58,8 +58,7 @@ void GatewayImpl::refresh() durationField.value() = m_advertisePeriod; auto iter = &m_outputData[0]; - auto es = m_protStack.write(msg, iter, m_outputData.size()); - static_cast(es); + [[maybe_unused]] auto es = m_protStack.write(msg, iter, m_outputData.size()); assert(es == comms::ErrorStatus::Success); assert(std::distance(&m_outputData[0], iter) == AdvertiseLength); } diff --git a/gateway/src/lib/SessionImpl.cpp b/gateway/src/lib/SessionImpl.cpp index 63359360..0dfb7905 100644 --- a/gateway/src/lib/SessionImpl.cpp +++ b/gateway/src/lib/SessionImpl.cpp @@ -88,8 +88,7 @@ void SessionImpl::sendMessage(const TMsg& msg, TStack& stack, SendDataReqCb& fun buf.resize(std::max(buf.size(), stack.length(msg))); auto iter = comms::writeIteratorFor(&buf[0]); - auto es = stack.write(msg, iter, buf.size()); - static_cast(es); + [[maybe_unused]] auto es = stack.write(msg, iter, buf.size()); assert(es == comms::ErrorStatus::Success); auto writtenCount = static_cast( @@ -190,9 +189,8 @@ bool SessionImpl::setTopicIdAllocationRange(std::uint16_t minVal, std::uint16_t return m_state.m_regMgr.setTopicIdAllocationRange(minVal, maxVal); } -void SessionImpl::handle(SearchgwMsg_SN& msg) +void SessionImpl::handle([[maybe_unused]] SearchgwMsg_SN& msg) { - static_cast(msg); GwinfoMsg_SN respMsg; auto& fields = respMsg.fields(); auto& gwIdField = std::get(fields); diff --git a/gateway/src/lib/session_op/Asleep.cpp b/gateway/src/lib/session_op/Asleep.cpp index 05d77717..790b764d 100644 --- a/gateway/src/lib/session_op/Asleep.cpp +++ b/gateway/src/lib/session_op/Asleep.cpp @@ -56,17 +56,15 @@ void Asleep::handle(DisconnectMsg_SN& msg) doPing(); } -void Asleep::handle(MqttsnMessage& msg) +void Asleep::handle([[maybe_unused]] MqttsnMessage& msg) { - static_cast(msg); if (state().m_connStatus != ConnectionStatus::Asleep) { cancelTick(); } } -void Asleep::handle(PingrespMsg& msg) +void Asleep::handle([[maybe_unused]] PingrespMsg& msg) { - static_cast(msg); auto& st = state(); if ((st.m_pendingClientDisconnect) || (st.m_connStatus != ConnectionStatus::Asleep)) { @@ -78,9 +76,8 @@ void Asleep::handle(PingrespMsg& msg) reqNextTick(); } -void Asleep::handle(MqttMessage& msg) +void Asleep::handle([[maybe_unused]] MqttMessage& msg) { - static_cast(msg); if (state().m_connStatus != ConnectionStatus::Asleep) { cancelTick(); } diff --git a/gateway/src/lib/session_op/AsleepMonitor.cpp b/gateway/src/lib/session_op/AsleepMonitor.cpp index 36eace07..a215d13d 100644 --- a/gateway/src/lib/session_op/AsleepMonitor.cpp +++ b/gateway/src/lib/session_op/AsleepMonitor.cpp @@ -39,8 +39,7 @@ void AsleepMonitor::handle(DisconnectMsg_SN& msg) } if (state().m_connStatus != ConnectionStatus::Asleep) { - constexpr bool Should_not_happen = false; - static_cast(Should_not_happen); + [[maybe_unused]] constexpr bool Should_not_happen = false; assert(Should_not_happen); return; } @@ -50,9 +49,8 @@ void AsleepMonitor::handle(DisconnectMsg_SN& msg) reqNextTick(); } -void AsleepMonitor::handle(PingreqMsg_SN& msg) +void AsleepMonitor::handle([[maybe_unused]] PingreqMsg_SN& msg) { - static_cast(msg); m_lastPing = state().m_timestamp; cancelTick(); if (state().m_connStatus == ConnectionStatus::Asleep) { @@ -60,15 +58,13 @@ void AsleepMonitor::handle(PingreqMsg_SN& msg) } } -void AsleepMonitor::handle(MqttsnMessage& msg) +void AsleepMonitor::handle([[maybe_unused]] MqttsnMessage& msg) { - static_cast(msg); checkTickRequired(); } -void AsleepMonitor::handle(MqttMessage& msg) +void AsleepMonitor::handle([[maybe_unused]] MqttMessage& msg) { - static_cast(msg); checkTickRequired(); } diff --git a/gateway/src/lib/session_op/Forward.cpp b/gateway/src/lib/session_op/Forward.cpp index 447f66b3..6e1c99b3 100644 --- a/gateway/src/lib/session_op/Forward.cpp +++ b/gateway/src/lib/session_op/Forward.cpp @@ -97,9 +97,8 @@ void Forward::handle(PubrelMsg_SN& msg) sendToBroker(fwdMsg); } -void Forward::handle(PingreqMsg_SN& msg) +void Forward::handle([[maybe_unused]] PingreqMsg_SN& msg) { - static_cast(msg); if (state().m_connStatus != ConnectionStatus::Connected) { return; } @@ -108,9 +107,8 @@ void Forward::handle(PingreqMsg_SN& msg) sendToBroker(PingreqMsg()); } -void Forward::handle(PingrespMsg_SN& msg) +void Forward::handle([[maybe_unused]] PingrespMsg_SN& msg) { - static_cast(msg); sendToBroker(PingrespMsg()); } @@ -323,9 +321,8 @@ void Forward::handle(PubcompMsg& msg) sendToClient(respMsg); } -void Forward::handle(PingrespMsg& msg) +void Forward::handle([[maybe_unused]] PingrespMsg& msg) { - static_cast(msg); if (!m_pingInProgress) { return; } diff --git a/gateway/src/lib/session_op/PubSend.cpp b/gateway/src/lib/session_op/PubSend.cpp index 134736a1..290e251e 100644 --- a/gateway/src/lib/session_op/PubSend.cpp +++ b/gateway/src/lib/session_op/PubSend.cpp @@ -132,15 +132,13 @@ void PubSend::handle(PingreqMsg_SN& msg) checkSend(); } -void PubSend::handle(MqttsnMessage& msg) +void PubSend::handle([[maybe_unused]] MqttsnMessage& msg) { - static_cast(msg); checkSend(); } -void PubSend::handle(MqttMessage& msg) +void PubSend::handle([[maybe_unused]] MqttMessage& msg) { - static_cast(msg); checkSend(); } diff --git a/gateway/src/lib/session_op/WillUpdate.cpp b/gateway/src/lib/session_op/WillUpdate.cpp index 1fbf17c6..296fab87 100644 --- a/gateway/src/lib/session_op/WillUpdate.cpp +++ b/gateway/src/lib/session_op/WillUpdate.cpp @@ -48,17 +48,15 @@ void WillUpdate::brokerConnectionUpdatedImpl() } } -void WillUpdate::handle(ConnectMsg_SN& msg) +void WillUpdate::handle([[maybe_unused]] ConnectMsg_SN& msg) { - static_cast(msg); if (m_op != Op::None) { cancelOp(); } } -void WillUpdate::handle(DisconnectMsg_SN& msg) +void WillUpdate::handle([[maybe_unused]] DisconnectMsg_SN& msg) { - static_cast(msg); if (m_op != Op::None) { cancelOp(); } diff --git a/gateway/test/Session.th b/gateway/test/Session.th index 7502b255..3d825cd6 100644 --- a/gateway/test/Session.th +++ b/gateway/test/Session.th @@ -113,8 +113,7 @@ private: [&state]() -> unsigned { if (state.m_elapsed.empty()) { - constexpr bool Elapsed_time_not_specified = false; - static_cast(Elapsed_time_not_specified); + [[maybe_unused]] constexpr bool Elapsed_time_not_specified = false; assert(Elapsed_time_not_specified); return 0U; } diff --git a/gateway/test/TestMsgHandler.cpp b/gateway/test/TestMsgHandler.cpp index 14359d2c..1d3e0c2d 100644 --- a/gateway/test/TestMsgHandler.cpp +++ b/gateway/test/TestMsgHandler.cpp @@ -42,8 +42,7 @@ void TestMsgHandler::processOutputInternal(TStack& stack, const DataBuf& data) auto iter = comms::readIteratorFor(&data[0]); MsgPtr msg; - auto es = stack.read(msg, iter, data.size()); - static_cast(es); + [[maybe_unused]] auto es = stack.read(msg, iter, data.size()); if (es != comms::ErrorStatus::Success) { std::cout << "es=" << static_cast(es) << ": Output buffer: " << std::hex; std::copy(data.begin(), data.end(), std::ostream_iterator(std::cout, " ")); @@ -66,8 +65,7 @@ TestMsgHandler::DataBuf TestMsgHandler::prepareInputInternal(TStack& stack, cons buf.resize(stack.length(msg)); auto iter = comms::writeIteratorFor(&buf[0]); - auto es = stack.write(msg, iter, buf.size()); - static_cast(es); + [[maybe_unused]] auto es = stack.write(msg, iter, buf.size()); assert(es == comms::ErrorStatus::Success); assert(buf.size() == static_cast(std::distance(comms::writeIteratorFor(&buf[0]), iter))); return buf; @@ -419,8 +417,7 @@ void TestMsgHandler::handle(WillmsgrespMsg_SN& msg) void TestMsgHandler::handle(TestMqttsnMessage& msg) { std::cout << "Unhandled message sent to client: " << static_cast(msg.getId()) << std::endl; - constexpr bool Unhandled_message = false; - static_cast(Unhandled_message); + [[maybe_unused]] constexpr bool Unhandled_message = false; assert(Unhandled_message); } @@ -493,8 +490,7 @@ void TestMsgHandler::handle(UnsubscribeMsg& msg) void TestMsgHandler::handle(TestMqttMessage& msg) { std::cout << "Unhandled message sent to broker: " << static_cast(msg.getId()) << std::endl; - constexpr bool Unhandled_message = false; - static_cast(Unhandled_message); + [[maybe_unused]] constexpr bool Unhandled_message = false; assert(Unhandled_message); } From 0a3bcce7af888ce0171d894fd5289735e5464ae6 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 1 May 2024 08:40:27 +1000 Subject: [PATCH 004/106] Disable usage of ccache by default. --- CMakeLists.txt | 2 +- script/env_dev.sh | 2 +- script/full_debug_build.sh | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c244082..a86758f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ option (CC_MQTTSN_BUILD_CLIENT_APPS "Build and install client applications" ${CC option (CC_MQTTSN_BUILD_GATEWAY_APPS "Build and install gateway applications" ${CC_MQTTSN_BUILD_GATEWAY}) option (CC_MQTTSN_BUILD_UNIT_TESTS "Build unittests." OFF) option (CC_MQTTSN_UNIT_TEST_WITH_VALGRIND "Disable valgrind in unittests." OFF) -option (CC_MQTTSN_USE_CCACHE "Use ccache on unix system" ON) +option (CC_MQTTSN_USE_CCACHE "Use ccache on unix system" OFF) option (CC_MQTTSN_WITH_SANITIZERS "Build with sanitizers" OFF) # Extra variables diff --git a/script/env_dev.sh b/script/env_dev.sh index 2383c760..cc5d732c 100755 --- a/script/env_dev.sh +++ b/script/env_dev.sh @@ -11,4 +11,4 @@ BUILD_DIR="${ROOT_DIR}/build.${CC}" mkdir -p ${BUILD_DIR} cd ${BUILD_DIR} -cmake .. -DCMAKE_INSTALL_PREFIX=install -DCMAKE_BUILD_TYPE=Debug "$@" +cmake .. -DCMAKE_INSTALL_PREFIX=install -DCMAKE_BUILD_TYPE=Debug -DCC_MQTTSN_USE_CCACHE=ON "$@" diff --git a/script/full_debug_build.sh b/script/full_debug_build.sh index a0cb20b9..121a0df8 100755 --- a/script/full_debug_build.sh +++ b/script/full_debug_build.sh @@ -17,6 +17,7 @@ ${SCRIPT_DIR}/prepare_externals.sh cd ${BUILD_DIR} cmake .. -DCMAKE_INSTALL_PREFIX=${COMMON_INSTALL_DIR} -DCMAKE_BUILD_TYPE=Debug \ + -DCC_MQTTSN_USE_CCACHE=ON \ -DCC_MQTTSN_WITH_SANITIZERS=ON -DCC_MQTTSN_BUILD_UNIT_TESTS=ON "$@" procs=$(nproc) From 09cc0bf64690d277614f78cf8b11b5a941898c99 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 1 May 2024 09:08:01 +1000 Subject: [PATCH 005/106] Added "session" handle report to the callbacks in gateway "C" interface. --- client/include/cc_mqttsn_client/common.h | 19 +-------- gateway/doc/session.dox | 19 ++++----- .../include/cc_mqttsn_gateway/gateway_all.h | 39 +++++++------------ gateway/src/lib/gateway_all.cpp | 32 +++++++-------- 4 files changed, 42 insertions(+), 67 deletions(-) diff --git a/client/include/cc_mqttsn_client/common.h b/client/include/cc_mqttsn_client/common.h index 73754b75..0902e821 100644 --- a/client/include/cc_mqttsn_client/common.h +++ b/client/include/cc_mqttsn_client/common.h @@ -12,25 +12,8 @@ #ifdef __cplusplus extern "C" { -#else - -#ifdef WIN32 -#ifndef bool -#define bool char -#endif - -#ifndef true -#define true 1 -#endif - -#ifndef false -#define false 0 -#endif - -#else // #ifdef WIN32 +#else // #ifdef __cplusplus #include -#endif // #ifdef WIN32 - #endif // #ifdef __cplusplus /// @brief Major verion of the library diff --git a/gateway/doc/session.dox b/gateway/doc/session.dox index 2bc88bbe..d19287c2 100644 --- a/gateway/doc/session.dox +++ b/gateway/doc/session.dox @@ -17,7 +17,7 @@ /// When using @b C++ interface, just instantiate object of cc_mqttsn_gateway::Session /// class. The destruction of the object will clean up all acquired resources. /// @code -/// cc_mqttsn_gateway::Session* session = new cc_mqttsn_gateway::Session(); // make sure to use smart pointer in production code +/// std::unique_ptr session(new cc_mqttsn_gateway::Session()); /// @endcode /// /// When using @b C interface, the allocation is performed using cc_mqttsn_gw_session_alloc() @@ -94,7 +94,7 @@ /// /// @b C interface: /// @code -/// void my_send_to_client(void* userData, const unsigned char* buf, unsigned bufLen) +/// void my_send_to_client(void* userData, CC_MqttsnSessionHandle session, const unsigned char* buf, unsigned bufLen) /// { /// ... /// } @@ -114,7 +114,7 @@ /// /// @b C interface: /// @code -/// void my_send_to_broker(void* userData, const unsigned char* buf, unsigned bufLen) +/// void my_send_to_broker(void* userData, CC_MqttsnSessionHandle session, const unsigned char* buf, unsigned bufLen) /// { /// ... /// } @@ -134,12 +134,12 @@ /// { /// ... // Set timer to expire after duration milliseconds /// // After expiry call session.tick() -/// }; +/// }); /// @endcode /// /// @b C interface: /// @code -/// void my_tick_req(void* userData, unsigned duration) +/// void my_tick_req(void* userData, CC_MqttsnSessionHandle session, unsigned duration) /// { /// ... /* Set timer to expire after duration milliseconds */ /// ... /* After expiry call cc_mqttsn_gw_session_tick() */ @@ -178,7 +178,7 @@ /// /// @b C interface: /// @code -/// unsigned my_cancel_timer_req(void* userData) +/// unsigned my_cancel_timer_req(void* userData, CC_MqttsnSessionHandle session) /// { /// ... /* cancel timer */ /// return ...; /* return number of elapsed milliseconds */ @@ -205,7 +205,7 @@ /// /// @b C interface: /// @code -/// void my_session_term(void* userData) +/// void my_session_term(void* userData, CC_MqttsnSessionHandle session) /// { /// ... /* Remove reference to session object from internal data structures */ /// cc_mqttsn_gw_session_free(handle); @@ -244,7 +244,7 @@ /// /// @b C interface /// @code -/// void my_broker_reconnect(void* userData) +/// void my_broker_reconnect(void* userData, CC_MqttsnSessionHandle session) /// { /// ... /* Close existing TCP/IP connection to broker and open a new one */ /// } @@ -432,6 +432,7 @@ /// @code /// void my_auth_callback( /// void* userData, +/// CC_MqttsnSessionHandle session, /// const char* clientId, /// const char** username, /// const unsigned char** password, @@ -465,7 +466,7 @@ /// /// @b C interface: /// @code -/// void my_client_connect_report(void* userData, const char* clientId) +/// void my_client_connect_report(void* userData, CC_MqttsnSessionHandle session, const char* clientId) /// { /// if (!cc_mqttsn_gw_session_add_predefined_topic(handle, "client/specific/predefined/topic", 2222)) { /// ... /* report error */ diff --git a/gateway/include/cc_mqttsn_gateway/gateway_all.h b/gateway/include/cc_mqttsn_gateway/gateway_all.h index d7170b8b..3b3d959d 100644 --- a/gateway/include/cc_mqttsn_gateway/gateway_all.h +++ b/gateway/include/cc_mqttsn_gateway/gateway_all.h @@ -14,25 +14,8 @@ #ifdef __cplusplus extern "C" { -#else - -#ifdef WIN32 -#ifndef bool -#define bool char -#endif - -#ifndef true -#define true 1 -#endif - -#ifndef false -#define false 0 -#endif - -#else // #ifdef WIN32 +#else // #ifdef __cplusplus #include -#endif // #ifdef WIN32 - #endif // #ifdef __cplusplus /*===================== Gateway Object ======================*/ @@ -135,55 +118,63 @@ typedef struct /// @details When the requested time is due, the driving code is expected /// to call cc_mqttsn_gw_session_tick() member function. /// @param[in] userData User data passed as the last parameter to the setting function. +/// @param[in] session Handle of session performing the request /// @param[in] duration Number of @b milliseconds to measure. -typedef void (*CC_MqttsnSessionTickReqCb)(void* userData, unsigned duration); +typedef void (*CC_MqttsnSessionTickReqCb)(void* userData, CC_MqttsnSessionHandle session, unsigned duration); /// @brief Type of callback, used to cancel existing time measurement. /// @details When invoked the existing time measurement needs to be cancelled. /// The function also needs to return amount of @b milliseconds elapsed /// since last timer programming request. /// @param[in] userData User data passed as the last parameter to the setting function. +/// @param[in] session Handle of session performing the request /// @return Number of elapsed @b milliseconds since last timer programming /// request. -typedef unsigned (*CC_MqttsnSessionCancelTickReqCb)(void* userData); +typedef unsigned (*CC_MqttsnSessionCancelTickReqCb)(void* userData, CC_MqttsnSessionHandle session); /// @brief Type of callback, used to request delivery of serialised message /// to the client or broker. /// @param[in] userData User data passed as the last parameter to the setting function. +/// @param[in] session Handle of session performing the request /// @param[in] buf Buffer containing serialised message. /// @param[in] bufLen Number of bytes in the buffer -typedef void (*CC_MqttsnSessionSendDataReqCb)(void* userData, const unsigned char* buf, unsigned bufLen); +typedef void (*CC_MqttsnSessionSendDataReqCb)(void* userData, CC_MqttsnSessionHandle session, const unsigned char* buf, unsigned bufLen); /// @brief Type of callback, used to request session termination. /// @details When the callback is invoked, the driving code must flush /// all the previously sent messages to appropriate I/O links and /// delete this session object. /// @param[in] userData User data passed as the last parameter to the setting function. -typedef void (*CC_MqttsnSessionTermReqCb)(void* userData); +/// @param[in] session Handle of session performing the request +typedef void (*CC_MqttsnSessionTermReqCb)(void* userData, CC_MqttsnSessionHandle session); /// @brief Type of callback used to request reconnection to the broker. /// @details When the callback is invoked, the driving code must close /// existing TCP/IP connection to the broker and create a new one. /// @param[in] userData User data passed as the last parameter to the setting function. -typedef void (*CC_MqttsnSessionBrokerReconnectReqCb)(void* userData); +/// @param[in] session Handle of session performing the request +typedef void (*CC_MqttsnSessionBrokerReconnectReqCb)(void* userData, CC_MqttsnSessionHandle session); /// @brief Type of callback used to report client ID of the newly connected /// MQTT-SN client. /// @details The callback can be used to provide additional client specific /// information, such as predefined topic IDs. /// @param[in] userData User data passed as the last parameter to the setting function. +/// @param[in] session Handle of session performing the report /// @param[in] clientId Client ID -typedef void (*CC_MqttsnSessionClientConnectReportCb)(void* userData, const char* clientId); +typedef void (*CC_MqttsnSessionClientConnectReportCb)(void* userData, CC_MqttsnSessionHandle session, const char* clientId); /// @brief Type of callback used to request authentication information of /// the client that is trying to connect. /// @param[in] userData User data passed as the last parameter to the setting function. +/// @param[in] session Handle of session performing the request /// @param[in] clientId Client ID /// @param[out] username Username string /// @param[out] password Binary password buffer /// @param[out] passwordLen Length of the binary password typedef void (*CC_MqttsnSessionAuthInfoReqCb)( void* userData, + CC_MqttsnSessionHandle session, const char* clientId, const char** username, const unsigned char** password, diff --git a/gateway/src/lib/gateway_all.cpp b/gateway/src/lib/gateway_all.cpp index cb8c0e82..ccbfa64e 100644 --- a/gateway/src/lib/gateway_all.cpp +++ b/gateway/src/lib/gateway_all.cpp @@ -138,9 +138,9 @@ void cc_mqttsn_gw_session_set_tick_req_cb( } reinterpret_cast(session.obj)->setNextTickProgramReqCb( - [cb, data](unsigned duration) + [cb, data, session](unsigned duration) { - cb(data, duration); + cb(data, session, duration); }); } @@ -154,9 +154,9 @@ void cc_mqttsn_gw_session_set_cancel_tick_cb( } reinterpret_cast(session.obj)->setCancelTickWaitReqCb( - [cb, data]() -> unsigned + [cb, data, session]() -> unsigned { - return cb(data); + return cb(data, session); }); } @@ -170,9 +170,9 @@ void cc_mqttsn_gw_session_set_send_data_to_client_cb( } reinterpret_cast(session.obj)->setSendDataClientReqCb( - [cb, data](const std::uint8_t* buf, std::size_t bufLen) + [cb, data, session](const std::uint8_t* buf, std::size_t bufLen) { - cb(data, buf, static_cast(bufLen)); + cb(data, session, buf, static_cast(bufLen)); }); } @@ -187,9 +187,9 @@ void cc_mqttsn_gw_session_set_send_data_to_broker_cb( } reinterpret_cast(session.obj)->setSendDataBrokerReqCb( - [cb, data](const std::uint8_t* buf, std::size_t bufLen) + [cb, data, session](const std::uint8_t* buf, std::size_t bufLen) { - cb(data, buf, static_cast(bufLen)); + cb(data, session, buf, static_cast(bufLen)); }); } @@ -203,9 +203,9 @@ void cc_mqttsn_gw_session_set_term_req_cb( } reinterpret_cast(session.obj)->setTerminationReqCb( - [cb, data]() + [cb, data, session]() { - cb(data); + cb(data, session); }); } @@ -219,9 +219,9 @@ void cc_mqttsn_gw_session_set_broker_reconnect_req_cb( } reinterpret_cast(session.obj)->setBrokerReconnectReqCb( - [cb, data]() + [cb, data, session]() { - cb(data); + cb(data, session); }); } @@ -240,9 +240,9 @@ void cc_mqttsn_gw_session_set_client_connect_report_cb( } reinterpret_cast(session.obj)->setClientConnectedReportCb( - [cb, data](const std::string& clientId) + [cb, data, session](const std::string& clientId) { - cb(data, clientId.c_str()); + cb(data, session, clientId.c_str()); }); } @@ -261,12 +261,12 @@ void cc_mqttsn_gw_session_set_auth_info_req_cb( } reinterpret_cast(session.obj)->setAuthInfoReqCb( - [cb, data](const std::string& clientId) -> Session::AuthInfo + [cb, data, session](const std::string& clientId) -> Session::AuthInfo { const char* username = nullptr; const std::uint8_t* password = nullptr; unsigned passLen = 0U; - cb(data, clientId.c_str(), &username, &password, &passLen); + cb(data, session, clientId.c_str(), &username, &password, &passLen); Session::AuthInfo info; if (username != nullptr) { From da405277095658630333d741a7e28ad198326584 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 2 May 2024 07:53:52 +1000 Subject: [PATCH 006/106] Fixing unittests. --- gateway/test/Session.th | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/gateway/test/Session.th b/gateway/test/Session.th index 3d825cd6..71fb719c 100644 --- a/gateway/test/Session.th +++ b/gateway/test/Session.th @@ -1641,25 +1641,6 @@ void SessionTest::test5() State state; auto session = allocSession(state, handler); - doConnect(*session, state, handler); - - auto disconnectSnMsg = handler.prepareClientDisconnect(); - dataFromClient(*session, disconnectSnMsg, "DISCONNECT"); - verifySentToClient_DisconnectMsg(state, handler); - verifySentToBroker_DisconnectMsg(state, handler); - verifyTermReq(state); - verifyNoOtherEvent(state, handler); - - session = allocSession(state, handler); - doConnect(*session, state, handler); - - auto disconnectMsg = handler.prepareBrokerDisconnect(); - dataFromBroker(*session, disconnectMsg, "DISCONNECT"); - verifySentToClient_DisconnectMsg(state, handler); - verifyTermReq(state); - verifyNoOtherEvent(state, handler); - - session = allocSession(state, handler); doConnect(*session, state, handler); doBrokerDisconnect(*session); verifySentToClient_DisconnectMsg(state, handler); @@ -2464,16 +2445,6 @@ void SessionTest::test20() verifySentToClient_PingrespMsg(state, handler); verifyNoOtherEvent(state, handler); - auto bReq = handler.prepareBrokerPingreq(); - dataFromBroker(*session, bReq, "PINGREQ"); - verifySentToClient_PingreqMsg(state, handler); - verifyNoOtherEvent(state, handler); - - auto cResp = handler.prepareClientPingresp(); - dataFromClient(*session, cResp, "PINGRESP"); - verifySentToBroker_PingrespMsg(state, handler); - verifyNoOtherEvent(state, handler); - // should be ignored dataFromBroker(*session, bResp, "PINGRESP"); verifyNoOtherEvent(state, handler); From 6509181ea931e752b57cda395cf2c609e6eb7857 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 2 May 2024 08:13:49 +1000 Subject: [PATCH 007/106] Testing without sanitizers when using clang-11 in github actions. --- .github/workflows/actions_build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index 68d25119..f08df953 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -118,10 +118,11 @@ jobs: run: | cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{matrix.type}} -DCMAKE_CXX_STANDARD=${{matrix.cpp}} \ -DCMAKE_INSTALL_PREFIX=install -DCMAKE_PREFIX_PATH=${{runner.workspace}}/build/install \ - -DCC_MQTTSN_BUILD_UNIT_TESTS=ON -DCC_MQTTSN_WITH_SANITIZERS=ON + -DCC_MQTTSN_BUILD_UNIT_TESTS=ON -DCC_MQTTSN_WITH_SANITIZERS=${{env.WITH_SANITIZERS}} env: CC: clang-${{matrix.cc_ver}} CXX: clang++-${{matrix.cc_ver}} + WITH_SANITIZERS: "${{ (matrix.cc_ver == 11) && 'OFF' || 'ON' }}" - name: Build working-directory: ${{runner.workspace}}/build From b07de2103c542503899a6758d9b012b75e2b1b1c Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 2 May 2024 08:48:44 +1000 Subject: [PATCH 008/106] Requiring and itegrating v2.7.1 of the cc.mqttsn.generated. --- .github/workflows/actions_build.yml | 2 +- client/src/basic/BasicClient.h | 6 +++--- client/test/DataProcessor.h | 31 +++-------------------------- gateway/src/lib/common.h | 2 +- gateway/src/lib/messages.h | 6 +++--- 5 files changed, 11 insertions(+), 36 deletions(-) diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index f08df953..99d25540 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -4,7 +4,7 @@ on: [push] env: COMMS_TAG: v5.2.3 - CC_MQTTSN_TAG: v2.7 + CC_MQTTSN_TAG: develop CC_MQTT311_TAG: v2.7 jobs: diff --git a/client/src/basic/BasicClient.h b/client/src/basic/BasicClient.h index 8bd0eaad..b471b4bf 100644 --- a/client/src/basic/BasicClient.h +++ b/client/src/basic/BasicClient.h @@ -19,12 +19,12 @@ #include "cc_mqttsn/Version.h" #include "cc_mqttsn/Message.h" #include "cc_mqttsn/frame/Frame.h" -#include "cc_mqttsn/input/ClientInputMessages.h" +#include "cc_mqttsn/input/ProtClientInputMessages.h" #include "cc_mqttsn/options/ClientDefaultOptions.h" #include "cc_mqttsn_client/common.h" #include "details/WriteBufStorageType.h" -static_assert(COMMS_MAKE_VERSION(2, 7, 0) <= cc_mqttsn::version(), +static_assert(COMMS_MAKE_VERSION(2, 7, 1) <= cc_mqttsn::version(), "The version of cc.mqttsn.generated library is too old"); namespace cc_mqttsn_client @@ -1798,7 +1798,7 @@ class BasicClient > >::Type OpStorageType; - using InputMessages = cc_mqttsn::input::ClientInputMessages; + using InputMessages = cc_mqttsn::input::ProtClientInputMessages; typedef cc_mqttsn::frame::Frame ProtStack; typedef typename ProtStack::MsgPtr MsgPtr; diff --git a/client/test/DataProcessor.h b/client/test/DataProcessor.h index f52695ca..135dcd55 100644 --- a/client/test/DataProcessor.h +++ b/client/test/DataProcessor.h @@ -14,6 +14,7 @@ #include "comms/comms.h" #include "cc_mqttsn/frame/Frame.h" +#include "cc_mqttsn/input/ProtMessages.h" #include "cc_mqttsn/Message.h" #include "cc_mqttsn_client/common.h" @@ -27,7 +28,7 @@ typedef cc_mqttsn::Message< comms::option::LengthInfoInterface > TestMessage; -typedef cc_mqttsn::input::AllMessages AllTestMessages; +typedef cc_mqttsn::input::ProtMessages AllTestMessages; class DataProcessor : public comms::GenericHandler { @@ -36,33 +37,7 @@ class DataProcessor : public comms::GenericHandler typedef std::vector DataBuf; using TopicIdTypeVal = cc_mqttsn::field::TopicIdTypeVal; - typedef cc_mqttsn::message::Advertise AdvertiseMsg; - typedef cc_mqttsn::message::Searchgw SearchgwMsg; - typedef cc_mqttsn::message::Gwinfo GwinfoMsg; - typedef cc_mqttsn::message::Connect ConnectMsg; - typedef cc_mqttsn::message::Connack ConnackMsg; - typedef cc_mqttsn::message::Willtopicreq WilltopicreqMsg; - typedef cc_mqttsn::message::Willtopic WilltopicMsg; - typedef cc_mqttsn::message::Willmsgreq WillmsgreqMsg; - typedef cc_mqttsn::message::Willmsg WillmsgMsg; - typedef cc_mqttsn::message::Register RegisterMsg; - typedef cc_mqttsn::message::Regack RegackMsg; - typedef cc_mqttsn::message::Publish PublishMsg; - typedef cc_mqttsn::message::Puback PubackMsg; - typedef cc_mqttsn::message::Pubrec PubrecMsg; - typedef cc_mqttsn::message::Pubrel PubrelMsg; - typedef cc_mqttsn::message::Pubcomp PubcompMsg; - typedef cc_mqttsn::message::Subscribe SubscribeMsg; - typedef cc_mqttsn::message::Suback SubackMsg; - typedef cc_mqttsn::message::Unsubscribe UnsubscribeMsg; - typedef cc_mqttsn::message::Unsuback UnsubackMsg; - typedef cc_mqttsn::message::Pingreq PingreqMsg; - typedef cc_mqttsn::message::Pingresp PingrespMsg; - typedef cc_mqttsn::message::Disconnect DisconnectMsg; - typedef cc_mqttsn::message::Willtopicupd WilltopicupdMsg; - typedef cc_mqttsn::message::Willmsgupd WillmsgupdMsg; - typedef cc_mqttsn::message::Willtopicresp WilltopicrespMsg; - typedef cc_mqttsn::message::Willmsgresp WillmsgrespMsg; + CC_MQTTSN_ALIASES_FOR_PROT_MESSAGES_DEFAULT_OPTIONS(, Msg, TestMessage); virtual ~DataProcessor(); diff --git a/gateway/src/lib/common.h b/gateway/src/lib/common.h index d7b18156..068eca7f 100644 --- a/gateway/src/lib/common.h +++ b/gateway/src/lib/common.h @@ -20,7 +20,7 @@ #include "RegMgr.h" -static_assert(COMMS_MAKE_VERSION(2, 7, 0) <= cc_mqttsn::version(), +static_assert(COMMS_MAKE_VERSION(2, 7, 1) <= cc_mqttsn::version(), "The version of cc.mqttsn.generated library is too old"); static_assert(COMMS_MAKE_VERSION(2, 7, 0) <= cc_mqtt311::version(), diff --git a/gateway/src/lib/messages.h b/gateway/src/lib/messages.h index 2f25f280..490e0569 100644 --- a/gateway/src/lib/messages.h +++ b/gateway/src/lib/messages.h @@ -10,7 +10,7 @@ #include "cc_mqttsn/Message.h" #include "cc_mqttsn/frame/Frame.h" #include "cc_mqttsn/input/AllMessages.h" -#include "cc_mqttsn/input/ServerInputMessages.h" +#include "cc_mqttsn/input/GwServerInputMessages.h" #include "cc_mqttsn/options/ServerDefaultOptions.h" #include "cc_mqttsn/options/DataViewDefaultOptions.h" #include "cc_mqtt311/Message.h" @@ -55,10 +55,10 @@ using MqttsnGwOptions = CC_MQTTSN_ALIASES_FOR_ALL_MESSAGES(, Msg_SN, MqttsnMessage, MqttsnGwOptions) template -using InputMqttsnMessages = cc_mqttsn::input::ServerInputMessages; +using InputMqttsnMessages = cc_mqttsn::input::GwServerInputMessages; using MqttsnFrame = - cc_mqttsn::frame::Frame >; + cc_mqttsn::frame::Frame, MqttsnGwOptions>; using Mqtt311GwOptions = cc_mqtt311::options::ClientDefaultOptions; From abfef11083ec8df283416824eba362af5441f6eb Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 7 May 2024 08:29:09 +1000 Subject: [PATCH 009/106] Preparation for the forwarding encapsulation support. --- gateway/include/cc_mqttsn_gateway/Session.h | 24 ++-- gateway/src/lib/CMakeLists.txt | 3 +- gateway/src/lib/Session.cpp | 10 ++ gateway/src/lib/SessionImpl.cpp | 137 ++++++++++++-------- gateway/src/lib/SessionImpl.h | 64 ++++----- gateway/src/lib/common.h | 29 ++--- gateway/src/lib/session_op/Encapsulate.cpp | 91 +++++++++++++ gateway/src/lib/session_op/Encapsulate.h | 53 ++++++++ gateway/src/lib/session_op/Forward.cpp | 2 + gateway/src/lib/session_op/Forward.h | 5 +- 10 files changed, 305 insertions(+), 113 deletions(-) create mode 100644 gateway/src/lib/session_op/Encapsulate.cpp create mode 100644 gateway/src/lib/session_op/Encapsulate.h diff --git a/gateway/include/cc_mqttsn_gateway/Session.h b/gateway/include/cc_mqttsn_gateway/Session.h index f1c73fc2..ece3ad1c 100644 --- a/gateway/include/cc_mqttsn_gateway/Session.h +++ b/gateway/include/cc_mqttsn_gateway/Session.h @@ -30,18 +30,18 @@ class Session public: /// @brief Type for buffer of binary data. - typedef std::vector BinaryData; + using BinaryData = std::vector; /// @brief Type of authentication information. /// @details The first element of the pair is @b username, and the /// second element of the pair is binary @b password. - typedef std::pair AuthInfo; + using AuthInfo = std::pair; /// @brief Type of callback, used to request new time measurement. /// @details When the requested time is due, the driving code is expected /// to call tick() member function. /// @param[in] value Number of @b milliseconds to measure. - typedef std::function NextTickProgramReqCb; + using NextTickProgramReqCb = std::function; /// @brief Type of callback, used to cancel existing time measurement. /// @details When invoked the existing time measurement needs to be cancelled. @@ -49,37 +49,40 @@ class Session /// since last timer programming request. /// @return Number of elapsed @b milliseconds since last timer programming /// request. - typedef std::function CancelTickWaitReqCb; + using CancelTickWaitReqCb = std::function; /// @brief Type of callback, used to request delivery of serialised message /// to the client or broker. /// @param[in] buf Buffer containing serialised message. /// @param[in] bufSize Number of bytes in the buffer - typedef std::function SendDataReqCb; + using SendDataReqCb = std::function; /// @brief Type of callback, used to request session termination. /// @details When the callback is invoked, the driving code must flush /// all the previously sent messages to appropriate I/O links and /// delete this session object. - typedef std::function TerminationReqCb; + using TerminationReqCb = std::function; /// @brief Type of callback used to request reconnection to the broker. /// @details When the callback is invoked, the driving code must close /// existing TCP/IP connection to the broker and create a new one. - typedef std::function BrokerReconnectReqCb; + using BrokerReconnectReqCb = std::function; /// @brief Type of callback used to report client ID of the newly connected /// MQTT-SN client. /// @details The callback can be used to provide additional client specific /// information, such as predefined topic IDs. /// @param[in] clientId Client ID - typedef std::function ClientConnectedReportCb; + using ClientConnectedReportCb = std::function; /// @brief Type of callback used to request authentication information of /// the client that is trying connect. /// @param[in] clientId Client ID /// @return Authentication information - typedef std::function AuthInfoReqCb; + using AuthInfoReqCb = std::function; + + using FwdEncSessionCreatedReportCb = std::function; + using FwdEncSessionDeletedReportCb = std::function; /// @brief Default constructor Session(); @@ -144,6 +147,9 @@ class Session /// @param[in] func R-value reference to the callback object void setAuthInfoReqCb(AuthInfoReqCb&& func); + void setFwdEncSessionCreatedReportCb(FwdEncSessionCreatedReportCb&& func); + void setFwdEncSessionDeletedReportCb(FwdEncSessionDeletedReportCb&& func); + /// @brief Set gateway numeric ID to be reported when requested. /// @details If not set, default value 0 is assumed. /// @param[in] value Gateway numeric ID. diff --git a/gateway/src/lib/CMakeLists.txt b/gateway/src/lib/CMakeLists.txt index 8d51a641..f6f20a71 100644 --- a/gateway/src/lib/CMakeLists.txt +++ b/gateway/src/lib/CMakeLists.txt @@ -16,9 +16,10 @@ function (lib_mqttsn_gateway) session_op/Disconnect.cpp session_op/Asleep.cpp session_op/AsleepMonitor.cpp + session_op/Encapsulate.cpp + session_op/Forward.cpp session_op/PubRecv.cpp session_op/PubSend.cpp - session_op/Forward.cpp session_op/WillUpdate.cpp ) diff --git a/gateway/src/lib/Session.cpp b/gateway/src/lib/Session.cpp index 8cbc7aab..c4477b8c 100644 --- a/gateway/src/lib/Session.cpp +++ b/gateway/src/lib/Session.cpp @@ -58,6 +58,16 @@ void Session::setAuthInfoReqCb(AuthInfoReqCb&& func) m_pImpl->setAuthInfoReqCb(std::move(func)); } +void Session::setFwdEncSessionCreatedReportCb(FwdEncSessionCreatedReportCb&& func) +{ + m_pImpl->setFwdEncSessionCreatedReportCb(std::move(func)); +} + +void Session::setFwdEncSessionDeletedReportCb(FwdEncSessionDeletedReportCb&& func) +{ + m_pImpl->setFwdEncSessionDeletedReportCb(std::move(func)); +} + void Session::setGatewayId(std::uint8_t value) { m_pImpl->setGatewayId(value); diff --git a/gateway/src/lib/SessionImpl.cpp b/gateway/src/lib/SessionImpl.cpp index 0dfb7905..2cce1053 100644 --- a/gateway/src/lib/SessionImpl.cpp +++ b/gateway/src/lib/SessionImpl.cpp @@ -12,10 +12,13 @@ #include #include +#include "comms/process.h" + #include "session_op/Connect.h" #include "session_op/Disconnect.h" #include "session_op/Asleep.h" #include "session_op/AsleepMonitor.h" +#include "session_op/Encapsulate.h" #include "session_op/PubRecv.h" #include "session_op/PubSend.h" #include "session_op/Forward.h" @@ -31,64 +34,18 @@ const unsigned NoTimeout = std::numeric_limits::max(); } // namespace -template -std::size_t SessionImpl::processInputData(const std::uint8_t* buf, std::size_t len, TStack& stack) -{ - if ((!isRunning()) || m_state.m_terminating) { - return 0U; - } - - auto guard = apiCall(); - const std::uint8_t* bufTmp = buf; - while (true) { - typename TStack::MsgPtr msg; - - typedef typename TStack::MsgPtr::element_type MsgType; - auto iter = comms::readIteratorFor(bufTmp); - auto consumedBytes = - static_cast(std::distance(buf, bufTmp)); - assert(consumedBytes <= len); - auto remLen = len - consumedBytes; - if (remLen == 0U) { - break; - } - - auto es = stack.read(msg, iter, remLen); - if (es == comms::ErrorStatus::NotEnoughData) { - break; - } - - if (es == comms::ErrorStatus::ProtocolError) { - ++bufTmp; - continue; - } - - if (es == comms::ErrorStatus::Success) { - assert(msg); - m_state.m_lastMsgTimestamp = m_state.m_timestamp; - msg->dispatch(*this); - } - - bufTmp = iter; - } - - auto consumed = static_cast(std::distance(buf, bufTmp)); - assert(consumed <= len); - return consumed; -} - -template -void SessionImpl::sendMessage(const TMsg& msg, TStack& stack, SendDataReqCb& func, DataBuf& buf) +template +void SessionImpl::sendMessage(const TMsg& msg, TFrame& frame, SendDataReqCb& func, DataBuf& buf) { if (!func) { return; } - typedef typename TStack::MsgPtr::element_type MsgType; + typedef typename TFrame::MsgPtr::element_type MsgType; - buf.resize(std::max(buf.size(), stack.length(msg))); + buf.resize(std::max(buf.size(), frame.length(msg))); auto iter = comms::writeIteratorFor(&buf[0]); - [[maybe_unused]] auto es = stack.write(msg, iter, buf.size()); + [[maybe_unused]] auto es = frame.write(msg, iter, buf.size()); assert(es == comms::ErrorStatus::Success); auto writtenCount = static_cast( @@ -133,12 +90,35 @@ SessionImpl::SessionImpl() m_ops.emplace_back(new session_op::PubSend(m_state)); m_ops.emplace_back(new session_op::Forward(m_state)); m_ops.emplace_back(new session_op::WillUpdate(m_state)); + m_ops.emplace_back(new session_op::Encapsulate(m_state)); + m_encapsulateOp = static_cast(m_ops.back().get()); for (auto& op : m_ops) { startOp(*op); } } +bool SessionImpl::start() +{ + if ((m_state.m_running) || + (!m_nextTickProgramCb) || + (!m_cancelTickCb) || + (!m_sendToClientCb) || + (!m_sendToBrokerCb) || + (!m_termReqCb) || + (!m_brokerReconnectReqCb)) { + return false; + } + + if (static_cast(m_fwdEncSessionCreatedReportCb) != static_cast(m_fwdEncSessionDeletedReportCb)) { + return false; + } + + m_state.m_running = true; + return true; +} + + void SessionImpl::tick() { if ((!isRunning()) || m_state.m_terminating) { @@ -154,12 +134,63 @@ void SessionImpl::tick() std::size_t SessionImpl::dataFromClient(const std::uint8_t* buf, std::size_t len) { - return processInputData(buf, len, m_mqttsnFrame); + if ((!isRunning()) || m_state.m_terminating) { + return 0U; + } + + auto guard = apiCall(); + const std::uint8_t* bufTmp = buf; + while (true) { + auto consumedBytes = + static_cast(std::distance(buf, bufTmp)); + assert(consumedBytes <= len); + auto remLen = len - consumedBytes; + if (remLen == 0U) { + m_state.m_encapsulatedMsg = false; // Just in case + break; + } + + if (m_state.m_encapsulatedMsg) { + bufTmp += m_encapsulateOp->encapsulatedData(bufTmp, remLen); + continue; + } + + using MsgPtr = typename MqttsnFrame::MsgPtr; + using MsgType = typename MsgPtr::element_type; + MsgPtr msg; + auto iter = comms::readIteratorFor(bufTmp); + auto es = m_mqttsnFrame.read(msg, iter, remLen); + if (es == comms::ErrorStatus::NotEnoughData) { + break; + } + + if (es == comms::ErrorStatus::ProtocolError) { + ++bufTmp; + continue; + } + + if (es == comms::ErrorStatus::Success) { + assert(msg); + m_state.m_lastMsgTimestamp = m_state.m_timestamp; + msg->dispatch(*this); + } + + bufTmp = iter; + } + + auto consumed = static_cast(std::distance(buf, bufTmp)); + assert(consumed <= len); + return consumed; } std::size_t SessionImpl::dataFromBroker(const std::uint8_t* buf, std::size_t len) { - return processInputData(buf, len, m_mqttFrame); + if ((!isRunning()) || m_state.m_terminating) { + return 0U; + } + + auto guard = apiCall(); + return comms::processAllWithDispatch(buf, len, m_mqttFrame, *this); } void SessionImpl::setBrokerConnected(bool connected) diff --git a/gateway/src/lib/SessionImpl.h b/gateway/src/lib/SessionImpl.h index ed20d5fe..659a871a 100644 --- a/gateway/src/lib/SessionImpl.h +++ b/gateway/src/lib/SessionImpl.h @@ -16,6 +16,7 @@ #include "cc_mqttsn_gateway/Session.h" #include "MsgHandler.h" #include "SessionOp.h" +#include "session_op/Encapsulate.h" #include "common.h" #include "comms/util/ScopeGuard.h" @@ -24,17 +25,18 @@ namespace cc_mqttsn_gateway class SessionImpl : public MsgHandler { - typedef MsgHandler Base; + using Base = MsgHandler; public: - typedef Session::AuthInfo AuthInfo; - - typedef Session::NextTickProgramReqCb NextTickProgramReqCb; - typedef Session::SendDataReqCb SendDataReqCb; - typedef Session::CancelTickWaitReqCb CancelTickWaitReqCb; - typedef Session::TerminationReqCb TerminationReqCb; - typedef Session::BrokerReconnectReqCb BrokerReconnectReqCb; - typedef Session::ClientConnectedReportCb ClientConnectedReportCb; - typedef Session::AuthInfoReqCb AuthInfoReqCb; + using AuthInfo = Session::AuthInfo; + using NextTickProgramReqCb = Session::NextTickProgramReqCb; + using SendDataReqCb = Session::SendDataReqCb; + using CancelTickWaitReqCb = Session::CancelTickWaitReqCb; + using TerminationReqCb = Session::TerminationReqCb; + using BrokerReconnectReqCb = Session::BrokerReconnectReqCb; + using ClientConnectedReportCb = Session::ClientConnectedReportCb; + using AuthInfoReqCb = Session::AuthInfoReqCb; + using FwdEncSessionCreatedReportCb = Session::FwdEncSessionCreatedReportCb; + using FwdEncSessionDeletedReportCb = Session::FwdEncSessionDeletedReportCb; SessionImpl(); ~SessionImpl() = default; @@ -82,12 +84,24 @@ class SessionImpl : public MsgHandler m_clientConnectedCb = std::forward(func); } - template + template void setAuthInfoReqCb(TFunc&& func) { m_authInfoReqCb = std::forward(func); } + template + void setFwdEncSessionCreatedReportCb(TFunc&& func) + { + m_fwdEncSessionCreatedReportCb = std::forward(func); + } + + template + void setFwdEncSessionDeletedReportCb(TFunc&& func) + { + m_fwdEncSessionDeletedReportCb = std::forward(func); + } + void setGatewayId(std::uint8_t value) { m_state.m_gwId = value; @@ -118,21 +132,7 @@ class SessionImpl : public MsgHandler m_state.m_pubOnlyKeepAlive = value; } - bool start() - { - if ((m_state.m_running) || - (!m_nextTickProgramCb) || - (!m_cancelTickCb) || - (!m_sendToClientCb) || - (!m_sendToBrokerCb) || - (!m_termReqCb) || - (!m_brokerReconnectReqCb)) { - return false; - } - - m_state.m_running = true; - return true; - } + bool start(); void stop() { @@ -156,7 +156,7 @@ class SessionImpl : public MsgHandler private: using ReturnCodeVal = cc_mqttsn::field::ReturnCodeVal; - typedef std::vector OpsList; + using OpsList = std::vector; using Base::handle; virtual void handle(SearchgwMsg_SN& msg) override; @@ -165,11 +165,8 @@ class SessionImpl : public MsgHandler virtual void handle(MqttMessage& msg) override; - template - std::size_t processInputData(const std::uint8_t* buf, std::size_t len, TStack& stack); - - template - void sendMessage(const TMsg& msg, TStack& stack, SendDataReqCb& func, DataBuf& buf); + template + void sendMessage(const TMsg& msg, TFrame& frame, SendDataReqCb& func, DataBuf& buf); template void dispatchToOpsCommon(TMsg& msg); @@ -199,6 +196,8 @@ class SessionImpl : public MsgHandler BrokerReconnectReqCb m_brokerReconnectReqCb; ClientConnectedReportCb m_clientConnectedCb; AuthInfoReqCb m_authInfoReqCb; + FwdEncSessionCreatedReportCb m_fwdEncSessionCreatedReportCb; + FwdEncSessionDeletedReportCb m_fwdEncSessionDeletedReportCb; MqttsnFrame m_mqttsnFrame; MqttFrame m_mqttFrame; @@ -207,6 +206,7 @@ class SessionImpl : public MsgHandler DataBuf m_mqttMsgData; OpsList m_ops; + session_op::Encapsulate* m_encapsulateOp = nullptr; SessionState m_state; }; diff --git a/gateway/src/lib/common.h b/gateway/src/lib/common.h index 068eca7f..1caf8b93 100644 --- a/gateway/src/lib/common.h +++ b/gateway/src/lib/common.h @@ -121,29 +121,28 @@ struct SessionState unsigned m_retryPeriod = DefaultRetryPeriod; unsigned m_retryCount = DefaultRetryCount; unsigned m_tickReq = 0U; - bool m_running = false; - bool m_brokerConnected = false; - bool m_reconnectingBroker = false; - bool m_terminating = false; - bool m_pendingClientDisconnect = false; - bool m_clientConnectReported = false; + unsigned m_callStackCount = 0U; Timestamp m_timestamp = InitialTimestamp; Timestamp m_lastMsgTimestamp = InitialTimestamp; - unsigned m_callStackCount = 0U; - - ConnectionStatus m_connStatus = ConnectionStatus::Disconnected; + std::size_t m_sleepPubAccLimit = std::numeric_limits::max(); std::string m_clientId; std::string m_defaultClientId; - WillInfo m_will; - std::size_t m_sleepPubAccLimit = std::numeric_limits::max(); - std::uint16_t m_keepAlive = 0U; - std::uint16_t m_pubOnlyKeepAlive = DefaultKeepAlive; - std::uint8_t m_gwId = 0U; std::string m_username; DataBuf m_password; - + WillInfo m_will; std::list m_brokerPubs; RegMgr m_regMgr; + ConnectionStatus m_connStatus = ConnectionStatus::Disconnected; + std::uint16_t m_keepAlive = 0U; + std::uint16_t m_pubOnlyKeepAlive = DefaultKeepAlive; + std::uint8_t m_gwId = 0U; + bool m_running = false; + bool m_brokerConnected = false; + bool m_reconnectingBroker = false; + bool m_terminating = false; + bool m_pendingClientDisconnect = false; + bool m_clientConnectReported = false; + bool m_encapsulatedMsg = false; }; } // namespace cc_mqttsn_gateway diff --git a/gateway/src/lib/session_op/Encapsulate.cpp b/gateway/src/lib/session_op/Encapsulate.cpp new file mode 100644 index 00000000..a44d6ede --- /dev/null +++ b/gateway/src/lib/session_op/Encapsulate.cpp @@ -0,0 +1,91 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "Encapsulate.h" + +#include +#include + +#include "comms/util/assign.h" +#include "comms/util/ScopeGuard.h" + +namespace cc_mqttsn_gateway +{ + +namespace session_op +{ + +Encapsulate::Encapsulate(SessionState& sessionState) + : Base(sessionState) +{ +} + +Encapsulate::~Encapsulate() = default; + +std::size_t Encapsulate::encapsulatedData(const std::uint8_t* buf, std::size_t len) +{ + auto& st = state(); + assert(st.m_encapsulatedMsg); + st.m_encapsulatedMsg = false; + + auto onExit = + comms::util::makeScopeGuard( + [this]() + { + m_selectedSession = nullptr; + }); + + if (m_selectedSession == nullptr) { + return len; + } + + return m_selectedSession->dataFromClient(buf, len); + return 0U; +} + +void Encapsulate::handle(FwdMsg_SN& msg) +{ + auto& st = state(); + assert(!st.m_encapsulatedMsg); + st.m_encapsulatedMsg = true; + + auto& nodeIdVec = msg.field_data().value(); + NodeId nodeId; + comms::util::assign(nodeId, nodeIdVec.begin(), nodeIdVec.end()); + + auto iter = m_sessions.find(nodeId); + do { + if (iter != m_sessions.end()) { + break; + } + + std::tie(iter, std::ignore) = m_sessions.insert(std::make_pair(std::move(nodeId), std::make_unique())); + assert(iter != m_sessions.end()); + auto& session = *(iter->second); + + // TODO: callbacks + + if (!session.start()) { + // Error failed to start session; + assert(false); // Should not happen + m_sessions.erase(iter); + iter = m_sessions.end(); + break; + } + + // TODO: report session for app management + + } while (false); + + if (iter != m_sessions.end()) { + m_selectedSession = iter->second.get(); + } +} + +} // namespace session_op + +} // namespace cc_mqttsn_gateway diff --git a/gateway/src/lib/session_op/Encapsulate.h b/gateway/src/lib/session_op/Encapsulate.h new file mode 100644 index 00000000..17e5ebb0 --- /dev/null +++ b/gateway/src/lib/session_op/Encapsulate.h @@ -0,0 +1,53 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include +#include +#include +#include +#include + +#include "cc_mqttsn_gateway/Session.h" + +#include "SessionOp.h" +#include "common.h" + +namespace cc_mqttsn_gateway +{ + +namespace session_op +{ + +class Encapsulate : public SessionOp +{ + using Base = SessionOp; + +public: + Encapsulate(SessionState& sessionState); + ~Encapsulate(); + + std::size_t encapsulatedData(const std::uint8_t* buf, std::size_t len); + +protected: + +private: + using NodeId = std::vector; + using SessionPtr = std::unique_ptr; + using SessionMap = std::map; + + using Base::handle; + virtual void handle(FwdMsg_SN& msg) override; + + SessionMap m_sessions; + Session* m_selectedSession = nullptr; +}; + +} // namespace session_op + +} // namespace cc_mqttsn_gateway diff --git a/gateway/src/lib/session_op/Forward.cpp b/gateway/src/lib/session_op/Forward.cpp index 6e1c99b3..6113ba77 100644 --- a/gateway/src/lib/session_op/Forward.cpp +++ b/gateway/src/lib/session_op/Forward.cpp @@ -10,6 +10,8 @@ #include #include +#include "comms/util/ScopeGuard.h" + namespace cc_mqttsn_gateway { diff --git a/gateway/src/lib/session_op/Forward.h b/gateway/src/lib/session_op/Forward.h index 80bce8c9..8a746499 100644 --- a/gateway/src/lib/session_op/Forward.h +++ b/gateway/src/lib/session_op/Forward.h @@ -10,7 +10,6 @@ #include #include -#include "comms/util/ScopeGuard.h" #include "SessionOp.h" #include "common.h" @@ -72,10 +71,10 @@ class Forward : public SessionOp ReturnCodeVal rc); - std::uint16_t m_lastPubTopicId = 0; - bool m_pingInProgress = false; SubsInProgressList m_subs; NoGwPubInfosList m_pubs; + std::uint16_t m_lastPubTopicId = 0; + bool m_pingInProgress = false; }; } // namespace session_op From 363095d6b900045c7c73bca9171a5fb340e18267 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 8 May 2024 09:36:00 +1000 Subject: [PATCH 010/106] Added support for forwarding encapsulation. --- gateway/src/lib/SessionImpl.cpp | 38 +++++++--- gateway/src/lib/SessionImpl.h | 15 ++++ gateway/src/lib/SessionOp.cpp | 41 +++++++++++ gateway/src/lib/SessionOp.h | 45 ++++-------- gateway/src/lib/session_op/Asleep.cpp | 4 +- gateway/src/lib/session_op/Asleep.h | 2 +- gateway/src/lib/session_op/AsleepMonitor.cpp | 4 +- gateway/src/lib/session_op/AsleepMonitor.h | 2 +- gateway/src/lib/session_op/Connect.cpp | 4 +- gateway/src/lib/session_op/Connect.h | 2 +- gateway/src/lib/session_op/Disconnect.cpp | 4 +- gateway/src/lib/session_op/Disconnect.h | 2 +- gateway/src/lib/session_op/Encapsulate.cpp | 74 +++++++++++++++++--- gateway/src/lib/session_op/Encapsulate.h | 7 +- gateway/src/lib/session_op/Forward.cpp | 4 +- gateway/src/lib/session_op/Forward.h | 2 +- gateway/src/lib/session_op/PubRecv.cpp | 4 +- gateway/src/lib/session_op/PubRecv.h | 2 +- gateway/src/lib/session_op/PubSend.cpp | 4 +- gateway/src/lib/session_op/PubSend.h | 2 +- gateway/src/lib/session_op/WillUpdate.cpp | 4 +- gateway/src/lib/session_op/WillUpdate.h | 2 +- 22 files changed, 194 insertions(+), 74 deletions(-) diff --git a/gateway/src/lib/SessionImpl.cpp b/gateway/src/lib/SessionImpl.cpp index 2cce1053..48d88901 100644 --- a/gateway/src/lib/SessionImpl.cpp +++ b/gateway/src/lib/SessionImpl.cpp @@ -64,7 +64,7 @@ void SessionImpl::dispatchToOpsCommon(TMsg& msg) SessionImpl::SessionImpl() { - std::unique_ptr connectOp(new session_op::Connect(m_state)); + std::unique_ptr connectOp(new session_op::Connect(*this)); connectOp->setClientConnectedReportCb( [this](const std::string& clientId) { @@ -83,14 +83,14 @@ SessionImpl::SessionImpl() }); m_ops.push_back(std::move(connectOp)); - m_ops.emplace_back(new session_op::Disconnect(m_state)); - m_ops.emplace_back(new session_op::Asleep(m_state)); - m_ops.emplace_back(new session_op::AsleepMonitor(m_state)); - m_ops.emplace_back(new session_op::PubRecv(m_state)); - m_ops.emplace_back(new session_op::PubSend(m_state)); - m_ops.emplace_back(new session_op::Forward(m_state)); - m_ops.emplace_back(new session_op::WillUpdate(m_state)); - m_ops.emplace_back(new session_op::Encapsulate(m_state)); + m_ops.emplace_back(new session_op::Disconnect(*this)); + m_ops.emplace_back(new session_op::Asleep(*this)); + m_ops.emplace_back(new session_op::AsleepMonitor(*this)); + m_ops.emplace_back(new session_op::PubRecv(*this)); + m_ops.emplace_back(new session_op::PubSend(*this)); + m_ops.emplace_back(new session_op::Forward(*this)); + m_ops.emplace_back(new session_op::WillUpdate(*this)); + m_ops.emplace_back(new session_op::Encapsulate(*this)); m_encapsulateOp = static_cast(m_ops.back().get()); for (auto& op : m_ops) { @@ -220,6 +220,26 @@ bool SessionImpl::setTopicIdAllocationRange(std::uint16_t minVal, std::uint16_t return m_state.m_regMgr.setTopicIdAllocationRange(minVal, maxVal); } +void SessionImpl::reportFwdEncSessionCreated(Session* session) +{ + assert(m_fwdEncSessionCreatedReportCb); + assert(session != nullptr); + m_fwdEncSessionCreatedReportCb(session); +} + +void SessionImpl::reportFwdEncSessionDeleted(Session* session) +{ + assert(m_fwdEncSessionDeletedReportCb); + assert(session != nullptr); + m_fwdEncSessionDeletedReportCb(session); +} + +void SessionImpl::sendDataToClient(const std::uint8_t* buf, std::size_t bufLen) +{ + assert(m_sendToClientCb); + m_sendToClientCb(buf, bufLen); +} + void SessionImpl::handle([[maybe_unused]] SearchgwMsg_SN& msg) { GwinfoMsg_SN respMsg; diff --git a/gateway/src/lib/SessionImpl.h b/gateway/src/lib/SessionImpl.h index 659a871a..24654324 100644 --- a/gateway/src/lib/SessionImpl.h +++ b/gateway/src/lib/SessionImpl.h @@ -153,6 +153,21 @@ class SessionImpl : public MsgHandler bool addPredefinedTopic(const std::string& topic, std::uint16_t topicId); bool setTopicIdAllocationRange(std::uint16_t minVal, std::uint16_t maxVal); + // API used by ops + SessionState& state() + { + return m_state; + } + + bool hasFwdEncSupport() const + { + return static_cast(m_fwdEncSessionCreatedReportCb); + } + + void reportFwdEncSessionCreated(Session* session); + void reportFwdEncSessionDeleted(Session* session); + void sendDataToClient(const std::uint8_t* buf, std::size_t bufLen); + private: using ReturnCodeVal = cc_mqttsn::field::ReturnCodeVal; diff --git a/gateway/src/lib/SessionOp.cpp b/gateway/src/lib/SessionOp.cpp index fca6572a..1d596a85 100644 --- a/gateway/src/lib/SessionOp.cpp +++ b/gateway/src/lib/SessionOp.cpp @@ -7,9 +7,38 @@ #include "SessionOp.h" +#include "SessionImpl.h" + namespace cc_mqttsn_gateway { +void SessionOp::timestampUpdated() +{ + if ((m_nextTickTimestamp != 0) && + (m_nextTickTimestamp <= state().m_timestamp)) { + m_nextTickTimestamp = 0; + tickImpl(); + } +} + +unsigned SessionOp::nextTick() +{ + if (m_nextTickTimestamp == 0) { + return std::numeric_limits::max(); + } + + if (m_nextTickTimestamp <= state().m_timestamp) { + return 1U; + } + + return static_cast(m_nextTickTimestamp - state().m_timestamp); +} + +SessionState& SessionOp::state() +{ + return m_session.state(); +} + void SessionOp::sendDisconnectToClient() { DisconnectMsg_SN msg; @@ -20,4 +49,16 @@ void SessionOp::sendDisconnectToClient() } +void SessionOp::tickImpl() +{ +} + +void SessionOp::startImpl() +{ +} + +void SessionOp::brokerConnectionUpdatedImpl() +{ +} + } // namespace cc_mqttsn_gateway diff --git a/gateway/src/lib/SessionOp.h b/gateway/src/lib/SessionOp.h index f9b3fe21..6077e0c1 100644 --- a/gateway/src/lib/SessionOp.h +++ b/gateway/src/lib/SessionOp.h @@ -18,6 +18,7 @@ namespace cc_mqttsn_gateway { +class SessionImpl; class SessionOp : public MsgHandler { typedef MsgHandler Base; @@ -55,27 +56,9 @@ class SessionOp : public MsgHandler m_brokerReconnectReqFunc = std::forward(func); } - void timestampUpdated() - { - if ((m_nextTickTimestamp != 0) && - (m_nextTickTimestamp <= m_state.m_timestamp)) { - m_nextTickTimestamp = 0; - tickImpl(); - } - } - - unsigned nextTick() - { - if (m_nextTickTimestamp == 0) { - return std::numeric_limits::max(); - } - - if (m_nextTickTimestamp <= m_state.m_timestamp) { - return 1U; - } - - return static_cast(m_nextTickTimestamp - m_state.m_timestamp); - } + void timestampUpdated(); + + unsigned nextTick(); void start() { @@ -88,8 +71,8 @@ class SessionOp : public MsgHandler } protected: - SessionOp(SessionState& state) - : m_state(state) + SessionOp(SessionImpl& session) + : m_session(session) { } @@ -119,7 +102,7 @@ class SessionOp : public MsgHandler void nextTickReq(unsigned ms) { - m_nextTickTimestamp = m_state.m_timestamp + ms; + m_nextTickTimestamp = state().m_timestamp + ms; } void cancelTick() @@ -127,19 +110,21 @@ class SessionOp : public MsgHandler m_nextTickTimestamp = 0; } - SessionState& state() + SessionImpl& session() { - return m_state; + return m_session; } + SessionState& state(); + void sendDisconnectToClient(); - virtual void tickImpl() {}; - virtual void startImpl() {}; - virtual void brokerConnectionUpdatedImpl() {} + virtual void tickImpl(); + virtual void startImpl(); + virtual void brokerConnectionUpdatedImpl(); private: - SessionState& m_state; + SessionImpl& m_session; SendToClientCb m_sendToClientFunc; SendToBrokerCb m_sendToBrokerFunc; diff --git a/gateway/src/lib/session_op/Asleep.cpp b/gateway/src/lib/session_op/Asleep.cpp index 790b764d..da98e862 100644 --- a/gateway/src/lib/session_op/Asleep.cpp +++ b/gateway/src/lib/session_op/Asleep.cpp @@ -16,8 +16,8 @@ namespace cc_mqttsn_gateway namespace session_op { -Asleep::Asleep(SessionState& sessionState) - : Base(sessionState) +Asleep::Asleep(SessionImpl& session) : + Base(session) { } diff --git a/gateway/src/lib/session_op/Asleep.h b/gateway/src/lib/session_op/Asleep.h index b9180958..111a0931 100644 --- a/gateway/src/lib/session_op/Asleep.h +++ b/gateway/src/lib/session_op/Asleep.h @@ -21,7 +21,7 @@ class Asleep : public SessionOp typedef SessionOp Base; public: - Asleep(SessionState& sessionState); + explicit Asleep(SessionImpl& session); ~Asleep(); protected: diff --git a/gateway/src/lib/session_op/AsleepMonitor.cpp b/gateway/src/lib/session_op/AsleepMonitor.cpp index a215d13d..f33cbb28 100644 --- a/gateway/src/lib/session_op/AsleepMonitor.cpp +++ b/gateway/src/lib/session_op/AsleepMonitor.cpp @@ -16,8 +16,8 @@ namespace cc_mqttsn_gateway namespace session_op { -AsleepMonitor::AsleepMonitor(SessionState& sessionState) - : Base(sessionState) +AsleepMonitor::AsleepMonitor(SessionImpl& session) : + Base(session) { } diff --git a/gateway/src/lib/session_op/AsleepMonitor.h b/gateway/src/lib/session_op/AsleepMonitor.h index bbafdada..7dc05b9d 100644 --- a/gateway/src/lib/session_op/AsleepMonitor.h +++ b/gateway/src/lib/session_op/AsleepMonitor.h @@ -21,7 +21,7 @@ class AsleepMonitor : public SessionOp typedef SessionOp Base; public: - AsleepMonitor(SessionState& sessionState); + explicit AsleepMonitor(SessionImpl& session); ~AsleepMonitor(); protected: diff --git a/gateway/src/lib/session_op/Connect.cpp b/gateway/src/lib/session_op/Connect.cpp index 7d4f7ab7..ed5f3db6 100644 --- a/gateway/src/lib/session_op/Connect.cpp +++ b/gateway/src/lib/session_op/Connect.cpp @@ -14,8 +14,8 @@ namespace cc_mqttsn_gateway namespace session_op { -Connect::Connect(SessionState& sessionState) - : Base(sessionState) +Connect::Connect(SessionImpl& session) : + Base(session) { } diff --git a/gateway/src/lib/session_op/Connect.h b/gateway/src/lib/session_op/Connect.h index d5c1df43..12c9d8d2 100644 --- a/gateway/src/lib/session_op/Connect.h +++ b/gateway/src/lib/session_op/Connect.h @@ -27,7 +27,7 @@ class Connect : public SessionOp typedef Session::ClientConnectedReportCb ClientConnectedReportCb; typedef Session::AuthInfoReqCb AuthInfoReqCb; - Connect(SessionState& sessionState); + explicit Connect(SessionImpl& session); ~Connect(); template diff --git a/gateway/src/lib/session_op/Disconnect.cpp b/gateway/src/lib/session_op/Disconnect.cpp index 24203ffb..a690c2b8 100644 --- a/gateway/src/lib/session_op/Disconnect.cpp +++ b/gateway/src/lib/session_op/Disconnect.cpp @@ -15,8 +15,8 @@ namespace cc_mqttsn_gateway namespace session_op { -Disconnect::Disconnect(SessionState& sessionState) - : Base(sessionState) +Disconnect::Disconnect(SessionImpl& session) : + Base(session) { } diff --git a/gateway/src/lib/session_op/Disconnect.h b/gateway/src/lib/session_op/Disconnect.h index d5455838..d38e161f 100644 --- a/gateway/src/lib/session_op/Disconnect.h +++ b/gateway/src/lib/session_op/Disconnect.h @@ -21,7 +21,7 @@ class Disconnect : public SessionOp typedef SessionOp Base; public: - Disconnect(SessionState& sessionState); + explicit Disconnect(SessionImpl& session); ~Disconnect(); protected: diff --git a/gateway/src/lib/session_op/Encapsulate.cpp b/gateway/src/lib/session_op/Encapsulate.cpp index a44d6ede..47bc0b03 100644 --- a/gateway/src/lib/session_op/Encapsulate.cpp +++ b/gateway/src/lib/session_op/Encapsulate.cpp @@ -13,14 +13,16 @@ #include "comms/util/assign.h" #include "comms/util/ScopeGuard.h" +#include "SessionImpl.h" + namespace cc_mqttsn_gateway { namespace session_op { -Encapsulate::Encapsulate(SessionState& sessionState) - : Base(sessionState) +Encapsulate::Encapsulate(SessionImpl& session) : + Base(session) { } @@ -53,6 +55,10 @@ void Encapsulate::handle(FwdMsg_SN& msg) assert(!st.m_encapsulatedMsg); st.m_encapsulatedMsg = true; + if (!session().hasFwdEncSupport()) { + return; + } + auto& nodeIdVec = msg.field_data().value(); NodeId nodeId; comms::util::assign(nodeId, nodeIdVec.begin(), nodeIdVec.end()); @@ -65,20 +71,37 @@ void Encapsulate::handle(FwdMsg_SN& msg) std::tie(iter, std::ignore) = m_sessions.insert(std::make_pair(std::move(nodeId), std::make_unique())); assert(iter != m_sessions.end()); - auto& session = *(iter->second); + auto sessionPtr = iter->second.get(); - // TODO: callbacks + sessionPtr->setGatewayId(st.m_gwId); + sessionPtr->setRetryPeriod(st.m_retryPeriod); + sessionPtr->setRetryCount(st.m_retryCount); + sessionPtr->setSleepingClientMsgLimit(st.m_sleepPubAccLimit); + sessionPtr->setDefaultClientId(st.m_defaultClientId); + sessionPtr->setPubOnlyKeepAlive(st.m_pubOnlyKeepAlive); - if (!session.start()) { + sessionPtr->setSendDataClientReqCb( + [this, nodeId](const std::uint8_t* buf, std::size_t bufSize) + { + sendDataClientReqFromSession(nodeId, buf, bufSize); + }); + + sessionPtr->setTerminationReqCb( + [this, sessionPtr]() + { + terminationReqFromSession(sessionPtr); + }); + + session().reportFwdEncSessionCreated(sessionPtr); + + if ((!sessionPtr->isRunning()) && (!sessionPtr->start())) { // Error failed to start session; - assert(false); // Should not happen + session().reportFwdEncSessionDeleted(sessionPtr); m_sessions.erase(iter); iter = m_sessions.end(); break; } - // TODO: report session for app management - } while (false); if (iter != m_sessions.end()) { @@ -86,6 +109,41 @@ void Encapsulate::handle(FwdMsg_SN& msg) } } +void Encapsulate::sendDataClientReqFromSession(const NodeId& nodeId, const std::uint8_t* buf, std::size_t bufSize) +{ + FwdMsg_SN fwdMsg; + fwdMsg.field_ctrl().field_radius().setValue(3); // TODO: make it configurable + comms::util::assign(fwdMsg.field_data().value(), nodeId.begin(), nodeId.end()); + + MqttsnFrame frame; + std::vector data; + data.resize(frame.length(fwdMsg) + bufSize); + auto writeIter = comms::writeIteratorFor(data.data()); + [[maybe_unused]] auto es = frame.write(fwdMsg, writeIter, data.size()); + assert(es == comms::ErrorStatus::Success); + std::copy_n(buf, bufSize, writeIter); + session().sendDataToClient(data.data(), data.size()); +} + +void Encapsulate::terminationReqFromSession(Session* sessionPtr) +{ + auto iter = + std::find_if( + m_sessions.begin(), m_sessions.end(), + [sessionPtr](auto& elem) + { + return elem.second.get() == sessionPtr; + }); + + assert(iter != m_sessions.end()); + if (iter == m_sessions.end()) { + return; + } + + session().reportFwdEncSessionDeleted(sessionPtr); + m_sessions.erase(iter); +} + } // namespace session_op } // namespace cc_mqttsn_gateway diff --git a/gateway/src/lib/session_op/Encapsulate.h b/gateway/src/lib/session_op/Encapsulate.h index 17e5ebb0..fd0032f6 100644 --- a/gateway/src/lib/session_op/Encapsulate.h +++ b/gateway/src/lib/session_op/Encapsulate.h @@ -29,13 +29,11 @@ class Encapsulate : public SessionOp using Base = SessionOp; public: - Encapsulate(SessionState& sessionState); + explicit Encapsulate(SessionImpl& session); ~Encapsulate(); std::size_t encapsulatedData(const std::uint8_t* buf, std::size_t len); -protected: - private: using NodeId = std::vector; using SessionPtr = std::unique_ptr; @@ -44,6 +42,9 @@ class Encapsulate : public SessionOp using Base::handle; virtual void handle(FwdMsg_SN& msg) override; + void sendDataClientReqFromSession(const NodeId& nodeId, const std::uint8_t* buf, std::size_t bufSize); + void terminationReqFromSession(Session* sessionPtr); + SessionMap m_sessions; Session* m_selectedSession = nullptr; }; diff --git a/gateway/src/lib/session_op/Forward.cpp b/gateway/src/lib/session_op/Forward.cpp index 6113ba77..279f48cb 100644 --- a/gateway/src/lib/session_op/Forward.cpp +++ b/gateway/src/lib/session_op/Forward.cpp @@ -18,8 +18,8 @@ namespace cc_mqttsn_gateway namespace session_op { -Forward::Forward(SessionState& sessionState) - : Base(sessionState) +Forward::Forward(SessionImpl& session) : + Base(session) { } diff --git a/gateway/src/lib/session_op/Forward.h b/gateway/src/lib/session_op/Forward.h index 8a746499..c0f9d33d 100644 --- a/gateway/src/lib/session_op/Forward.h +++ b/gateway/src/lib/session_op/Forward.h @@ -24,7 +24,7 @@ class Forward : public SessionOp typedef SessionOp Base; public: - Forward(SessionState& sessionState); + explicit Forward(SessionImpl& session); ~Forward(); protected: diff --git a/gateway/src/lib/session_op/PubRecv.cpp b/gateway/src/lib/session_op/PubRecv.cpp index ed300f6b..f37c0631 100644 --- a/gateway/src/lib/session_op/PubRecv.cpp +++ b/gateway/src/lib/session_op/PubRecv.cpp @@ -16,8 +16,8 @@ namespace cc_mqttsn_gateway namespace session_op { -PubRecv::PubRecv(SessionState& sessionState) - : Base(sessionState) +PubRecv::PubRecv(SessionImpl& session) : + Base(session) { } diff --git a/gateway/src/lib/session_op/PubRecv.h b/gateway/src/lib/session_op/PubRecv.h index e2155430..f989f061 100644 --- a/gateway/src/lib/session_op/PubRecv.h +++ b/gateway/src/lib/session_op/PubRecv.h @@ -21,7 +21,7 @@ class PubRecv : public SessionOp typedef SessionOp Base; public: - PubRecv(SessionState& sessionState); + explicit PubRecv(SessionImpl& session); ~PubRecv(); protected: diff --git a/gateway/src/lib/session_op/PubSend.cpp b/gateway/src/lib/session_op/PubSend.cpp index 290e251e..e0a8e75f 100644 --- a/gateway/src/lib/session_op/PubSend.cpp +++ b/gateway/src/lib/session_op/PubSend.cpp @@ -16,8 +16,8 @@ namespace cc_mqttsn_gateway namespace session_op { -PubSend::PubSend(SessionState& sessionState) - : Base(sessionState) +PubSend::PubSend(SessionImpl& session) : + Base(session) { } diff --git a/gateway/src/lib/session_op/PubSend.h b/gateway/src/lib/session_op/PubSend.h index 3ead8e89..72f71300 100644 --- a/gateway/src/lib/session_op/PubSend.h +++ b/gateway/src/lib/session_op/PubSend.h @@ -21,7 +21,7 @@ class PubSend : public SessionOp typedef SessionOp Base; public: - PubSend(SessionState& sessionState); + explicit PubSend(SessionImpl& session); ~PubSend(); protected: diff --git a/gateway/src/lib/session_op/WillUpdate.cpp b/gateway/src/lib/session_op/WillUpdate.cpp index 296fab87..5a491452 100644 --- a/gateway/src/lib/session_op/WillUpdate.cpp +++ b/gateway/src/lib/session_op/WillUpdate.cpp @@ -16,8 +16,8 @@ namespace cc_mqttsn_gateway namespace session_op { -WillUpdate::WillUpdate(SessionState& sessionState) - : Base(sessionState) +WillUpdate::WillUpdate(SessionImpl& session) : + Base(session) { } diff --git a/gateway/src/lib/session_op/WillUpdate.h b/gateway/src/lib/session_op/WillUpdate.h index 7c380334..31d028fc 100644 --- a/gateway/src/lib/session_op/WillUpdate.h +++ b/gateway/src/lib/session_op/WillUpdate.h @@ -21,7 +21,7 @@ class WillUpdate : public SessionOp typedef SessionOp Base; public: - WillUpdate(SessionState& sessionState); + explicit WillUpdate(SessionImpl& session); ~WillUpdate(); protected: From 09cf01c52eae7e3f91eac12b3d98b4411b4284aa Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 9 May 2024 16:34:20 +1000 Subject: [PATCH 011/106] More preparation for FWD encapsulation support in the gateway. --- gateway/src/lib/SessionImpl.cpp | 76 +++++----- gateway/src/lib/SessionImpl.h | 9 +- gateway/src/lib/SessionOp.cpp | 20 +++ gateway/src/lib/SessionOp.h | 60 +------- gateway/src/lib/session_op/Encapsulate.cpp | 18 ++- gateway/test/Session.th | 28 ++++ gateway/test/TestMsgHandler.cpp | 31 +++- gateway/test/TestMsgHandler.h | 162 +++++++++------------ 8 files changed, 197 insertions(+), 207 deletions(-) diff --git a/gateway/src/lib/SessionImpl.cpp b/gateway/src/lib/SessionImpl.cpp index 48d88901..c362dc96 100644 --- a/gateway/src/lib/SessionImpl.cpp +++ b/gateway/src/lib/SessionImpl.cpp @@ -94,7 +94,7 @@ SessionImpl::SessionImpl() m_encapsulateOp = static_cast(m_ops.back().get()); for (auto& op : m_ops) { - startOp(*op); + op->start(); } } @@ -220,11 +220,11 @@ bool SessionImpl::setTopicIdAllocationRange(std::uint16_t minVal, std::uint16_t return m_state.m_regMgr.setTopicIdAllocationRange(minVal, maxVal); } -void SessionImpl::reportFwdEncSessionCreated(Session* session) +bool SessionImpl::reportFwdEncSessionCreated(Session* session) { assert(m_fwdEncSessionCreatedReportCb); assert(session != nullptr); - m_fwdEncSessionCreatedReportCb(session); + return m_fwdEncSessionCreatedReportCb(session); } void SessionImpl::reportFwdEncSessionDeleted(Session* session) @@ -240,6 +240,37 @@ void SessionImpl::sendDataToClient(const std::uint8_t* buf, std::size_t bufLen) m_sendToClientCb(buf, bufLen); } +void SessionImpl::sendToClient(const MqttsnMessage& msg) +{ + sendMessage(msg, m_mqttsnFrame, m_sendToClientCb, m_mqttsnMsgData); +} + +void SessionImpl::sendToBroker(const MqttMessage& msg) +{ + sendMessage(msg, m_mqttFrame, m_sendToBrokerCb, m_mqttMsgData); +} + +void SessionImpl::termRequest() +{ + if ((!m_termReqCb) || (m_state.m_terminating)) { + return; + } + + m_state.m_terminating = true; +} + +void SessionImpl::brokerReconnectRequest() +{ + if ((!m_brokerReconnectReqCb) || + (m_state.m_reconnectingBroker) || + (m_state.m_terminating)) { + return; + } + + m_state.m_reconnectingBroker = true; + m_brokerReconnectReqCb(); +} + void SessionImpl::handle([[maybe_unused]] SearchgwMsg_SN& msg) { GwinfoMsg_SN respMsg; @@ -292,45 +323,6 @@ void SessionImpl::handle(MqttMessage& msg) dispatchToOps(msg); } -void SessionImpl::sendToClient(const MqttsnMessage& msg) -{ - sendMessage(msg, m_mqttsnFrame, m_sendToClientCb, m_mqttsnMsgData); -} - -void SessionImpl::sendToBroker(const MqttMessage& msg) -{ - sendMessage(msg, m_mqttFrame, m_sendToBrokerCb, m_mqttMsgData); -} - -void SessionImpl::startOp(SessionOp& op) -{ - op.setSendToClientCb(std::bind(&SessionImpl::sendToClient, this, std::placeholders::_1)); - op.setSendToBrokerCb(std::bind(&SessionImpl::sendToBroker, this, std::placeholders::_1)); - op.setSessionTermReqCb( - [this]() noexcept - { - if ((!m_termReqCb) || (m_state.m_terminating)) { - return; - } - - m_state.m_terminating = true; - }); - - op.setBrokerReconnectReqCb( - [this]() - { - if ((!m_brokerReconnectReqCb) || - (m_state.m_reconnectingBroker) || - (m_state.m_terminating)) { - return; - } - - m_state.m_reconnectingBroker = true; - m_brokerReconnectReqCb(); - }); - op.start(); -} - void SessionImpl::dispatchToOps(MqttsnMessage& msg) { dispatchToOpsCommon(msg); diff --git a/gateway/src/lib/SessionImpl.h b/gateway/src/lib/SessionImpl.h index 24654324..417c3b68 100644 --- a/gateway/src/lib/SessionImpl.h +++ b/gateway/src/lib/SessionImpl.h @@ -164,9 +164,13 @@ class SessionImpl : public MsgHandler return static_cast(m_fwdEncSessionCreatedReportCb); } - void reportFwdEncSessionCreated(Session* session); + bool reportFwdEncSessionCreated(Session* session); void reportFwdEncSessionDeleted(Session* session); void sendDataToClient(const std::uint8_t* buf, std::size_t bufLen); + void sendToClient(const MqttsnMessage& msg); + void sendToBroker(const MqttMessage& msg); + void termRequest(); + void brokerReconnectRequest(); private: @@ -186,9 +190,6 @@ class SessionImpl : public MsgHandler template void dispatchToOpsCommon(TMsg& msg); - void sendToClient(const MqttsnMessage& msg); - void sendToBroker(const MqttMessage& msg); - void startOp(SessionOp& op); void dispatchToOps(MqttsnMessage& msg); void dispatchToOps(MqttMessage& msg); void programNextTimeout(); diff --git a/gateway/src/lib/SessionOp.cpp b/gateway/src/lib/SessionOp.cpp index 1d596a85..643fd4ce 100644 --- a/gateway/src/lib/SessionOp.cpp +++ b/gateway/src/lib/SessionOp.cpp @@ -21,6 +21,26 @@ void SessionOp::timestampUpdated() } } +void SessionOp::sendToClient(const MqttsnMessage& msg) +{ + m_session.sendToClient(msg); +} + +void SessionOp::sendToBroker(const MqttMessage& msg) +{ + m_session.sendToBroker(msg); +} + +void SessionOp::termRequest() +{ + m_session.termRequest(); +} + +void SessionOp::brokerReconnectRequest() +{ + m_session.brokerReconnectRequest(); +} + unsigned SessionOp::nextTick() { if (m_nextTickTimestamp == 0) { diff --git a/gateway/src/lib/SessionOp.h b/gateway/src/lib/SessionOp.h index 6077e0c1..2d7cc724 100644 --- a/gateway/src/lib/SessionOp.h +++ b/gateway/src/lib/SessionOp.h @@ -24,38 +24,10 @@ class SessionOp : public MsgHandler typedef MsgHandler Base; public: - typedef std::function SendToClientCb; - typedef std::function SendToBrokerCb; - typedef std::function SessionTermReqCb; - typedef std::function BrokerReconnectReqCb; typedef unsigned long long Timestamp; virtual ~SessionOp() = default; - template - void setSendToClientCb(TFunc&& func) - { - m_sendToClientFunc = std::forward(func); - } - - template - void setSendToBrokerCb(TFunc&& func) - { - m_sendToBrokerFunc = std::forward(func); - } - - template - void setSessionTermReqCb(TFunc&& func) - { - m_termReqFunc = std::forward(func); - } - - template - void setBrokerReconnectReqCb(TFunc&& func) - { - m_brokerReconnectReqFunc = std::forward(func); - } - void timestampUpdated(); unsigned nextTick(); @@ -76,29 +48,10 @@ class SessionOp : public MsgHandler { } - void sendToClient(const MqttsnMessage& msg) - { - assert(m_sendToClientFunc); - m_sendToClientFunc(msg); - } - - void sendToBroker(const MqttMessage& msg) - { - assert(m_sendToBrokerFunc); - m_sendToBrokerFunc(msg); - } - - void termRequest() - { - assert(m_termReqFunc); - m_termReqFunc(); - } - - void brokerReconnectRequest() - { - assert(m_brokerReconnectReqFunc); - m_brokerReconnectReqFunc(); - } + void sendToClient(const MqttsnMessage& msg); + void sendToBroker(const MqttMessage& msg); + void termRequest(); + void brokerReconnectRequest(); void nextTickReq(unsigned ms) { @@ -125,11 +78,6 @@ class SessionOp : public MsgHandler private: SessionImpl& m_session; - - SendToClientCb m_sendToClientFunc; - SendToBrokerCb m_sendToBrokerFunc; - SessionTermReqCb m_termReqFunc; - BrokerReconnectReqCb m_brokerReconnectReqFunc; Timestamp m_nextTickTimestamp = 0; }; diff --git a/gateway/src/lib/session_op/Encapsulate.cpp b/gateway/src/lib/session_op/Encapsulate.cpp index 47bc0b03..261eecd0 100644 --- a/gateway/src/lib/session_op/Encapsulate.cpp +++ b/gateway/src/lib/session_op/Encapsulate.cpp @@ -26,7 +26,12 @@ Encapsulate::Encapsulate(SessionImpl& session) : { } -Encapsulate::~Encapsulate() = default; +Encapsulate::~Encapsulate() +{ + for (auto& info : m_sessions) { + session().reportFwdEncSessionDeleted(info.second.get()); + } +} std::size_t Encapsulate::encapsulatedData(const std::uint8_t* buf, std::size_t len) { @@ -46,7 +51,6 @@ std::size_t Encapsulate::encapsulatedData(const std::uint8_t* buf, std::size_t l } return m_selectedSession->dataFromClient(buf, len); - return 0U; } void Encapsulate::handle(FwdMsg_SN& msg) @@ -59,7 +63,7 @@ void Encapsulate::handle(FwdMsg_SN& msg) return; } - auto& nodeIdVec = msg.field_data().value(); + auto& nodeIdVec = msg.field_nodeId().value(); NodeId nodeId; comms::util::assign(nodeId, nodeIdVec.begin(), nodeIdVec.end()); @@ -92,7 +96,11 @@ void Encapsulate::handle(FwdMsg_SN& msg) terminationReqFromSession(sessionPtr); }); - session().reportFwdEncSessionCreated(sessionPtr); + if (!session().reportFwdEncSessionCreated(sessionPtr)) { + m_sessions.erase(iter); + iter = m_sessions.end(); + break; + } if ((!sessionPtr->isRunning()) && (!sessionPtr->start())) { // Error failed to start session; @@ -113,7 +121,7 @@ void Encapsulate::sendDataClientReqFromSession(const NodeId& nodeId, const std:: { FwdMsg_SN fwdMsg; fwdMsg.field_ctrl().field_radius().setValue(3); // TODO: make it configurable - comms::util::assign(fwdMsg.field_data().value(), nodeId.begin(), nodeId.end()); + comms::util::assign(fwdMsg.field_nodeId().value(), nodeId.begin(), nodeId.end()); MqttsnFrame frame; std::vector data; diff --git a/gateway/test/Session.th b/gateway/test/Session.th index 71fb719c..f224d6fa 100644 --- a/gateway/test/Session.th +++ b/gateway/test/Session.th @@ -74,6 +74,7 @@ private: std::list m_termRequests; std::list m_brokerReconnectRequests; std::list m_connectedClients; + std::list m_fwdEncSessions; }; struct WillInfo @@ -168,6 +169,27 @@ private: return info; }); + session->setFwdEncSessionCreatedReportCb( + [&state](cc_mqttsn_gateway::Session* fwdEncSession) + { + TS_TRACE("New FWD Encapsulated Session"); + auto iter = std::find(state.m_fwdEncSessions.begin(), state.m_fwdEncSessions.end(), fwdEncSession); + TS_ASSERT_EQUALS(iter, state.m_fwdEncSessions.end()); + + state.m_fwdEncSessions.push_back(fwdEncSession); + return true; + }); + + session->setFwdEncSessionDeletedReportCb( + [&state](cc_mqttsn_gateway::Session* fwdEncSession) + { + TS_TRACE("Deleting FWD Encapsulated Session"); + auto iter = std::find(state.m_fwdEncSessions.begin(), state.m_fwdEncSessions.end(), fwdEncSession); + TS_ASSERT_DIFFERS(iter, state.m_fwdEncSessions.end()); + + state.m_fwdEncSessions.erase(iter); + }); + handler.setGwinfoMsgHandler( [](const GwinfoMsg_SN&) { @@ -276,6 +298,12 @@ private: TS_TRACE("(CLIENT) <-- WILLMSGRESP"); }); + handler.setFwdMsgHandler( + [](const FwdMsg_SN&) + { + TS_TRACE("(CLIENT) <-- FWD"); + }); + handler.setConnectMsgHandler( [](const ConnectMsg&) { diff --git a/gateway/test/TestMsgHandler.cpp b/gateway/test/TestMsgHandler.cpp index 1d3e0c2d..a5f18ef5 100644 --- a/gateway/test/TestMsgHandler.cpp +++ b/gateway/test/TestMsgHandler.cpp @@ -218,6 +218,14 @@ TestMsgHandler::WillmsgrespMsgHandlerFunc TestMsgHandler::setWillmsgrespMsgHandl return old; } +TestMsgHandler::FwdMsgHandlerFunc TestMsgHandler::setFwdMsgHandler( + FwdMsgHandlerFunc&& func) +{ + FwdMsgHandlerFunc old(std::move(m_fwdMsgHandler)); + m_fwdMsgHandler = std::move(func); + return old; +} + TestMsgHandler::ConnectMsgHandlerFunc TestMsgHandler::setConnectMsgHandler(ConnectMsgHandlerFunc&& func) { @@ -414,6 +422,12 @@ void TestMsgHandler::handle(WillmsgrespMsg_SN& msg) m_willmsgrespMsgHandler(msg); } +void TestMsgHandler::handle(FwdMsg_SN& msg) +{ + assert(m_fwdMsgHandler); + m_fwdMsgHandler(msg); +} + void TestMsgHandler::handle(TestMqttsnMessage& msg) { std::cout << "Unhandled message sent to client: " << static_cast(msg.getId()) << std::endl; @@ -496,22 +510,22 @@ void TestMsgHandler::handle(TestMqttMessage& msg) void TestMsgHandler::processDataForClient(const DataBuf& data) { - processOutputInternal(m_mqttsnStack, data); + processOutputInternal(m_mqttsnFrame, data); } void TestMsgHandler::processDataForBroker(const DataBuf& data) { - processOutputInternal(m_mqttStack, data); + processOutputInternal(m_mqttFrame, data); } TestMsgHandler::DataBuf TestMsgHandler::prepareInput(const TestMqttsnMessage& msg) { - return prepareInputInternal(m_mqttsnStack, msg); + return prepareInputInternal(m_mqttsnFrame, msg); } TestMsgHandler::DataBuf TestMsgHandler::prepareInput(const TestMqttMessage& msg) { - return prepareInputInternal(m_mqttStack, msg); + return prepareInputInternal(m_mqttFrame, msg); } TestMsgHandler::DataBuf TestMsgHandler::prepareSearchgw(std::uint8_t radius) @@ -757,6 +771,15 @@ TestMsgHandler::DataBuf TestMsgHandler::prepareClientWillmsgupd(const DataBuf& d return prepareInput(msg); } +TestMsgHandler::DataBuf TestMsgHandler::prepareClientFwd(std::uint8_t nodeId, const DataBuf& data) +{ + FwdMsg_SN msg; + msg.field_nodeId().value().push_back(nodeId); + auto result = prepareInput(msg); + result.insert(result.end(), data.begin(), data.end()); + return result; +} + TestMsgHandler::DataBuf TestMsgHandler::prepareBrokerConnack( ConnackResponseCodeVal rc, bool sessionPresent) diff --git a/gateway/test/TestMsgHandler.h b/gateway/test/TestMsgHandler.h index a1986875..0db9091d 100644 --- a/gateway/test/TestMsgHandler.h +++ b/gateway/test/TestMsgHandler.h @@ -11,74 +11,39 @@ #include "comms/comms.h" #include "cc_mqttsn/Message.h" +#include "cc_mqttsn/input/AllMessages.h" #include "cc_mqttsn/frame/Frame.h" #include "cc_mqtt311/Message.h" +#include "cc_mqtt311/input/AllMessages.h" #include "cc_mqtt311/frame/Frame.h" class TestMsgHandler; -typedef cc_mqttsn::Message< - comms::option::IdInfoInterface, - comms::option::ReadIterator, - comms::option::WriteIterator, - comms::option::Handler, - comms::option::LengthInfoInterface, - comms::option::RefreshInterface -> TestMqttsnMessage; - -typedef cc_mqtt311::Message< - comms::option::IdInfoInterface, - comms::option::ReadIterator, - comms::option::WriteIterator, - comms::option::Handler, - comms::option::LengthInfoInterface, - comms::option::RefreshInterface -> TestMqttMessage; - -typedef cc_mqttsn::message::Advertise AdvertiseMsg_SN; -typedef cc_mqttsn::message::Searchgw SearchgwMsg_SN; -typedef cc_mqttsn::message::Gwinfo GwinfoMsg_SN; -typedef cc_mqttsn::message::Connect ConnectMsg_SN; -typedef cc_mqttsn::message::Connack ConnackMsg_SN; -typedef cc_mqttsn::message::Willtopicreq WilltopicreqMsg_SN; -typedef cc_mqttsn::message::Willtopic WilltopicMsg_SN; -typedef cc_mqttsn::message::Willmsgreq WillmsgreqMsg_SN; -typedef cc_mqttsn::message::Willmsg WillmsgMsg_SN; -typedef cc_mqttsn::message::Register RegisterMsg_SN; -typedef cc_mqttsn::message::Regack RegackMsg_SN; -typedef cc_mqttsn::message::Publish PublishMsg_SN; -typedef cc_mqttsn::message::Puback PubackMsg_SN; -typedef cc_mqttsn::message::Pubrec PubrecMsg_SN; -typedef cc_mqttsn::message::Pubrel PubrelMsg_SN; -typedef cc_mqttsn::message::Pubcomp PubcompMsg_SN; -typedef cc_mqttsn::message::Subscribe SubscribeMsg_SN; -typedef cc_mqttsn::message::Suback SubackMsg_SN; -typedef cc_mqttsn::message::Unsubscribe UnsubscribeMsg_SN; -typedef cc_mqttsn::message::Unsuback UnsubackMsg_SN; -typedef cc_mqttsn::message::Pingreq PingreqMsg_SN; -typedef cc_mqttsn::message::Pingresp PingrespMsg_SN; -typedef cc_mqttsn::message::Disconnect DisconnectMsg_SN; -typedef cc_mqttsn::message::Willtopicupd WilltopicupdMsg_SN; -typedef cc_mqttsn::message::Willtopicresp WilltopicrespMsg_SN; -typedef cc_mqttsn::message::Willmsgupd WillmsgupdMsg_SN; -typedef cc_mqttsn::message::Willmsgresp WillmsgrespMsg_SN; -typedef cc_mqttsn::frame::Frame TestMqttsnProtStack; - -typedef cc_mqtt311::message::Connect ConnectMsg; -typedef cc_mqtt311::message::Connack ConnackMsg; -typedef cc_mqtt311::message::Publish PublishMsg; -typedef cc_mqtt311::message::Puback PubackMsg; -typedef cc_mqtt311::message::Pubrec PubrecMsg; -typedef cc_mqtt311::message::Pubrel PubrelMsg; -typedef cc_mqtt311::message::Pubcomp PubcompMsg; -typedef cc_mqtt311::message::Subscribe SubscribeMsg; -typedef cc_mqtt311::message::Suback SubackMsg; -typedef cc_mqtt311::message::Unsubscribe UnsubscribeMsg; -typedef cc_mqtt311::message::Unsuback UnsubackMsg; -typedef cc_mqtt311::message::Pingreq PingreqMsg; -typedef cc_mqtt311::message::Pingresp PingrespMsg; -typedef cc_mqtt311::message::Disconnect DisconnectMsg; -typedef cc_mqtt311::frame::Frame TestMqttProtStack; +using TestMqttsnMessage = + cc_mqttsn::Message< + comms::option::IdInfoInterface, + comms::option::ReadIterator, + comms::option::WriteIterator, + comms::option::Handler, + comms::option::LengthInfoInterface, + comms::option::RefreshInterface + >; + +using TestMqttMessage = + cc_mqtt311::Message< + comms::option::IdInfoInterface, + comms::option::ReadIterator, + comms::option::WriteIterator, + comms::option::Handler, + comms::option::LengthInfoInterface, + comms::option::RefreshInterface + >; + +CC_MQTTSN_ALIASES_FOR_ALL_MESSAGES_DEFAULT_OPTIONS(, Msg_SN, TestMqttsnMessage) +CC_MQTT311_ALIASES_FOR_ALL_MESSAGES_DEFAULT_OPTIONS(, Msg, TestMqttMessage) + +using TestMqttsnFrame = cc_mqttsn::frame::Frame; +using TestMqttFrame = cc_mqtt311::frame::Frame; using TestMqttsnMsgHandler = comms::GenericHandler< TestMqttsnMessage, @@ -93,100 +58,102 @@ using TestMqttMsgHandler = comms::GenericHandler< class TestMsgHandler : public TestMqttsnMsgHandler, public TestMqttMsgHandler { - typedef TestMqttsnMsgHandler MqttsnBase; - typedef TestMqttMsgHandler MqttBase; + using MqttsnBase = TestMqttsnMsgHandler; + using MqttBase = TestMqttMsgHandler; public: - typedef std::vector DataBuf; + using DataBuf = std::vector; TestMsgHandler(); ~TestMsgHandler(); - typedef std::function GwinfoMsgHandlerFunc; + using GwinfoMsgHandlerFunc = std::function; GwinfoMsgHandlerFunc setGwinfoMsgHandler(GwinfoMsgHandlerFunc&& func); - typedef std::function ConnackMsgHandlerFunc; + using ConnackMsgHandlerFunc = std::function; ConnackMsgHandlerFunc setConnackMsgHandler(ConnackMsgHandlerFunc&& func); - typedef std::function WilltopicreqMsgHandlerFunc; + using WilltopicreqMsgHandlerFunc = std::function; WilltopicreqMsgHandlerFunc setWilltopicreqMsgHandler(WilltopicreqMsgHandlerFunc&& func); - typedef std::function WillmsgreqMsgHandlerFunc; + using WillmsgreqMsgHandlerFunc = std::function; WillmsgreqMsgHandlerFunc setWillmsgreqMsgHandler(WillmsgreqMsgHandlerFunc&& func); - typedef std::function DisconnectSnMsgHandlerFunc; + using DisconnectSnMsgHandlerFunc = std::function; DisconnectSnMsgHandlerFunc setDisconnectSnMsgHandler(DisconnectSnMsgHandlerFunc&& func); - typedef std::function RegisterMsgHandlerFunc; + using RegisterMsgHandlerFunc = std::function; RegisterMsgHandlerFunc setRegisterMsgHandler(RegisterMsgHandlerFunc&& func); - typedef std::function RegackMsgHandlerFunc; + using RegackMsgHandlerFunc = std::function; RegackMsgHandlerFunc setRegackMsgHandler(RegackMsgHandlerFunc&& func); - typedef std::function PublishSnMsgHandlerFunc; + using PublishSnMsgHandlerFunc = std::function; PublishSnMsgHandlerFunc setPublishSnMsgHandler(PublishSnMsgHandlerFunc&& func); - typedef std::function PubackSnMsgHandlerFunc; + using PubackSnMsgHandlerFunc = std::function; PubackSnMsgHandlerFunc setPubackSnMsgHandler(PubackSnMsgHandlerFunc&& func); - typedef std::function PubrecSnMsgHandlerFunc; + using PubrecSnMsgHandlerFunc = std::function; PubrecSnMsgHandlerFunc setPubrecSnMsgHandler(PubrecSnMsgHandlerFunc&& func); - typedef std::function PubrelSnMsgHandlerFunc; + using PubrelSnMsgHandlerFunc = std::function; PubrelSnMsgHandlerFunc setPubrelSnMsgHandler(PubrelSnMsgHandlerFunc&& func); - typedef std::function PubcompSnMsgHandlerFunc; + using PubcompSnMsgHandlerFunc = std::function; PubcompSnMsgHandlerFunc setPubcompSnMsgHandler(PubcompSnMsgHandlerFunc&& func); - typedef std::function PingreqSnMsgHandlerFunc; + using PingreqSnMsgHandlerFunc = std::function; PingreqSnMsgHandlerFunc setPingreqSnMsgHandler(PingreqSnMsgHandlerFunc&& func); - typedef std::function PingrespSnMsgHandlerFunc; + using PingrespSnMsgHandlerFunc = std::function; PingrespSnMsgHandlerFunc setPingrespSnMsgHandler(PingrespSnMsgHandlerFunc&& func); - typedef std::function SubackSnMsgHandlerFunc; + using SubackSnMsgHandlerFunc = std::function; SubackSnMsgHandlerFunc setSubackSnMsgHandler(SubackSnMsgHandlerFunc&& func); - typedef std::function UnsubackSnMsgHandlerFunc; + using UnsubackSnMsgHandlerFunc = std::function; UnsubackSnMsgHandlerFunc setUnsubackSnMsgHandler(UnsubackSnMsgHandlerFunc&& func); - typedef std::function WilltopicrespMsgHandlerFunc; + using WilltopicrespMsgHandlerFunc = std::function; WilltopicrespMsgHandlerFunc setWilltopicrespMsgHandler(WilltopicrespMsgHandlerFunc&& func); - typedef std::function WillmsgrespMsgHandlerFunc; + using WillmsgrespMsgHandlerFunc = std::function; WillmsgrespMsgHandlerFunc setWillmsgrespMsgHandler(WillmsgrespMsgHandlerFunc&& func); + using FwdMsgHandlerFunc = std::function; + FwdMsgHandlerFunc setFwdMsgHandler(FwdMsgHandlerFunc&& func); - typedef std::function ConnectMsgHandlerFunc; + using ConnectMsgHandlerFunc = std::function; ConnectMsgHandlerFunc setConnectMsgHandler(ConnectMsgHandlerFunc&& func); - typedef std::function DisconnectMsgHandlerFunc; + using DisconnectMsgHandlerFunc = std::function; DisconnectMsgHandlerFunc setDisconnectMsgHandler(DisconnectMsgHandlerFunc&& func); - typedef std::function PingreqMsgHandlerFunc; + using PingreqMsgHandlerFunc = std::function; PingreqMsgHandlerFunc setPingreqMsgHandler(PingreqMsgHandlerFunc&& func); - typedef std::function PingrespMsgHandlerFunc; + using PingrespMsgHandlerFunc = std::function; PingrespMsgHandlerFunc setPingrespMsgHandler(PingrespMsgHandlerFunc&& func); - typedef std::function PublishMsgHandlerFunc; + using PublishMsgHandlerFunc = std::function; PublishMsgHandlerFunc setPublishMsgHandler(PublishMsgHandlerFunc&& func); - typedef std::function PubackMsgHandlerFunc; + using PubackMsgHandlerFunc = std::function; PubackMsgHandlerFunc setPubackMsgHandler(PubackMsgHandlerFunc&& func); - typedef std::function PubrecMsgHandlerFunc; + using PubrecMsgHandlerFunc = std::function; PubrecMsgHandlerFunc setPubrecMsgHandler(PubrecMsgHandlerFunc&& func); - typedef std::function PubrelMsgHandlerFunc; + using PubrelMsgHandlerFunc = std::function; PubrelMsgHandlerFunc setPubrelMsgHandler(PubrelMsgHandlerFunc&& func); - typedef std::function PubcompMsgHandlerFunc; + using PubcompMsgHandlerFunc = std::function; PubcompMsgHandlerFunc setPubcompMsgHandler(PubcompMsgHandlerFunc&& func); - typedef std::function SubscribeMsgHandlerFunc; + using SubscribeMsgHandlerFunc = std::function; SubscribeMsgHandlerFunc setSubscribeMsgHandler(SubscribeMsgHandlerFunc&& func); - typedef std::function UnsubscribeMsgHandlerFunc; + using UnsubscribeMsgHandlerFunc = std::function; UnsubscribeMsgHandlerFunc setUnsubscribeMsgHandler(UnsubscribeMsgHandlerFunc&& func); using MqttsnBase::handle; @@ -210,6 +177,7 @@ class TestMsgHandler : public TestMqttsnMsgHandler, public TestMqttMsgHandler virtual void handle(UnsubackMsg_SN& msg) override; virtual void handle(WilltopicrespMsg_SN& msg) override; virtual void handle(WillmsgrespMsg_SN& msg) override; + virtual void handle(FwdMsg_SN& msg) override; virtual void handle(TestMqttsnMessage& msg) override; virtual void handle(ConnectMsg& msg) override; @@ -282,6 +250,7 @@ class TestMsgHandler : public TestMqttsnMsgHandler, public TestMqttMsgHandler cc_mqttsn::field::QosVal qos, bool retain); DataBuf prepareClientWillmsgupd(const DataBuf& data); + DataBuf prepareClientFwd(std::uint8_t nodeId, const DataBuf& data); using ConnackResponseCodeVal = ConnackMsg::Field_returnCode::ValueType; DataBuf prepareBrokerConnack(ConnackResponseCodeVal rc, bool sessionPresent = false); @@ -312,8 +281,8 @@ class TestMsgHandler : public TestMqttsnMsgHandler, public TestMqttMsgHandler template static DataBuf prepareInputInternal(TStack& stack, const TMsg& msg); - TestMqttsnProtStack m_mqttsnStack; - TestMqttProtStack m_mqttStack; + TestMqttsnFrame m_mqttsnFrame; + TestMqttFrame m_mqttFrame; GwinfoMsgHandlerFunc m_gwInfoMsgHandler; ConnackMsgHandlerFunc m_connackMsgHandler; @@ -333,6 +302,7 @@ class TestMsgHandler : public TestMqttsnMsgHandler, public TestMqttMsgHandler UnsubackSnMsgHandlerFunc m_unsubackSnMsgHandler; WilltopicrespMsgHandlerFunc m_willtopicrespMsgHandler; WillmsgrespMsgHandlerFunc m_willmsgrespMsgHandler; + FwdMsgHandlerFunc m_fwdMsgHandler; ConnectMsgHandlerFunc m_connectMsgHandler; DisconnectMsgHandlerFunc m_disconnectMsgHandler; From eacd095cba23b30ba5adfc9518d947a5c1cc8879 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 10 May 2024 08:46:46 +1000 Subject: [PATCH 012/106] Improvements to the ConnectOp in the gateway. --- gateway/src/lib/SessionImpl.cpp | 36 ++++++++++++-------------- gateway/src/lib/SessionImpl.h | 2 ++ gateway/src/lib/session_op/Connect.cpp | 8 +++--- gateway/src/lib/session_op/Connect.h | 21 ++------------- 4 files changed, 25 insertions(+), 42 deletions(-) diff --git a/gateway/src/lib/SessionImpl.cpp b/gateway/src/lib/SessionImpl.cpp index c362dc96..a9c869c4 100644 --- a/gateway/src/lib/SessionImpl.cpp +++ b/gateway/src/lib/SessionImpl.cpp @@ -64,25 +64,7 @@ void SessionImpl::dispatchToOpsCommon(TMsg& msg) SessionImpl::SessionImpl() { - std::unique_ptr connectOp(new session_op::Connect(*this)); - connectOp->setClientConnectedReportCb( - [this](const std::string& clientId) - { - if (m_clientConnectedCb) { - m_clientConnectedCb(clientId); - } - }); - connectOp->setAuthInfoReqCb( - [this](const std::string& clientId) -> AuthInfo - { - if (!m_authInfoReqCb) { - return AuthInfo(); - } - - return m_authInfoReqCb(clientId); - }); - - m_ops.push_back(std::move(connectOp)); + m_ops.emplace_back(new session_op::Connect(*this)); m_ops.emplace_back(new session_op::Disconnect(*this)); m_ops.emplace_back(new session_op::Asleep(*this)); m_ops.emplace_back(new session_op::AsleepMonitor(*this)); @@ -271,6 +253,22 @@ void SessionImpl::brokerReconnectRequest() m_brokerReconnectReqCb(); } +void SessionImpl::clientConnectedReport(const std::string& clientId) +{ + if (m_clientConnectedCb) { + m_clientConnectedCb(clientId); + } +} + +SessionImpl::AuthInfo SessionImpl::authInfoRequest(const std::string& clientId) +{ + if (!m_authInfoReqCb) { + return AuthInfo(); + } + + return m_authInfoReqCb(clientId); +} + void SessionImpl::handle([[maybe_unused]] SearchgwMsg_SN& msg) { GwinfoMsg_SN respMsg; diff --git a/gateway/src/lib/SessionImpl.h b/gateway/src/lib/SessionImpl.h index 417c3b68..7e9f16ad 100644 --- a/gateway/src/lib/SessionImpl.h +++ b/gateway/src/lib/SessionImpl.h @@ -171,6 +171,8 @@ class SessionImpl : public MsgHandler void sendToBroker(const MqttMessage& msg); void termRequest(); void brokerReconnectRequest(); + void clientConnectedReport(const std::string& clientId); + AuthInfo authInfoRequest(const std::string& clientId); private: diff --git a/gateway/src/lib/session_op/Connect.cpp b/gateway/src/lib/session_op/Connect.cpp index ed5f3db6..68e43dd2 100644 --- a/gateway/src/lib/session_op/Connect.cpp +++ b/gateway/src/lib/session_op/Connect.cpp @@ -8,6 +8,8 @@ #include "Connect.h" #include +#include "SessionImpl.h" + namespace cc_mqttsn_gateway { @@ -263,8 +265,7 @@ void Connect::doNextStep() if ((m_clientId != st.m_clientId) || (st.m_clientId.empty() && (!st.m_clientConnectReported))) { - assert(m_authInfoReqCb); - m_authInfo = m_authInfoReqCb(m_clientId); + m_authInfo = session().authInfoRequest(m_clientId); } else { m_authInfo = std::make_pair(st.m_username, st.m_password); @@ -361,8 +362,7 @@ void Connect::processAck(ConnackMsg::Field_returnCode::ValueType respCode) auto& sessionState = state(); if (!sessionState.m_clientConnectReported) { sessionState.m_clientConnectReported = true; - assert(m_clientConnectedCb); - m_clientConnectedCb(m_clientId); + session().clientConnectedReport(m_clientId); } sessionState.m_clientId = std::move(m_clientId); sessionState.m_connStatus = ConnectionStatus::Connected; diff --git a/gateway/src/lib/session_op/Connect.h b/gateway/src/lib/session_op/Connect.h index 12c9d8d2..f22877f4 100644 --- a/gateway/src/lib/session_op/Connect.h +++ b/gateway/src/lib/session_op/Connect.h @@ -19,29 +19,14 @@ namespace session_op class Connect : public SessionOp { - typedef SessionOp Base; + using Base = SessionOp; public: - typedef Session::AuthInfo AuthInfo; - - typedef Session::ClientConnectedReportCb ClientConnectedReportCb; - typedef Session::AuthInfoReqCb AuthInfoReqCb; + using AuthInfo = Session::AuthInfo; explicit Connect(SessionImpl& session); ~Connect(); - template - void setClientConnectedReportCb(TFunc&& func) - { - m_clientConnectedCb = std::forward(func); - } - - template - void setAuthInfoReqCb(TFunc&& func) - { - m_authInfoReqCb = std::forward(func); - } - protected: virtual void tickImpl() override; virtual void brokerConnectionUpdatedImpl() override; @@ -78,8 +63,6 @@ class Connect : public SessionOp std::uint16_t m_keepAlive = 0; bool m_clean = false; State m_internalState; - ClientConnectedReportCb m_clientConnectedCb; - AuthInfoReqCb m_authInfoReqCb; }; } // namespace session_op From 5cc36db069f13b1afe985be2532f5100bf5d7fec Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sat, 11 May 2024 11:11:03 +1000 Subject: [PATCH 013/106] Unittesting the forwading encapsulation in the gateway. --- client/test/ClientBasic.th | 2 +- gateway/src/lib/session_op/Encapsulate.cpp | 16 +-- gateway/test/Session.th | 134 +++++++++++++++++---- gateway/test/TestMsgHandler.cpp | 51 ++++---- 4 files changed, 147 insertions(+), 56 deletions(-) diff --git a/client/test/ClientBasic.th b/client/test/ClientBasic.th index 3c7476c5..3590df8e 100644 --- a/client/test/ClientBasic.th +++ b/client/test/ClientBasic.th @@ -349,7 +349,7 @@ private: [state, dataProc](const std::uint8_t* buf, unsigned bufLen, bool broadcast) { if (!state->m_nextOutput.empty()) { - TS_TRACE("ERROR: unconsumed message!!!"); + TS_TRACE("ERROR: unconsumed message!"); if (dataProc != nullptr) { processOutput(*state, *dataProc, state->m_broadcast); } diff --git a/gateway/src/lib/session_op/Encapsulate.cpp b/gateway/src/lib/session_op/Encapsulate.cpp index 261eecd0..4863c0c7 100644 --- a/gateway/src/lib/session_op/Encapsulate.cpp +++ b/gateway/src/lib/session_op/Encapsulate.cpp @@ -73,7 +73,7 @@ void Encapsulate::handle(FwdMsg_SN& msg) break; } - std::tie(iter, std::ignore) = m_sessions.insert(std::make_pair(std::move(nodeId), std::make_unique())); + std::tie(iter, std::ignore) = m_sessions.insert(std::make_pair(nodeId, std::make_unique())); assert(iter != m_sessions.end()); auto sessionPtr = iter->second.get(); @@ -84,6 +84,12 @@ void Encapsulate::handle(FwdMsg_SN& msg) sessionPtr->setDefaultClientId(st.m_defaultClientId); sessionPtr->setPubOnlyKeepAlive(st.m_pubOnlyKeepAlive); + if (!session().reportFwdEncSessionCreated(sessionPtr)) { + m_sessions.erase(iter); + iter = m_sessions.end(); + break; + } + sessionPtr->setSendDataClientReqCb( [this, nodeId](const std::uint8_t* buf, std::size_t bufSize) { @@ -94,13 +100,7 @@ void Encapsulate::handle(FwdMsg_SN& msg) [this, sessionPtr]() { terminationReqFromSession(sessionPtr); - }); - - if (!session().reportFwdEncSessionCreated(sessionPtr)) { - m_sessions.erase(iter); - iter = m_sessions.end(); - break; - } + }); if ((!sessionPtr->isRunning()) && (!sessionPtr->start())) { // Error failed to start session; diff --git a/gateway/test/Session.th b/gateway/test/Session.th index f224d6fa..ef031ecd 100644 --- a/gateway/test/Session.th +++ b/gateway/test/Session.th @@ -51,6 +51,7 @@ public: void test26(); void test27(); void test28(); + void test29(); private: typedef std::unique_ptr SessionPtr; @@ -58,6 +59,7 @@ private: using TopicIdTypeVal = cc_mqttsn::field::TopicIdTypeVal; static const std::uint8_t DefaultGwId = 5; + static const std::uint8_t DefaultFwdNodeId = 1; static const std::uint16_t DefaultKeepAlivePeriod = 60; static const unsigned DefaultRetryPeriod = 15; static const unsigned DefaultRetryCount = 3; @@ -95,7 +97,8 @@ private: return stream.str(); } - static SessionPtr allocSession( + static void prepareSession( + cc_mqttsn_gateway::Session& session, State& state, TestMsgHandler& handler, const std::uint8_t gwId = DefaultGwId, @@ -103,14 +106,12 @@ private: const DataBuf* password = nullptr, bool connectToBroker = true) { - SessionPtr session(new cc_mqttsn_gateway::Session); - - session->setNextTickProgramReqCb( + session.setNextTickProgramReqCb( [&state](unsigned val) { state.m_tickReq.push_back(val); }); - session->setCancelTickWaitReqCb( + session.setCancelTickWaitReqCb( [&state]() -> unsigned { if (state.m_elapsed.empty()) { @@ -124,37 +125,38 @@ private: state.m_elapsed.pop_front(); return val; }); - session->setSendDataClientReqCb( + + session.setSendDataClientReqCb( [&state](const std::uint8_t* buf, std::size_t bufSize) { state.m_sentToClient.emplace_back(buf, buf + bufSize); }); - session->setSendDataBrokerReqCb( + session.setSendDataBrokerReqCb( [&state](const std::uint8_t* buf, std::size_t bufSize) { state.m_sentToBroker.emplace_back(buf, buf + bufSize); }); - session->setTerminationReqCb( + session.setTerminationReqCb( [&state]() { state.m_termRequests.push_back(true); }); - session->setBrokerReconnectReqCb( + session.setBrokerReconnectReqCb( [&state]() { state.m_brokerReconnectRequests.push_back(true); }); - session->setClientConnectedReportCb( + session.setClientConnectedReportCb( [&state](const std::string& clientId) { state.m_connectedClients.push_back(clientId); }); - session->setAuthInfoReqCb( + session.setAuthInfoReqCb( [username, password](const std::string&) -> cc_mqttsn_gateway::Session::AuthInfo { cc_mqttsn_gateway::Session::AuthInfo info; @@ -169,18 +171,20 @@ private: return info; }); - session->setFwdEncSessionCreatedReportCb( - [&state](cc_mqttsn_gateway::Session* fwdEncSession) + session.setFwdEncSessionCreatedReportCb( + [&state, &handler, gwId, username, password, connectToBroker](cc_mqttsn_gateway::Session* fwdEncSession) { TS_TRACE("New FWD Encapsulated Session"); auto iter = std::find(state.m_fwdEncSessions.begin(), state.m_fwdEncSessions.end(), fwdEncSession); TS_ASSERT_EQUALS(iter, state.m_fwdEncSessions.end()); + prepareSession(*fwdEncSession, state, handler, gwId, username, password, connectToBroker); + state.m_fwdEncSessions.push_back(fwdEncSession); return true; }); - session->setFwdEncSessionDeletedReportCb( + session.setFwdEncSessionDeletedReportCb( [&state](cc_mqttsn_gateway::Session* fwdEncSession) { TS_TRACE("Deleting FWD Encapsulated Session"); @@ -371,20 +375,33 @@ private: }); - session->setRetryPeriod(DefaultRetryPeriod); - session->setRetryCount(DefaultRetryCount); - session->setGatewayId(gwId); - session->setTopicIdAllocationRange(DefaultMinTopicId, DefaultMaxTopicId); - bool result = session->start(); + session.setRetryPeriod(DefaultRetryPeriod); + session.setRetryCount(DefaultRetryCount); + session.setGatewayId(gwId); + session.setTopicIdAllocationRange(DefaultMinTopicId, DefaultMaxTopicId); + bool result = session.start(); + TS_ASSERT(result); if (connectToBroker) { - session->setBrokerConnected(true); + session.setBrokerConnected(true); } - TS_ASSERT(result); - TS_ASSERT(session->isRunning()); + + TS_ASSERT(session.isRunning()); + } + + static SessionPtr allocSession( + State& state, + TestMsgHandler& handler, + const std::uint8_t gwId = DefaultGwId, + const std::string* username = nullptr, + const DataBuf* password = nullptr, + bool connectToBroker = true) + { + SessionPtr session(new cc_mqttsn_gateway::Session); + prepareSession(*session, state, handler, gwId, username, password, connectToBroker); TS_ASSERT(state.m_sentToClient.empty()); TS_ASSERT(state.m_sentToBroker.empty()); TS_ASSERT(state.m_tickReq.empty()); - TS_ASSERT(state.m_elapsed.empty()); + TS_ASSERT(state.m_elapsed.empty()); return session; } @@ -403,7 +420,7 @@ private: const DataBuf& buf, const std::string& msgStr) { - auto consumed = session.dataFromBroker(&buf[0], buf.size()); + auto consumed = session.dataFromBroker(buf.data(), buf.size()); TS_ASSERT_EQUALS(consumed, buf.size()); TS_TRACE("(BROKER) --> " + msgStr); @@ -452,13 +469,25 @@ private: static void verifySentToClient_ConnackMsg( State& state, TestMsgHandler& handler, - cc_mqttsn::field::ReturnCodeVal rc) + cc_mqttsn::field::ReturnCodeVal rc, + std::uint8_t fwdId = 0) { if (state.m_sentToClient.empty()) { TS_FAIL("No data was sent to client"); return; } + bool fwdSent = false; + auto oldFwCb = + handler.setFwdMsgHandler( + [&](const FwdMsg_SN& msg) + { + fwdSent = true; + TS_TRACE("(CLIENT) <-- FWD"); + TS_ASSERT(!msg.field_nodeId().value().empty()); + TS_ASSERT_EQUALS(msg.field_nodeId().value().front(), fwdId); + }); + bool sent = false; auto oldCb = handler.setConnackMsgHandler( @@ -471,9 +500,12 @@ private: handler.processDataForClient(state.m_sentToClient.front()); TS_ASSERT(sent); + TS_ASSERT((fwdId == 0) || (fwdSent)); + TS_ASSERT((fwdId != 0) || (!fwdSent)); state.m_sentToClient.pop_front(); handler.setConnackMsgHandler(std::move(oldCb)); + handler.setFwdMsgHandler(std::move(oldFwCb)); } static void verifySentToClient_WilltopicreqMsg(State& state, TestMsgHandler& handler) @@ -1343,6 +1375,12 @@ private: state.m_connectedClients.pop_front(); } + static void verifyFwdSession(State& state, unsigned count = 1U) + { + TS_TRACE("[Forward sesssions]: " + std::to_string(state.m_fwdEncSessions.size())); + TS_ASSERT_EQUALS(state.m_fwdEncSessions.size(), count); + } + static void verifyTermReq(State& state) { if (state.m_termRequests.empty()) { @@ -3043,3 +3081,49 @@ void SessionTest::test28() verifyConnectedClient(state, DefaultClientId); verifyNoOtherEvent(state, handler); } + +void SessionTest::test29() +{ + TestMsgHandler handler; + State state; + auto session = allocSession(state, handler, DefaultGwId, nullptr, nullptr, false); + verifyNoOtherEvent(state, handler); + + auto connectMsg = handler.prepareClientConnect(DefaultClientId, DefaultKeepAlivePeriod, false, true); + auto fwdConnectMsg = handler.prepareClientFwd(DefaultFwdNodeId, connectMsg); + dataFromClient(*session, fwdConnectMsg, "FWD + CONNECT"); + verifyFwdSession(state); + + state.m_elapsed.push_back(100); + auto* fwdSession = state.m_fwdEncSessions.front(); + + static const std::string PredefinedTopic("predefined/topic"); + static const std::uint16_t PredefinedTopicId = 0x1111; + fwdSession->addPredefinedTopic(PredefinedTopic, PredefinedTopicId); + fwdSession->setBrokerConnected(true); + verifySentToBroker_ConnectMsg(state, handler, DefaultClientId, DefaultKeepAlivePeriod, true); + + state.m_elapsed.push_back(100); + using ConnackResponseCodeVal = ConnackMsg::Field_returnCode::ValueType; + auto connackMsg = handler.prepareBrokerConnack(ConnackResponseCodeVal::Accepted); + dataFromBroker(*fwdSession, connackMsg, "CONNACK"); + verifySentToClient_ConnackMsg(state, handler, cc_mqttsn::field::ReturnCodeVal::Accepted, DefaultFwdNodeId); + verifyConnectedClient(state, DefaultClientId); + + static const auto Qos = cc_mqttsn::field::QosVal::ExactlyOnceDelivery; + static const std::uint16_t SubMsgId1 = 0x1234; + auto subMsg1 = handler.prepareClientSubscribe(PredefinedTopicId, SubMsgId1, Qos); + auto fwdSubMsg1 = handler.prepareClientFwd(DefaultFwdNodeId, subMsg1); + dataFromClient(*session, fwdSubMsg1, "FWD+SUBSCRIBE"); + verifySentToBroker_SubscribeMsg(state, handler, PredefinedTopic, translateQos(Qos), SubMsgId1); + + auto fwdConnectMsg2 = handler.prepareClientFwd(DefaultFwdNodeId + 1, connectMsg); + dataFromClient(*session, fwdConnectMsg2, "FWD + CONNECT"); + verifyFwdSession(state, 2U); + + state.m_elapsed.push_back(100); + auto* fwdSession2 = state.m_fwdEncSessions.back(); + + fwdSession2->setBrokerConnected(true); + verifySentToBroker_ConnectMsg(state, handler, DefaultClientId, DefaultKeepAlivePeriod, true); +} \ No newline at end of file diff --git a/gateway/test/TestMsgHandler.cpp b/gateway/test/TestMsgHandler.cpp index a5f18ef5..d0097147 100644 --- a/gateway/test/TestMsgHandler.cpp +++ b/gateway/test/TestMsgHandler.cpp @@ -32,40 +32,47 @@ std::uint16_t shortTopicNameToId(const std::string& topic) } // namespace -template -void TestMsgHandler::processOutputInternal(TStack& stack, const DataBuf& data) +template +void TestMsgHandler::processOutputInternal(TFrame& frame, const DataBuf& data) { - typedef typename std::decay::type StackType; - typedef typename StackType::MsgPtr MsgPtr; + typedef typename std::decay::type FrameType; + typedef typename FrameType::MsgPtr MsgPtr; typedef typename MsgPtr::element_type MsgType; - auto iter = comms::readIteratorFor(&data[0]); - MsgPtr msg; - - [[maybe_unused]] auto es = stack.read(msg, iter, data.size()); - if (es != comms::ErrorStatus::Success) { - std::cout << "es=" << static_cast(es) << ": Output buffer: " << std::hex; - std::copy(data.begin(), data.end(), std::ostream_iterator(std::cout, " ")); - std::cout << std::dec << std::endl; + auto iter = comms::readIteratorFor(data.data()); + std::size_t consumed = 0U; + while (consumed < data.size()) { + auto begIter = iter; + auto remLen = data.size() - consumed; + MsgPtr msg; + + auto es = frame.read(msg, iter, remLen); + if (es != comms::ErrorStatus::Success) { + std::cout << "es=" << static_cast(es) << ": Output buffer: " << std::hex; + std::copy_n(begIter, remLen, std::ostream_iterator(std::cout, " ")); + std::cout << std::dec << std::endl; + } + assert(es == comms::ErrorStatus::Success); + assert(msg); + msg->dispatch(*this); + consumed += static_cast(std::distance(begIter, iter)); } - assert(es == comms::ErrorStatus::Success); - assert(msg); - assert(static_cast(std::distance(comms::readIteratorFor(&data[0]), iter)) == data.size()); - msg->dispatch(*this); + + assert(static_cast(std::distance(comms::readIteratorFor(data.data()), iter)) == data.size()); } -template -TestMsgHandler::DataBuf TestMsgHandler::prepareInputInternal(TStack& stack, const TMsg& msg) +template +TestMsgHandler::DataBuf TestMsgHandler::prepareInputInternal(TFrame& frame, const TMsg& msg) { - typedef typename std::decay::type StackType; - typedef typename StackType::MsgPtr::element_type MsgType; + typedef typename std::decay::type FrameType; + typedef typename FrameType::MsgPtr::element_type MsgType; DataBuf buf; - buf.resize(stack.length(msg)); + buf.resize(frame.length(msg)); auto iter = comms::writeIteratorFor(&buf[0]); - [[maybe_unused]] auto es = stack.write(msg, iter, buf.size()); + [[maybe_unused]] auto es = frame.write(msg, iter, buf.size()); assert(es == comms::ErrorStatus::Success); assert(buf.size() == static_cast(std::distance(comms::writeIteratorFor(&buf[0]), iter))); return buf; From 6bb9521aaa7f838e97127fa7f71e643cb01ef572 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Mon, 13 May 2024 09:19:39 +1000 Subject: [PATCH 014/106] Added forward encapsuation info to the gateway doxygen documentation. --- gateway/doc/namespaces.dox | 4 ++ gateway/doc/session.dox | 67 +++++++++++++++++-- gateway/include/cc_mqttsn_gateway/Session.h | 22 ++++++ .../include/cc_mqttsn_gateway/gateway_all.h | 40 +++++++++++ gateway/src/lib/gateway_all.cpp | 51 ++++++++++++++ gateway/test/Session.th | 26 ++++--- 6 files changed, 192 insertions(+), 18 deletions(-) create mode 100644 gateway/doc/namespaces.dox diff --git a/gateway/doc/namespaces.dox b/gateway/doc/namespaces.dox new file mode 100644 index 00000000..707e99d4 --- /dev/null +++ b/gateway/doc/namespaces.dox @@ -0,0 +1,4 @@ +/// @namespace cc_mqttsn_gateway +/// @brief Main namespace for all classes / functions of the gateway library. + + diff --git a/gateway/doc/session.dox b/gateway/doc/session.dox index d19287c2..4616e3bc 100644 --- a/gateway/doc/session.dox +++ b/gateway/doc/session.dox @@ -18,6 +18,10 @@ /// class. The destruction of the object will clean up all acquired resources. /// @code /// std::unique_ptr session(new cc_mqttsn_gateway::Session()); +/// +/// // Using raw pointer to force doxygen to propely generate links to the +/// // member functions in the code snippets below. +/// cc_mqttsn_gateway::Session* session = new cc_mqttsn_gateway::Session(); /// @endcode /// /// When using @b C interface, the allocation is performed using cc_mqttsn_gw_session_alloc() @@ -432,7 +436,7 @@ /// @code /// void my_auth_callback( /// void* userData, -/// CC_MqttsnSessionHandle session, +/// CC_MqttsnSessionHandle handle, /// const char* clientId, /// const char** username, /// const unsigned char** password, @@ -466,7 +470,7 @@ /// /// @b C interface: /// @code -/// void my_client_connect_report(void* userData, CC_MqttsnSessionHandle session, const char* clientId) +/// void my_client_connect_report(void* userData, CC_MqttsnSessionHandle handle, const char* clientId) /// { /// if (!cc_mqttsn_gw_session_add_predefined_topic(handle, "client/specific/predefined/topic", 2222)) { /// ... /* report error */ @@ -519,7 +523,7 @@ /// session->setPubOnlyKeepAlive(100); // 100 seconds /// @endcode /// -/// @b C++ interface: +/// @b C interface: /// @code /// cc_mqttsn_gw_session_set_pub_only_keep_alive(handle, 100); /* 100 seconds */ /// @endcode @@ -546,4 +550,59 @@ /// cc_mqttsn_gw_session_set_sleeping_client_msg_limit(handle, 1000); /* no more that 1000 messages */ /// @endcode /// - +/// @section cc_mqttsn_gw_session_page_fwd_enc Forwarder Encapsulation Support +/// In case the gateway need to support forwarder encapsulation functionality there +/// is a need to set extra two callbacks. +/// +/// @b C++ interface: +/// @code +/// session->setFwdEncSessionCreatedReportCb( +/// [](cc_mqttsn_gateway::Session* fwdSession) -> bool +/// { +/// ... // Perform the session configuration and set all the necessary callbacks +/// return true; +/// }); +/// +/// session->setFwdEncSessionDeletedReportCb( +/// [](cc_mqttsn_gateway::Session* fwdSession) -> bool +/// { +/// ... // Remove any reference to fwdSession in the data structures +/// }); +/// @endcode +/// +/// @b C interface: +/// @code +/// bool my_fwd_enc_session_created_report_cb(void* userData, CC_MqttsnSessionHandle handle) +/// { +/// ... // Perform the session configuration and set all the necessary callbacks +/// return true; +/// }); +/// +/// void my_fwd_enc_session_deleted_report_cb(void* userData, CC_MqttsnSessionHandle handle) +/// { +/// ... // Remove any reference to handle in the data structures +/// }); +/// +/// cc_mqttsn_gw_session_set_fwd_enc_session_created_cb(handle, &my_fwd_enc_session_created_report_cb, someDserData); +/// cc_mqttsn_gw_session_set_fwd_enc_session_deleted_cb(handle, &my_fwd_enc_session_deleted_report_cb, someDserData); +/// @endcode +/// +/// When the new forward encapsulation session creation is reported, the application is +/// responsible to perform the necessary session configuration as well as set all +/// the necessary callbacks, except the @ref cc_mqttsn_gw_session_page_send_client +/// and the @ref cc_mqttsn_gw_session_page_term. These callbacks will be +/// set by the reporting session. +/// +/// The reported forward encapsulation session object is owned by the reporting session +/// and its deletion will be reported using the deletion report callback. +/// +/// The gateway application is responsible to manage the connection to the broker +/// as well as timers of the reported forward encapsulation session. +/// +/// When the forward encapsulation session is reported, it's not +/// @ref cc_mqttsn_gw_session_page_start "started" yet. It will be when the +/// callback function returns. However, the +/// gateway application is expected to initiate asynchronous connection to the +/// broker and report the @ref cc_mqttsn_gw_session_page_broker_conn "connectivity" +/// later on. The @b important part is that broker connectivity must be reported +/// @b after the callback function returns. diff --git a/gateway/include/cc_mqttsn_gateway/Session.h b/gateway/include/cc_mqttsn_gateway/Session.h index ece3ad1c..82b78e49 100644 --- a/gateway/include/cc_mqttsn_gateway/Session.h +++ b/gateway/include/cc_mqttsn_gateway/Session.h @@ -81,7 +81,21 @@ class Session /// @return Authentication information using AuthInfoReqCb = std::function; + /// @brief Type of callback used to notify the application about forwarding encapsulation session being created. + /// @details The application is responsible to perform the necessary session configuration + /// as well as set all the callbacks except the one set by the @ref setSendDataClientReqCb() + /// and @ref setTerminationReqCb(). The data sent to the client as well as the session + /// termination are managed by the calling session object. The application is responsible + /// to manage the timer as well as broker connection of the reported session. + /// @param[in] session Pointer to the created session object. Owned by the session object invoking the callback, + /// mustn't be deleted by the application. + /// @return @b true in case of success, @b false in case of falure. using FwdEncSessionCreatedReportCb = std::function; + + /// @brief Type of callback used to notify the application about forwarding encapsulation session being deleted. + /// @details The application is responsible to remove any reference to the session object from its internal data structes. + /// @param[in] session Pointer to the created session object. Owned by the session object invoking the callback, + /// mustn't be deleted by the application. using FwdEncSessionDeletedReportCb = std::function; /// @brief Default constructor @@ -147,7 +161,15 @@ class Session /// @param[in] func R-value reference to the callback object void setAuthInfoReqCb(AuthInfoReqCb&& func); + /// @brief Set the callback to be invoked when the forwarding encapsulation session + /// is detected and to notify application about such session creation. + /// @details When not set, the forwarding enapsulation messages will be ignored + /// @param[in] func R-value reference to the callback object void setFwdEncSessionCreatedReportCb(FwdEncSessionCreatedReportCb&& func); + + /// @brief Set the callback to be invoked when the forwarding encapsulation session + /// is about to be deleted. + /// @param[in] func R-value reference to the callback object void setFwdEncSessionDeletedReportCb(FwdEncSessionDeletedReportCb&& func); /// @brief Set gateway numeric ID to be reported when requested. diff --git a/gateway/include/cc_mqttsn_gateway/gateway_all.h b/gateway/include/cc_mqttsn_gateway/gateway_all.h index 3b3d959d..849b48b9 100644 --- a/gateway/include/cc_mqttsn_gateway/gateway_all.h +++ b/gateway/include/cc_mqttsn_gateway/gateway_all.h @@ -164,6 +164,23 @@ typedef void (*CC_MqttsnSessionBrokerReconnectReqCb)(void* userData, CC_MqttsnSe /// @param[in] clientId Client ID typedef void (*CC_MqttsnSessionClientConnectReportCb)(void* userData, CC_MqttsnSessionHandle session, const char* clientId); +/// @brief Type of callback used to report forwarding encapsulated session creation. +/// @details The application is responsible to perform the necessary session configuration +/// as well as set all the callbacks except the one set by the @ref cc_mqttsn_gw_session_set_send_data_to_client_cb() +/// and @ref cc_mqttsn_gw_session_set_term_req_cb(). The data sent to the client as well as the session +/// termination are managed by the calling session object. The application is responsible +/// to manage the timer as well as broker connection of the reported session. +/// @param[in] userData User data passed as the last parameter to the setting function. +/// @param[in] session Handle of created session object +/// @return @b true in case of success, @b false in case of falure. +typedef bool (*CC_MqttsnSessionFwdEncSessionCreatedCb)(void* userData, CC_MqttsnSessionHandle session); + +/// @brief Type of callback used to report forwarding encapsulated session about to be deleted. +/// @details The application is responsible to remove any reference to the session object from its internal data structes. +/// @param[in] userData User data passed as the last parameter to the setting function. +/// @param[in] session Handle of session object about to be deleted +typedef void (*CC_MqttsnSessionFwdEncSessionDeletedCb)(void* userData, CC_MqttsnSessionHandle session); + /// @brief Type of callback used to request authentication information of /// the client that is trying to connect. /// @param[in] userData User data passed as the last parameter to the setting function. @@ -297,6 +314,29 @@ void cc_mqttsn_gw_session_set_auth_info_req_cb( CC_MqttsnSessionAuthInfoReqCb cb, void* data); +/// @brief Set the callback to be invoked when the forwarding encapsulation session +/// is detected and to notify application about such session creation. +/// @details When not set, the forwarding enapsulation messages will be ignored +/// @param[in] session Handle returned by cc_mqttsn_gw_session_alloc() function. +/// @param[in] cb Pointer to callback function +/// @param[in] data Pointer to any user data, will be passed back as first +/// parameter to the callback. +void cc_mqttsn_gw_session_set_fwd_enc_session_created_cb( + CC_MqttsnSessionHandle session, + CC_MqttsnSessionFwdEncSessionCreatedCb cb, + void* data); + +/// @brief Set the callback to be invoked when the forwarding encapsulation session +/// is about to be deleted. +/// @param[in] session Handle returned by cc_mqttsn_gw_session_alloc() function. +/// @param[in] cb Pointer to callback function +/// @param[in] data Pointer to any user data, will be passed back as first +/// parameter to the callback. +void cc_mqttsn_gw_session_set_fwd_enc_session_deleted_cb( + CC_MqttsnSessionHandle session, + CC_MqttsnSessionFwdEncSessionDeletedCb cb, + void* data); + /// @brief Set gateway numeric ID to be reported when requested. /// @details If not set, default value @b 0 is assumed. /// @param[in] session Handle returned by cc_mqttsn_gw_session_alloc() function. diff --git a/gateway/src/lib/gateway_all.cpp b/gateway/src/lib/gateway_all.cpp index ccbfa64e..f4c68ae8 100644 --- a/gateway/src/lib/gateway_all.cpp +++ b/gateway/src/lib/gateway_all.cpp @@ -281,6 +281,57 @@ void cc_mqttsn_gw_session_set_auth_info_req_cb( }); } +void cc_mqttsn_gw_session_set_fwd_enc_session_created_cb( + CC_MqttsnSessionHandle session, + CC_MqttsnSessionFwdEncSessionCreatedCb cb, + void* data) +{ + if (session.obj == nullptr) { + return; + } + + if (cb == nullptr) { + reinterpret_cast(session.obj)->setFwdEncSessionCreatedReportCb(nullptr); + return; + } + + reinterpret_cast(session.obj)->setFwdEncSessionCreatedReportCb( + [cb, data](cc_mqttsn_gateway::Session* sessionPtr) + { + CC_MqttsnSessionHandle encSession; + encSession.obj = sessionPtr; + return cb(data, encSession); + }); +} + +void cc_mqttsn_gw_session_set_fwd_enc_session_deleted_cb( + CC_MqttsnSessionHandle session, + CC_MqttsnSessionFwdEncSessionDeletedCb cb, + void* data) +{ + if (session.obj == nullptr) { + return; + } + + if (cb == nullptr) { + reinterpret_cast(session.obj)->setFwdEncSessionDeletedReportCb(nullptr); + return; + } + + reinterpret_cast(session.obj)->setFwdEncSessionDeletedReportCb( + [cb, data](cc_mqttsn_gateway::Session* sessionPtr) + { + CC_MqttsnSessionHandle encSession; + encSession.obj = sessionPtr; + cb(data, encSession); + }); +} + +void cc_mqttsn_gw_session_set_fwd_enc_session_deleted_cb( + CC_MqttsnSessionHandle session, + CC_MqttsnSessionFwdEncSessionDeletedCb cb, + void* data); + void cc_mqttsn_gw_session_set_id(CC_MqttsnSessionHandle session, unsigned char id) { if (session.obj == nullptr) { diff --git a/gateway/test/Session.th b/gateway/test/Session.th index ef031ecd..a49ddb24 100644 --- a/gateway/test/Session.th +++ b/gateway/test/Session.th @@ -103,8 +103,7 @@ private: TestMsgHandler& handler, const std::uint8_t gwId = DefaultGwId, const std::string* username = nullptr, - const DataBuf* password = nullptr, - bool connectToBroker = true) + const DataBuf* password = nullptr) { session.setNextTickProgramReqCb( [&state](unsigned val) { @@ -172,13 +171,13 @@ private: }); session.setFwdEncSessionCreatedReportCb( - [&state, &handler, gwId, username, password, connectToBroker](cc_mqttsn_gateway::Session* fwdEncSession) + [&state, &handler, gwId, username, password](cc_mqttsn_gateway::Session* fwdEncSession) { TS_TRACE("New FWD Encapsulated Session"); auto iter = std::find(state.m_fwdEncSessions.begin(), state.m_fwdEncSessions.end(), fwdEncSession); TS_ASSERT_EQUALS(iter, state.m_fwdEncSessions.end()); - prepareSession(*fwdEncSession, state, handler, gwId, username, password, connectToBroker); + prepareSession(*fwdEncSession, state, handler, gwId, username, password); state.m_fwdEncSessions.push_back(fwdEncSession); return true; @@ -379,13 +378,6 @@ private: session.setRetryCount(DefaultRetryCount); session.setGatewayId(gwId); session.setTopicIdAllocationRange(DefaultMinTopicId, DefaultMaxTopicId); - bool result = session.start(); - TS_ASSERT(result); - if (connectToBroker) { - session.setBrokerConnected(true); - } - - TS_ASSERT(session.isRunning()); } static SessionPtr allocSession( @@ -397,11 +389,18 @@ private: bool connectToBroker = true) { SessionPtr session(new cc_mqttsn_gateway::Session); - prepareSession(*session, state, handler, gwId, username, password, connectToBroker); + prepareSession(*session, state, handler, gwId, username, password); TS_ASSERT(state.m_sentToClient.empty()); TS_ASSERT(state.m_sentToBroker.empty()); TS_ASSERT(state.m_tickReq.empty()); - TS_ASSERT(state.m_elapsed.empty()); + TS_ASSERT(state.m_elapsed.empty()); + bool result = session->start(); + TS_ASSERT(result); + if (connectToBroker) { + session->setBrokerConnected(true); + } + + TS_ASSERT(session->isRunning()); return session; } @@ -3096,7 +3095,6 @@ void SessionTest::test29() state.m_elapsed.push_back(100); auto* fwdSession = state.m_fwdEncSessions.front(); - static const std::string PredefinedTopic("predefined/topic"); static const std::uint16_t PredefinedTopicId = 0x1111; fwdSession->addPredefinedTopic(PredefinedTopic, PredefinedTopicId); From c9d9e1cda3c94181230f421e16bf8f39c492438f Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 14 May 2024 08:13:07 +1000 Subject: [PATCH 015/106] Gateway directory structure re-organizing. --- gateway/CMakeLists.txt | 49 +------------------ gateway/{src => }/app/CMakeLists.txt | 0 gateway/{src => }/app/udp/CMakeLists.txt | 4 +- gateway/{src => }/app/udp/GatewayWrapper.cpp | 0 gateway/{src => }/app/udp/GatewayWrapper.h | 0 gateway/{src => }/app/udp/Mgr.cpp | 0 gateway/{src => }/app/udp/Mgr.h | 0 gateway/{src => }/app/udp/SessionWrapper.cpp | 0 gateway/{src => }/app/udp/SessionWrapper.h | 0 gateway/{src => }/app/udp/main.cpp | 0 gateway/etc/cc_mqttsn_gateway.conf.example | 2 +- gateway/lib/CMakeLists.txt | 48 ++++++++++++++++++ gateway/{ => lib}/doc/config.dox | 0 gateway/{ => lib}/doc/doxygen.conf | 0 gateway/{ => lib}/doc/gateway.dox | 0 gateway/{ => lib}/doc/layout.xml | 0 gateway/{ => lib}/doc/main.dox | 0 gateway/{ => lib}/doc/namespaces.dox | 0 gateway/{ => lib}/doc/session.dox | 2 +- .../include/cc_mqttsn_gateway/Config.h | 0 .../include/cc_mqttsn_gateway/Gateway.h | 0 .../include/cc_mqttsn_gateway/Session.h | 0 .../include/cc_mqttsn_gateway/gateway_all.h | 0 .../include/cc_mqttsn_gateway/gateway_allpp.h | 0 .../include/cc_mqttsn_gateway/version.h | 0 .../include/cc_mqttsn_gateway/versionpp.h | 0 gateway/{src/lib => lib/src}/CMakeLists.txt | 3 +- gateway/{src/lib => lib/src}/Config.cpp | 0 gateway/{src/lib => lib/src}/ConfigImpl.cpp | 2 +- gateway/{src/lib => lib/src}/ConfigImpl.h | 0 gateway/{src/lib => lib/src}/Gateway.cpp | 0 gateway/{src/lib => lib/src}/GatewayImpl.cpp | 0 gateway/{src/lib => lib/src}/GatewayImpl.h | 0 gateway/{src/lib => lib/src}/MsgHandler.h | 0 gateway/{src/lib => lib/src}/RegMgr.cpp | 0 gateway/{src/lib => lib/src}/RegMgr.h | 0 gateway/{src/lib => lib/src}/Session.cpp | 0 gateway/{src/lib => lib/src}/SessionImpl.cpp | 0 gateway/{src/lib => lib/src}/SessionImpl.h | 0 gateway/{src/lib => lib/src}/SessionOp.cpp | 0 gateway/{src/lib => lib/src}/SessionOp.h | 0 gateway/{src/lib => lib/src}/common.h | 0 gateway/{src/lib => lib/src}/gateway_all.c | 0 gateway/{src/lib => lib/src}/gateway_all.cpp | 0 gateway/{src/lib => lib/src}/messages.h | 0 .../lib => lib/src}/session_op/Asleep.cpp | 0 .../{src/lib => lib/src}/session_op/Asleep.h | 0 .../src}/session_op/AsleepMonitor.cpp | 0 .../src}/session_op/AsleepMonitor.h | 0 .../lib => lib/src}/session_op/Connect.cpp | 0 .../{src/lib => lib/src}/session_op/Connect.h | 0 .../lib => lib/src}/session_op/Disconnect.cpp | 0 .../lib => lib/src}/session_op/Disconnect.h | 0 .../src}/session_op/Encapsulate.cpp | 0 .../lib => lib/src}/session_op/Encapsulate.h | 0 .../lib => lib/src}/session_op/Forward.cpp | 0 .../{src/lib => lib/src}/session_op/Forward.h | 0 .../lib => lib/src}/session_op/PubRecv.cpp | 0 .../{src/lib => lib/src}/session_op/PubRecv.h | 0 .../lib => lib/src}/session_op/PubSend.cpp | 0 .../{src/lib => lib/src}/session_op/PubSend.h | 0 .../lib => lib/src}/session_op/WillUpdate.cpp | 0 .../lib => lib/src}/session_op/WillUpdate.h | 0 gateway/{ => lib}/test/CMakeLists.txt | 0 gateway/{ => lib}/test/Gateway.th | 0 gateway/{ => lib}/test/Session.th | 0 gateway/{ => lib}/test/TestMsgHandler.cpp | 0 gateway/{ => lib}/test/TestMsgHandler.h | 0 gateway/src/CMakeLists.txt | 2 - 69 files changed, 56 insertions(+), 56 deletions(-) rename gateway/{src => }/app/CMakeLists.txt (100%) rename gateway/{src => }/app/udp/CMakeLists.txt (84%) rename gateway/{src => }/app/udp/GatewayWrapper.cpp (100%) rename gateway/{src => }/app/udp/GatewayWrapper.h (100%) rename gateway/{src => }/app/udp/Mgr.cpp (100%) rename gateway/{src => }/app/udp/Mgr.h (100%) rename gateway/{src => }/app/udp/SessionWrapper.cpp (100%) rename gateway/{src => }/app/udp/SessionWrapper.h (100%) rename gateway/{src => }/app/udp/main.cpp (100%) create mode 100644 gateway/lib/CMakeLists.txt rename gateway/{ => lib}/doc/config.dox (100%) rename gateway/{ => lib}/doc/doxygen.conf (100%) rename gateway/{ => lib}/doc/gateway.dox (100%) rename gateway/{ => lib}/doc/layout.xml (100%) rename gateway/{ => lib}/doc/main.dox (100%) rename gateway/{ => lib}/doc/namespaces.dox (100%) rename gateway/{ => lib}/doc/session.dox (99%) rename gateway/{ => lib}/include/cc_mqttsn_gateway/Config.h (100%) rename gateway/{ => lib}/include/cc_mqttsn_gateway/Gateway.h (100%) rename gateway/{ => lib}/include/cc_mqttsn_gateway/Session.h (100%) rename gateway/{ => lib}/include/cc_mqttsn_gateway/gateway_all.h (100%) rename gateway/{ => lib}/include/cc_mqttsn_gateway/gateway_allpp.h (100%) rename gateway/{ => lib}/include/cc_mqttsn_gateway/version.h (100%) rename gateway/{ => lib}/include/cc_mqttsn_gateway/versionpp.h (100%) rename gateway/{src/lib => lib/src}/CMakeLists.txt (94%) rename gateway/{src/lib => lib/src}/Config.cpp (100%) rename gateway/{src/lib => lib/src}/ConfigImpl.cpp (99%) rename gateway/{src/lib => lib/src}/ConfigImpl.h (100%) rename gateway/{src/lib => lib/src}/Gateway.cpp (100%) rename gateway/{src/lib => lib/src}/GatewayImpl.cpp (100%) rename gateway/{src/lib => lib/src}/GatewayImpl.h (100%) rename gateway/{src/lib => lib/src}/MsgHandler.h (100%) rename gateway/{src/lib => lib/src}/RegMgr.cpp (100%) rename gateway/{src/lib => lib/src}/RegMgr.h (100%) rename gateway/{src/lib => lib/src}/Session.cpp (100%) rename gateway/{src/lib => lib/src}/SessionImpl.cpp (100%) rename gateway/{src/lib => lib/src}/SessionImpl.h (100%) rename gateway/{src/lib => lib/src}/SessionOp.cpp (100%) rename gateway/{src/lib => lib/src}/SessionOp.h (100%) rename gateway/{src/lib => lib/src}/common.h (100%) rename gateway/{src/lib => lib/src}/gateway_all.c (100%) rename gateway/{src/lib => lib/src}/gateway_all.cpp (100%) rename gateway/{src/lib => lib/src}/messages.h (100%) rename gateway/{src/lib => lib/src}/session_op/Asleep.cpp (100%) rename gateway/{src/lib => lib/src}/session_op/Asleep.h (100%) rename gateway/{src/lib => lib/src}/session_op/AsleepMonitor.cpp (100%) rename gateway/{src/lib => lib/src}/session_op/AsleepMonitor.h (100%) rename gateway/{src/lib => lib/src}/session_op/Connect.cpp (100%) rename gateway/{src/lib => lib/src}/session_op/Connect.h (100%) rename gateway/{src/lib => lib/src}/session_op/Disconnect.cpp (100%) rename gateway/{src/lib => lib/src}/session_op/Disconnect.h (100%) rename gateway/{src/lib => lib/src}/session_op/Encapsulate.cpp (100%) rename gateway/{src/lib => lib/src}/session_op/Encapsulate.h (100%) rename gateway/{src/lib => lib/src}/session_op/Forward.cpp (100%) rename gateway/{src/lib => lib/src}/session_op/Forward.h (100%) rename gateway/{src/lib => lib/src}/session_op/PubRecv.cpp (100%) rename gateway/{src/lib => lib/src}/session_op/PubRecv.h (100%) rename gateway/{src/lib => lib/src}/session_op/PubSend.cpp (100%) rename gateway/{src/lib => lib/src}/session_op/PubSend.h (100%) rename gateway/{src/lib => lib/src}/session_op/WillUpdate.cpp (100%) rename gateway/{src/lib => lib/src}/session_op/WillUpdate.h (100%) rename gateway/{ => lib}/test/CMakeLists.txt (100%) rename gateway/{ => lib}/test/Gateway.th (100%) rename gateway/{ => lib}/test/Session.th (100%) rename gateway/{ => lib}/test/TestMsgHandler.cpp (100%) rename gateway/{ => lib}/test/TestMsgHandler.h (100%) delete mode 100644 gateway/src/CMakeLists.txt diff --git a/gateway/CMakeLists.txt b/gateway/CMakeLists.txt index b71b599f..47428ffe 100644 --- a/gateway/CMakeLists.txt +++ b/gateway/CMakeLists.txt @@ -2,52 +2,7 @@ if (NOT CC_MQTTSN_BUILD_GATEWAY) return () endif () -###################################################################### - -find_package(LibComms REQUIRED) -find_package(cc_mqttsn REQUIRED) -find_package(cc_mqtt311 REQUIRED) - -###################################################################### - -set (GATEWAY_INC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) - -if (CMAKE_COMPILER_IS_GNUCC) - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftemplate-backtrace-limit=0") -endif () - set (MQTTSN_GATEWAY_LIB_NAME "cc_mqttsn_gateway") -add_subdirectory (src) -add_subdirectory (test) - -install ( - DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/cc_mqttsn_gateway - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) - -install ( - DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/etc/ - DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/cc_mqttsn_gateway -) - -###################################################################### - -find_package (Doxygen) -if (DOXYGEN_FOUND) - set (doc_output_dir "${CMAKE_INSTALL_FULL_DATAROOTDIR}/doc/cc_mqttsn_gateway") - make_directory (${doc_output_dir}) - - set (match_str "OUTPUT_DIRECTORY[^\n]*") - set (replacement_str "OUTPUT_DIRECTORY = ${doc_output_dir}") - set (output_file "${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf") - - set (config_file "${CMAKE_CURRENT_SOURCE_DIR}/doc/doxygen.conf") - file (READ ${config_file} config_text) - string (REGEX REPLACE "${match_str}" "${replacement_str}" modified_config_text "${config_text}") - file (WRITE "${output_file}" "${modified_config_text}") - - add_custom_target ("doc_mqttsn_gateway" - COMMAND ${DOXYGEN_EXECUTABLE} ${output_file} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) -endif () +add_subdirectory (lib) +add_subdirectory (app) \ No newline at end of file diff --git a/gateway/src/app/CMakeLists.txt b/gateway/app/CMakeLists.txt similarity index 100% rename from gateway/src/app/CMakeLists.txt rename to gateway/app/CMakeLists.txt diff --git a/gateway/src/app/udp/CMakeLists.txt b/gateway/app/udp/CMakeLists.txt similarity index 84% rename from gateway/src/app/udp/CMakeLists.txt rename to gateway/app/udp/CMakeLists.txt index b6a3ca56..bb195c6a 100644 --- a/gateway/src/app/udp/CMakeLists.txt +++ b/gateway/app/udp/CMakeLists.txt @@ -26,10 +26,8 @@ function (bin_gateway_udp) SessionWrapper.h ) - #qt5_add_resources(resources ${CMAKE_CURRENT_SOURCE_DIR}/ui.qrc) - add_executable(${name} ${src} ${moc}) - target_link_libraries(${name} ${MQTTSN_GATEWAY_LIB_NAME} Qt5::Network Qt5::Core) + target_link_libraries(${name} cc::${MQTTSN_GATEWAY_LIB_NAME} Qt5::Network Qt5::Core) install ( TARGETS ${name} diff --git a/gateway/src/app/udp/GatewayWrapper.cpp b/gateway/app/udp/GatewayWrapper.cpp similarity index 100% rename from gateway/src/app/udp/GatewayWrapper.cpp rename to gateway/app/udp/GatewayWrapper.cpp diff --git a/gateway/src/app/udp/GatewayWrapper.h b/gateway/app/udp/GatewayWrapper.h similarity index 100% rename from gateway/src/app/udp/GatewayWrapper.h rename to gateway/app/udp/GatewayWrapper.h diff --git a/gateway/src/app/udp/Mgr.cpp b/gateway/app/udp/Mgr.cpp similarity index 100% rename from gateway/src/app/udp/Mgr.cpp rename to gateway/app/udp/Mgr.cpp diff --git a/gateway/src/app/udp/Mgr.h b/gateway/app/udp/Mgr.h similarity index 100% rename from gateway/src/app/udp/Mgr.h rename to gateway/app/udp/Mgr.h diff --git a/gateway/src/app/udp/SessionWrapper.cpp b/gateway/app/udp/SessionWrapper.cpp similarity index 100% rename from gateway/src/app/udp/SessionWrapper.cpp rename to gateway/app/udp/SessionWrapper.cpp diff --git a/gateway/src/app/udp/SessionWrapper.h b/gateway/app/udp/SessionWrapper.h similarity index 100% rename from gateway/src/app/udp/SessionWrapper.h rename to gateway/app/udp/SessionWrapper.h diff --git a/gateway/src/app/udp/main.cpp b/gateway/app/udp/main.cpp similarity index 100% rename from gateway/src/app/udp/main.cpp rename to gateway/app/udp/main.cpp diff --git a/gateway/etc/cc_mqttsn_gateway.conf.example b/gateway/etc/cc_mqttsn_gateway.conf.example index 66f5f552..68f729f0 100644 --- a/gateway/etc/cc_mqttsn_gateway.conf.example +++ b/gateway/etc/cc_mqttsn_gateway.conf.example @@ -5,7 +5,7 @@ # ================================================================= # Gateway ID, reported in ADVERTISE and GWINFO messages. Default value is 0. -#cc_mqttsn_gw_id 0 +#mqttsn_gw_id 0 # Advertise period (in seconds), when gateway is expected to advertise its # presence by broadcasting ADVERTISE message. Default value is 900 (=15 min). diff --git a/gateway/lib/CMakeLists.txt b/gateway/lib/CMakeLists.txt new file mode 100644 index 00000000..cb09eac0 --- /dev/null +++ b/gateway/lib/CMakeLists.txt @@ -0,0 +1,48 @@ +###################################################################### + +find_package(LibComms REQUIRED) +find_package(cc_mqttsn REQUIRED) +find_package(cc_mqtt311 REQUIRED) + +###################################################################### + +set (GATEWAY_INC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) + +if (CMAKE_COMPILER_IS_GNUCC) + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftemplate-backtrace-limit=0") +endif () + + +add_subdirectory (src) +add_subdirectory (test) + +install ( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/cc_mqttsn_gateway + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) + +install ( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/etc/ + DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/cc_mqttsn_gateway +) + +###################################################################### + +find_package (Doxygen) +if (DOXYGEN_FOUND) + set (doc_output_dir "${CMAKE_INSTALL_FULL_DATAROOTDIR}/doc/cc_mqttsn_gateway") + make_directory (${doc_output_dir}) + + set (match_str "OUTPUT_DIRECTORY[^\n]*") + set (replacement_str "OUTPUT_DIRECTORY = ${doc_output_dir}") + set (output_file "${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf") + + set (config_file "${CMAKE_CURRENT_SOURCE_DIR}/doc/doxygen.conf") + file (READ ${config_file} config_text) + string (REGEX REPLACE "${match_str}" "${replacement_str}" modified_config_text "${config_text}") + file (WRITE "${output_file}" "${modified_config_text}") + + add_custom_target ("doc_mqttsn_gateway" + COMMAND ${DOXYGEN_EXECUTABLE} ${output_file} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +endif () diff --git a/gateway/doc/config.dox b/gateway/lib/doc/config.dox similarity index 100% rename from gateway/doc/config.dox rename to gateway/lib/doc/config.dox diff --git a/gateway/doc/doxygen.conf b/gateway/lib/doc/doxygen.conf similarity index 100% rename from gateway/doc/doxygen.conf rename to gateway/lib/doc/doxygen.conf diff --git a/gateway/doc/gateway.dox b/gateway/lib/doc/gateway.dox similarity index 100% rename from gateway/doc/gateway.dox rename to gateway/lib/doc/gateway.dox diff --git a/gateway/doc/layout.xml b/gateway/lib/doc/layout.xml similarity index 100% rename from gateway/doc/layout.xml rename to gateway/lib/doc/layout.xml diff --git a/gateway/doc/main.dox b/gateway/lib/doc/main.dox similarity index 100% rename from gateway/doc/main.dox rename to gateway/lib/doc/main.dox diff --git a/gateway/doc/namespaces.dox b/gateway/lib/doc/namespaces.dox similarity index 100% rename from gateway/doc/namespaces.dox rename to gateway/lib/doc/namespaces.dox diff --git a/gateway/doc/session.dox b/gateway/lib/doc/session.dox similarity index 99% rename from gateway/doc/session.dox rename to gateway/lib/doc/session.dox index 4616e3bc..dac0b414 100644 --- a/gateway/doc/session.dox +++ b/gateway/lib/doc/session.dox @@ -173,7 +173,7 @@ /// @b C++ interface: /// @code /// session->setCancelTickWaitReqCb( -/// []() +/// []() -> unsigned /// { /// ... // cancel timer /// return ...; // return number of elapsed milliseconds diff --git a/gateway/include/cc_mqttsn_gateway/Config.h b/gateway/lib/include/cc_mqttsn_gateway/Config.h similarity index 100% rename from gateway/include/cc_mqttsn_gateway/Config.h rename to gateway/lib/include/cc_mqttsn_gateway/Config.h diff --git a/gateway/include/cc_mqttsn_gateway/Gateway.h b/gateway/lib/include/cc_mqttsn_gateway/Gateway.h similarity index 100% rename from gateway/include/cc_mqttsn_gateway/Gateway.h rename to gateway/lib/include/cc_mqttsn_gateway/Gateway.h diff --git a/gateway/include/cc_mqttsn_gateway/Session.h b/gateway/lib/include/cc_mqttsn_gateway/Session.h similarity index 100% rename from gateway/include/cc_mqttsn_gateway/Session.h rename to gateway/lib/include/cc_mqttsn_gateway/Session.h diff --git a/gateway/include/cc_mqttsn_gateway/gateway_all.h b/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h similarity index 100% rename from gateway/include/cc_mqttsn_gateway/gateway_all.h rename to gateway/lib/include/cc_mqttsn_gateway/gateway_all.h diff --git a/gateway/include/cc_mqttsn_gateway/gateway_allpp.h b/gateway/lib/include/cc_mqttsn_gateway/gateway_allpp.h similarity index 100% rename from gateway/include/cc_mqttsn_gateway/gateway_allpp.h rename to gateway/lib/include/cc_mqttsn_gateway/gateway_allpp.h diff --git a/gateway/include/cc_mqttsn_gateway/version.h b/gateway/lib/include/cc_mqttsn_gateway/version.h similarity index 100% rename from gateway/include/cc_mqttsn_gateway/version.h rename to gateway/lib/include/cc_mqttsn_gateway/version.h diff --git a/gateway/include/cc_mqttsn_gateway/versionpp.h b/gateway/lib/include/cc_mqttsn_gateway/versionpp.h similarity index 100% rename from gateway/include/cc_mqttsn_gateway/versionpp.h rename to gateway/lib/include/cc_mqttsn_gateway/versionpp.h diff --git a/gateway/src/lib/CMakeLists.txt b/gateway/lib/src/CMakeLists.txt similarity index 94% rename from gateway/src/lib/CMakeLists.txt rename to gateway/lib/src/CMakeLists.txt index f6f20a71..0936d8c8 100644 --- a/gateway/src/lib/CMakeLists.txt +++ b/gateway/lib/src/CMakeLists.txt @@ -24,6 +24,7 @@ function (lib_mqttsn_gateway) ) add_library (${name} STATIC ${src}) + add_library (cc::${name} ALIAS ${name}) target_link_libraries(${name} PRIVATE cc::cc_mqtt311 cc::cc_mqttsn cc::comms) @@ -51,7 +52,7 @@ function (lib_mqttsn_gateway) ) include(CMakePackageConfigHelpers) - file (READ "${CMAKE_CURRENT_SOURCE_DIR}/../../include/cc_mqttsn_gateway/version.h" version_file) + file (READ "${CMAKE_CURRENT_SOURCE_DIR}/../include/cc_mqttsn_gateway/version.h" version_file) string (REGEX MATCH "CC_MQTTSN_GW_MAJOR_VERSION ([0-9]*)U*" _ ${version_file}) set (major_ver ${CMAKE_MATCH_1}) string (REGEX MATCH "CC_MQTTSN_GW_MINOR_VERSION ([0-9]*)U*" _ ${version_file}) diff --git a/gateway/src/lib/Config.cpp b/gateway/lib/src/Config.cpp similarity index 100% rename from gateway/src/lib/Config.cpp rename to gateway/lib/src/Config.cpp diff --git a/gateway/src/lib/ConfigImpl.cpp b/gateway/lib/src/ConfigImpl.cpp similarity index 99% rename from gateway/src/lib/ConfigImpl.cpp rename to gateway/lib/src/ConfigImpl.cpp index 08f8ed7f..20d2affd 100644 --- a/gateway/src/lib/ConfigImpl.cpp +++ b/gateway/lib/src/ConfigImpl.cpp @@ -22,7 +22,7 @@ namespace const char CommentChar = '#'; const std::string SpaceChars(" \t"); -const std::string GatewayIdKey("cc_mqttsn_gw_id"); +const std::string GatewayIdKey("mqttsn_gw_id"); const std::string AdvertiseKey("mqttsn_advertise"); const std::string RetryPeriodKey("mqttsn_retry_period"); const std::string RetryCountKey("mqttsn_retry_count"); diff --git a/gateway/src/lib/ConfigImpl.h b/gateway/lib/src/ConfigImpl.h similarity index 100% rename from gateway/src/lib/ConfigImpl.h rename to gateway/lib/src/ConfigImpl.h diff --git a/gateway/src/lib/Gateway.cpp b/gateway/lib/src/Gateway.cpp similarity index 100% rename from gateway/src/lib/Gateway.cpp rename to gateway/lib/src/Gateway.cpp diff --git a/gateway/src/lib/GatewayImpl.cpp b/gateway/lib/src/GatewayImpl.cpp similarity index 100% rename from gateway/src/lib/GatewayImpl.cpp rename to gateway/lib/src/GatewayImpl.cpp diff --git a/gateway/src/lib/GatewayImpl.h b/gateway/lib/src/GatewayImpl.h similarity index 100% rename from gateway/src/lib/GatewayImpl.h rename to gateway/lib/src/GatewayImpl.h diff --git a/gateway/src/lib/MsgHandler.h b/gateway/lib/src/MsgHandler.h similarity index 100% rename from gateway/src/lib/MsgHandler.h rename to gateway/lib/src/MsgHandler.h diff --git a/gateway/src/lib/RegMgr.cpp b/gateway/lib/src/RegMgr.cpp similarity index 100% rename from gateway/src/lib/RegMgr.cpp rename to gateway/lib/src/RegMgr.cpp diff --git a/gateway/src/lib/RegMgr.h b/gateway/lib/src/RegMgr.h similarity index 100% rename from gateway/src/lib/RegMgr.h rename to gateway/lib/src/RegMgr.h diff --git a/gateway/src/lib/Session.cpp b/gateway/lib/src/Session.cpp similarity index 100% rename from gateway/src/lib/Session.cpp rename to gateway/lib/src/Session.cpp diff --git a/gateway/src/lib/SessionImpl.cpp b/gateway/lib/src/SessionImpl.cpp similarity index 100% rename from gateway/src/lib/SessionImpl.cpp rename to gateway/lib/src/SessionImpl.cpp diff --git a/gateway/src/lib/SessionImpl.h b/gateway/lib/src/SessionImpl.h similarity index 100% rename from gateway/src/lib/SessionImpl.h rename to gateway/lib/src/SessionImpl.h diff --git a/gateway/src/lib/SessionOp.cpp b/gateway/lib/src/SessionOp.cpp similarity index 100% rename from gateway/src/lib/SessionOp.cpp rename to gateway/lib/src/SessionOp.cpp diff --git a/gateway/src/lib/SessionOp.h b/gateway/lib/src/SessionOp.h similarity index 100% rename from gateway/src/lib/SessionOp.h rename to gateway/lib/src/SessionOp.h diff --git a/gateway/src/lib/common.h b/gateway/lib/src/common.h similarity index 100% rename from gateway/src/lib/common.h rename to gateway/lib/src/common.h diff --git a/gateway/src/lib/gateway_all.c b/gateway/lib/src/gateway_all.c similarity index 100% rename from gateway/src/lib/gateway_all.c rename to gateway/lib/src/gateway_all.c diff --git a/gateway/src/lib/gateway_all.cpp b/gateway/lib/src/gateway_all.cpp similarity index 100% rename from gateway/src/lib/gateway_all.cpp rename to gateway/lib/src/gateway_all.cpp diff --git a/gateway/src/lib/messages.h b/gateway/lib/src/messages.h similarity index 100% rename from gateway/src/lib/messages.h rename to gateway/lib/src/messages.h diff --git a/gateway/src/lib/session_op/Asleep.cpp b/gateway/lib/src/session_op/Asleep.cpp similarity index 100% rename from gateway/src/lib/session_op/Asleep.cpp rename to gateway/lib/src/session_op/Asleep.cpp diff --git a/gateway/src/lib/session_op/Asleep.h b/gateway/lib/src/session_op/Asleep.h similarity index 100% rename from gateway/src/lib/session_op/Asleep.h rename to gateway/lib/src/session_op/Asleep.h diff --git a/gateway/src/lib/session_op/AsleepMonitor.cpp b/gateway/lib/src/session_op/AsleepMonitor.cpp similarity index 100% rename from gateway/src/lib/session_op/AsleepMonitor.cpp rename to gateway/lib/src/session_op/AsleepMonitor.cpp diff --git a/gateway/src/lib/session_op/AsleepMonitor.h b/gateway/lib/src/session_op/AsleepMonitor.h similarity index 100% rename from gateway/src/lib/session_op/AsleepMonitor.h rename to gateway/lib/src/session_op/AsleepMonitor.h diff --git a/gateway/src/lib/session_op/Connect.cpp b/gateway/lib/src/session_op/Connect.cpp similarity index 100% rename from gateway/src/lib/session_op/Connect.cpp rename to gateway/lib/src/session_op/Connect.cpp diff --git a/gateway/src/lib/session_op/Connect.h b/gateway/lib/src/session_op/Connect.h similarity index 100% rename from gateway/src/lib/session_op/Connect.h rename to gateway/lib/src/session_op/Connect.h diff --git a/gateway/src/lib/session_op/Disconnect.cpp b/gateway/lib/src/session_op/Disconnect.cpp similarity index 100% rename from gateway/src/lib/session_op/Disconnect.cpp rename to gateway/lib/src/session_op/Disconnect.cpp diff --git a/gateway/src/lib/session_op/Disconnect.h b/gateway/lib/src/session_op/Disconnect.h similarity index 100% rename from gateway/src/lib/session_op/Disconnect.h rename to gateway/lib/src/session_op/Disconnect.h diff --git a/gateway/src/lib/session_op/Encapsulate.cpp b/gateway/lib/src/session_op/Encapsulate.cpp similarity index 100% rename from gateway/src/lib/session_op/Encapsulate.cpp rename to gateway/lib/src/session_op/Encapsulate.cpp diff --git a/gateway/src/lib/session_op/Encapsulate.h b/gateway/lib/src/session_op/Encapsulate.h similarity index 100% rename from gateway/src/lib/session_op/Encapsulate.h rename to gateway/lib/src/session_op/Encapsulate.h diff --git a/gateway/src/lib/session_op/Forward.cpp b/gateway/lib/src/session_op/Forward.cpp similarity index 100% rename from gateway/src/lib/session_op/Forward.cpp rename to gateway/lib/src/session_op/Forward.cpp diff --git a/gateway/src/lib/session_op/Forward.h b/gateway/lib/src/session_op/Forward.h similarity index 100% rename from gateway/src/lib/session_op/Forward.h rename to gateway/lib/src/session_op/Forward.h diff --git a/gateway/src/lib/session_op/PubRecv.cpp b/gateway/lib/src/session_op/PubRecv.cpp similarity index 100% rename from gateway/src/lib/session_op/PubRecv.cpp rename to gateway/lib/src/session_op/PubRecv.cpp diff --git a/gateway/src/lib/session_op/PubRecv.h b/gateway/lib/src/session_op/PubRecv.h similarity index 100% rename from gateway/src/lib/session_op/PubRecv.h rename to gateway/lib/src/session_op/PubRecv.h diff --git a/gateway/src/lib/session_op/PubSend.cpp b/gateway/lib/src/session_op/PubSend.cpp similarity index 100% rename from gateway/src/lib/session_op/PubSend.cpp rename to gateway/lib/src/session_op/PubSend.cpp diff --git a/gateway/src/lib/session_op/PubSend.h b/gateway/lib/src/session_op/PubSend.h similarity index 100% rename from gateway/src/lib/session_op/PubSend.h rename to gateway/lib/src/session_op/PubSend.h diff --git a/gateway/src/lib/session_op/WillUpdate.cpp b/gateway/lib/src/session_op/WillUpdate.cpp similarity index 100% rename from gateway/src/lib/session_op/WillUpdate.cpp rename to gateway/lib/src/session_op/WillUpdate.cpp diff --git a/gateway/src/lib/session_op/WillUpdate.h b/gateway/lib/src/session_op/WillUpdate.h similarity index 100% rename from gateway/src/lib/session_op/WillUpdate.h rename to gateway/lib/src/session_op/WillUpdate.h diff --git a/gateway/test/CMakeLists.txt b/gateway/lib/test/CMakeLists.txt similarity index 100% rename from gateway/test/CMakeLists.txt rename to gateway/lib/test/CMakeLists.txt diff --git a/gateway/test/Gateway.th b/gateway/lib/test/Gateway.th similarity index 100% rename from gateway/test/Gateway.th rename to gateway/lib/test/Gateway.th diff --git a/gateway/test/Session.th b/gateway/lib/test/Session.th similarity index 100% rename from gateway/test/Session.th rename to gateway/lib/test/Session.th diff --git a/gateway/test/TestMsgHandler.cpp b/gateway/lib/test/TestMsgHandler.cpp similarity index 100% rename from gateway/test/TestMsgHandler.cpp rename to gateway/lib/test/TestMsgHandler.cpp diff --git a/gateway/test/TestMsgHandler.h b/gateway/lib/test/TestMsgHandler.h similarity index 100% rename from gateway/test/TestMsgHandler.h rename to gateway/lib/test/TestMsgHandler.h diff --git a/gateway/src/CMakeLists.txt b/gateway/src/CMakeLists.txt deleted file mode 100644 index cdfef8ab..00000000 --- a/gateway/src/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -add_subdirectory (lib) -add_subdirectory (app) \ No newline at end of file From 07e01ce138a959cf16836e2a86362cc8f0c8eac7 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 15 May 2024 07:59:30 +1000 Subject: [PATCH 016/106] Preliminary infrastructure for the boost based gateway app. --- .github/workflows/actions_build.yml | 4 +- gateway/app/CMakeLists.txt | 3 +- gateway/app/gateway/CMakeLists.txt | 34 ++++++ gateway/app/gateway/GatewayApp.cpp | 82 +++++++++++++ gateway/app/gateway/GatewayApp.h | 32 +++++ gateway/app/gateway/GatewayProgramOptions.cpp | 112 ++++++++++++++++++ gateway/app/gateway/GatewayProgramOptions.h | 51 ++++++++ .../etc/cc_mqttsn_gateway.conf.example | 0 gateway/app/gateway/main.cpp | 58 +++++++++ gateway/lib/CMakeLists.txt | 5 - 10 files changed, 373 insertions(+), 8 deletions(-) create mode 100644 gateway/app/gateway/CMakeLists.txt create mode 100644 gateway/app/gateway/GatewayApp.cpp create mode 100644 gateway/app/gateway/GatewayApp.h create mode 100644 gateway/app/gateway/GatewayProgramOptions.cpp create mode 100644 gateway/app/gateway/GatewayProgramOptions.h rename gateway/{ => app/gateway}/etc/cc_mqttsn_gateway.conf.example (100%) create mode 100644 gateway/app/gateway/main.cpp diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index 99d25540..8dd4b945 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -24,7 +24,7 @@ jobs: run: sudo apt-get update --fix-missing - name: Install Packages - run: sudo apt install qtbase5-dev gcc-${{matrix.cc_ver}} g++-${{matrix.cc_ver}} + run: sudo apt install qtbase5-dev libboost-all-dev gcc-${{matrix.cc_ver}} g++-${{matrix.cc_ver}} - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build @@ -92,7 +92,7 @@ jobs: run: sudo apt-get update --fix-missing - name: Install Packages - run: sudo apt install qtbase5-dev clang-${{matrix.cc_ver}} + run: sudo apt install qtbase5-dev libboost-all-dev clang-${{matrix.cc_ver}} - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build diff --git a/gateway/app/CMakeLists.txt b/gateway/app/CMakeLists.txt index cec60b51..9c784be8 100644 --- a/gateway/app/CMakeLists.txt +++ b/gateway/app/CMakeLists.txt @@ -2,4 +2,5 @@ if (NOT CC_MQTTSN_BUILD_GATEWAY_APPS) return () endif () -add_subdirectory (udp) \ No newline at end of file +add_subdirectory (udp) +add_subdirectory (gateway) \ No newline at end of file diff --git a/gateway/app/gateway/CMakeLists.txt b/gateway/app/gateway/CMakeLists.txt new file mode 100644 index 00000000..3d82be6a --- /dev/null +++ b/gateway/app/gateway/CMakeLists.txt @@ -0,0 +1,34 @@ +function (bin_cc_mqttsn_gateway_app) + set (name "cc_mqttsn_gateway_app") + + set (src + main.cpp + GatewayApp.cpp + GatewayProgramOptions.cpp + ) + + add_executable(${name} ${src}) + target_link_libraries(${name} cc::${MQTTSN_GATEWAY_LIB_NAME} Boost::system Boost::program_options ${EXTRA_BOOST_TARGETS}) + + install ( + TARGETS ${name} + DESTINATION ${CMAKE_INSTALL_BINDIR}) + + install ( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/etc/ + DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/cc_mqttsn_gateway + ) + +endfunction () + +########################################################### + +find_package(Boost COMPONENTS system program_options) + +set (EXTRA_BOOST_TARGETS) +if (WIN32) + find_package (Boost REQUIRED COMPONENTS date_time regex) + set (EXTRA_BOOST_TARGETS Boost::date_time Boost::regex) +endif () + +bin_cc_mqttsn_gateway_app() diff --git a/gateway/app/gateway/GatewayApp.cpp b/gateway/app/gateway/GatewayApp.cpp new file mode 100644 index 00000000..ae7afe1c --- /dev/null +++ b/gateway/app/gateway/GatewayApp.cpp @@ -0,0 +1,82 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "GatewayApp.h" + +#include +#include + +namespace cc_mqttsn_gateway_app +{ + +namespace +{ + +std::ostream& logError() +{ + return std::cerr << "ERROR: "; +} + +std::ostream& logInfo() +{ + return std::cout << "INFO: "; +} + +std::ostream& logWarning() +{ + return std::cout << "WARNING: "; +} + +} // namespace + + +GatewayApp::GatewayApp(boost::asio::io_context& io) : + m_io(io) +{ +} + +GatewayApp::~GatewayApp() = default; + +bool GatewayApp::start(int argc, const char* argv[]) +{ + GatewayProgramOptions opts; + + if (!opts.parseArgs(argc, argv)) { + logError() << "Failed to parse arguments." << std::endl; + return false; + } + + if (opts.helpRequested()) { + std::cout << "Usage: " << argv[0] << " [options...]" << '\n'; + opts.printHelp(); + m_io.stop(); + return true; + } + + auto configFile = opts.configFile(); + do { + if (!configFile.empty()) { + logInfo() << "No configuration file provided, using default configuration." << std::endl; + break; + } + + std::ifstream stream(configFile); + if (!stream) { + logWarning() << "Failed to open configuration file \"" << + configFile << "\", using default configuration." << std::endl; + break; + } + + m_config.read(stream); + } while (false); + + // TODO: + logError() << "NYI" << std::endl; + return false; +} + +} // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayApp.h b/gateway/app/gateway/GatewayApp.h new file mode 100644 index 00000000..7319cbb7 --- /dev/null +++ b/gateway/app/gateway/GatewayApp.h @@ -0,0 +1,32 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "GatewayProgramOptions.h" + +#include "cc_mqttsn_gateway/Config.h" + +#include + +namespace cc_mqttsn_gateway_app +{ + +class GatewayApp +{ +public: + GatewayApp(boost::asio::io_context& io); + ~GatewayApp(); + + bool start(int argc, const char* argv[]); + +private: + boost::asio::io_context& m_io; + cc_mqttsn_gateway::Config m_config; +}; + +} // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayProgramOptions.cpp b/gateway/app/gateway/GatewayProgramOptions.cpp new file mode 100644 index 00000000..83f7bdbe --- /dev/null +++ b/gateway/app/gateway/GatewayProgramOptions.cpp @@ -0,0 +1,112 @@ +// +// Copyright 2023 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "GatewayProgramOptions.h" + +#include +#include +#include + +namespace po = boost::program_options; + +namespace cc_mqttsn_gateway_app +{ + +namespace +{ + +using ConnectionTypeMap = std::map; +const ConnectionTypeMap& connectionTypeMap() +{ + static const ConnectionTypeMap Map = { + {"udp", GatewayProgramOptions::ConnectionType_Udp}, + }; + + return Map; +} + +std::string supportedConnectionTypes() +{ + std::string result; + for (auto& info : connectionTypeMap()) { + if (!result.empty()) { + result.append(", "); + } + + result.append(info.first); + } + + return result; +} + +const std::string& defaultConnectionType() +{ + auto& map = connectionTypeMap(); + auto iter = + std::find_if( + map.begin(), map.end(), + [](auto& info) + { + return info.second == GatewayProgramOptions::DefaultConnectionType; + }); + + assert(iter != map.end()); + return iter->first; +} + +} // namespace + + +GatewayProgramOptions::GatewayProgramOptions() +{ + po::options_description opts("Options"); + opts.add_options() + ("help,h", "Display help message") + ("config,c", po::value()->default_value(std::string()), "Configuration file.") + ("socket,s", po::value()->default_value(defaultConnectionType()), + ("Low level connection socket type. Available: " + supportedConnectionTypes()).c_str()) + ; + + m_desc.add(opts); +} + +void GatewayProgramOptions::printHelp() +{ + std::cout << m_desc << std::endl; +} + +bool GatewayProgramOptions::parseArgs(int argc, const char* argv[]) +{ + po::store(po::parse_command_line(argc, argv, m_desc), m_vm); + po::notify(m_vm); + + return true; +} + +bool GatewayProgramOptions::helpRequested() const +{ + return m_vm.count("help") > 0U; +} + +std::string GatewayProgramOptions::configFile() const +{ + return m_vm["config"].as(); +} + +GatewayProgramOptions::ConnectionType GatewayProgramOptions::connectionType() const +{ + auto socketStr = m_vm["socket"].as(); + auto& map = connectionTypeMap(); + auto iter = map.find(socketStr); + if (iter == map.end()) { + return ConnectionType_ValuesLimit; + } + + return iter->second; +} + +} // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayProgramOptions.h b/gateway/app/gateway/GatewayProgramOptions.h new file mode 100644 index 00000000..3f4af24f --- /dev/null +++ b/gateway/app/gateway/GatewayProgramOptions.h @@ -0,0 +1,51 @@ +// +// Copyright 2023 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include + +#include +#include +#include + +namespace cc_mqttsn_gateway_app +{ + +class GatewayProgramOptions +{ +public: + using OptDesc = boost::program_options::options_description; + + static constexpr std::uint16_t DefaultPort = 1883U; + + enum ConnectionType + { + ConnectionType_Udp, + ConnectionType_ValuesLimit + }; + + static const ConnectionType DefaultConnectionType = ConnectionType_Udp; + + GatewayProgramOptions(); + + void printHelp(); + + bool parseArgs(int argc, const char* argv[]); + + bool helpRequested() const; + std::string configFile() const; + ConnectionType connectionType() const; + + +private: + + boost::program_options::variables_map m_vm; + OptDesc m_desc; +}; + +} // namespace cc_mqttsn_gateway_app diff --git a/gateway/etc/cc_mqttsn_gateway.conf.example b/gateway/app/gateway/etc/cc_mqttsn_gateway.conf.example similarity index 100% rename from gateway/etc/cc_mqttsn_gateway.conf.example rename to gateway/app/gateway/etc/cc_mqttsn_gateway.conf.example diff --git a/gateway/app/gateway/main.cpp b/gateway/app/gateway/main.cpp new file mode 100644 index 00000000..32ff5960 --- /dev/null +++ b/gateway/app/gateway/main.cpp @@ -0,0 +1,58 @@ + +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "GatewayApp.h" + +#include + +#include +#include +#include + +int main(int argc, const char* argv[]) +{ + int result = 0U; + try { + boost::asio::io_context io; + + boost::asio::signal_set signals(io, SIGINT, SIGTERM); + signals.async_wait( + [&io, &result](const boost::system::error_code& ec, int sigNum) + { + if (ec == boost::asio::error::operation_aborted) { + return; + } + + if (ec) { + std::cerr << "ERROR: Unexpected error in signal handling: " << ec.message() << std::endl; + result = 150; + io.stop(); + return; + } + + std::cerr << "Terminated with signal " << sigNum << std::endl; + result = 100; + io.stop(); + }); + + cc_mqttsn_gateway_app::GatewayApp app(io); + + if (!app.start(argc, argv)) { + return -1; + } + + io.run(); + } + catch (const std::exception& ec) + { + std::cerr << "ERROR: Unexpected exception: " << ec.what() << std::endl; + result = 200; + } + + return result; +} \ No newline at end of file diff --git a/gateway/lib/CMakeLists.txt b/gateway/lib/CMakeLists.txt index cb09eac0..ad523471 100644 --- a/gateway/lib/CMakeLists.txt +++ b/gateway/lib/CMakeLists.txt @@ -21,11 +21,6 @@ install ( DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) -install ( - DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/etc/ - DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/cc_mqttsn_gateway -) - ###################################################################### find_package (Doxygen) From a7c46a0fc13e1d75a6a69b4b690044a2afcd7004 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 15 May 2024 09:03:10 +1000 Subject: [PATCH 017/106] Added client socket type support to the gateway Config. --- gateway/app/gateway/GatewayApp.cpp | 2 +- gateway/app/gateway/GatewayProgramOptions.cpp | 59 --------- gateway/app/gateway/GatewayProgramOptions.h | 11 -- .../etc/cc_mqttsn_gateway.conf.example | 16 ++- gateway/lib/doc/config.dox | 125 ++++++++++++++++++ .../lib/include/cc_mqttsn_gateway/Config.h | 20 ++- .../include/cc_mqttsn_gateway/gateway_all.h | 12 ++ gateway/lib/src/Config.cpp | 5 + gateway/lib/src/ConfigImpl.cpp | 32 ++++- gateway/lib/src/ConfigImpl.h | 15 ++- gateway/lib/src/gateway_all.cpp | 9 ++ 11 files changed, 218 insertions(+), 88 deletions(-) diff --git a/gateway/app/gateway/GatewayApp.cpp b/gateway/app/gateway/GatewayApp.cpp index ae7afe1c..bdb0a552 100644 --- a/gateway/app/gateway/GatewayApp.cpp +++ b/gateway/app/gateway/GatewayApp.cpp @@ -59,7 +59,7 @@ bool GatewayApp::start(int argc, const char* argv[]) auto configFile = opts.configFile(); do { - if (!configFile.empty()) { + if (configFile.empty()) { logInfo() << "No configuration file provided, using default configuration." << std::endl; break; } diff --git a/gateway/app/gateway/GatewayProgramOptions.cpp b/gateway/app/gateway/GatewayProgramOptions.cpp index 83f7bdbe..05752021 100644 --- a/gateway/app/gateway/GatewayProgramOptions.cpp +++ b/gateway/app/gateway/GatewayProgramOptions.cpp @@ -16,59 +16,12 @@ namespace po = boost::program_options; namespace cc_mqttsn_gateway_app { -namespace -{ - -using ConnectionTypeMap = std::map; -const ConnectionTypeMap& connectionTypeMap() -{ - static const ConnectionTypeMap Map = { - {"udp", GatewayProgramOptions::ConnectionType_Udp}, - }; - - return Map; -} - -std::string supportedConnectionTypes() -{ - std::string result; - for (auto& info : connectionTypeMap()) { - if (!result.empty()) { - result.append(", "); - } - - result.append(info.first); - } - - return result; -} - -const std::string& defaultConnectionType() -{ - auto& map = connectionTypeMap(); - auto iter = - std::find_if( - map.begin(), map.end(), - [](auto& info) - { - return info.second == GatewayProgramOptions::DefaultConnectionType; - }); - - assert(iter != map.end()); - return iter->first; -} - -} // namespace - - GatewayProgramOptions::GatewayProgramOptions() { po::options_description opts("Options"); opts.add_options() ("help,h", "Display help message") ("config,c", po::value()->default_value(std::string()), "Configuration file.") - ("socket,s", po::value()->default_value(defaultConnectionType()), - ("Low level connection socket type. Available: " + supportedConnectionTypes()).c_str()) ; m_desc.add(opts); @@ -97,16 +50,4 @@ std::string GatewayProgramOptions::configFile() const return m_vm["config"].as(); } -GatewayProgramOptions::ConnectionType GatewayProgramOptions::connectionType() const -{ - auto socketStr = m_vm["socket"].as(); - auto& map = connectionTypeMap(); - auto iter = map.find(socketStr); - if (iter == map.end()) { - return ConnectionType_ValuesLimit; - } - - return iter->second; -} - } // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayProgramOptions.h b/gateway/app/gateway/GatewayProgramOptions.h index 3f4af24f..219d9bab 100644 --- a/gateway/app/gateway/GatewayProgramOptions.h +++ b/gateway/app/gateway/GatewayProgramOptions.h @@ -21,16 +21,6 @@ class GatewayProgramOptions public: using OptDesc = boost::program_options::options_description; - static constexpr std::uint16_t DefaultPort = 1883U; - - enum ConnectionType - { - ConnectionType_Udp, - ConnectionType_ValuesLimit - }; - - static const ConnectionType DefaultConnectionType = ConnectionType_Udp; - GatewayProgramOptions(); void printHelp(); @@ -39,7 +29,6 @@ class GatewayProgramOptions bool helpRequested() const; std::string configFile() const; - ConnectionType connectionType() const; private: diff --git a/gateway/app/gateway/etc/cc_mqttsn_gateway.conf.example b/gateway/app/gateway/etc/cc_mqttsn_gateway.conf.example index 68f729f0..4c921667 100644 --- a/gateway/app/gateway/etc/cc_mqttsn_gateway.conf.example +++ b/gateway/app/gateway/etc/cc_mqttsn_gateway.conf.example @@ -18,11 +18,11 @@ # both client / broker directions. Default value is 10 seconds. #mqttsn_retry_period 10 -# Number of attemts to try to re-send a message, acknowledgement of which needs +# Number of attempts to try to re-send a message, acknowledgement of which needs # to be received. Default value is 3. #mqttsn_retry_count 3 -# In some use-cases, with interfaces such as RS-232 and/or bluetooth links, +# In some use-cases, with interfaces such as RS-232 and/or some bluetooth links, # there is only one MQTT-SN client that can be connected to the gateway. In this # case the client may omit its client ID information in the CONNECT message # to save the amount of traffic. When forwarding the connection request to the @@ -78,14 +78,22 @@ # 127.0.0.1 and 1883 respectively. #mqttsn_broker 127.0.0.1 1883 +# ================================================================= +# Client socket configuration +# ================================================================= + +# Use "mqttsn_client_socket" option to specify the type of the I/O link socket +# to use. Supported values are: "udp" (default) +# mqttsn_client_socket udp + # ================================================================= # UDP configuration # ================================================================= # Local UDP port the gateway listens to new incomming messages on. Default is -# 1883. +# 1883. Applicable only if "mqttsn_client_socket" is "udp". #udp_listen_port 1883 # Remote UDP port the gateway broadcasts its ADVERTISE messages to. Default is -# 1883. +# 1883. Applicable only if "mqttsn_client_socket" is "udp". #udp_broadcast_port 1883 diff --git a/gateway/lib/doc/config.dox b/gateway/lib/doc/config.dox index a287d054..09c564c1 100644 --- a/gateway/lib/doc/config.dox +++ b/gateway/lib/doc/config.dox @@ -75,6 +75,12 @@ /// unsigned char id = cc_mqttsn_gw_config_id(handle); /// @endcode /// +/// Applicable configuration file contents: +/// @code{.unparsed} +/// # Gateway ID, reported in ADVERTISE and GWINFO messages. Default value is 0. +/// mqttsn_gw_id 0 +/// @endcode +/// /// @section cc_mqttsn_gw_config_page_advertise Advertise Period /// The @ref cc_mqttsn_gw_gateway_page object sends @b ADVERTISE message periodically. /// It needs to be configured with propere period value @@ -91,6 +97,15 @@ /// unsigned short period = cc_mqttsn_gw_config_advertise_period(handle); /// @endcode /// +/// Applicable configuration file contents: +/// @code{.unparsed} +/// # Advertise period (in seconds), when gateway is expected to advertise its +/// # presence by broadcasting ADVERTISE message. Default value is 900 (=15 min). +/// # This value can be set to 0, which will indicate that gateway supports only +/// # direct connections and doesn't advertise its presence to others. +/// mqttsn_advertise 900 +/// @endcode +/// /// @section cc_mqttsn_gw_config_page_retry Retry Configuration /// The @ref cc_mqttsn_gw_session_page object can be configured with period between /// its retry attempts as well as number of such attempts (see @@ -109,6 +124,18 @@ /// unsigned attempts = cc_mqttsn_gw_config_retry_count(handle); /// @endcode /// +/// Applicable configuration file contents: +/// @code{.unparsed} +/// # Time in seconds to wait before re-sending message, acknowledgement of which +/// # needs to be received. The same time interval is used to resend messages to +/// # both client / broker directions. Default value is 10 seconds. +/// mqttsn_retry_period 10 +/// +/// # Number of attempts to try to re-send a message, acknowledgement of which needs +/// # to be received. Default value is 3. +/// mqttsn_retry_count 3 +/// @endcode +/// /// @section cc_mqttsn_gw_config_page_default_client Default Client ID /// Can be used in @ref cc_mqttsn_gw_session_page object configuration (see /// @ref cc_mqttsn_gw_session_page_default_client_id). @@ -123,6 +150,17 @@ /// const char* clientId = cc_mqttsn_gw_config_default_client_id(handle); /// @endcode /// +/// Applicable configuration file contents: +/// @code{.unparsed} +/// # In some use-cases, with interfaces such as RS-232 and/or some bluetooth links, +/// # there is only one MQTT-SN client that can be connected to the gateway. In this +/// # case the client may omit its client ID information in the CONNECT message +/// # to save the amount of traffic. When forwarding the connection request to the +/// # broker, gateway may replace empty client ID with some predefined value. Use +/// # "mqttsn_default_client_id" option to specify such default client ID. +/// mqttsn_default_client_id some_id +/// @endcode +/// /// @section cc_mqttsn_gw_config_page_pub_only_keep_alive Keep Alive Period for Publish Only Clients /// Can be used in @ref cc_mqttsn_gw_session_page object configuration (see /// @ref cc_mqttsn_gw_session_page_publish_only). @@ -137,6 +175,18 @@ /// unsigned short keepAlivePeriod = cc_mqttsn_gw_config_pub_only_keep_alive(handle); /// @endcode /// +/// Applicable configuration file contents: +/// @code{.unparsed} +/// # The gateway supports "publish only" clients, that do not attempt to connect +/// # to the gateway, and don't subscribe to any topics. Such clients are allowed to +/// # publish predefined topics with QoS=-1. The gateway connects to broker on +/// # behalf to such client. In addition to setting "mqttsn_default_client_id" value +/// # the gateway may be configured to set "keep alive" period in which client must +/// # send at least one message. Use "mqttsn_pub_only_keep_alive" option to set +/// # the value keep alive period in seconds. The default value is 60. +/// mqttsn_pub_only_keep_alive 60 +/// @endcode +/// /// @section cc_mqttsn_gw_config_page_sleeping_client_msg_limit Limit Messages for Sleeping Clients /// Can be used in @ref cc_mqttsn_gw_session_page object configuration (see /// @ref cc_mqttsn_gw_session_page_sleep). @@ -151,6 +201,16 @@ /// unsigned limit = cc_mqttsn_gw_config_sleeping_client_msg_limit(handle); /// @endcode /// +/// Applicable configuration file contents: +/// @code{.unparsed} +/// # Gateway may support sleeping clients. When client is sleeping, the broker +/// # may publish the message to the client. The gateway is responsible to store +/// # these messages and report them to the client, when the latter wakes up. It +/// # is possible to set a limit on number of such stored messages using +/// # "mqttsn_sleeping_client_msg_limit" option. +/// mqttsn_sleeping_client_msg_limit 1024 +/// @endcode +/// /// @section cc_mqttsn_gw_config_page_predefined_topics Predefined Topics /// The @ref cc_mqttsn_gw_session_page object can be configured with /// number of predefined topics (see @ref cc_mqttsn_gw_session_page_predefined_topics). @@ -174,6 +234,17 @@ /// } /// @endcode /// +/// Applicable configuration file contents: +/// @code{.unparsed} +/// # List of predefined ids can be specified using multiple +/// # "mqttsn_predefined_topic" options. This option is expected to have 3 +/// # parameters: client ID, topic string, and topic ID. The common predefined +/// # topic ID for all the possible clients may be specified using '*' as +/// # client ID parameter. +/// mqttsn_predefined_topic client1 predefined/topic/client1 1 +/// mqttsn_predefined_topic * common/predefined/topic 2 +/// @endcode +/// /// @b C interface also allows to retrieve number of predefined topics in case /// there is no known limit for the buffer upfront and it needs to be dynamically /// allocated: @@ -207,6 +278,21 @@ /// unsigned count = cc_mqttsn_gw_config_available_auth_infos(handle); /// @endcode /// +/// Applicable configuration file contents: +/// @code{.unparsed} +/// # MQTT protocol supports client authentication using username and password, +/// # while MQTT-SN doesn't have such option. The gateway may be configured to +/// # use some authentication information when forwarding connection requests to +/// # the broker. Such authentication information may be specified using +/// # "mqttsn_auth" option. This option is expected to have 3 parameters: +/// # client ID, username string, password string. Note, that MQTT protocol +/// # specifies password as binary data. The password string parameter will be used +/// # as is unless it uses "\x" prefix for every binary byte. Just like with +/// # "mqttsn_predefined_topic" option, the client ID may also be a wildcard ('*'). +/// mqttsn_auth client1 username1 ascii_password +/// mqttsn_auth client2 username2 \x00\x01\x02\x03\x04\x05 +/// @endcode +/// /// @section cc_mqttsn_gw_config_page_topic_id_range Topic ID Allocation Range /// The @ref cc_mqttsn_gw_session_page object can be configured to limit its /// range of topic IDs, which are allocated for newly registered topic strings @@ -228,6 +314,15 @@ /// cc_mqttsn_gw_config_topic_id_alloc_range(handle, &minTopicId, &maxTopicId); /// @endcode /// +/// Applicable configuration file contents: +/// @code{.unparsed} +/// # The gateway is responsible to allocate topic IDs for published topics. It is +/// # possible to limit the range of such ID values using +/// # "mqttsn_topic_id_alloc_range" option. It receives two parameters of minimal +/// # and maximal numeric ID. Note, that valid IDs must be in range [1 - 65534]. +/// mqttsn_topic_id_alloc_range 1000 5000 +/// @endcode +/// /// @section cc_mqttsn_gw_config_page_broker_addr Broker Address and Port /// The MQTT-SN gateway application must connect and forward traffic to /// MQTT broker. Below are API functions that can be used to retrieve the @@ -245,6 +340,36 @@ /// unsigned short port = cc_mqttsn_gw_config_broker_port(handle); /// @endcode /// +/// Applicable configuration file contents: +/// @code{.unparsed} +/// # The connection to MQTT broker is usually performed over TCP/IP link. +/// # Use "mqttsn_broker" option to specify the address of the broker. +/// # The option receives two parameters: address and port. The default values are +/// # 127.0.0.1 and 1883 respectively. +/// mqttsn_broker 127.0.0.1 1883 +/// @endcode +/// +/// @section cc_mqttsn_gw_config_page_client_socket Client I/O Socket Type +/// The MQTT-SN gateway application can support multiple client communication I/O +/// socket types. Below are API function that can retrieve such information. +/// +/// @b C++ interface +/// @code +/// cc_mqttsn_gateway::Config::ClientConnectionType type = config.clientConnectionType(); +/// @endcode +/// +/// @b C interface +/// @code +/// CC_MqttsnClientConnectionType type = cc_mqttsn_gw_config_client_socket_type(handle); +/// @endcode +/// +/// Applicable configuration file contents: +/// @code{.unparsed} +/// # Use "mqttsn_client_socket" option to specify the type of the I/O link socket +/// # to use. Supported values are: "udp" (default) +/// mqttsn_client_socket udp +/// @endcode +/// /// @section cc_mqttsn_gw_config_page_custom Custom Configuration Values /// The @b Config object has a list of predefined options it recognises in the /// configuration file. It also accumulates all the options it doesn't recognise. diff --git a/gateway/lib/include/cc_mqttsn_gateway/Config.h b/gateway/lib/include/cc_mqttsn_gateway/Config.h index 96d19d04..4b48a586 100644 --- a/gateway/lib/include/cc_mqttsn_gateway/Config.h +++ b/gateway/lib/include/cc_mqttsn_gateway/Config.h @@ -39,10 +39,10 @@ class Config /// value is rest of the string until the end of the line. /// @b NOTE, that the type is @b multimap, which allows multiple /// entries with the same key. - typedef std::multimap ConfigMap; + using ConfigMap = std::multimap; /// @brief Type of buffer that contains binary data. - typedef std::vector BinaryData; + using BinaryData = std::vector; /// @brief Info about single predefined topic struct PredefinedTopicInfo @@ -53,7 +53,7 @@ class Config }; /// @brief Type of list containing predefined topics. - typedef std::vector PredefinedTopicsList; + using PredefinedTopicsList = std::vector; /// @brief Authentication info for single client struct AuthInfo @@ -64,12 +64,19 @@ class Config }; /// @brief Type of list containing authentication information for multiple clients. - typedef std::vector AuthInfosList; + using AuthInfosList = std::vector; /// @brief Range of topic IDs /// @details First element of the pair is minimal ID, and second /// element of the pair is maximal ID. - typedef std::pair TopicIdsRange; + using TopicIdsRange = std::pair; + + /// @brief Client I/O socket connection type + enum ClientConnectionType + { + ClientConnectionType_Udp, ///< UDP/IP + ClientConnectionType_ValuesLimit ///< Limit to available values, must be last + }; /// @brief Constructor Config(); @@ -139,6 +146,9 @@ class Config /// @details Default value is @b 1883 std::uint16_t brokerTcpHostPort() const; + /// @brief Get client side I/O socket connection type + ClientConnectionType clientConnectionType() const; + private: std::unique_ptr m_pImpl; }; diff --git a/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h b/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h index 849b48b9..c5dd208d 100644 --- a/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h +++ b/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h @@ -490,6 +490,13 @@ typedef struct const char* password; ///< Password string (from the configuration) } CC_MqttsnAuthInfo; +/// @brief Client I/O socket connection type +typedef enum +{ + CC_MqttsnClientConnectionType_Udp, ///< UDP/IP + CC_MqttsnClientConnectionType_ValuesLimit ///< Limit to available values, must be last +} CC_MqttsnClientConnectionType; + /// @brief Handle for configuration object used in all @b cc_mqttsn_gw_config_* functions. typedef struct { @@ -611,6 +618,10 @@ const char* cc_mqttsn_gw_config_broker_address(CC_MqttsnConfigHandle config); /// @param[in] config Handle returned by cc_mqttsn_gw_config_alloc() function. unsigned short cc_mqttsn_gw_config_broker_port(CC_MqttsnConfigHandle config); +/// @brief Get client I/O socket connection type +/// @param config Handle returned by cc_mqttsn_gw_config_alloc() function. +CC_MqttsnClientConnectionType cc_mqttsn_gw_config_client_socket_type(CC_MqttsnConfigHandle config); + /// @brief Get number of available configuration values for the provided key /// @details The key is the first word in the configuration line, and the /// value is rest of the string until the end of the line. @@ -624,6 +635,7 @@ unsigned cc_mqttsn_gw_config_values_count(CC_MqttsnConfigHandle config, const ch /// If the configuration value doesn't exist, @b NULL is returned. const char* cc_mqttsn_gw_config_get_value(CC_MqttsnConfigHandle config, const char* key, unsigned idx); + #ifdef __cplusplus } #endif diff --git a/gateway/lib/src/Config.cpp b/gateway/lib/src/Config.cpp index 50d08224..3cf70c7f 100644 --- a/gateway/lib/src/Config.cpp +++ b/gateway/lib/src/Config.cpp @@ -89,4 +89,9 @@ std::uint16_t Config::brokerTcpHostPort() const return m_pImpl->brokerTcpHostPort(); } +Config::ClientConnectionType Config::clientConnectionType() const +{ + return m_pImpl->clientConnectionType(); +} + } // namespace cc_mqttsn_gateway diff --git a/gateway/lib/src/ConfigImpl.cpp b/gateway/lib/src/ConfigImpl.cpp index 20d2affd..d44d846d 100644 --- a/gateway/lib/src/ConfigImpl.cpp +++ b/gateway/lib/src/ConfigImpl.cpp @@ -7,12 +7,13 @@ #include "ConfigImpl.h" -#include #include #include +#include #include #include -#include +#include +#include namespace cc_mqttsn_gateway { @@ -33,6 +34,7 @@ const std::string PredefinedTopicKey("mqttsn_predefined_topic"); const std::string AuthKey("mqttsn_auth"); const std::string TopicIdAllocRangeKey("mqttsn_topic_id_alloc_range"); const std::string BrokerKey("mqttsn_broker"); +const std::string ClientSocketKey("mqttsn_client_socket"); const std::uint16_t DefaultAdvertise = 15 * 60; const unsigned DefaultRetryPeriod = 10; @@ -365,6 +367,32 @@ std::uint16_t ConfigImpl::brokerTcpHostPort() const return m_brokerPort; } +ConfigImpl::ClientConnectionType ConfigImpl::clientConnectionType() const +{ + static const std::map Map = { + {"udp", ClientConnectionType::ClientConnectionType_Udp}, + }; + + ClientConnectionType result = ClientConnectionType::ClientConnectionType_Udp; + + do { + auto typeStr = stringValue(ClientSocketKey); + if (typeStr.empty()) { + break; + } + + auto iter = Map.find(typeStr); + if (iter == Map.end()) { + result = ClientConnectionType::ClientConnectionType_ValuesLimit; + break; + } + + result = iter->second; + } while (false); + + return result; +} + const std::string& ConfigImpl::stringValue( const std::string& key, const std::string& defaultValue) const diff --git a/gateway/lib/src/ConfigImpl.h b/gateway/lib/src/ConfigImpl.h index 9d155940..5c0dce79 100644 --- a/gateway/lib/src/ConfigImpl.h +++ b/gateway/lib/src/ConfigImpl.h @@ -17,12 +17,13 @@ namespace cc_mqttsn_gateway class ConfigImpl { public: - typedef Config::ConfigMap ConfigMap; - typedef Config::PredefinedTopicInfo PredefinedTopicInfo; - typedef Config::PredefinedTopicsList PredefinedTopicsList; - typedef Config::AuthInfo AuthInfo; - typedef Config::AuthInfosList AuthInfosList; - typedef Config::TopicIdsRange TopicIdsRange; + using ConfigMap = Config::ConfigMap; + using PredefinedTopicInfo = Config::PredefinedTopicInfo; + using PredefinedTopicsList = Config::PredefinedTopicsList; + using AuthInfo = Config::AuthInfo; + using AuthInfosList = Config::AuthInfosList; + using TopicIdsRange = Config::TopicIdsRange; + using ClientConnectionType = Config::ClientConnectionType; ConfigImpl() = default; @@ -57,6 +58,8 @@ class ConfigImpl const std::string& brokerTcpHostAddress() const; std::uint16_t brokerTcpHostPort() const; + ClientConnectionType clientConnectionType() const; + private: template T numericValue(const std::string& key, T defaultValue = T()) const; diff --git a/gateway/lib/src/gateway_all.cpp b/gateway/lib/src/gateway_all.cpp index f4c68ae8..21bdf6b2 100644 --- a/gateway/lib/src/gateway_all.cpp +++ b/gateway/lib/src/gateway_all.cpp @@ -691,6 +691,15 @@ unsigned short cc_mqttsn_gw_config_broker_port(CC_MqttsnConfigHandle config) return reinterpret_cast(config.obj)->brokerTcpHostPort(); } +CC_MqttsnClientConnectionType cc_mqttsn_gw_config_client_socket_type(CC_MqttsnConfigHandle config) +{ + if (config.obj == nullptr) { + return CC_MqttsnClientConnectionType_Udp; + } + + return static_cast(reinterpret_cast(config.obj)->clientConnectionType()); +} + unsigned cc_mqttsn_gw_config_values_count(CC_MqttsnConfigHandle config, const char* key) { if (config.obj == nullptr) { From 8d02cd8d0e70abe3118bf5dc16dc2e84e7df51ad Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 16 May 2024 08:54:41 +1000 Subject: [PATCH 018/106] More infrastructure for the gateway app. --- .github/workflows/actions_build.yml | 23 +++++-- gateway/app/CMakeLists.txt | 2 +- gateway/app/gateway/CMakeLists.txt | 3 + gateway/app/gateway/GatewayApp.cpp | 15 ++++- gateway/app/gateway/GatewayApp.h | 2 + .../app/gateway/GatewayIoClientAcceptor.cpp | 59 ++++++++++++++++++ gateway/app/gateway/GatewayIoClientAcceptor.h | 62 +++++++++++++++++++ .../gateway/GatewayIoClientAcceptor_Udp.cpp | 32 ++++++++++ .../app/gateway/GatewayIoClientAcceptor_Udp.h | 30 +++++++++ gateway/app/gateway/GatewayIoClientSocket.cpp | 18 ++++++ gateway/app/gateway/GatewayIoClientSocket.h | 48 ++++++++++++++ gateway/lib/src/gateway_all.cpp | 5 -- 12 files changed, 285 insertions(+), 14 deletions(-) create mode 100644 gateway/app/gateway/GatewayIoClientAcceptor.cpp create mode 100644 gateway/app/gateway/GatewayIoClientAcceptor.h create mode 100644 gateway/app/gateway/GatewayIoClientAcceptor_Udp.cpp create mode 100644 gateway/app/gateway/GatewayIoClientAcceptor_Udp.h create mode 100644 gateway/app/gateway/GatewayIoClientSocket.cpp create mode 100644 gateway/app/gateway/GatewayIoClientSocket.h diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index 8dd4b945..21f8f98a 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -151,6 +151,11 @@ jobs: - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build + - name: Install Boost + shell: cmd + run: | + choco install boost-msvc-14.2 + - name: Prepare externals shell: cmd run: | @@ -172,8 +177,8 @@ jobs: run: | cmake %GITHUB_WORKSPACE% -A ${{matrix.arch}} -DCMAKE_BUILD_TYPE=${{matrix.type}} -DCMAKE_INSTALL_PREFIX=install ^ -DCMAKE_PREFIX_PATH=${{runner.workspace}}/build/install -DCMAKE_CXX_STANDARD=${{matrix.cpp}} ^ - -DCC_MQTTSN_BUILD_UNIT_TESTS=ON ^ - -DCC_MQTTSN_BUILD_CLIENT_APPS=OFF -DCC_MQTTSN_BUILD_GATEWAY_APPS=OFF + -DBoost_USE_STATIC_LIBS=ON -DCC_MQTTSN_BUILD_UNIT_TESTS=ON ^ + -DCC_MQTTSN_BUILD_CLIENT_APPS=OFF -DCC_MQTTSN_BUILD_GATEWAY_APPS=ON - name: Build Target working-directory: ${{runner.workspace}}/build @@ -202,6 +207,12 @@ jobs: - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build + - name: Install Boost + if: matrix.arch == 'x64' + shell: cmd + run: | + choco install boost-msvc-14.3 + - name: Prepare externals shell: cmd run: | @@ -223,8 +234,10 @@ jobs: run: | cmake %GITHUB_WORKSPACE% -A ${{matrix.arch}} -DCMAKE_BUILD_TYPE=${{matrix.type}} -DCMAKE_INSTALL_PREFIX=install ^ -DCMAKE_PREFIX_PATH=${{runner.workspace}}/build/install -DCMAKE_CXX_STANDARD=${{matrix.cpp}} ^ - -DCC_MQTTSN_BUILD_UNIT_TESTS=ON ^ - -DCC_MQTTSN_BUILD_CLIENT_APPS=OFF -DCC_MQTTSN_BUILD_GATEWAY_APPS=OFF + -DBoost_USE_STATIC_LIBS=ON -DCC_MQTTSN_BUILD_UNIT_TESTS=ON ^ + -DCC_MQTTSN_BUILD_CLIENT_APPS=OFF -DCC_MQTTSN_BUILD_GATEWAY_APPS=${{env.HAS_BOOST}} + env: + HAS_BOOST: "${{ matrix.arch == 'x64' && 'ON' || 'OFF' }}" - name: Build Target working-directory: ${{runner.workspace}}/build @@ -236,4 +249,4 @@ jobs: - name: Testing working-directory: ${{runner.workspace}}/build shell: cmd - run: ctest -V \ No newline at end of file + run: ctest -V diff --git a/gateway/app/CMakeLists.txt b/gateway/app/CMakeLists.txt index 9c784be8..8518c501 100644 --- a/gateway/app/CMakeLists.txt +++ b/gateway/app/CMakeLists.txt @@ -2,5 +2,5 @@ if (NOT CC_MQTTSN_BUILD_GATEWAY_APPS) return () endif () -add_subdirectory (udp) +#add_subdirectory (udp) add_subdirectory (gateway) \ No newline at end of file diff --git a/gateway/app/gateway/CMakeLists.txt b/gateway/app/gateway/CMakeLists.txt index 3d82be6a..d21cc2be 100644 --- a/gateway/app/gateway/CMakeLists.txt +++ b/gateway/app/gateway/CMakeLists.txt @@ -4,6 +4,9 @@ function (bin_cc_mqttsn_gateway_app) set (src main.cpp GatewayApp.cpp + GatewayIoClientAcceptor.cpp + GatewayIoClientAcceptor_Udp.cpp + GatewayIoClientSocket.cpp GatewayProgramOptions.cpp ) diff --git a/gateway/app/gateway/GatewayApp.cpp b/gateway/app/gateway/GatewayApp.cpp index bdb0a552..b2738c99 100644 --- a/gateway/app/gateway/GatewayApp.cpp +++ b/gateway/app/gateway/GatewayApp.cpp @@ -74,9 +74,18 @@ bool GatewayApp::start(int argc, const char* argv[]) m_config.read(stream); } while (false); - // TODO: - logError() << "NYI" << std::endl; - return false; + m_acceptor = GatewayIoClientAcceptor::create(m_io, m_config); + if (!m_acceptor) { + logError() << "Unknown / unsupported client socket type" << std::endl; + return false; + } + + if (!m_acceptor->start()) { + logError() << "Failed to start client socket" << std::endl; + return false; + } + + return true; } } // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayApp.h b/gateway/app/gateway/GatewayApp.h index 7319cbb7..a3a6d118 100644 --- a/gateway/app/gateway/GatewayApp.h +++ b/gateway/app/gateway/GatewayApp.h @@ -7,6 +7,7 @@ #pragma once +#include "GatewayIoClientAcceptor.h" #include "GatewayProgramOptions.h" #include "cc_mqttsn_gateway/Config.h" @@ -27,6 +28,7 @@ class GatewayApp private: boost::asio::io_context& m_io; cc_mqttsn_gateway::Config m_config; + GatewayIoClientAcceptorPtr m_acceptor; }; } // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayIoClientAcceptor.cpp b/gateway/app/gateway/GatewayIoClientAcceptor.cpp new file mode 100644 index 00000000..01f23e8e --- /dev/null +++ b/gateway/app/gateway/GatewayIoClientAcceptor.cpp @@ -0,0 +1,59 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "GatewayIoClientAcceptor.h" + +#include "GatewayIoClientAcceptor_Udp.h" + +#include +#include + +namespace cc_mqttsn_gateway_app +{ + +GatewayIoClientAcceptor::~GatewayIoClientAcceptor() = default; + +GatewayIoClientAcceptor::Ptr GatewayIoClientAcceptor::create(boost::asio::io_context& io, const cc_mqttsn_gateway::Config& config) +{ + using CreateFunc = Ptr (*)(boost::asio::io_context&, const cc_mqttsn_gateway::Config&); + static const CreateFunc Map[] = { + /* ClientConnectionType_Udp */ &GatewayIoClientAcceptor_Udp::create, + }; + static const std::size_t MapSize = std::extent::value; + static_assert(MapSize == cc_mqttsn_gateway::Config::ClientConnectionType_ValuesLimit); + + auto idx = static_cast(config.clientConnectionType()); + if (MapSize <= idx) { + return Ptr(); + } + + auto func = Map[idx]; + if (func == nullptr) { + return Ptr(); + } + + return func(io, config); +} + +bool GatewayIoClientAcceptor::start() +{ + if (!m_newConnectionReportCb) { + return false; + } + + return startImpl(); +} + +void GatewayIoClientAcceptor::reportNewConnection(GatewayIoClientSocketPtr socket) +{ + assert(m_newConnectionReportCb); + m_newConnectionReportCb(std::move(socket)); +} + + + +} // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayIoClientAcceptor.h b/gateway/app/gateway/GatewayIoClientAcceptor.h new file mode 100644 index 00000000..c3ec1de4 --- /dev/null +++ b/gateway/app/gateway/GatewayIoClientAcceptor.h @@ -0,0 +1,62 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "GatewayIoClientSocket.h" + +#include "cc_mqttsn_gateway/Config.h" + +#include + +#include +#include + +namespace cc_mqttsn_gateway_app +{ + +class GatewayIoClientAcceptor +{ +public: + using Ptr = std::unique_ptr; + using NewConnectionReportCb = std::function; + + virtual ~GatewayIoClientAcceptor(); + + static Ptr create(boost::asio::io_context& io, const cc_mqttsn_gateway::Config& config); + + bool start(); + + template + void setNewConnectionReportCb(TFunc&& func) + { + m_newConnectionReportCb = std::forward(func); + } + +protected: + GatewayIoClientAcceptor(boost::asio::io_context& io) : + m_io(io) + { + }; + + virtual bool startImpl() = 0; + + boost::asio::io_context& io() + { + return m_io; + } + + void reportNewConnection(GatewayIoClientSocketPtr socket); + +private: + boost::asio::io_context& m_io; + NewConnectionReportCb m_newConnectionReportCb; +}; + +using GatewayIoClientAcceptorPtr = GatewayIoClientAcceptor::Ptr; + +} // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayIoClientAcceptor_Udp.cpp b/gateway/app/gateway/GatewayIoClientAcceptor_Udp.cpp new file mode 100644 index 00000000..23cbdc6e --- /dev/null +++ b/gateway/app/gateway/GatewayIoClientAcceptor_Udp.cpp @@ -0,0 +1,32 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "GatewayIoClientAcceptor_Udp.h" + +namespace cc_mqttsn_gateway_app +{ + +GatewayIoClientAcceptor_Udp::GatewayIoClientAcceptor_Udp(boost::asio::io_context& io, const cc_mqttsn_gateway::Config& config) : + Base(io) +{ + static_cast(config); +} + +GatewayIoClientAcceptor_Udp::~GatewayIoClientAcceptor_Udp() = default; + +GatewayIoClientAcceptor_Udp::Ptr GatewayIoClientAcceptor_Udp::create(boost::asio::io_context& io, const cc_mqttsn_gateway::Config& config) +{ + return std::make_unique(io, config); +} + +bool GatewayIoClientAcceptor_Udp::startImpl() +{ + // TODO + return false; +} + +} // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayIoClientAcceptor_Udp.h b/gateway/app/gateway/GatewayIoClientAcceptor_Udp.h new file mode 100644 index 00000000..e6010256 --- /dev/null +++ b/gateway/app/gateway/GatewayIoClientAcceptor_Udp.h @@ -0,0 +1,30 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "GatewayIoClientAcceptor.h" + +namespace cc_mqttsn_gateway_app +{ + +class GatewayIoClientAcceptor_Udp : public GatewayIoClientAcceptor +{ + using Base = GatewayIoClientAcceptor; +public: + GatewayIoClientAcceptor_Udp(boost::asio::io_context& io, const cc_mqttsn_gateway::Config& config); + virtual ~GatewayIoClientAcceptor_Udp(); + + static Ptr create(boost::asio::io_context& io, const cc_mqttsn_gateway::Config& config); + +protected: + virtual bool startImpl() override; + +private: +}; + +} // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayIoClientSocket.cpp b/gateway/app/gateway/GatewayIoClientSocket.cpp new file mode 100644 index 00000000..40f81700 --- /dev/null +++ b/gateway/app/gateway/GatewayIoClientSocket.cpp @@ -0,0 +1,18 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "GatewayIoClientSocket.h" + +#include + +namespace cc_mqttsn_gateway_app +{ + +GatewayIoClientSocket::~GatewayIoClientSocket() = default; + + +} // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayIoClientSocket.h b/gateway/app/gateway/GatewayIoClientSocket.h new file mode 100644 index 00000000..77a3c5c9 --- /dev/null +++ b/gateway/app/gateway/GatewayIoClientSocket.h @@ -0,0 +1,48 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "cc_mqttsn_gateway/Config.h" + +#include + +#include + +namespace cc_mqttsn_gateway_app +{ + +class GatewayIoClientSocket +{ +public: + virtual ~GatewayIoClientSocket(); + + bool start() + { + return startImpl(); + } + +protected: + GatewayIoClientSocket(boost::asio::io_context& io) : + m_io(io) + { + }; + + virtual bool startImpl() = 0; + + boost::asio::io_context& io() + { + return m_io; + } + +private: + boost::asio::io_context& m_io; +}; + +using GatewayIoClientSocketPtr = std::unique_ptr; + +} // namespace cc_mqttsn_gateway_app diff --git a/gateway/lib/src/gateway_all.cpp b/gateway/lib/src/gateway_all.cpp index 21bdf6b2..c6acac66 100644 --- a/gateway/lib/src/gateway_all.cpp +++ b/gateway/lib/src/gateway_all.cpp @@ -327,11 +327,6 @@ void cc_mqttsn_gw_session_set_fwd_enc_session_deleted_cb( }); } -void cc_mqttsn_gw_session_set_fwd_enc_session_deleted_cb( - CC_MqttsnSessionHandle session, - CC_MqttsnSessionFwdEncSessionDeletedCb cb, - void* data); - void cc_mqttsn_gw_session_set_id(CC_MqttsnSessionHandle session, unsigned char id) { if (session.obj == nullptr) { From e2f85715d738d11ce2c604c053b52696bb18b373 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 16 May 2024 09:11:20 +1000 Subject: [PATCH 019/106] Added support of the log file to the gateway Config. --- .../etc/cc_mqttsn_gateway.conf.example | 4 ++++ gateway/lib/doc/config.dox | 22 +++++++++++++++++++ .../lib/include/cc_mqttsn_gateway/Config.h | 5 +++++ .../include/cc_mqttsn_gateway/gateway_all.h | 5 +++++ gateway/lib/src/Config.cpp | 5 +++++ gateway/lib/src/ConfigImpl.cpp | 7 ++++++ gateway/lib/src/ConfigImpl.h | 2 ++ gateway/lib/src/gateway_all.cpp | 9 ++++++++ 8 files changed, 59 insertions(+) diff --git a/gateway/app/gateway/etc/cc_mqttsn_gateway.conf.example b/gateway/app/gateway/etc/cc_mqttsn_gateway.conf.example index 4c921667..e889a4dd 100644 --- a/gateway/app/gateway/etc/cc_mqttsn_gateway.conf.example +++ b/gateway/app/gateway/etc/cc_mqttsn_gateway.conf.example @@ -78,6 +78,10 @@ # 127.0.0.1 and 1883 respectively. #mqttsn_broker 127.0.0.1 1883 +# Log file is expected be either an absolute path or one of the +# special values: stdout (default), stderr. +#mqttsn_log_file stdout + # ================================================================= # Client socket configuration # ================================================================= diff --git a/gateway/lib/doc/config.dox b/gateway/lib/doc/config.dox index 09c564c1..927e53fa 100644 --- a/gateway/lib/doc/config.dox +++ b/gateway/lib/doc/config.dox @@ -349,6 +349,28 @@ /// mqttsn_broker 127.0.0.1 1883 /// @endcode /// +/// @section cc_mqttsn_gw_config_page_log_file Log File +/// The gateway application can produce various information and/or error +/// messages. These messages can be stored in a special log file. To retrieve +/// the relevant configuration information use the following API functions: +/// +/// @b C++ interface +/// @code +/// const std::string& logFile = config.logFile(); +/// @endcode +/// +/// @b C interface +/// @code +/// const char* logFile = cc_mqttsn_gw_config_log_file(handle); +/// @endcode +/// +/// Applicable configuration file contents: +/// @code{.unparsed} +/// # Log file is expected be either an absolute path or one of the +/// # special values: stdout (default), stderr. +/// mqttsn_log_file stdout +/// @endcode +/// /// @section cc_mqttsn_gw_config_page_client_socket Client I/O Socket Type /// The MQTT-SN gateway application can support multiple client communication I/O /// socket types. Below are API function that can retrieve such information. diff --git a/gateway/lib/include/cc_mqttsn_gateway/Config.h b/gateway/lib/include/cc_mqttsn_gateway/Config.h index 4b48a586..f2c2bc9d 100644 --- a/gateway/lib/include/cc_mqttsn_gateway/Config.h +++ b/gateway/lib/include/cc_mqttsn_gateway/Config.h @@ -146,6 +146,11 @@ class Config /// @details Default value is @b 1883 std::uint16_t brokerTcpHostPort() const; + /// @brief Get log file. + /// @details Expected to be an aboslute file path or one of the special values: + /// "stdout", "stderr". Defaults to "stdout" + const std::string& logFile() const; + /// @brief Get client side I/O socket connection type ClientConnectionType clientConnectionType() const; diff --git a/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h b/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h index c5dd208d..be0cbd6d 100644 --- a/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h +++ b/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h @@ -618,6 +618,11 @@ const char* cc_mqttsn_gw_config_broker_address(CC_MqttsnConfigHandle config); /// @param[in] config Handle returned by cc_mqttsn_gw_config_alloc() function. unsigned short cc_mqttsn_gw_config_broker_port(CC_MqttsnConfigHandle config); +/// @brief Get log file. +/// @details Expected to be an aboslute file path or one of the special values: +/// "stdout", "stderr". Defaults to "stdout" +const char* cc_mqttsn_gw_config_log_file(CC_MqttsnConfigHandle config); + /// @brief Get client I/O socket connection type /// @param config Handle returned by cc_mqttsn_gw_config_alloc() function. CC_MqttsnClientConnectionType cc_mqttsn_gw_config_client_socket_type(CC_MqttsnConfigHandle config); diff --git a/gateway/lib/src/Config.cpp b/gateway/lib/src/Config.cpp index 3cf70c7f..399dbd91 100644 --- a/gateway/lib/src/Config.cpp +++ b/gateway/lib/src/Config.cpp @@ -89,6 +89,11 @@ std::uint16_t Config::brokerTcpHostPort() const return m_pImpl->brokerTcpHostPort(); } +const std::string& Config::logFile() const +{ + return m_pImpl->logFile(); +} + Config::ClientConnectionType Config::clientConnectionType() const { return m_pImpl->clientConnectionType(); diff --git a/gateway/lib/src/ConfigImpl.cpp b/gateway/lib/src/ConfigImpl.cpp index d44d846d..daca4041 100644 --- a/gateway/lib/src/ConfigImpl.cpp +++ b/gateway/lib/src/ConfigImpl.cpp @@ -34,6 +34,7 @@ const std::string PredefinedTopicKey("mqttsn_predefined_topic"); const std::string AuthKey("mqttsn_auth"); const std::string TopicIdAllocRangeKey("mqttsn_topic_id_alloc_range"); const std::string BrokerKey("mqttsn_broker"); +const std::string LogFileKey("mqttsn_log_file"); const std::string ClientSocketKey("mqttsn_client_socket"); const std::uint16_t DefaultAdvertise = 15 * 60; @@ -367,6 +368,12 @@ std::uint16_t ConfigImpl::brokerTcpHostPort() const return m_brokerPort; } +const std::string& ConfigImpl::logFile() const +{ + static const std::string DefaultLogFile("stdout"); + return stringValue(LogFileKey, DefaultLogFile); +} + ConfigImpl::ClientConnectionType ConfigImpl::clientConnectionType() const { static const std::map Map = { diff --git a/gateway/lib/src/ConfigImpl.h b/gateway/lib/src/ConfigImpl.h index 5c0dce79..577fce6e 100644 --- a/gateway/lib/src/ConfigImpl.h +++ b/gateway/lib/src/ConfigImpl.h @@ -58,6 +58,8 @@ class ConfigImpl const std::string& brokerTcpHostAddress() const; std::uint16_t brokerTcpHostPort() const; + const std::string& logFile() const; + ClientConnectionType clientConnectionType() const; private: diff --git a/gateway/lib/src/gateway_all.cpp b/gateway/lib/src/gateway_all.cpp index c6acac66..7e87fe2a 100644 --- a/gateway/lib/src/gateway_all.cpp +++ b/gateway/lib/src/gateway_all.cpp @@ -686,6 +686,15 @@ unsigned short cc_mqttsn_gw_config_broker_port(CC_MqttsnConfigHandle config) return reinterpret_cast(config.obj)->brokerTcpHostPort(); } +const char* cc_mqttsn_gw_config_log_file(CC_MqttsnConfigHandle config) +{ + if (config.obj == nullptr) { + return nullptr; + } + + return reinterpret_cast(config.obj)->logFile().c_str(); +} + CC_MqttsnClientConnectionType cc_mqttsn_gw_config_client_socket_type(CC_MqttsnConfigHandle config) { if (config.obj == nullptr) { From 57c532d6687b10b4a176cc4ae3fa70bb5a832d53 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 17 May 2024 09:33:10 +1000 Subject: [PATCH 020/106] Saving work on the gateway application. --- gateway/app/gateway/CMakeLists.txt | 2 + gateway/app/gateway/GatewayApp.cpp | 34 +--- gateway/app/gateway/GatewayApp.h | 2 + .../app/gateway/GatewayIoClientAcceptor.cpp | 16 +- gateway/app/gateway/GatewayIoClientAcceptor.h | 14 +- .../gateway/GatewayIoClientAcceptor_Udp.cpp | 162 +++++++++++++++++- .../app/gateway/GatewayIoClientAcceptor_Udp.h | 31 +++- gateway/app/gateway/GatewayIoClientSocket.cpp | 9 + gateway/app/gateway/GatewayIoClientSocket.h | 28 ++- .../app/gateway/GatewayIoClientSocket_Udp.cpp | 56 ++++++ .../app/gateway/GatewayIoClientSocket_Udp.h | 67 ++++++++ gateway/app/gateway/GatewayLogger.cpp | 58 +++++++ gateway/app/gateway/GatewayLogger.h | 35 ++++ gateway/lib/src/ConfigImpl.cpp | 9 +- 14 files changed, 475 insertions(+), 48 deletions(-) create mode 100644 gateway/app/gateway/GatewayIoClientSocket_Udp.cpp create mode 100644 gateway/app/gateway/GatewayIoClientSocket_Udp.h create mode 100644 gateway/app/gateway/GatewayLogger.cpp create mode 100644 gateway/app/gateway/GatewayLogger.h diff --git a/gateway/app/gateway/CMakeLists.txt b/gateway/app/gateway/CMakeLists.txt index d21cc2be..118a7155 100644 --- a/gateway/app/gateway/CMakeLists.txt +++ b/gateway/app/gateway/CMakeLists.txt @@ -7,6 +7,8 @@ function (bin_cc_mqttsn_gateway_app) GatewayIoClientAcceptor.cpp GatewayIoClientAcceptor_Udp.cpp GatewayIoClientSocket.cpp + GatewayIoClientSocket_Udp.cpp + GatewayLogger.cpp GatewayProgramOptions.cpp ) diff --git a/gateway/app/gateway/GatewayApp.cpp b/gateway/app/gateway/GatewayApp.cpp index b2738c99..77e8944d 100644 --- a/gateway/app/gateway/GatewayApp.cpp +++ b/gateway/app/gateway/GatewayApp.cpp @@ -12,26 +12,6 @@ namespace cc_mqttsn_gateway_app { - -namespace -{ - -std::ostream& logError() -{ - return std::cerr << "ERROR: "; -} - -std::ostream& logInfo() -{ - return std::cout << "INFO: "; -} - -std::ostream& logWarning() -{ - return std::cout << "WARNING: "; -} - -} // namespace GatewayApp::GatewayApp(boost::asio::io_context& io) : @@ -46,7 +26,7 @@ bool GatewayApp::start(int argc, const char* argv[]) GatewayProgramOptions opts; if (!opts.parseArgs(argc, argv)) { - logError() << "Failed to parse arguments." << std::endl; + m_logger.error() << "Failed to parse arguments." << std::endl; return false; } @@ -60,13 +40,13 @@ bool GatewayApp::start(int argc, const char* argv[]) auto configFile = opts.configFile(); do { if (configFile.empty()) { - logInfo() << "No configuration file provided, using default configuration." << std::endl; + m_logger.info() << "No configuration file provided, using default configuration." << std::endl; break; } std::ifstream stream(configFile); if (!stream) { - logWarning() << "Failed to open configuration file \"" << + m_logger.warning() << "Failed to open configuration file \"" << configFile << "\", using default configuration." << std::endl; break; } @@ -74,14 +54,16 @@ bool GatewayApp::start(int argc, const char* argv[]) m_config.read(stream); } while (false); - m_acceptor = GatewayIoClientAcceptor::create(m_io, m_config); + m_logger.configure(m_config); + + m_acceptor = GatewayIoClientAcceptor::create(m_io, m_logger, m_config); if (!m_acceptor) { - logError() << "Unknown / unsupported client socket type" << std::endl; + m_logger.error() << "Unknown / unsupported client socket type" << std::endl; return false; } if (!m_acceptor->start()) { - logError() << "Failed to start client socket" << std::endl; + m_logger.error() << "Failed to start client socket" << std::endl; return false; } diff --git a/gateway/app/gateway/GatewayApp.h b/gateway/app/gateway/GatewayApp.h index a3a6d118..1eb809a1 100644 --- a/gateway/app/gateway/GatewayApp.h +++ b/gateway/app/gateway/GatewayApp.h @@ -8,6 +8,7 @@ #pragma once #include "GatewayIoClientAcceptor.h" +#include "GatewayLogger.h" #include "GatewayProgramOptions.h" #include "cc_mqttsn_gateway/Config.h" @@ -28,6 +29,7 @@ class GatewayApp private: boost::asio::io_context& m_io; cc_mqttsn_gateway::Config m_config; + GatewayLogger m_logger; GatewayIoClientAcceptorPtr m_acceptor; }; diff --git a/gateway/app/gateway/GatewayIoClientAcceptor.cpp b/gateway/app/gateway/GatewayIoClientAcceptor.cpp index 01f23e8e..b05580cd 100644 --- a/gateway/app/gateway/GatewayIoClientAcceptor.cpp +++ b/gateway/app/gateway/GatewayIoClientAcceptor.cpp @@ -17,9 +17,12 @@ namespace cc_mqttsn_gateway_app GatewayIoClientAcceptor::~GatewayIoClientAcceptor() = default; -GatewayIoClientAcceptor::Ptr GatewayIoClientAcceptor::create(boost::asio::io_context& io, const cc_mqttsn_gateway::Config& config) +GatewayIoClientAcceptor::Ptr GatewayIoClientAcceptor::create( + boost::asio::io_context& io, + GatewayLogger& logger, + const cc_mqttsn_gateway::Config& config) { - using CreateFunc = Ptr (*)(boost::asio::io_context&, const cc_mqttsn_gateway::Config&); + using CreateFunc = Ptr (*)(boost::asio::io_context&, GatewayLogger&, const cc_mqttsn_gateway::Config&); static const CreateFunc Map[] = { /* ClientConnectionType_Udp */ &GatewayIoClientAcceptor_Udp::create, }; @@ -28,15 +31,16 @@ GatewayIoClientAcceptor::Ptr GatewayIoClientAcceptor::create(boost::asio::io_con auto idx = static_cast(config.clientConnectionType()); if (MapSize <= idx) { + logger.error() << "Unknown client connection type" << std::endl; return Ptr(); } auto func = Map[idx]; - if (func == nullptr) { - return Ptr(); - } + // if (func == nullptr) { + // return Ptr(); + // } - return func(io, config); + return func(io, logger, config); } bool GatewayIoClientAcceptor::start() diff --git a/gateway/app/gateway/GatewayIoClientAcceptor.h b/gateway/app/gateway/GatewayIoClientAcceptor.h index c3ec1de4..e4fc3e85 100644 --- a/gateway/app/gateway/GatewayIoClientAcceptor.h +++ b/gateway/app/gateway/GatewayIoClientAcceptor.h @@ -8,6 +8,7 @@ #pragma once #include "GatewayIoClientSocket.h" +#include "GatewayLogger.h" #include "cc_mqttsn_gateway/Config.h" @@ -27,7 +28,7 @@ class GatewayIoClientAcceptor virtual ~GatewayIoClientAcceptor(); - static Ptr create(boost::asio::io_context& io, const cc_mqttsn_gateway::Config& config); + static Ptr create(boost::asio::io_context& io, GatewayLogger& logger, const cc_mqttsn_gateway::Config& config); bool start(); @@ -38,8 +39,9 @@ class GatewayIoClientAcceptor } protected: - GatewayIoClientAcceptor(boost::asio::io_context& io) : - m_io(io) + GatewayIoClientAcceptor(boost::asio::io_context& io, GatewayLogger& logger) : + m_io(io), + m_logger(logger) { }; @@ -50,10 +52,16 @@ class GatewayIoClientAcceptor return m_io; } + GatewayLogger& logger() + { + return m_logger; + } + void reportNewConnection(GatewayIoClientSocketPtr socket); private: boost::asio::io_context& m_io; + GatewayLogger& m_logger; NewConnectionReportCb m_newConnectionReportCb; }; diff --git a/gateway/app/gateway/GatewayIoClientAcceptor_Udp.cpp b/gateway/app/gateway/GatewayIoClientAcceptor_Udp.cpp index 23cbdc6e..c4ecd4a3 100644 --- a/gateway/app/gateway/GatewayIoClientAcceptor_Udp.cpp +++ b/gateway/app/gateway/GatewayIoClientAcceptor_Udp.cpp @@ -10,23 +10,173 @@ namespace cc_mqttsn_gateway_app { -GatewayIoClientAcceptor_Udp::GatewayIoClientAcceptor_Udp(boost::asio::io_context& io, const cc_mqttsn_gateway::Config& config) : - Base(io) +namespace +{ + +const std::string UdpListenPortKey("udp_listen_port"); +const std::string UdpBroadcastPortKey("udp_broadcast_port"); +const std::uint16_t DefaultListenPort = 1883; +const std::uint16_t DefaultBroadcastPort = 1883; + +std::uint16_t getPortInfo( + GatewayLogger& logger, + const cc_mqttsn_gateway::Config& config, + const std::string& key, + std::uint16_t defaultValue) +{ + auto& map = config.configMap(); + auto iter = map.find(key); + if ((iter == map.end()) || + (iter->second.empty())) { + return defaultValue; + } + + try { + return static_cast(std::stoul(iter->second)); + } + catch (...) { + logger.warning() << "Invalid value (" << iter->second << ") specified for key \"" << key << "\" in the configuration, assuming " << defaultValue << std::endl; + // nothing to do + } + + return defaultValue; +} + +} // namespace + + +GatewayIoClientAcceptor_Udp::GatewayIoClientAcceptor_Udp( + boost::asio::io_context& io, + GatewayLogger& logger, + const cc_mqttsn_gateway::Config& config) : + Base(io, logger), + m_socket(io), + m_acceptPort(getPortInfo(logger, config, UdpListenPortKey, DefaultListenPort)), + m_broadcastPort(getPortInfo(logger, config, UdpBroadcastPortKey, DefaultBroadcastPort)) { static_cast(config); } GatewayIoClientAcceptor_Udp::~GatewayIoClientAcceptor_Udp() = default; -GatewayIoClientAcceptor_Udp::Ptr GatewayIoClientAcceptor_Udp::create(boost::asio::io_context& io, const cc_mqttsn_gateway::Config& config) +GatewayIoClientAcceptor_Udp::Ptr GatewayIoClientAcceptor_Udp::create( + boost::asio::io_context& io, + GatewayLogger& logger, + const cc_mqttsn_gateway::Config& config) { - return std::make_unique(io, config); + return std::make_unique(io, logger, config); } bool GatewayIoClientAcceptor_Udp::startImpl() { - // TODO - return false; + boost::system::error_code ec; + m_socket.open(boost::asio::ip::udp::v4(), ec); + if (ec) { + logger().error() << "Failed to open local UDP socket: " << ec.message() << std::endl; + return false; + } + + + m_socket.bind(boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), m_acceptPort)); + if (ec) { + logger().error() << "Failed to bind local port " << m_acceptPort << ": " << ec.message() << std::endl; + return false; + } + + doAccept(); + return true; +} + +void GatewayIoClientAcceptor_Udp::doAccept() +{ + m_socket.async_receive_from( + boost::asio::buffer(m_inBuf), + m_senderEndpoint, + [this](boost::system::error_code ec, std::size_t bytesCount) + { + if (ec == boost::asio::error::operation_aborted) { + return; + } + + do { + if (ec) { + logger().error() << "UDP read error: " << ec.message() << std::endl; + break; + } + + auto iter = m_clients.find(m_senderEndpoint); + if (iter != m_clients.end()) { + auto* socketPtr = iter->second; + assert(socketPtr != nullptr); + socketPtr->newDataArrived(m_inBuf.data(), bytesCount); + break; + } + + auto socketPtr = std::make_unique(io(), logger(), m_senderEndpoint); + socketPtr->setSendDataCb( + std::bind(&GatewayIoClientAcceptor_Udp::sendData, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + + socketPtr->setSocketDeletedCb( + [this](const Endpoint& endpoint) + { + auto delIter = m_clients.find(endpoint); + assert(delIter != m_clients.end()); + m_clients.erase(delIter); + }); + + socketPtr->newDataArrived(m_inBuf.data(), bytesCount); + m_clients[m_senderEndpoint] = socketPtr.get(); + reportNewConnection(std::move(socketPtr)); + } while (false); + + doAccept(); + }); +} + +void GatewayIoClientAcceptor_Udp::sendData(const Endpoint& endpoint, const std::uint8_t* buf, std::size_t bufSize) +{ + bool toSend = m_pendingWrites.empty(); + m_pendingWrites.push_back(WriteInfo()); + auto& info = m_pendingWrites.back(); + info.m_endpoint = endpoint; + info.m_data.assign(buf, buf + bufSize); + + if (toSend) { + sendPendingWrites(); + } +} + +void GatewayIoClientAcceptor_Udp::sendPendingWrites() +{ + if (m_pendingWrites.empty()) { + return; + } + + auto& info = m_pendingWrites.front(); + m_socket.async_send_to( + boost::asio::buffer(info.m_data), info.m_endpoint, + [this, &info](boost::system::error_code ec, std::size_t bytesSent) + { + if (ec == boost::asio::error::operation_aborted) { + return; + } + + do { + if (ec) { + logger().error() << "Failed to send data to " << info.m_endpoint << ": " << ec.message() << std::endl; + break; + } + + if (bytesSent < info.m_data.size()) { + logger().warning() << "Not all bytes are sent to " << info.m_endpoint << std::endl; + break; + } + + } while (false); + + m_pendingWrites.pop_front(); + sendPendingWrites(); + }); } } // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayIoClientAcceptor_Udp.h b/gateway/app/gateway/GatewayIoClientAcceptor_Udp.h index e6010256..67c594b1 100644 --- a/gateway/app/gateway/GatewayIoClientAcceptor_Udp.h +++ b/gateway/app/gateway/GatewayIoClientAcceptor_Udp.h @@ -8,6 +8,12 @@ #pragma once #include "GatewayIoClientAcceptor.h" +#include "GatewayIoClientSocket_Udp.h" + +#include +#include +#include +#include namespace cc_mqttsn_gateway_app { @@ -16,15 +22,36 @@ class GatewayIoClientAcceptor_Udp : public GatewayIoClientAcceptor { using Base = GatewayIoClientAcceptor; public: - GatewayIoClientAcceptor_Udp(boost::asio::io_context& io, const cc_mqttsn_gateway::Config& config); + GatewayIoClientAcceptor_Udp(boost::asio::io_context& io, GatewayLogger& logger, const cc_mqttsn_gateway::Config& config); virtual ~GatewayIoClientAcceptor_Udp(); - static Ptr create(boost::asio::io_context& io, const cc_mqttsn_gateway::Config& config); + static Ptr create(boost::asio::io_context& io, GatewayLogger& logger, const cc_mqttsn_gateway::Config& config); protected: virtual bool startImpl() override; private: + using Socket = boost::asio::ip::udp::socket; + using Endpoint = Socket::endpoint_type; + using ClientsMap = std::map; + using DataBuf = std::vector; + struct WriteInfo + { + Endpoint m_endpoint; + DataBuf m_data; + }; + + void doAccept(); + void sendData(const Endpoint& endpoint, const std::uint8_t* buf, std::size_t bufSize); + void sendPendingWrites(); + + Socket m_socket; + Endpoint m_senderEndpoint; + std::uint16_t m_acceptPort = 0U; + std::uint16_t m_broadcastPort = 0U; + std::array m_inBuf; + ClientsMap m_clients; + std::list m_pendingWrites; }; } // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayIoClientSocket.cpp b/gateway/app/gateway/GatewayIoClientSocket.cpp index 40f81700..eed149f7 100644 --- a/gateway/app/gateway/GatewayIoClientSocket.cpp +++ b/gateway/app/gateway/GatewayIoClientSocket.cpp @@ -14,5 +14,14 @@ namespace cc_mqttsn_gateway_app GatewayIoClientSocket::~GatewayIoClientSocket() = default; +bool GatewayIoClientSocket::start() +{ + if (!m_dataReportCb) { + m_logger.error() << "Not all callbacks are set for GatewayIoClientSocket" << std::endl; + return false; + } + + return startImpl(); +} } // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayIoClientSocket.h b/gateway/app/gateway/GatewayIoClientSocket.h index 77a3c5c9..b9a55ade 100644 --- a/gateway/app/gateway/GatewayIoClientSocket.h +++ b/gateway/app/gateway/GatewayIoClientSocket.h @@ -7,6 +7,8 @@ #pragma once +#include "GatewayLogger.h" + #include "cc_mqttsn_gateway/Config.h" #include @@ -21,14 +23,20 @@ class GatewayIoClientSocket public: virtual ~GatewayIoClientSocket(); - bool start() + bool start(); + + using DataReportCb = std::function; + + template + void setDataReportCb(TFunc&& func) { - return startImpl(); + m_dataReportCb = std::forward(func); } protected: - GatewayIoClientSocket(boost::asio::io_context& io) : - m_io(io) + GatewayIoClientSocket(boost::asio::io_context& io, GatewayLogger& logger) : + m_io(io), + m_logger(logger) { }; @@ -39,8 +47,20 @@ class GatewayIoClientSocket return m_io; } + GatewayLogger& logger() + { + return m_logger; + } + + void reportClientData(const std::uint8_t* buf, std::size_t bufSize) + { + m_dataReportCb(buf, bufSize); + } + private: boost::asio::io_context& m_io; + GatewayLogger& m_logger; + DataReportCb m_dataReportCb; }; using GatewayIoClientSocketPtr = std::unique_ptr; diff --git a/gateway/app/gateway/GatewayIoClientSocket_Udp.cpp b/gateway/app/gateway/GatewayIoClientSocket_Udp.cpp new file mode 100644 index 00000000..b3a845a4 --- /dev/null +++ b/gateway/app/gateway/GatewayIoClientSocket_Udp.cpp @@ -0,0 +1,56 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "GatewayIoClientSocket_Udp.h" + +namespace cc_mqttsn_gateway_app +{ + +GatewayIoClientSocket_Udp::~GatewayIoClientSocket_Udp() +{ + if (m_socketDeletedCb) { + m_socketDeletedCb(m_endpoint); + } +} + +void GatewayIoClientSocket_Udp::newDataArrived(const std::uint8_t* buf, std::size_t bufSize) +{ + if (m_started && m_pendingData.empty()) { + reportClientData(buf, bufSize); + return; + } + + m_pendingData.emplace_back(buf, buf + bufSize); +} + +bool GatewayIoClientSocket_Udp::startImpl() +{ + if ((!m_sendDataCb) || + (!m_socketDeletedCb)) { + logger().error() << "Not all callbacks are set for GatewayIoClientSocket_Udp" << std::endl; + return false; + } + + boost::asio::post(io(), + [this]() + { + reportPendingData(); + }); + + return true; +} + +void GatewayIoClientSocket_Udp::reportPendingData() +{ + while (!m_pendingData.empty()) { + auto& dataBuf = m_pendingData.front(); + reportClientData(dataBuf.data(), dataBuf.size()); + m_pendingData.pop_front(); + } +} + +} // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayIoClientSocket_Udp.h b/gateway/app/gateway/GatewayIoClientSocket_Udp.h new file mode 100644 index 00000000..bd71437d --- /dev/null +++ b/gateway/app/gateway/GatewayIoClientSocket_Udp.h @@ -0,0 +1,67 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "GatewayIoClientSocket.h" + +#include +#include +#include + +namespace cc_mqttsn_gateway_app +{ + +class GatewayIoClientSocket_Udp : public GatewayIoClientSocket +{ + using Base = GatewayIoClientSocket; +public: + using Endpoint = boost::asio::ip::udp::endpoint; + + explicit GatewayIoClientSocket_Udp(boost::asio::io_context& io, GatewayLogger& logger, const Endpoint& endpoint) : + Base(io, logger), + m_endpoint(endpoint) + { + }; + + virtual ~GatewayIoClientSocket_Udp(); + + void newDataArrived(const std::uint8_t* buf, std::size_t bufSize); + + using SendDataCb = std::function; + template + void setSendDataCb(TFunc&& func) + { + m_sendDataCb = std::forward(func); + } + + using SocketDeletedCb = std::function; + template + void setSocketDeletedCb(TFunc&& func) + { + m_socketDeletedCb = std::forward(func); + } + +protected: + + virtual bool startImpl() override; + +private: + using DataBuf = std::vector; + using DataBufsList = std::list; + + void reportPendingData(); + + const Endpoint m_endpoint; + bool m_started = false; + DataBufsList m_pendingData; + SendDataCb m_sendDataCb; + SocketDeletedCb m_socketDeletedCb; +}; + + +} // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayLogger.cpp b/gateway/app/gateway/GatewayLogger.cpp new file mode 100644 index 00000000..b566cf3e --- /dev/null +++ b/gateway/app/gateway/GatewayLogger.cpp @@ -0,0 +1,58 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "GatewayLogger.h" + +#include + +namespace cc_mqttsn_gateway_app +{ + +GatewayLogger::GatewayLogger() : + m_out(&std::cout) +{ +} + +void GatewayLogger::configure(const cc_mqttsn_gateway::Config& config) +{ + const std::map Map = { + {"stdout", &std::cout}, + {"stderr", &std::cerr}, + }; + + auto& logFile = config.logFile(); + auto iter = Map.find(logFile); + if (iter != Map.end()) { + m_out = iter->second; + return; + } + + m_fstream = std::make_unique(logFile, std::ios_base::out | std::ios_base::app); + if (!(*m_fstream)) { + error() << "Failed to open " << logFile << " for logging." << std::endl; + return; + } + + m_out = m_fstream.get(); +} + +std::ostream& GatewayLogger::error() +{ + return (*m_out) << "[ERROR]: "; +} + +std::ostream& GatewayLogger::info() +{ + return (*m_out) << "[INFO]: "; +} + +std::ostream& GatewayLogger::warning() +{ + return (*m_out) << "[WARNING]: "; +} + +} // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayLogger.h b/gateway/app/gateway/GatewayLogger.h new file mode 100644 index 00000000..f9f028b6 --- /dev/null +++ b/gateway/app/gateway/GatewayLogger.h @@ -0,0 +1,35 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "cc_mqttsn_gateway/Config.h" + +#include +#include +#include + +namespace cc_mqttsn_gateway_app +{ + +class GatewayLogger +{ +public: + GatewayLogger(); + + void configure(const cc_mqttsn_gateway::Config& config); + + std::ostream& error(); + std::ostream& info(); + std::ostream& warning(); + +private: + std::unique_ptr m_fstream; + std::ostream* m_out = nullptr; +}; + +} // namespace cc_mqttsn_gateway_app diff --git a/gateway/lib/src/ConfigImpl.cpp b/gateway/lib/src/ConfigImpl.cpp index daca4041..8d255f0b 100644 --- a/gateway/lib/src/ConfigImpl.cpp +++ b/gateway/lib/src/ConfigImpl.cpp @@ -99,7 +99,14 @@ void ConfigImpl::read(std::istream& stream) continue; } - map.insert(std::make_pair(std::move(key), std::string(str.begin() + valuePos, str.end()))); + auto valueEndPos = str.size(); + auto lastValueCharPos = str.find_last_not_of(" \t\r\n"); + if (lastValueCharPos != std::string::npos) { + valueEndPos = lastValueCharPos + 1U; + assert(valuePos <= valueEndPos); + } + + map.insert(std::make_pair(std::move(key), std::string(str.begin() + valuePos, str.begin() + valueEndPos))); } m_map.swap(map); From 39f4e376bf3f573cf28e4d7c9c9dd06782584d2b Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sat, 18 May 2024 12:08:40 +1000 Subject: [PATCH 021/106] Infrastructure for the broker socket in the gateway application. --- gateway/app/gateway/CMakeLists.txt | 2 + gateway/app/gateway/GatewayIoBrokerSocket.cpp | 53 +++++++ gateway/app/gateway/GatewayIoBrokerSocket.h | 92 ++++++++++++ .../app/gateway/GatewayIoBrokerSocket_Tcp.cpp | 136 ++++++++++++++++++ .../app/gateway/GatewayIoBrokerSocket_Tcp.h | 50 +++++++ gateway/app/gateway/GatewayIoClientAcceptor.h | 6 + .../gateway/GatewayIoClientAcceptor_Udp.cpp | 97 +++++++++++-- .../app/gateway/GatewayIoClientAcceptor_Udp.h | 10 +- gateway/app/gateway/GatewayIoClientSocket.h | 6 + .../app/gateway/GatewayIoClientSocket_Udp.cpp | 6 + .../app/gateway/GatewayIoClientSocket_Udp.h | 5 +- .../etc/cc_mqttsn_gateway.conf.example | 18 ++- gateway/lib/doc/config.dox | 25 +++- .../lib/include/cc_mqttsn_gateway/Config.h | 12 ++ .../include/cc_mqttsn_gateway/gateway_all.h | 13 +- gateway/lib/src/Config.cpp | 5 + gateway/lib/src/ConfigImpl.cpp | 27 ++++ gateway/lib/src/ConfigImpl.h | 2 + gateway/lib/src/gateway_all.cpp | 13 +- 19 files changed, 556 insertions(+), 22 deletions(-) create mode 100644 gateway/app/gateway/GatewayIoBrokerSocket.cpp create mode 100644 gateway/app/gateway/GatewayIoBrokerSocket.h create mode 100644 gateway/app/gateway/GatewayIoBrokerSocket_Tcp.cpp create mode 100644 gateway/app/gateway/GatewayIoBrokerSocket_Tcp.h diff --git a/gateway/app/gateway/CMakeLists.txt b/gateway/app/gateway/CMakeLists.txt index 118a7155..6eab6ae3 100644 --- a/gateway/app/gateway/CMakeLists.txt +++ b/gateway/app/gateway/CMakeLists.txt @@ -4,6 +4,8 @@ function (bin_cc_mqttsn_gateway_app) set (src main.cpp GatewayApp.cpp + GatewayIoBrokerSocket.cpp + GatewayIoBrokerSocket_Tcp.cpp GatewayIoClientAcceptor.cpp GatewayIoClientAcceptor_Udp.cpp GatewayIoClientSocket.cpp diff --git a/gateway/app/gateway/GatewayIoBrokerSocket.cpp b/gateway/app/gateway/GatewayIoBrokerSocket.cpp new file mode 100644 index 00000000..eeef7d67 --- /dev/null +++ b/gateway/app/gateway/GatewayIoBrokerSocket.cpp @@ -0,0 +1,53 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "GatewayIoBrokerSocket.h" + +#include "GatewayIoBrokerSocket_Tcp.h" + +#include + +namespace cc_mqttsn_gateway_app +{ + +GatewayIoBrokerSocket::~GatewayIoBrokerSocket() = default; + +GatewayIoBrokerSocket::Ptr GatewayIoBrokerSocket::create(boost::asio::io_context& io, GatewayLogger& logger, const cc_mqttsn_gateway::Config& config) +{ + using CreateFunc = Ptr (*)(boost::asio::io_context&, GatewayLogger&, const cc_mqttsn_gateway::Config&); + static const CreateFunc Map[] = { + /* BrokerConnectionType_Tcp */ &GatewayIoBrokerSocket_Tcp::create, + }; + static const std::size_t MapSize = std::extent::value; + static_assert(MapSize == cc_mqttsn_gateway::Config::BrokerConnectionType_ValuesLimit); + + auto idx = static_cast(config.clientConnectionType()); + if (MapSize <= idx) { + logger.error() << "Unknown client connection type" << std::endl; + return Ptr(); + } + + auto func = Map[idx]; + // if (func == nullptr) { + // return Ptr(); + // } + + return func(io, logger, config); +} + +bool GatewayIoBrokerSocket::start() +{ + if ((!m_dataReportCb) || + (!m_errorReportCb)) { + m_logger.error() << "Not all callbacks are set for GatewayIoBrokerSocket" << std::endl; + return false; + } + + return startImpl(); +} + +} // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayIoBrokerSocket.h b/gateway/app/gateway/GatewayIoBrokerSocket.h new file mode 100644 index 00000000..7a207576 --- /dev/null +++ b/gateway/app/gateway/GatewayIoBrokerSocket.h @@ -0,0 +1,92 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "GatewayLogger.h" + +#include "cc_mqttsn_gateway/Config.h" + +#include + +#include + +namespace cc_mqttsn_gateway_app +{ + +class GatewayIoBrokerSocket +{ +public: + using Ptr = std::unique_ptr; + + virtual ~GatewayIoBrokerSocket(); + + static Ptr create(boost::asio::io_context& io, GatewayLogger& logger, const cc_mqttsn_gateway::Config& config); + + bool start(); + + using DataReportCb = std::function; + + template + void setDataReportCb(TFunc&& func) + { + m_dataReportCb = std::forward(func); + } + + using ErrorReportCb = std::function; + + template + void setErrorReportCb(TFunc&& func) + { + m_errorReportCb = std::forward(func); + } + + void sendData(const std::uint8_t* buf, std::size_t bufSize) + { + sendDataImpl(buf, bufSize); + } + +protected: + GatewayIoBrokerSocket(boost::asio::io_context& io, GatewayLogger& logger) : + m_io(io), + m_logger(logger) + { + }; + + virtual bool startImpl() = 0; + virtual void sendDataImpl(const std::uint8_t* buf, std::size_t bufSize) = 0; + + boost::asio::io_context& io() + { + return m_io; + } + + GatewayLogger& logger() + { + return m_logger; + } + + void reportClientData(const std::uint8_t* buf, std::size_t bufSize) + { + m_dataReportCb(buf, bufSize); + } + + void reportError() + { + m_errorReportCb(); + } + +private: + boost::asio::io_context& m_io; + GatewayLogger& m_logger; + DataReportCb m_dataReportCb; + ErrorReportCb m_errorReportCb; +}; + +using GatewayIoBrokerSocketPtr = GatewayIoBrokerSocket::Ptr; + +} // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayIoBrokerSocket_Tcp.cpp b/gateway/app/gateway/GatewayIoBrokerSocket_Tcp.cpp new file mode 100644 index 00000000..3b1e8f93 --- /dev/null +++ b/gateway/app/gateway/GatewayIoBrokerSocket_Tcp.cpp @@ -0,0 +1,136 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "GatewayIoBrokerSocket_Tcp.h" + +namespace cc_mqttsn_gateway_app +{ + +GatewayIoBrokerSocket_Tcp::GatewayIoBrokerSocket_Tcp(boost::asio::io_context& io, GatewayLogger& logger, const cc_mqttsn_gateway::Config& config) : + Base(io, logger), + m_socket(io), + m_resolver(io), + m_addr(config.brokerTcpHostAddress()), + m_port(config.brokerTcpHostPort()) +{ +} + +GatewayIoBrokerSocket_Tcp::~GatewayIoBrokerSocket_Tcp() = default; + +bool GatewayIoBrokerSocket_Tcp::startImpl() +{ + m_resolver.async_resolve( + m_addr, std::to_string(m_port), + [this](const boost::system::error_code& ec, const auto& results) + { + if (ec == boost::asio::error::operation_aborted) { + return; + } + + if (ec) { + logger().error() << "TCP/IP resolution error for address " << m_addr << ": " << ec.message() << std::endl; + reportError(); + return; + } + + if (results.empty()) { + logger().error() << "Failed to resolve address " << m_addr << std::endl; + reportError(); + return; + } + + auto endpoint = results.begin()->endpoint(); + m_socket.async_connect( + endpoint, + [this](const boost::system::error_code& connectEc) + { + if (connectEc == boost::asio::error::operation_aborted) { + return; + } + + if (connectEc) { + logger().error() << "Failed to estabilish TCP/IP connection to " << m_addr << ": " << connectEc.message() << std::endl; + reportError(); + return; + } + + m_connected = true; + doRead(); + sendPendingWrites(); + } + ); + + }); + return true; +} + +void GatewayIoBrokerSocket_Tcp::sendDataImpl(const std::uint8_t* buf, std::size_t bufSize) +{ + bool sendRightAway = m_connected && m_sentData.empty(); + m_sentData.emplace_back(buf, buf + bufSize); + if (sendRightAway) { + sendPendingWrites(); + } +} + +void GatewayIoBrokerSocket_Tcp::doRead() +{ + m_socket.async_read_some( + boost::asio::buffer(m_inData), + [this](const boost::system::error_code& ec, std::size_t bytesCount) + { + if (ec == boost::asio::error::operation_aborted) { + return; + } + + if (ec) { + logger().error() << "TCP/IP read error: " << ec.message() << std::endl; + reportError(); + return; + } + + reportClientData(m_inData.data(), bytesCount); + doRead(); + } + ); +} + +void GatewayIoBrokerSocket_Tcp::sendPendingWrites() +{ + if (m_sentData.empty()) { + return; + } + + auto& buf = m_sentData.front(); + m_socket.async_write_some( + boost::asio::buffer(buf), + [this, &buf](const boost::system::error_code& ec, std::size_t bytesCount) + { + if (ec == boost::asio::error::operation_aborted) { + return; + } + + if (ec) { + logger().error() << "TCP/IP write error: " << ec.message() << std::endl; + reportError(); + return; + } + + do { + if (buf.size() <= bytesCount) { + m_sentData.pop_front(); + break; + } + + buf.erase(buf.begin(), buf.begin() + bytesCount); + } while (false); + + sendPendingWrites(); + }); +} + +} // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayIoBrokerSocket_Tcp.h b/gateway/app/gateway/GatewayIoBrokerSocket_Tcp.h new file mode 100644 index 00000000..f6e5f77f --- /dev/null +++ b/gateway/app/gateway/GatewayIoBrokerSocket_Tcp.h @@ -0,0 +1,50 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "GatewayIoBrokerSocket.h" + +#include +#include + +namespace cc_mqttsn_gateway_app +{ + +class GatewayIoBrokerSocket_Tcp final : public GatewayIoBrokerSocket +{ + using Base = GatewayIoBrokerSocket; +public: + explicit GatewayIoBrokerSocket_Tcp(boost::asio::io_context& io, GatewayLogger& logger, const cc_mqttsn_gateway::Config& config); + + virtual ~GatewayIoBrokerSocket_Tcp(); + +protected: + + virtual bool startImpl() override; + virtual void sendDataImpl(const std::uint8_t* buf, std::size_t bufSize) override; + +private: + using Socket = boost::asio::ip::tcp::socket; + using Resolver = boost::asio::ip::tcp::resolver; + using DataBuf = std::vector; + using DataBufsList = std::list; + + void doRead(); + void sendPendingWrites(); + + Socket m_socket; + Resolver m_resolver; + std::string m_addr; + std::uint16_t m_port = 0; + bool m_connected = false; + DataBufsList m_sentData; + std::array m_inData; +}; + + +} // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayIoClientAcceptor.h b/gateway/app/gateway/GatewayIoClientAcceptor.h index e4fc3e85..a38e0b47 100644 --- a/gateway/app/gateway/GatewayIoClientAcceptor.h +++ b/gateway/app/gateway/GatewayIoClientAcceptor.h @@ -38,6 +38,11 @@ class GatewayIoClientAcceptor m_newConnectionReportCb = std::forward(func); } + void broadcastData(const std::uint8_t* buf, std::size_t bufSize) + { + broadcastDataImpl(buf, bufSize); + } + protected: GatewayIoClientAcceptor(boost::asio::io_context& io, GatewayLogger& logger) : m_io(io), @@ -46,6 +51,7 @@ class GatewayIoClientAcceptor }; virtual bool startImpl() = 0; + virtual void broadcastDataImpl(const std::uint8_t* buf, std::size_t bufSize) = 0; boost::asio::io_context& io() { diff --git a/gateway/app/gateway/GatewayIoClientAcceptor_Udp.cpp b/gateway/app/gateway/GatewayIoClientAcceptor_Udp.cpp index c4ecd4a3..8897f0e9 100644 --- a/gateway/app/gateway/GatewayIoClientAcceptor_Udp.cpp +++ b/gateway/app/gateway/GatewayIoClientAcceptor_Udp.cpp @@ -15,10 +15,14 @@ namespace const std::string UdpListenPortKey("udp_listen_port"); const std::string UdpBroadcastPortKey("udp_broadcast_port"); -const std::uint16_t DefaultListenPort = 1883; -const std::uint16_t DefaultBroadcastPort = 1883; +const std::string UdpBroadcastAddressKey("udp_broadcast_address"); +const std::string UdpBroadcastRadiusKey("udp_broadcast_radius"); +const unsigned DefaultListenPort = 1883; +const unsigned DefaultBroadcastPort = 1883; +const std::string DefaultBroadcastAddress("255.255.255.255"); +const unsigned DefaultBroadcastRadius = 128; -std::uint16_t getPortInfo( +unsigned long getUnsignedConfigValue( GatewayLogger& logger, const cc_mqttsn_gateway::Config& config, const std::string& key, @@ -32,7 +36,7 @@ std::uint16_t getPortInfo( } try { - return static_cast(std::stoul(iter->second)); + return std::stoul(iter->second); } catch (...) { logger.warning() << "Invalid value (" << iter->second << ") specified for key \"" << key << "\" in the configuration, assuming " << defaultValue << std::endl; @@ -42,6 +46,33 @@ std::uint16_t getPortInfo( return defaultValue; } +std::uint16_t getListenPort(GatewayLogger& logger, const cc_mqttsn_gateway::Config& config) +{ + return static_cast(getUnsignedConfigValue(logger, config, UdpListenPortKey, DefaultListenPort)); +} + +std::uint16_t getBroadcastPort(GatewayLogger& logger, const cc_mqttsn_gateway::Config& config) +{ + return static_cast(getUnsignedConfigValue(logger, config, UdpBroadcastPortKey, DefaultBroadcastPort)); +} + +const std::string& getBroadcastAddress(const cc_mqttsn_gateway::Config& config) +{ + auto& map = config.configMap(); + auto iter = map.find(UdpBroadcastAddressKey); + if ((iter == map.end()) || + (iter->second.empty())) { + return DefaultBroadcastAddress; + } + + return iter->second; +} + +unsigned getBroadcastTtl(GatewayLogger& logger, const cc_mqttsn_gateway::Config& config) +{ + return static_cast(getUnsignedConfigValue(logger, config, UdpBroadcastRadiusKey, DefaultBroadcastRadius)); +} + } // namespace @@ -51,10 +82,11 @@ GatewayIoClientAcceptor_Udp::GatewayIoClientAcceptor_Udp( const cc_mqttsn_gateway::Config& config) : Base(io, logger), m_socket(io), - m_acceptPort(getPortInfo(logger, config, UdpListenPortKey, DefaultListenPort)), - m_broadcastPort(getPortInfo(logger, config, UdpBroadcastPortKey, DefaultBroadcastPort)) + m_acceptPort(getListenPort(logger, config)), + m_broadcastPort(getBroadcastPort(logger, config)), + m_broadcastAddress(getBroadcastAddress(config)), + m_broadcastTtl(getBroadcastTtl(logger, config)) { - static_cast(config); } GatewayIoClientAcceptor_Udp::~GatewayIoClientAcceptor_Udp() = default; @@ -70,12 +102,35 @@ GatewayIoClientAcceptor_Udp::Ptr GatewayIoClientAcceptor_Udp::create( bool GatewayIoClientAcceptor_Udp::startImpl() { boost::system::error_code ec; + auto asioBroadcastAddr = boost::asio::ip::make_address_v4(m_broadcastAddress, ec); + if (ec) { + logger().error() << "Invalid UDP broadcast address: " << ec.message() << std::endl; + return false; + } + + m_broadcastEndpoint.address(asioBroadcastAddr); + m_broadcastEndpoint.port(m_broadcastPort); + m_socket.open(boost::asio::ip::udp::v4(), ec); if (ec) { logger().error() << "Failed to open local UDP socket: " << ec.message() << std::endl; return false; } + boost::asio::ip::unicast::hops defaultTtl; + m_socket.get_option(defaultTtl, ec); + if (ec) { + logger().error() << "Failed to retrieve defaultTTL: " << ec.message() << ", assuming " << m_defaultTtl << std::endl; + } + else { + m_defaultTtl = defaultTtl.value(); + } + + m_socket.set_option(boost::asio::socket_base::broadcast(true), ec); + if (ec) { + logger().error() << "Failed to enable broadcast for UDP socket: " << ec.message() << std::endl; + return false; + } m_socket.bind(boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), m_acceptPort)); if (ec) { @@ -87,6 +142,11 @@ bool GatewayIoClientAcceptor_Udp::startImpl() return true; } +void GatewayIoClientAcceptor_Udp::broadcastDataImpl(const std::uint8_t* buf, std::size_t bufSize) +{ + sendData(m_broadcastEndpoint, buf, bufSize, m_broadcastTtl); +} + void GatewayIoClientAcceptor_Udp::doAccept() { m_socket.async_receive_from( @@ -114,7 +174,9 @@ void GatewayIoClientAcceptor_Udp::doAccept() auto socketPtr = std::make_unique(io(), logger(), m_senderEndpoint); socketPtr->setSendDataCb( - std::bind(&GatewayIoClientAcceptor_Udp::sendData, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + std::bind( + &GatewayIoClientAcceptor_Udp::sendData, this, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); socketPtr->setSocketDeletedCb( [this](const Endpoint& endpoint) @@ -133,14 +195,22 @@ void GatewayIoClientAcceptor_Udp::doAccept() }); } -void GatewayIoClientAcceptor_Udp::sendData(const Endpoint& endpoint, const std::uint8_t* buf, std::size_t bufSize) +void GatewayIoClientAcceptor_Udp::sendData(const Endpoint& endpoint, const std::uint8_t* buf, std::size_t bufSize, unsigned broadcastRadius) { bool toSend = m_pendingWrites.empty(); m_pendingWrites.push_back(WriteInfo()); auto& info = m_pendingWrites.back(); - info.m_endpoint = endpoint; info.m_data.assign(buf, buf + bufSize); + if (broadcastRadius > 0U) { + info.m_ttl = broadcastRadius; + info.m_endpoint = m_broadcastEndpoint; + } + else { + info.m_endpoint = endpoint; + info.m_ttl = m_defaultTtl; + } + if (toSend) { sendPendingWrites(); } @@ -153,6 +223,13 @@ void GatewayIoClientAcceptor_Udp::sendPendingWrites() } auto& info = m_pendingWrites.front(); + + boost::system::error_code ecTmp; + m_socket.set_option(boost::asio::ip::unicast::hops(info.m_ttl), ecTmp); + if (ecTmp) { + logger().error() << "Failed to update outgoing packet TTL: " << ecTmp.message() << std::endl; + } + m_socket.async_send_to( boost::asio::buffer(info.m_data), info.m_endpoint, [this, &info](boost::system::error_code ec, std::size_t bytesSent) diff --git a/gateway/app/gateway/GatewayIoClientAcceptor_Udp.h b/gateway/app/gateway/GatewayIoClientAcceptor_Udp.h index 67c594b1..83489845 100644 --- a/gateway/app/gateway/GatewayIoClientAcceptor_Udp.h +++ b/gateway/app/gateway/GatewayIoClientAcceptor_Udp.h @@ -18,7 +18,7 @@ namespace cc_mqttsn_gateway_app { -class GatewayIoClientAcceptor_Udp : public GatewayIoClientAcceptor +class GatewayIoClientAcceptor_Udp final : public GatewayIoClientAcceptor { using Base = GatewayIoClientAcceptor; public: @@ -29,6 +29,7 @@ class GatewayIoClientAcceptor_Udp : public GatewayIoClientAcceptor protected: virtual bool startImpl() override; + virtual void broadcastDataImpl(const std::uint8_t* buf, std::size_t bufSize) override; private: using Socket = boost::asio::ip::udp::socket; @@ -39,16 +40,21 @@ class GatewayIoClientAcceptor_Udp : public GatewayIoClientAcceptor { Endpoint m_endpoint; DataBuf m_data; + unsigned m_ttl = 0U; }; void doAccept(); - void sendData(const Endpoint& endpoint, const std::uint8_t* buf, std::size_t bufSize); + void sendData(const Endpoint& endpoint, const std::uint8_t* buf, std::size_t bufSize, unsigned broadcastRadius); void sendPendingWrites(); Socket m_socket; Endpoint m_senderEndpoint; + Endpoint m_broadcastEndpoint; std::uint16_t m_acceptPort = 0U; std::uint16_t m_broadcastPort = 0U; + std::string m_broadcastAddress; + unsigned m_defaultTtl = 128; + unsigned m_broadcastTtl = 128; std::array m_inBuf; ClientsMap m_clients; std::list m_pendingWrites; diff --git a/gateway/app/gateway/GatewayIoClientSocket.h b/gateway/app/gateway/GatewayIoClientSocket.h index b9a55ade..2ef02dfd 100644 --- a/gateway/app/gateway/GatewayIoClientSocket.h +++ b/gateway/app/gateway/GatewayIoClientSocket.h @@ -33,6 +33,11 @@ class GatewayIoClientSocket m_dataReportCb = std::forward(func); } + void sendData(const std::uint8_t* buf, std::size_t bufSize, unsigned broadcastRadius = 0) + { + sendDataImpl(buf, bufSize, broadcastRadius); + } + protected: GatewayIoClientSocket(boost::asio::io_context& io, GatewayLogger& logger) : m_io(io), @@ -41,6 +46,7 @@ class GatewayIoClientSocket }; virtual bool startImpl() = 0; + virtual void sendDataImpl(const std::uint8_t* buf, std::size_t bufSize, unsigned broadcastRadius) = 0; boost::asio::io_context& io() { diff --git a/gateway/app/gateway/GatewayIoClientSocket_Udp.cpp b/gateway/app/gateway/GatewayIoClientSocket_Udp.cpp index b3a845a4..cde148f6 100644 --- a/gateway/app/gateway/GatewayIoClientSocket_Udp.cpp +++ b/gateway/app/gateway/GatewayIoClientSocket_Udp.cpp @@ -44,6 +44,12 @@ bool GatewayIoClientSocket_Udp::startImpl() return true; } +void GatewayIoClientSocket_Udp::sendDataImpl(const std::uint8_t* buf, std::size_t bufSize, unsigned broadcastRadius) +{ + assert(m_sendDataCb); + m_sendDataCb(m_endpoint, buf, bufSize, broadcastRadius); +} + void GatewayIoClientSocket_Udp::reportPendingData() { while (!m_pendingData.empty()) { diff --git a/gateway/app/gateway/GatewayIoClientSocket_Udp.h b/gateway/app/gateway/GatewayIoClientSocket_Udp.h index bd71437d..9b650aca 100644 --- a/gateway/app/gateway/GatewayIoClientSocket_Udp.h +++ b/gateway/app/gateway/GatewayIoClientSocket_Udp.h @@ -16,7 +16,7 @@ namespace cc_mqttsn_gateway_app { -class GatewayIoClientSocket_Udp : public GatewayIoClientSocket +class GatewayIoClientSocket_Udp final : public GatewayIoClientSocket { using Base = GatewayIoClientSocket; public: @@ -32,7 +32,7 @@ class GatewayIoClientSocket_Udp : public GatewayIoClientSocket void newDataArrived(const std::uint8_t* buf, std::size_t bufSize); - using SendDataCb = std::function; + using SendDataCb = std::function; template void setSendDataCb(TFunc&& func) { @@ -49,6 +49,7 @@ class GatewayIoClientSocket_Udp : public GatewayIoClientSocket protected: virtual bool startImpl() override; + virtual void sendDataImpl(const std::uint8_t* buf, std::size_t bufSize, unsigned broadcastRadius) override; private: using DataBuf = std::vector; diff --git a/gateway/app/gateway/etc/cc_mqttsn_gateway.conf.example b/gateway/app/gateway/etc/cc_mqttsn_gateway.conf.example index e889a4dd..f50aaa73 100644 --- a/gateway/app/gateway/etc/cc_mqttsn_gateway.conf.example +++ b/gateway/app/gateway/etc/cc_mqttsn_gateway.conf.example @@ -83,12 +83,16 @@ #mqttsn_log_file stdout # ================================================================= -# Client socket configuration +# Socket configuration # ================================================================= -# Use "mqttsn_client_socket" option to specify the type of the I/O link socket +# Use "mqttsn_client_socket" option to specify the type of the client side I/O link socket # to use. Supported values are: "udp" (default) -# mqttsn_client_socket udp +#mqttsn_client_socket udp + +# Use "mqttsn_broker_socket" option to specify the type of the broker side I/O link socket +# to use. Supported values are: "tcp" (default) +#mqttsn_broker_socket tcp # ================================================================= # UDP configuration @@ -101,3 +105,11 @@ # Remote UDP port the gateway broadcasts its ADVERTISE messages to. Default is # 1883. Applicable only if "mqttsn_client_socket" is "udp". #udp_broadcast_port 1883 + +# UDP IPv4 address for broacasting. Default is 255.255.255.255. +# Applicable only if "mqttsn_client_socket" is "udp". +#udp_broadcast_address 255.255.255.255 + +# Default broadcast radius. Defaults to 128. +# Applicable only if "mqttsn_client_socket" is "udp". +#udp_broadcast_radius 128 diff --git a/gateway/lib/doc/config.dox b/gateway/lib/doc/config.dox index 927e53fa..cf03e051 100644 --- a/gateway/lib/doc/config.dox +++ b/gateway/lib/doc/config.dox @@ -382,16 +382,37 @@ /// /// @b C interface /// @code -/// CC_MqttsnClientConnectionType type = cc_mqttsn_gw_config_client_socket_type(handle); +/// CC_MqttsnClientConnectionType type = cc_mqttsn_gw_config_client_connection_type(handle); /// @endcode /// /// Applicable configuration file contents: /// @code{.unparsed} -/// # Use "mqttsn_client_socket" option to specify the type of the I/O link socket +/// # Use "mqttsn_client_socket" option to specify the type of the client side I/O link socket /// # to use. Supported values are: "udp" (default) /// mqttsn_client_socket udp /// @endcode /// +/// @section cc_mqttsn_gw_config_page_broker_socket Broker I/O Socket Type +/// The MQTT-SN gateway application can support multiple broker communication I/O +/// socket types. Below are API function that can retrieve such information. +/// +/// @b C++ interface +/// @code +/// cc_mqttsn_gateway::Config::BrokerConnectionType type = config.brokerConnectionType(); +/// @endcode +/// +/// @b C interface +/// @code +/// CC_MqttsnBrokerConnectionType type = cc_mqttsn_gw_config_broker_connection_type(handle); +/// @endcode +/// +/// Applicable configuration file contents: +/// @code{.unparsed} +/// # Use "mqttsn_broker_socket" option to specify the type of the broker side I/O link socket +/// # to use. Supported values are: "tcp" (default) +/// mqttsn_client_socket udp +/// @endcode +/// /// @section cc_mqttsn_gw_config_page_custom Custom Configuration Values /// The @b Config object has a list of predefined options it recognises in the /// configuration file. It also accumulates all the options it doesn't recognise. diff --git a/gateway/lib/include/cc_mqttsn_gateway/Config.h b/gateway/lib/include/cc_mqttsn_gateway/Config.h index f2c2bc9d..aad32a7b 100644 --- a/gateway/lib/include/cc_mqttsn_gateway/Config.h +++ b/gateway/lib/include/cc_mqttsn_gateway/Config.h @@ -78,6 +78,13 @@ class Config ClientConnectionType_ValuesLimit ///< Limit to available values, must be last }; + /// @brief Broker I/O socket connection type + enum BrokerConnectionType + { + BrokerConnectionType_Tcp, ///< TCP/IP + BrokerConnectionType_ValuesLimit ///< Limit to available values, must be last + }; + /// @brief Constructor Config(); @@ -152,8 +159,13 @@ class Config const std::string& logFile() const; /// @brief Get client side I/O socket connection type + /// @details Default value is @ref ClientConnectionType_Udp ClientConnectionType clientConnectionType() const; + /// @brief Get broker side I/O socket connection type + /// @details Default value is @ref BrokerConnectionType_Tcp + BrokerConnectionType brokerConnectionType() const; + private: std::unique_ptr m_pImpl; }; diff --git a/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h b/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h index be0cbd6d..74da72cd 100644 --- a/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h +++ b/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h @@ -497,6 +497,13 @@ typedef enum CC_MqttsnClientConnectionType_ValuesLimit ///< Limit to available values, must be last } CC_MqttsnClientConnectionType; +/// @brief Broker I/O socket connection type +typedef enum +{ + CC_MqttsnBrokerConnectionType_Tcp, ///< TCP/IP + CC_MqttsnBrokerConnectionType_ValuesLimit ///< Limit to available values, must be last +} CC_MqttsnBrokerConnectionType; + /// @brief Handle for configuration object used in all @b cc_mqttsn_gw_config_* functions. typedef struct { @@ -625,7 +632,11 @@ const char* cc_mqttsn_gw_config_log_file(CC_MqttsnConfigHandle config); /// @brief Get client I/O socket connection type /// @param config Handle returned by cc_mqttsn_gw_config_alloc() function. -CC_MqttsnClientConnectionType cc_mqttsn_gw_config_client_socket_type(CC_MqttsnConfigHandle config); +CC_MqttsnClientConnectionType cc_mqttsn_gw_config_client_connection_type(CC_MqttsnConfigHandle config); + +/// @brief Get broker I/O socket connection type +/// @param config Handle returned by cc_mqttsn_gw_config_alloc() function. +CC_MqttsnBrokerConnectionType cc_mqttsn_gw_config_broker_connection_type(CC_MqttsnConfigHandle config); /// @brief Get number of available configuration values for the provided key /// @details The key is the first word in the configuration line, and the diff --git a/gateway/lib/src/Config.cpp b/gateway/lib/src/Config.cpp index 399dbd91..673dc0bd 100644 --- a/gateway/lib/src/Config.cpp +++ b/gateway/lib/src/Config.cpp @@ -99,4 +99,9 @@ Config::ClientConnectionType Config::clientConnectionType() const return m_pImpl->clientConnectionType(); } +Config::BrokerConnectionType Config::brokerConnectionType() const +{ + return m_pImpl->brokerConnectionType(); +} + } // namespace cc_mqttsn_gateway diff --git a/gateway/lib/src/ConfigImpl.cpp b/gateway/lib/src/ConfigImpl.cpp index 8d255f0b..daee6637 100644 --- a/gateway/lib/src/ConfigImpl.cpp +++ b/gateway/lib/src/ConfigImpl.cpp @@ -36,6 +36,7 @@ const std::string TopicIdAllocRangeKey("mqttsn_topic_id_alloc_range"); const std::string BrokerKey("mqttsn_broker"); const std::string LogFileKey("mqttsn_log_file"); const std::string ClientSocketKey("mqttsn_client_socket"); +const std::string BrokerSocketKey("mqttsn_broker_socket"); const std::uint16_t DefaultAdvertise = 15 * 60; const unsigned DefaultRetryPeriod = 10; @@ -407,6 +408,32 @@ ConfigImpl::ClientConnectionType ConfigImpl::clientConnectionType() const return result; } +ConfigImpl::BrokerConnectionType ConfigImpl::brokerConnectionType() const +{ + static const std::map Map = { + {"tcp", BrokerConnectionType::BrokerConnectionType_Tcp}, + }; + + BrokerConnectionType result = BrokerConnectionType::BrokerConnectionType_Tcp; + + do { + auto typeStr = stringValue(BrokerSocketKey); + if (typeStr.empty()) { + break; + } + + auto iter = Map.find(typeStr); + if (iter == Map.end()) { + result = BrokerConnectionType::BrokerConnectionType_ValuesLimit; + break; + } + + result = iter->second; + } while (false); + + return result; +} + const std::string& ConfigImpl::stringValue( const std::string& key, const std::string& defaultValue) const diff --git a/gateway/lib/src/ConfigImpl.h b/gateway/lib/src/ConfigImpl.h index 577fce6e..81c9fbcf 100644 --- a/gateway/lib/src/ConfigImpl.h +++ b/gateway/lib/src/ConfigImpl.h @@ -24,6 +24,7 @@ class ConfigImpl using AuthInfosList = Config::AuthInfosList; using TopicIdsRange = Config::TopicIdsRange; using ClientConnectionType = Config::ClientConnectionType; + using BrokerConnectionType = Config::BrokerConnectionType; ConfigImpl() = default; @@ -61,6 +62,7 @@ class ConfigImpl const std::string& logFile() const; ClientConnectionType clientConnectionType() const; + BrokerConnectionType brokerConnectionType() const; private: template diff --git a/gateway/lib/src/gateway_all.cpp b/gateway/lib/src/gateway_all.cpp index 7e87fe2a..7158c742 100644 --- a/gateway/lib/src/gateway_all.cpp +++ b/gateway/lib/src/gateway_all.cpp @@ -695,15 +695,24 @@ const char* cc_mqttsn_gw_config_log_file(CC_MqttsnConfigHandle config) return reinterpret_cast(config.obj)->logFile().c_str(); } -CC_MqttsnClientConnectionType cc_mqttsn_gw_config_client_socket_type(CC_MqttsnConfigHandle config) +CC_MqttsnClientConnectionType cc_mqttsn_gw_config_client_connection_type(CC_MqttsnConfigHandle config) { if (config.obj == nullptr) { - return CC_MqttsnClientConnectionType_Udp; + return CC_MqttsnClientConnectionType_ValuesLimit; } return static_cast(reinterpret_cast(config.obj)->clientConnectionType()); } +CC_MqttsnBrokerConnectionType cc_mqttsn_gw_config_broker_connection_type(CC_MqttsnConfigHandle config) +{ + if (config.obj == nullptr) { + return CC_MqttsnBrokerConnectionType_ValuesLimit; + } + + return static_cast(reinterpret_cast(config.obj)->brokerConnectionType()); +} + unsigned cc_mqttsn_gw_config_values_count(CC_MqttsnConfigHandle config, const char* key) { if (config.obj == nullptr) { From d30a6a9ac262a52d1c0f9fdc4f4d4d8e37af64e1 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sat, 18 May 2024 15:56:40 +1000 Subject: [PATCH 022/106] Saving work on the gateway application. --- gateway/app/gateway/CMakeLists.txt | 2 + gateway/app/gateway/GatewayApp.cpp | 48 ++- gateway/app/gateway/GatewayApp.h | 8 + gateway/app/gateway/GatewayIoBrokerSocket.cpp | 3 +- gateway/app/gateway/GatewayIoBrokerSocket.h | 14 + .../app/gateway/GatewayIoBrokerSocket_Tcp.cpp | 6 + .../app/gateway/GatewayIoBrokerSocket_Tcp.h | 2 + .../gateway/GatewayIoClientAcceptor_Udp.cpp | 1 + gateway/app/gateway/GatewaySession.cpp | 329 ++++++++++++++++++ gateway/app/gateway/GatewaySession.h | 73 ++++ gateway/app/gateway/GatewayWrapper.cpp | 69 ++++ gateway/app/gateway/GatewayWrapper.h | 46 +++ .../lib/include/cc_mqttsn_gateway/Gateway.h | 4 +- 13 files changed, 600 insertions(+), 5 deletions(-) create mode 100644 gateway/app/gateway/GatewaySession.cpp create mode 100644 gateway/app/gateway/GatewaySession.h create mode 100644 gateway/app/gateway/GatewayWrapper.cpp create mode 100644 gateway/app/gateway/GatewayWrapper.h diff --git a/gateway/app/gateway/CMakeLists.txt b/gateway/app/gateway/CMakeLists.txt index 6eab6ae3..7ebf7441 100644 --- a/gateway/app/gateway/CMakeLists.txt +++ b/gateway/app/gateway/CMakeLists.txt @@ -12,6 +12,8 @@ function (bin_cc_mqttsn_gateway_app) GatewayIoClientSocket_Udp.cpp GatewayLogger.cpp GatewayProgramOptions.cpp + GatewaySession.cpp + GatewayWrapper.cpp ) add_executable(${name} ${src}) diff --git a/gateway/app/gateway/GatewayApp.cpp b/gateway/app/gateway/GatewayApp.cpp index 77e8944d..f2e34f86 100644 --- a/gateway/app/gateway/GatewayApp.cpp +++ b/gateway/app/gateway/GatewayApp.cpp @@ -15,7 +15,8 @@ namespace cc_mqttsn_gateway_app GatewayApp::GatewayApp(boost::asio::io_context& io) : - m_io(io) + m_io(io), + m_gwWrapper(io, m_logger) { } @@ -62,10 +63,53 @@ bool GatewayApp::start(int argc, const char* argv[]) return false; } + m_acceptor->setNewConnectionReportCb( + [this](GatewayIoClientSocketPtr clientSocket) + { + auto session = std::make_unique(m_io, m_logger, m_config, std::move(clientSocket)); + + session->setTermpReqCb( + [this, sessionPtr = session.get()]() + { + auto iter = + std::find_if( + m_sessions.begin(), m_sessions.end(), + [sessionPtr](auto& ptr) + { + return sessionPtr == ptr.get(); + }); + + assert(iter != m_sessions.end()); + if (iter == m_sessions.end()) { + return; + } + + m_sessions.erase(iter); + }); + + if (!session->start()) { + m_logger.error() << "Failed to start session" << std::endl; + return; + } + + m_sessions.push_back(std::move(session)); + }); + if (!m_acceptor->start()) { m_logger.error() << "Failed to start client socket" << std::endl; return false; - } + } + + m_gwWrapper.setBroadcastReqCb( + [this](const std::uint8_t* buf, std::size_t bufSize) + { + assert(m_acceptor); + m_acceptor->broadcastData(buf, bufSize); + }); + + if (!m_gwWrapper.start(m_config)) { + return false; + } return true; } diff --git a/gateway/app/gateway/GatewayApp.h b/gateway/app/gateway/GatewayApp.h index 1eb809a1..d3ad9e27 100644 --- a/gateway/app/gateway/GatewayApp.h +++ b/gateway/app/gateway/GatewayApp.h @@ -10,11 +10,15 @@ #include "GatewayIoClientAcceptor.h" #include "GatewayLogger.h" #include "GatewayProgramOptions.h" +#include "GatewaySession.h" +#include "GatewayWrapper.h" #include "cc_mqttsn_gateway/Config.h" #include +#include + namespace cc_mqttsn_gateway_app { @@ -27,10 +31,14 @@ class GatewayApp bool start(int argc, const char* argv[]); private: + using SessionsList = std::list; + boost::asio::io_context& m_io; cc_mqttsn_gateway::Config m_config; GatewayLogger m_logger; GatewayIoClientAcceptorPtr m_acceptor; + GatewayWrapper m_gwWrapper; + SessionsList m_sessions; }; } // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayIoBrokerSocket.cpp b/gateway/app/gateway/GatewayIoBrokerSocket.cpp index eeef7d67..cdc806e9 100644 --- a/gateway/app/gateway/GatewayIoBrokerSocket.cpp +++ b/gateway/app/gateway/GatewayIoBrokerSocket.cpp @@ -42,7 +42,8 @@ GatewayIoBrokerSocket::Ptr GatewayIoBrokerSocket::create(boost::asio::io_context bool GatewayIoBrokerSocket::start() { if ((!m_dataReportCb) || - (!m_errorReportCb)) { + (!m_errorReportCb) || + (!m_connectedReportCb)) { m_logger.error() << "Not all callbacks are set for GatewayIoBrokerSocket" << std::endl; return false; } diff --git a/gateway/app/gateway/GatewayIoBrokerSocket.h b/gateway/app/gateway/GatewayIoBrokerSocket.h index 7a207576..19340ad8 100644 --- a/gateway/app/gateway/GatewayIoBrokerSocket.h +++ b/gateway/app/gateway/GatewayIoBrokerSocket.h @@ -37,6 +37,14 @@ class GatewayIoBrokerSocket m_dataReportCb = std::forward(func); } + using ConnectedReportCb = std::function; + + template + void setConnectedReportCb(TFunc&& func) + { + m_connectedReportCb = std::forward(func); + } + using ErrorReportCb = std::function; template @@ -75,6 +83,11 @@ class GatewayIoBrokerSocket m_dataReportCb(buf, bufSize); } + void reportConnected() + { + m_connectedReportCb(); + } + void reportError() { m_errorReportCb(); @@ -84,6 +97,7 @@ class GatewayIoBrokerSocket boost::asio::io_context& m_io; GatewayLogger& m_logger; DataReportCb m_dataReportCb; + ConnectedReportCb m_connectedReportCb; ErrorReportCb m_errorReportCb; }; diff --git a/gateway/app/gateway/GatewayIoBrokerSocket_Tcp.cpp b/gateway/app/gateway/GatewayIoBrokerSocket_Tcp.cpp index 3b1e8f93..24713df7 100644 --- a/gateway/app/gateway/GatewayIoBrokerSocket_Tcp.cpp +++ b/gateway/app/gateway/GatewayIoBrokerSocket_Tcp.cpp @@ -21,6 +21,11 @@ GatewayIoBrokerSocket_Tcp::GatewayIoBrokerSocket_Tcp(boost::asio::io_context& io GatewayIoBrokerSocket_Tcp::~GatewayIoBrokerSocket_Tcp() = default; +GatewayIoBrokerSocket_Tcp::Ptr GatewayIoBrokerSocket_Tcp::create(boost::asio::io_context& io, GatewayLogger& logger, const cc_mqttsn_gateway::Config& config) +{ + return std::make_unique(io, logger, config); +} + bool GatewayIoBrokerSocket_Tcp::startImpl() { m_resolver.async_resolve( @@ -61,6 +66,7 @@ bool GatewayIoBrokerSocket_Tcp::startImpl() m_connected = true; doRead(); sendPendingWrites(); + reportConnected(); } ); diff --git a/gateway/app/gateway/GatewayIoBrokerSocket_Tcp.h b/gateway/app/gateway/GatewayIoBrokerSocket_Tcp.h index f6e5f77f..9c894891 100644 --- a/gateway/app/gateway/GatewayIoBrokerSocket_Tcp.h +++ b/gateway/app/gateway/GatewayIoBrokerSocket_Tcp.h @@ -23,6 +23,8 @@ class GatewayIoBrokerSocket_Tcp final : public GatewayIoBrokerSocket virtual ~GatewayIoBrokerSocket_Tcp(); + static Ptr create(boost::asio::io_context& io, GatewayLogger& logger, const cc_mqttsn_gateway::Config& config); + protected: virtual bool startImpl() override; diff --git a/gateway/app/gateway/GatewayIoClientAcceptor_Udp.cpp b/gateway/app/gateway/GatewayIoClientAcceptor_Udp.cpp index 8897f0e9..40281708 100644 --- a/gateway/app/gateway/GatewayIoClientAcceptor_Udp.cpp +++ b/gateway/app/gateway/GatewayIoClientAcceptor_Udp.cpp @@ -230,6 +230,7 @@ void GatewayIoClientAcceptor_Udp::sendPendingWrites() logger().error() << "Failed to update outgoing packet TTL: " << ecTmp.message() << std::endl; } + // logger().info() << "!!! (udp) <-- " << info.m_endpoint << " -- " << info.m_data.size() << std::endl; m_socket.async_send_to( boost::asio::buffer(info.m_data), info.m_endpoint, [this, &info](boost::system::error_code ec, std::size_t bytesSent) diff --git a/gateway/app/gateway/GatewaySession.cpp b/gateway/app/gateway/GatewaySession.cpp new file mode 100644 index 00000000..5fecfa93 --- /dev/null +++ b/gateway/app/gateway/GatewaySession.cpp @@ -0,0 +1,329 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "GatewaySession.h" + +namespace cc_mqttsn_gateway_app +{ + +namespace +{ + +const std::string WildcardStr("*"); + +} // namespace + + +GatewaySession::GatewaySession( + boost::asio::io_context& io, + GatewayLogger& logger, + const cc_mqttsn_gateway::Config& config, + GatewayIoClientSocketPtr clientSocket) : + m_io(io), + m_logger(logger), + m_config(config), + m_timer(io), + m_clientSocket(std::move(clientSocket)) +{ +} + +bool GatewaySession::start() +{ + assert(m_termReqCb); + if (!startSession()) { + return false; + } + + m_clientSocket->setDataReportCb( + [this](const std::uint8_t* buf, std::size_t bufSize) + { + // m_logger.info() << "!!! (client) --> " << bufSize << std::endl; + [[maybe_unused]] auto consumed = m_session.dataFromClient(buf, bufSize); + }); + + if (!m_clientSocket->start()) { + m_logger.error() << "Failed to start client socket" << std::endl; + return false; + } + + doBrokerConnect(); + return true; +} + +void GatewaySession::doTerminate() +{ + boost::asio::post( + m_io, + [this]() + { + assert(m_termReqCb); + m_termReqCb(); + }); +} + +void GatewaySession::doBrokerConnect() +{ + if (m_brokerConnected) { + m_session.setBrokerConnected(false); + } + + m_brokerSocket = GatewayIoBrokerSocket::create(m_io, m_logger, m_config); + if (!m_brokerSocket) { + m_logger.error() << "Failed to allocate broker socket " << std::endl; + doTerminate(); + return; + } + + m_brokerSocket->setDataReportCb( + [this](const std::uint8_t* buf, std::size_t bufSize) + { + // m_logger.info() << "!!! (broker) --> " << bufSize << std::endl; + + auto actBuf = buf; + auto actSize = bufSize; + + if (!m_brokerData.empty()) { + m_brokerData.insert(m_brokerData.end(), buf, buf + bufSize); + actBuf = m_brokerData.data(); + actSize = m_brokerData.size(); + } + + auto consumed = m_session.dataFromBroker(actBuf, actSize); + if (actSize <= consumed) { + m_brokerData.clear(); + return; + } + + if (m_brokerData.empty()) { + m_brokerData.assign(buf + consumed, buf + bufSize); + return; + } + + m_brokerData.erase(m_brokerData.begin(), m_brokerData.begin() + consumed); + }); + + m_brokerSocket->setConnectedReportCb( + [this]() + { + m_session.setBrokerConnected(true); + } + ); + + m_brokerSocket->setErrorReportCb( + [this]() + { + if (m_brokerConnected) { + m_session.setBrokerConnected(false); + } + + doBrokerReconnect(); + }); + + if (!m_brokerSocket->start()) { + m_logger.error() << "Failed to start TCP/IP socket" << std::endl; + doTerminate(); + return; + } +} + +void GatewaySession::doBrokerReconnect() +{ + boost::asio::post( + m_io, + [this]() + { + doBrokerConnect(); + }); +} + +bool GatewaySession::startSession() +{ + auto topicIdsAllocRange = m_config.topicIdAllocRange(); + + m_session.setGatewayId(m_config.gatewayId()); + m_session.setRetryPeriod(m_config.retryPeriod()); + m_session.setRetryCount(m_config.retryCount()); + m_session.setSleepingClientMsgLimit(m_config.sleepingClientMsgLimit()); + m_session.setDefaultClientId(m_config.defaultClientId()); + m_session.setPubOnlyKeepAlive(m_config.pubOnlyKeepAlive()); + m_session.setTopicIdAllocationRange(topicIdsAllocRange.first, topicIdsAllocRange.second); + + m_session.setNextTickProgramReqCb( + [this](unsigned ms) + { + m_tickReqTs = TimestampClock::now(); + m_timer.expires_after(std::chrono::milliseconds(ms)); + m_timer.async_wait( + [this](const boost::system::error_code& ec) + { + if (ec == boost::asio::error::operation_aborted) { + return; + } + + m_session.tick(); + }); + }); + + m_session.setCancelTickWaitReqCb( + [this]() -> unsigned + { + boost::system::error_code ec; + m_timer.cancel(ec); + assert(m_tickReqTs != Timestamp()); + auto now = TimestampClock::now(); + auto elapsed = std::chrono::duration_cast(now - m_tickReqTs); + return static_cast(elapsed.count()); + } + ); + + m_session.setSendDataClientReqCb( + [this](const std::uint8_t* buf, std::size_t bufSize) + { + assert(m_clientSocket); + // TODO: broadcast support + // m_logger.info() << "!!! (client) <-- " << bufSize << std::endl; + m_clientSocket->sendData(buf, bufSize); + }); + + m_session.setSendDataBrokerReqCb( + [this](const std::uint8_t* buf, std::size_t bufSize) + { + // m_logger.info() << "!!! (broker) <-- " << bufSize << std::endl; + assert(m_brokerSocket); + m_brokerSocket->sendData(buf, bufSize); + }); + + m_session.setTerminationReqCb( + [this]() + { + doTerminate(); + }); + + m_session.setBrokerReconnectReqCb( + [this]() + { + doBrokerReconnect(); + }); + + m_session.setClientConnectedReportCb( + [this](const std::string& clientId) + { + auto& predefinedTopics = m_config.predefinedTopics(); + + auto applyForClient = + [this, &predefinedTopics](const std::string& id) + { + auto iter = + std::lower_bound( + predefinedTopics.begin(), predefinedTopics.end(), id, + [](auto& info, const std::string& idParam) + { + return info.clientId < idParam; + }); + + if ((iter == predefinedTopics.end()) || (iter->clientId != id)) { + return; + } + + while (iter != predefinedTopics.end()) { + if (iter->clientId != id) { + break; + } + + m_session.addPredefinedTopic(iter->topic, iter->topicId); + ++iter; + } + }; + + applyForClient(clientId); + applyForClient(WildcardStr); + }); + + m_session.setAuthInfoReqCb( + [this](const std::string& clientId) + { + return getAuthInfoFor(clientId); + } + ); + + if (!m_session.start()) { + m_logger.error() << "Failed to start client session" << std::endl; + return false; + } + + return true; +} + +GatewaySession::AuthInfo GatewaySession::getAuthInfoFor(const std::string& clientId) +{ + auto& authInfos = m_config.authInfos(); + + auto findElemFunc = + [&authInfos](const std::string& cId) + { + return std::lower_bound( + authInfos.begin(), authInfos.end(), cId, + [](auto& elem, const std::string& val) -> bool + { + return elem.clientId < val; + }); + }; + + auto iter = findElemFunc(clientId); + if ((iter == authInfos.end()) || + (iter->clientId != clientId)) { + iter = findElemFunc(WildcardStr); + } + + if ((iter == authInfos.end()) || + (iter->clientId != clientId)) { + return AuthInfo(); + } + + using BinaryData = decltype(m_session)::BinaryData; + BinaryData data; + data.reserve(iter->password.size()); + + unsigned pos = 0U; + while (pos < iter->password.size()) { + auto remSize = iter->password.size() - pos; + const char* remStr = &iter->password[pos]; + + static const std::string BackSlashStr("\\\\"); + if ((BackSlashStr.size() <= remSize) && + (std::equal(BackSlashStr.begin(), BackSlashStr.end(), remStr))) { + data.push_back(static_cast('\\')); + pos = static_cast(pos + BackSlashStr.size()); + continue; + } + + static const std::string HexNumStr("\\x"); + static const std::size_t HexNumSize = HexNumStr.size() + 2; + if ((HexNumSize <= remSize) && + (std::equal(HexNumStr.begin(), HexNumStr.end(), remStr))) { + try { + auto* numStrBegin = &remStr[2]; + auto* numStrEnd = numStrBegin + 2; + std::string numStr(numStrBegin, numStrEnd); + auto byte = static_cast(std::stoul(numStr)); + data.push_back(byte); + pos = static_cast(pos + HexNumSize); + continue; + } + catch (...) { + // do nothing, fall through + } + } + + data.push_back(static_cast(iter->password[pos])); + ++pos; + } + + return std::make_pair(iter->username, std::move(data)); +} + +} // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewaySession.h b/gateway/app/gateway/GatewaySession.h new file mode 100644 index 00000000..c106e5fd --- /dev/null +++ b/gateway/app/gateway/GatewaySession.h @@ -0,0 +1,73 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "GatewayLogger.h" +#include "GatewayIoBrokerSocket.h" +#include "GatewayIoClientSocket.h" + +#include "cc_mqttsn_gateway/Config.h" +#include "cc_mqttsn_gateway/Session.h" + +#include + +#include +#include +#include +#include + +namespace cc_mqttsn_gateway_app +{ + +class GatewaySession +{ +public: + explicit GatewaySession( + boost::asio::io_context& io, + GatewayLogger& logger, + const cc_mqttsn_gateway::Config& config, + GatewayIoClientSocketPtr clientSocket); + + bool start(); + + using TermpReqCb = std::function; + template + void setTermpReqCb(TFunc&& func) + { + m_termReqCb = std::forward(func); + } + +private: + using Timer = boost::asio::steady_timer; + using TimestampClock = std::chrono::steady_clock; + using Timestamp = std::chrono::time_point; + using DataBuf = std::vector; + using AuthInfo = cc_mqttsn_gateway::Session::AuthInfo; + + void doTerminate(); + void doBrokerConnect(); + void doBrokerReconnect(); + bool startSession(); + AuthInfo getAuthInfoFor(const std::string& clientId); + + boost::asio::io_context& m_io; + GatewayLogger& m_logger; + const cc_mqttsn_gateway::Config& m_config; + Timer m_timer; + GatewayIoClientSocketPtr m_clientSocket; + GatewayIoBrokerSocketPtr m_brokerSocket; + cc_mqttsn_gateway::Session m_session; + TermpReqCb m_termReqCb; + DataBuf m_brokerData; + Timestamp m_tickReqTs; + bool m_brokerConnected = false; +}; + +using GatewaySessionPtr = std::unique_ptr; + +} // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayWrapper.cpp b/gateway/app/gateway/GatewayWrapper.cpp new file mode 100644 index 00000000..b3c5f52b --- /dev/null +++ b/gateway/app/gateway/GatewayWrapper.cpp @@ -0,0 +1,69 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "GatewayWrapper.h" + +#include + +namespace cc_mqttsn_gateway_app +{ + + +GatewayWrapper::GatewayWrapper(boost::asio::io_context& io, GatewayLogger& logger) : + m_logger(logger), + m_timer(io) +{ +} + +GatewayWrapper::~GatewayWrapper() = default; + +bool GatewayWrapper::start(const cc_mqttsn_gateway::Config& config) +{ + assert(m_broadcastReqCb); + if (!m_broadcastReqCb) { + m_logger.error() << "Not all callbacks are set for the GatewayWrapper" << std::endl; + return false; + } + + m_gw.setGatewayId(config.gatewayId()); + m_gw.setAdvertisePeriod(config.advertisePeriod()); + m_gw.setNextTickProgramReqCb( + [this](unsigned ms) + { + m_timer.expires_after(std::chrono::milliseconds(ms)); + m_timer.async_wait( + [this](const boost::system::error_code& ec) + { + if (ec == boost::asio::error::operation_aborted) { + return; + } + + if (ec) { + m_logger.error() << "Timer error: " << ec.message() << std::endl; + return; + } + + m_gw.tick(); + }); + }); + + m_gw.setSendDataReqCb( + [this](const std::uint8_t* buf, std::size_t bufSize) + { + m_broadcastReqCb(buf, bufSize); + }); + + + if (!m_gw.start()) { + m_logger.error() << "Failed to start Gateway" << std::endl; + return false; + } + + return true; +} + +} // namespace cc_mqttsn_gateway_app diff --git a/gateway/app/gateway/GatewayWrapper.h b/gateway/app/gateway/GatewayWrapper.h new file mode 100644 index 00000000..2acf0cda --- /dev/null +++ b/gateway/app/gateway/GatewayWrapper.h @@ -0,0 +1,46 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "GatewayLogger.h" + +#include "cc_mqttsn_gateway/Config.h" +#include "cc_mqttsn_gateway/Gateway.h" + +#include + +#include + +namespace cc_mqttsn_gateway_app +{ + +class GatewayWrapper +{ +public: + explicit GatewayWrapper(boost::asio::io_context& io, GatewayLogger& logger); + ~GatewayWrapper(); + + bool start(const cc_mqttsn_gateway::Config& config); + + using BroadcastReqCb = std::function; + template + void setBroadcastReqCb(TFunc&& func) + { + m_broadcastReqCb = std::forward(func); + } + +private: + using Timer = boost::asio::steady_timer; + + GatewayLogger& m_logger; + Timer m_timer; + cc_mqttsn_gateway::Gateway m_gw; + BroadcastReqCb m_broadcastReqCb; +}; + +} // namespace cc_mqttsn_gateway_app diff --git a/gateway/lib/include/cc_mqttsn_gateway/Gateway.h b/gateway/lib/include/cc_mqttsn_gateway/Gateway.h index 3fbd0a5e..89363c71 100644 --- a/gateway/lib/include/cc_mqttsn_gateway/Gateway.h +++ b/gateway/lib/include/cc_mqttsn_gateway/Gateway.h @@ -37,7 +37,7 @@ class Gateway /// when the requested time expires. The callback is set using /// setNextTickProgramReqCb() member function. /// @param[in] ms Number of @b milliseconds to measure. - typedef std::function NextTickProgramReqCb; + using NextTickProgramReqCb = std::function; /// @brief Type of callback used to request send the serialised @b ADVERTISE message. /// @details According to MQTT-SN protocol, the @b ADVERTISE message needs @@ -54,7 +54,7 @@ class Gateway /// @param[in] buf Pointer to buffer containing serialised @b ADVERTISE /// message. /// @param[in] bufSize Number of bytes in the buffer. - typedef std::function SendDataReqCb; + using SendDataReqCb = std::function; /// @brief Set the callback to be invoked when new time measurement is required. /// @details This is a must have callback, without it the object can not From 80cdf7031bfea5b120b5e721898a0b6022d40625 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sat, 18 May 2024 17:39:16 +1000 Subject: [PATCH 023/106] Support for the broadcast from the Sessions object. --- gateway/app/gateway/GatewaySession.cpp | 5 +- .../lib/include/cc_mqttsn_gateway/Session.h | 15 +++-- .../include/cc_mqttsn_gateway/gateway_all.h | 17 ++++-- gateway/lib/src/Session.cpp | 4 +- gateway/lib/src/SessionImpl.cpp | 58 ++++++++++--------- gateway/lib/src/SessionImpl.h | 14 ++--- gateway/lib/src/gateway_all.cpp | 8 +-- gateway/lib/src/session_op/Encapsulate.cpp | 12 ++-- gateway/lib/src/session_op/Encapsulate.h | 2 +- gateway/lib/test/Session.th | 2 +- 10 files changed, 79 insertions(+), 58 deletions(-) diff --git a/gateway/app/gateway/GatewaySession.cpp b/gateway/app/gateway/GatewaySession.cpp index 5fecfa93..e8772420 100644 --- a/gateway/app/gateway/GatewaySession.cpp +++ b/gateway/app/gateway/GatewaySession.cpp @@ -181,12 +181,11 @@ bool GatewaySession::startSession() ); m_session.setSendDataClientReqCb( - [this](const std::uint8_t* buf, std::size_t bufSize) + [this](const std::uint8_t* buf, std::size_t bufSize, unsigned broadcastRadius) { assert(m_clientSocket); - // TODO: broadcast support // m_logger.info() << "!!! (client) <-- " << bufSize << std::endl; - m_clientSocket->sendData(buf, bufSize); + m_clientSocket->sendData(buf, bufSize, broadcastRadius); }); m_session.setSendDataBrokerReqCb( diff --git a/gateway/lib/include/cc_mqttsn_gateway/Session.h b/gateway/lib/include/cc_mqttsn_gateway/Session.h index 82b78e49..7d6c0dae 100644 --- a/gateway/lib/include/cc_mqttsn_gateway/Session.h +++ b/gateway/lib/include/cc_mqttsn_gateway/Session.h @@ -52,10 +52,17 @@ class Session using CancelTickWaitReqCb = std::function; /// @brief Type of callback, used to request delivery of serialised message - /// to the client or broker. + /// to the client. + /// @param[in] buf Buffer containing serialised message. + /// @param[in] bufSize Number of bytes in the buffer + /// @param[in] broadcastRadius Broadcast radius. 0 means unitcast + using ClientSendDataReqCb = std::function; + + /// @brief Type of callback, used to request delivery of serialised message + /// to the broker. /// @param[in] buf Buffer containing serialised message. /// @param[in] bufSize Number of bytes in the buffer - using SendDataReqCb = std::function; + using BrokerSendDataReqCb = std::function; /// @brief Type of callback, used to request session termination. /// @details When the callback is invoked, the driving code must flush @@ -122,14 +129,14 @@ class Session /// @details This is a must have callback, without it the object can not /// be started (see start()). /// @param[in] func R-value reference to the callback object - void setSendDataClientReqCb(SendDataReqCb&& func); + void setSendDataClientReqCb(ClientSendDataReqCb&& func); /// @brief Set the callback to be invoked when new data needs to be sent /// to the broker. /// @details This is a must have callback, without it the object can not /// be started (see start()). /// @param[in] func R-value reference to the callback object - void setSendDataBrokerReqCb(SendDataReqCb&& func); + void setSendDataBrokerReqCb(BrokerSendDataReqCb&& func); /// @brief Set the callback to be invoked when the session needs to be /// terminated and this @ref Session object deleted. diff --git a/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h b/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h index 74da72cd..b1a6ff1d 100644 --- a/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h +++ b/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h @@ -133,12 +133,21 @@ typedef void (*CC_MqttsnSessionTickReqCb)(void* userData, CC_MqttsnSessionHandle typedef unsigned (*CC_MqttsnSessionCancelTickReqCb)(void* userData, CC_MqttsnSessionHandle session); /// @brief Type of callback, used to request delivery of serialised message -/// to the client or broker. +/// to the client. /// @param[in] userData User data passed as the last parameter to the setting function. /// @param[in] session Handle of session performing the request /// @param[in] buf Buffer containing serialised message. /// @param[in] bufLen Number of bytes in the buffer -typedef void (*CC_MqttsnSessionSendDataReqCb)(void* userData, CC_MqttsnSessionHandle session, const unsigned char* buf, unsigned bufLen); +/// @param[in] broadcastRadius Broadcast radius. 0 means unicast. +typedef void (*CC_MqttsnSessionClientSendDataReqCb)(void* userData, CC_MqttsnSessionHandle session, const unsigned char* buf, unsigned bufLen, unsigned broadcastRadius); + +/// @brief Type of callback, used to request delivery of serialised message +/// to the broker. +/// @param[in] userData User data passed as the last parameter to the setting function. +/// @param[in] session Handle of session performing the request +/// @param[in] buf Buffer containing serialised message. +/// @param[in] bufLen Number of bytes in the buffer +typedef void (*CC_MqttsnSessionBrokerSendDataReqCb)(void* userData, CC_MqttsnSessionHandle session, const unsigned char* buf, unsigned bufLen); /// @brief Type of callback, used to request session termination. /// @details When the callback is invoked, the driving code must flush @@ -244,7 +253,7 @@ void cc_mqttsn_gw_session_set_cancel_tick_cb( /// parameter to the callback. void cc_mqttsn_gw_session_set_send_data_to_client_cb( CC_MqttsnSessionHandle session, - CC_MqttsnSessionSendDataReqCb cb, + CC_MqttsnSessionClientSendDataReqCb cb, void* data); /// @brief Set the callback to be invoked when new data needs to be sent @@ -257,7 +266,7 @@ void cc_mqttsn_gw_session_set_send_data_to_client_cb( /// parameter to the callback. void cc_mqttsn_gw_session_set_send_data_to_broker_cb( CC_MqttsnSessionHandle session, - CC_MqttsnSessionSendDataReqCb cb, + CC_MqttsnSessionBrokerSendDataReqCb cb, void* data); /// @brief Set the callback to be invoked when the @b Session needs to be diff --git a/gateway/lib/src/Session.cpp b/gateway/lib/src/Session.cpp index c4477b8c..0f286664 100644 --- a/gateway/lib/src/Session.cpp +++ b/gateway/lib/src/Session.cpp @@ -28,12 +28,12 @@ void Session::setCancelTickWaitReqCb(CancelTickWaitReqCb&& func) m_pImpl->setCancelTickWaitReqCb(std::move(func)); } -void Session::setSendDataClientReqCb(SendDataReqCb&& func) +void Session::setSendDataClientReqCb(ClientSendDataReqCb&& func) { m_pImpl->setSendDataClientReqCb(std::move(func)); } -void Session::setSendDataBrokerReqCb(SendDataReqCb&& func) +void Session::setSendDataBrokerReqCb(BrokerSendDataReqCb&& func) { m_pImpl->setSendDataBrokerReqCb(std::move(func)); } diff --git a/gateway/lib/src/SessionImpl.cpp b/gateway/lib/src/SessionImpl.cpp index a9c869c4..387e9594 100644 --- a/gateway/lib/src/SessionImpl.cpp +++ b/gateway/lib/src/SessionImpl.cpp @@ -34,26 +34,6 @@ const unsigned NoTimeout = std::numeric_limits::max(); } // namespace -template -void SessionImpl::sendMessage(const TMsg& msg, TFrame& frame, SendDataReqCb& func, DataBuf& buf) -{ - if (!func) { - return; - } - - typedef typename TFrame::MsgPtr::element_type MsgType; - - buf.resize(std::max(buf.size(), frame.length(msg))); - auto iter = comms::writeIteratorFor(&buf[0]); - [[maybe_unused]] auto es = frame.write(msg, iter, buf.size()); - assert(es == comms::ErrorStatus::Success); - auto writtenCount = - static_cast( - std::distance(comms::writeIteratorFor(&buf[0]), iter)); - - func(&buf[0], writtenCount); -} - template void SessionImpl::dispatchToOpsCommon(TMsg& msg) { @@ -216,20 +196,44 @@ void SessionImpl::reportFwdEncSessionDeleted(Session* session) m_fwdEncSessionDeletedReportCb(session); } -void SessionImpl::sendDataToClient(const std::uint8_t* buf, std::size_t bufLen) +void SessionImpl::sendDataToClient(const std::uint8_t* buf, std::size_t bufLen, unsigned brokerRadius) { assert(m_sendToClientCb); - m_sendToClientCb(buf, bufLen); + m_sendToClientCb(buf, bufLen, brokerRadius); } -void SessionImpl::sendToClient(const MqttsnMessage& msg) +void SessionImpl::sendToClient(const MqttsnMessage& msg, unsigned broadcastRadius) { - sendMessage(msg, m_mqttsnFrame, m_sendToClientCb, m_mqttsnMsgData); + if (!m_sendToClientCb) { + return; + } + + m_mqttsnMsgData.resize(std::max(m_mqttsnMsgData.size(), m_mqttsnFrame.length(msg))); + auto iter = comms::writeIteratorFor(m_mqttsnMsgData.data()); + [[maybe_unused]] auto es = m_mqttsnFrame.write(msg, iter, m_mqttsnMsgData.size()); + assert(es == comms::ErrorStatus::Success); + auto writtenCount = + static_cast( + std::distance(comms::writeIteratorFor(m_mqttsnMsgData.data()), iter)); + + m_sendToClientCb(m_mqttsnMsgData.data(), writtenCount, broadcastRadius); } void SessionImpl::sendToBroker(const MqttMessage& msg) { - sendMessage(msg, m_mqttFrame, m_sendToBrokerCb, m_mqttMsgData); + if (!m_sendToBrokerCb) { + return; + } + + m_mqttMsgData.resize(std::max(m_mqttMsgData.size(), m_mqttFrame.length(msg))); + auto iter = comms::writeIteratorFor(m_mqttMsgData.data()); + [[maybe_unused]] auto es = m_mqttFrame.write(msg, iter, m_mqttMsgData.size()); + assert(es == comms::ErrorStatus::Success); + auto writtenCount = + static_cast( + std::distance(comms::writeIteratorFor(m_mqttMsgData.data()), iter)); + + m_sendToBrokerCb(m_mqttMsgData.data(), writtenCount); } void SessionImpl::termRequest() @@ -269,13 +273,13 @@ SessionImpl::AuthInfo SessionImpl::authInfoRequest(const std::string& clientId) return m_authInfoReqCb(clientId); } -void SessionImpl::handle([[maybe_unused]] SearchgwMsg_SN& msg) +void SessionImpl::handle(SearchgwMsg_SN& msg) { GwinfoMsg_SN respMsg; auto& fields = respMsg.fields(); auto& gwIdField = std::get(fields); gwIdField.value() = m_state.m_gwId; - sendToClient(respMsg); + sendToClient(respMsg, msg.field_radius().value()); if (m_state.m_connStatus != ConnectionStatus::Disconnected) { sendToBroker(PingreqMsg()); diff --git a/gateway/lib/src/SessionImpl.h b/gateway/lib/src/SessionImpl.h index 7e9f16ad..122fdb5a 100644 --- a/gateway/lib/src/SessionImpl.h +++ b/gateway/lib/src/SessionImpl.h @@ -29,7 +29,8 @@ class SessionImpl : public MsgHandler public: using AuthInfo = Session::AuthInfo; using NextTickProgramReqCb = Session::NextTickProgramReqCb; - using SendDataReqCb = Session::SendDataReqCb; + using ClientSendDataReqCb = Session::ClientSendDataReqCb; + using BrokerSendDataReqCb = Session::BrokerSendDataReqCb; using CancelTickWaitReqCb = Session::CancelTickWaitReqCb; using TerminationReqCb = Session::TerminationReqCb; using BrokerReconnectReqCb = Session::BrokerReconnectReqCb; @@ -166,8 +167,8 @@ class SessionImpl : public MsgHandler bool reportFwdEncSessionCreated(Session* session); void reportFwdEncSessionDeleted(Session* session); - void sendDataToClient(const std::uint8_t* buf, std::size_t bufLen); - void sendToClient(const MqttsnMessage& msg); + void sendDataToClient(const std::uint8_t* buf, std::size_t bufLen, unsigned brokerRadius); + void sendToClient(const MqttsnMessage& msg, unsigned brokerRadius = 0U); void sendToBroker(const MqttMessage& msg); void termRequest(); void brokerReconnectRequest(); @@ -186,9 +187,6 @@ class SessionImpl : public MsgHandler virtual void handle(MqttMessage& msg) override; - template - void sendMessage(const TMsg& msg, TFrame& frame, SendDataReqCb& func, DataBuf& buf); - template void dispatchToOpsCommon(TMsg& msg); @@ -208,8 +206,8 @@ class SessionImpl : public MsgHandler NextTickProgramReqCb m_nextTickProgramCb; CancelTickWaitReqCb m_cancelTickCb; - SendDataReqCb m_sendToClientCb; - SendDataReqCb m_sendToBrokerCb; + ClientSendDataReqCb m_sendToClientCb; + BrokerSendDataReqCb m_sendToBrokerCb; TerminationReqCb m_termReqCb; BrokerReconnectReqCb m_brokerReconnectReqCb; ClientConnectedReportCb m_clientConnectedCb; diff --git a/gateway/lib/src/gateway_all.cpp b/gateway/lib/src/gateway_all.cpp index 7158c742..47bf13dd 100644 --- a/gateway/lib/src/gateway_all.cpp +++ b/gateway/lib/src/gateway_all.cpp @@ -162,7 +162,7 @@ void cc_mqttsn_gw_session_set_cancel_tick_cb( void cc_mqttsn_gw_session_set_send_data_to_client_cb( CC_MqttsnSessionHandle session, - CC_MqttsnSessionSendDataReqCb cb, + CC_MqttsnSessionClientSendDataReqCb cb, void* data) { if ((session.obj == nullptr) || (cb == nullptr)) { @@ -170,16 +170,16 @@ void cc_mqttsn_gw_session_set_send_data_to_client_cb( } reinterpret_cast(session.obj)->setSendDataClientReqCb( - [cb, data, session](const std::uint8_t* buf, std::size_t bufLen) + [cb, data, session](const std::uint8_t* buf, std::size_t bufLen, unsigned broadcastRadius) { - cb(data, session, buf, static_cast(bufLen)); + cb(data, session, buf, static_cast(bufLen), broadcastRadius); }); } void cc_mqttsn_gw_session_set_send_data_to_broker_cb( CC_MqttsnSessionHandle session, - CC_MqttsnSessionSendDataReqCb cb, + CC_MqttsnSessionBrokerSendDataReqCb cb, void* data) { if ((session.obj == nullptr) || (cb == nullptr)) { diff --git a/gateway/lib/src/session_op/Encapsulate.cpp b/gateway/lib/src/session_op/Encapsulate.cpp index 4863c0c7..769b05a3 100644 --- a/gateway/lib/src/session_op/Encapsulate.cpp +++ b/gateway/lib/src/session_op/Encapsulate.cpp @@ -91,9 +91,9 @@ void Encapsulate::handle(FwdMsg_SN& msg) } sessionPtr->setSendDataClientReqCb( - [this, nodeId](const std::uint8_t* buf, std::size_t bufSize) + [this, nodeId](const std::uint8_t* buf, std::size_t bufSize, unsigned broadcastRadius) { - sendDataClientReqFromSession(nodeId, buf, bufSize); + sendDataClientReqFromSession(nodeId, buf, bufSize, broadcastRadius); }); sessionPtr->setTerminationReqCb( @@ -117,7 +117,11 @@ void Encapsulate::handle(FwdMsg_SN& msg) } } -void Encapsulate::sendDataClientReqFromSession(const NodeId& nodeId, const std::uint8_t* buf, std::size_t bufSize) +void Encapsulate::sendDataClientReqFromSession( + const NodeId& nodeId, + const std::uint8_t* buf, + std::size_t bufSize, + unsigned broadcastRadius) { FwdMsg_SN fwdMsg; fwdMsg.field_ctrl().field_radius().setValue(3); // TODO: make it configurable @@ -130,7 +134,7 @@ void Encapsulate::sendDataClientReqFromSession(const NodeId& nodeId, const std:: [[maybe_unused]] auto es = frame.write(fwdMsg, writeIter, data.size()); assert(es == comms::ErrorStatus::Success); std::copy_n(buf, bufSize, writeIter); - session().sendDataToClient(data.data(), data.size()); + session().sendDataToClient(data.data(), data.size(), broadcastRadius); } void Encapsulate::terminationReqFromSession(Session* sessionPtr) diff --git a/gateway/lib/src/session_op/Encapsulate.h b/gateway/lib/src/session_op/Encapsulate.h index fd0032f6..1055e54a 100644 --- a/gateway/lib/src/session_op/Encapsulate.h +++ b/gateway/lib/src/session_op/Encapsulate.h @@ -42,7 +42,7 @@ class Encapsulate : public SessionOp using Base::handle; virtual void handle(FwdMsg_SN& msg) override; - void sendDataClientReqFromSession(const NodeId& nodeId, const std::uint8_t* buf, std::size_t bufSize); + void sendDataClientReqFromSession(const NodeId& nodeId, const std::uint8_t* buf, std::size_t bufSize, unsigned broadcastRadius); void terminationReqFromSession(Session* sessionPtr); SessionMap m_sessions; diff --git a/gateway/lib/test/Session.th b/gateway/lib/test/Session.th index a49ddb24..d90d82a2 100644 --- a/gateway/lib/test/Session.th +++ b/gateway/lib/test/Session.th @@ -126,7 +126,7 @@ private: }); session.setSendDataClientReqCb( - [&state](const std::uint8_t* buf, std::size_t bufSize) + [&state](const std::uint8_t* buf, std::size_t bufSize, [[maybe_unused]] unsigned broadcastRadius) { state.m_sentToClient.emplace_back(buf, buf + bufSize); }); From 5be4e80926124c26c421c19bbe3fc76e557ab709 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Mon, 20 May 2024 08:29:21 +1000 Subject: [PATCH 024/106] Supporting forward encapsulated session in the gateway application. --- .../gateway/GatewayIoClientAcceptor_Udp.cpp | 1 - gateway/app/gateway/GatewaySession.cpp | 158 ++++++++++++------ gateway/app/gateway/GatewaySession.h | 17 +- script/appveyor_install.bat | 2 +- 4 files changed, 124 insertions(+), 54 deletions(-) diff --git a/gateway/app/gateway/GatewayIoClientAcceptor_Udp.cpp b/gateway/app/gateway/GatewayIoClientAcceptor_Udp.cpp index 40281708..8897f0e9 100644 --- a/gateway/app/gateway/GatewayIoClientAcceptor_Udp.cpp +++ b/gateway/app/gateway/GatewayIoClientAcceptor_Udp.cpp @@ -230,7 +230,6 @@ void GatewayIoClientAcceptor_Udp::sendPendingWrites() logger().error() << "Failed to update outgoing packet TTL: " << ecTmp.message() << std::endl; } - // logger().info() << "!!! (udp) <-- " << info.m_endpoint << " -- " << info.m_data.size() << std::endl; m_socket.async_send_to( boost::asio::buffer(info.m_data), info.m_endpoint, [this, &info](boost::system::error_code ec, std::size_t bytesSent) diff --git a/gateway/app/gateway/GatewaySession.cpp b/gateway/app/gateway/GatewaySession.cpp index e8772420..27d95820 100644 --- a/gateway/app/gateway/GatewaySession.cpp +++ b/gateway/app/gateway/GatewaySession.cpp @@ -27,10 +27,25 @@ GatewaySession::GatewaySession( m_logger(logger), m_config(config), m_timer(io), - m_clientSocket(std::move(clientSocket)) + m_clientSocket(std::move(clientSocket)), + m_sessionPtr(std::make_unique()), + m_session(m_sessionPtr.get()) { } +GatewaySession::GatewaySession( + boost::asio::io_context& io, + GatewayLogger& logger, + const cc_mqttsn_gateway::Config& config, + cc_mqttsn_gateway::Session* session) : + m_io(io), + m_logger(logger), + m_config(config), + m_timer(io), + m_session(session) +{ +} + bool GatewaySession::start() { assert(m_termReqCb); @@ -38,17 +53,19 @@ bool GatewaySession::start() return false; } - m_clientSocket->setDataReportCb( - [this](const std::uint8_t* buf, std::size_t bufSize) - { - // m_logger.info() << "!!! (client) --> " << bufSize << std::endl; - [[maybe_unused]] auto consumed = m_session.dataFromClient(buf, bufSize); - }); + if (m_clientSocket) { + m_clientSocket->setDataReportCb( + [this](const std::uint8_t* buf, std::size_t bufSize) + { + assert(m_sessionPtr.get() == m_session); + [[maybe_unused]] auto consumed = m_session->dataFromClient(buf, bufSize); + }); - if (!m_clientSocket->start()) { - m_logger.error() << "Failed to start client socket" << std::endl; - return false; - } + if (!m_clientSocket->start()) { + m_logger.error() << "Failed to start client socket" << std::endl; + return false; + } + } doBrokerConnect(); return true; @@ -68,7 +85,7 @@ void GatewaySession::doTerminate() void GatewaySession::doBrokerConnect() { if (m_brokerConnected) { - m_session.setBrokerConnected(false); + m_session->setBrokerConnected(false); } m_brokerSocket = GatewayIoBrokerSocket::create(m_io, m_logger, m_config); @@ -81,8 +98,6 @@ void GatewaySession::doBrokerConnect() m_brokerSocket->setDataReportCb( [this](const std::uint8_t* buf, std::size_t bufSize) { - // m_logger.info() << "!!! (broker) --> " << bufSize << std::endl; - auto actBuf = buf; auto actSize = bufSize; @@ -92,7 +107,7 @@ void GatewaySession::doBrokerConnect() actSize = m_brokerData.size(); } - auto consumed = m_session.dataFromBroker(actBuf, actSize); + auto consumed = m_session->dataFromBroker(actBuf, actSize); if (actSize <= consumed) { m_brokerData.clear(); return; @@ -109,7 +124,7 @@ void GatewaySession::doBrokerConnect() m_brokerSocket->setConnectedReportCb( [this]() { - m_session.setBrokerConnected(true); + m_session->setBrokerConnected(true); } ); @@ -117,7 +132,7 @@ void GatewaySession::doBrokerConnect() [this]() { if (m_brokerConnected) { - m_session.setBrokerConnected(false); + m_session->setBrokerConnected(false); } doBrokerReconnect(); @@ -144,15 +159,15 @@ bool GatewaySession::startSession() { auto topicIdsAllocRange = m_config.topicIdAllocRange(); - m_session.setGatewayId(m_config.gatewayId()); - m_session.setRetryPeriod(m_config.retryPeriod()); - m_session.setRetryCount(m_config.retryCount()); - m_session.setSleepingClientMsgLimit(m_config.sleepingClientMsgLimit()); - m_session.setDefaultClientId(m_config.defaultClientId()); - m_session.setPubOnlyKeepAlive(m_config.pubOnlyKeepAlive()); - m_session.setTopicIdAllocationRange(topicIdsAllocRange.first, topicIdsAllocRange.second); + m_session->setGatewayId(m_config.gatewayId()); + m_session->setRetryPeriod(m_config.retryPeriod()); + m_session->setRetryCount(m_config.retryCount()); + m_session->setSleepingClientMsgLimit(m_config.sleepingClientMsgLimit()); + m_session->setDefaultClientId(m_config.defaultClientId()); + m_session->setPubOnlyKeepAlive(m_config.pubOnlyKeepAlive()); + m_session->setTopicIdAllocationRange(topicIdsAllocRange.first, topicIdsAllocRange.second); - m_session.setNextTickProgramReqCb( + m_session->setNextTickProgramReqCb( [this](unsigned ms) { m_tickReqTs = TimestampClock::now(); @@ -164,11 +179,11 @@ bool GatewaySession::startSession() return; } - m_session.tick(); + m_session->tick(); }); }); - m_session.setCancelTickWaitReqCb( + m_session->setCancelTickWaitReqCb( [this]() -> unsigned { boost::system::error_code ec; @@ -180,35 +195,21 @@ bool GatewaySession::startSession() } ); - m_session.setSendDataClientReqCb( - [this](const std::uint8_t* buf, std::size_t bufSize, unsigned broadcastRadius) - { - assert(m_clientSocket); - // m_logger.info() << "!!! (client) <-- " << bufSize << std::endl; - m_clientSocket->sendData(buf, bufSize, broadcastRadius); - }); - m_session.setSendDataBrokerReqCb( + m_session->setSendDataBrokerReqCb( [this](const std::uint8_t* buf, std::size_t bufSize) { - // m_logger.info() << "!!! (broker) <-- " << bufSize << std::endl; assert(m_brokerSocket); m_brokerSocket->sendData(buf, bufSize); - }); - - m_session.setTerminationReqCb( - [this]() - { - doTerminate(); - }); + }); - m_session.setBrokerReconnectReqCb( + m_session->setBrokerReconnectReqCb( [this]() { doBrokerReconnect(); }); - m_session.setClientConnectedReportCb( + m_session->setClientConnectedReportCb( [this](const std::string& clientId) { auto& predefinedTopics = m_config.predefinedTopics(); @@ -233,7 +234,7 @@ bool GatewaySession::startSession() break; } - m_session.addPredefinedTopic(iter->topic, iter->topicId); + m_session->addPredefinedTopic(iter->topic, iter->topicId); ++iter; } }; @@ -242,14 +243,71 @@ bool GatewaySession::startSession() applyForClient(WildcardStr); }); - m_session.setAuthInfoReqCb( + m_session->setAuthInfoReqCb( [this](const std::string& clientId) { return getAuthInfoFor(clientId); } - ); + ); + + m_session->setFwdEncSessionCreatedReportCb( + [this](cc_mqttsn_gateway::Session* fwdEncSession) -> bool + { + m_fwdEncSessions.push_back(std::make_unique(m_io, m_logger, m_config, fwdEncSession)); + auto& sessionPtr = m_fwdEncSessions.back(); + if (!sessionPtr->start()) { + m_logger.error() << "Failed to start forwarder encapsulated session" << std::endl; + return false; + } + + return true; + }); + + m_session->setFwdEncSessionDeletedReportCb( + [this](cc_mqttsn_gateway::Session* fwdEncSession) + { + boost::asio::post( + m_io, + [this, fwdEncSession]() + { + auto iter = + std::find_if( + m_fwdEncSessions.begin(), m_fwdEncSessions.end(), + [fwdEncSession](auto& sPtr) + { + return sPtr->m_session == fwdEncSession; + }); + + assert(iter != m_fwdEncSessions.end()); + if (iter == m_fwdEncSessions.end()) { + return; + } + + m_fwdEncSessions.erase(iter); + }); + }); + + if (!m_sessionPtr) { + // Forwarder encapsulated session + return true; + } + + assert(m_sessionPtr.get() == m_session); + m_session->setSendDataClientReqCb( + [this](const std::uint8_t* buf, std::size_t bufSize, unsigned broadcastRadius) + { + assert(m_clientSocket); + m_clientSocket->sendData(buf, bufSize, broadcastRadius); + }); + + m_session->setTerminationReqCb( + [this]() + { + doTerminate(); + }); + - if (!m_session.start()) { + if (!m_session->start()) { m_logger.error() << "Failed to start client session" << std::endl; return false; } @@ -283,7 +341,7 @@ GatewaySession::AuthInfo GatewaySession::getAuthInfoFor(const std::string& clien return AuthInfo(); } - using BinaryData = decltype(m_session)::BinaryData; + using BinaryData = cc_mqttsn_gateway::Session::BinaryData; BinaryData data; data.reserve(iter->password.size()); diff --git a/gateway/app/gateway/GatewaySession.h b/gateway/app/gateway/GatewaySession.h index c106e5fd..df49b1ae 100644 --- a/gateway/app/gateway/GatewaySession.h +++ b/gateway/app/gateway/GatewaySession.h @@ -19,6 +19,7 @@ #include #include #include +#include #include namespace cc_mqttsn_gateway_app @@ -27,12 +28,22 @@ namespace cc_mqttsn_gateway_app class GatewaySession { public: + using Ptr = std::unique_ptr; + + // Used by the GatewayApp explicit GatewaySession( boost::asio::io_context& io, GatewayLogger& logger, const cc_mqttsn_gateway::Config& config, GatewayIoClientSocketPtr clientSocket); + // Used when forwarding encapsulated session is reported + explicit GatewaySession( + boost::asio::io_context& io, + GatewayLogger& logger, + const cc_mqttsn_gateway::Config& config, + cc_mqttsn_gateway::Session* session); + bool start(); using TermpReqCb = std::function; @@ -61,13 +72,15 @@ class GatewaySession Timer m_timer; GatewayIoClientSocketPtr m_clientSocket; GatewayIoBrokerSocketPtr m_brokerSocket; - cc_mqttsn_gateway::Session m_session; + std::unique_ptr m_sessionPtr; + cc_mqttsn_gateway::Session* m_session = nullptr; TermpReqCb m_termReqCb; DataBuf m_brokerData; Timestamp m_tickReqTs; + std::list m_fwdEncSessions; bool m_brokerConnected = false; }; -using GatewaySessionPtr = std::unique_ptr; +using GatewaySessionPtr = GatewaySession::Ptr; } // namespace cc_mqttsn_gateway_app diff --git a/script/appveyor_install.bat b/script/appveyor_install.bat index ae970143..5496519f 100644 --- a/script/appveyor_install.bat +++ b/script/appveyor_install.bat @@ -65,7 +65,7 @@ IF "%PLATFORM%"=="x86" ( set QTDIR=%QTDIR_PREFIX%/%QT_SUBDIR%%QTDIR_SUFFIX% IF NOT EXIST %QTDIR% ( - echo WARNING: %QTDIR% does not exist!!! + echo WARNING: %QTDIR% does not exist! set QTDIR=%QTDIR_PREFIX%/msvc2015%QTDIR_SUFFIX% ) From 3a20a06534612811ca5182e7d420a98337b73b27 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Mon, 20 May 2024 09:23:37 +1000 Subject: [PATCH 025/106] Added get functions for the gateway library. --- gateway/lib/doc/gateway.dox | 24 ++++++ gateway/lib/doc/session.dox | 76 ++++++++++++++++- .../lib/include/cc_mqttsn_gateway/Gateway.h | 6 ++ .../lib/include/cc_mqttsn_gateway/Session.h | 21 +++++ .../include/cc_mqttsn_gateway/gateway_all.h | 41 ++++++++- gateway/lib/src/Gateway.cpp | 10 +++ gateway/lib/src/GatewayImpl.h | 10 +++ gateway/lib/src/Session.cpp | 35 ++++++++ gateway/lib/src/SessionImpl.h | 36 ++++++++ gateway/lib/src/gateway_all.cpp | 85 ++++++++++++++++++- 10 files changed, 339 insertions(+), 5 deletions(-) diff --git a/gateway/lib/doc/gateway.dox b/gateway/lib/doc/gateway.dox index db66f583..7ca3aee1 100644 --- a/gateway/lib/doc/gateway.dox +++ b/gateway/lib/doc/gateway.dox @@ -102,6 +102,18 @@ /// cc_mqttsn_gw_set_advertise_period(gw, 900); /* The advertise period is in seconds */ /// @endcode /// +/// It is also possible to retrieve the current configuration: +/// +/// @b C++ interface: +/// @code +/// std::uint16_t period = gw.getAdvertisePeriod(); +/// @endcode +/// +/// @b C interface: +/// @code +/// unsigned short period = cc_mqttsn_gw_get_advertise_period(gw); +/// @endcode +/// /// @section cc_mqttsn_gw_gateway_page_gw Gateway ID /// The @b ADVERTISE message also contain 1 byte of numeric gateway ID. If the /// actual gateway ID differs from default value @b 0. It must be provided @@ -117,6 +129,18 @@ /// cc_mqttsn_gw_set_id(gw, 5); /// @endcode /// +/// It is also possible to retrieve the current configuration: +/// +/// @b C++ interface: +/// @code +/// std::uint8_t id = gw.getGatewayId(); +/// @endcode +/// +/// @b C interface: +/// @code +/// unsigned char id = cc_mqttsn_gw_get_id(gw); +/// @endcode +/// /// @section cc_mqttsn_gw_gateway_page_start Start Operation /// After all the callbacks have been set and the advertise period was provided, /// the operation of the @b Gateway object needs to be properly started. diff --git a/gateway/lib/doc/session.dox b/gateway/lib/doc/session.dox index dac0b414..1ad8daa4 100644 --- a/gateway/lib/doc/session.dox +++ b/gateway/lib/doc/session.dox @@ -277,6 +277,18 @@ /// cc_mqttsn_gw_session_set_id(handle, 5); /// @endcode /// +/// It is also possible to retrieve the current configuration: +/// +/// @b C++ interface: +/// @code +/// std::uint8_t id = session->getGatewayId(); +/// @endcode +/// +/// @b C interface: +/// @code +/// unsigned char id = cc_mqttsn_gw_session_get_id(gw); +/// @endcode +/// /// @section cc_mqttsn_gw_session_page_retry Retry Attempts /// There are cases when the communication to either client or broker is /// driven by the @b Session object itself. Such communication may involve @@ -298,6 +310,20 @@ /// @endcode /// If not configured, the default values of @b 10 seconds and @b 3 attempts apply. /// +/// It is also possible to retrieve the current configuration: +/// +/// @b C++ interface: +/// @code +/// unsigned retryPeriod = session->getRetryPeriod(); +/// unsigned retryCount = session->getRetryCount(); +/// @endcode +/// +/// @b C interface: +/// @code +/// unsigned retryPeriod = cc_mqttsn_gw_session_get_retry_period(gw); +/// unsigned retryCount = cc_mqttsn_gw_session_get_retry_count(gw); +/// @endcode +/// /// @section cc_mqttsn_gw_session_page_predefined_topics Predefined Topics /// The messages in MQTT-SN protocol are published with numeric topic IDs /// instead of strings (like in original MQTT). The protocol also allows @@ -403,7 +429,7 @@ /// /// @b C interface: /// @code -/// cc_mqttsn_gw_session_broker_connected(handle, true); // Reports broker being connected +/// cc_mqttsn_gw_session_set_broker_connected(handle, true); // Reports broker being connected /// @endcode /// When issuing broker re-connection request (see @ref cc_mqttsn_gw_session_page_broker_reconnect), /// the @b Session object expects this call to happen twice: the first one for disconnection @@ -413,6 +439,18 @@ /// TCP/IP link to the broker and report it to the @b Session object using the /// same function call. /// +/// It is also possible to retrieve the current status: +/// +/// @b C++ interface: +/// @code +/// bool brokerConnected = session->getBrokerConnected(); +/// @endcode +/// +/// @b C interface: +/// @code +/// bool brokerConnected = cc_mqttsn_gw_session_get_broker_connected(handle); +/// @endcode +/// /// @section cc_mqttsn_gw_session_page_client_auth Client Authentication /// The @b MQTT protocol supports client authentication, where the @b CONNECT /// message may contain username and password. The @b MQTT-SN protocol, @@ -508,6 +546,18 @@ /// reported client ID if and only if the client ID @b is empty and /// configured default client ID is @b NOT. /// +/// It is also possible to retrieve the current configuration: +/// +/// @b C++ interface: +/// @code +/// const std::string& defaultClientId = session->getDefaultClientId(); +/// @endcode +/// +/// @b C interface: +/// @code +/// const char* defaultClientId = cc_mqttsn_gw_session_get_default_client_id(handle); +/// @endcode +/// /// @section cc_mqttsn_gw_session_page_publish_only Publish Only Client /// The MQTT-SN protocol allows "publish only" clients, which don't make /// an attempt to connect to the gateway/broker, but allowed to @@ -530,6 +580,18 @@ /// If such configuration is not provided, the default value of @b 60 seconds /// is assumed. /// +/// It is also possible to retrieve the current configuration: +/// +/// @b C++ interface: +/// @code +/// std::uint16_t pubOnlyKeepAlive = session->getPubOnlyKeepAlive(); +/// @endcode +/// +/// @b C interface: +/// @code +/// unsigned pubOnlyKeepAlive = cc_mqttsn_gw_session_get_pub_only_keep_alive(handle); +/// @endcode +/// /// @section cc_mqttsn_gw_session_page_sleep Sleeping Client /// The @b Session object supports client entering the @b SLEEP mode without any /// extra configuration. It will send the @b PINGREQ messages on behalf of @@ -550,6 +612,18 @@ /// cc_mqttsn_gw_session_set_sleeping_client_msg_limit(handle, 1000); /* no more that 1000 messages */ /// @endcode /// +/// It is also possible to retrieve the current configuration: +/// +/// @b C++ interface: +/// @code +/// std::size_t limit = session->getSleepingClientMsgLimit(); +/// @endcode +/// +/// @b C interface: +/// @code +/// unsigned long long limit = cc_mqttsn_gw_session_get_sleeping_client_msg_limit(handle); +/// @endcode +/// /// @section cc_mqttsn_gw_session_page_fwd_enc Forwarder Encapsulation Support /// In case the gateway need to support forwarder encapsulation functionality there /// is a need to set extra two callbacks. diff --git a/gateway/lib/include/cc_mqttsn_gateway/Gateway.h b/gateway/lib/include/cc_mqttsn_gateway/Gateway.h index 89363c71..fa9163b5 100644 --- a/gateway/lib/include/cc_mqttsn_gateway/Gateway.h +++ b/gateway/lib/include/cc_mqttsn_gateway/Gateway.h @@ -75,11 +75,17 @@ class Gateway /// @param[in] value Advertise period in @b seconds. void setAdvertisePeriod(std::uint16_t value); + /// @brief Get current configuration of the @b ADVERTISE message period. + std::uint16_t getAdvertisePeriod() const; + /// @brief Set gateway numeric ID to be advertised. /// @details If not set, default value 0 is assumed. /// @param[in] value Gateway numeric ID. void setGatewayId(std::uint8_t value); + /// @brief Get current gateway numeric ID configuration + std::uint8_t getGatewayId() const; + /// @brief Start the operation of the object /// @details The function will check whether all callbacks as well as /// advertise period have been properly set. It will also immediately diff --git a/gateway/lib/include/cc_mqttsn_gateway/Session.h b/gateway/lib/include/cc_mqttsn_gateway/Session.h index 7d6c0dae..11ef7b18 100644 --- a/gateway/lib/include/cc_mqttsn_gateway/Session.h +++ b/gateway/lib/include/cc_mqttsn_gateway/Session.h @@ -184,6 +184,9 @@ class Session /// @param[in] value Gateway numeric ID. void setGatewayId(std::uint8_t value); + /// @brief Get configured gateway numeric ID to be reported when requested. + std::uint8_t getGatewayId() const; + /// @brief Set retry period to wait between resending unacknowledged message /// to the client and/or broker. /// @details Some messages, may require acknowledgement by @@ -193,6 +196,9 @@ class Session /// @param[in] value Number of @b seconds to wait before making an attempt to resend. void setRetryPeriod(unsigned value); + /// @brief Get the current configuration of the retry period. + unsigned getRetryPeriod() const; + /// @brief Set number of retry attempts to perform before abandoning attempt /// to send unacknowledged message. /// @details Some messages, may require acknowledgement by @@ -202,6 +208,9 @@ class Session /// @param[in] value Number of retry attempts. void setRetryCount(unsigned value); + /// @brief Get the current configuration of the retry count. + unsigned getRetryCount() const; + /// @brief Provide limit to number pending messages being accumulated for /// the sleeping client. /// @details When client is known to be in "ASLEEP" state, the gateway must @@ -212,11 +221,17 @@ class Session /// @param[in] value Max number of pending messages. void setSleepingClientMsgLimit(std::size_t value); + /// @brief Get currenly configured limit to pending messages being accumulated for the sleeping client. + std::size_t getSleepingClientMsgLimit() const; + /// @brief Provide default client ID for clients that report empty one /// in their attempt to connect. /// @param[in] value Default client ID string. void setDefaultClientId(const std::string& value); + /// @brief Get current default client id configuration. + const std::string& getDefaultClientId() const; + /// @brief Provide default "keep alive" period for "publish only" clients, /// that do not make an attempt to connect to the gateway. /// @details MQTT-SN protocol allows "publish only" clients that don't @@ -229,6 +244,9 @@ class Session /// client is going to send. void setPubOnlyKeepAlive(std::uint16_t value); + /// @brief Get current configuration of the default "keep alive" period for "publish only" clients. + std::uint16_t getPubOnlyKeepAlive() const; + /// @brief Start this object's operation. /// @details The function will check whether all necessary callbacks have been /// set. @@ -278,6 +296,9 @@ class Session /// @param[in] connected Connection status - @b true means connected, @b false disconnected. void setBrokerConnected(bool connected); + /// @brief Get currently recorded broker connection status. + bool getBrokerConnected() const; + /// @brief Add predefined topic string and ID information. /// @param[in] topic Topic string /// @param[in] topicId Numeric topic ID. diff --git a/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h b/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h index b1a6ff1d..6e46d7ab 100644 --- a/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h +++ b/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h @@ -64,11 +64,19 @@ void cc_mqttsn_gw_free(CC_MqttsnGatewayHandle gw); /// @param[in] value Advertise period in @b seconds. void cc_mqttsn_gw_set_advertise_period(CC_MqttsnGatewayHandle gw, unsigned short value); +/// @brief Get current configuration of the advertise period +/// @param[in] gw Handle returned by cc_mqttsn_gw_alloc() function. +unsigned short cc_mqttsn_gw_get_advertise_period(CC_MqttsnGatewayHandle gw); + /// @brief Set the numeric gateway ID. /// @param[in] gw Handle returned by cc_mqttsn_gw_alloc() function. /// @param[in] id Numeric gateway ID. void cc_mqttsn_gw_set_id(CC_MqttsnGatewayHandle gw, unsigned char id); +/// @brief Get current configuration of the numeric gateway id +/// @param[in] gw Handle returned by cc_mqttsn_gw_alloc() function. +unsigned char cc_mqttsn_gw_get_id(CC_MqttsnGatewayHandle gw); + /// @brief Set callback that requests to perform time measurement. /// @details The @b Gateway object will invoke the callback to request time /// measurement. When requested time expires, the driving code is responsible @@ -352,6 +360,10 @@ void cc_mqttsn_gw_session_set_fwd_enc_session_deleted_cb( /// @param[in] id Gateway numeric ID. void cc_mqttsn_gw_session_set_id(CC_MqttsnSessionHandle session, unsigned char id); +/// @brief Get current gateway numeric ID configuration. +/// @param[in] session Handle returned by cc_mqttsn_gw_session_alloc() function. +unsigned char cc_mqttsn_gw_session_get_id(CC_MqttsnSessionHandle session); + /// @brief Set retry period to wait between resending unacknowledged message /// to the client and/or broker. /// @details Some messages, may require acknowledgement by @@ -362,6 +374,10 @@ void cc_mqttsn_gw_session_set_id(CC_MqttsnSessionHandle session, unsigned char i /// @param[in] value Number of @b seconds to wait before making an attempt to resend. void cc_mqttsn_gw_session_set_retry_period(CC_MqttsnSessionHandle session, unsigned value); +/// @brief Get the current configuration of the retry period. +/// @param[in] session Handle returned by cc_mqttsn_gw_session_alloc() function. +unsigned cc_mqttsn_gw_session_get_retry_period(CC_MqttsnSessionHandle session); + /// @brief Set number of retry attempts to perform before abandoning attempt /// to send unacknowledged message. /// @details Some messages, may require acknowledgement by @@ -372,6 +388,10 @@ void cc_mqttsn_gw_session_set_retry_period(CC_MqttsnSessionHandle session, unsig /// @param[in] value Number of retry attempts. void cc_mqttsn_gw_session_set_retry_count(CC_MqttsnSessionHandle session, unsigned value); +/// @brief Get the current configuration of the retry count. +/// @param[in] session Handle returned by cc_mqttsn_gw_session_alloc() function. +unsigned cc_mqttsn_gw_session_get_retry_count(CC_MqttsnSessionHandle session); + /// @brief Provide limit to number pending messages being accumulated for /// the sleeping client. /// @details When client is known to be in "ASLEEP" state, the gateway must @@ -383,7 +403,11 @@ void cc_mqttsn_gw_session_set_retry_count(CC_MqttsnSessionHandle session, unsign /// @param[in] value Max number of pending messages. void cc_mqttsn_gw_session_set_sleeping_client_msg_limit( CC_MqttsnSessionHandle session, - unsigned value); + unsigned long long value); + +/// @brief Get currenly configured limit to pending messages being accumulated for the sleeping client. +/// @param[in] session Handle returned by cc_mqttsn_gw_session_alloc() function. +unsigned long long cc_mqttsn_gw_session_get_sleeping_client_msg_limit(CC_MqttsnSessionHandle session); /// @brief Provide default client ID for clients that report empty one /// in their attempt to connect. @@ -391,6 +415,10 @@ void cc_mqttsn_gw_session_set_sleeping_client_msg_limit( /// @param[in] clientId Default client ID string. void cc_mqttsn_gw_session_set_default_client_id(CC_MqttsnSessionHandle session, const char* clientId); +/// @brief Get current default client ID configuration. +/// @param[in] session Handle returned by cc_mqttsn_gw_session_alloc() function. +const char* cc_mqttsn_gw_session_get_default_client_id(CC_MqttsnSessionHandle session); + /// @brief Provide default "keep alive" period for "publish only" clients, /// that do not make an attempt to connect to the gateway. /// @details MQTT-SN protocol allows "publish only" clients that don't @@ -402,8 +430,13 @@ void cc_mqttsn_gw_session_set_default_client_id(CC_MqttsnSessionHandle session, /// @param[in] session Handle returned by cc_mqttsn_gw_session_alloc() function. /// @param[in] value Max number of seconds between messages the "publish only" /// client is going to send. +/// @pre The provided value mustn't exceed 65535. void cc_mqttsn_gw_session_set_pub_only_keep_alive(CC_MqttsnSessionHandle session, unsigned value); +/// @brief Get current configuration of the default "keep alive" period for "publish only" clients. +/// @param[in] session Handle returned by cc_mqttsn_gw_session_alloc() function. +unsigned cc_mqttsn_gw_session_get_pub_only_keep_alive(CC_MqttsnSessionHandle session); + /// @brief Start the @b Session's object's operation. /// @details The function will check whether all necessary callbacks have been /// set. @@ -459,7 +492,11 @@ unsigned cc_mqttsn_gw_session_data_from_broker( /// ignored. /// @param[in] session Handle returned by cc_mqttsn_gw_session_alloc() function. /// @param[in] connected Connection status - @b true means connected, @b false disconnected. -void cc_mqttsn_gw_session_broker_connected(CC_MqttsnSessionHandle session, bool connected); +void cc_mqttsn_gw_session_set_broker_connected(CC_MqttsnSessionHandle session, bool connected); + +/// @brief Get currently recorded broker connection status. +/// @param[in] session Handle returned by cc_mqttsn_gw_session_alloc() function. +bool cc_mqttsn_gw_session_get_broker_connected(CC_MqttsnSessionHandle session); /// @brief Add predefined topic string and ID information. /// @param[in] session Handle returned by cc_mqttsn_gw_session_alloc() function. diff --git a/gateway/lib/src/Gateway.cpp b/gateway/lib/src/Gateway.cpp index cd62b609..4e4651bb 100644 --- a/gateway/lib/src/Gateway.cpp +++ b/gateway/lib/src/Gateway.cpp @@ -34,11 +34,21 @@ void Gateway::setAdvertisePeriod(std::uint16_t value) m_pImpl->setAdvertisePeriod(value); } +std::uint16_t Gateway::getAdvertisePeriod() const +{ + return m_pImpl->getAdvertisePeriod(); +} + void Gateway::setGatewayId(std::uint8_t value) { m_pImpl->setGatewayId(value); } +std::uint8_t Gateway::getGatewayId() const +{ + return m_pImpl->getGatewayId(); +} + bool Gateway::start() { return m_pImpl->start(); diff --git a/gateway/lib/src/GatewayImpl.h b/gateway/lib/src/GatewayImpl.h index d726c218..d994240d 100644 --- a/gateway/lib/src/GatewayImpl.h +++ b/gateway/lib/src/GatewayImpl.h @@ -55,12 +55,22 @@ class GatewayImpl refresh(); } + std::uint16_t getAdvertisePeriod() const + { + return m_advertisePeriod; + } + void setGatewayId(std::uint8_t value) { m_gwId = value; refresh(); } + std::uint8_t getGatewayId() const + { + return m_gwId; + } + bool start(); void stop(); void tick(); diff --git a/gateway/lib/src/Session.cpp b/gateway/lib/src/Session.cpp index 0f286664..1d942f04 100644 --- a/gateway/lib/src/Session.cpp +++ b/gateway/lib/src/Session.cpp @@ -73,31 +73,61 @@ void Session::setGatewayId(std::uint8_t value) m_pImpl->setGatewayId(value); } +std::uint8_t Session::getGatewayId() const +{ + return m_pImpl->getGatewayId(); +} + void Session::setRetryPeriod(unsigned value) { m_pImpl->setRetryPeriod(value); } +unsigned Session::getRetryPeriod() const +{ + return m_pImpl->getRetryPeriod(); +} + void Session::setRetryCount(unsigned value) { m_pImpl->setRetryCount(value); } +unsigned Session::getRetryCount() const +{ + return m_pImpl->getRetryCount(); +} + void Session::setSleepingClientMsgLimit(std::size_t value) { m_pImpl->setSleepingClientMsgLimit(value); } +std::size_t Session::getSleepingClientMsgLimit() const +{ + return m_pImpl->getSleepingClientMsgLimit(); +} + void Session::setDefaultClientId(const std::string& value) { m_pImpl->setDefaultClientId(value); } +const std::string& Session::getDefaultClientId() const +{ + return m_pImpl->getDefaultClientId(); +} + void Session::setPubOnlyKeepAlive(std::uint16_t value) { m_pImpl->setPubOnlyKeepAlive(value); } +std::uint16_t Session::getPubOnlyKeepAlive() const +{ + return m_pImpl->getPubOnlyKeepAlive(); +} + bool Session::start() { return m_pImpl->start(); @@ -133,6 +163,11 @@ void Session::setBrokerConnected(bool connected) m_pImpl->setBrokerConnected(connected); } +bool Session::getBrokerConnected() const +{ + return m_pImpl->getBrokerConnected(); +} + bool Session::addPredefinedTopic(const std::string& topic, std::uint16_t topicId) { return m_pImpl->addPredefinedTopic(topic, topicId); diff --git a/gateway/lib/src/SessionImpl.h b/gateway/lib/src/SessionImpl.h index 122fdb5a..d8698afe 100644 --- a/gateway/lib/src/SessionImpl.h +++ b/gateway/lib/src/SessionImpl.h @@ -108,31 +108,61 @@ class SessionImpl : public MsgHandler m_state.m_gwId = value; } + std::uint8_t getGatewayId() const + { + return m_state.m_gwId; + } + void setRetryPeriod(unsigned value) { m_state.m_retryPeriod = std::min(std::numeric_limits::max() / 1000, value) * 1000; } + unsigned getRetryPeriod() const + { + return m_state.m_retryPeriod; + } + void setRetryCount(unsigned value) { m_state.m_retryCount = value; } + unsigned getRetryCount() const + { + return m_state.m_retryCount; + } + void setSleepingClientMsgLimit(std::size_t value) { m_state.m_sleepPubAccLimit = std::min(m_state.m_brokerPubs.max_size(), value); } + std::size_t getSleepingClientMsgLimit() const + { + return m_state.m_sleepPubAccLimit; + } + void setDefaultClientId(const std::string& value) { m_state.m_defaultClientId = value; } + const std::string& getDefaultClientId() const + { + return m_state.m_defaultClientId; + } + void setPubOnlyKeepAlive(std::uint16_t value) { m_state.m_pubOnlyKeepAlive = value; } + std::uint16_t getPubOnlyKeepAlive() const + { + return m_state.m_pubOnlyKeepAlive; + } + bool start(); void stop() @@ -151,6 +181,12 @@ class SessionImpl : public MsgHandler std::size_t dataFromBroker(const std::uint8_t* buf, std::size_t len); void setBrokerConnected(bool connected); + + bool getBrokerConnected() const + { + return m_state.m_brokerConnected; + } + bool addPredefinedTopic(const std::string& topic, std::uint16_t topicId); bool setTopicIdAllocationRange(std::uint16_t minVal, std::uint16_t maxVal); diff --git a/gateway/lib/src/gateway_all.cpp b/gateway/lib/src/gateway_all.cpp index 47bf13dd..d14d5ef8 100644 --- a/gateway/lib/src/gateway_all.cpp +++ b/gateway/lib/src/gateway_all.cpp @@ -48,6 +48,15 @@ void cc_mqttsn_gw_set_advertise_period( reinterpret_cast(gw.obj)->setAdvertisePeriod(value); } +unsigned short cc_mqttsn_gw_get_advertise_period(CC_MqttsnGatewayHandle gw) +{ + if (gw.obj == nullptr) { + return 0U; + } + + return reinterpret_cast(gw.obj)->getAdvertisePeriod(); +} + void cc_mqttsn_gw_set_id(CC_MqttsnGatewayHandle gw, unsigned char id) { if (gw.obj == nullptr) { @@ -57,6 +66,15 @@ void cc_mqttsn_gw_set_id(CC_MqttsnGatewayHandle gw, unsigned char id) reinterpret_cast(gw.obj)->setGatewayId(id); } +unsigned char cc_mqttsn_gw_get_id(CC_MqttsnGatewayHandle gw) +{ + if (gw.obj == nullptr) { + return 0U; + } + + return reinterpret_cast(gw.obj)->getGatewayId(); +} + void cc_mqttsn_gw_set_tick_req_cb(CC_MqttsnGatewayHandle gw, CC_MqttsnGwTickReqCb cb, void* data) { if ((gw.obj == nullptr) || (cb == nullptr)) { @@ -336,6 +354,15 @@ void cc_mqttsn_gw_session_set_id(CC_MqttsnSessionHandle session, unsigned char i reinterpret_cast(session.obj)->setGatewayId(id); } +unsigned char cc_mqttsn_gw_session_get_id(CC_MqttsnSessionHandle session) +{ + if (session.obj == nullptr) { + return 0; + } + + return reinterpret_cast(session.obj)->getGatewayId(); +} + void cc_mqttsn_gw_session_set_retry_period(CC_MqttsnSessionHandle session, unsigned value) { if (session.obj == nullptr) { @@ -345,6 +372,15 @@ void cc_mqttsn_gw_session_set_retry_period(CC_MqttsnSessionHandle session, unsig reinterpret_cast(session.obj)->setRetryPeriod(value); } +unsigned cc_mqttsn_gw_session_get_retry_period(CC_MqttsnSessionHandle session) +{ + if (session.obj == nullptr) { + return 0U; + } + + return reinterpret_cast(session.obj)->getRetryPeriod(); +} + void cc_mqttsn_gw_session_set_retry_count(CC_MqttsnSessionHandle session, unsigned value) { if (session.obj == nullptr) { @@ -354,6 +390,15 @@ void cc_mqttsn_gw_session_set_retry_count(CC_MqttsnSessionHandle session, unsign reinterpret_cast(session.obj)->setRetryCount(value); } +unsigned cc_mqttsn_gw_session_get_retry_count(CC_MqttsnSessionHandle session) +{ + if (session.obj == nullptr) { + return 0U; + } + + return reinterpret_cast(session.obj)->getRetryCount(); +} + void cc_mqttsn_gw_session_set_sleeping_client_msg_limit( CC_MqttsnSessionHandle session, unsigned value) @@ -362,7 +407,16 @@ void cc_mqttsn_gw_session_set_sleeping_client_msg_limit( return; } - reinterpret_cast(session.obj)->setSleepingClientMsgLimit(value); + reinterpret_cast(session.obj)->setSleepingClientMsgLimit(static_cast(value)); +} + +unsigned long long cc_mqttsn_gw_session_get_sleeping_client_msg_limit(CC_MqttsnSessionHandle session) +{ + if (session.obj == nullptr) { + return 0U; + } + + return static_cast(reinterpret_cast(session.obj)->getSleepingClientMsgLimit()); } void cc_mqttsn_gw_session_set_default_client_id(CC_MqttsnSessionHandle session, const char* clientId) @@ -374,6 +428,15 @@ void cc_mqttsn_gw_session_set_default_client_id(CC_MqttsnSessionHandle session, reinterpret_cast(session.obj)->setDefaultClientId(clientId); } +const char* cc_mqttsn_gw_session_get_default_client_id(CC_MqttsnSessionHandle session) +{ + if (session.obj == nullptr) { + return nullptr; + } + + return reinterpret_cast(session.obj)->getDefaultClientId().c_str(); +} + void cc_mqttsn_gw_session_set_pub_only_keep_alive( CC_MqttsnSessionHandle session, unsigned value) @@ -385,6 +448,15 @@ void cc_mqttsn_gw_session_set_pub_only_keep_alive( reinterpret_cast(session.obj)->setPubOnlyKeepAlive(static_cast(value)); } +unsigned cc_mqttsn_gw_session_get_pub_only_keep_alive(CC_MqttsnSessionHandle session) +{ + if (session.obj == nullptr) { + return 0U; + } + + return reinterpret_cast(session.obj)->getPubOnlyKeepAlive(); +} + bool cc_mqttsn_gw_session_start(CC_MqttsnSessionHandle session) { if (session.obj == nullptr) { @@ -440,7 +512,7 @@ unsigned cc_mqttsn_gw_session_data_from_broker( } -void cc_mqttsn_gw_session_broker_connected(CC_MqttsnSessionHandle session, bool connected) +void cc_mqttsn_gw_session_set_broker_connected(CC_MqttsnSessionHandle session, bool connected) { if (session.obj == nullptr) { return; @@ -449,6 +521,15 @@ void cc_mqttsn_gw_session_broker_connected(CC_MqttsnSessionHandle session, bool reinterpret_cast(session.obj)->setBrokerConnected(connected); } +bool cc_mqttsn_gw_session_get_broker_connected(CC_MqttsnSessionHandle session) +{ + if (session.obj == nullptr) { + return false; + } + + return reinterpret_cast(session.obj)->getBrokerConnected(); +} + bool cc_mqttsn_gw_session_add_predefined_topic( CC_MqttsnSessionHandle session, const char* topic, From f4179c8003fc2297cf5c3b72ba4ec0ac4cb6ca61 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 21 May 2024 09:07:16 +1000 Subject: [PATCH 026/106] Added error report callback to the gateway session. --- gateway/app/gateway/GatewaySession.cpp | 6 +++ gateway/lib/doc/session.dox | 25 ++++++++++ .../lib/include/cc_mqttsn_gateway/Session.h | 8 +++ .../include/cc_mqttsn_gateway/gateway_all.h | 49 +++++++++++++------ gateway/lib/src/Session.cpp | 5 ++ gateway/lib/src/SessionImpl.cpp | 15 +++++- gateway/lib/src/SessionImpl.h | 9 ++++ gateway/lib/src/gateway_all.cpp | 16 ++++++ gateway/lib/src/session_op/Connect.cpp | 1 + gateway/lib/src/session_op/Forward.cpp | 16 ++++-- gateway/lib/src/session_op/PubSend.cpp | 7 ++- gateway/lib/src/session_op/WillUpdate.cpp | 4 ++ 12 files changed, 136 insertions(+), 25 deletions(-) diff --git a/gateway/app/gateway/GatewaySession.cpp b/gateway/app/gateway/GatewaySession.cpp index 27d95820..5f7721d3 100644 --- a/gateway/app/gateway/GatewaySession.cpp +++ b/gateway/app/gateway/GatewaySession.cpp @@ -287,6 +287,12 @@ bool GatewaySession::startSession() }); }); + m_session->setErrorReportCb( + [this](const char* msg) + { + m_logger.error() << msg << std::endl; + }); + if (!m_sessionPtr) { // Forwarder encapsulated session return true; diff --git a/gateway/lib/doc/session.dox b/gateway/lib/doc/session.dox index 1ad8daa4..7af25334 100644 --- a/gateway/lib/doc/session.dox +++ b/gateway/lib/doc/session.dox @@ -680,3 +680,28 @@ /// broker and report the @ref cc_mqttsn_gw_session_page_broker_conn "connectivity" /// later on. The @b important part is that broker connectivity must be reported /// @b after the callback function returns. +/// +/// @section cc_mqttsn_gw_session_page_error_report Errors Report +/// The gateway library can detect and report some unexpected behavior from the +/// application, client, and/or broker. The @b Session object reports such +/// errors via callback: +/// +/// @b C++ interface +/// @code +/// session->setErrorReportCb( +/// [](const char* msg) +/// { +/// std::cerr << "ERROR: " << msg << std::endl; +/// }); +/// @endcode +/// +/// @b C interface +/// @code +/// void my_error_report(void* userData, CC_MqttsnSessionHandle session, const char* msg) +/// { +/// printf("ERROR: %s\n", msg); +/// } +/// +/// cc_mqttsn_gw_session_set_error_report_cb(handle, &my_error_report, someUserData); +/// @endcode +/// diff --git a/gateway/lib/include/cc_mqttsn_gateway/Session.h b/gateway/lib/include/cc_mqttsn_gateway/Session.h index 11ef7b18..eda4b2af 100644 --- a/gateway/lib/include/cc_mqttsn_gateway/Session.h +++ b/gateway/lib/include/cc_mqttsn_gateway/Session.h @@ -88,6 +88,10 @@ class Session /// @return Authentication information using AuthInfoReqCb = std::function; + /// @brief Type of the callback used to report detected errors. + /// @param[in] msg Error message + using ErrorReportCb = std::function; + /// @brief Type of callback used to notify the application about forwarding encapsulation session being created. /// @details The application is responsible to perform the necessary session configuration /// as well as set all the callbacks except the one set by the @ref setSendDataClientReqCb() @@ -168,6 +172,10 @@ class Session /// @param[in] func R-value reference to the callback object void setAuthInfoReqCb(AuthInfoReqCb&& func); + /// @brief Set the callback to be used to report detected errors + /// @param[in] func R-value reference to the callback object + void setErrorReportCb(ErrorReportCb&& func); + /// @brief Set the callback to be invoked when the forwarding encapsulation session /// is detected and to notify application about such session creation. /// @details When not set, the forwarding enapsulation messages will be ignored diff --git a/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h b/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h index 6e46d7ab..1a629103 100644 --- a/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h +++ b/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h @@ -181,6 +181,28 @@ typedef void (*CC_MqttsnSessionBrokerReconnectReqCb)(void* userData, CC_MqttsnSe /// @param[in] clientId Client ID typedef void (*CC_MqttsnSessionClientConnectReportCb)(void* userData, CC_MqttsnSessionHandle session, const char* clientId); +/// @brief Type of callback used to request authentication information of +/// the client that is trying to connect. +/// @param[in] userData User data passed as the last parameter to the setting function. +/// @param[in] session Handle of session performing the request +/// @param[in] clientId Client ID +/// @param[out] username Username string +/// @param[out] password Binary password buffer +/// @param[out] passwordLen Length of the binary password +typedef void (*CC_MqttsnSessionAuthInfoReqCb)( + void* userData, + CC_MqttsnSessionHandle session, + const char* clientId, + const char** username, + const unsigned char** password, + unsigned* passwordLen); + +/// @brief Type of callback used to report error messages detected by the session. +/// @param[in] userData User data passed as the last parameter to the setting function. +/// @param[in] session Handle of session performing the request +/// @param[in] msg Error message +typedef void (*CC_MqttsnSessionErrorReportCb)(void* userData, CC_MqttsnSessionHandle session, const char* msg); + /// @brief Type of callback used to report forwarding encapsulated session creation. /// @details The application is responsible to perform the necessary session configuration /// as well as set all the callbacks except the one set by the @ref cc_mqttsn_gw_session_set_send_data_to_client_cb() @@ -198,22 +220,6 @@ typedef bool (*CC_MqttsnSessionFwdEncSessionCreatedCb)(void* userData, CC_Mqttsn /// @param[in] session Handle of session object about to be deleted typedef void (*CC_MqttsnSessionFwdEncSessionDeletedCb)(void* userData, CC_MqttsnSessionHandle session); -/// @brief Type of callback used to request authentication information of -/// the client that is trying to connect. -/// @param[in] userData User data passed as the last parameter to the setting function. -/// @param[in] session Handle of session performing the request -/// @param[in] clientId Client ID -/// @param[out] username Username string -/// @param[out] password Binary password buffer -/// @param[out] passwordLen Length of the binary password -typedef void (*CC_MqttsnSessionAuthInfoReqCb)( - void* userData, - CC_MqttsnSessionHandle session, - const char* clientId, - const char** username, - const unsigned char** password, - unsigned* passwordLen); - /// @brief Allocate @b Session object. /// @details The returned handle need to be passed as first parameter /// to all relevant functions. Note that the @b Session object is @@ -331,6 +337,17 @@ void cc_mqttsn_gw_session_set_auth_info_req_cb( CC_MqttsnSessionAuthInfoReqCb cb, void* data); +/// @brief Set the callback to be used to report error messages detected by the session. +/// @details This is an optional callback. +/// @param[in] session Handle returned by cc_mqttsn_gw_session_alloc() function. +/// @param[in] cb Pointer to callback function +/// @param[in] data Pointer to any user data, will be passed back as first +/// parameter to the callback. +void cc_mqttsn_gw_session_set_error_report_cb( + CC_MqttsnSessionHandle session, + CC_MqttsnSessionErrorReportCb cb, + void* data); + /// @brief Set the callback to be invoked when the forwarding encapsulation session /// is detected and to notify application about such session creation. /// @details When not set, the forwarding enapsulation messages will be ignored diff --git a/gateway/lib/src/Session.cpp b/gateway/lib/src/Session.cpp index 1d942f04..0e9e9ed3 100644 --- a/gateway/lib/src/Session.cpp +++ b/gateway/lib/src/Session.cpp @@ -58,6 +58,11 @@ void Session::setAuthInfoReqCb(AuthInfoReqCb&& func) m_pImpl->setAuthInfoReqCb(std::move(func)); } +void Session::setErrorReportCb(ErrorReportCb&& func) +{ + m_pImpl->setErrorReportCb(std::move(func)); +} + void Session::setFwdEncSessionCreatedReportCb(FwdEncSessionCreatedReportCb&& func) { m_pImpl->setFwdEncSessionCreatedReportCb(std::move(func)); diff --git a/gateway/lib/src/SessionImpl.cpp b/gateway/lib/src/SessionImpl.cpp index 387e9594..2258c7a6 100644 --- a/gateway/lib/src/SessionImpl.cpp +++ b/gateway/lib/src/SessionImpl.cpp @@ -69,10 +69,12 @@ bool SessionImpl::start() (!m_sendToBrokerCb) || (!m_termReqCb) || (!m_brokerReconnectReqCb)) { + reportError("Not all \"must have\" callbacks have been set"); return false; } if (static_cast(m_fwdEncSessionCreatedReportCb) != static_cast(m_fwdEncSessionDeletedReportCb)) { + reportError("Not all forward encapsulated session management callbacks have been set"); return false; } @@ -127,8 +129,8 @@ std::size_t SessionImpl::dataFromClient(const std::uint8_t* buf, std::size_t len } if (es == comms::ErrorStatus::ProtocolError) { - ++bufTmp; - continue; + reportError("Protocol error from the client side is detected"); + return len; } if (es == comms::ErrorStatus::Success) { @@ -273,6 +275,15 @@ SessionImpl::AuthInfo SessionImpl::authInfoRequest(const std::string& clientId) return m_authInfoReqCb(clientId); } +void SessionImpl::reportError(const char* str) +{ + if (!m_errorReportCb) { + return; + } + + m_errorReportCb(str); +} + void SessionImpl::handle(SearchgwMsg_SN& msg) { GwinfoMsg_SN respMsg; diff --git a/gateway/lib/src/SessionImpl.h b/gateway/lib/src/SessionImpl.h index d8698afe..b3e2465f 100644 --- a/gateway/lib/src/SessionImpl.h +++ b/gateway/lib/src/SessionImpl.h @@ -36,6 +36,7 @@ class SessionImpl : public MsgHandler using BrokerReconnectReqCb = Session::BrokerReconnectReqCb; using ClientConnectedReportCb = Session::ClientConnectedReportCb; using AuthInfoReqCb = Session::AuthInfoReqCb; + using ErrorReportCb = Session::ErrorReportCb; using FwdEncSessionCreatedReportCb = Session::FwdEncSessionCreatedReportCb; using FwdEncSessionDeletedReportCb = Session::FwdEncSessionDeletedReportCb; @@ -91,6 +92,12 @@ class SessionImpl : public MsgHandler m_authInfoReqCb = std::forward(func); } + template + void setErrorReportCb(TFunc&& func) + { + m_errorReportCb = std::forward(func); + } + template void setFwdEncSessionCreatedReportCb(TFunc&& func) { @@ -210,6 +217,7 @@ class SessionImpl : public MsgHandler void brokerReconnectRequest(); void clientConnectedReport(const std::string& clientId); AuthInfo authInfoRequest(const std::string& clientId); + void reportError(const char* str); private: @@ -248,6 +256,7 @@ class SessionImpl : public MsgHandler BrokerReconnectReqCb m_brokerReconnectReqCb; ClientConnectedReportCb m_clientConnectedCb; AuthInfoReqCb m_authInfoReqCb; + ErrorReportCb m_errorReportCb; FwdEncSessionCreatedReportCb m_fwdEncSessionCreatedReportCb; FwdEncSessionDeletedReportCb m_fwdEncSessionDeletedReportCb; diff --git a/gateway/lib/src/gateway_all.cpp b/gateway/lib/src/gateway_all.cpp index d14d5ef8..1271bb27 100644 --- a/gateway/lib/src/gateway_all.cpp +++ b/gateway/lib/src/gateway_all.cpp @@ -299,6 +299,22 @@ void cc_mqttsn_gw_session_set_auth_info_req_cb( }); } +void cc_mqttsn_gw_session_set_error_report_cb( + CC_MqttsnSessionHandle session, + CC_MqttsnSessionErrorReportCb cb, + void* data) +{ + if (session.obj == nullptr) { + return; + } + + reinterpret_cast(session.obj)->setErrorReportCb( + [cb, data, session](const char* msg) + { + cb(data, session, msg); + }); +} + void cc_mqttsn_gw_session_set_fwd_enc_session_created_cb( CC_MqttsnSessionHandle session, CC_MqttsnSessionFwdEncSessionCreatedCb cb, diff --git a/gateway/lib/src/session_op/Connect.cpp b/gateway/lib/src/session_op/Connect.cpp index 68e43dd2..ae67013b 100644 --- a/gateway/lib/src/session_op/Connect.cpp +++ b/gateway/lib/src/session_op/Connect.cpp @@ -55,6 +55,7 @@ void Connect::handle(ConnectMsg_SN& msg) if ((st.m_connStatus != ConnectionStatus::Disconnected) && (reqClientId != st.m_clientId)) { + session().reportError("Different client id reconnection in the same session"); sendDisconnectToClient(); state().m_connStatus = ConnectionStatus::Disconnected; termRequest(); diff --git a/gateway/lib/src/session_op/Forward.cpp b/gateway/lib/src/session_op/Forward.cpp index 279f48cb..1241317e 100644 --- a/gateway/lib/src/session_op/Forward.cpp +++ b/gateway/lib/src/session_op/Forward.cpp @@ -7,10 +7,14 @@ #include "Forward.h" +#include "SessionImpl.h" + +#include "comms/util/ScopeGuard.h" +#include "comms/util/assign.h" + #include #include -#include "comms/util/ScopeGuard.h" namespace cc_mqttsn_gateway { @@ -49,6 +53,7 @@ void Forward::handle(PublishMsg_SN& msg) } while (false); if (st.m_connStatus != ConnectionStatus::Connected) { + session().reportError("PUBLISH from client when not connected"); sendPubackToClient( msg.field_topicId().value(), msg.field_msgId().value(), @@ -66,6 +71,7 @@ void Forward::handle(PublishMsg_SN& msg) auto& topic = st.m_regMgr.mapTopicId(msg.field_topicId().value()); if (topic.empty()) { + session().reportError("Invalid topic ID in PUBLISH"); sendPubackToClient( msg.field_topicId().value(), msg.field_msgId().value(), @@ -87,7 +93,7 @@ void Forward::handle(PublishMsg_SN& msg) fwdMsg.field_topic().value() = topic; fwdMsg.field_packetId().field().value() = msg.field_msgId().value(); auto& data = msg.field_data().value(); - fwdMsg.field_payload().value().assign(data.begin(), data.end()); + comms::util::assign(fwdMsg.field_payload().value(), data.begin(), data.end()); fwdMsg.doRefresh(); sendToBroker(fwdMsg); } @@ -128,6 +134,7 @@ void Forward::handle(SubscribeMsg_SN& msg) }; if (state().m_connStatus != ConnectionStatus::Connected) { + session().reportError("SUBSCRIBE from client when not connected"); sendSubackFunc(ReturnCodeVal::NotSupported); return; } @@ -140,9 +147,10 @@ void Forward::handle(SubscribeMsg_SN& msg) assert(msg.field_topicId().isMissing()); assert(msg.field_flags().field_topicIdType().value() == TopicIdTypeVal::Normal); auto& topicStorage = msg.field_topicName().field().value(); - topic.assign(topicStorage.begin(), topicStorage.end()); + comms::util::assign(topic, topicStorage.begin(), topicStorage.end()); if (topic.empty()) { + session().reportError("Empty topic in the SUBSCRIBE message from the client"); sendSubackFunc(ReturnCodeVal::NotSupported); sendToBroker(PingreqMsg()); return; @@ -174,6 +182,7 @@ void Forward::handle(SubscribeMsg_SN& msg) } if (msg.field_flags().field_topicIdType().value() != TopicIdTypeVal::PredefinedTopicId) { + session().reportError("Unknown topic type in SUBSCRIBE message from the client"); sendSubackFunc(ReturnCodeVal::NotSupported); sendToBroker(PingreqMsg()); return; @@ -189,6 +198,7 @@ void Forward::handle(SubscribeMsg_SN& msg) break; } + session().reportError("Invalid topic topic ID in SUBSCRIBE message from the client"); sendSubackFunc(ReturnCodeVal::InvalidTopicId); sendToBroker(PingreqMsg()); return; diff --git a/gateway/lib/src/session_op/PubSend.cpp b/gateway/lib/src/session_op/PubSend.cpp index e0a8e75f..d73cc5d6 100644 --- a/gateway/lib/src/session_op/PubSend.cpp +++ b/gateway/lib/src/session_op/PubSend.cpp @@ -7,6 +7,8 @@ #include "PubSend.h" +#include "comms/util/assign.h" + #include #include @@ -241,11 +243,8 @@ void PubSend::doSend() } if (!m_currPub->m_msg.empty()) { - auto& dataStorage = msg.field_data().value(); - using DataStorageType = typename std::decay::type; - dataStorage = DataStorageType(&(*m_currPub->m_msg.begin()), m_currPub->m_msg.size()); + comms::util::assign(msg.field_data().value(), m_currPub->m_msg.begin(), m_currPub->m_msg.end()); } - //msg.field_data().value().assign(m_currPub->m_msg.begin(), m_currPub->m_msg.end()); sendToClient(msg); if (m_currPub->m_qos == QoS_AtMostOnceDelivery) { diff --git a/gateway/lib/src/session_op/WillUpdate.cpp b/gateway/lib/src/session_op/WillUpdate.cpp index 5a491452..c4a0b2d1 100644 --- a/gateway/lib/src/session_op/WillUpdate.cpp +++ b/gateway/lib/src/session_op/WillUpdate.cpp @@ -7,6 +7,8 @@ #include "WillUpdate.h" +#include "SessionImpl.h" + #include #include @@ -65,6 +67,7 @@ void WillUpdate::handle([[maybe_unused]] DisconnectMsg_SN& msg) void WillUpdate::handle(WilltopicupdMsg_SN& msg) { if (state().m_connStatus != ConnectionStatus::Connected) { + session().reportError("WILLTOPICUPD message from the client when not connected"); sendTopicResp(ReturnCodeVal::NotSupported); return; } @@ -103,6 +106,7 @@ void WillUpdate::handle(WilltopicupdMsg_SN& msg) void WillUpdate::handle(WillmsgupdMsg_SN& msg) { if (state().m_connStatus != ConnectionStatus::Connected) { + session().reportError("WILLMSGUPD message from the client when not connected"); sendMsgResp(ReturnCodeVal::NotSupported); return; } From 13b92a56f42f781f276c75274684e5259c5f2a2f Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 21 May 2024 09:23:34 +1000 Subject: [PATCH 027/106] Making gateway handlers to be pointers without struct wrap. --- .../include/cc_mqttsn_gateway/gateway_all.h | 26 +- gateway/lib/src/gateway_all.cpp | 322 ++++++++++-------- 2 files changed, 186 insertions(+), 162 deletions(-) diff --git a/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h b/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h index 1a629103..0537d09a 100644 --- a/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h +++ b/gateway/lib/include/cc_mqttsn_gateway/gateway_all.h @@ -20,11 +20,11 @@ extern "C" { /*===================== Gateway Object ======================*/ +/// @brief Declaration of the struct for the @ref CC_MqttsnGatewayHandle definition. +struct CC_MqttsnGateway; + /// @brief Handle for gateway object used in all @b cc_mqttsn_gw_* functions. -typedef struct -{ - void* obj; -} CC_MqttsnGatewayHandle; +typedef struct CC_MqttsnGateway* CC_MqttsnGatewayHandle; /// @brief Type of callback function, to be used to request time measurement for /// the @b Gateway object. @@ -116,11 +116,11 @@ void cc_mqttsn_gw_tick(CC_MqttsnGatewayHandle gw); /*===================== Session Object ======================*/ +/// @brief Declaration of the struct for the @ref CC_MqttsnSessionHandle definition. +struct CC_MqttsnSession; + /// @brief Handle for session object used in all @b cc_mqttsn_gw_session_* functions. -typedef struct -{ - void* obj; -} CC_MqttsnSessionHandle; +typedef struct CC_MqttsnSession* CC_MqttsnSessionHandle; /// @brief Type of callback, used to request new time measurement. /// @details When the requested time is due, the driving code is expected @@ -567,11 +567,11 @@ typedef enum CC_MqttsnBrokerConnectionType_ValuesLimit ///< Limit to available values, must be last } CC_MqttsnBrokerConnectionType; -/// @brief Handle for configuration object used in all @b cc_mqttsn_gw_config_* functions. -typedef struct -{ - void* obj; -} CC_MqttsnConfigHandle; +/// @brief Declaration of the struct for the @ref CC_MqttsnConfigHandle definition. +struct CC_MqttsnConfig; + +/// @brief Handle for session object used in all @b cc_mqttsn_gw_config_* functions. +typedef struct CC_MqttsnConfig* CC_MqttsnConfigHandle; /// @brief Allocate @b Config object. /// @details The returned handle need to be passed as first parameter diff --git a/gateway/lib/src/gateway_all.cpp b/gateway/lib/src/gateway_all.cpp index 1271bb27..054a11ed 100644 --- a/gateway/lib/src/gateway_all.cpp +++ b/gateway/lib/src/gateway_all.cpp @@ -14,12 +14,46 @@ #include "cc_mqttsn_gateway/gateway_allpp.h" +struct CC_MqttsnGateway {}; +struct CC_MqttsnSession {}; +struct CC_MqttsnConfig {}; + namespace { -typedef cc_mqttsn_gateway::Config Config; -typedef cc_mqttsn_gateway::Gateway Gateway; -typedef cc_mqttsn_gateway::Session Session; +using Config = cc_mqttsn_gateway::Config; +using Gateway = cc_mqttsn_gateway::Gateway; +using Session = cc_mqttsn_gateway::Session; + +Gateway* asGateway(CC_MqttsnGatewayHandle handle) +{ + return reinterpret_cast(handle); +} + +CC_MqttsnGatewayHandle fromGateway(Gateway* obj) +{ + return reinterpret_cast(obj); +} + +Session* asSession(CC_MqttsnSessionHandle handle) +{ + return reinterpret_cast(handle); +} + +CC_MqttsnSessionHandle fromSession(Session* obj) +{ + return reinterpret_cast(obj); +} + +Config* asConfig(CC_MqttsnConfigHandle handle) +{ + return reinterpret_cast(handle); +} + +CC_MqttsnConfigHandle fromConfig(Config* obj) +{ + return reinterpret_cast(obj); +} } // namespace @@ -27,61 +61,59 @@ typedef cc_mqttsn_gateway::Session Session; CC_MqttsnGatewayHandle cc_mqttsn_gw_alloc(void) { - CC_MqttsnGatewayHandle gw; - gw.obj = new Gateway(); - return gw; + return fromGateway(new Gateway()); } void cc_mqttsn_gw_free(CC_MqttsnGatewayHandle gw) { - std::unique_ptr(reinterpret_cast(gw.obj)); + std::unique_ptr(asGateway(gw)); } void cc_mqttsn_gw_set_advertise_period( CC_MqttsnGatewayHandle gw, unsigned short value) { - if (gw.obj == nullptr) { + if (gw == nullptr) { return; } - reinterpret_cast(gw.obj)->setAdvertisePeriod(value); + asGateway(gw)->setAdvertisePeriod(value); } unsigned short cc_mqttsn_gw_get_advertise_period(CC_MqttsnGatewayHandle gw) { - if (gw.obj == nullptr) { + if (gw == nullptr) { return 0U; } - return reinterpret_cast(gw.obj)->getAdvertisePeriod(); + return asGateway(gw)->getAdvertisePeriod(); } void cc_mqttsn_gw_set_id(CC_MqttsnGatewayHandle gw, unsigned char id) { - if (gw.obj == nullptr) { + if (gw == nullptr) { return; } - reinterpret_cast(gw.obj)->setGatewayId(id); + asGateway(gw)->setGatewayId(id); } unsigned char cc_mqttsn_gw_get_id(CC_MqttsnGatewayHandle gw) { - if (gw.obj == nullptr) { + if (gw == nullptr) { return 0U; } - return reinterpret_cast(gw.obj)->getGatewayId(); + return asGateway(gw)->getGatewayId(); } void cc_mqttsn_gw_set_tick_req_cb(CC_MqttsnGatewayHandle gw, CC_MqttsnGwTickReqCb cb, void* data) { - if ((gw.obj == nullptr) || (cb == nullptr)) { + if ((gw == nullptr) || (cb == nullptr)) { return; } - reinterpret_cast(gw.obj)->setNextTickProgramReqCb( + asGateway(gw)->setNextTickProgramReqCb( [cb, data](unsigned duration) { cb(data, duration); @@ -93,11 +125,11 @@ void cc_mqttsn_gw_set_advertise_broadcast_req_cb( CC_MqttsnGwBroadcastReqCb cb, void* data) { - if ((gw.obj == nullptr) || (cb == nullptr)) { + if ((gw == nullptr) || (cb == nullptr)) { return; } - reinterpret_cast(gw.obj)->setSendDataReqCb( + asGateway(gw)->setSendDataReqCb( [cb, data](const unsigned char* buf, std::size_t bufLen) { cb(data, buf, static_cast(bufLen)); @@ -106,29 +138,29 @@ void cc_mqttsn_gw_set_advertise_broadcast_req_cb( bool cc_mqttsn_gw_start(CC_MqttsnGatewayHandle gw) { - if (gw.obj == nullptr) { + if (gw == nullptr) { return false; } - return reinterpret_cast(gw.obj)->start(); + return asGateway(gw)->start(); } void cc_mqttsn_gw_stop(CC_MqttsnGatewayHandle gw) { - if (gw.obj == nullptr) { + if (gw == nullptr) { return; } - reinterpret_cast(gw.obj)->stop(); + asGateway(gw)->stop(); } void cc_mqttsn_gw_tick(CC_MqttsnGatewayHandle gw) { - if (gw.obj == nullptr) { + if (gw == nullptr) { return; } - reinterpret_cast(gw.obj)->tick(); + asGateway(gw)->tick(); } /*===================== Session Object ======================*/ @@ -136,14 +168,12 @@ void cc_mqttsn_gw_tick(CC_MqttsnGatewayHandle gw) CC_MqttsnSessionHandle cc_mqttsn_gw_session_alloc(void) { - CC_MqttsnSessionHandle session; - session.obj = new Session; - return session; + return fromSession(new Session); } void cc_mqttsn_gw_session_free(CC_MqttsnSessionHandle session) { - std::unique_ptr(reinterpret_cast(session.obj)); + std::unique_ptr(asSession(session)); } void cc_mqttsn_gw_session_set_tick_req_cb( @@ -151,11 +181,11 @@ void cc_mqttsn_gw_session_set_tick_req_cb( CC_MqttsnSessionTickReqCb cb, void* data) { - if ((session.obj == nullptr) || (cb == nullptr)) { + if ((session == nullptr) || (cb == nullptr)) { return; } - reinterpret_cast(session.obj)->setNextTickProgramReqCb( + asSession(session)->setNextTickProgramReqCb( [cb, data, session](unsigned duration) { cb(data, session, duration); @@ -167,11 +197,11 @@ void cc_mqttsn_gw_session_set_cancel_tick_cb( CC_MqttsnSessionCancelTickReqCb cb, void* data) { - if ((session.obj == nullptr) || (cb == nullptr)) { + if ((session == nullptr) || (cb == nullptr)) { return; } - reinterpret_cast(session.obj)->setCancelTickWaitReqCb( + asSession(session)->setCancelTickWaitReqCb( [cb, data, session]() -> unsigned { return cb(data, session); @@ -183,11 +213,11 @@ void cc_mqttsn_gw_session_set_send_data_to_client_cb( CC_MqttsnSessionClientSendDataReqCb cb, void* data) { - if ((session.obj == nullptr) || (cb == nullptr)) { + if ((session == nullptr) || (cb == nullptr)) { return; } - reinterpret_cast(session.obj)->setSendDataClientReqCb( + asSession(session)->setSendDataClientReqCb( [cb, data, session](const std::uint8_t* buf, std::size_t bufLen, unsigned broadcastRadius) { cb(data, session, buf, static_cast(bufLen), broadcastRadius); @@ -200,11 +230,11 @@ void cc_mqttsn_gw_session_set_send_data_to_broker_cb( CC_MqttsnSessionBrokerSendDataReqCb cb, void* data) { - if ((session.obj == nullptr) || (cb == nullptr)) { + if ((session == nullptr) || (cb == nullptr)) { return; } - reinterpret_cast(session.obj)->setSendDataBrokerReqCb( + asSession(session)->setSendDataBrokerReqCb( [cb, data, session](const std::uint8_t* buf, std::size_t bufLen) { cb(data, session, buf, static_cast(bufLen)); @@ -216,11 +246,11 @@ void cc_mqttsn_gw_session_set_term_req_cb( CC_MqttsnSessionTermReqCb cb, void* data) { - if ((session.obj == nullptr) || (cb == nullptr)) { + if ((session == nullptr) || (cb == nullptr)) { return; } - reinterpret_cast(session.obj)->setTerminationReqCb( + asSession(session)->setTerminationReqCb( [cb, data, session]() { cb(data, session); @@ -232,11 +262,11 @@ void cc_mqttsn_gw_session_set_broker_reconnect_req_cb( CC_MqttsnSessionBrokerReconnectReqCb cb, void* data) { - if ((session.obj == nullptr) || (cb == nullptr)) { + if ((session == nullptr) || (cb == nullptr)) { return; } - reinterpret_cast(session.obj)->setBrokerReconnectReqCb( + asSession(session)->setBrokerReconnectReqCb( [cb, data, session]() { cb(data, session); @@ -248,16 +278,16 @@ void cc_mqttsn_gw_session_set_client_connect_report_cb( CC_MqttsnSessionClientConnectReportCb cb, void* data) { - if (session.obj == nullptr) { + if (session == nullptr) { return; } if (cb == nullptr) { - reinterpret_cast(session.obj)->setClientConnectedReportCb(nullptr); + asSession(session)->setClientConnectedReportCb(nullptr); return; } - reinterpret_cast(session.obj)->setClientConnectedReportCb( + asSession(session)->setClientConnectedReportCb( [cb, data, session](const std::string& clientId) { cb(data, session, clientId.c_str()); @@ -269,16 +299,16 @@ void cc_mqttsn_gw_session_set_auth_info_req_cb( CC_MqttsnSessionAuthInfoReqCb cb, void* data) { - if (session.obj == nullptr) { + if (session == nullptr) { return; } if (cb == nullptr) { - reinterpret_cast(session.obj)->setAuthInfoReqCb(nullptr); + asSession(session)->setAuthInfoReqCb(nullptr); return; } - reinterpret_cast(session.obj)->setAuthInfoReqCb( + asSession(session)->setAuthInfoReqCb( [cb, data, session](const std::string& clientId) -> Session::AuthInfo { const char* username = nullptr; @@ -304,11 +334,11 @@ void cc_mqttsn_gw_session_set_error_report_cb( CC_MqttsnSessionErrorReportCb cb, void* data) { - if (session.obj == nullptr) { + if (session == nullptr) { return; } - reinterpret_cast(session.obj)->setErrorReportCb( + asSession(session)->setErrorReportCb( [cb, data, session](const char* msg) { cb(data, session, msg); @@ -320,21 +350,19 @@ void cc_mqttsn_gw_session_set_fwd_enc_session_created_cb( CC_MqttsnSessionFwdEncSessionCreatedCb cb, void* data) { - if (session.obj == nullptr) { + if (session == nullptr) { return; } if (cb == nullptr) { - reinterpret_cast(session.obj)->setFwdEncSessionCreatedReportCb(nullptr); + asSession(session)->setFwdEncSessionCreatedReportCb(nullptr); return; } - reinterpret_cast(session.obj)->setFwdEncSessionCreatedReportCb( + asSession(session)->setFwdEncSessionCreatedReportCb( [cb, data](cc_mqttsn_gateway::Session* sessionPtr) { - CC_MqttsnSessionHandle encSession; - encSession.obj = sessionPtr; - return cb(data, encSession); + return cb(data, fromSession(sessionPtr)); }); } @@ -343,161 +371,159 @@ void cc_mqttsn_gw_session_set_fwd_enc_session_deleted_cb( CC_MqttsnSessionFwdEncSessionDeletedCb cb, void* data) { - if (session.obj == nullptr) { + if (session == nullptr) { return; } if (cb == nullptr) { - reinterpret_cast(session.obj)->setFwdEncSessionDeletedReportCb(nullptr); + asSession(session)->setFwdEncSessionDeletedReportCb(nullptr); return; } - reinterpret_cast(session.obj)->setFwdEncSessionDeletedReportCb( + asSession(session)->setFwdEncSessionDeletedReportCb( [cb, data](cc_mqttsn_gateway::Session* sessionPtr) { - CC_MqttsnSessionHandle encSession; - encSession.obj = sessionPtr; - cb(data, encSession); + cb(data, fromSession(sessionPtr)); }); } void cc_mqttsn_gw_session_set_id(CC_MqttsnSessionHandle session, unsigned char id) { - if (session.obj == nullptr) { + if (session == nullptr) { return; } - reinterpret_cast(session.obj)->setGatewayId(id); + asSession(session)->setGatewayId(id); } unsigned char cc_mqttsn_gw_session_get_id(CC_MqttsnSessionHandle session) { - if (session.obj == nullptr) { + if (session == nullptr) { return 0; } - return reinterpret_cast(session.obj)->getGatewayId(); + return asSession(session)->getGatewayId(); } void cc_mqttsn_gw_session_set_retry_period(CC_MqttsnSessionHandle session, unsigned value) { - if (session.obj == nullptr) { + if (session == nullptr) { return; } - reinterpret_cast(session.obj)->setRetryPeriod(value); + asSession(session)->setRetryPeriod(value); } unsigned cc_mqttsn_gw_session_get_retry_period(CC_MqttsnSessionHandle session) { - if (session.obj == nullptr) { + if (session == nullptr) { return 0U; } - return reinterpret_cast(session.obj)->getRetryPeriod(); + return asSession(session)->getRetryPeriod(); } void cc_mqttsn_gw_session_set_retry_count(CC_MqttsnSessionHandle session, unsigned value) { - if (session.obj == nullptr) { + if (session == nullptr) { return; } - reinterpret_cast(session.obj)->setRetryCount(value); + asSession(session)->setRetryCount(value); } unsigned cc_mqttsn_gw_session_get_retry_count(CC_MqttsnSessionHandle session) { - if (session.obj == nullptr) { + if (session == nullptr) { return 0U; } - return reinterpret_cast(session.obj)->getRetryCount(); + return asSession(session)->getRetryCount(); } void cc_mqttsn_gw_session_set_sleeping_client_msg_limit( CC_MqttsnSessionHandle session, unsigned value) { - if (session.obj == nullptr) { + if (session == nullptr) { return; } - reinterpret_cast(session.obj)->setSleepingClientMsgLimit(static_cast(value)); + asSession(session)->setSleepingClientMsgLimit(static_cast(value)); } unsigned long long cc_mqttsn_gw_session_get_sleeping_client_msg_limit(CC_MqttsnSessionHandle session) { - if (session.obj == nullptr) { + if (session == nullptr) { return 0U; } - return static_cast(reinterpret_cast(session.obj)->getSleepingClientMsgLimit()); + return static_cast(asSession(session)->getSleepingClientMsgLimit()); } void cc_mqttsn_gw_session_set_default_client_id(CC_MqttsnSessionHandle session, const char* clientId) { - if (session.obj == nullptr) { + if (session == nullptr) { return; } - reinterpret_cast(session.obj)->setDefaultClientId(clientId); + asSession(session)->setDefaultClientId(clientId); } const char* cc_mqttsn_gw_session_get_default_client_id(CC_MqttsnSessionHandle session) { - if (session.obj == nullptr) { + if (session == nullptr) { return nullptr; } - return reinterpret_cast(session.obj)->getDefaultClientId().c_str(); + return asSession(session)->getDefaultClientId().c_str(); } void cc_mqttsn_gw_session_set_pub_only_keep_alive( CC_MqttsnSessionHandle session, unsigned value) { - if (session.obj == nullptr) { + if (session == nullptr) { return; } - reinterpret_cast(session.obj)->setPubOnlyKeepAlive(static_cast(value)); + asSession(session)->setPubOnlyKeepAlive(static_cast(value)); } unsigned cc_mqttsn_gw_session_get_pub_only_keep_alive(CC_MqttsnSessionHandle session) { - if (session.obj == nullptr) { + if (session == nullptr) { return 0U; } - return reinterpret_cast(session.obj)->getPubOnlyKeepAlive(); + return asSession(session)->getPubOnlyKeepAlive(); } bool cc_mqttsn_gw_session_start(CC_MqttsnSessionHandle session) { - if (session.obj == nullptr) { + if (session == nullptr) { return false; } - return reinterpret_cast(session.obj)->start(); + return asSession(session)->start(); } void cc_mqttsn_gw_session_stop(CC_MqttsnSessionHandle session) { - if (session.obj == nullptr) { + if (session == nullptr) { return; } - reinterpret_cast(session.obj)->stop(); + asSession(session)->stop(); } void cc_mqttsn_gw_session_tick(CC_MqttsnSessionHandle session) { - if (session.obj == nullptr) { + if (session == nullptr) { return; } - reinterpret_cast(session.obj)->tick(); + asSession(session)->tick(); } unsigned cc_mqttsn_gw_session_data_from_client( @@ -505,12 +531,12 @@ unsigned cc_mqttsn_gw_session_data_from_client( const unsigned char* buf, unsigned bufLen) { - if (session.obj == nullptr) { + if (session == nullptr) { return 0U; } return static_cast( - reinterpret_cast(session.obj)->dataFromClient(buf, bufLen)); + asSession(session)->dataFromClient(buf, bufLen)); } @@ -519,31 +545,31 @@ unsigned cc_mqttsn_gw_session_data_from_broker( const unsigned char* buf, unsigned bufLen) { - if (session.obj == nullptr) { + if (session == nullptr) { return 0U; } return static_cast( - reinterpret_cast(session.obj)->dataFromBroker(buf, bufLen)); + asSession(session)->dataFromBroker(buf, bufLen)); } void cc_mqttsn_gw_session_set_broker_connected(CC_MqttsnSessionHandle session, bool connected) { - if (session.obj == nullptr) { + if (session == nullptr) { return; } - reinterpret_cast(session.obj)->setBrokerConnected(connected); + asSession(session)->setBrokerConnected(connected); } bool cc_mqttsn_gw_session_get_broker_connected(CC_MqttsnSessionHandle session) { - if (session.obj == nullptr) { + if (session == nullptr) { return false; } - return reinterpret_cast(session.obj)->getBrokerConnected(); + return asSession(session)->getBrokerConnected(); } bool cc_mqttsn_gw_session_add_predefined_topic( @@ -551,11 +577,11 @@ bool cc_mqttsn_gw_session_add_predefined_topic( const char* topic, unsigned short topicId) { - if (session.obj == nullptr) { + if (session == nullptr) { return false; } - return reinterpret_cast(session.obj)->addPredefinedTopic(topic, topicId); + return asSession(session)->addPredefinedTopic(topic, topicId); } bool cc_mqttsn_gw_session_set_topic_id_alloc_range( @@ -563,41 +589,39 @@ bool cc_mqttsn_gw_session_set_topic_id_alloc_range( unsigned short minTopicId, unsigned short maxTopicId) { - if (session.obj == nullptr) { + if (session == nullptr) { return false; } - return reinterpret_cast(session.obj)->setTopicIdAllocationRange(minTopicId, maxTopicId); + return asSession(session)->setTopicIdAllocationRange(minTopicId, maxTopicId); } /*===================== Config Object ======================*/ CC_MqttsnConfigHandle cc_mqttsn_gw_config_alloc(void) { - CC_MqttsnConfigHandle config; - config.obj = new Config; - return config; + return fromConfig(new Config); } void cc_mqttsn_gw_config_free(CC_MqttsnConfigHandle config) { - std::unique_ptr(reinterpret_cast(config.obj)); + std::unique_ptr(asConfig(config)); } void cc_mqttsn_gw_config_parse(CC_MqttsnConfigHandle config, const char* str) { - if (config.obj == nullptr) { + if (config == nullptr) { return; } std::stringstream stream; stream << str; - reinterpret_cast(config.obj)->read(stream); + asConfig(config)->read(stream); } bool cc_mqttsn_gw_config_read(CC_MqttsnConfigHandle config, const char* filename) { - if (config.obj == nullptr) { + if (config == nullptr) { return false; } @@ -606,84 +630,84 @@ bool cc_mqttsn_gw_config_read(CC_MqttsnConfigHandle config, const char* filename return false; } - reinterpret_cast(config.obj)->read(stream); + asConfig(config)->read(stream); return true; } unsigned char cc_mqttsn_gw_config_id(CC_MqttsnConfigHandle config) { - if (config.obj == nullptr) { + if (config == nullptr) { return 0U; } - return reinterpret_cast(config.obj)->gatewayId(); + return reinterpret_cast(config)->gatewayId(); } unsigned short cc_mqttsn_gw_config_advertise_period(CC_MqttsnConfigHandle config) { - if (config.obj == nullptr) { + if (config == nullptr) { return 0U; } - return reinterpret_cast(config.obj)->advertisePeriod(); + return reinterpret_cast(config)->advertisePeriod(); } unsigned cc_mqttsn_gw_config_retry_period(CC_MqttsnConfigHandle config) { - if (config.obj == nullptr) { + if (config == nullptr) { return 0U; } - return reinterpret_cast(config.obj)->retryPeriod(); + return reinterpret_cast(config)->retryPeriod(); } unsigned cc_mqttsn_gw_config_retry_count(CC_MqttsnConfigHandle config) { - if (config.obj == nullptr) { + if (config == nullptr) { return 0U; } - return reinterpret_cast(config.obj)->retryCount(); + return reinterpret_cast(config)->retryCount(); } const char* cc_mqttsn_gw_config_default_client_id(CC_MqttsnConfigHandle config) { - if (config.obj == nullptr) { + if (config == nullptr) { return nullptr; } - return reinterpret_cast(config.obj)->defaultClientId().c_str(); + return reinterpret_cast(config)->defaultClientId().c_str(); } unsigned cc_mqttsn_gw_config_pub_only_keep_alive(CC_MqttsnConfigHandle config) { - if (config.obj == nullptr) { + if (config == nullptr) { return 0U; } - return reinterpret_cast(config.obj)->pubOnlyKeepAlive(); + return reinterpret_cast(config)->pubOnlyKeepAlive(); } unsigned cc_mqttsn_gw_config_sleeping_client_msg_limit(CC_MqttsnConfigHandle config) { - if (config.obj == nullptr) { + if (config == nullptr) { return std::numeric_limits::max(); } return static_cast( std::max( - reinterpret_cast(config.obj)->sleepingClientMsgLimit(), + reinterpret_cast(config)->sleepingClientMsgLimit(), static_cast(std::numeric_limits::max()))); } unsigned cc_mqttsn_gw_config_available_predefined_topics(CC_MqttsnConfigHandle config) { - if (config.obj == nullptr) { + if (config == nullptr) { return 0U; } return static_cast( - reinterpret_cast(config.obj)->predefinedTopics().size()); + reinterpret_cast(config)->predefinedTopics().size()); } unsigned cc_mqttsn_gw_config_get_predefined_topics( @@ -691,11 +715,11 @@ unsigned cc_mqttsn_gw_config_get_predefined_topics( CC_MqttsnPredefinedTopicInfo* buf, unsigned bufLen) { - if (config.obj == nullptr) { + if (config == nullptr) { return 0U; } - auto& predefinedTopics = reinterpret_cast(config.obj)->predefinedTopics(); + auto& predefinedTopics = reinterpret_cast(config)->predefinedTopics(); auto total = std::min(static_cast(predefinedTopics.size()), bufLen); std::transform( @@ -713,12 +737,12 @@ unsigned cc_mqttsn_gw_config_get_predefined_topics( unsigned cc_mqttsn_gw_config_available_auth_infos(CC_MqttsnConfigHandle config) { - if (config.obj == nullptr) { + if (config == nullptr) { return 0U; } return static_cast( - reinterpret_cast(config.obj)->authInfos().size()); + reinterpret_cast(config)->authInfos().size()); } unsigned cc_mqttsn_gw_config_get_auth_infos( @@ -726,11 +750,11 @@ unsigned cc_mqttsn_gw_config_get_auth_infos( CC_MqttsnAuthInfo* buf, unsigned bufLen) { - if (config.obj == nullptr) { + if (config == nullptr) { return 0U; } - auto& authInfos = reinterpret_cast(config.obj)->authInfos(); + auto& authInfos = reinterpret_cast(config)->authInfos(); auto total = std::min(static_cast(authInfos.size()), bufLen); std::transform( @@ -751,11 +775,11 @@ void cc_mqttsn_gw_config_topic_id_alloc_range( unsigned short* min, unsigned short* max) { - if (config.obj == nullptr) { + if (config == nullptr) { return; } - auto range = reinterpret_cast(config.obj)->topicIdAllocRange(); + auto range = reinterpret_cast(config)->topicIdAllocRange(); if (min != nullptr) { *min = range.first; } @@ -767,67 +791,67 @@ void cc_mqttsn_gw_config_topic_id_alloc_range( const char* cc_mqttsn_gw_config_broker_address(CC_MqttsnConfigHandle config) { - if (config.obj == nullptr) { + if (config == nullptr) { return nullptr; } - return reinterpret_cast(config.obj)->brokerTcpHostAddress().c_str(); + return reinterpret_cast(config)->brokerTcpHostAddress().c_str(); } unsigned short cc_mqttsn_gw_config_broker_port(CC_MqttsnConfigHandle config) { - if (config.obj == nullptr) { + if (config == nullptr) { return 0U; } - return reinterpret_cast(config.obj)->brokerTcpHostPort(); + return reinterpret_cast(config)->brokerTcpHostPort(); } const char* cc_mqttsn_gw_config_log_file(CC_MqttsnConfigHandle config) { - if (config.obj == nullptr) { + if (config == nullptr) { return nullptr; } - return reinterpret_cast(config.obj)->logFile().c_str(); + return reinterpret_cast(config)->logFile().c_str(); } CC_MqttsnClientConnectionType cc_mqttsn_gw_config_client_connection_type(CC_MqttsnConfigHandle config) { - if (config.obj == nullptr) { + if (config == nullptr) { return CC_MqttsnClientConnectionType_ValuesLimit; } - return static_cast(reinterpret_cast(config.obj)->clientConnectionType()); + return static_cast(reinterpret_cast(config)->clientConnectionType()); } CC_MqttsnBrokerConnectionType cc_mqttsn_gw_config_broker_connection_type(CC_MqttsnConfigHandle config) { - if (config.obj == nullptr) { + if (config == nullptr) { return CC_MqttsnBrokerConnectionType_ValuesLimit; } - return static_cast(reinterpret_cast(config.obj)->brokerConnectionType()); + return static_cast(reinterpret_cast(config)->brokerConnectionType()); } unsigned cc_mqttsn_gw_config_values_count(CC_MqttsnConfigHandle config, const char* key) { - if (config.obj == nullptr) { + if (config == nullptr) { return 0U; } - auto& map = reinterpret_cast(config.obj)->configMap(); + auto& map = reinterpret_cast(config)->configMap(); auto range = map.equal_range(key); return static_cast(std::distance(range.first, range.second)); } const char* cc_mqttsn_gw_config_get_value(CC_MqttsnConfigHandle config, const char* key, unsigned idx) { - if (config.obj == nullptr) { + if (config == nullptr) { return 0U; } - auto& map = reinterpret_cast(config.obj)->configMap(); + auto& map = reinterpret_cast(config)->configMap(); auto range = map.equal_range(key); unsigned count = 0; From ce8656a43d2c51eda4d67fc0b51379d5297ed3bd Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 22 May 2024 07:53:59 +1000 Subject: [PATCH 028/106] Disable timestamp for gateway doxygen documentation. --- gateway/lib/doc/doxygen.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gateway/lib/doc/doxygen.conf b/gateway/lib/doc/doxygen.conf index 78455d84..f3461724 100644 --- a/gateway/lib/doc/doxygen.conf +++ b/gateway/lib/doc/doxygen.conf @@ -1322,7 +1322,7 @@ HTML_COLORSTYLE_GAMMA = 80 # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_TIMESTAMP = YES +HTML_TIMESTAMP = NO # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that From 9f8228344b0c49f39f7b060ff76e6b44640a60a2 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 23 May 2024 08:17:55 +1000 Subject: [PATCH 029/106] Preliminary infrastructure for the new client library. --- CMakeLists.txt | 27 +- client.old/CMakeLists.txt | 196 +++ {client => client.old}/doc/main.dox | 0 .../script/ProcessTemplate.cmake | 0 {client => client.old}/src/CMakeLists.txt | 0 {client => client.old}/src/app/CMakeLists.txt | 0 .../src/app/pub/CMakeLists.txt | 0 .../src/app/pub/udp/CMakeLists.txt | 0 .../src/app/pub/udp/Pub.cpp | 0 {client => client.old}/src/app/pub/udp/Pub.h | 0 .../src/app/pub/udp/main.cpp | 0 .../src/app/sub/CMakeLists.txt | 0 .../src/app/sub/udp/CMakeLists.txt | 0 .../src/app/sub/udp/Sub.cpp | 0 {client => client.old}/src/app/sub/udp/Sub.h | 0 .../src/app/sub/udp/main.cpp | 0 .../src/basic/BasicClient.h | 0 .../src/basic/CMakeLists.txt | 0 {client => client.old}/src/basic/ClientMgr.h | 0 .../src/basic/ParsedOptions.h | 0 .../src/basic/details/OptionsParser.h | 0 .../src/basic/details/WriteBufStorageType.h | 0 {client => client.old}/src/basic/option.h | 0 {client => client.old}/templ/client.cpp.templ | 0 {client => client.old}/templ/client.h.templ | 0 {client => client.old}/test/CMakeLists.txt | 0 {client => client.old}/test/ClientBasic.th | 0 .../test/CommonTestClient.cpp | 0 .../test/CommonTestClient.h | 0 {client => client.old}/test/DataProcessor.cpp | 0 {client => client.old}/test/DataProcessor.h | 0 .../test/bare_metal_app/CMakeLists.txt | 0 .../test/bare_metal_app/raspberrypi.ld | 0 .../bare_metal_app/startup/CMakeLists.txt | 0 .../test/bare_metal_app/startup/startup.s | 0 .../test_client_build/CMakeLists.txt | 0 .../bare_metal_app/test_client_build/main.c | 0 .../bare_metal_app/test_client_build/stub.cpp | 0 client/CMakeLists.txt | 198 +-- client/lib/CMakeLists.txt | 267 +++++ client/{doc => lib/doxygen}/doxygen.conf | 0 .../include/cc_mqttsn_client/common.h | 30 +- .../lib/script/DefineDefaultConfigVars.cmake | 31 + client/lib/script/WriteConfigHeader.cmake | 64 + client/lib/script/WriteProtocolOptions.cmake | 117 ++ client/lib/src/ClientAllocator.h | 19 + client/lib/src/ClientImpl.cpp | 1059 +++++++++++++++++ client/lib/src/ClientImpl.h | 321 +++++ client/lib/src/ExtConfig.h | 76 ++ client/lib/src/ObjAllocator.h | 54 + client/lib/src/ObjListType.h | 48 + client/lib/src/ProtocolDefs.h | 78 ++ client/lib/src/TimerMgr.cpp | 206 ++++ client/lib/src/TimerMgr.h | 124 ++ client/lib/templ/Config.h.templ | 38 + client/lib/templ/ProtocolOptions.h.templ | 18 + client/lib/templ/client.cpp.templ | 46 + client/lib/templ/client.h.templ | 35 + cmake/CC_Prefetch.cmake | 63 - cmake/Compile.cmake | 39 + cmake/ProcessTemplate.cmake | 14 + doc/custom_client_build.md | 4 +- .../lib/include/cc_mqttsn_gateway/version.h | 4 +- 63 files changed, 2874 insertions(+), 302 deletions(-) create mode 100644 client.old/CMakeLists.txt rename {client => client.old}/doc/main.dox (100%) rename {client => client.old}/script/ProcessTemplate.cmake (100%) rename {client => client.old}/src/CMakeLists.txt (100%) rename {client => client.old}/src/app/CMakeLists.txt (100%) rename {client => client.old}/src/app/pub/CMakeLists.txt (100%) rename {client => client.old}/src/app/pub/udp/CMakeLists.txt (100%) rename {client => client.old}/src/app/pub/udp/Pub.cpp (100%) rename {client => client.old}/src/app/pub/udp/Pub.h (100%) rename {client => client.old}/src/app/pub/udp/main.cpp (100%) rename {client => client.old}/src/app/sub/CMakeLists.txt (100%) rename {client => client.old}/src/app/sub/udp/CMakeLists.txt (100%) rename {client => client.old}/src/app/sub/udp/Sub.cpp (100%) rename {client => client.old}/src/app/sub/udp/Sub.h (100%) rename {client => client.old}/src/app/sub/udp/main.cpp (100%) rename {client => client.old}/src/basic/BasicClient.h (100%) rename {client => client.old}/src/basic/CMakeLists.txt (100%) rename {client => client.old}/src/basic/ClientMgr.h (100%) rename {client => client.old}/src/basic/ParsedOptions.h (100%) rename {client => client.old}/src/basic/details/OptionsParser.h (100%) rename {client => client.old}/src/basic/details/WriteBufStorageType.h (100%) rename {client => client.old}/src/basic/option.h (100%) rename {client => client.old}/templ/client.cpp.templ (100%) rename {client => client.old}/templ/client.h.templ (100%) rename {client => client.old}/test/CMakeLists.txt (100%) rename {client => client.old}/test/ClientBasic.th (100%) rename {client => client.old}/test/CommonTestClient.cpp (100%) rename {client => client.old}/test/CommonTestClient.h (100%) rename {client => client.old}/test/DataProcessor.cpp (100%) rename {client => client.old}/test/DataProcessor.h (100%) rename {client => client.old}/test/bare_metal_app/CMakeLists.txt (100%) rename {client => client.old}/test/bare_metal_app/raspberrypi.ld (100%) rename {client => client.old}/test/bare_metal_app/startup/CMakeLists.txt (100%) rename {client => client.old}/test/bare_metal_app/startup/startup.s (100%) rename {client => client.old}/test/bare_metal_app/test_client_build/CMakeLists.txt (100%) rename {client => client.old}/test/bare_metal_app/test_client_build/main.c (100%) rename {client => client.old}/test/bare_metal_app/test_client_build/stub.cpp (100%) create mode 100644 client/lib/CMakeLists.txt rename client/{doc => lib/doxygen}/doxygen.conf (100%) rename client/{ => lib}/include/cc_mqttsn_client/common.h (91%) create mode 100644 client/lib/script/DefineDefaultConfigVars.cmake create mode 100644 client/lib/script/WriteConfigHeader.cmake create mode 100644 client/lib/script/WriteProtocolOptions.cmake create mode 100644 client/lib/src/ClientAllocator.h create mode 100644 client/lib/src/ClientImpl.cpp create mode 100644 client/lib/src/ClientImpl.h create mode 100644 client/lib/src/ExtConfig.h create mode 100644 client/lib/src/ObjAllocator.h create mode 100644 client/lib/src/ObjListType.h create mode 100644 client/lib/src/ProtocolDefs.h create mode 100644 client/lib/src/TimerMgr.cpp create mode 100644 client/lib/src/TimerMgr.h create mode 100644 client/lib/templ/Config.h.templ create mode 100644 client/lib/templ/ProtocolOptions.h.templ create mode 100644 client/lib/templ/client.cpp.templ create mode 100644 client/lib/templ/client.h.templ delete mode 100644 cmake/CC_Prefetch.cmake create mode 100644 cmake/Compile.cmake create mode 100644 cmake/ProcessTemplate.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index a86758f1..4ddb424e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,6 @@ option (CC_MQTTSN_WITH_SANITIZERS "Build with sanitizers" OFF) # Extra variables # CC_MQTTSN_CUSTOM_CLIENT_CONFIG_FILES - List of custom client configuration files # CC_MQTTSN_DEFAULT_CLIENT_CONFIG_FILE - Custom congiruation of the default client. -# CC_MQTTSN_SANITIZERS - List of sanitizing options ########################################################################## @@ -24,30 +23,8 @@ endif () find_package(LibComms REQUIRED) -set (compile_opts) -if (CC_MQTTSN_USE_CCACHE) - list (APPEND compile_opts USE_CCACHE) -endif () - -if (CC_MQTTSN_WARN_AS_ERR) - list (APPEND compile_opts WARN_AS_ERR) -endif () - -if (CC_MQTTSN_WITH_SANITIZERS) - list (APPEND compile_opts DEFAULT_SANITIZERS) - - if (CMAKE_COMPILER_IS_GNUCC) - # gcc gives false pasitive on vptr sanitizer - list (APPEND compile_opts EXTRA -fno-sanitize=vptr) - endif () -endif () - -if (EXISTS ${LibComms_DIR}/CC_Compile.cmake) - include (${LibComms_DIR}/CC_Compile.cmake) - cc_compile(${compile_opts}) -else () - message (WARNING "Unexpected COMMS cmake scripts installation path, cannot reuse compilation options") -endif () +include (${PROJECT_SOURCE_DIR}/cmake/Compile.cmake) +cc_mqttsn_compile () set (EXTERNALS_DIR "${PROJECT_SOURCE_DIR}/externals") diff --git a/client.old/CMakeLists.txt b/client.old/CMakeLists.txt new file mode 100644 index 00000000..06bf433c --- /dev/null +++ b/client.old/CMakeLists.txt @@ -0,0 +1,196 @@ +option (CC_MQTTSN_BARE_METAL_IMAGE_TEST "Include build of bare metal image" OFF) + +if ((NOT CC_MQTTSN_CLIENT_DEFAULT_LIB) AND + (NOT CC_MQTTSN_BARE_METAL_IMAGE_TEST) AND + ("${CC_MQTTSN_CUSTOM_CLIENT_CONFIG_FILES}" STREQUAL "")) + return () +endif () + +###################################################################### + +set (HEADER_TEMPL ${CMAKE_CURRENT_SOURCE_DIR}/templ/client.h.templ) +set (SRC_TEMPL ${CMAKE_CURRENT_SOURCE_DIR}/templ/client.cpp.templ) +set (TEMPL_PROCESS_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/script/ProcessTemplate.cmake) +set (DEFAULT_CLIENT_NAME "") +set (MQTTSN_CLIENT_LIB_NAME "cc_mqttsn_client") +set (MQTTSN_CLIENT_HEADER_TGT "client.h.tgt") +set (COMMON_INC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) + +###################################################################### + +include(CMakePackageConfigHelpers) +file (READ "${CMAKE_CURRENT_SOURCE_DIR}/include/cc_mqttsn_client/common.h" version_file) +string (REGEX MATCH "CC_MQTTSN_CLIENT_MAJOR_VERSION ([0-9]*)U*" _ ${version_file}) +set (major_ver ${CMAKE_MATCH_1}) +string (REGEX MATCH "CC_MQTTSN_CLIENT_MINOR_VERSION ([0-9]*)U*" _ ${version_file}) +set (minor_ver ${CMAKE_MATCH_1}) +string (REGEX MATCH "CC_MQTTSN_CLIENT_PATCH_VERSION ([0-9]*)U*" _ ${version_file}) +set (patch_ver ${CMAKE_MATCH_1}) +set (CC_MQTTSN_CLIENT_VERSION "${major_ver}.${minor_ver}.${patch_ver}") +write_basic_package_version_file( + ${PROJECT_BINARY_DIR}/ClientLibConfigVersion.cmake + VERSION ${CC_MQTTSN_CLIENT_VERSION} + COMPATIBILITY AnyNewerVersion) + +function (gen_lib_mqttsn_client name client_opts inst extra_flags) + if (NOT ${name} STREQUAL "") + set (name "${name}_") + endif () + + set (lib_name "cc_mqttsn_${name}client") + + set (header_output ${CMAKE_CURRENT_BINARY_DIR}/${name}client.h) + set (src_output ${CMAKE_CURRENT_BINARY_DIR}/${name}client.cpp) + + add_custom_command( + OUTPUT "${header_output}" + COMMAND ${CMAKE_COMMAND} + -DIN_FILE="${HEADER_TEMPL}" + -DOUT_FILE="${header_output}" + -DNAME="${name}" + -DCLIENT_OPTS="${client_opts}" + -P ${TEMPL_PROCESS_SCRIPT} + DEPENDS ${HEADER_TEMPL} ${TEMPL_PROCESS_SCRIPT} + ) + + set_source_files_properties( + ${header_output} + PROPERTIES GENERATED TRUE + ) + + set (header_tgt_name "${name}client.h.tgt") + add_custom_target( + ${header_tgt_name} + DEPENDS "${header_output}" ${HEADER_TEMPL} ${TEMPL_PROCESS_SCRIPT} + ) + + add_custom_command( + OUTPUT "${src_output}" + COMMAND ${CMAKE_COMMAND} + -DIN_FILE="${SRC_TEMPL}" + -DOUT_FILE="${src_output}" + -DNAME="${name}" + -DCLIENT_OPTS="${client_opts}" + -P ${TEMPL_PROCESS_SCRIPT} + DEPENDS ${SRC_TEMPL} ${TEMPL_PROCESS_SCRIPT} + ) + + set_source_files_properties( + ${src_output} + PROPERTIES GENERATED TRUE + ) + + set (src_tgt_name "${name}client.cpp.tgt") + add_custom_target( + ${src_tgt_name} + DEPENDS "${src_output}" ${SRC_TEMPL} ${TEMPL_PROCESS_SCRIPT} + ) + + message (STATUS "Defining library ${lib_name}") + add_library (${lib_name} STATIC ${src_output}) + add_library (cc::${lib_name} ALIAS ${lib_name}) + target_link_libraries(${lib_name} PRIVATE cc::cc_mqttsn cc::comms) + target_include_directories( + ${lib_name} BEFORE + PUBLIC + $ + $ + PRIVATE + "${CMAKE_CURRENT_BINARY_DIR}") + + set_target_properties( + ${lib_name} PROPERTIES + INTERFACE_LINK_LIBRARIES "" + ) + add_dependencies(${lib_name} ${header_tgt_name} ${src_tgt_name}) + + if (CMAKE_COMPILER_IS_GNUCC) + list (APPEND extra_flags + "-ftemplate-backtrace-limit=0" + "-fno-rtti" + "-fno-exceptions" + "-fno-unwind-tables" + "-fno-threadsafe-statics" + ) + + endif () + + if (NOT "${extra_flags}" STREQUAL "") + string(REPLACE ";" " " extra_flags "${extra_flags}") + + set_target_properties( + ${lib_name} PROPERTIES + COMPILE_FLAGS ${extra_flags} + ) + endif () + + if (inst) + install ( + FILES ${header_output} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cc_mqttsn_client + ) + + install ( + TARGETS ${lib_name} + DESTINATION ${CMAKE_INSTALL_LIBDIR} + EXPORT ${lib_name}Config + ) + + install(EXPORT ${lib_name}Config NAMESPACE cc:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/${lib_name}/cmake + ) + + install( + FILES ${PROJECT_BINARY_DIR}/ClientLibConfigVersion.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/${lib_name}/cmake + RENAME ${lib_name}ConfigVersion.cmake + ) + endif () +endfunction() + +###################################################################### + +find_package(LibComms REQUIRED) +find_package(cc_mqttsn REQUIRED) + +install ( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/cc_mqttsn_client + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) + +add_subdirectory (src) +add_subdirectory (test) + +###################################################################### + +find_package (Doxygen) +if (DOXYGEN_FOUND AND CC_MQTTSN_CLIENT_DEFAULT_LIB) + set (doc_output_dir "${CMAKE_INSTALL_FULL_DATAROOTDIR}/doc/cc_mqttsn_client") + make_directory (${doc_output_dir}) + + set (match_str "OUTPUT_DIRECTORY[^\n]*") + set (replacement_str "OUTPUT_DIRECTORY = ${doc_output_dir}") + set (output_file "${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf") + + set (config_file "${CMAKE_CURRENT_SOURCE_DIR}/doc/doxygen.conf") + file (READ ${config_file} config_text) + string (REGEX REPLACE "${match_str}" "${replacement_str}" modified_config_text "${config_text}") + file (WRITE "${output_file}" "${modified_config_text}") + + set (interface_doc_dir "${CMAKE_CURRENT_BINARY_DIR}/interface_doc") + execute_process( + COMMAND ${CMAKE_COMMAND} -E make_directory ${interface_doc_dir}) + execute_process( + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/doc/main.dox + ${CMAKE_CURRENT_SOURCE_DIR}/include/cc_mqttsn_client/common.h + ${interface_doc_dir}/) + + add_custom_target ("doc_mqttsn_client" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_BINARY_DIR}/src/basic/client.h" ${interface_doc_dir}/ + COMMAND ${DOXYGEN_EXECUTABLE} ${output_file} + WORKING_DIRECTORY ${interface_doc_dir}) + + add_dependencies("doc_mqttsn_client" ${MQTTSN_CLIENT_HEADER_TGT}) +endif () + diff --git a/client/doc/main.dox b/client.old/doc/main.dox similarity index 100% rename from client/doc/main.dox rename to client.old/doc/main.dox diff --git a/client/script/ProcessTemplate.cmake b/client.old/script/ProcessTemplate.cmake similarity index 100% rename from client/script/ProcessTemplate.cmake rename to client.old/script/ProcessTemplate.cmake diff --git a/client/src/CMakeLists.txt b/client.old/src/CMakeLists.txt similarity index 100% rename from client/src/CMakeLists.txt rename to client.old/src/CMakeLists.txt diff --git a/client/src/app/CMakeLists.txt b/client.old/src/app/CMakeLists.txt similarity index 100% rename from client/src/app/CMakeLists.txt rename to client.old/src/app/CMakeLists.txt diff --git a/client/src/app/pub/CMakeLists.txt b/client.old/src/app/pub/CMakeLists.txt similarity index 100% rename from client/src/app/pub/CMakeLists.txt rename to client.old/src/app/pub/CMakeLists.txt diff --git a/client/src/app/pub/udp/CMakeLists.txt b/client.old/src/app/pub/udp/CMakeLists.txt similarity index 100% rename from client/src/app/pub/udp/CMakeLists.txt rename to client.old/src/app/pub/udp/CMakeLists.txt diff --git a/client/src/app/pub/udp/Pub.cpp b/client.old/src/app/pub/udp/Pub.cpp similarity index 100% rename from client/src/app/pub/udp/Pub.cpp rename to client.old/src/app/pub/udp/Pub.cpp diff --git a/client/src/app/pub/udp/Pub.h b/client.old/src/app/pub/udp/Pub.h similarity index 100% rename from client/src/app/pub/udp/Pub.h rename to client.old/src/app/pub/udp/Pub.h diff --git a/client/src/app/pub/udp/main.cpp b/client.old/src/app/pub/udp/main.cpp similarity index 100% rename from client/src/app/pub/udp/main.cpp rename to client.old/src/app/pub/udp/main.cpp diff --git a/client/src/app/sub/CMakeLists.txt b/client.old/src/app/sub/CMakeLists.txt similarity index 100% rename from client/src/app/sub/CMakeLists.txt rename to client.old/src/app/sub/CMakeLists.txt diff --git a/client/src/app/sub/udp/CMakeLists.txt b/client.old/src/app/sub/udp/CMakeLists.txt similarity index 100% rename from client/src/app/sub/udp/CMakeLists.txt rename to client.old/src/app/sub/udp/CMakeLists.txt diff --git a/client/src/app/sub/udp/Sub.cpp b/client.old/src/app/sub/udp/Sub.cpp similarity index 100% rename from client/src/app/sub/udp/Sub.cpp rename to client.old/src/app/sub/udp/Sub.cpp diff --git a/client/src/app/sub/udp/Sub.h b/client.old/src/app/sub/udp/Sub.h similarity index 100% rename from client/src/app/sub/udp/Sub.h rename to client.old/src/app/sub/udp/Sub.h diff --git a/client/src/app/sub/udp/main.cpp b/client.old/src/app/sub/udp/main.cpp similarity index 100% rename from client/src/app/sub/udp/main.cpp rename to client.old/src/app/sub/udp/main.cpp diff --git a/client/src/basic/BasicClient.h b/client.old/src/basic/BasicClient.h similarity index 100% rename from client/src/basic/BasicClient.h rename to client.old/src/basic/BasicClient.h diff --git a/client/src/basic/CMakeLists.txt b/client.old/src/basic/CMakeLists.txt similarity index 100% rename from client/src/basic/CMakeLists.txt rename to client.old/src/basic/CMakeLists.txt diff --git a/client/src/basic/ClientMgr.h b/client.old/src/basic/ClientMgr.h similarity index 100% rename from client/src/basic/ClientMgr.h rename to client.old/src/basic/ClientMgr.h diff --git a/client/src/basic/ParsedOptions.h b/client.old/src/basic/ParsedOptions.h similarity index 100% rename from client/src/basic/ParsedOptions.h rename to client.old/src/basic/ParsedOptions.h diff --git a/client/src/basic/details/OptionsParser.h b/client.old/src/basic/details/OptionsParser.h similarity index 100% rename from client/src/basic/details/OptionsParser.h rename to client.old/src/basic/details/OptionsParser.h diff --git a/client/src/basic/details/WriteBufStorageType.h b/client.old/src/basic/details/WriteBufStorageType.h similarity index 100% rename from client/src/basic/details/WriteBufStorageType.h rename to client.old/src/basic/details/WriteBufStorageType.h diff --git a/client/src/basic/option.h b/client.old/src/basic/option.h similarity index 100% rename from client/src/basic/option.h rename to client.old/src/basic/option.h diff --git a/client/templ/client.cpp.templ b/client.old/templ/client.cpp.templ similarity index 100% rename from client/templ/client.cpp.templ rename to client.old/templ/client.cpp.templ diff --git a/client/templ/client.h.templ b/client.old/templ/client.h.templ similarity index 100% rename from client/templ/client.h.templ rename to client.old/templ/client.h.templ diff --git a/client/test/CMakeLists.txt b/client.old/test/CMakeLists.txt similarity index 100% rename from client/test/CMakeLists.txt rename to client.old/test/CMakeLists.txt diff --git a/client/test/ClientBasic.th b/client.old/test/ClientBasic.th similarity index 100% rename from client/test/ClientBasic.th rename to client.old/test/ClientBasic.th diff --git a/client/test/CommonTestClient.cpp b/client.old/test/CommonTestClient.cpp similarity index 100% rename from client/test/CommonTestClient.cpp rename to client.old/test/CommonTestClient.cpp diff --git a/client/test/CommonTestClient.h b/client.old/test/CommonTestClient.h similarity index 100% rename from client/test/CommonTestClient.h rename to client.old/test/CommonTestClient.h diff --git a/client/test/DataProcessor.cpp b/client.old/test/DataProcessor.cpp similarity index 100% rename from client/test/DataProcessor.cpp rename to client.old/test/DataProcessor.cpp diff --git a/client/test/DataProcessor.h b/client.old/test/DataProcessor.h similarity index 100% rename from client/test/DataProcessor.h rename to client.old/test/DataProcessor.h diff --git a/client/test/bare_metal_app/CMakeLists.txt b/client.old/test/bare_metal_app/CMakeLists.txt similarity index 100% rename from client/test/bare_metal_app/CMakeLists.txt rename to client.old/test/bare_metal_app/CMakeLists.txt diff --git a/client/test/bare_metal_app/raspberrypi.ld b/client.old/test/bare_metal_app/raspberrypi.ld similarity index 100% rename from client/test/bare_metal_app/raspberrypi.ld rename to client.old/test/bare_metal_app/raspberrypi.ld diff --git a/client/test/bare_metal_app/startup/CMakeLists.txt b/client.old/test/bare_metal_app/startup/CMakeLists.txt similarity index 100% rename from client/test/bare_metal_app/startup/CMakeLists.txt rename to client.old/test/bare_metal_app/startup/CMakeLists.txt diff --git a/client/test/bare_metal_app/startup/startup.s b/client.old/test/bare_metal_app/startup/startup.s similarity index 100% rename from client/test/bare_metal_app/startup/startup.s rename to client.old/test/bare_metal_app/startup/startup.s diff --git a/client/test/bare_metal_app/test_client_build/CMakeLists.txt b/client.old/test/bare_metal_app/test_client_build/CMakeLists.txt similarity index 100% rename from client/test/bare_metal_app/test_client_build/CMakeLists.txt rename to client.old/test/bare_metal_app/test_client_build/CMakeLists.txt diff --git a/client/test/bare_metal_app/test_client_build/main.c b/client.old/test/bare_metal_app/test_client_build/main.c similarity index 100% rename from client/test/bare_metal_app/test_client_build/main.c rename to client.old/test/bare_metal_app/test_client_build/main.c diff --git a/client/test/bare_metal_app/test_client_build/stub.cpp b/client.old/test/bare_metal_app/test_client_build/stub.cpp similarity index 100% rename from client/test/bare_metal_app/test_client_build/stub.cpp rename to client.old/test/bare_metal_app/test_client_build/stub.cpp diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 06bf433c..c40d13b5 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -1,196 +1,2 @@ -option (CC_MQTTSN_BARE_METAL_IMAGE_TEST "Include build of bare metal image" OFF) - -if ((NOT CC_MQTTSN_CLIENT_DEFAULT_LIB) AND - (NOT CC_MQTTSN_BARE_METAL_IMAGE_TEST) AND - ("${CC_MQTTSN_CUSTOM_CLIENT_CONFIG_FILES}" STREQUAL "")) - return () -endif () - -###################################################################### - -set (HEADER_TEMPL ${CMAKE_CURRENT_SOURCE_DIR}/templ/client.h.templ) -set (SRC_TEMPL ${CMAKE_CURRENT_SOURCE_DIR}/templ/client.cpp.templ) -set (TEMPL_PROCESS_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/script/ProcessTemplate.cmake) -set (DEFAULT_CLIENT_NAME "") -set (MQTTSN_CLIENT_LIB_NAME "cc_mqttsn_client") -set (MQTTSN_CLIENT_HEADER_TGT "client.h.tgt") -set (COMMON_INC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) - -###################################################################### - -include(CMakePackageConfigHelpers) -file (READ "${CMAKE_CURRENT_SOURCE_DIR}/include/cc_mqttsn_client/common.h" version_file) -string (REGEX MATCH "CC_MQTTSN_CLIENT_MAJOR_VERSION ([0-9]*)U*" _ ${version_file}) -set (major_ver ${CMAKE_MATCH_1}) -string (REGEX MATCH "CC_MQTTSN_CLIENT_MINOR_VERSION ([0-9]*)U*" _ ${version_file}) -set (minor_ver ${CMAKE_MATCH_1}) -string (REGEX MATCH "CC_MQTTSN_CLIENT_PATCH_VERSION ([0-9]*)U*" _ ${version_file}) -set (patch_ver ${CMAKE_MATCH_1}) -set (CC_MQTTSN_CLIENT_VERSION "${major_ver}.${minor_ver}.${patch_ver}") -write_basic_package_version_file( - ${PROJECT_BINARY_DIR}/ClientLibConfigVersion.cmake - VERSION ${CC_MQTTSN_CLIENT_VERSION} - COMPATIBILITY AnyNewerVersion) - -function (gen_lib_mqttsn_client name client_opts inst extra_flags) - if (NOT ${name} STREQUAL "") - set (name "${name}_") - endif () - - set (lib_name "cc_mqttsn_${name}client") - - set (header_output ${CMAKE_CURRENT_BINARY_DIR}/${name}client.h) - set (src_output ${CMAKE_CURRENT_BINARY_DIR}/${name}client.cpp) - - add_custom_command( - OUTPUT "${header_output}" - COMMAND ${CMAKE_COMMAND} - -DIN_FILE="${HEADER_TEMPL}" - -DOUT_FILE="${header_output}" - -DNAME="${name}" - -DCLIENT_OPTS="${client_opts}" - -P ${TEMPL_PROCESS_SCRIPT} - DEPENDS ${HEADER_TEMPL} ${TEMPL_PROCESS_SCRIPT} - ) - - set_source_files_properties( - ${header_output} - PROPERTIES GENERATED TRUE - ) - - set (header_tgt_name "${name}client.h.tgt") - add_custom_target( - ${header_tgt_name} - DEPENDS "${header_output}" ${HEADER_TEMPL} ${TEMPL_PROCESS_SCRIPT} - ) - - add_custom_command( - OUTPUT "${src_output}" - COMMAND ${CMAKE_COMMAND} - -DIN_FILE="${SRC_TEMPL}" - -DOUT_FILE="${src_output}" - -DNAME="${name}" - -DCLIENT_OPTS="${client_opts}" - -P ${TEMPL_PROCESS_SCRIPT} - DEPENDS ${SRC_TEMPL} ${TEMPL_PROCESS_SCRIPT} - ) - - set_source_files_properties( - ${src_output} - PROPERTIES GENERATED TRUE - ) - - set (src_tgt_name "${name}client.cpp.tgt") - add_custom_target( - ${src_tgt_name} - DEPENDS "${src_output}" ${SRC_TEMPL} ${TEMPL_PROCESS_SCRIPT} - ) - - message (STATUS "Defining library ${lib_name}") - add_library (${lib_name} STATIC ${src_output}) - add_library (cc::${lib_name} ALIAS ${lib_name}) - target_link_libraries(${lib_name} PRIVATE cc::cc_mqttsn cc::comms) - target_include_directories( - ${lib_name} BEFORE - PUBLIC - $ - $ - PRIVATE - "${CMAKE_CURRENT_BINARY_DIR}") - - set_target_properties( - ${lib_name} PROPERTIES - INTERFACE_LINK_LIBRARIES "" - ) - add_dependencies(${lib_name} ${header_tgt_name} ${src_tgt_name}) - - if (CMAKE_COMPILER_IS_GNUCC) - list (APPEND extra_flags - "-ftemplate-backtrace-limit=0" - "-fno-rtti" - "-fno-exceptions" - "-fno-unwind-tables" - "-fno-threadsafe-statics" - ) - - endif () - - if (NOT "${extra_flags}" STREQUAL "") - string(REPLACE ";" " " extra_flags "${extra_flags}") - - set_target_properties( - ${lib_name} PROPERTIES - COMPILE_FLAGS ${extra_flags} - ) - endif () - - if (inst) - install ( - FILES ${header_output} - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cc_mqttsn_client - ) - - install ( - TARGETS ${lib_name} - DESTINATION ${CMAKE_INSTALL_LIBDIR} - EXPORT ${lib_name}Config - ) - - install(EXPORT ${lib_name}Config NAMESPACE cc:: - DESTINATION ${CMAKE_INSTALL_LIBDIR}/${lib_name}/cmake - ) - - install( - FILES ${PROJECT_BINARY_DIR}/ClientLibConfigVersion.cmake - DESTINATION ${CMAKE_INSTALL_LIBDIR}/${lib_name}/cmake - RENAME ${lib_name}ConfigVersion.cmake - ) - endif () -endfunction() - -###################################################################### - -find_package(LibComms REQUIRED) -find_package(cc_mqttsn REQUIRED) - -install ( - DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/cc_mqttsn_client - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) - -add_subdirectory (src) -add_subdirectory (test) - -###################################################################### - -find_package (Doxygen) -if (DOXYGEN_FOUND AND CC_MQTTSN_CLIENT_DEFAULT_LIB) - set (doc_output_dir "${CMAKE_INSTALL_FULL_DATAROOTDIR}/doc/cc_mqttsn_client") - make_directory (${doc_output_dir}) - - set (match_str "OUTPUT_DIRECTORY[^\n]*") - set (replacement_str "OUTPUT_DIRECTORY = ${doc_output_dir}") - set (output_file "${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf") - - set (config_file "${CMAKE_CURRENT_SOURCE_DIR}/doc/doxygen.conf") - file (READ ${config_file} config_text) - string (REGEX REPLACE "${match_str}" "${replacement_str}" modified_config_text "${config_text}") - file (WRITE "${output_file}" "${modified_config_text}") - - set (interface_doc_dir "${CMAKE_CURRENT_BINARY_DIR}/interface_doc") - execute_process( - COMMAND ${CMAKE_COMMAND} -E make_directory ${interface_doc_dir}) - execute_process( - COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_CURRENT_SOURCE_DIR}/doc/main.dox - ${CMAKE_CURRENT_SOURCE_DIR}/include/cc_mqttsn_client/common.h - ${interface_doc_dir}/) - - add_custom_target ("doc_mqttsn_client" - COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_BINARY_DIR}/src/basic/client.h" ${interface_doc_dir}/ - COMMAND ${DOXYGEN_EXECUTABLE} ${output_file} - WORKING_DIRECTORY ${interface_doc_dir}) - - add_dependencies("doc_mqttsn_client" ${MQTTSN_CLIENT_HEADER_TGT}) -endif () - +add_subdirectory (lib) +#add_subdirectory (app) \ No newline at end of file diff --git a/client/lib/CMakeLists.txt b/client/lib/CMakeLists.txt new file mode 100644 index 00000000..b3a56b84 --- /dev/null +++ b/client/lib/CMakeLists.txt @@ -0,0 +1,267 @@ +if ((NOT CC_MQTTSN_CLIENT_DEFAULT_LIB) OR + (CC_MQTTSN_CUSTOM_CLIENT_CONFIG_FILES STREQUAL "")) + return() +endif () + + +###################################################################### + +set (HEADER_TEMPL ${CMAKE_CURRENT_SOURCE_DIR}/templ/client.h.templ) +set (SRC_TEMPL ${CMAKE_CURRENT_SOURCE_DIR}/templ/client.cpp.templ) +set (CONFIG_TEMPL ${CMAKE_CURRENT_SOURCE_DIR}/templ/Config.h.templ) +set (PROT_OPTS_TEMPL ${CMAKE_CURRENT_SOURCE_DIR}/templ/ProtocolOptions.h.templ) +set (TEMPL_PROCESS_SCRIPT ${PROJECT_SOURCE_DIR}/cmake/ProcessTemplate.cmake) +set (WRITE_CONFIG_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/script/WriteConfigHeader.cmake) +set (WRITE_PROT_OPTS_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/script/WriteProtocolOptions.cmake) +set (DEFAULT_CONFIG_VARS_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/script/DefineDefaultConfigVars.cmake) +set (DEFAULT_CLIENT_DIR_NAME "default") +set (COMMON_INC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) + +###################################################################### + +function (gen_lib_mqttsn_client config_file) + if (NOT "${config_file}" STREQUAL "") + include (${config_file}) + endif() + + set (name "${CC_MQTTSN_CLIENT_CUSTOM_NAME}") + if ("${CC_MQTTSN_CLIENT_CUSTOM_NAME}" STREQUAL "") + set (dir ${DEFAULT_CLIENT_DIR_NAME}) + set (lib_name "cc_mqttsn_client") + else () + set (dir "${CC_MQTTSN_CLIENT_CUSTOM_NAME}") + set (name "${name}_") + set (lib_name "cc_mqttsn_${name}client") + endif () + + execute_process( + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/${dir}) + + set (header_output ${CMAKE_CURRENT_BINARY_DIR}/${dir}/${name}client.h) + set (src_output ${CMAKE_CURRENT_BINARY_DIR}/${dir}/${name}client.cpp) + set (config_output ${CMAKE_CURRENT_BINARY_DIR}/${dir}/Config.h) + set (prot_opts_output ${CMAKE_CURRENT_BINARY_DIR}/${dir}/ProtocolOptions.h) + + # --------------------------------- + + add_custom_command( + OUTPUT "${header_output}" + COMMAND ${CMAKE_COMMAND} + -DIN_FILE="${HEADER_TEMPL}" + -DOUT_FILE="${header_output}" + -DNAME="${name}" + -P ${TEMPL_PROCESS_SCRIPT} + DEPENDS ${HEADER_TEMPL} ${TEMPL_PROCESS_SCRIPT} + ) + + set_source_files_properties( + ${header_output} + PROPERTIES GENERATED TRUE + ) + + set (header_tgt_name "${name}client.h.tgt") + add_custom_target( + ${header_tgt_name} + DEPENDS "${header_output}" ${HEADER_TEMPL} ${TEMPL_PROCESS_SCRIPT} + ) + + # --------------------------------- + + add_custom_command( + OUTPUT "${src_output}" + COMMAND ${CMAKE_COMMAND} + -DIN_FILE="${SRC_TEMPL}" + -DOUT_FILE="${src_output}" + -DNAME="${name}" + -P ${TEMPL_PROCESS_SCRIPT} + DEPENDS ${SRC_TEMPL} ${TEMPL_PROCESS_SCRIPT} + ) + + set_source_files_properties( + ${src_output} + PROPERTIES GENERATED TRUE + ) + + set (src_tgt_name "${name}client.cpp.tgt") + add_custom_target( + ${src_tgt_name} + DEPENDS "${src_output}" ${SRC_TEMPL} ${TEMPL_PROCESS_SCRIPT} + ) + + # --------------------------------- + + add_custom_command( + OUTPUT "${config_output}" + COMMAND ${CMAKE_COMMAND} + -DCMAKE_CONFIG_FILE="${config_file}" + -DCMAKE_DEFAULT_CONFIG_VARS="${DEFAULT_CONFIG_VARS_SCRIPT}" + -DCONFIG_HEADER_TEMPL="${CONFIG_TEMPL}" + -DOUT_FILE="${config_output}" + -P ${WRITE_CONFIG_SCRIPT} + DEPENDS ${config_file} ${CONFIG_TEMPL} ${WRITE_CONFIG_SCRIPT} ${DEFAULT_CONFIG_VARS_SCRIPT} + ) + + set_source_files_properties( + ${config_output} + PROPERTIES GENERATED TRUE + ) + + set (config_tgt_name "${name}Config.h.tgt") + add_custom_target( + ${config_tgt_name} + DEPENDS "${config_output}" ${CONFIG_TEMPL} ${WRITE_CONFIG_SCRIPT} + ) + + # --------------------------------- + + add_custom_command( + OUTPUT "${prot_opts_output}" + COMMAND ${CMAKE_COMMAND} + -DCMAKE_CONFIG_FILE="${config_file}" + -DCMAKE_DEFAULT_CONFIG_VARS="${DEFAULT_CONFIG_VARS_SCRIPT}" + -DPROT_OPTS_HEADER_TEMPL="${PROT_OPTS_TEMPL}" + -DOUT_FILE="${prot_opts_output}" + -P ${WRITE_PROT_OPTS_SCRIPT} + DEPENDS ${config_file} ${PROT_OPTS_TEMPL} ${WRITE_PROT_OPTS_SCRIPT} + ) + + set_source_files_properties( + ${prot_opts_output} + PROPERTIES GENERATED TRUE + ) + + set (prot_opts_tgt_name "${name}ProtocolOptions.h.tgt") + add_custom_target( + ${prot_opts_tgt_name} + DEPENDS "${prot_opts_output}" ${PROT_OPTS_TEMPL} ${WRITE_PROT_OPTS_SCRIPT} + ) + + # --------------------------------- + + message (STATUS "Defining library ${lib_name}") + set (src + src/ClientImpl.cpp + src/TimerMgr.cpp + ) + add_library (${lib_name} ${src} ${src_output}) + add_library (cc::${lib_name} ALIAS ${lib_name}) + target_link_libraries(${lib_name} PRIVATE cc::cc_mqttsn cc::comms) + target_include_directories( + ${lib_name} BEFORE + PUBLIC + $ + $ + $ + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src) + + set_target_properties( + ${lib_name} PROPERTIES + INTERFACE_LINK_LIBRARIES "" + ) + add_dependencies(${lib_name} ${header_tgt_name} ${src_tgt_name} ${config_tgt_name} ${prot_opts_tgt_name}) + + if (CC_MQTTSN_CLIENT_LIB_FORCE_PIC) + set_property(TARGET ${lib_name} PROPERTY POSITION_INDEPENDENT_CODE ON) + endif () + + file (READ "${CMAKE_CURRENT_SOURCE_DIR}/include/cc_mqttsn_client/common.h" version_file) + string (REGEX MATCH "CC_MQTTSN_CLIENT_MAJOR_VERSION ([0-9]*)U*" _ ${version_file}) + set (major_ver ${CMAKE_MATCH_1}) + string (REGEX MATCH "CC_MQTTSN_CLIENT_MINOR_VERSION ([0-9]*)U*" _ ${version_file}) + set (minor_ver ${CMAKE_MATCH_1}) + string (REGEX MATCH "CC_MQTTSN_CLIENT_PATCH_VERSION ([0-9]*)U*" _ ${version_file}) + set (patch_ver ${CMAKE_MATCH_1}) + set (CC_MQTTSN_CLIENT_VERSION "${major_ver}.${minor_ver}.${patch_ver}") + + write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/${lib_name}ConfigVersion.cmake + VERSION ${CC_MQTTSN_CLIENT_VERSION} + COMPATIBILITY AnyNewerVersion) + + install ( + FILES ${CMAKE_CURRENT_BINARY_DIR}/${lib_name}ConfigVersion.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/${lib_name}/cmake + ) + + install ( + FILES ${header_output} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cc_mqttsn_client + ) + + install ( + TARGETS ${lib_name} + DESTINATION ${CMAKE_INSTALL_LIBDIR} + EXPORT ${lib_name}Config + ) + + install(EXPORT ${lib_name}Config NAMESPACE cc:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/${lib_name}/cmake + ) + + if (DOXYGEN_FOUND) + set (doc_output_dir "${CMAKE_INSTALL_FULL_DATAROOTDIR}/doc/${lib_name}") + make_directory (${doc_output_dir}) + + set (match_str "OUTPUT_DIRECTORY[^\n]*") + set (replacement_str "OUTPUT_DIRECTORY = ${doc_output_dir}") + set (output_file "${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf") + + if (NOT "${CC_MQTTSN_CLIENT_CUSTOM_NAME}" STREQUAL "") + set (output_file "${CMAKE_CURRENT_BINARY_DIR}/doxygen_${CC_MQTTSN_CLIENT_CUSTOM_NAME}.conf") + endif () + + set (config_file "${CMAKE_CURRENT_SOURCE_DIR}/doxygen/doxygen.conf") + file (READ ${config_file} config_text) + string (REGEX REPLACE "${match_str}" "${replacement_str}" modified_config_text "${config_text}") + file (WRITE "${output_file}" "${modified_config_text}") + + set (interface_doc_dir "${CMAKE_CURRENT_BINARY_DIR}/interface_doc") + if (NOT "${CC_MQTTSN_CLIENT_CUSTOM_NAME}" STREQUAL "") + set (interface_doc_dir "${interface_doc_dir}_${CC_MQTTSN_CLIENT_CUSTOM_NAME}") + endif () + + execute_process( + COMMAND ${CMAKE_COMMAND} -E make_directory ${interface_doc_dir}) + + set (doc_tgt_name "doc_cc_mqttsn_client") + if (NOT "${CC_MQTTSN_CLIENT_CUSTOM_NAME}" STREQUAL "") + set (doc_tgt_name "doc_cc_mqttsn_${CC_MQTTSN_CLIENT_CUSTOM_NAME}_client") + endif () + + add_custom_target (${doc_tgt_name} + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/doxygen/main.dox + ${CMAKE_CURRENT_SOURCE_DIR}/include/cc_mqttsn_client/common.h + "${header_output}" + ${interface_doc_dir}/ + COMMAND ${DOXYGEN_EXECUTABLE} ${output_file} + WORKING_DIRECTORY ${interface_doc_dir}) + + add_dependencies(${doc_tgt_name} ${header_tgt_name}) + endif () +endfunction() + +###################################################################### + +find_package(cc_mqttsn REQUIRED) +find_package (Doxygen) + +install ( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/cc_mqttsn_client + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) + +include(CMakePackageConfigHelpers) + +if (CC_MQTTSN_CLIENT_DEFAULT_LIB) + gen_lib_mqttsn_client("") +endif () + +if (NOT "${CC_MQTTSN_CUSTOM_CLIENT_CONFIG_FILES}" STREQUAL "") + foreach (custom_config ${CC_MQTTSN_CUSTOM_CLIENT_CONFIG_FILES}) + gen_lib_mqttsn_client("${custom_config}") + endforeach () +endif () + +#add_subdirectory(test) diff --git a/client/doc/doxygen.conf b/client/lib/doxygen/doxygen.conf similarity index 100% rename from client/doc/doxygen.conf rename to client/lib/doxygen/doxygen.conf diff --git a/client/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h similarity index 91% rename from client/include/cc_mqttsn_client/common.h rename to client/lib/include/cc_mqttsn_client/common.h index 0902e821..e1abaf86 100644 --- a/client/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -17,13 +17,13 @@ extern "C" { #endif // #ifdef __cplusplus /// @brief Major verion of the library -#define CC_MQTTSN_CLIENT_MAJOR_VERSION 1U +#define CC_MQTTSN_CLIENT_MAJOR_VERSION 2U /// @brief Minor verion of the library #define CC_MQTTSN_CLIENT_MINOR_VERSION 0U /// @brief Patch level of the library -#define CC_MQTTSN_CLIENT_PATCH_VERSION 9U +#define CC_MQTTSN_CLIENT_PATCH_VERSION 0U /// @brief Macro to create numeric version as single unsigned number #define CC_MQTTSN_CLIENT_MAKE_VERSION(major_, minor_, patch_) \ @@ -77,12 +77,12 @@ typedef enum CC_MqttsnAsyncOpStatus_Aborted, ///< The operation was cancelled using cc_mqttsn_client_cancel() call. } CC_MqttsnAsyncOpStatus; +/// @brief Declaration of struct for the @ref CC_MqttsnClientHandle; +struct CC_MqttsnClient; + /// @brief Handler used to access client specific data structures. /// @details Returned by cc_mqttsn_client_new() function. -typedef struct -{ - void* m_ptr; -} CC_MqttsnClientHandle; +typedef struct CC_MqttsnClient* CC_MqttsnClientHandle; /// @brief Type used to hold Topic ID value. typedef unsigned short CC_MqttsnTopicId; @@ -115,7 +115,7 @@ typedef struct /// cc_mqttsn_client_set_next_tick_program_callback() function. /// @param[in] duration Time duration in @b milliseconds. After the requested /// time expires, the cc_mqttsn_client_tick() function is expected to be invoked. -typedef void (*CC_MqttsnNextTickProgramFn)(void* data, unsigned duration); +typedef void (*CC_MqttsnNextTickProgramCb)(void* data, unsigned duration); /// @brief Callback used to request termination of existing time measurement. /// @details The callback is set using @@ -123,7 +123,7 @@ typedef void (*CC_MqttsnNextTickProgramFn)(void* data, unsigned duration); /// @param[in] data Pointer to user data object, passed as last parameter to /// cc_mqttsn_client_set_cancel_next_tick_wait_callback() function. /// @return Number of elapsed milliseconds since last time measurement request. -typedef unsigned (*CC_MqttsnCancelNextTickWaitFn)(void* data); +typedef unsigned (*CC_MqttsnCancelNextTickWaitCb)(void* data); /// @brief Callback used to request to send data to the gateway. /// @details The callback is set using @@ -138,7 +138,7 @@ typedef unsigned (*CC_MqttsnCancelNextTickWaitFn)(void* data); /// @param[in] bufLen Number of bytes to send /// @param[in] broadcast Indication whether data needs to be broadcasted or /// sent directly to the gateway. -typedef void (*CC_MqttsnSendOutputDataFn)(void* data, const unsigned char* buf, unsigned bufLen, bool broadcast); +typedef void (*CC_MqttsnSendOutputDataCb)(void* data, const unsigned char* buf, unsigned bufLen, bool broadcast); /// @brief Callback used to report gateway status. /// @details The callback is set using @@ -147,25 +147,25 @@ typedef void (*CC_MqttsnSendOutputDataFn)(void* data, const unsigned char* buf, /// cc_mqttsn_client_set_gw_status_report_callback() function. /// @param[in] gwId ID of the gateway. /// @param[in] status Status of the gateway. -typedef void (*CC_MqttsnGwStatusReportFn)(void* data, unsigned char gwId, CC_MqttsnGwStatus status); +typedef void (*CC_MqttsnGwStatusReportCb)(void* data, unsigned char gwId, CC_MqttsnGwStatus status); /// @brief Callback used to report unsolicited disconnection of the gateway. /// @param[in] data Pointer to user data object, passed as the last parameter to /// the request call. -typedef void (*CC_MqttsnGwDisconnectReportFn)(void* data); +typedef void (*CC_MqttsnGwDisconnectReportCb)(void* data); /// @brief Callback used to report completion of the asynchronous operation. /// @param[in] data Pointer to user data object, passed as the last parameter to /// the request call. /// @param[in] status Status of the asynchronous operation. -typedef void (*CC_MqttsnAsyncOpCompleteReportFn)(void* data, CC_MqttsnAsyncOpStatus status); +typedef void (*CC_MqttsnAsyncOpCompleteReportCb)(void* data, CC_MqttsnAsyncOpStatus status); /// @brief Callback used to report completion of the subscribe operation. /// @param[in] data Pointer to user data object, passed as the last parameter to /// the subscribe request. /// @param[in] status Status of the subscribe operation. /// @param[in] qos Maximal level of quality of service, the gateway/broker is going to use to publish incoming messages. -typedef void (*CC_MqttsnSubscribeCompleteReportFn)(void* data, CC_MqttsnAsyncOpStatus status, CC_MqttsnQoS qos); +typedef void (*CC_MqttsnSubscribeCompleteReportCb)(void* data, CC_MqttsnAsyncOpStatus status, CC_MqttsnQoS qos); /// @brief Callback used to report incoming messages. /// @details The callback is set using @@ -175,7 +175,9 @@ typedef void (*CC_MqttsnSubscribeCompleteReportFn)(void* data, CC_MqttsnAsyncOpS /// @param[in] data Pointer to user data object, passed as last parameter to /// cc_mqttsn_client_set_message_report_callback() function. /// @param[in] msgInfo Information about incoming message. -typedef void (*CC_MqttsnMessageReportFn)(void* data, const CC_MqttsnMessageInfo* msgInfo); +typedef void (*CC_MqttsnMessageReportCb)(void* data, const CC_MqttsnMessageInfo* msgInfo); + +typedef void (*CC_MqttsnErrorLogCb)(void* data, const char* msg); #ifdef __cplusplus } diff --git a/client/lib/script/DefineDefaultConfigVars.cmake b/client/lib/script/DefineDefaultConfigVars.cmake new file mode 100644 index 00000000..5a727d88 --- /dev/null +++ b/client/lib/script/DefineDefaultConfigVars.cmake @@ -0,0 +1,31 @@ +cmake_policy(SET CMP0012 NEW) + +macro (set_default_var_value name val) + if (NOT DEFINED ${name}) + set (${name} ${val}) + endif () +endmacro() + +macro (replace_in_text var_name) + string (REPLACE "##${var_name}##" "${${var_name}}" text "${text}") +endmacro() + +set_default_var_value(CC_MQTTSN_CLIENT_CUSTOM_NAME "") +set_default_var_value(CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC TRUE) +set_default_var_value(CC_MQTTSN_CLIENT_ALLOC_LIMIT 0) +#set_default_var_value(CC_MQTTSN_CLIENT_STRING_FIELD_FIXED_LEN 0) +#set_default_var_value(CC_MQTTSN_CLIENT_CLIENT_ID_FIELD_FIXED_LEN 0) +#set_default_var_value(CC_MQTTSN_CLIENT_USERNAME_FIELD_FIXED_LEN 0) +#set_default_var_value(CC_MQTTSN_CLIENT_PASSWORD_FIELD_FIXED_LEN 0) +#set_default_var_value(CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN 0) +#set_default_var_value(CC_MQTTSN_CLIENT_BIN_DATA_FIELD_FIXED_LEN 0) +#set_default_var_value(CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE 0) +set_default_var_value(CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT 0) +set_default_var_value(CC_MQTTSN_CLIENT_SEND_MAX_LIMIT 0) +set_default_var_value(CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT 0) +set_default_var_value(CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT 0) +set_default_var_value(CC_MQTTSN_CLIENT_HAS_ERROR_LOG TRUE) +set_default_var_value(CC_MQTTSN_CLIENT_HAS_TOPIC_FORMAT_VERIFICATION TRUE) +set_default_var_value(CC_MQTTSN_CLIENT_HAS_SUB_TOPIC_VERIFICATION TRUE) +#set_default_var_value(CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT 0) +set_default_var_value(CC_MQTTSN_CLIENT_MAX_QOS 2) \ No newline at end of file diff --git a/client/lib/script/WriteConfigHeader.cmake b/client/lib/script/WriteConfigHeader.cmake new file mode 100644 index 00000000..2b6726dd --- /dev/null +++ b/client/lib/script/WriteConfigHeader.cmake @@ -0,0 +1,64 @@ +# CMAKE_CONFIG_FILE - input cmake file +# CMAKE_DEFAULT_CONFIG_VARS - input cmake file setting default variables +# CONFIG_HEADER_TEMPL - config template file +# OUT_FILE - output header file + +if ((NOT "${CMAKE_CONFIG_FILE}" STREQUAL "") AND (NOT EXISTS "${CMAKE_CONFIG_FILE}")) + message (FATAL_ERROR "Input file \"${CMAKE_CONFIG_FILE}\" doesn't exist!") +endif () + +if (NOT EXISTS "${CMAKE_DEFAULT_CONFIG_VARS}") + message (FATAL_ERROR "Input file \"${CMAKE_DEFAULT_CONFIG_VARS}\" doesn't exist!") +endif () + +if (NOT EXISTS "${CONFIG_HEADER_TEMPL}") + message (FATAL_ERROR "Input file \"${CONFIG_HEADER_TEMPL}\" doesn't exist!") +endif () + +if (NOT "${CMAKE_CONFIG_FILE}" STREQUAL "") + include (${CMAKE_CONFIG_FILE}) +endif () + +file (READ ${CONFIG_HEADER_TEMPL} text) + +include (${CMAKE_DEFAULT_CONFIG_VARS} NO_POLICY_SCOPE) + +######################################### + +macro (adjust_bool_value name adjusted_name) + if (${${name}}) + set (${adjusted_name} "true") + else () + set (${adjusted_name} "false") + endif () +endmacro () + +adjust_bool_value ("CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC" "CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC_CPP") +adjust_bool_value ("CC_MQTTSN_CLIENT_HAS_ERROR_LOG" "CC_MQTTSN_CLIENT_HAS_ERROR_LOG_CPP") +adjust_bool_value ("CC_MQTTSN_CLIENT_HAS_TOPIC_FORMAT_VERIFICATION" "CC_MQTTSN_CLIENT_HAS_TOPIC_FORMAT_VERIFICATION_CPP") +adjust_bool_value ("CC_MQTTSN_CLIENT_HAS_SUB_TOPIC_VERIFICATION" "CC_MQTTSN_CLIENT_HAS_SUB_TOPIC_VERIFICATION_CPP") + +######################################### + +replace_in_text (CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC_CPP) +replace_in_text (CC_MQTTSN_CLIENT_ALLOC_LIMIT) +#replace_in_text (CC_MQTTSN_CLIENT_STRING_FIELD_FIXED_LEN) +#replace_in_text (CC_MQTTSN_CLIENT_BIN_DATA_FIELD_FIXED_LEN) +#replace_in_text (CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE) +replace_in_text (CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT) +replace_in_text (CC_MQTTSN_CLIENT_SEND_MAX_LIMIT) +replace_in_text (CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT) +replace_in_text (CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT) +replace_in_text (CC_MQTTSN_CLIENT_HAS_ERROR_LOG_CPP) +replace_in_text (CC_MQTTSN_CLIENT_HAS_TOPIC_FORMAT_VERIFICATION_CPP) +replace_in_text (CC_MQTTSN_CLIENT_HAS_SUB_TOPIC_VERIFICATION_CPP) +#replace_in_text (CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT) +replace_in_text (CC_MQTTSN_CLIENT_MAX_QOS) + +file (WRITE "${OUT_FILE}.tmp" "${text}") + +execute_process( + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${OUT_FILE}.tmp" "${OUT_FILE}") + +execute_process( + COMMAND ${CMAKE_COMMAND} -E rm -rf "${OUT_FILE}.tmp") \ No newline at end of file diff --git a/client/lib/script/WriteProtocolOptions.cmake b/client/lib/script/WriteProtocolOptions.cmake new file mode 100644 index 00000000..cd8b2acf --- /dev/null +++ b/client/lib/script/WriteProtocolOptions.cmake @@ -0,0 +1,117 @@ +# CMAKE_CONFIG_FILE - input cmake file +# CMAKE_DEFAULT_CONFIG_VARS - input cmake file setting default variables +# PROT_OPTS_HEADER_TEMPL - protocol options template file +# OUT_FILE - output header file + +if ((NOT "${CMAKE_CONFIG_FILE}" STREQUAL "") AND (NOT EXISTS "${CMAKE_CONFIG_FILE}")) + message (FATAL_ERROR "Input file \"${CMAKE_CONFIG_FILE}\" doesn't exist!") +endif () + +if (NOT EXISTS "${CMAKE_DEFAULT_CONFIG_VARS}") + message (FATAL_ERROR "Input file \"${CMAKE_DEFAULT_CONFIG_VARS}\" doesn't exist!") +endif () + +if (NOT EXISTS "${PROT_OPTS_HEADER_TEMPL}") + message (FATAL_ERROR "Input file \"${PROT_OPTS_HEADER_TEMPL}\" doesn't exist!") +endif () + +if (NOT "${CMAKE_CONFIG_FILE}" STREQUAL "") + include (${CMAKE_CONFIG_FILE}) +endif () + +file (READ ${PROT_OPTS_HEADER_TEMPL} text) + +include (${CMAKE_DEFAULT_CONFIG_VARS} NO_POLICY_SCOPE) + +######################################### + +macro (set_default_opt name) + set (${name} "comms::option::EmptyOption") +endmacro() + +######################################### + +#set_default_opt (FIELD_BIN_DATA) +#set_default_opt (FIELD_PROPERTIES_LIST) +#set_default_opt (FIELD_PROTOCOL_NAME) +#set_default_opt (FIELD_STRING) +#set_default_opt (FIELD_TOPIC) + +#set_default_opt (MESSAGE_CONNECT_FIELDS_CLIENT_ID) +#set_default_opt (MESSAGE_CONNECT_FIELDS_USERNAME) +#set_default_opt (MESSAGE_CONNECT_FIELDS_PASSWORD) +#set_default_opt (MESSAGE_CONNECT_FIELDS_WILL_TOPIC) +#set_default_opt (MESSAGE_SUBSCRIBE_FIELDS_LIST) +#set_default_opt (MESSAGE_UNSUBSCRIBE_FIELDS_LIST) + +#set_default_opt (MAX_PACKET_SIZE) +#set_default_opt (MSG_ALLOC_OPT) + +######################################### + +# Update options + +if (NOT ${CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC}) +# set (FIELD_PROTOCOL_NAME "comms::option::app::FixedSizeStorage<4>") +# set (MSG_ALLOC_OPT "comms::option::app::InPlaceAllocation") +endif () + +#if (NOT ${CC_MQTTSN_CLIENT_BIN_DATA_FIELD_FIXED_LEN} EQUAL 0) +# set (FIELD_BIN_DATA "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_BIN_DATA_FIELD_FIXED_LEN}>") +#endif () + +#if (NOT ${CC_MQTTSN_CLIENT_STRING_FIELD_FIXED_LEN} EQUAL 0) +# set (FIELD_STRING "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_STRING_FIELD_FIXED_LEN}>") +#endif () + +#if (NOT ${CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE} EQUAL 0) +# set (MAX_PACKET_SIZE "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE}>") +#endif () + +#if (NOT ${CC_MQTTSN_CLIENT_CLIENT_ID_FIELD_FIXED_LEN} EQUAL 0) +# set (MESSAGE_CONNECT_FIELDS_CLIENT_ID "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_CLIENT_ID_FIELD_FIXED_LEN}>") +#endif () + +#if (NOT ${CC_MQTTSN_CLIENT_USERNAME_FIELD_FIXED_LEN} EQUAL 0) +# set (MESSAGE_CONNECT_FIELDS_USERNAME "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_USERNAME_FIELD_FIXED_LEN}>") +#endif () + +#if (NOT ${CC_MQTTSN_CLIENT_PASSWORD_FIELD_FIXED_LEN} EQUAL 0) +# set (MESSAGE_CONNECT_FIELDS_PASSWORD "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_PASSWORD_FIELD_FIXED_LEN}>") +#endif () + +#if (NOT ${CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN} EQUAL 0) +# set (FIELD_TOPIC "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN}>") +# set (MESSAGE_CONNECT_FIELDS_WILL_TOPIC "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN}>") +#endif () + +#if (NOT ${CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT} EQUAL 0) +# set (MESSAGE_SUBSCRIBE_FIELDS_LIST "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT}>") +#endif () + +#if (NOT ${CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT} EQUAL 0) +# set (MESSAGE_UNSUBSCRIBE_FIELDS_LIST "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT}>") +#endif () + + +######################################### + +#replace_in_text (FIELD_BIN_DATA) +#replace_in_text (FIELD_PROPERTIES_LIST) +#replace_in_text (FIELD_PROTOCOL_NAME) +#replace_in_text (FIELD_STRING) +#replace_in_text (FIELD_TOPIC) + +#replace_in_text (MAX_PACKET_SIZE) +#replace_in_text (MSG_ALLOC_OPT) + +file (WRITE "${OUT_FILE}.tmp" "${text}") + +execute_process( + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${OUT_FILE}.tmp" "${OUT_FILE}") + +execute_process( + COMMAND ${CMAKE_COMMAND} -E rm -rf "${OUT_FILE}.tmp") + + + diff --git a/client/lib/src/ClientAllocator.h b/client/lib/src/ClientAllocator.h new file mode 100644 index 00000000..f95412c0 --- /dev/null +++ b/client/lib/src/ClientAllocator.h @@ -0,0 +1,19 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "ClientImpl.h" +#include "Config.h" +#include "ObjAllocator.h" + +namespace cc_mqttsn_client +{ + +using ClientAllocator = ObjAllocator; + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp new file mode 100644 index 00000000..b4482f20 --- /dev/null +++ b/client/lib/src/ClientImpl.cpp @@ -0,0 +1,1059 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "ClientImpl.h" + +#include "comms/cast.h" +#include "comms/Assert.h" +#include "comms/process.h" +#include "comms/util/ScopeGuard.h" + +#include +#include + +namespace cc_mqttsn_client +{ + +namespace +{ + +// template +// unsigned eraseFromList(const op::Op* op, TList& list) +// { +// auto iter = +// std::find_if( +// list.begin(), list.end(), +// [op](auto& opPtr) +// { +// return op == opPtr.get(); +// }); + +// auto result = static_cast(std::distance(list.begin(), iter)); + +// COMMS_ASSERT(iter != list.end()); +// if (iter != list.end()) { +// list.erase(iter); +// } + +// return result; +// } + +// void updateEc(CC_MqttsnErrorCode* ec, CC_MqttsnErrorCode val) +// { +// if (ec != nullptr) { +// *ec = val; +// } +// } + +} // namespace + +ClientImpl::ClientImpl() = default; + +ClientImpl::~ClientImpl() +{ + COMMS_ASSERT(m_apiEnterCount == 0U); + // terminateOps(CC_MqttsnAsyncOpStatus_Aborted, TerminateMode_AbortSendRecvOps); +} + + +void ClientImpl::tick(unsigned ms) +{ + COMMS_ASSERT(m_apiEnterCount == 0U); + ++m_apiEnterCount; + m_timerMgr.tick(ms); + doApiExit(); +} + +// unsigned ClientImpl::processData(const std::uint8_t* iter, unsigned len) +// { +// auto guard = apiEnter(); +// COMMS_ASSERT(!m_clientState.m_networkDisconnected); + +// if (m_clientState.m_networkDisconnected) { +// errorLog("Incoming data when network is disconnected"); +// return 0U; +// } + +// auto disconnectOnExitGuard = +// comms::util::makeScopeGuard( +// [this]() +// { +// brokerDisconnected(CC_MqttsnBrokerDisconnectReason_ProtocolError, CC_MqttsnAsyncOpStatus_ProtocolError); +// }); + +// unsigned consumed = 0; +// while (consumed < len) { +// auto remLen = len - consumed; +// auto* iterTmp = iter; + +// using IdAndFlagsField = ProtFrame::Layer_idAndFlags::Field; +// static_assert(IdAndFlagsField::minLength() == IdAndFlagsField::maxLength()); + +// if (remLen <= IdAndFlagsField::minLength()) { +// // Size info is not available +// break; +// } + +// using SizeField = ProtFrame::Layer_size::Field; +// SizeField sizeField; +// std::advance(iterTmp, IdAndFlagsField::minLength()); +// auto es = sizeField.read(iterTmp, remLen - IdAndFlagsField::minLength()); +// if (es == comms::ErrorStatus::NotEnoughData) { +// break; +// } + +// if (es != comms::ErrorStatus::Success) { +// return len; // Disconnect +// } + +// iterTmp = iter; +// ProtFrame::MsgPtr msg; +// es = m_frame.read(msg, iterTmp, remLen); +// if (es == comms::ErrorStatus::NotEnoughData) { +// break; +// } + +// if (es != comms::ErrorStatus::Success) { +// errorLog("Unexpected error in framing / payload parsing"); +// return len; +// } + +// COMMS_ASSERT(msg); +// msg->dispatch(*this); +// consumed += static_cast(std::distance(iter, iterTmp)); +// iter = iterTmp; +// } + +// disconnectOnExitGuard.release(); +// return consumed; +// } + +// void ClientImpl::notifyNetworkDisconnected() +// { +// auto guard = apiEnter(); +// m_clientState.m_networkDisconnected = true; +// if (m_sessionState.m_disconnecting) { +// return; // No need to go through broker disconnection +// } + +// brokerDisconnected(); +// } + +// bool ClientImpl::isNetworkDisconnected() const +// { +// return m_clientState.m_networkDisconnected; +// } + + +// op::ConnectOp* ClientImpl::connectPrepare(CC_MqttsnErrorCode* ec) +// { +// op::ConnectOp* connectOp = nullptr; +// do { +// m_clientState.m_networkDisconnected = false; + +// if (!m_clientState.m_initialized) { +// if (m_apiEnterCount > 0U) { +// errorLog("Cannot prepare connect from within callback"); +// updateEc(ec, CC_MqttsnErrorCode_RetryLater); +// break; +// } + +// auto initEc = initInternal(); +// if (initEc != CC_MqttsnErrorCode_Success) { +// updateEc(ec, initEc); +// break; +// } +// } + +// if (!m_connectOps.empty()) { +// // Already allocated +// errorLog("Another connect operation is in progress."); +// updateEc(ec, CC_MqttsnErrorCode_Busy); +// break; +// } + +// if (m_sessionState.m_disconnecting) { +// errorLog("Session disconnection is in progress, cannot initiate connection."); +// updateEc(ec, CC_MqttsnErrorCode_Disconnecting); +// break; +// } + +// if (m_sessionState.m_connected) { +// errorLog("Client is already connected."); +// updateEc(ec, CC_MqttsnErrorCode_AlreadyConnected); +// break; +// } + +// if (m_ops.max_size() <= m_ops.size()) { +// errorLog("Cannot start connect operation, retry in next event loop iteration."); +// updateEc(ec, CC_MqttsnErrorCode_RetryLater); +// break; +// } + +// if (m_preparationLocked) { +// errorLog("Another operation is being prepared, cannot prepare \"connect\" without \"send\" or \"cancel\" of the previous."); +// updateEc(ec, CC_MqttsnErrorCode_PreparationLocked); +// break; +// } + +// auto ptr = m_connectOpAlloc.alloc(*this); +// if (!ptr) { +// errorLog("Cannot allocate new connect operation."); +// updateEc(ec, CC_MqttsnErrorCode_OutOfMemory); +// break; +// } + +// m_preparationLocked = true; +// m_ops.push_back(ptr.get()); +// m_connectOps.push_back(std::move(ptr)); +// connectOp = m_connectOps.back().get(); +// updateEc(ec, CC_MqttsnErrorCode_Success); +// } while (false); + +// return connectOp; +// } + +// op::DisconnectOp* ClientImpl::disconnectPrepare(CC_MqttsnErrorCode* ec) +// { +// op::DisconnectOp* disconnectOp = nullptr; +// do { +// if (!m_sessionState.m_connected) { +// errorLog("Client must be connected to allow disconnect."); +// updateEc(ec, CC_MqttsnErrorCode_NotConnected); +// break; +// } + +// if (!m_disconnectOps.empty()) { +// errorLog("Another disconnect operation is in progress."); +// updateEc(ec, CC_MqttsnErrorCode_Busy); +// break; +// } + +// if (m_sessionState.m_disconnecting) { +// errorLog("Session disconnection is in progress, cannot initiate disconnection."); +// updateEc(ec, CC_MqttsnErrorCode_Disconnecting); +// break; +// } + +// if (m_clientState.m_networkDisconnected) { +// errorLog("Network is disconnected."); +// updateEc(ec, CC_MqttsnErrorCode_NetworkDisconnected); +// break; +// } + +// if (m_ops.max_size() <= m_ops.size()) { +// errorLog("Cannot start disconnect operation, retry in next event loop iteration."); +// updateEc(ec, CC_MqttsnErrorCode_RetryLater); +// break; +// } + +// if (m_preparationLocked) { +// errorLog("Another operation is being prepared, cannot prepare \"disconnect\" without \"send\" or \"cancel\" of the previous."); +// updateEc(ec, CC_MqttsnErrorCode_PreparationLocked); +// break; +// } + +// auto ptr = m_disconnectOpsAlloc.alloc(*this); +// if (!ptr) { +// errorLog("Cannot allocate new disconnect operation."); +// updateEc(ec, CC_MqttsnErrorCode_OutOfMemory); +// break; +// } + +// m_preparationLocked = true; +// m_ops.push_back(ptr.get()); +// m_disconnectOps.push_back(std::move(ptr)); +// disconnectOp = m_disconnectOps.back().get(); +// updateEc(ec, CC_MqttsnErrorCode_Success); +// } while (false); + +// return disconnectOp; +// } + +// op::SubscribeOp* ClientImpl::subscribePrepare(CC_MqttsnErrorCode* ec) +// { +// op::SubscribeOp* subOp = nullptr; +// do { +// if (!m_sessionState.m_connected) { +// errorLog("Client must be connected to allow subscription."); +// updateEc(ec, CC_MqttsnErrorCode_NotConnected); +// break; +// } + +// if (m_sessionState.m_disconnecting) { +// errorLog("Session disconnection is in progress, cannot initiate subscription."); +// updateEc(ec, CC_MqttsnErrorCode_Disconnecting); +// break; +// } + +// if (m_clientState.m_networkDisconnected) { +// errorLog("Network is disconnected."); +// updateEc(ec, CC_MqttsnErrorCode_NetworkDisconnected); +// break; +// } + +// if (m_ops.max_size() <= m_ops.size()) { +// errorLog("Cannot start subscribe operation, retry in next event loop iteration."); +// updateEc(ec, CC_MqttsnErrorCode_RetryLater); +// break; +// } + +// if (m_preparationLocked) { +// errorLog("Another operation is being prepared, cannot prepare \"subscribe\" without \"send\" or \"cancel\" of the previous."); +// updateEc(ec, CC_MqttsnErrorCode_PreparationLocked); +// break; +// } + +// auto ptr = m_subscribeOpsAlloc.alloc(*this); +// if (!ptr) { +// errorLog("Cannot allocate new subscribe operation."); +// updateEc(ec, CC_MqttsnErrorCode_OutOfMemory); +// break; +// } + +// m_preparationLocked = true; +// m_ops.push_back(ptr.get()); +// m_subscribeOps.push_back(std::move(ptr)); +// subOp = m_subscribeOps.back().get(); +// updateEc(ec, CC_MqttsnErrorCode_Success); +// } while (false); + +// return subOp; +// } + +// op::UnsubscribeOp* ClientImpl::unsubscribePrepare(CC_MqttsnErrorCode* ec) +// { +// op::UnsubscribeOp* unsubOp = nullptr; +// do { +// if (!m_sessionState.m_connected) { +// errorLog("Client must be connected to allow unsubscription."); +// updateEc(ec, CC_MqttsnErrorCode_NotConnected); +// break; +// } + +// if (m_sessionState.m_disconnecting) { +// errorLog("Session disconnection is in progress, cannot initiate unsubscription."); +// updateEc(ec, CC_MqttsnErrorCode_Disconnecting); +// break; +// } + +// if (m_clientState.m_networkDisconnected) { +// errorLog("Network is disconnected."); +// updateEc(ec, CC_MqttsnErrorCode_NetworkDisconnected); +// break; +// } + +// if (m_ops.max_size() <= m_ops.size()) { +// errorLog("Cannot start subscribe operation, retry in next event loop iteration."); +// updateEc(ec, CC_MqttsnErrorCode_RetryLater); +// break; +// } + +// if (m_preparationLocked) { +// errorLog("Another operation is being prepared, cannot prepare \"unsubscribe\" without \"send\" or \"cancel\" of the previous."); +// updateEc(ec, CC_MqttsnErrorCode_PreparationLocked); +// break; +// } + +// auto ptr = m_unsubscribeOpsAlloc.alloc(*this); +// if (!ptr) { +// errorLog("Cannot allocate new unsubscribe operation."); +// updateEc(ec, CC_MqttsnErrorCode_OutOfMemory); +// break; +// } + +// m_preparationLocked = true; +// m_ops.push_back(ptr.get()); +// m_unsubscribeOps.push_back(std::move(ptr)); +// unsubOp = m_unsubscribeOps.back().get(); +// updateEc(ec, CC_MqttsnErrorCode_Success); +// } while (false); + +// return unsubOp; +// } + +// op::SendOp* ClientImpl::publishPrepare(CC_MqttsnErrorCode* ec) +// { +// op::SendOp* sendOp = nullptr; +// do { +// if (!m_sessionState.m_connected) { +// errorLog("Client must be connected to allow publish."); +// updateEc(ec, CC_MqttsnErrorCode_NotConnected); +// break; +// } + +// if (m_sessionState.m_disconnecting) { +// errorLog("Session disconnection is in progress, cannot initiate publish."); +// updateEc(ec, CC_MqttsnErrorCode_Disconnecting); +// break; +// } + +// if (m_clientState.m_networkDisconnected) { +// errorLog("Network is disconnected."); +// updateEc(ec, CC_MqttsnErrorCode_NetworkDisconnected); +// break; +// } + +// if (m_ops.max_size() <= m_ops.size()) { +// errorLog("Cannot start publish operation, retry in next event loop iteration."); +// updateEc(ec, CC_MqttsnErrorCode_RetryLater); +// break; +// } + +// auto ptr = m_sendOpsAlloc.alloc(*this); +// if (!ptr) { +// errorLog("Cannot allocate new publish operation."); +// updateEc(ec, CC_MqttsnErrorCode_OutOfMemory); +// break; +// } + +// if (m_preparationLocked) { +// errorLog("Another operation is being prepared, cannot prepare \"unsubscribe\" without \"send\" or \"cancel\" of the previous."); +// updateEc(ec, CC_MqttsnErrorCode_PreparationLocked); +// break; +// } + +// m_preparationLocked = true; +// m_ops.push_back(ptr.get()); +// m_sendOps.push_back(std::move(ptr)); +// sendOp = m_sendOps.back().get(); +// updateEc(ec, CC_MqttsnErrorCode_Success); +// } while (false); + +// return sendOp; +// } + +// CC_MqttsnErrorCode ClientImpl::setPublishOrdering(CC_MqttsnPublishOrdering ordering) +// { +// if (CC_MqttsnPublishOrdering_ValuesLimit <= ordering) { +// errorLog("Bad publish ordering value"); +// return CC_MqttsnErrorCode_BadParam; +// } + +// m_configState.m_publishOrdering = ordering; +// return CC_MqttsnErrorCode_Success; +// } + +// void ClientImpl::handle(PublishMsg& msg) +// { +// if (m_sessionState.m_disconnecting) { +// return; +// } + +// for (auto& opPtr : m_keepAliveOps) { +// msg.dispatch(*opPtr); +// } + +// do { +// auto createRecvOp = +// [this, &msg]() +// { +// auto ptr = m_recvOpsAlloc.alloc(*this); +// if (!ptr) { +// errorLog("Failed to allocate handling op for the incoming PUBLISH message, ignoring."); +// return; +// } + +// m_ops.push_back(ptr.get()); +// m_recvOps.push_back(std::move(ptr)); +// msg.dispatch(*m_recvOps.back()); +// }; + +// using Qos = op::Op::Qos; +// auto qos = msg.transportField_flags().field_qos().value(); +// if ((qos == Qos::AtMostOnceDelivery) || +// (qos == Qos::AtLeastOnceDelivery)) { +// createRecvOp(); +// break; +// } + +// if constexpr (Config::MaxQos >= 2) { +// auto iter = +// std::find_if( +// m_recvOps.begin(), m_recvOps.end(), +// [&msg](auto& opPtr) +// { +// return opPtr->packetId() == msg.field_packetId().field().value(); +// }); + +// if (iter == m_recvOps.end()) { +// createRecvOp(); +// break; +// } + +// PubrecMsg pubrecMsg; +// pubrecMsg.field_packetId().setValue(msg.field_packetId().field().value()); + +// if (!msg.transportField_flags().field_dup().getBitValue_bit()) { +// errorLog("Non duplicate PUBLISH with packet ID in use"); +// brokerDisconnected(CC_MqttsnBrokerDisconnectReason_ProtocolError); +// return; +// } +// else { +// // Duplicate detected, just re-confirming +// (*iter)->resetTimer(); +// } + +// sendMessage(pubrecMsg); +// return; +// } +// else { +// createRecvOp(); +// break; +// } +// } while (false); +// } + +// #if CC_MQTTSN_CLIENT_MAX_QOS >= 1 +// void ClientImpl::handle(PubackMsg& msg) +// { +// static_assert(Config::MaxQos >= 1); +// if (!processPublishAckMsg(msg, msg.field_packetId().value(), false)) { +// errorLog("PUBACK with unknown packet id"); +// } +// } +// #endif // #if CC_MQTTSN_CLIENT_MAX_QOS >= 1 + +// #if CC_MQTTSN_CLIENT_MAX_QOS >= 2 +// void ClientImpl::handle(PubrecMsg& msg) +// { +// static_assert(Config::MaxQos >= 2); +// if (!processPublishAckMsg(msg, msg.field_packetId().value(), false)) { +// errorLog("PUBREC with unknown packet id"); +// } +// } + +// void ClientImpl::handle(PubrelMsg& msg) +// { +// static_assert(Config::MaxQos >= 2); +// for (auto& opPtr : m_keepAliveOps) { +// msg.dispatch(*opPtr); +// } + +// auto iter = +// std::find_if( +// m_recvOps.begin(), m_recvOps.end(), +// [&msg](auto& opPtr) +// { +// COMMS_ASSERT(opPtr); +// return opPtr->packetId() == msg.field_packetId().value(); +// }); + +// if (iter == m_recvOps.end()) { +// errorLog("PUBREL with unknown packet id"); +// return; +// } + +// msg.dispatch(**iter); +// } + +// void ClientImpl::handle(PubcompMsg& msg) +// { +// static_assert(Config::MaxQos >= 2); +// if (!processPublishAckMsg(msg, msg.field_packetId().value(), true)) { +// errorLog("PUBCOMP with unknown packet id"); +// } +// } + +// #endif // #if CC_MQTTSN_CLIENT_MAX_QOS >= 2 + +// void ClientImpl::handle(ProtMessage& msg) +// { +// static_cast(msg); +// if (m_sessionState.m_disconnecting) { +// return; +// } + +// // During the dispatch to callbacks can be called and new ops issues, +// // the m_ops vector can be resized and iterators invalidated. +// // As the result, the iteration needs to be performed using indices +// // instead of iterators. +// // Also do not dispatch the message to new ops. +// auto count = m_ops.size(); +// for (auto idx = 0U; idx < count; ++idx) { +// auto* op = m_ops[idx]; +// if (op == nullptr) { +// // ops can be deleted, but the pointer will be nullified +// // until last api guard. +// continue; +// } + +// msg.dispatch(*op); + +// // After message dispatching the whole session may be in terminating state +// // Don't continue iteration +// if (m_sessionState.m_disconnecting) { +// break; +// } +// } +// } + +// CC_MqttsnErrorCode ClientImpl::sendMessage(const ProtMessage& msg) +// { +// auto len = m_frame.length(msg); + +// if (m_buf.max_size() < len) { +// errorLog("Output buffer overflow."); +// return CC_MqttsnErrorCode_BufferOverflow; +// } + +// m_buf.resize(len); +// auto writeIter = comms::writeIteratorFor(&m_buf[0]); +// auto es = m_frame.write(msg, writeIter, len); +// COMMS_ASSERT(es == comms::ErrorStatus::Success); +// if (es != comms::ErrorStatus::Success) { +// errorLog("Failed to serialize output message."); +// return CC_MqttsnErrorCode_InternalError; +// } + +// COMMS_ASSERT(m_sendOutputDataCb != nullptr); +// m_sendOutputDataCb(m_sendOutputDataData, &m_buf[0], static_cast(len)); + +// for (auto& opPtr : m_keepAliveOps) { +// opPtr->messageSent(); +// } + +// return CC_MqttsnErrorCode_Success; +// } + +// void ClientImpl::opComplete(const op::Op* op) +// { +// auto iter = std::find(m_ops.begin(), m_ops.end(), op); +// COMMS_ASSERT(iter != m_ops.end()); +// if (iter == m_ops.end()) { +// return; +// } + +// *iter = nullptr; +// m_opsDeleted = true; + +// using ExtraCompleteFunc = void (ClientImpl::*)(const op::Op*); +// static const ExtraCompleteFunc Map[] = { +// /* Type_Connect */ &ClientImpl::opComplete_Connect, +// /* Type_KeepAlive */ &ClientImpl::opComplete_KeepAlive, +// /* Type_Disconnect */ &ClientImpl::opComplete_Disconnect, +// /* Type_Subscribe */ &ClientImpl::opComplete_Subscribe, +// /* Type_Unsubscribe */ &ClientImpl::opComplete_Unsubscribe, +// /* Type_Recv */ &ClientImpl::opComplete_Recv, +// /* Type_Send */ &ClientImpl::opComplete_Send, +// }; +// static const std::size_t MapSize = std::extent::value; +// static_assert(MapSize == op::Op::Type_NumOfValues); + +// auto idx = static_cast(op->type()); +// COMMS_ASSERT(idx < MapSize); +// if (MapSize <= idx) { +// return; +// } + +// auto func = Map[idx]; +// (this->*func)(op); +// } + +// void ClientImpl::brokerConnected(bool sessionPresent) +// { +// static_cast(sessionPresent); +// m_clientState.m_firstConnect = false; +// m_sessionState.m_connected = true; + +// do { +// if (sessionPresent) { +// for (auto& sendOpPtr : m_sendOps) { +// sendOpPtr->postReconnectionResend(); +// } + +// for (auto& recvOpPtr : m_recvOps) { +// recvOpPtr->postReconnectionResume(); +// } + +// auto resumeUntilIdx = m_sendOps.size(); +// auto resumeFromIdx = resumeUntilIdx; +// for (auto count = resumeUntilIdx; count > 0U; --count) { +// auto idx = count - 1U; +// auto& sendOpPtr = m_sendOps[idx]; +// if (!sendOpPtr->isPaused()) { +// break; +// } + +// resumeFromIdx = idx; +// } + +// if (resumeFromIdx < resumeUntilIdx) { +// resumeSendOpsSince(static_cast(resumeFromIdx)); +// } + +// break; +// } + +// // Old stored session, terminate pending ops +// for (auto* op : m_ops) { +// auto opType = op->type(); +// if ((opType != op::Op::Type::Type_Send) && +// (opType != op::Op::Type::Type_Recv)) { +// continue; +// } + +// op->terminateOp(CC_MqttsnAsyncOpStatus_Aborted); +// } +// } while (false); + +// createKeepAliveOpIfNeeded(); +// } + +// void ClientImpl::brokerDisconnected( +// CC_MqttsnBrokerDisconnectReason reason, +// CC_MqttsnAsyncOpStatus status) +// { +// m_clientState.m_initialized = false; // Require re-initialization +// m_sessionState.m_connected = false; + +// m_sessionState.m_disconnecting = true; +// terminateOps(status, TerminateMode_KeepSendRecvOps); + +// for (auto* op : m_ops) { +// if (op != nullptr) { +// op->connectivityChanged(); +// } +// } + +// if (reason < CC_MqttsnBrokerDisconnectReason_ValuesLimit) { +// COMMS_ASSERT(m_brokerDisconnectReportCb != nullptr); +// m_brokerDisconnectReportCb(m_brokerDisconnectReportData, reason); +// } +// } + +// void ClientImpl::reportMsgInfo(const CC_MqttsnMessageInfo& info) +// { +// COMMS_ASSERT(m_messageReceivedReportCb != nullptr); +// m_messageReceivedReportCb(m_messageReceivedReportData, &info); +// } + +// bool ClientImpl::hasPausedSendsBefore(const op::SendOp* sendOp) const +// { +// auto riter = +// std::find_if( +// m_sendOps.rbegin(), m_sendOps.rend(), +// [sendOp](auto& opPtr) +// { +// return opPtr.get() == sendOp; +// }); + +// COMMS_ASSERT(riter != m_sendOps.rend()); +// if (riter == m_sendOps.rend()) { +// return false; +// } + +// auto iter = riter.base() - 1; +// auto idx = static_cast(std::distance(m_sendOps.begin(), iter)); +// COMMS_ASSERT(idx < m_sendOps.size()); +// if (idx == 0U) { +// return false; +// } + +// auto& prevSendOpPtr = m_sendOps[idx - 1U]; +// return prevSendOpPtr->isPaused(); +// } + +// bool ClientImpl::hasHigherQosSendsBefore(const op::SendOp* sendOp, op::Op::Qos qos) const +// { +// for (auto& sendOpPtr : m_sendOps) { +// if (sendOpPtr.get() == sendOp) { +// return false; +// } + +// if (sendOpPtr->qos() > qos) { +// return true; +// } +// } + +// COMMS_ASSERT(false); // Mustn't reach here +// return false; +// } + +// void ClientImpl::allowNextPrepare() +// { +// COMMS_ASSERT(m_preparationLocked); +// m_preparationLocked = false; +// } + +void ClientImpl::doApiEnter() +{ + ++m_apiEnterCount; + if ((m_apiEnterCount > 1U) || (m_cancelNextTickWaitCb == nullptr)) { + return; + } + + auto prevWait = m_timerMgr.getMinWait(); + if (prevWait == 0U) { + return; + } + + auto elapsed = m_cancelNextTickWaitCb(m_cancelNextTickWaitData); + m_timerMgr.tick(elapsed); +} + +void ClientImpl::doApiExit() +{ + COMMS_ASSERT(m_apiEnterCount > 0U); + --m_apiEnterCount; + if (m_apiEnterCount > 0U) { + return; + } + + cleanOps(); + + if (m_nextTickProgramCb == nullptr) { + return; + } + + auto nextWait = m_timerMgr.getMinWait(); + if (nextWait == 0U) { + return; + } + + m_nextTickProgramCb(m_nextTickProgramData, nextWait); +} + +// void ClientImpl::createKeepAliveOpIfNeeded() +// { +// if (!m_keepAliveOps.empty()) { +// return; +// } + +// auto ptr = m_keepAliveOpsAlloc.alloc(*this); +// if (!ptr) { +// COMMS_ASSERT(false); // Should not happen +// return; +// } + +// m_ops.push_back(ptr.get()); +// m_keepAliveOps.push_back(std::move(ptr)); +// } + +// void ClientImpl::terminateOps(CC_MqttsnAsyncOpStatus status, TerminateMode mode) +// { +// for (auto* op : m_ops) { +// if (op == nullptr) { +// continue; +// } + +// if (mode == TerminateMode_KeepSendRecvOps) { +// auto opType = op->type(); + +// if ((opType == op::Op::Type_Recv) || (opType == op::Op::Type_Send)) { +// continue; +// } +// } + +// op->terminateOp(status); +// } +// } + +void ClientImpl::cleanOps() +{ +// if (!m_opsDeleted) { +// return; +// } + +// m_ops.erase( +// std::remove_if( +// m_ops.begin(), m_ops.end(), +// [](auto* op) +// { +// return op == nullptr; +// }), +// m_ops.end()); + +// m_opsDeleted = false; +} + +void ClientImpl::errorLogInternal(const char* msg) +{ + if constexpr (Config::HasErrorLog) { + if (m_errorLogCb == nullptr) { + return; + } + + m_errorLogCb(m_errorLogData, msg); + } +} + +// CC_MqttsnErrorCode ClientImpl::initInternal() +// { +// auto guard = apiEnter(); +// if ((m_sendOutputDataCb == nullptr) || +// (m_brokerDisconnectReportCb == nullptr) || +// (m_messageReceivedReportCb == nullptr)) { +// errorLog("Hasn't set all must have callbacks"); +// return CC_MqttsnErrorCode_NotIntitialized; +// } + +// bool hasTimerCallbacks = +// (m_nextTickProgramCb != nullptr) || +// (m_cancelNextTickWaitCb != nullptr); + +// if (hasTimerCallbacks) { +// bool hasAllTimerCallbacks = +// (m_nextTickProgramCb != nullptr) && +// (m_cancelNextTickWaitCb != nullptr); + +// if (!hasAllTimerCallbacks) { +// errorLog("Hasn't set all timer management callbacks callbacks"); +// return CC_MqttsnErrorCode_NotIntitialized; +// } +// } + +// terminateOps(CC_MqttsnAsyncOpStatus_Aborted, TerminateMode_KeepSendRecvOps); +// m_sessionState = SessionState(); +// m_clientState.m_initialized = true; +// return CC_MqttsnErrorCode_Success; +// } + +// void ClientImpl::resumeSendOpsSince(unsigned idx) +// { +// while (idx < m_sendOps.size()) { +// auto& opToResumePtr = m_sendOps[idx]; +// if (!opToResumePtr->isPaused()) { +// ++idx; +// continue; +// } + +// if (!opToResumePtr->resume()) { +// break; +// } + +// // After resuming some (QoS0) ops can complete right away, increment idx next iteration +// } +// } + +// op::SendOp* ClientImpl::findSendOp(std::uint16_t packetId) +// { +// auto iter = +// std::find_if( +// m_sendOps.begin(), m_sendOps.end(), +// [packetId](auto& opPtr) +// { +// COMMS_ASSERT(opPtr); +// return opPtr->packetId() == packetId; +// }); + +// if (iter == m_sendOps.end()) { +// return nullptr; +// } + +// return iter->get(); +// } + +// bool ClientImpl::isLegitSendAck(const op::SendOp* sendOp, bool pubcompAck) const +// { +// if (!sendOp->isPublished()) { +// return false; +// } + +// for (auto& sendOpPtr : m_sendOps) { +// if (sendOpPtr.get() == sendOp) { +// return true; +// } + +// if (!sendOpPtr->isAcked()) { +// return false; +// } + +// if (pubcompAck && (sendOp != m_sendOps.front().get())) { +// return false; +// } +// } + +// COMMS_ASSERT(false); // Should not be reached; +// return false; +// } + +// void ClientImpl::resendAllUntil(op::SendOp* sendOp) +// { +// // Do index controlled iteration because forcing dup resend can +// // cause early message destruction. +// for (auto idx = 0U; idx < m_sendOps.size();) { +// auto& sendOpPtr = m_sendOps[idx]; +// COMMS_ASSERT(sendOpPtr); +// auto* opBeforeResend = sendOpPtr.get(); +// sendOpPtr->forceDupResend(); // can destruct object +// if (opBeforeResend == sendOp) { +// break; +// } + +// auto* opAfterResend = sendOpPtr.get(); +// if (opBeforeResend != opAfterResend) { +// // The op object was destructed and erased, +// // do not increment index; +// continue; +// } + +// ++idx; +// } +// } + +// bool ClientImpl::processPublishAckMsg(ProtMessage& msg, std::uint16_t packetId, bool pubcompAck) +// { +// for (auto& opPtr : m_keepAliveOps) { +// msg.dispatch(*opPtr); +// } + +// auto* sendOp = findSendOp(packetId); +// if (sendOp == nullptr) { +// return false; +// } + +// if (isLegitSendAck(sendOp, pubcompAck)) { +// msg.dispatch(*sendOp); +// return true; +// } + +// resendAllUntil(sendOp); +// return true; +// } + +// void ClientImpl::opComplete_Connect(const op::Op* op) +// { +// eraseFromList(op, m_connectOps); +// } + +// void ClientImpl::opComplete_KeepAlive(const op::Op* op) +// { +// eraseFromList(op, m_keepAliveOps); +// } + +// void ClientImpl::opComplete_Disconnect(const op::Op* op) +// { +// eraseFromList(op, m_disconnectOps); +// } + +// void ClientImpl::opComplete_Subscribe(const op::Op* op) +// { +// eraseFromList(op, m_subscribeOps); +// } + +// void ClientImpl::opComplete_Unsubscribe(const op::Op* op) +// { +// eraseFromList(op, m_unsubscribeOps); +// } + +// void ClientImpl::opComplete_Recv(const op::Op* op) +// { +// eraseFromList(op, m_recvOps); +// } + +// void ClientImpl::opComplete_Send(const op::Op* op) +// { +// auto idx = eraseFromList(op, m_sendOps); +// if (m_sessionState.m_disconnecting) { +// return; +// } + +// resumeSendOpsSince(idx); +// } + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h new file mode 100644 index 00000000..c491abcc --- /dev/null +++ b/client/lib/src/ClientImpl.h @@ -0,0 +1,321 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +// #include "ClientState.h" +// #include "ConfigState.h" +#include "ExtConfig.h" +#include "ObjAllocator.h" +#include "ObjListType.h" +#include "ProtocolDefs.h" +// #include "ReuseState.h" +// #include "SessionState.h" +#include "TimerMgr.h" + +// #include "op/ConnectOp.h" +// #include "op/DisconnectOp.h" +// #include "op/KeepAliveOp.h" +// #include "op/Op.h" +// #include "op/RecvOp.h" +// #include "op/SendOp.h" +// #include "op/SubscribeOp.h" +// #include "op/UnsubscribeOp.h" + +#include "cc_mqttsn_client/common.h" + +namespace cc_mqttsn_client +{ + +class ClientImpl final : public ProtMsgHandler +{ + using Base = ProtMsgHandler; + +public: + class ApiEnterGuard + { + public: + ApiEnterGuard(ClientImpl& client) : m_client(client) + { + m_client.doApiEnter(); + } + + ~ApiEnterGuard() noexcept + { + m_client.doApiExit(); + } + + private: + ClientImpl& m_client; + }; + + ClientImpl(); + ~ClientImpl(); + + ApiEnterGuard apiEnter() + { + return ApiEnterGuard(*this); + } + + // -------------------- API Calls ----------------------------- + void tick(unsigned ms); + unsigned processData(const std::uint8_t* iter, unsigned len); + // void notifyNetworkDisconnected(); + // bool isNetworkDisconnected() const; + + // op::ConnectOp* connectPrepare(CC_MqttsnErrorCode* ec); + // op::DisconnectOp* disconnectPrepare(CC_MqttsnErrorCode* ec); + // op::SubscribeOp* subscribePrepare(CC_MqttsnErrorCode* ec); + // op::UnsubscribeOp* unsubscribePrepare(CC_MqttsnErrorCode* ec); + // op::SendOp* publishPrepare(CC_MqttsnErrorCode* ec); + + // std::size_t sendsCount() const + // { + // return m_sendOps.size(); + // } + + // void setNextTickProgramCallback(CC_MqttsnNextTickProgramCb cb, void* data) + // { + // if (cb != nullptr) { + // m_nextTickProgramCb = cb; + // m_nextTickProgramData = data; + // } + // } + + // void setCancelNextTickWaitCallback(CC_MqttsnCancelNextTickWaitCb cb, void* data) + // { + // if (cb != nullptr) { + // m_cancelNextTickWaitCb = cb; + // m_cancelNextTickWaitData = data; + // } + // } + + // void setSendOutputDataCallback(CC_MqttsnSendOutputDataCb cb, void* data) + // { + // if (cb != nullptr) { + // m_sendOutputDataCb = cb; + // m_sendOutputDataData = data; + // } + // } + + // void setBrokerDisconnectReportCallback(CC_MqttsnBrokerDisconnectReportCb cb, void* data) + // { + // if (cb != nullptr) { + // m_brokerDisconnectReportCb = cb; + // m_brokerDisconnectReportData = data; + // } + // } + + // void setMessageReceivedCallback(CC_MqttsnMessageReceivedReportCb cb, void* data) + // { + // if (cb != nullptr) { + // m_messageReceivedReportCb = cb; + // m_messageReceivedReportData = data; + // } + // } + + void setErrorLogCallback(CC_MqttsnErrorLogCb cb, void* data) + { + m_errorLogCb = cb; + m_errorLogData = data; + } + + // -------------------- Message Handling ----------------------------- + +// using Base::handle; +// virtual void handle(PublishMsg& msg) override; + +// #if CC_MQTTSN_CLIENT_MAX_QOS >= 1 +// virtual void handle(PubackMsg& msg) override; +// #endif // #if CC_MQTTSN_CLIENT_MAX_QOS >= 1 + +// #if CC_MQTTSN_CLIENT_MAX_QOS >= 2 +// virtual void handle(PubrecMsg& msg) override; +// virtual void handle(PubrelMsg& msg) override; +// virtual void handle(PubcompMsg& msg) override; +// #endif // #if CC_MQTTSN_CLIENT_MAX_QOS >= 2 + +// virtual void handle(ProtMessage& msg) override; + + // -------------------- Ops Access API ----------------------------- + + // CC_MqttsnErrorCode sendMessage(const ProtMessage& msg); + // void opComplete(const op::Op* op); + // void brokerConnected(bool sessionPresent); + // void brokerDisconnected( + // CC_MqttsnBrokerDisconnectReason reason = CC_MqttsnBrokerDisconnectReason_ValuesLimit, + // CC_MqttsnAsyncOpStatus status = CC_MqttsnAsyncOpStatus_BrokerDisconnected); + // void reportMsgInfo(const CC_MqttsnMessageInfo& info); + // bool hasPausedSendsBefore(const op::SendOp* sendOp) const; + // bool hasHigherQosSendsBefore(const op::SendOp* sendOp, op::Op::Qos qos) const; + // void allowNextPrepare(); + + TimerMgr& timerMgr() + { + return m_timerMgr; + } + + // ConfigState& configState() + // { + // return m_configState; + // } + + // const ConfigState& configState() const + // { + // return m_configState; + // } + + // ClientState& clientState() + // { + // return m_clientState; + // } + + // const ClientState& clientState() const + // { + // return m_clientState; + // } + + // SessionState& sessionState() + // { + // return m_sessionState; + // } + + // const SessionState& sessionState() const + // { + // return m_sessionState; + // } + + // ReuseState& reuseState() + // { + // return m_reuseState; + // } + + inline void errorLog(const char* msg) + { + if constexpr (Config::HasErrorLog) { + errorLogInternal(msg); + } + } + + // std::size_t recvsCount() const + // { + // return m_recvOps.size(); + // } + +private: + // using ConnectOpAlloc = ObjAllocator; + // using ConnectOpsList = ObjListType; + + // using KeepAliveOpAlloc = ObjAllocator; + // using KeepAliveOpsList = ObjListType; + + // using DisconnectOpAlloc = ObjAllocator; + // using DisconnectOpsList = ObjListType; + + // using SubscribeOpAlloc = ObjAllocator; + // using SubscribeOpsList = ObjListType; + + // using UnsubscribeOpAlloc = ObjAllocator; + // using UnsubscribeOpsList = ObjListType; + + // using RecvOpAlloc = ObjAllocator; + // using RecvOpsList = ObjListType; + + // using SendOpAlloc = ObjAllocator; + // using SendOpsList = ObjListType; + + // using OpPtrsList = ObjListType; + // using OpToDeletePtrsList = ObjListType; + // using OutputBuf = ObjListType; + + // enum TerminateMode + // { + // TerminateMode_KeepSendRecvOps, + // TerminateMode_AbortSendRecvOps, + // TerminateMode_NumOfValues + // }; + + void doApiEnter(); + void doApiExit(); + // void createKeepAliveOpIfNeeded(); + // void terminateOps(CC_MqttsnAsyncOpStatus status, TerminateMode mode); + void cleanOps(); + void errorLogInternal(const char* msg); + // CC_MqttsnErrorCode initInternal(); + // void resumeSendOpsSince(unsigned idx); + // op::SendOp* findSendOp(std::uint16_t packetId); + // bool isLegitSendAck(const op::SendOp* sendOp, bool pubcompAck = false) const; + // void resendAllUntil(op::SendOp* sendOp); + // bool processPublishAckMsg(ProtMessage& msg, std::uint16_t packetId, bool pubcompAck = false); + + // void opComplete_Connect(const op::Op* op); + // void opComplete_KeepAlive(const op::Op* op); + // void opComplete_Disconnect(const op::Op* op); + // void opComplete_Subscribe(const op::Op* op); + // void opComplete_Unsubscribe(const op::Op* op); + // void opComplete_Recv(const op::Op* op); + // void opComplete_Send(const op::Op* op); + + friend class ApiEnterGuard; + + CC_MqttsnNextTickProgramCb m_nextTickProgramCb = nullptr; + void* m_nextTickProgramData = nullptr; + + CC_MqttsnCancelNextTickWaitCb m_cancelNextTickWaitCb = nullptr; + void* m_cancelNextTickWaitData = nullptr; + + // CC_MqttsnSendOutputDataCb m_sendOutputDataCb = nullptr; + // void* m_sendOutputDataData = nullptr; + + // CC_MqttsnBrokerDisconnectReportCb m_brokerDisconnectReportCb = nullptr; + // void* m_brokerDisconnectReportData = nullptr; + + // CC_MqttsnMessageReceivedReportCb m_messageReceivedReportCb = nullptr; + // void* m_messageReceivedReportData = nullptr; + + CC_MqttsnErrorLogCb m_errorLogCb = nullptr; + void* m_errorLogData = nullptr; + + // ConfigState m_configState; + // ClientState m_clientState; + // SessionState m_sessionState; + // ReuseState m_reuseState; + + TimerMgr m_timerMgr; + unsigned m_apiEnterCount = 0U; + + // OutputBuf m_buf; + + ProtFrame m_frame; + + // ConnectOpAlloc m_connectOpAlloc; + // ConnectOpsList m_connectOps; + + // KeepAliveOpAlloc m_keepAliveOpsAlloc; + // KeepAliveOpsList m_keepAliveOps; + + // DisconnectOpAlloc m_disconnectOpsAlloc; + // DisconnectOpsList m_disconnectOps; + + // SubscribeOpAlloc m_subscribeOpsAlloc; + // SubscribeOpsList m_subscribeOps; + + // UnsubscribeOpAlloc m_unsubscribeOpsAlloc; + // UnsubscribeOpsList m_unsubscribeOps; + + // RecvOpAlloc m_recvOpsAlloc; + // RecvOpsList m_recvOps; + + // SendOpAlloc m_sendOpsAlloc; + // SendOpsList m_sendOps; + + // OpPtrsList m_ops; + bool m_opsDeleted = false; + bool m_preparationLocked = false; +}; + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/ExtConfig.h b/client/lib/src/ExtConfig.h new file mode 100644 index 00000000..ca7c7156 --- /dev/null +++ b/client/lib/src/ExtConfig.h @@ -0,0 +1,76 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Config.h" + +#include + +namespace cc_mqttsn_client +{ + +struct ExtConfig : public Config +{ + static constexpr unsigned ConnectOpsLimit = HasDynMemAlloc ? 0 : 1U; + static constexpr unsigned KeepAliveOpsLimit = HasDynMemAlloc ? 0 : 1U; + static constexpr unsigned ConnectOpTimers = 1U; + static constexpr unsigned KeepAliveOpTimers = 3U; + static constexpr unsigned DisconnectOpsLimit = HasDynMemAlloc ? 0 : 1U; + static constexpr unsigned DisconnectOpTimers = 0U; + static constexpr unsigned SubscribeOpTimers = 1U; + static constexpr unsigned UnsubscribeOpTimers = 1U; + static constexpr unsigned RecvOpsLimit = MaxQos < 2 ? 1U : (ReceiveMaxLimit == 0U ? 0U : ReceiveMaxLimit + 1U); + static constexpr unsigned RecvOpTimers = 1U; + static constexpr unsigned SendOpsLimit = SendMaxLimit == 0U ? 0U : SendMaxLimit + 1U; + static constexpr unsigned SendOpTimers = 1U; + static constexpr bool HasOpsLimit = + (ConnectOpsLimit > 0U) && + (KeepAliveOpsLimit > 0U) && + (DisconnectOpsLimit > 0U) && + (SubscribeOpsLimit > 0U) && + (UnsubscribeOpsLimit > 0U) && + (RecvOpsLimit > 0U) && + (SendOpsLimit > 0U); + static constexpr unsigned MaxTimersLimit = + (ConnectOpsLimit * ConnectOpTimers) + + (KeepAliveOpsLimit * KeepAliveOpTimers) + + (DisconnectOpsLimit * DisconnectOpTimers) + + (SubscribeOpsLimit * SubscribeOpTimers) + + (UnsubscribeOpsLimit * UnsubscribeOpTimers) + + (RecvOpsLimit * RecvOpTimers) + + (SendOpsLimit * SendOpTimers); + static constexpr unsigned TimersLimit = HasOpsLimit ? MaxTimersLimit : 0U; + + static const unsigned MaxOpsLimit = + ConnectOpsLimit + + KeepAliveOpsLimit + + DisconnectOpsLimit + + SubscribeOpsLimit + + UnsubscribeOpsLimit + + RecvOpsLimit + + SendOpsLimit; + + static const unsigned OpsLimit = HasOpsLimit ? MaxOpsLimit : 0U; + + static const unsigned PacketIdsLimitSumTmp = + SubscribeOpsLimit + + UnsubscribeOpsLimit + + SendOpsLimit; + + static const unsigned PacketIdsLimit = HasDynMemAlloc ? 0U : PacketIdsLimitSumTmp; + + static_assert(HasDynMemAlloc || (TimersLimit > 0U)); + static_assert(HasDynMemAlloc || (ConnectOpsLimit > 0U)); + static_assert(HasDynMemAlloc || (KeepAliveOpsLimit > 0U)); + static_assert(HasDynMemAlloc || (RecvOpsLimit > 0U)); + static_assert(HasDynMemAlloc || (SendOpsLimit > 0U)); + static_assert(HasDynMemAlloc || (OpsLimit > 0U)); + static_assert(HasDynMemAlloc || (PacketIdsLimit > 0U)); +}; + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/ObjAllocator.h b/client/lib/src/ObjAllocator.h new file mode 100644 index 00000000..d9e1bad5 --- /dev/null +++ b/client/lib/src/ObjAllocator.h @@ -0,0 +1,54 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "comms/util/alloc.h" +#include "comms/util/type_traits.h" + +namespace cc_mqttsn_client +{ + +template +class ObjAllocator +{ + template + using DynMemoryAlloc = comms::util::alloc::DynMemory; + + template + using InPlaceAlloc = comms::util::alloc::InPlacePool; + + template + using Alloc = + typename comms::util::LazyShallowConditional< + TLimit == 0U + >::template Type< + DynMemoryAlloc, + InPlaceAlloc + >; + + using AllocType = Alloc<>; + +public: + using Ptr = typename AllocType::Ptr; + + template + Ptr alloc(TArgs&&... args) + { + return m_alloc.template alloc(std::forward(args)...); + } + + void free(TObj* client) { + auto ptr = m_alloc.wrap(client); + static_cast(ptr); + } + +private: + AllocType m_alloc; +}; + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/ObjListType.h b/client/lib/src/ObjListType.h new file mode 100644 index 00000000..ffc738cc --- /dev/null +++ b/client/lib/src/ObjListType.h @@ -0,0 +1,48 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "comms/util/StaticVector.h" +#include "comms/util/type_traits.h" + +#include + +namespace cc_mqttsn_client +{ + +namespace details +{ + +template +class ObjListTypeHelper +{ + template + using DynVector = std::vector; + + template + using StaticVector = comms::util::StaticVector; + + template + using Vector = + typename comms::util::LazyShallowConditional< + (TLimit == 0U) && THasFeature + >::template Type< + DynVector, + StaticVector + >; + +public: + using VectorType = Vector<>; +}; + +} // namespace details + +template +using ObjListType = typename details::ObjListTypeHelper::VectorType; + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/ProtocolDefs.h b/client/lib/src/ProtocolDefs.h new file mode 100644 index 00000000..c60de733 --- /dev/null +++ b/client/lib/src/ProtocolDefs.h @@ -0,0 +1,78 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Config.h" +#include "ProtocolOptions.h" + +#include "cc_mqttsn/Message.h" +#include "cc_mqttsn/Version.h" +#include "cc_mqttsn/frame/Frame.h" +#include "cc_mqttsn/input/AllMessages.h" +#include "cc_mqttsn/input/ProtClientInputMessages.h" + +#include "comms/GenericHandler.h" + +#include + +static_assert(COMMS_MAKE_VERSION(2, 7, 1) <= cc_mqttsn::version(), + "The version of cc.mqttsn.generated library is too old"); + +namespace cc_mqttsn_client +{ + +class ProtMsgHandler; + +using ProtMessage = cc_mqttsn::Message< + comms::option::app::ReadIterator, + comms::option::app::WriteIterator, + comms::option::app::LengthInfoInterface, + comms::option::app::IdInfoInterface, + comms::option::app::Handler +>; + +CC_MQTTSN_ALIASES_FOR_ALL_MESSAGES(, Msg, ProtMessage, ProtocolOptions) + +using ProtInputMessages = + std::tuple< + cc_mqttsn::message::Advertise, + cc_mqttsn::message::Gwinfo, + cc_mqttsn::message::Connack, + cc_mqttsn::message::Willtopicreq, + cc_mqttsn::message::Willmsgreq, + cc_mqttsn::message::Register, + cc_mqttsn::message::Regack, + cc_mqttsn::message::Publish, +#if CC_MQTTSN_CLIENT_MAX_QOS > 0 + cc_mqttsn::message::Puback, +#if CC_MQTTSN_CLIENT_MAX_QOS > 1 + cc_mqttsn::message::Pubcomp, + cc_mqttsn::message::Pubrec, + cc_mqttsn::message::Pubrel, +#endif // #if CC_MQTTSN_CLIENT_MAX_QOS > 1 +#endif // #if CC_MQTTSN_CLIENT_MAX_QOS > 0 + cc_mqttsn::message::Suback, + cc_mqttsn::message::Unsuback, + cc_mqttsn::message::Pingreq, + cc_mqttsn::message::Pingresp, + cc_mqttsn::message::Disconnect, + cc_mqttsn::message::Willtopicresp, + cc_mqttsn::message::Willmsgresp + >; + +using ProtFrame = cc_mqttsn::frame::Frame; +using ProtMsgPtr = ProtFrame::MsgPtr; + +class ProtMsgHandler : public comms::GenericHandler +{ +protected: + ProtMsgHandler() = default; + ~ProtMsgHandler() noexcept = default; +}; + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/TimerMgr.cpp b/client/lib/src/TimerMgr.cpp new file mode 100644 index 00000000..f19d57da --- /dev/null +++ b/client/lib/src/TimerMgr.cpp @@ -0,0 +1,206 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "TimerMgr.h" + +#include "comms/Assert.h" + +#include +#include +#include + +namespace cc_mqttsn_client +{ + +TimerMgr::Timer TimerMgr::allocTimer() +{ + auto createTimer = + [this](unsigned idx) + { + m_timers[idx].m_allocated = true; + ++m_allocatedTimers; + return Timer(*this, idx); + }; + + if (m_allocatedTimers < m_timers.size()) { + auto iter = std::find_if( + m_timers.begin(), m_timers.end(), + [](auto& info) + { + return !info.m_allocated; + }); + + if (iter != m_timers.end()) { + auto idx = static_cast(std::distance(m_timers.begin(), iter)); + return createTimer(idx); + } + + [[maybe_unused]] static constexpr bool Should_not_happen = false; + COMMS_ASSERT(Should_not_happen); + } + + if (m_timers.max_size() <= m_timers.size()) { + return Timer(*this); + } + + auto idx = static_cast(m_timers.size()); + m_timers.resize(m_timers.size() + 1U); + return createTimer(idx); +} + +void TimerMgr::tick(unsigned ms) +{ + struct CbInfo + { + TimeoutCb m_timeoutCb = nullptr; + void* m_timeoutData = nullptr; + }; + + using CbList = ObjListType; + CbList cbList; + + for (auto idx = 0U; idx < m_timers.size(); ++idx) { + auto& info = m_timers[idx]; + if ((!info.m_allocated) || + (info.m_timeoutCb == nullptr) || + (info.m_suspended)) { + continue; + } + + if (info.m_timeoutMs <= ms) { + cbList.push_back({info.m_timeoutCb, info.m_timeoutData}); + timerCancel(idx); + continue; + } + + info.m_timeoutMs -= ms; + } + + for (auto& info : cbList) { + info.m_timeoutCb(info.m_timeoutData); + } +} + +unsigned TimerMgr::getMinWait() const +{ + if (m_allocatedTimers == 0U) { + return 0U; + } + + static constexpr auto Limit = std::numeric_limits::max(); + auto result = Limit; + + for (auto& info : m_timers) { + if ((!info.m_allocated) || + (info.m_timeoutCb == nullptr) || + (info.m_suspended)) { + continue; + } + + result = std::min(result, info.m_timeoutMs); + } + + if (result == Limit) { + return 0U; + } + + return static_cast(std::min(result, std::uint64_t(std::numeric_limits::max()))); +} + +unsigned TimerMgr::allocCount() const +{ + return + static_cast( + std::count_if( + m_timers.begin(), m_timers.end(), + [](auto& info) + { + return info.m_allocated; + })); +} + +void TimerMgr::freeTimer(unsigned idx) +{ + COMMS_ASSERT(idx < m_timers.size()); + if (m_timers.size() <= idx) { + return; + } + + COMMS_ASSERT(m_allocatedTimers > 0U); + auto& info = m_timers[idx]; + COMMS_ASSERT(info.m_allocated); + info = TimerInfo(); + --m_allocatedTimers; +} + +void TimerMgr::timerWait(unsigned idx, std::uint64_t timeoutMs, TimeoutCb cb, void* data) +{ + COMMS_ASSERT(idx < m_timers.size()); + if (m_timers.size() <= idx) { + return; + } + + auto& info = m_timers[idx]; + COMMS_ASSERT(info.m_allocated); + COMMS_ASSERT(cb != nullptr); + info.m_timeoutMs = timeoutMs; + info.m_timeoutCb = cb; + info.m_timeoutData = data; +} + +void TimerMgr::timerCancel(unsigned idx) +{ + COMMS_ASSERT(idx < m_timers.size()); + if (m_timers.size() <= idx) { + return; + } + + auto& info = m_timers[idx]; + COMMS_ASSERT(info.m_allocated); + info.m_timeoutMs = 0; + info.m_timeoutCb = nullptr; + info.m_timeoutData = nullptr; +} + +bool TimerMgr::timerIsActive(unsigned idx) const +{ + COMMS_ASSERT(idx < m_timers.size()); + if (m_timers.size() <= idx) { + return false; + } + + auto& info = m_timers[idx]; + COMMS_ASSERT(info.m_allocated); + COMMS_ASSERT(info.m_timeoutCb != nullptr || (info.m_timeoutMs == 0U)); + return (info.m_timeoutCb != nullptr); +} + +void TimerMgr::timerSetSuspended(unsigned idx, bool suspended) +{ + COMMS_ASSERT(idx < m_timers.size()); + if (m_timers.size() <= idx) { + return; + } + + auto& info = m_timers[idx]; + COMMS_ASSERT(info.m_allocated); + info.m_suspended = suspended; +} + +bool TimerMgr::timerIsSuspended(unsigned idx) const +{ + COMMS_ASSERT(idx < m_timers.size()); + if (m_timers.size() <= idx) { + return false; + } + + auto& info = m_timers[idx]; + COMMS_ASSERT(info.m_allocated); + return info.m_suspended; +} + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/TimerMgr.h b/client/lib/src/TimerMgr.h new file mode 100644 index 00000000..e2d12cf1 --- /dev/null +++ b/client/lib/src/TimerMgr.h @@ -0,0 +1,124 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "ExtConfig.h" +#include "ObjListType.h" + +#include "comms/util/StaticVector.h" +#include "comms/util/type_traits.h" + +#include + +namespace cc_mqttsn_client +{ + +class TimerMgr +{ +public: + using TimeoutCb = void (*)(void*); + + class Timer + { + public: + bool isValid() const + { + return m_idx < InvalidIdx; + } + + ~Timer() + { + if (isValid()) { + m_timerMgr.freeTimer(m_idx); + } + } + + void wait(std::uint64_t timeoutMs, TimeoutCb cb, void* data) + { + m_timerMgr.timerWait(m_idx, timeoutMs, cb, data); + } + + void cancel() + { + m_timerMgr.timerCancel(m_idx); + } + + bool isActive() const + { + return m_timerMgr.timerIsActive(m_idx); + } + + void setSuspended(bool suspended) + { + if (!isActive()) { + return; + } + + m_timerMgr.timerSetSuspended(m_idx, suspended); + } + + bool isSuspended() const + { + if (!isActive()) { + return false; + } + + return m_timerMgr.timerIsSuspended(m_idx); + } + + private: + Timer (TimerMgr& timerMgr, unsigned idx) : + m_timerMgr(timerMgr), + m_idx(idx) + { + } + + Timer (TimerMgr& timerMgr) : + m_timerMgr(timerMgr) + { + } + + TimerMgr& m_timerMgr; + unsigned m_idx = InvalidIdx; + + friend class TimerMgr; + + static const unsigned InvalidIdx = std::numeric_limits::max(); + }; + + Timer allocTimer(); + void tick(unsigned ms); + unsigned getMinWait() const; + unsigned allocCount() const; + +private: + struct TimerInfo + { + std::uint64_t m_timeoutMs = 0U; + TimeoutCb m_timeoutCb = nullptr; + void* m_timeoutData = nullptr; + bool m_allocated = false; + bool m_suspended = false; + }; + + using StorageType = ObjListType; + + friend class Timer; + + void freeTimer(unsigned idx); + void timerWait(unsigned idx, std::uint64_t timeoutMs, TimeoutCb cb, void* data); + void timerCancel(unsigned idx); + bool timerIsActive(unsigned idx) const; + void timerSetSuspended(unsigned idx, bool suspended); + bool timerIsSuspended(unsigned idx) const; + + StorageType m_timers; + unsigned m_allocatedTimers = 0U; +}; + +} // namespace cc_mqttsn_client diff --git a/client/lib/templ/Config.h.templ b/client/lib/templ/Config.h.templ new file mode 100644 index 00000000..cd585cde --- /dev/null +++ b/client/lib/templ/Config.h.templ @@ -0,0 +1,38 @@ +#pragma once + +namespace cc_mqttsn_client +{ + +struct Config +{ + static constexpr bool HasDynMemAlloc = ##CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC_CPP##; + static constexpr unsigned ClientAllocLimit = ##CC_MQTTSN_CLIENT_ALLOC_LIMIT##; +// static constexpr unsigned StringFieldFixedLen = ##CC_MQTTSN_CLIENT_STRING_FIELD_FIXED_LEN##; +// static constexpr unsigned MaxOutputPacketSize = ##CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE##; + static constexpr unsigned ReceiveMaxLimit = ##CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT##; + static constexpr unsigned SendMaxLimit = ##CC_MQTTSN_CLIENT_SEND_MAX_LIMIT##; + static constexpr unsigned SubscribeOpsLimit = ##CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT##; + static constexpr unsigned UnsubscribeOpsLimit = ##CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT##; + static constexpr bool HasErrorLog = ##CC_MQTTSN_CLIENT_HAS_ERROR_LOG_CPP##; + static constexpr bool HasTopicFormatVerification = ##CC_MQTTSN_CLIENT_HAS_TOPIC_FORMAT_VERIFICATION_CPP##; + static constexpr bool HasSubTopicVerification = ##CC_MQTTSN_CLIENT_HAS_SUB_TOPIC_VERIFICATION_CPP##; +// static constexpr unsigned SubFiltersLimit = ##CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT##; + static constexpr unsigned MaxQos = ##CC_MQTTSN_CLIENT_MAX_QOS##; + + static_assert(HasDynMemAlloc || (ClientAllocLimit > 0U), "Must use CC_MQTTSN_CLIENT_ALLOC_LIMIT in configuration to limit number of clients"); +// static_assert(HasDynMemAlloc || (StringFieldFixedLen > 0U), "Must use CC_MQTTSN_CLIENT_STRING_FIELD_FIXED_LEN in configuration to limit string field length"); +// static_assert(HasDynMemAlloc || (MaxOutputPacketSize > 0U), "Must use CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE in configuration to limit packet size"); +// static_assert(HasDynMemAlloc || (ReceiveMaxLimit > 0U) || (MaxQos < 2), "Must use CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT in configuration to limit amount of messages to receive"); + static_assert(HasDynMemAlloc || (SendMaxLimit > 0U), "Must use CC_MQTTSN_CLIENT_SEND_MAX_LIMIT in configuration to limit amount of messages to send"); + static_assert(HasDynMemAlloc || (SubscribeOpsLimit > 0U), "Must use CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT in configuration to limit amount of unfinished subscribes."); + static_assert(HasDynMemAlloc || (UnsubscribeOpsLimit > 0U), "Must use CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT in configuration to limit amount of unfinished unsubscribes."); + //static_assert(HasDynMemAlloc || (!HasSubTopicVerification) || (SubFiltersLimit > 0U), "Must use CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT in configuration to limit amount of subscribe filters"); + + static_assert(MaxQos <= 2, "Not supported QoS value"); +}; + +} // namespace cc_mqttsn_client + +#ifndef CC_MQTTSN_CLIENT_MAX_QOS +#define CC_MQTTSN_CLIENT_MAX_QOS ##CC_MQTTSN_CLIENT_MAX_QOS## +#endif \ No newline at end of file diff --git a/client/lib/templ/ProtocolOptions.h.templ b/client/lib/templ/ProtocolOptions.h.templ new file mode 100644 index 00000000..42e8db6a --- /dev/null +++ b/client/lib/templ/ProtocolOptions.h.templ @@ -0,0 +1,18 @@ +#pragma once + +#include "cc_mqttsn/options/ClientDefaultOptions.h" +#include "cc_mqttsn/Version.h" + +#include + +namespace cc_mqttsn_client +{ + +class ProtocolOptions : public cc_mqttsn::options::ClientDefaultOptions +{ + using BaseImpl = cc_mqttsn::options::ClientDefaultOptions; + +public: +}; + +} // namespace cc_mqttsn_client diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ new file mode 100644 index 00000000..871633b3 --- /dev/null +++ b/client/lib/templ/client.cpp.templ @@ -0,0 +1,46 @@ +// +// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "##NAME##client.h" +#include "ClientAllocator.h" +#include "ExtConfig.h" + +#include "comms/util/ScopeGuard.h" + +struct CC_MqttsnClient {}; + +namespace +{ + +cc_mqttsn_client::ClientAllocator& getClientAllocator() +{ + static cc_mqttsn_client::ClientAllocator Allocator; + return Allocator; +} + +inline cc_mqttsn_client::ClientImpl* clientFromHandle(CC_MqttsnClientHandle handle) +{ + return reinterpret_cast(handle); +} + +inline CC_MqttsnClientHandle handleFromClient(cc_mqttsn_client::ClientImpl* client) +{ + return reinterpret_cast(client); +} + +} // namespace + +CC_MqttsnClientHandle cc_mqttsn_##NAME##client_new() +{ + auto client = getClientAllocator().alloc(); + return handleFromClient(client.release()); +} + +void cc_mqttsn_##NAME##client_free(CC_MqttsnClientHandle handle) +{ + getClientAllocator().free(clientFromHandle(handle)); +} diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ new file mode 100644 index 00000000..a46057cd --- /dev/null +++ b/client/lib/templ/client.h.templ @@ -0,0 +1,35 @@ +// +// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// @file +/// @brief Functions of MQTT-SN client library. + +#pragma once + +#include "cc_mqttsn_client/common.h" + +#ifdef __cplusplus +extern "C" { +#endif // #ifdef __cplusplus + +/// @brief Allocate new client. +/// @details When work with the client is complete, @ref cc_mqttsn_##NAME##client_free() +/// function must be invoked. +/// @return Handle to allocated client object. This handle needs to be passed +/// as first parameter to all other API functions. +CC_MqttsnClientHandle cc_mqttsn_##NAME##client_new(); + +/// @brief Free previously allocated client. +/// @details When used communication channel to the gateway is no longer +/// needed, the client data structes allocated with +/// cc_mqttsn_##NAME##client_new() must be released using this function. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +void cc_mqttsn_##NAME##client_free(CC_MqttsnClientHandle client); + +#ifdef __cplusplus +} +#endif diff --git a/cmake/CC_Prefetch.cmake b/cmake/CC_Prefetch.cmake deleted file mode 100644 index 75e065c2..00000000 --- a/cmake/CC_Prefetch.cmake +++ /dev/null @@ -1,63 +0,0 @@ -# This file contains contains a function that prefetches comms project. - -# ****************************************************** -# cc_prefetch( -# SRC_DIR -# [TAG ] -# [REPO ] -# ) -# -# - SRC_DIR - A directory where comms sources will end up. -# - TAG - Override the default tag to checkout. -# - REPO - Override the default repository of the comms. -# - -set (CC_FETCH_DEFAULT_REPO "https://github.com/commschamp/comms.git") -set (CC_FETCH_DEFAULT_TAG "master") - -function (cc_prefetch) - set (_prefix CC_FETCH) - set (_options) - set (_oneValueArgs SRC_DIR REPO TAG) - set (_mutiValueArgs) - cmake_parse_arguments(${_prefix} "${_options}" "${_oneValueArgs}" "${_mutiValueArgs}" ${ARGN}) - - if (NOT CC_FETCH_SRC_DIR) - message (FATAL_ERROR "The SRC_DIR parameter is not provided") - endif () - - if (NOT CC_FETCH_REPO) - set (CC_FETCH_REPO ${CC_FETCH_DEFAULT_REPO}) - endif () - - if (NOT CC_FETCH_TAG) - set (CC_FETCH_TAG ${CC_FETCH_DEFAULT_TAG}) - endif () - - if (NOT GIT_FOUND) - find_package(Git REQUIRED) - endif () - - if (EXISTS "${CC_FETCH_SRC_DIR}/cmake/CC_CommsExternal.cmake") - return () - endif() - - execute_process ( - COMMAND ${CMAKE_COMMAND} -E remove_directory "${CC_FETCH_SRC_DIR}" - ) - - execute_process ( - COMMAND ${CMAKE_COMMAND} -E make_directory "${CC_FETCH_SRC_DIR}" - ) - - execute_process ( - COMMAND - ${GIT_EXECUTABLE} clone -b ${CC_FETCH_TAG} ${CC_FETCH_REPO} ${CC_FETCH_SRC_DIR} - RESULT_VARIABLE git_result - ) - - if (NOT "${git_result}" STREQUAL "0") - message (WARNING "git clone/checkout failed") - endif () - -endfunction() diff --git a/cmake/Compile.cmake b/cmake/Compile.cmake new file mode 100644 index 00000000..26cae7c1 --- /dev/null +++ b/cmake/Compile.cmake @@ -0,0 +1,39 @@ +macro (cc_mqttsn_compile) + set (compile_opts) + if (CC_MQTTSN_USE_CCACHE) + list (APPEND compile_opts USE_CCACHE) + endif () + + if (CC_MQTTSN_WARN_AS_ERR) + list (APPEND compile_opts WARN_AS_ERR) + endif () + + if (CC_MQTTSN_WITH_DEFAULT_SANITIZERS) + list (APPEND compile_opts DEFAULT_SANITIZERS) + endif () + + if (EXISTS ${LibComms_DIR}/CC_Compile.cmake) + include (${LibComms_DIR}/CC_Compile.cmake) + cc_compile(${compile_opts}) + else () + message (WARNING "Unexpected COMMS cmake scripts installation path, cannot reuse compilation options") + endif () + + set (extra_flags_list) + if ((CMAKE_COMPILER_IS_GNUCC OR ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")) AND + (NOT "${CC_MQTTSN_CUSTOM_CLIENT_CONFIG_FILES}" STREQUAL "")) + # When features are disabled some functions may remain unused + list (APPEND extra_flags_list + "-Wno-unused-function" + ) + endif () + + if (("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") AND ("${CMAKE_CXX_STANDARD}" STREQUAL "20")) + list (APPEND extra_flags_list + "-Wno-tautological-constant-out-of-range-compare" + ) + endif () + + string(REPLACE ";" " " extra_flags "${extra_flags_list}") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${extra_flags}") +endmacro() \ No newline at end of file diff --git a/cmake/ProcessTemplate.cmake b/cmake/ProcessTemplate.cmake new file mode 100644 index 00000000..b233392c --- /dev/null +++ b/cmake/ProcessTemplate.cmake @@ -0,0 +1,14 @@ +# IN_FILE - input file +# OUT_FILE - output file +# NAME - name to replace + +if (NOT EXISTS "${IN_FILE}") + message (FATAL_ERROR "Input file \"${IN_FILE}\" doesn't exist!") +endif () + +message (STATUS "Processing template \"${IN_FILE}\", updating name to \"${NAME}\"") + +file (READ ${IN_FILE} text) +string (REPLACE "##NAME##" "${NAME}" text "${text}") +file (WRITE "${OUT_FILE}" "${text}") + diff --git a/doc/custom_client_build.md b/doc/custom_client_build.md index e92c73b7..2d34490c 100644 --- a/doc/custom_client_build.md +++ b/doc/custom_client_build.md @@ -198,12 +198,12 @@ void mqttsn_bare_metal_client_free(CC_MqttsnClientHandle client); void mqttsn_bare_metal_client_set_next_tick_program_callback( CC_MqttsnClientHandle client, - CC_MqttsnNextTickProgramFn fn, + CC_MqttsnNextTickProgramCb fn, void* data); void mqttsn_bare_metal_client_set_cancel_next_tick_wait_callback( CC_MqttsnClientHandle client, - CC_MqttsnCancelNextTickWaitFn fn, + CC_MqttsnCancelNextTickWaitCb fn, void* data); ... diff --git a/gateway/lib/include/cc_mqttsn_gateway/version.h b/gateway/lib/include/cc_mqttsn_gateway/version.h index edd8469c..03fbbd98 100644 --- a/gateway/lib/include/cc_mqttsn_gateway/version.h +++ b/gateway/lib/include/cc_mqttsn_gateway/version.h @@ -11,13 +11,13 @@ #pragma once /// @brief Major verion of the library -#define CC_MQTTSN_GW_MAJOR_VERSION 1U +#define CC_MQTTSN_GW_MAJOR_VERSION 2U /// @brief Minor verion of the library #define CC_MQTTSN_GW_MINOR_VERSION 0U /// @brief Patch level of the library -#define CC_MQTTSN_GW_PATCH_VERSION 9U +#define CC_MQTTSN_GW_PATCH_VERSION 0U /// @brief Macro to create numeric version as single unsigned number #define CC_MQTTSN_GW_MAKE_VERSION(major_, minor_, patch_) \ From 196593a431a43a0b40e06dee4427f3b9c97d351e Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 23 May 2024 09:27:09 +1000 Subject: [PATCH 030/106] Saving work on the new client library. --- client/lib/CMakeLists.txt | 2 +- client/lib/doxygen/main.dox | 0 client/lib/include/cc_mqttsn_client/common.h | 58 +++++--- client/lib/src/ClientImpl.cpp | 89 ++----------- client/lib/src/ClientImpl.h | 119 +++++++++-------- client/lib/src/ConfigState.h | 27 ++++ client/lib/templ/client.cpp.templ | 93 ++++++++++++- client/lib/templ/client.h.templ | 133 +++++++++++++++++++ 8 files changed, 368 insertions(+), 153 deletions(-) create mode 100644 client/lib/doxygen/main.dox create mode 100644 client/lib/src/ConfigState.h diff --git a/client/lib/CMakeLists.txt b/client/lib/CMakeLists.txt index b3a56b84..1df4505b 100644 --- a/client/lib/CMakeLists.txt +++ b/client/lib/CMakeLists.txt @@ -224,7 +224,7 @@ function (gen_lib_mqttsn_client config_file) execute_process( COMMAND ${CMAKE_COMMAND} -E make_directory ${interface_doc_dir}) - set (doc_tgt_name "doc_cc_mqttsn_client") + set (doc_tgt_name "doc_mqttsn_client") if (NOT "${CC_MQTTSN_CLIENT_CUSTOM_NAME}" STREQUAL "") set (doc_tgt_name "doc_cc_mqttsn_${CC_MQTTSN_CLIENT_CUSTOM_NAME}_client") endif () diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox new file mode 100644 index 00000000..e69de29b diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index e1abaf86..28db596d 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -46,14 +46,21 @@ typedef enum /// @brief Error code returned by various API functions. typedef enum { - CC_MqttsnErrorCode_Success, ///< The requested operation was successfully started. - CC_MqttsnErrorCode_AlreadyStarted, ///< Returned by cc_mqttsn_client_start() function if invoked twice. - CC_MqttsnErrorCode_NotStarted, ///< Returned by various operations if issued prior to successful start using cc_mqttsn_client_start(). - CC_MqttsnErrorCode_Busy, ///< The client library is in the middle of previous operation, cannot start a new one. - CC_MqttsnErrorCode_AlreadyConnected, ///< The client library is already connected to the gateway. Returned when cc_mqttsn_client_connect() invoked second time. - CC_MqttsnErrorCode_NotConnected, ///< The client library is not connected to the gateway. Returned by operations that require connection to the gateway. - CC_MqttsnErrorCode_NotSleeping, ///< The client is not in ASLEEP mode. - CC_MqttsnErrorCode_BadParam, ///< Bad parameter is passed to the function. + CC_MqttsnErrorCode_Success = 0, ///< The requested operation was successfully started. + CC_MqttsnErrorCode_InternalError = 1, ///< Internal library error, please submit bug report + CC_MqttsnErrorCode_NotIntitialized = 2, ///< The allocated client hasn't been initialized. + CC_MqttsnErrorCode_Busy = 3, ///< The client library is in the middle of previous operation(s), cannot start a new one. + CC_MqttsnErrorCode_NotConnected = 4, ///< The client library is not connected to the gateway. Returned by operations that require connection to the gateway. + CC_MqttsnErrorCode_AlreadyConnected = 5, ///< The client library is already connected to the gateway. Returned when cc_mqttsn_client_connect() invoked second time. + CC_MqttsnErrorCode_BadParam = 6, ///< Bad parameter is passed to the function. + CC_MqttsnErrorCode_InsufficientConfig = 7, ///< The required configuration hasn't been performed. + CC_MqttsnErrorCode_OutOfMemory = 8, ///< Memory allocation failed. + CC_MqttsnErrorCode_BufferOverflow = 9, ///< Output buffer is too short + CC_MqttsnErrorCode_NotSupported = 10, ///< Feature is not supported + CC_MqttsnErrorCode_RetryLater = 11, ///< Retry in next event loop iteration. + CC_MqttsnErrorCode_Disconnecting = 12, ///< The client is in "disconnecting" state, (re)connect is required in the next iteration loop. + CC_MqttsnErrorCode_NotSleeping = 13, ///< The client is not in ASLEEP mode. + CC_MqttsnErrorCode_PreparationLocked = 14, ///< Another operation is being prepared, cannot create a new one without performing "send" or "cancel". } CC_MqttsnErrorCode; /// @brief Status of the gateway @@ -70,13 +77,22 @@ typedef enum { CC_MqttsnAsyncOpStatus_Invalid, ///< Invalid value, should never be used CC_MqttsnAsyncOpStatus_Successful, ///< The operation was successful - CC_MqttsnAsyncOpStatus_Congestion, ///< The gateway/broker was busy and could not handle the request, try again + CC_MqttsnAsyncOpStatus_Congestion, ///< The gateway/gateway was busy and could not handle the request, try again CC_MqttsnAsyncOpStatus_InvalidId, ///< Publish message used invalid topic ID. CC_MqttsnAsyncOpStatus_NotSupported, ///< The issued request is not supported by the gateway. - CC_MqttsnAsyncOpStatus_NoResponse, ///< The gateway/broker didn't respond the the request + CC_MqttsnAsyncOpStatus_NoResponse, ///< The gateway/gateway didn't respond the the request CC_MqttsnAsyncOpStatus_Aborted, ///< The operation was cancelled using cc_mqttsn_client_cancel() call. } CC_MqttsnAsyncOpStatus; +/// @brief Reason for reporting unsolicited gateway disconnection +/// @ingroup global +typedef enum +{ + CC_MqttsnGatewayDisconnectReason_DisconnectMsg = 0, ///< Gateway sent @b DISCONNECT message. + CC_MqttsnGatewayDisconnectReason_NoGatewayResponse = 0, ///< No messages from the gateway and no response to @b PINGREQ. + CC_MqttsnGatewayDisconnectReason_ValuesLimit ///< Limit for the values +} CC_MqttsnGatewayDisconnectReason; + /// @brief Declaration of struct for the @ref CC_MqttsnClientHandle; struct CC_MqttsnClient; @@ -108,6 +124,13 @@ typedef struct bool retain; ///< Retain flag of the message. } CC_MqttsnMessageInfo; +/// @brief Gateway information +typedef struct +{ + unsigned char gwId; ///< Gateway ID + CC_MqttsnGwStatus status; ///< Gateway status +} CC_MqttsnGatewayInfo; + /// @brief Callback used to request time measurement. /// @details The callback is set using /// cc_mqttsn_client_set_next_tick_program_callback() function. @@ -136,23 +159,22 @@ typedef unsigned (*CC_MqttsnCancelNextTickWaitCb)(void* data); /// cc_mqttsn_client_set_send_output_data_callback() function. /// @param[in] buf Pointer to the buffer containing data to send /// @param[in] bufLen Number of bytes to send -/// @param[in] broadcast Indication whether data needs to be broadcasted or -/// sent directly to the gateway. -typedef void (*CC_MqttsnSendOutputDataCb)(void* data, const unsigned char* buf, unsigned bufLen, bool broadcast); +/// @param[in] broadcastRadius Broadcast radius. When @b 0, means unicast to the connected gateway. +typedef void (*CC_MqttsnSendOutputDataCb)(void* data, const unsigned char* buf, unsigned bufLen, unsigned broadcastRadius); /// @brief Callback used to report gateway status. /// @details The callback is set using /// cc_mqttsn_client_set_gw_status_report_callback() function. /// @param[in] data Pointer to user data object, passed as last parameter to /// cc_mqttsn_client_set_gw_status_report_callback() function. -/// @param[in] gwId ID of the gateway. -/// @param[in] status Status of the gateway. -typedef void (*CC_MqttsnGwStatusReportCb)(void* data, unsigned char gwId, CC_MqttsnGwStatus status); +/// @param[in] info Gateway status info. +typedef void (*CC_MqttsnGwStatusReportCb)(void* data, const CC_MqttsnGatewayInfo* info); /// @brief Callback used to report unsolicited disconnection of the gateway. /// @param[in] data Pointer to user data object, passed as the last parameter to /// the request call. -typedef void (*CC_MqttsnGwDisconnectReportCb)(void* data); +/// @param[in] reason Reason of the disconnection. +typedef void (*CC_MqttsnGwDisconnectedReportCb)(void* data, CC_MqttsnGatewayDisconnectReason reason); /// @brief Callback used to report completion of the asynchronous operation. /// @param[in] data Pointer to user data object, passed as the last parameter to @@ -164,7 +186,7 @@ typedef void (*CC_MqttsnAsyncOpCompleteReportCb)(void* data, CC_MqttsnAsyncOpSta /// @param[in] data Pointer to user data object, passed as the last parameter to /// the subscribe request. /// @param[in] status Status of the subscribe operation. -/// @param[in] qos Maximal level of quality of service, the gateway/broker is going to use to publish incoming messages. +/// @param[in] qos Maximal level of quality of service, the gateway/gateway is going to use to publish incoming messages. typedef void (*CC_MqttsnSubscribeCompleteReportCb)(void* data, CC_MqttsnAsyncOpStatus status, CC_MqttsnQoS qos); /// @brief Callback used to report incoming messages. diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index b4482f20..c41e8236 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -68,86 +68,17 @@ void ClientImpl::tick(unsigned ms) doApiExit(); } -// unsigned ClientImpl::processData(const std::uint8_t* iter, unsigned len) -// { -// auto guard = apiEnter(); -// COMMS_ASSERT(!m_clientState.m_networkDisconnected); - -// if (m_clientState.m_networkDisconnected) { -// errorLog("Incoming data when network is disconnected"); -// return 0U; -// } - -// auto disconnectOnExitGuard = -// comms::util::makeScopeGuard( -// [this]() -// { -// brokerDisconnected(CC_MqttsnBrokerDisconnectReason_ProtocolError, CC_MqttsnAsyncOpStatus_ProtocolError); -// }); - -// unsigned consumed = 0; -// while (consumed < len) { -// auto remLen = len - consumed; -// auto* iterTmp = iter; - -// using IdAndFlagsField = ProtFrame::Layer_idAndFlags::Field; -// static_assert(IdAndFlagsField::minLength() == IdAndFlagsField::maxLength()); - -// if (remLen <= IdAndFlagsField::minLength()) { -// // Size info is not available -// break; -// } - -// using SizeField = ProtFrame::Layer_size::Field; -// SizeField sizeField; -// std::advance(iterTmp, IdAndFlagsField::minLength()); -// auto es = sizeField.read(iterTmp, remLen - IdAndFlagsField::minLength()); -// if (es == comms::ErrorStatus::NotEnoughData) { -// break; -// } - -// if (es != comms::ErrorStatus::Success) { -// return len; // Disconnect -// } - -// iterTmp = iter; -// ProtFrame::MsgPtr msg; -// es = m_frame.read(msg, iterTmp, remLen); -// if (es == comms::ErrorStatus::NotEnoughData) { -// break; -// } - -// if (es != comms::ErrorStatus::Success) { -// errorLog("Unexpected error in framing / payload parsing"); -// return len; -// } - -// COMMS_ASSERT(msg); -// msg->dispatch(*this); -// consumed += static_cast(std::distance(iter, iterTmp)); -// iter = iterTmp; -// } - -// disconnectOnExitGuard.release(); -// return consumed; -// } - -// void ClientImpl::notifyNetworkDisconnected() -// { -// auto guard = apiEnter(); -// m_clientState.m_networkDisconnected = true; -// if (m_sessionState.m_disconnecting) { -// return; // No need to go through broker disconnection -// } - -// brokerDisconnected(); -// } - -// bool ClientImpl::isNetworkDisconnected() const -// { -// return m_clientState.m_networkDisconnected; -// } +void ClientImpl::processData(const std::uint8_t* iter, unsigned len) +{ + auto guard = apiEnter(); + ProtFrame::MsgPtr msgPtr; + auto es = comms::processSingleWithDispatch(iter, len, m_frame, msgPtr, *this); + if (es != comms::ErrorStatus::Success) { + errorLog("Failed to decode the received message"); + return; + } +} // op::ConnectOp* ClientImpl::connectPrepare(CC_MqttsnErrorCode* ec) // { diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index c491abcc..703ed65c 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -8,7 +8,7 @@ #pragma once // #include "ClientState.h" -// #include "ConfigState.h" +#include "ConfigState.h" #include "ExtConfig.h" #include "ObjAllocator.h" #include "ObjListType.h" @@ -63,7 +63,7 @@ class ClientImpl final : public ProtMsgHandler // -------------------- API Calls ----------------------------- void tick(unsigned ms); - unsigned processData(const std::uint8_t* iter, unsigned len); + void processData(const std::uint8_t* iter, unsigned len); // void notifyNetworkDisconnected(); // bool isNetworkDisconnected() const; @@ -78,45 +78,53 @@ class ClientImpl final : public ProtMsgHandler // return m_sendOps.size(); // } - // void setNextTickProgramCallback(CC_MqttsnNextTickProgramCb cb, void* data) - // { - // if (cb != nullptr) { - // m_nextTickProgramCb = cb; - // m_nextTickProgramData = data; - // } - // } + void setNextTickProgramCallback(CC_MqttsnNextTickProgramCb cb, void* data) + { + if (cb != nullptr) { + m_nextTickProgramCb = cb; + m_nextTickProgramData = data; + } + } - // void setCancelNextTickWaitCallback(CC_MqttsnCancelNextTickWaitCb cb, void* data) - // { - // if (cb != nullptr) { - // m_cancelNextTickWaitCb = cb; - // m_cancelNextTickWaitData = data; - // } - // } + void setCancelNextTickWaitCallback(CC_MqttsnCancelNextTickWaitCb cb, void* data) + { + if (cb != nullptr) { + m_cancelNextTickWaitCb = cb; + m_cancelNextTickWaitData = data; + } + } - // void setSendOutputDataCallback(CC_MqttsnSendOutputDataCb cb, void* data) - // { - // if (cb != nullptr) { - // m_sendOutputDataCb = cb; - // m_sendOutputDataData = data; - // } - // } + void setSendOutputDataCallback(CC_MqttsnSendOutputDataCb cb, void* data) + { + if (cb != nullptr) { + m_sendOutputDataCb = cb; + m_sendOutputDataData = data; + } + } - // void setBrokerDisconnectReportCallback(CC_MqttsnBrokerDisconnectReportCb cb, void* data) - // { - // if (cb != nullptr) { - // m_brokerDisconnectReportCb = cb; - // m_brokerDisconnectReportData = data; - // } - // } + void setGatewayStatusReportCallback(CC_MqttsnGwStatusReportCb cb, void* data) + { + if (cb != nullptr) { + m_gatewayStatusReportCb = cb; + m_gatewayStatusReportData = data; + } + } - // void setMessageReceivedCallback(CC_MqttsnMessageReceivedReportCb cb, void* data) - // { - // if (cb != nullptr) { - // m_messageReceivedReportCb = cb; - // m_messageReceivedReportData = data; - // } - // } + void setGatewayDisconnectedReportCallback(CC_MqttsnGwDisconnectedReportCb cb, void* data) + { + if (cb != nullptr) { + m_gatewayDisconnectedReportCb = cb; + m_gatewayDisconnectedReportData = data; + } + } + + void setMessageReceivedCallback(CC_MqttsnMessageReportCb cb, void* data) + { + if (cb != nullptr) { + m_messageReceivedReportCb = cb; + m_messageReceivedReportData = data; + } + } void setErrorLogCallback(CC_MqttsnErrorLogCb cb, void* data) { @@ -159,15 +167,15 @@ class ClientImpl final : public ProtMsgHandler return m_timerMgr; } - // ConfigState& configState() - // { - // return m_configState; - // } + ConfigState& configState() + { + return m_configState; + } - // const ConfigState& configState() const - // { - // return m_configState; - // } + const ConfigState& configState() const + { + return m_configState; + } // ClientState& clientState() // { @@ -268,19 +276,22 @@ class ClientImpl final : public ProtMsgHandler CC_MqttsnCancelNextTickWaitCb m_cancelNextTickWaitCb = nullptr; void* m_cancelNextTickWaitData = nullptr; - // CC_MqttsnSendOutputDataCb m_sendOutputDataCb = nullptr; - // void* m_sendOutputDataData = nullptr; + CC_MqttsnSendOutputDataCb m_sendOutputDataCb = nullptr; + void* m_sendOutputDataData = nullptr; + + CC_MqttsnGwStatusReportCb m_gatewayStatusReportCb = nullptr; + void* m_gatewayStatusReportData = nullptr; - // CC_MqttsnBrokerDisconnectReportCb m_brokerDisconnectReportCb = nullptr; - // void* m_brokerDisconnectReportData = nullptr; + CC_MqttsnGwDisconnectedReportCb m_gatewayDisconnectedReportCb = nullptr; + void* m_gatewayDisconnectedReportData = nullptr; - // CC_MqttsnMessageReceivedReportCb m_messageReceivedReportCb = nullptr; - // void* m_messageReceivedReportData = nullptr; + CC_MqttsnMessageReportCb m_messageReceivedReportCb = nullptr; + void* m_messageReceivedReportData = nullptr; CC_MqttsnErrorLogCb m_errorLogCb = nullptr; void* m_errorLogData = nullptr; - // ConfigState m_configState; + ConfigState m_configState; // ClientState m_clientState; // SessionState m_sessionState; // ReuseState m_reuseState; @@ -314,8 +325,8 @@ class ClientImpl final : public ProtMsgHandler // SendOpsList m_sendOps; // OpPtrsList m_ops; - bool m_opsDeleted = false; - bool m_preparationLocked = false; + // bool m_opsDeleted = false; + // bool m_preparationLocked = false; }; } // namespace cc_mqttsn_client diff --git a/client/lib/src/ConfigState.h b/client/lib/src/ConfigState.h new file mode 100644 index 00000000..729c8592 --- /dev/null +++ b/client/lib/src/ConfigState.h @@ -0,0 +1,27 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Config.h" + +namespace cc_mqttsn_client +{ + +struct ConfigState +{ + static constexpr unsigned DefaultResponseTimeoutMs = 10000; + static constexpr unsigned DefaultRetryCount = 3U; + unsigned m_responseTimeoutMs = DefaultResponseTimeoutMs; + unsigned m_retryCount = DefaultRetryCount; + // CC_MqttsnPublishOrdering m_publishOrdering = CC_MqttsnPublishOrdering_SameQos; + // bool m_verifyOutgoingTopic = Config::HasTopicFormatVerification; + // bool m_verifyIncomingTopic = Config::HasTopicFormatVerification; + // bool m_verifySubFilter = Config::HasSubTopicVerification; +}; + +} // namespace cc_mqttsn_client diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index 871633b3..6a72a05c 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -9,7 +9,8 @@ #include "ClientAllocator.h" #include "ExtConfig.h" -#include "comms/util/ScopeGuard.h" +#include "comms/Assert.h" +//#include "comms/util/ScopeGuard.h" struct CC_MqttsnClient {}; @@ -44,3 +45,93 @@ void cc_mqttsn_##NAME##client_free(CC_MqttsnClientHandle handle) { getClientAllocator().free(clientFromHandle(handle)); } + +void cc_mqttsn_##NAME##client_set_next_tick_program_callback( + CC_MqttsnClientHandle client, + CC_MqttsnNextTickProgramCb cb, + void* data) +{ + COMMS_ASSERT(client != nullptr); + clientFromHandle(client)->setNextTickProgramCallback(cb, data); +} + +void cc_mqttsn_##NAME##client_set_cancel_next_tick_wait_callback( + CC_MqttsnClientHandle client, + CC_MqttsnCancelNextTickWaitCb cb, + void* data) +{ + COMMS_ASSERT(client != nullptr); + clientFromHandle(client)->setCancelNextTickWaitCallback(cb, data); +} + +void cc_mqttsn_##NAME##client_set_send_output_data_callback( + CC_MqttsnClientHandle client, + CC_MqttsnSendOutputDataCb cb, + void* data) +{ + COMMS_ASSERT(client != nullptr); + clientFromHandle(client)->setSendOutputDataCallback(cb, data); +} + +void cc_mqttsn_##NAME##client_set_gw_status_report_callback( + CC_MqttsnClientHandle client, + CC_MqttsnGwStatusReportCb cb, + void* data) +{ + COMMS_ASSERT(client != nullptr); + clientFromHandle(client)->setGatewayStatusReportCallback(cb, data); +} + +void cc_mqttsn_##NAME##client_set_gw_disconnect_report_callback( + CC_MqttsnClientHandle client, + CC_MqttsnGwDisconnectedReportCb cb, + void* data) +{ + COMMS_ASSERT(client != nullptr); + clientFromHandle(client)->setGatewayDisconnectedReportCallback(cb, data); +} + +void cc_mqttsn_##NAME##client_set_message_report_callback( + CC_MqttsnClientHandle client, + CC_MqttsnMessageReportCb cb, + void* data) +{ + COMMS_ASSERT(client != nullptr); + clientFromHandle(client)->setMessageReceivedCallback(cb, data); +} + +void cc_mqttsn_##NAME##client_tick(CC_MqttsnClientHandle client, unsigned ms) +{ + COMMS_ASSERT(client != nullptr); + clientFromHandle(client)->tick(ms); +} + +void cc_mqttsn_##NAME##client_process_data(CC_MqttsnClientHandle client, const unsigned char* buf, unsigned bufLen) +{ + COMMS_ASSERT(client != nullptr); + clientFromHandle(client)->processData(buf, bufLen); +} + +void cc_mqttsn_##NAME##client_set_retry_period(CC_MqttsnClientHandle client, unsigned value) +{ + COMMS_ASSERT(client != nullptr); + clientFromHandle(client)->configState().m_responseTimeoutMs = value; +} + +unsigned cc_mqttsn_##NAME##client_get_retry_period(CC_MqttsnClientHandle client) +{ + COMMS_ASSERT(client != nullptr); + return clientFromHandle(client)->configState().m_responseTimeoutMs; +} + +void cc_mqttsn_##NAME##client_set_retry_count(CC_MqttsnClientHandle client, unsigned value) +{ + COMMS_ASSERT(client != nullptr); + clientFromHandle(client)->configState().m_retryCount = value; +} + +unsigned cc_mqttsn_##NAME##client_get_retry_count(CC_MqttsnClientHandle client) +{ + COMMS_ASSERT(client != nullptr); + return clientFromHandle(client)->configState().m_retryCount; +} \ No newline at end of file diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index a46057cd..d1bb4c86 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -30,6 +30,139 @@ CC_MqttsnClientHandle cc_mqttsn_##NAME##client_new(); /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. void cc_mqttsn_##NAME##client_free(CC_MqttsnClientHandle client); +/// @brief Notify client about requested time expiry. +/// @details The reported amount of milliseconds needs to be from the +/// last request to program timer via callback (set by +/// cc_mqttsn_##NAME##client_set_next_tick_program_callback()). +/// It can be less than actually requested via the callback. If this +/// function is called, the library assumes that previously requested +/// timeout measurement is not in progress any more, and will request +/// new measurement if needed. +/// This call may cause invocation of some other callbacks, such as a request +/// to send new data to the gateway. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @param[in] ms Number of elapsed @b milliseconds. +void cc_mqttsn_##NAME##client_tick(CC_MqttsnClientHandle client, unsigned ms); + +/// @brief Provide data, received over I/O link, to the library for processing. +/// @details This call may cause invocation of some callbacks, such as +/// request to cancel the currently running time measurement, send some messages to +/// the gateway, report incoming application message, and (re)start time +/// measurement. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @param[in] buf Pointer to the buffer of data to process. +/// @param[in] bufLen Number of bytes in the data buffer. +/// @note According to the MQTT-SN specification every message should be sent in a separate +/// single datagram packet. This function assumes such behaviour and "consumes" +/// all the provided data discarding any additional bytes after the message (if exist). +void cc_mqttsn_##NAME##client_process_data(CC_MqttsnClientHandle client, const unsigned char* buf, unsigned bufLen); + +/// @brief Set retry period to wait between resending unacknowledged message to the gateway (@b Tretry from spec). +/// @details Some messages, sent to the gateway, may require acknowledgement by +/// the latter. The delay (in seconds) between such attempts to resend the +/// message may be specified using this function. The default value is +/// @b 10 seconds. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @param[in] value Number of @b seconds to wait before making an attempt to resend. +void cc_mqttsn_##NAME##client_set_retry_period(CC_MqttsnClientHandle client, unsigned value); + +/// @brief Set configured retry period to wait between resending unacknowledged message to the gateway (@b Tretry from spec). +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @see @ref cc_mqttsn_##NAME##client_set_retry_period(). +unsigned cc_mqttsn_##NAME##client_get_retry_period(CC_MqttsnClientHandle client); + +/// @brief Set number of retry attempts to perform before reporting unsuccessful result of the operation (@b Nretry from spec). +/// @details Some messages, sent to the gateway, may require acknowledgement by +/// the latter. The amount of retry attempts before reporting unsuccessful result +/// of the operation may be specified using this function. The default value +/// is @b 3. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @param[in] value Number of retry attempts. +void cc_mqttsn_##NAME##client_set_retry_count(CC_MqttsnClientHandle client, unsigned value); + +/// @brief Get configured number of retry attempts to perform before reporting unsuccessful result of the operation (@b Nretry from spec). +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @see @ref cc_mqttsn_##NAME##client_set_retry_count() +unsigned cc_mqttsn_##NAME##client_get_retry_count(CC_MqttsnClientHandle client); + +// --------------------- Callbacks --------------------- + +/// @brief Set callback to call when time measurement is required. +/// @details The MQTT-SN client may require to measure time. When such +/// measurement is required, the provided callback will be invoked with +/// the timeout duration in milliseconds. After requested time expires, +/// the @ref cc_mqttsn_##NAME##client_tick() function must be invoked. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @param[in] cb Callback function. +/// @param[in] data Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +void cc_mqttsn_##NAME##client_set_next_tick_program_callback( + CC_MqttsnClientHandle client, + CC_MqttsnNextTickProgramCb cb, + void* data); + +/// @brief Set callback to terminate current time measurement. +/// @details The client may request termination of currently running time +/// measurement, previously requested via callback, which was set using +/// @ref cc_mqttsn_##NAME##client_set_next_tick_program_callback() function. This function +/// sets appropriate callback. When invoked, it must return number of +/// elapsed milliseconds since previoius time measurement request. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @param[in] cb Callback function. +/// @param[in] data Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +void cc_mqttsn_##NAME##client_set_cancel_next_tick_wait_callback( + CC_MqttsnClientHandle client, + CC_MqttsnCancelNextTickWaitCb cb, + void* data); + +/// @brief Set callback to send raw data over I/O link. +/// @details The callback is invoked when there is a need to send data +/// to the gateway. The callback is invoked for every single message +/// that need to be sent as a single datagram. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @param[in] cb Callback function. +/// @param[in] data Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +void cc_mqttsn_##NAME##client_set_send_output_data_callback( + CC_MqttsnClientHandle client, + CC_MqttsnSendOutputDataCb cb, + void* data); + +/// @brief Set callback to report status of the gateway. +/// @details The callback is invoked when gateway status has changed. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @param[in] cb Callback function. +/// @param[in] data Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +void cc_mqttsn_##NAME##client_set_gw_status_report_callback( + CC_MqttsnClientHandle client, + CC_MqttsnGwStatusReportCb cb, + void* data); + +/// @brief Set callback to report unsolicited disconnection of the gateway. +/// @details The callback will be invoked when gateway sends unsolicited +/// @b DISCONNECT message or does not reply to @b PINGREQ message. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @param[in] cb Callback function. +/// @param[in] data Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +void cc_mqttsn_##NAME##client_set_gw_disconnect_report_callback( + CC_MqttsnClientHandle client, + CC_MqttsnGwDisconnectedReportCb cb, + void* data); + +/// @brief Set callback to report incoming messages. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @param[in] cb Callback function. +/// @param[in] data Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +void cc_mqttsn_##NAME##client_set_message_report_callback( + CC_MqttsnClientHandle client, + CC_MqttsnMessageReportCb cb, + void* data); + + #ifdef __cplusplus } #endif From 18bf971b2757b06fa9620f3339326c617b12de64 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 23 May 2024 15:48:46 +1000 Subject: [PATCH 031/106] Saving work on the new client application. --- client/lib/CMakeLists.txt | 1 + client/lib/include/cc_mqttsn_client/common.h | 13 + .../lib/script/DefineDefaultConfigVars.cmake | 2 +- client/lib/script/WriteConfigHeader.cmake | 2 +- client/lib/script/WriteProtocolOptions.cmake | 8 +- client/lib/src/ClientImpl.cpp | 106 ++++---- client/lib/src/ClientImpl.h | 25 +- client/lib/src/ConfigState.h | 4 + client/lib/src/op/Op.cpp | 245 ++++++++++++++++++ client/lib/src/op/Op.h | 136 ++++++++++ client/lib/templ/Config.h.templ | 4 +- client/lib/templ/client.cpp.templ | 109 +++++--- client/lib/templ/client.h.templ | 26 +- 13 files changed, 578 insertions(+), 103 deletions(-) create mode 100644 client/lib/src/op/Op.cpp create mode 100644 client/lib/src/op/Op.h diff --git a/client/lib/CMakeLists.txt b/client/lib/CMakeLists.txt index 1df4505b..78905206 100644 --- a/client/lib/CMakeLists.txt +++ b/client/lib/CMakeLists.txt @@ -140,6 +140,7 @@ function (gen_lib_mqttsn_client config_file) message (STATUS "Defining library ${lib_name}") set (src + src/op/Op.cpp src/ClientImpl.cpp src/TimerMgr.cpp ) diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index 28db596d..a4a8b320 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -129,6 +129,8 @@ typedef struct { unsigned char gwId; ///< Gateway ID CC_MqttsnGwStatus status; ///< Gateway status + const unsigned char* m_addr; ///< Gateway address, NULL if not known + unsigned m_addrLen; ///< Length of the gateway address, 0 if not known. } CC_MqttsnGatewayInfo; /// @brief Callback used to request time measurement. @@ -199,8 +201,19 @@ typedef void (*CC_MqttsnSubscribeCompleteReportCb)(void* data, CC_MqttsnAsyncOpS /// @param[in] msgInfo Information about incoming message. typedef void (*CC_MqttsnMessageReportCb)(void* data, const CC_MqttsnMessageInfo* msgInfo); +/// @brief Callback used to report discovered errors. +/// @param[in] data Pointer to user data object, passed as the last parameter to +/// the request call. +/// @param[in] msg Error log message. +/// @ingroup client typedef void (*CC_MqttsnErrorLogCb)(void* data, const char* msg); +/// @brief Callback used to request delay (in ms) to wait before +/// responding with @b GWINFO message on behalf of a gateway. +/// @details In case function return 0U, the response on behalf of the gateway is disabled. +/// @return Number of milliseconds to wait for another @b GWINFO to cancel the intended send of @b GWINFO on behalf of the gateway. +typedef unsigned (*CC_MqttsnGwinfoDelayRequestCb)(void* data); + #ifdef __cplusplus } #endif diff --git a/client/lib/script/DefineDefaultConfigVars.cmake b/client/lib/script/DefineDefaultConfigVars.cmake index 5a727d88..f1da0ec7 100644 --- a/client/lib/script/DefineDefaultConfigVars.cmake +++ b/client/lib/script/DefineDefaultConfigVars.cmake @@ -19,7 +19,7 @@ set_default_var_value(CC_MQTTSN_CLIENT_ALLOC_LIMIT 0) #set_default_var_value(CC_MQTTSN_CLIENT_PASSWORD_FIELD_FIXED_LEN 0) #set_default_var_value(CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN 0) #set_default_var_value(CC_MQTTSN_CLIENT_BIN_DATA_FIELD_FIXED_LEN 0) -#set_default_var_value(CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE 0) +set_default_var_value(CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE 0) set_default_var_value(CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT 0) set_default_var_value(CC_MQTTSN_CLIENT_SEND_MAX_LIMIT 0) set_default_var_value(CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT 0) diff --git a/client/lib/script/WriteConfigHeader.cmake b/client/lib/script/WriteConfigHeader.cmake index 2b6726dd..4df6c3ed 100644 --- a/client/lib/script/WriteConfigHeader.cmake +++ b/client/lib/script/WriteConfigHeader.cmake @@ -44,7 +44,7 @@ replace_in_text (CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC_CPP) replace_in_text (CC_MQTTSN_CLIENT_ALLOC_LIMIT) #replace_in_text (CC_MQTTSN_CLIENT_STRING_FIELD_FIXED_LEN) #replace_in_text (CC_MQTTSN_CLIENT_BIN_DATA_FIELD_FIXED_LEN) -#replace_in_text (CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE) +replace_in_text (CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE) replace_in_text (CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT) replace_in_text (CC_MQTTSN_CLIENT_SEND_MAX_LIMIT) replace_in_text (CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT) diff --git a/client/lib/script/WriteProtocolOptions.cmake b/client/lib/script/WriteProtocolOptions.cmake index cd8b2acf..eb21f4e8 100644 --- a/client/lib/script/WriteProtocolOptions.cmake +++ b/client/lib/script/WriteProtocolOptions.cmake @@ -44,7 +44,7 @@ endmacro() #set_default_opt (MESSAGE_SUBSCRIBE_FIELDS_LIST) #set_default_opt (MESSAGE_UNSUBSCRIBE_FIELDS_LIST) -#set_default_opt (MAX_PACKET_SIZE) +set_default_opt (MAX_PACKET_SIZE) #set_default_opt (MSG_ALLOC_OPT) ######################################### @@ -64,9 +64,9 @@ endif () # set (FIELD_STRING "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_STRING_FIELD_FIXED_LEN}>") #endif () -#if (NOT ${CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE} EQUAL 0) -# set (MAX_PACKET_SIZE "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE}>") -#endif () +if (NOT ${CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE} EQUAL 0) + set (MAX_PACKET_SIZE "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE}>") +endif () #if (NOT ${CC_MQTTSN_CLIENT_CLIENT_ID_FIELD_FIXED_LEN} EQUAL 0) # set (MESSAGE_CONNECT_FIELDS_CLIENT_ID "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_CLIENT_ID_FIELD_FIXED_LEN}>") diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index c41e8236..fab9f6b5 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -523,67 +523,67 @@ void ClientImpl::processData(const std::uint8_t* iter, unsigned len) // } // } -// CC_MqttsnErrorCode ClientImpl::sendMessage(const ProtMessage& msg) -// { -// auto len = m_frame.length(msg); +CC_MqttsnErrorCode ClientImpl::sendMessage(const ProtMessage& msg, unsigned broadcastRadius) +{ + auto len = m_frame.length(msg); -// if (m_buf.max_size() < len) { -// errorLog("Output buffer overflow."); -// return CC_MqttsnErrorCode_BufferOverflow; -// } + if (m_buf.max_size() < len) { + errorLog("Output buffer overflow."); + return CC_MqttsnErrorCode_BufferOverflow; + } -// m_buf.resize(len); -// auto writeIter = comms::writeIteratorFor(&m_buf[0]); -// auto es = m_frame.write(msg, writeIter, len); -// COMMS_ASSERT(es == comms::ErrorStatus::Success); -// if (es != comms::ErrorStatus::Success) { -// errorLog("Failed to serialize output message."); -// return CC_MqttsnErrorCode_InternalError; -// } + m_buf.resize(len); + auto writeIter = comms::writeIteratorFor(&m_buf[0]); + auto es = m_frame.write(msg, writeIter, len); + COMMS_ASSERT(es == comms::ErrorStatus::Success); + if (es != comms::ErrorStatus::Success) { + errorLog("Failed to serialize output message."); + return CC_MqttsnErrorCode_InternalError; + } -// COMMS_ASSERT(m_sendOutputDataCb != nullptr); -// m_sendOutputDataCb(m_sendOutputDataData, &m_buf[0], static_cast(len)); + COMMS_ASSERT(m_sendOutputDataCb != nullptr); + m_sendOutputDataCb(m_sendOutputDataData, &m_buf[0], static_cast(len), broadcastRadius); -// for (auto& opPtr : m_keepAliveOps) { -// opPtr->messageSent(); -// } + // for (auto& opPtr : m_keepAliveOps) { + // opPtr->messageSent(); + // } -// return CC_MqttsnErrorCode_Success; -// } + return CC_MqttsnErrorCode_Success; +} -// void ClientImpl::opComplete(const op::Op* op) -// { -// auto iter = std::find(m_ops.begin(), m_ops.end(), op); -// COMMS_ASSERT(iter != m_ops.end()); -// if (iter == m_ops.end()) { -// return; -// } +void ClientImpl::opComplete(const op::Op* op) +{ + auto iter = std::find(m_ops.begin(), m_ops.end(), op); + COMMS_ASSERT(iter != m_ops.end()); + if (iter == m_ops.end()) { + return; + } -// *iter = nullptr; -// m_opsDeleted = true; - -// using ExtraCompleteFunc = void (ClientImpl::*)(const op::Op*); -// static const ExtraCompleteFunc Map[] = { -// /* Type_Connect */ &ClientImpl::opComplete_Connect, -// /* Type_KeepAlive */ &ClientImpl::opComplete_KeepAlive, -// /* Type_Disconnect */ &ClientImpl::opComplete_Disconnect, -// /* Type_Subscribe */ &ClientImpl::opComplete_Subscribe, -// /* Type_Unsubscribe */ &ClientImpl::opComplete_Unsubscribe, -// /* Type_Recv */ &ClientImpl::opComplete_Recv, -// /* Type_Send */ &ClientImpl::opComplete_Send, -// }; -// static const std::size_t MapSize = std::extent::value; -// static_assert(MapSize == op::Op::Type_NumOfValues); - -// auto idx = static_cast(op->type()); -// COMMS_ASSERT(idx < MapSize); -// if (MapSize <= idx) { -// return; -// } + *iter = nullptr; + m_opsDeleted = true; + + using ExtraCompleteFunc = void (ClientImpl::*)(const op::Op*); + static const ExtraCompleteFunc Map[] = { + // /* Type_Connect */ &ClientImpl::opComplete_Connect, + // /* Type_KeepAlive */ &ClientImpl::opComplete_KeepAlive, + // /* Type_Disconnect */ &ClientImpl::opComplete_Disconnect, + // /* Type_Subscribe */ &ClientImpl::opComplete_Subscribe, + // /* Type_Unsubscribe */ &ClientImpl::opComplete_Unsubscribe, + // /* Type_Recv */ &ClientImpl::opComplete_Recv, + // /* Type_Send */ &ClientImpl::opComplete_Send, + }; + static const std::size_t MapSize = std::extent::value; + static_assert(MapSize == op::Op::Type_NumOfValues); + + auto idx = static_cast(op->type()); + COMMS_ASSERT(idx < MapSize); + if (MapSize <= idx) { + return; + } -// auto func = Map[idx]; -// (this->*func)(op); -// } + auto func = Map[idx]; + (this->*func)(op); +} // void ClientImpl::brokerConnected(bool sessionPresent) // { diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index 703ed65c..ab84fd40 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -20,7 +20,7 @@ // #include "op/ConnectOp.h" // #include "op/DisconnectOp.h" // #include "op/KeepAliveOp.h" -// #include "op/Op.h" +#include "op/Op.h" // #include "op/RecvOp.h" // #include "op/SendOp.h" // #include "op/SubscribeOp.h" @@ -132,6 +132,12 @@ class ClientImpl final : public ProtMsgHandler m_errorLogData = data; } + void setGwinfoDelayReqCb(CC_MqttsnGwinfoDelayRequestCb cb, void* data) + { + m_gwinfoDelayReqCb = cb; + m_gwinfoDelayReqData = data; + } + // -------------------- Message Handling ----------------------------- // using Base::handle; @@ -151,8 +157,8 @@ class ClientImpl final : public ProtMsgHandler // -------------------- Ops Access API ----------------------------- - // CC_MqttsnErrorCode sendMessage(const ProtMessage& msg); - // void opComplete(const op::Op* op); + CC_MqttsnErrorCode sendMessage(const ProtMessage& msg, unsigned broadcastRadius = 0); + void opComplete(const op::Op* op); // void brokerConnected(bool sessionPresent); // void brokerDisconnected( // CC_MqttsnBrokerDisconnectReason reason = CC_MqttsnBrokerDisconnectReason_ValuesLimit, @@ -236,9 +242,9 @@ class ClientImpl final : public ProtMsgHandler // using SendOpAlloc = ObjAllocator; // using SendOpsList = ObjListType; - // using OpPtrsList = ObjListType; + using OpPtrsList = ObjListType; // using OpToDeletePtrsList = ObjListType; - // using OutputBuf = ObjListType; + using OutputBuf = ObjListType; // enum TerminateMode // { @@ -291,6 +297,9 @@ class ClientImpl final : public ProtMsgHandler CC_MqttsnErrorLogCb m_errorLogCb = nullptr; void* m_errorLogData = nullptr; + CC_MqttsnGwinfoDelayRequestCb m_gwinfoDelayReqCb = nullptr; + void* m_gwinfoDelayReqData = nullptr; + ConfigState m_configState; // ClientState m_clientState; // SessionState m_sessionState; @@ -299,7 +308,7 @@ class ClientImpl final : public ProtMsgHandler TimerMgr m_timerMgr; unsigned m_apiEnterCount = 0U; - // OutputBuf m_buf; + OutputBuf m_buf; ProtFrame m_frame; @@ -324,8 +333,8 @@ class ClientImpl final : public ProtMsgHandler // SendOpAlloc m_sendOpsAlloc; // SendOpsList m_sendOps; - // OpPtrsList m_ops; - // bool m_opsDeleted = false; + OpPtrsList m_ops; + bool m_opsDeleted = false; // bool m_preparationLocked = false; }; diff --git a/client/lib/src/ConfigState.h b/client/lib/src/ConfigState.h index 729c8592..bc26662e 100644 --- a/client/lib/src/ConfigState.h +++ b/client/lib/src/ConfigState.h @@ -16,8 +16,12 @@ struct ConfigState { static constexpr unsigned DefaultResponseTimeoutMs = 10000; static constexpr unsigned DefaultRetryCount = 3U; + static constexpr unsigned DefaultBroadcastRadius = 3U; + static constexpr unsigned MaxBroadcastRadius = 255U; + unsigned m_responseTimeoutMs = DefaultResponseTimeoutMs; unsigned m_retryCount = DefaultRetryCount; + unsigned m_broadcastRadius = DefaultBroadcastRadius; // CC_MqttsnPublishOrdering m_publishOrdering = CC_MqttsnPublishOrdering_SameQos; // bool m_verifyOutgoingTopic = Config::HasTopicFormatVerification; // bool m_verifyIncomingTopic = Config::HasTopicFormatVerification; diff --git a/client/lib/src/op/Op.cpp b/client/lib/src/op/Op.cpp new file mode 100644 index 00000000..2521e9fc --- /dev/null +++ b/client/lib/src/op/Op.cpp @@ -0,0 +1,245 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "op/Op.h" + +#include "ClientImpl.h" + +#include "comms/util/ScopeGuard.h" +#include "comms/cast.h" + +#include +#include + +namespace cc_mqttsn_client +{ + +namespace op +{ + +namespace +{ + +// static constexpr char TopicSep = '/'; +// static constexpr char MultLevelWildcard = '#'; +// static constexpr char SingleLevelWildcard = '+'; + +} // namespace + + + +Op::Op(ClientImpl& client) : + m_client(client), + m_responseTimeoutMs(client.configState().m_responseTimeoutMs), + m_retryCount(client.configState().m_retryCount) +{ +} + +void Op::sendMessage(const ProtMessage& msg, unsigned broadcastRadius) +{ + m_client.sendMessage(msg, broadcastRadius); +} + +void Op::terminateOpImpl([[maybe_unused]] CC_MqttsnAsyncOpStatus status) +{ + opComplete(); +} + +void Op::opComplete() +{ + m_client.opComplete(this); +} + +// std::uint16_t Op::allocPacketId() +// { +// static constexpr auto MaxPacketId = std::numeric_limits::max(); +// auto& allocatedPacketIds = m_client.clientState().m_allocatedPacketIds; + +// if ((allocatedPacketIds.max_size() <= allocatedPacketIds.size()) || +// (MaxPacketId <= allocatedPacketIds.size())) { +// errorLog("No more available packet IDs for allocation"); +// return 0U; +// } + +// auto& lastPacketId = m_client.clientState().m_lastPacketId; +// auto nextPacketId = static_cast(lastPacketId + 1U); + +// if (nextPacketId == 0U) { +// nextPacketId = 1U; +// } + +// while (true) { +// if (allocatedPacketIds.empty() || (allocatedPacketIds.back() < nextPacketId)) { +// allocatedPacketIds.push_back(nextPacketId); +// break; +// } + +// auto iter = std::lower_bound(allocatedPacketIds.begin(), allocatedPacketIds.end(), nextPacketId); +// if ((iter == allocatedPacketIds.end()) || (*iter != nextPacketId)) { +// allocatedPacketIds.insert(iter, nextPacketId); +// break; +// } + +// ++nextPacketId; +// } + +// lastPacketId = static_cast(nextPacketId); +// return lastPacketId; +// } + +// void Op::releasePacketId(std::uint16_t id) +// { +// if (id == 0U) { +// return; +// } + +// auto& allocatedPacketIds = m_client.clientState().m_allocatedPacketIds; +// auto iter = std::lower_bound(allocatedPacketIds.begin(), allocatedPacketIds.end(), id); +// if ((iter == allocatedPacketIds.end()) || (*iter != id)) { +// [[maybe_unused]] static constexpr bool ShouldNotHappen = false; +// COMMS_ASSERT(ShouldNotHappen); +// return; +// } + +// allocatedPacketIds.erase(iter); +// } + +void Op::errorLogInternal(const char* msg) +{ + if constexpr (Config::HasErrorLog) { + m_client.errorLog(msg); + } +} + +// bool Op::verifySubFilterInternal(const char* filter) +// { +// if (Config::HasTopicFormatVerification) { +// if (!m_client.configState().m_verifyOutgoingTopic) { +// return true; +// } + +// COMMS_ASSERT(filter != nullptr); +// if (filter[0] == '\0') { +// return false; +// } + +// auto pos = 0U; +// int lastSep = -1; +// while (filter[pos] != '\0') { +// auto incPosGuard = +// comms::util::makeScopeGuard( +// [&pos]() +// { +// ++pos; +// }); + +// auto ch = filter[pos]; + +// if (ch == TopicSep) { +// comms::cast_assign(lastSep) = pos; +// continue; +// } + +// if (ch == MultLevelWildcard) { + +// if (filter[pos + 1] != '\0') { +// errorLog("Multi-level wildcard \'#\' must be last."); +// return false; +// } + +// if (pos == 0U) { +// return true; +// } + +// if ((lastSep < 0) || (static_cast(pos - 1U) != lastSep)) { +// errorLog("Multi-level wildcard \'#\' must follow separator."); +// return false; +// } + +// return true; +// } + +// if (ch != SingleLevelWildcard) { +// continue; +// } + +// auto nextCh = filter[pos + 1]; +// if ((nextCh != '\0') && (nextCh != TopicSep)) { +// errorLog("Single-level wildcard \'+\' must be last of followed by /."); +// return false; +// } + +// if (pos == 0U) { +// continue; +// } + +// if ((lastSep < 0) || (static_cast(pos - 1U) != lastSep)) { +// errorLog("Single-level wildcard \'+\' must follow separator."); +// return false; +// } +// } + +// return true; +// } +// else { +// [[maybe_unused]] static constexpr bool ShouldNotBeCalled = false; +// COMMS_ASSERT(ShouldNotBeCalled); +// return false; +// } +// } + +// bool Op::verifyPubTopicInternal(const char* topic, bool outgoing) +// { +// if (Config::HasTopicFormatVerification) { +// if (outgoing && (!m_client.configState().m_verifyOutgoingTopic)) { +// return true; +// } + +// if ((!outgoing) && (!m_client.configState().m_verifyIncomingTopic)) { +// return true; +// } + +// COMMS_ASSERT(topic != nullptr); +// if (topic[0] == '\0') { +// return false; +// } + +// if (outgoing && (topic[0] == '$')) { +// errorLog("Cannot start topic with \'$\'."); +// return false; +// } + +// auto pos = 0U; +// while (topic[pos] != '\0') { +// auto incPosGuard = +// comms::util::makeScopeGuard( +// [&pos]() +// { +// ++pos; +// }); + +// auto ch = topic[pos]; + +// if ((ch == MultLevelWildcard) || +// (ch == SingleLevelWildcard)) { +// errorLog("Wildcards cannot be used in publish topic"); +// return false; +// } +// } + +// return true; +// } +// else { +// [[maybe_unused]] static constexpr bool ShouldNotBeCalled = false; +// COMMS_ASSERT(ShouldNotBeCalled); +// return false; +// } +// } + +} // namespace op + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/op/Op.h b/client/lib/src/op/Op.h new file mode 100644 index 00000000..26e0b867 --- /dev/null +++ b/client/lib/src/op/Op.h @@ -0,0 +1,136 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "ExtConfig.h" +#include "ObjListType.h" +#include "ProtocolDefs.h" + +#include "cc_mqttsn_client/common.h" + +#include + +namespace cc_mqttsn_client +{ + +class ClientImpl; + +namespace op +{ + +class Op : public ProtMsgHandler +{ +public: + enum Type + { + // Type_Connect, + // Type_KeepAlive, + // Type_Disconnect, + // Type_Subscribe, + // Type_Unsubscribe, + // Type_Recv, + // Type_Send, + Type_NumOfValues // Must be last + }; + + using Qos = cc_mqttsn::field::QosCommon::ValueType; + + virtual ~Op() noexcept = default; + + Type type() const + { + return typeImpl(); + } + + void terminateOp(CC_MqttsnAsyncOpStatus status) + { + terminateOpImpl(status); + } + + unsigned getResponseTimeout() const + { + return m_responseTimeoutMs; + } + + void setResponseTimeout(unsigned ms) + { + m_responseTimeoutMs = ms; + } + + inline + static bool verifyQosValid(Qos qos) + { + return (qos <= static_cast(Config::MaxQos)); + } + +protected: + explicit Op(ClientImpl& client); + + virtual Type typeImpl() const = 0; + virtual void terminateOpImpl(CC_MqttsnAsyncOpStatus status); + + void sendMessage(const ProtMessage& msg, unsigned broadcastRadius = 0U); + void opComplete(); + // std::uint16_t allocPacketId(); + // void releasePacketId(std::uint16_t id); + + ClientImpl& client() + { + return m_client; + } + + const ClientImpl& client() const + { + return m_client; + } + + inline void errorLog(const char* msg) + { + if constexpr (Config::HasErrorLog) { + errorLogInternal(msg); + } + } + + // inline bool verifySubFilter(const char* filter) + // { + // if (Config::HasTopicFormatVerification) { + // return verifySubFilterInternal(filter); + // } + // else { + // return true; + // } + // } + + // inline bool verifyPubTopic(const char* topic, bool outgoing) + // { + // if (Config::HasTopicFormatVerification) { + // return verifyPubTopicInternal(topic, outgoing); + // } + // else { + // return true; + // } + // } + + static constexpr std::size_t maxStringLen() + { + return std::numeric_limits::max(); + } + +private: + void errorLogInternal(const char* msg); + // bool verifySubFilterInternal(const char* filter); + // bool verifyPubTopicInternal(const char* topic, bool outgoing); + + ClientImpl& m_client; + unsigned m_responseTimeoutMs = 0U; + unsigned m_retryCount = 0U; +}; + +} // namespace op + +} // namespace cc_mqttsn_client diff --git a/client/lib/templ/Config.h.templ b/client/lib/templ/Config.h.templ index cd585cde..9eda962a 100644 --- a/client/lib/templ/Config.h.templ +++ b/client/lib/templ/Config.h.templ @@ -8,7 +8,7 @@ struct Config static constexpr bool HasDynMemAlloc = ##CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC_CPP##; static constexpr unsigned ClientAllocLimit = ##CC_MQTTSN_CLIENT_ALLOC_LIMIT##; // static constexpr unsigned StringFieldFixedLen = ##CC_MQTTSN_CLIENT_STRING_FIELD_FIXED_LEN##; -// static constexpr unsigned MaxOutputPacketSize = ##CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE##; + static constexpr unsigned MaxOutputPacketSize = ##CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE##; static constexpr unsigned ReceiveMaxLimit = ##CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT##; static constexpr unsigned SendMaxLimit = ##CC_MQTTSN_CLIENT_SEND_MAX_LIMIT##; static constexpr unsigned SubscribeOpsLimit = ##CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT##; @@ -21,7 +21,7 @@ struct Config static_assert(HasDynMemAlloc || (ClientAllocLimit > 0U), "Must use CC_MQTTSN_CLIENT_ALLOC_LIMIT in configuration to limit number of clients"); // static_assert(HasDynMemAlloc || (StringFieldFixedLen > 0U), "Must use CC_MQTTSN_CLIENT_STRING_FIELD_FIXED_LEN in configuration to limit string field length"); -// static_assert(HasDynMemAlloc || (MaxOutputPacketSize > 0U), "Must use CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE in configuration to limit packet size"); + static_assert(HasDynMemAlloc || (MaxOutputPacketSize > 0U), "Must use CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE in configuration to limit packet size"); // static_assert(HasDynMemAlloc || (ReceiveMaxLimit > 0U) || (MaxQos < 2), "Must use CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT in configuration to limit amount of messages to receive"); static_assert(HasDynMemAlloc || (SendMaxLimit > 0U), "Must use CC_MQTTSN_CLIENT_SEND_MAX_LIMIT in configuration to limit amount of messages to send"); static_assert(HasDynMemAlloc || (SubscribeOpsLimit > 0U), "Must use CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT in configuration to limit amount of unfinished subscribes."); diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index 6a72a05c..ddec5b8b 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -12,6 +12,8 @@ #include "comms/Assert.h" //#include "comms/util/ScopeGuard.h" +#include + struct CC_MqttsnClient {}; namespace @@ -46,6 +48,76 @@ void cc_mqttsn_##NAME##client_free(CC_MqttsnClientHandle handle) getClientAllocator().free(clientFromHandle(handle)); } +void cc_mqttsn_##NAME##client_tick(CC_MqttsnClientHandle client, unsigned ms) +{ + COMMS_ASSERT(client != nullptr); + clientFromHandle(client)->tick(ms); +} + +void cc_mqttsn_##NAME##client_process_data(CC_MqttsnClientHandle client, const unsigned char* buf, unsigned bufLen) +{ + COMMS_ASSERT(client != nullptr); + clientFromHandle(client)->processData(buf, bufLen); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_retry_period(CC_MqttsnClientHandle client, unsigned value) +{ + COMMS_ASSERT(client != nullptr); + static const unsigned MaxValue = std::numeric_limits::max() / 1000U; + if (MaxValue < value) { + clientFromHandle(client)->errorLog("The retry period value is too high"); + return CC_MqttsnErrorCode_BadParam; + } + + clientFromHandle(client)->configState().m_responseTimeoutMs = value * 1000U; + return CC_MqttsnErrorCode_Success; +} + +unsigned cc_mqttsn_##NAME##client_get_retry_period(CC_MqttsnClientHandle client) +{ + COMMS_ASSERT(client != nullptr); + return clientFromHandle(client)->configState().m_responseTimeoutMs; +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_retry_count(CC_MqttsnClientHandle client, unsigned value) +{ + COMMS_ASSERT(client != nullptr); + if (value < 1U) { + clientFromHandle(client)->errorLog("The retry count setting must be greater than zero."); + return CC_MqttsnErrorCode_BadParam; + } + + COMMS_ASSERT(client != nullptr); + clientFromHandle(client)->configState().m_retryCount = value; + return CC_MqttsnErrorCode_Success; +} + +unsigned cc_mqttsn_##NAME##client_get_retry_count(CC_MqttsnClientHandle client) +{ + COMMS_ASSERT(client != nullptr); + return clientFromHandle(client)->configState().m_retryCount; +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_broadcast_radius(CC_MqttsnClientHandle client, unsigned value) +{ + COMMS_ASSERT(client != nullptr); + if (cc_mqttsn_client::ConfigState::MaxBroadcastRadius < value) { + clientFromHandle(client)->errorLog("The broadcast radius is too high"); + return CC_MqttsnErrorCode_BadParam; + } + + clientFromHandle(client)->configState().m_broadcastRadius = value; + return CC_MqttsnErrorCode_Success; +} + +unsigned cc_mqttsn_##NAME##client_get_broadcast_radius(CC_MqttsnClientHandle client) +{ + COMMS_ASSERT(client != nullptr); + return clientFromHandle(client)->configState().m_broadcastRadius; +} + +// --------------------- Callbacks --------------------- + void cc_mqttsn_##NAME##client_set_next_tick_program_callback( CC_MqttsnClientHandle client, CC_MqttsnNextTickProgramCb cb, @@ -100,38 +172,11 @@ void cc_mqttsn_##NAME##client_set_message_report_callback( clientFromHandle(client)->setMessageReceivedCallback(cb, data); } -void cc_mqttsn_##NAME##client_tick(CC_MqttsnClientHandle client, unsigned ms) -{ - COMMS_ASSERT(client != nullptr); - clientFromHandle(client)->tick(ms); -} - -void cc_mqttsn_##NAME##client_process_data(CC_MqttsnClientHandle client, const unsigned char* buf, unsigned bufLen) -{ - COMMS_ASSERT(client != nullptr); - clientFromHandle(client)->processData(buf, bufLen); -} - -void cc_mqttsn_##NAME##client_set_retry_period(CC_MqttsnClientHandle client, unsigned value) -{ - COMMS_ASSERT(client != nullptr); - clientFromHandle(client)->configState().m_responseTimeoutMs = value; -} - -unsigned cc_mqttsn_##NAME##client_get_retry_period(CC_MqttsnClientHandle client) -{ - COMMS_ASSERT(client != nullptr); - return clientFromHandle(client)->configState().m_responseTimeoutMs; -} - -void cc_mqttsn_##NAME##client_set_retry_count(CC_MqttsnClientHandle client, unsigned value) +void cc_mqttsn_##NAME##client_set_gwinfo_delay_request_callback( + CC_MqttsnClientHandle client, + CC_MqttsnGwinfoDelayRequestCb cb, + void* data) { COMMS_ASSERT(client != nullptr); - clientFromHandle(client)->configState().m_retryCount = value; + clientFromHandle(client)->setGwinfoDelayReqCb(cb, data); } - -unsigned cc_mqttsn_##NAME##client_get_retry_count(CC_MqttsnClientHandle client) -{ - COMMS_ASSERT(client != nullptr); - return clientFromHandle(client)->configState().m_retryCount; -} \ No newline at end of file diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index d1bb4c86..62cb98c5 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -64,7 +64,7 @@ void cc_mqttsn_##NAME##client_process_data(CC_MqttsnClientHandle client, const u /// @b 10 seconds. /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. /// @param[in] value Number of @b seconds to wait before making an attempt to resend. -void cc_mqttsn_##NAME##client_set_retry_period(CC_MqttsnClientHandle client, unsigned value); +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_retry_period(CC_MqttsnClientHandle client, unsigned value); /// @brief Set configured retry period to wait between resending unacknowledged message to the gateway (@b Tretry from spec). /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. @@ -78,13 +78,27 @@ unsigned cc_mqttsn_##NAME##client_get_retry_period(CC_MqttsnClientHandle client) /// is @b 3. /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. /// @param[in] value Number of retry attempts. -void cc_mqttsn_##NAME##client_set_retry_count(CC_MqttsnClientHandle client, unsigned value); +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_retry_count(CC_MqttsnClientHandle client, unsigned value); /// @brief Get configured number of retry attempts to perform before reporting unsuccessful result of the operation (@b Nretry from spec). /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. /// @see @ref cc_mqttsn_##NAME##client_set_retry_count() unsigned cc_mqttsn_##NAME##client_get_retry_count(CC_MqttsnClientHandle client); +/// @brief Set broadcast radius. +/// @details When searching for gateways, the client library broadcasts @b SEARCHGW +/// messages. It contains the broadcast radius value. This value can be +/// set using this function. Default radius value is @b 3. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @param[in] value Broadcast radius. +/// @pre The broadcast value cannot exceed 255. +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_broadcast_radius(CC_MqttsnClientHandle client, unsigned value); + +/// @brief Get current broadcast radius configuration. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @see @ref cc_mqttsn_##NAME##client_set_broadcast_radius() +unsigned cc_mqttsn_##NAME##client_get_broadcast_radius(CC_MqttsnClientHandle client); + // --------------------- Callbacks --------------------- /// @brief Set callback to call when time measurement is required. @@ -162,6 +176,14 @@ void cc_mqttsn_##NAME##client_set_message_report_callback( CC_MqttsnMessageReportCb cb, void* data); +/// @brief Set callback to request a random timeout to send @b GWINFO as a response to the @b SEARCHGW from other client. +/// @details According to the MQTT-SN specification, the client can send @b GWINFO message on behalf of a gateway +/// after some randrom amount of time. Use this function to allow the library to +/// request such timeout. If not set, sending @b GWINFO on behalf of the gateway is @b disabled. +void cc_mqttsn_##NAME##client_set_gwinfo_delay_request_callback( + CC_MqttsnClientHandle client, + CC_MqttsnGwinfoDelayRequestCb cb, + void* data); #ifdef __cplusplus } From d6432e584b074c9940ab9b0dbb352a7d25d641eb Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 24 May 2024 07:52:46 +1000 Subject: [PATCH 032/106] Not expecting PINGRESP from the client. --- gateway/lib/src/session_op/Forward.cpp | 5 ----- gateway/lib/src/session_op/Forward.h | 1 - 2 files changed, 6 deletions(-) diff --git a/gateway/lib/src/session_op/Forward.cpp b/gateway/lib/src/session_op/Forward.cpp index 1241317e..8d355680 100644 --- a/gateway/lib/src/session_op/Forward.cpp +++ b/gateway/lib/src/session_op/Forward.cpp @@ -115,11 +115,6 @@ void Forward::handle([[maybe_unused]] PingreqMsg_SN& msg) sendToBroker(PingreqMsg()); } -void Forward::handle([[maybe_unused]] PingrespMsg_SN& msg) -{ - sendToBroker(PingrespMsg()); -} - void Forward::handle(SubscribeMsg_SN& msg) { auto sendSubackFunc = diff --git a/gateway/lib/src/session_op/Forward.h b/gateway/lib/src/session_op/Forward.h index c0f9d33d..a2fec114 100644 --- a/gateway/lib/src/session_op/Forward.h +++ b/gateway/lib/src/session_op/Forward.h @@ -34,7 +34,6 @@ class Forward : public SessionOp virtual void handle(PublishMsg_SN& msg) override; virtual void handle(PubrelMsg_SN& msg) override; virtual void handle(PingreqMsg_SN& msg) override; - virtual void handle(PingrespMsg_SN& msg) override; virtual void handle(SubscribeMsg_SN& msg) override; virtual void handle(UnsubscribeMsg_SN& msg) override; From a661cf87fa97d7c6a70a25c3e604087d77d7ad08 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 24 May 2024 09:22:21 +1000 Subject: [PATCH 033/106] Saving work on the gateway discovery functionality for client. --- client/lib/CMakeLists.txt | 1 + client/lib/include/cc_mqttsn_client/common.h | 33 +- .../lib/script/DefineDefaultConfigVars.cmake | 3 + client/lib/script/WriteConfigHeader.cmake | 4 + client/lib/src/ClientImpl.cpp | 349 +++++++++++++++--- client/lib/src/ClientImpl.h | 46 ++- client/lib/src/ClientState.h | 47 +++ client/lib/src/ExtConfig.h | 10 +- client/lib/src/ProtocolDefs.h | 3 + client/lib/src/op/Op.cpp | 106 +++--- client/lib/src/op/Op.h | 20 +- client/lib/src/op/SearchOp.cpp | 143 +++++++ client/lib/src/op/SearchOp.h | 57 +++ client/lib/templ/Config.h.templ | 11 +- 14 files changed, 692 insertions(+), 141 deletions(-) create mode 100644 client/lib/src/ClientState.h create mode 100644 client/lib/src/op/SearchOp.cpp create mode 100644 client/lib/src/op/SearchOp.h diff --git a/client/lib/CMakeLists.txt b/client/lib/CMakeLists.txt index 78905206..031db6fd 100644 --- a/client/lib/CMakeLists.txt +++ b/client/lib/CMakeLists.txt @@ -141,6 +141,7 @@ function (gen_lib_mqttsn_client config_file) message (STATUS "Defining library ${lib_name}") set (src src/op/Op.cpp + src/op/SearchOp.cpp src/ClientImpl.cpp src/TimerMgr.cpp ) diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index a4a8b320..74416bcc 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -75,13 +75,13 @@ typedef enum /// @brief Status of the asynchronous operation typedef enum { - CC_MqttsnAsyncOpStatus_Invalid, ///< Invalid value, should never be used - CC_MqttsnAsyncOpStatus_Successful, ///< The operation was successful - CC_MqttsnAsyncOpStatus_Congestion, ///< The gateway/gateway was busy and could not handle the request, try again - CC_MqttsnAsyncOpStatus_InvalidId, ///< Publish message used invalid topic ID. - CC_MqttsnAsyncOpStatus_NotSupported, ///< The issued request is not supported by the gateway. - CC_MqttsnAsyncOpStatus_NoResponse, ///< The gateway/gateway didn't respond the the request - CC_MqttsnAsyncOpStatus_Aborted, ///< The operation was cancelled using cc_mqttsn_client_cancel() call. + CC_MqttsnAsyncOpStatus_Complete = 0, ///< The requested operation has been completed, refer to reported extra details for information + CC_MqttsnAsyncOpStatus_InternalError = 1, ///< Internal library error, please submit bug report + CC_MqttsnAsyncOpStatus_Timeout = 2, ///< The required response from broker hasn't been received in time + CC_MqttsnAsyncOpStatus_Aborted = 3, ///< The operation has been aborted before completion due to client's side operation. + CC_MqttsnAsyncOpStatus_OutOfMemory = 4, ///< The client library wasn't able to allocate necessary memory. + CC_MqttsnAsyncOpStatus_BadParam = 5, ///< Bad value has been returned from the relevant callback. + CC_MqttsnAsyncOpStatus_ValuesLimit ///< Limit for the values } CC_MqttsnAsyncOpStatus; /// @brief Reason for reporting unsolicited gateway disconnection @@ -124,13 +124,19 @@ typedef struct bool retain; ///< Retain flag of the message. } CC_MqttsnMessageInfo; +/// @brief Tracked gateway status information +typedef struct +{ + unsigned char m_gwId; ///< Gateway ID + CC_MqttsnGwStatus m_status; ///< Gateway status +} CC_MqttsnGatewayStatusInfo; + /// @brief Gateway information typedef struct { - unsigned char gwId; ///< Gateway ID - CC_MqttsnGwStatus status; ///< Gateway status - const unsigned char* m_addr; ///< Gateway address, NULL if not known - unsigned m_addrLen; ///< Length of the gateway address, 0 if not known. + unsigned char m_gwId; ///< Gateway ID + const unsigned char* m_addr; ///< Address of the gateway if known, NULL if not. + unsigned m_addrLen; ///< Length of the address } CC_MqttsnGatewayInfo; /// @brief Callback used to request time measurement. @@ -170,7 +176,7 @@ typedef void (*CC_MqttsnSendOutputDataCb)(void* data, const unsigned char* buf, /// @param[in] data Pointer to user data object, passed as last parameter to /// cc_mqttsn_client_set_gw_status_report_callback() function. /// @param[in] info Gateway status info. -typedef void (*CC_MqttsnGwStatusReportCb)(void* data, const CC_MqttsnGatewayInfo* info); +typedef void (*CC_MqttsnGwStatusReportCb)(void* data, const CC_MqttsnGatewayStatusInfo* info); /// @brief Callback used to report unsolicited disconnection of the gateway. /// @param[in] data Pointer to user data object, passed as the last parameter to @@ -182,7 +188,8 @@ typedef void (*CC_MqttsnGwDisconnectedReportCb)(void* data, CC_MqttsnGatewayDisc /// @param[in] data Pointer to user data object, passed as the last parameter to /// the request call. /// @param[in] status Status of the asynchronous operation. -typedef void (*CC_MqttsnAsyncOpCompleteReportCb)(void* data, CC_MqttsnAsyncOpStatus status); +/// @param[in] info Discovered gateway information. Not NULL if and only if @b status is @ref CC_MqttsnAsyncOpStatus_Complete. +typedef void (*CC_MqttsnSearchCompleteReportCb)(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info); /// @brief Callback used to report completion of the subscribe operation. /// @param[in] data Pointer to user data object, passed as the last parameter to diff --git a/client/lib/script/DefineDefaultConfigVars.cmake b/client/lib/script/DefineDefaultConfigVars.cmake index f1da0ec7..9da5f31c 100644 --- a/client/lib/script/DefineDefaultConfigVars.cmake +++ b/client/lib/script/DefineDefaultConfigVars.cmake @@ -13,6 +13,9 @@ endmacro() set_default_var_value(CC_MQTTSN_CLIENT_CUSTOM_NAME "") set_default_var_value(CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC TRUE) set_default_var_value(CC_MQTTSN_CLIENT_ALLOC_LIMIT 0) +set_default_var_value(CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY TRUE) +set_default_var_value(CC_MQTTSN_CLIENT_GATEWAY_INFOS_MAX_LIMIT 0) +set_default_var_value(CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN 0) #set_default_var_value(CC_MQTTSN_CLIENT_STRING_FIELD_FIXED_LEN 0) #set_default_var_value(CC_MQTTSN_CLIENT_CLIENT_ID_FIELD_FIXED_LEN 0) #set_default_var_value(CC_MQTTSN_CLIENT_USERNAME_FIELD_FIXED_LEN 0) diff --git a/client/lib/script/WriteConfigHeader.cmake b/client/lib/script/WriteConfigHeader.cmake index 4df6c3ed..c2cf7c23 100644 --- a/client/lib/script/WriteConfigHeader.cmake +++ b/client/lib/script/WriteConfigHeader.cmake @@ -34,6 +34,7 @@ macro (adjust_bool_value name adjusted_name) endmacro () adjust_bool_value ("CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC" "CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC_CPP") +adjust_bool_value ("CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY" "CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY_CPP") adjust_bool_value ("CC_MQTTSN_CLIENT_HAS_ERROR_LOG" "CC_MQTTSN_CLIENT_HAS_ERROR_LOG_CPP") adjust_bool_value ("CC_MQTTSN_CLIENT_HAS_TOPIC_FORMAT_VERIFICATION" "CC_MQTTSN_CLIENT_HAS_TOPIC_FORMAT_VERIFICATION_CPP") adjust_bool_value ("CC_MQTTSN_CLIENT_HAS_SUB_TOPIC_VERIFICATION" "CC_MQTTSN_CLIENT_HAS_SUB_TOPIC_VERIFICATION_CPP") @@ -42,6 +43,9 @@ adjust_bool_value ("CC_MQTTSN_CLIENT_HAS_SUB_TOPIC_VERIFICATION" "CC_MQTTSN_CLIE replace_in_text (CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC_CPP) replace_in_text (CC_MQTTSN_CLIENT_ALLOC_LIMIT) +replace_in_text (CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY_CPP) +replace_in_text (CC_MQTTSN_CLIENT_GATEWAY_INFOS_MAX_LIMIT) +replace_in_text (CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN) #replace_in_text (CC_MQTTSN_CLIENT_STRING_FIELD_FIXED_LEN) #replace_in_text (CC_MQTTSN_CLIENT_BIN_DATA_FIELD_FIXED_LEN) replace_in_text (CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE) diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index fab9f6b5..4f708c72 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -10,6 +10,7 @@ #include "comms/cast.h" #include "comms/Assert.h" #include "comms/process.h" +#include "comms/units.h" #include "comms/util/ScopeGuard.h" #include @@ -21,37 +22,42 @@ namespace cc_mqttsn_client namespace { -// template -// unsigned eraseFromList(const op::Op* op, TList& list) -// { -// auto iter = -// std::find_if( -// list.begin(), list.end(), -// [op](auto& opPtr) -// { -// return op == opPtr.get(); -// }); - -// auto result = static_cast(std::distance(list.begin(), iter)); - -// COMMS_ASSERT(iter != list.end()); -// if (iter != list.end()) { -// list.erase(iter); -// } +template +unsigned eraseFromList(const op::Op* op, TList& list) +{ + auto iter = + std::find_if( + list.begin(), list.end(), + [op](auto& opPtr) + { + return op == opPtr.get(); + }); + + auto result = static_cast(std::distance(list.begin(), iter)); + + COMMS_ASSERT(iter != list.end()); + if (iter != list.end()) { + list.erase(iter); + } -// return result; -// } + return result; +} -// void updateEc(CC_MqttsnErrorCode* ec, CC_MqttsnErrorCode val) -// { -// if (ec != nullptr) { -// *ec = val; -// } -// } +void updateEc(CC_MqttsnErrorCode* ec, CC_MqttsnErrorCode val) +{ + if (ec != nullptr) { + *ec = val; + } +} } // namespace -ClientImpl::ClientImpl() = default; +ClientImpl::ClientImpl() : + m_gwDiscoveryTimer(m_timerMgr.allocTimer()) +{ + // TODO: check validity of timer in during intialization + static_cast(m_searchOpAlloc); +} ClientImpl::~ClientImpl() { @@ -64,6 +70,7 @@ void ClientImpl::tick(unsigned ms) { COMMS_ASSERT(m_apiEnterCount == 0U); ++m_apiEnterCount; + m_clientState.m_timestamp += ms; m_timerMgr.tick(ms); doApiExit(); } @@ -80,6 +87,67 @@ void ClientImpl::processData(const std::uint8_t* iter, unsigned len) } } +op::SearchOp* ClientImpl::searchPrepare(CC_MqttsnErrorCode* ec) +{ + if constexpr (Config::HasGatewayDiscovery) { + op::SearchOp* op = nullptr; + do { + if (!m_clientState.m_initialized) { + if (m_apiEnterCount > 0U) { + errorLog("Cannot prepare search from within callback"); + updateEc(ec, CC_MqttsnErrorCode_RetryLater); + break; + } + + auto initEc = initInternal(); + if (initEc != CC_MqttsnErrorCode_Success) { + updateEc(ec, initEc); + break; + } + } + + if (!m_searchOps.empty()) { + // Already allocated + errorLog("Another search operation is in progress."); + updateEc(ec, CC_MqttsnErrorCode_Busy); + break; + } + + if (m_ops.max_size() <= m_ops.size()) { + errorLog("Cannot start search operation, retry in next event loop iteration."); + updateEc(ec, CC_MqttsnErrorCode_RetryLater); + break; + } + + if (m_preparationLocked) { + errorLog("Another operation is being prepared, cannot prepare \"search\" without \"send\" or \"cancel\" of the previous."); + updateEc(ec, CC_MqttsnErrorCode_PreparationLocked); + break; + } + + auto ptr = m_searchOpAlloc.alloc(*this); + if (!ptr) { + errorLog("Cannot allocate new search operation."); + updateEc(ec, CC_MqttsnErrorCode_OutOfMemory); + break; + } + + m_preparationLocked = true; + m_ops.push_back(ptr.get()); + m_searchOps.push_back(std::move(ptr)); + op = m_searchOps.back().get(); + updateEc(ec, CC_MqttsnErrorCode_Success); + } while (false); + + return op; + } + else { + errorLog("Gateway discovery support for excluded from compilation"); + updateEc(ec, CC_MqttsnErrorCode_NotSupported); + return nullptr; + } +} + // op::ConnectOp* ClientImpl::connectPrepare(CC_MqttsnErrorCode* ec) // { // op::ConnectOp* connectOp = nullptr; @@ -369,6 +437,104 @@ void ClientImpl::processData(const std::uint8_t* iter, unsigned len) // return CC_MqttsnErrorCode_Success; // } +#if CC_MQTTSN_HAS_GATEWAY_DISCOVERY +void ClientImpl::handle(AdvertiseMsg& msg) +{ + static_assert(Config::HasGatewayDiscovery); + + m_gwDiscoveryTimer.cancel(); + + auto onExit = + comms::util::makeScopeGuard( + [this]() + { + monitorGatewayExpiry(); + }); + + auto iter = + std::find_if( + m_clientState.m_gwInfos.begin(), m_clientState.m_gwInfos.end(), + [&msg](auto& info) + { + return msg.field_gwId().value() == info.m_gwId; + }); + + if (iter != m_clientState.m_gwInfos.end()) { + iter->m_expiryTimestamp = m_clientState.m_timestamp + comms::units::getMilliseconds(msg.field_duration()); + return; + } + + if (m_clientState.m_gwInfos.max_size() <= m_clientState.m_gwInfos.size()) { + // Ignore new gateways if they cannot be stored + return; + } + + m_clientState.m_gwInfos.resize(m_clientState.m_gwInfos.size() + 1U); + auto& info = m_clientState.m_gwInfos.back(); + info.m_gwId = msg.field_gwId().value(); + info.m_expiryTimestamp = m_clientState.m_timestamp + comms::units::getMilliseconds(msg.field_duration()); + + if (m_gatewayStatusReportCb != nullptr) { + auto cbInfo = CC_MqttsnGatewayStatusInfo(); + cbInfo.m_gwId = info.m_gwId; + cbInfo.m_status = CC_MqttsnGwStatus_Available; + m_gatewayStatusReportCb(m_gatewayStatusReportData, &cbInfo); + } +} + +void ClientImpl::handle(GwinfoMsg& msg) +{ + auto onExit = + comms::util::makeScopeGuard( + [this, &msg]() + { + for (auto& op : m_searchOps) { + COMMS_ASSERT(op); + op->handle(msg); + } + }); + + auto iter = + std::find_if( + m_clientState.m_gwInfos.begin(), m_clientState.m_gwInfos.end(), + [&msg](auto& info) + { + return msg.field_gwId().value() == info.m_gwId; + }); + + if (iter != m_clientState.m_gwInfos.end()) { + auto& addr = msg.field_gwAdd().value(); + if (addr.empty()) { + return; + } + + if (iter->m_addr.max_size() < addr.size()) { + iter->m_addr.clear(); + return; + } + + iter->m_addr.assign(addr.begin(), addr.end()); + return; + } + + auto& addr = msg.field_gwAdd().value(); + if (addr.empty()) { + return; + } + + if (m_clientState.m_gwInfos.max_size() <= m_clientState.m_gwInfos.size()) { + // Not enough space + return; + } + + m_clientState.m_gwInfos.resize(m_clientState.m_gwInfos.size() + 1U); + auto& info = m_clientState.m_gwInfos.back(); + info.m_gwId = msg.field_gwId().value(); + info.m_addr.assign(addr.begin(), addr.end()); + // TODO: report gateway discovery +} +#endif // #if CC_MQTTSN_HAS_GATEWAY_DISCOVERY + // void ClientImpl::handle(PublishMsg& msg) // { // if (m_sessionState.m_disconnecting) { @@ -564,6 +730,7 @@ void ClientImpl::opComplete(const op::Op* op) using ExtraCompleteFunc = void (ClientImpl::*)(const op::Op*); static const ExtraCompleteFunc Map[] = { + /* Type_Search */ &ClientImpl::opComplete_Search, // /* Type_Connect */ &ClientImpl::opComplete_Connect, // /* Type_KeepAlive */ &ClientImpl::opComplete_KeepAlive, // /* Type_Disconnect */ &ClientImpl::opComplete_Disconnect, @@ -705,11 +872,11 @@ void ClientImpl::opComplete(const op::Op* op) // return false; // } -// void ClientImpl::allowNextPrepare() -// { -// COMMS_ASSERT(m_preparationLocked); -// m_preparationLocked = false; -// } +void ClientImpl::allowNextPrepare() +{ + COMMS_ASSERT(m_preparationLocked); + m_preparationLocked = false; +} void ClientImpl::doApiEnter() { @@ -724,6 +891,7 @@ void ClientImpl::doApiEnter() } auto elapsed = m_cancelNextTickWaitCb(m_cancelNextTickWaitData); + m_clientState.m_timestamp += elapsed; m_timerMgr.tick(elapsed); } @@ -813,36 +981,37 @@ void ClientImpl::errorLogInternal(const char* msg) } } -// CC_MqttsnErrorCode ClientImpl::initInternal() -// { -// auto guard = apiEnter(); -// if ((m_sendOutputDataCb == nullptr) || -// (m_brokerDisconnectReportCb == nullptr) || -// (m_messageReceivedReportCb == nullptr)) { -// errorLog("Hasn't set all must have callbacks"); -// return CC_MqttsnErrorCode_NotIntitialized; -// } +CC_MqttsnErrorCode ClientImpl::initInternal() +{ + auto guard = apiEnter(); -// bool hasTimerCallbacks = -// (m_nextTickProgramCb != nullptr) || -// (m_cancelNextTickWaitCb != nullptr); + if ((m_sendOutputDataCb == nullptr) || + (m_messageReceivedReportCb == nullptr)) { + errorLog("Hasn't set all must have callbacks"); + return CC_MqttsnErrorCode_NotIntitialized; + } -// if (hasTimerCallbacks) { -// bool hasAllTimerCallbacks = -// (m_nextTickProgramCb != nullptr) && -// (m_cancelNextTickWaitCb != nullptr); + bool hasTimerCallbacks = + (m_nextTickProgramCb != nullptr) || + (m_cancelNextTickWaitCb != nullptr); -// if (!hasAllTimerCallbacks) { -// errorLog("Hasn't set all timer management callbacks callbacks"); -// return CC_MqttsnErrorCode_NotIntitialized; -// } -// } + if (hasTimerCallbacks) { + bool hasAllTimerCallbacks = + (m_nextTickProgramCb != nullptr) && + (m_cancelNextTickWaitCb != nullptr); -// terminateOps(CC_MqttsnAsyncOpStatus_Aborted, TerminateMode_KeepSendRecvOps); -// m_sessionState = SessionState(); -// m_clientState.m_initialized = true; -// return CC_MqttsnErrorCode_Success; -// } + if (!hasAllTimerCallbacks) { + errorLog("Hasn't set all timer management callbacks callbacks"); + return CC_MqttsnErrorCode_NotIntitialized; + } + } + + // TODO: + // terminateOps(CC_MqttsnAsyncOpStatus_Aborted, TerminateMode_KeepSendRecvOps); + // m_sessionState = SessionState(); + m_clientState.m_initialized = true; + return CC_MqttsnErrorCode_Success; +} // void ClientImpl::resumeSendOpsSince(unsigned idx) // { @@ -947,6 +1116,11 @@ void ClientImpl::errorLogInternal(const char* msg) // return true; // } +void ClientImpl::opComplete_Search(const op::Op* op) +{ + eraseFromList(op, m_searchOps); +} + // void ClientImpl::opComplete_Connect(const op::Op* op) // { // eraseFromList(op, m_connectOps); @@ -987,4 +1161,65 @@ void ClientImpl::errorLogInternal(const char* msg) // resumeSendOpsSince(idx); // } +void ClientImpl::monitorGatewayExpiry() +{ + if constexpr (Config::HasGatewayDiscovery) { + auto iter = + std::min_element( + m_clientState.m_gwInfos.begin(), m_clientState.m_gwInfos.end(), + [](const auto& first, const auto& second) + { + if (first.m_expiryTimestamp == 0) { + return false; + } + + return first.m_expiryTimestamp < second.m_expiryTimestamp; + }); + + if ((iter == m_clientState.m_gwInfos.end()) || + (iter->m_expiryTimestamp == 0U)) { + return; + } + + COMMS_ASSERT(m_clientState.m_timestamp < iter->m_expiryTimestamp); + m_gwDiscoveryTimer.wait(iter->m_expiryTimestamp - m_clientState.m_timestamp, &ClientImpl::gwExpiryTimeoutCb, this); + } +} + +void ClientImpl::gwExpiryTimeout() +{ + if constexpr (Config::HasGatewayDiscovery) { + for (auto& info : m_clientState.m_gwInfos) { + if (m_clientState.m_timestamp < info.m_expiryTimestamp) { + continue; + } + + if (m_gatewayStatusReportCb != nullptr) { + auto cbInfo = CC_MqttsnGatewayStatusInfo(); + cbInfo.m_gwId = info.m_gwId; + cbInfo.m_status = CC_MqttsnGwStatus_TimedOut; + m_gatewayStatusReportCb(m_gatewayStatusReportData, &cbInfo); + } + } + + m_clientState.m_gwInfos.erase( + std::remove_if( + m_clientState.m_gwInfos.begin(), m_clientState.m_gwInfos.end(), + [this](auto& info) + { + return (info.m_expiryTimestamp <= m_clientState.m_timestamp); + }), + m_clientState.m_gwInfos.end()); + + monitorGatewayExpiry(); + } +} + +void ClientImpl::gwExpiryTimeoutCb(void* data) +{ + if constexpr (Config::HasGatewayDiscovery) { + reinterpret_cast(data)->gwExpiryTimeout(); + } +} + } // namespace cc_mqttsn_client diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index ab84fd40..9a6d8000 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -7,7 +7,7 @@ #pragma once -// #include "ClientState.h" +#include "ClientState.h" #include "ConfigState.h" #include "ExtConfig.h" #include "ObjAllocator.h" @@ -22,6 +22,7 @@ // #include "op/KeepAliveOp.h" #include "op/Op.h" // #include "op/RecvOp.h" +#include "op/SearchOp.h" // #include "op/SendOp.h" // #include "op/SubscribeOp.h" // #include "op/UnsubscribeOp.h" @@ -67,6 +68,7 @@ class ClientImpl final : public ProtMsgHandler // void notifyNetworkDisconnected(); // bool isNetworkDisconnected() const; + op::SearchOp* searchPrepare(CC_MqttsnErrorCode* ec); // op::ConnectOp* connectPrepare(CC_MqttsnErrorCode* ec); // op::DisconnectOp* disconnectPrepare(CC_MqttsnErrorCode* ec); // op::SubscribeOp* subscribePrepare(CC_MqttsnErrorCode* ec); @@ -140,7 +142,11 @@ class ClientImpl final : public ProtMsgHandler // -------------------- Message Handling ----------------------------- -// using Base::handle; + using Base::handle; +#if CC_MQTTSN_HAS_GATEWAY_DISCOVERY + virtual void handle(AdvertiseMsg& msg) override; + virtual void handle(GwinfoMsg& msg) override; +#endif // #if CC_MQTTSN_HAS_GATEWAY_DISCOVERY // virtual void handle(PublishMsg& msg) override; // #if CC_MQTTSN_CLIENT_MAX_QOS >= 1 @@ -166,7 +172,7 @@ class ClientImpl final : public ProtMsgHandler // void reportMsgInfo(const CC_MqttsnMessageInfo& info); // bool hasPausedSendsBefore(const op::SendOp* sendOp) const; // bool hasHigherQosSendsBefore(const op::SendOp* sendOp, op::Op::Qos qos) const; - // void allowNextPrepare(); + void allowNextPrepare(); TimerMgr& timerMgr() { @@ -183,15 +189,15 @@ class ClientImpl final : public ProtMsgHandler return m_configState; } - // ClientState& clientState() - // { - // return m_clientState; - // } + ClientState& clientState() + { + return m_clientState; + } - // const ClientState& clientState() const - // { - // return m_clientState; - // } + const ClientState& clientState() const + { + return m_clientState; + } // SessionState& sessionState() // { @@ -221,6 +227,9 @@ class ClientImpl final : public ProtMsgHandler // } private: + using SearchOpAlloc = ObjAllocator; + using SearchOpsList = ObjListType; + // using ConnectOpAlloc = ObjAllocator; // using ConnectOpsList = ObjListType; @@ -259,13 +268,14 @@ class ClientImpl final : public ProtMsgHandler // void terminateOps(CC_MqttsnAsyncOpStatus status, TerminateMode mode); void cleanOps(); void errorLogInternal(const char* msg); - // CC_MqttsnErrorCode initInternal(); + CC_MqttsnErrorCode initInternal(); // void resumeSendOpsSince(unsigned idx); // op::SendOp* findSendOp(std::uint16_t packetId); // bool isLegitSendAck(const op::SendOp* sendOp, bool pubcompAck = false) const; // void resendAllUntil(op::SendOp* sendOp); // bool processPublishAckMsg(ProtMessage& msg, std::uint16_t packetId, bool pubcompAck = false); + void opComplete_Search(const op::Op* op); // void opComplete_Connect(const op::Op* op); // void opComplete_KeepAlive(const op::Op* op); // void opComplete_Disconnect(const op::Op* op); @@ -274,6 +284,10 @@ class ClientImpl final : public ProtMsgHandler // void opComplete_Recv(const op::Op* op); // void opComplete_Send(const op::Op* op); + void monitorGatewayExpiry(); + void gwExpiryTimeout(); + static void gwExpiryTimeoutCb(void* data); + friend class ApiEnterGuard; CC_MqttsnNextTickProgramCb m_nextTickProgramCb = nullptr; @@ -301,17 +315,21 @@ class ClientImpl final : public ProtMsgHandler void* m_gwinfoDelayReqData = nullptr; ConfigState m_configState; - // ClientState m_clientState; + ClientState m_clientState; // SessionState m_sessionState; // ReuseState m_reuseState; TimerMgr m_timerMgr; + TimerMgr::Timer m_gwDiscoveryTimer; unsigned m_apiEnterCount = 0U; OutputBuf m_buf; ProtFrame m_frame; + SearchOpAlloc m_searchOpAlloc; + SearchOpsList m_searchOps; + // ConnectOpAlloc m_connectOpAlloc; // ConnectOpsList m_connectOps; @@ -335,7 +353,7 @@ class ClientImpl final : public ProtMsgHandler OpPtrsList m_ops; bool m_opsDeleted = false; - // bool m_preparationLocked = false; + bool m_preparationLocked = false; }; } // namespace cc_mqttsn_client diff --git a/client/lib/src/ClientState.h b/client/lib/src/ClientState.h new file mode 100644 index 00000000..d71ad9a0 --- /dev/null +++ b/client/lib/src/ClientState.h @@ -0,0 +1,47 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "ExtConfig.h" +#include "ObjListType.h" +#include "ProtocolDefs.h" + +#include "cc_mqttsn_client/common.h" + +#include + +namespace cc_mqttsn_client +{ + +struct ClientState +{ + using GwAddr = ObjListType; + using Timestamp = std::uint64_t; + + struct GwInfo + { + Timestamp m_expiryTimestamp = 0U; + GwAddr m_addr; + std::uint8_t m_gwId = 0; + }; + + using PacketIdsList = ObjListType; + using GwInfosList = ObjListType; + + static constexpr unsigned DefaultKeepAlive = 60; + + GwInfosList m_gwInfos; + PacketIdsList m_allocatedPacketIds; + Timestamp m_timestamp = 0U; + std::uint16_t m_lastPacketId = 0U; + bool m_initialized = false; + bool m_firstConnect = true; + // bool m_networkDisconnected = false; +}; + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/ExtConfig.h b/client/lib/src/ExtConfig.h index ca7c7156..3b6ec83c 100644 --- a/client/lib/src/ExtConfig.h +++ b/client/lib/src/ExtConfig.h @@ -16,8 +16,11 @@ namespace cc_mqttsn_client struct ExtConfig : public Config { - static constexpr unsigned ConnectOpsLimit = HasDynMemAlloc ? 0 : 1U; static constexpr unsigned KeepAliveOpsLimit = HasDynMemAlloc ? 0 : 1U; + static constexpr unsigned AdvertiseTimers = 1U; + static constexpr unsigned SearchOpsLimit = HasDynMemAlloc ? 0 : 1U; + static constexpr unsigned SearchOpTimers = 1U; + static constexpr unsigned ConnectOpsLimit = HasDynMemAlloc ? 0 : 1U; static constexpr unsigned ConnectOpTimers = 1U; static constexpr unsigned KeepAliveOpTimers = 3U; static constexpr unsigned DisconnectOpsLimit = HasDynMemAlloc ? 0 : 1U; @@ -29,6 +32,7 @@ struct ExtConfig : public Config static constexpr unsigned SendOpsLimit = SendMaxLimit == 0U ? 0U : SendMaxLimit + 1U; static constexpr unsigned SendOpTimers = 1U; static constexpr bool HasOpsLimit = + (SearchOpsLimit > 0U) && (ConnectOpsLimit > 0U) && (KeepAliveOpsLimit > 0U) && (DisconnectOpsLimit > 0U) && @@ -36,7 +40,9 @@ struct ExtConfig : public Config (UnsubscribeOpsLimit > 0U) && (RecvOpsLimit > 0U) && (SendOpsLimit > 0U); - static constexpr unsigned MaxTimersLimit = + static constexpr unsigned MaxTimersLimit = + (AdvertiseTimers) + + (SearchOpsLimit * SearchOpTimers) + (ConnectOpsLimit * ConnectOpTimers) + (KeepAliveOpsLimit * KeepAliveOpTimers) + (DisconnectOpsLimit * DisconnectOpTimers) + diff --git a/client/lib/src/ProtocolDefs.h b/client/lib/src/ProtocolDefs.h index c60de733..1865818d 100644 --- a/client/lib/src/ProtocolDefs.h +++ b/client/lib/src/ProtocolDefs.h @@ -40,8 +40,11 @@ CC_MQTTSN_ALIASES_FOR_ALL_MESSAGES(, Msg, ProtMessage, ProtocolOptions) using ProtInputMessages = std::tuple< +#if CC_MQTTSN_HAS_GATEWAY_DISCOVERY cc_mqttsn::message::Advertise, + cc_mqttsn::message::Searchgw, cc_mqttsn::message::Gwinfo, +#endif // CC_MQTTSN_HAS_GATEWAY_DISCOVERY cc_mqttsn::message::Connack, cc_mqttsn::message::Willtopicreq, cc_mqttsn::message::Willmsgreq, diff --git a/client/lib/src/op/Op.cpp b/client/lib/src/op/Op.cpp index 2521e9fc..de2af082 100644 --- a/client/lib/src/op/Op.cpp +++ b/client/lib/src/op/Op.cpp @@ -39,14 +39,14 @@ Op::Op(ClientImpl& client) : { } -void Op::sendMessage(const ProtMessage& msg, unsigned broadcastRadius) +void Op::terminateOpImpl([[maybe_unused]] CC_MqttsnAsyncOpStatus status) { - m_client.sendMessage(msg, broadcastRadius); + opComplete(); } -void Op::terminateOpImpl([[maybe_unused]] CC_MqttsnAsyncOpStatus status) +CC_MqttsnErrorCode Op::sendMessage(const ProtMessage& msg, unsigned broadcastRadius) { - opComplete(); + return m_client.sendMessage(msg, broadcastRadius); } void Op::opComplete() @@ -54,59 +54,65 @@ void Op::opComplete() m_client.opComplete(this); } -// std::uint16_t Op::allocPacketId() -// { -// static constexpr auto MaxPacketId = std::numeric_limits::max(); -// auto& allocatedPacketIds = m_client.clientState().m_allocatedPacketIds; +std::uint16_t Op::allocPacketId() +{ + static constexpr auto MaxPacketId = std::numeric_limits::max(); + auto& allocatedPacketIds = m_client.clientState().m_allocatedPacketIds; -// if ((allocatedPacketIds.max_size() <= allocatedPacketIds.size()) || -// (MaxPacketId <= allocatedPacketIds.size())) { -// errorLog("No more available packet IDs for allocation"); -// return 0U; -// } + if ((allocatedPacketIds.max_size() <= allocatedPacketIds.size()) || + (MaxPacketId <= allocatedPacketIds.size())) { + errorLog("No more available packet IDs for allocation"); + return 0U; + } -// auto& lastPacketId = m_client.clientState().m_lastPacketId; -// auto nextPacketId = static_cast(lastPacketId + 1U); + auto& lastPacketId = m_client.clientState().m_lastPacketId; + auto nextPacketId = static_cast(lastPacketId + 1U); -// if (nextPacketId == 0U) { -// nextPacketId = 1U; -// } + if (nextPacketId == 0U) { + nextPacketId = 1U; + } -// while (true) { -// if (allocatedPacketIds.empty() || (allocatedPacketIds.back() < nextPacketId)) { -// allocatedPacketIds.push_back(nextPacketId); -// break; -// } - -// auto iter = std::lower_bound(allocatedPacketIds.begin(), allocatedPacketIds.end(), nextPacketId); -// if ((iter == allocatedPacketIds.end()) || (*iter != nextPacketId)) { -// allocatedPacketIds.insert(iter, nextPacketId); -// break; -// } + while (true) { + if (allocatedPacketIds.empty() || (allocatedPacketIds.back() < nextPacketId)) { + allocatedPacketIds.push_back(nextPacketId); + break; + } + + auto iter = std::lower_bound(allocatedPacketIds.begin(), allocatedPacketIds.end(), nextPacketId); + if ((iter == allocatedPacketIds.end()) || (*iter != nextPacketId)) { + allocatedPacketIds.insert(iter, nextPacketId); + break; + } + + ++nextPacketId; + } + + lastPacketId = static_cast(nextPacketId); + return lastPacketId; +} -// ++nextPacketId; -// } +void Op::releasePacketId(std::uint16_t id) +{ + if (id == 0U) { + return; + } + + auto& allocatedPacketIds = m_client.clientState().m_allocatedPacketIds; + auto iter = std::lower_bound(allocatedPacketIds.begin(), allocatedPacketIds.end(), id); + if ((iter == allocatedPacketIds.end()) || (*iter != id)) { + [[maybe_unused]] static constexpr bool ShouldNotHappen = false; + COMMS_ASSERT(ShouldNotHappen); + return; + } -// lastPacketId = static_cast(nextPacketId); -// return lastPacketId; -// } + allocatedPacketIds.erase(iter); +} -// void Op::releasePacketId(std::uint16_t id) -// { -// if (id == 0U) { -// return; -// } - -// auto& allocatedPacketIds = m_client.clientState().m_allocatedPacketIds; -// auto iter = std::lower_bound(allocatedPacketIds.begin(), allocatedPacketIds.end(), id); -// if ((iter == allocatedPacketIds.end()) || (*iter != id)) { -// [[maybe_unused]] static constexpr bool ShouldNotHappen = false; -// COMMS_ASSERT(ShouldNotHappen); -// return; -// } - -// allocatedPacketIds.erase(iter); -// } +void Op::decRetryCount() +{ + COMMS_ASSERT(m_retryCount > 0U); + --m_retryCount; +} void Op::errorLogInternal(const char* msg) { diff --git a/client/lib/src/op/Op.h b/client/lib/src/op/Op.h index 26e0b867..910fe702 100644 --- a/client/lib/src/op/Op.h +++ b/client/lib/src/op/Op.h @@ -28,6 +28,7 @@ class Op : public ProtMsgHandler public: enum Type { + Type_Search, // Type_Connect, // Type_KeepAlive, // Type_Disconnect, @@ -60,7 +61,17 @@ class Op : public ProtMsgHandler void setResponseTimeout(unsigned ms) { m_responseTimeoutMs = ms; - } + } + + unsigned getRetryCount() const + { + return m_retryCount; + } + + void setRetryCount(unsigned value) + { + m_retryCount = value; + } inline static bool verifyQosValid(Qos qos) @@ -74,10 +85,11 @@ class Op : public ProtMsgHandler virtual Type typeImpl() const = 0; virtual void terminateOpImpl(CC_MqttsnAsyncOpStatus status); - void sendMessage(const ProtMessage& msg, unsigned broadcastRadius = 0U); + CC_MqttsnErrorCode sendMessage(const ProtMessage& msg, unsigned broadcastRadius = 0U); void opComplete(); - // std::uint16_t allocPacketId(); - // void releasePacketId(std::uint16_t id); + std::uint16_t allocPacketId(); + void releasePacketId(std::uint16_t id); + void decRetryCount(); ClientImpl& client() { diff --git a/client/lib/src/op/SearchOp.cpp b/client/lib/src/op/SearchOp.cpp new file mode 100644 index 00000000..027d24f2 --- /dev/null +++ b/client/lib/src/op/SearchOp.cpp @@ -0,0 +1,143 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "op/SearchOp.h" +#include "ClientImpl.h" + +#include "comms/util/assign.h" +#include "comms/util/ScopeGuard.h" +#include "comms/units.h" + +#include +#include + +namespace cc_mqttsn_client +{ + +namespace op +{ + +namespace +{ + +inline SearchOp* asSearchOp(void* data) +{ + return reinterpret_cast(data); +} + +} // namespace + + +SearchOp::SearchOp(ClientImpl& client) : + Base(client), + m_timer(client.timerMgr().allocTimer()), + m_radius(client.configState().m_broadcastRadius) +{ +} + +CC_MqttsnErrorCode SearchOp::send(CC_MqttsnSearchCompleteReportCb cb, void* cbData) +{ + client().allowNextPrepare(); + auto completeOnError = + comms::util::makeScopeGuard( + [this]() + { + opComplete(); + }); + + if (cb == nullptr) { + errorLog("Search completion callback is not provided."); + return CC_MqttsnErrorCode_BadParam; + } + + if (!m_timer.isValid()) { + errorLog("The library cannot allocate required number of timers."); + return CC_MqttsnErrorCode_InternalError; + } + + m_cb = cb; + m_cbData = cbData; + + return sendInternal(); +} + +CC_MqttsnErrorCode SearchOp::cancel() +{ + if (m_cb == nullptr) { + // hasn't been sent yet + client().allowNextPrepare(); + } + + opComplete(); + return CC_MqttsnErrorCode_Success; +} + +void SearchOp::handle(GwinfoMsg& msg) +{ + m_timer.cancel(); + + auto info = CC_MqttsnGatewayInfo(); + info.m_gwId = msg.field_gwId().value(); + auto& addr = msg.field_gwAdd().value(); + if (!addr.empty()) { + info.m_addr = addr.data(); + comms::cast_assign(info.m_addrLen) = addr.size(); + } + + // TODO: store gateway info + + completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &info); +} + +Op::Type SearchOp::typeImpl() const +{ + return Type_Search; +} + +void SearchOp::terminateOpImpl(CC_MqttsnAsyncOpStatus status) +{ + completeOpInternal(status); +} + +void SearchOp::completeOpInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info) +{ + auto cb = m_cb; + auto* cbData = m_cbData; + opComplete(); // mustn't access data members after destruction + if (cb != nullptr) { + cb(cbData, status, info); + } +} + +void SearchOp::restartTimer() +{ + m_timer.wait(getResponseTimeout(), &SearchOp::opTimeoutCb, this); +} + +CC_MqttsnErrorCode SearchOp::sendInternal() +{ + if (getRetryCount() == 0U) { + errorLog("All retries of the search operation have been exhausted."); + completeOpInternal(CC_MqttsnAsyncOpStatus_Timeout); + return CC_MqttsnErrorCode_InternalError; + } + + decRetryCount(); + + SearchgwMsg msg; + msg.field_radius().setValue(m_radius); + return sendMessage(msg, m_radius); +} + +void SearchOp::opTimeoutCb(void* data) +{ + asSearchOp(data)->sendInternal(); +} + +} // namespace op + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/op/SearchOp.h b/client/lib/src/op/SearchOp.h new file mode 100644 index 00000000..64942e4a --- /dev/null +++ b/client/lib/src/op/SearchOp.h @@ -0,0 +1,57 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "op/Op.h" +#include "ExtConfig.h" +#include "ProtocolDefs.h" + +#include "TimerMgr.h" + +namespace cc_mqttsn_client +{ + +namespace op +{ + +class SearchOp final : public Op +{ + using Base = Op; +public: + explicit SearchOp(ClientImpl& client); + + CC_MqttsnErrorCode send(CC_MqttsnSearchCompleteReportCb cb, void* cbData); + CC_MqttsnErrorCode cancel(); + + using Base::handle; + virtual void handle(GwinfoMsg& msg) override; + +protected: + virtual Type typeImpl() const override; + virtual void terminateOpImpl(CC_MqttsnAsyncOpStatus status) override; + +private: + void completeOpInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info = nullptr); + void restartTimer(); + CC_MqttsnErrorCode sendInternal(); + + static void opTimeoutCb(void* data); + + ConnectMsg m_connectMsg; + TimerMgr::Timer m_timer; + unsigned m_radius = 0U; + CC_MqttsnSearchCompleteReportCb m_cb = nullptr; + void* m_cbData = nullptr; + + static_assert(ExtConfig::SearchOpTimers == 1U); +}; + +} // namespace op + + +} // namespace cc_mqttsn_client diff --git a/client/lib/templ/Config.h.templ b/client/lib/templ/Config.h.templ index 9eda962a..d5b3ee7e 100644 --- a/client/lib/templ/Config.h.templ +++ b/client/lib/templ/Config.h.templ @@ -7,6 +7,9 @@ struct Config { static constexpr bool HasDynMemAlloc = ##CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC_CPP##; static constexpr unsigned ClientAllocLimit = ##CC_MQTTSN_CLIENT_ALLOC_LIMIT##; + static constexpr bool HasGatewayDiscovery = ##CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY_CPP##; + static constexpr unsigned GatewayInfoxMaxLimit = ##CC_MQTTSN_CLIENT_GATEWAY_INFOS_MAX_LIMIT##; + static constexpr unsigned GatewayAddrLen = ##CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN##; // static constexpr unsigned StringFieldFixedLen = ##CC_MQTTSN_CLIENT_STRING_FIELD_FIXED_LEN##; static constexpr unsigned MaxOutputPacketSize = ##CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE##; static constexpr unsigned ReceiveMaxLimit = ##CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT##; @@ -20,6 +23,8 @@ struct Config static constexpr unsigned MaxQos = ##CC_MQTTSN_CLIENT_MAX_QOS##; static_assert(HasDynMemAlloc || (ClientAllocLimit > 0U), "Must use CC_MQTTSN_CLIENT_ALLOC_LIMIT in configuration to limit number of clients"); + static_assert(HasDynMemAlloc || (!HasGatewayDiscovery) || (GatewayAddrLen > 0U), "Must use CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN in configuration to limit length of the gateway addr"); + static_assert(HasDynMemAlloc || (!HasGatewayDiscovery) || (GatewayInfoxMaxLimit > 0U), "Must use CC_MQTTSN_CLIENT_GATEWAY_INFOS_MAX_LIMIT in configuration to limit amount of gateways to store"); // static_assert(HasDynMemAlloc || (StringFieldFixedLen > 0U), "Must use CC_MQTTSN_CLIENT_STRING_FIELD_FIXED_LEN in configuration to limit string field length"); static_assert(HasDynMemAlloc || (MaxOutputPacketSize > 0U), "Must use CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE in configuration to limit packet size"); // static_assert(HasDynMemAlloc || (ReceiveMaxLimit > 0U) || (MaxQos < 2), "Must use CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT in configuration to limit amount of messages to receive"); @@ -33,6 +38,10 @@ struct Config } // namespace cc_mqttsn_client +#ifndef CC_MQTTSN_HAS_GATEWAY_DISCOVERY +#define CC_MQTTSN_HAS_GATEWAY_DISCOVERY ##CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY_CPP## +#endif // #ifndef CC_MQTTSN_HAS_GATEWAY_DISCOVERY + #ifndef CC_MQTTSN_CLIENT_MAX_QOS #define CC_MQTTSN_CLIENT_MAX_QOS ##CC_MQTTSN_CLIENT_MAX_QOS## -#endif \ No newline at end of file +#endif // #ifndef CC_MQTTSN_CLIENT_MAX_QOS From 32268f88be041cd82d094c00dde8a123fd0a8136 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 24 May 2024 17:03:26 +1000 Subject: [PATCH 034/106] Saving work on gateway discovery functionality. --- client/lib/include/cc_mqttsn_client/common.h | 21 +- client/lib/src/ClientImpl.cpp | 196 +++++++++++++------ client/lib/src/ClientImpl.h | 10 +- client/lib/src/ConfigState.h | 3 + client/lib/src/ExtConfig.h | 4 +- gateway/lib/src/session_op/Encapsulate.cpp | 1 + 6 files changed, 162 insertions(+), 73 deletions(-) diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index 74416bcc..bf5f6e0d 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -66,10 +66,11 @@ typedef enum /// @brief Status of the gateway typedef enum { - CC_MqttsnGwStatus_Invalid, ///< Invalid value, should never be used - CC_MqttsnGwStatus_Available, ///< The gateway is available. - CC_MqttsnGwStatus_TimedOut, ///< The gateway hasn't advertised its presence in time, assumed disconnected. - CC_MqttsnGwStatus_Discarded ///< The gateway info was discarded using cc_mqttsn_client_discard_gw() or cc_mqttsn_client_discard_all_gw(). + CC_MqttsnGwStatus_AddedByGateway, ///< Added by the @b ADVERTISE or @b GWINFO sent by the gateway messages + CC_MqttsnGwStatus_AddedByClient, ///< Added by the @b GWINFO message sent by another client. + CC_MqttsnGwStatus_UpdatedByClient, ///< The gateway's address was updated by another client. + CC_MqttsnGwStatus_Alive, ///< The @b ADVERTISE or @b GWINFO message have been received from the gateway indicating it's alive. + CC_MqttsnGwStatus_Removed, ///< The gateway hasn't advertised its presence in time, assumed no longer available. } CC_MqttsnGwStatus; /// @brief Status of the asynchronous operation @@ -124,13 +125,6 @@ typedef struct bool retain; ///< Retain flag of the message. } CC_MqttsnMessageInfo; -/// @brief Tracked gateway status information -typedef struct -{ - unsigned char m_gwId; ///< Gateway ID - CC_MqttsnGwStatus m_status; ///< Gateway status -} CC_MqttsnGatewayStatusInfo; - /// @brief Gateway information typedef struct { @@ -175,8 +169,9 @@ typedef void (*CC_MqttsnSendOutputDataCb)(void* data, const unsigned char* buf, /// cc_mqttsn_client_set_gw_status_report_callback() function. /// @param[in] data Pointer to user data object, passed as last parameter to /// cc_mqttsn_client_set_gw_status_report_callback() function. -/// @param[in] info Gateway status info. -typedef void (*CC_MqttsnGwStatusReportCb)(void* data, const CC_MqttsnGatewayStatusInfo* info); +/// @param[in] status Current status of the gateway. +/// @param[in] info Currently stored gateway information. +typedef void (*CC_MqttsnGwStatusReportCb)(void* data, CC_MqttsnGwStatus status, const CC_MqttsnGatewayInfo* info); /// @brief Callback used to report unsolicited disconnection of the gateway. /// @param[in] data Pointer to user data object, passed as the last parameter to diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 4f708c72..160cfc61 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -12,6 +12,7 @@ #include "comms/process.h" #include "comms/units.h" #include "comms/util/ScopeGuard.h" +#include "comms/util/assign.h" #include #include @@ -53,7 +54,8 @@ void updateEc(CC_MqttsnErrorCode* ec, CC_MqttsnErrorCode val) } // namespace ClientImpl::ClientImpl() : - m_gwDiscoveryTimer(m_timerMgr.allocTimer()) + m_gwDiscoveryTimer(m_timerMgr.allocTimer()), + m_sendGwinfoTimer(m_timerMgr.allocTimer()) { // TODO: check validity of timer in during intialization static_cast(m_searchOpAlloc); @@ -461,11 +463,13 @@ void ClientImpl::handle(AdvertiseMsg& msg) if (iter != m_clientState.m_gwInfos.end()) { iter->m_expiryTimestamp = m_clientState.m_timestamp + comms::units::getMilliseconds(msg.field_duration()); + reportGwStatus(CC_MqttsnGwStatus_Alive, *iter); return; } if (m_clientState.m_gwInfos.max_size() <= m_clientState.m_gwInfos.size()) { // Ignore new gateways if they cannot be stored + errorLog("Failed to store the new gateway information, due to insufficient storage"); return; } @@ -474,25 +478,38 @@ void ClientImpl::handle(AdvertiseMsg& msg) info.m_gwId = msg.field_gwId().value(); info.m_expiryTimestamp = m_clientState.m_timestamp + comms::units::getMilliseconds(msg.field_duration()); - if (m_gatewayStatusReportCb != nullptr) { - auto cbInfo = CC_MqttsnGatewayStatusInfo(); - cbInfo.m_gwId = info.m_gwId; - cbInfo.m_status = CC_MqttsnGwStatus_Available; - m_gatewayStatusReportCb(m_gatewayStatusReportData, &cbInfo); - } + reportGwStatus(CC_MqttsnGwStatus_AddedByGateway, info); +} + +void ClientImpl::handle(SearchgwMsg& msg) +{ + static_assert(Config::HasGatewayDiscovery); + if (m_gwinfoDelayReqCb == nullptr) { + // The application didn't provide a callback to inquire about the delay for resonditing to SEARCHGW + return; + } + + // TODO: check active + + auto delay = m_gwinfoDelayReqCb(m_gwinfoDelayReqData); + if (delay == 0U) { + // The application rejected sending GWINFO on behalf of gateway + return; + } + + m_pendingGwinfoBroadcastRadius = msg.field_radius().value(); + m_sendGwinfoTimer.wait(delay, &ClientImpl::sendGwinfoCb, this); } void ClientImpl::handle(GwinfoMsg& msg) { - auto onExit = - comms::util::makeScopeGuard( - [this, &msg]() - { - for (auto& op : m_searchOps) { - COMMS_ASSERT(op); - op->handle(msg); - } - }); + static_assert(Config::HasGatewayDiscovery); + m_sendGwinfoTimer.cancel(); // Do not send GWINFO if pending + + for (auto& op : m_searchOps) { + COMMS_ASSERT(op); + op->handle(msg); + } auto iter = std::find_if( @@ -509,21 +526,30 @@ void ClientImpl::handle(GwinfoMsg& msg) } if (iter->m_addr.max_size() < addr.size()) { - iter->m_addr.clear(); + errorLog("The gateway address reported by the client doesn't fit into the dedicated address storage, ignoring"); + return; + } + + if ((addr.size() == iter->m_addr.size()) && + (std::equal(addr.begin(), addr.end(), iter->m_addr.begin()))) { + // The address is already recorded. return; } iter->m_addr.assign(addr.begin(), addr.end()); + reportGwStatus(CC_MqttsnGwStatus_UpdatedByClient, *iter); return; } auto& addr = msg.field_gwAdd().value(); if (addr.empty()) { + reportGwStatus(CC_MqttsnGwStatus_Alive, *iter); return; } if (m_clientState.m_gwInfos.max_size() <= m_clientState.m_gwInfos.size()) { // Not enough space + errorLog("Failed to store the new gateway information, due to insufficient storage"); return; } @@ -531,7 +557,10 @@ void ClientImpl::handle(GwinfoMsg& msg) auto& info = m_clientState.m_gwInfos.back(); info.m_gwId = msg.field_gwId().value(); info.m_addr.assign(addr.begin(), addr.end()); - // TODO: report gateway discovery + info.m_expiryTimestamp = m_clientState.m_timestamp + m_configState.m_gwAdvTimeoutMs; + monitorGatewayExpiry(); + + reportGwStatus(CC_MqttsnGwStatus_AddedByClient, info); } #endif // #if CC_MQTTSN_HAS_GATEWAY_DISCOVERY @@ -658,36 +687,37 @@ void ClientImpl::handle(GwinfoMsg& msg) // #endif // #if CC_MQTTSN_CLIENT_MAX_QOS >= 2 -// void ClientImpl::handle(ProtMessage& msg) -// { -// static_cast(msg); -// if (m_sessionState.m_disconnecting) { -// return; -// } +void ClientImpl::handle(ProtMessage& msg) +{ + static_cast(msg); + // if (m_sessionState.m_disconnecting) { + // return; + // } -// // During the dispatch to callbacks can be called and new ops issues, -// // the m_ops vector can be resized and iterators invalidated. -// // As the result, the iteration needs to be performed using indices -// // instead of iterators. -// // Also do not dispatch the message to new ops. -// auto count = m_ops.size(); -// for (auto idx = 0U; idx < count; ++idx) { -// auto* op = m_ops[idx]; -// if (op == nullptr) { -// // ops can be deleted, but the pointer will be nullified -// // until last api guard. -// continue; -// } + // During the dispatch to callbacks can be called and new ops issues, + // the m_ops vector can be resized and iterators invalidated. + // As the result, the iteration needs to be performed using indices + // instead of iterators. + // Also do not dispatch the message to new ops. + auto count = m_ops.size(); + for (auto idx = 0U; idx < count; ++idx) { + auto* op = m_ops[idx]; + if (op == nullptr) { + // ops can be deleted, but the pointer will be nullified + // until last api guard. + continue; + } -// msg.dispatch(*op); + msg.dispatch(*op); -// // After message dispatching the whole session may be in terminating state -// // Don't continue iteration -// if (m_sessionState.m_disconnecting) { -// break; -// } -// } -// } + // After message dispatching the whole session may be in terminating state + // Don't continue iteration + + // if (m_sessionState.m_disconnecting) { + // break; + // } + } +} CC_MqttsnErrorCode ClientImpl::sendMessage(const ProtMessage& msg, unsigned broadcastRadius) { @@ -1169,15 +1199,13 @@ void ClientImpl::monitorGatewayExpiry() m_clientState.m_gwInfos.begin(), m_clientState.m_gwInfos.end(), [](const auto& first, const auto& second) { - if (first.m_expiryTimestamp == 0) { - return false; - } + COMMS_ASSERT(first.m_expiryTimestamp != 0); + COMMS_ASSERT(second.m_expiryTimestamp != 0); return first.m_expiryTimestamp < second.m_expiryTimestamp; }); - if ((iter == m_clientState.m_gwInfos.end()) || - (iter->m_expiryTimestamp == 0U)) { + if (iter == m_clientState.m_gwInfos.end()) { return; } @@ -1194,12 +1222,7 @@ void ClientImpl::gwExpiryTimeout() continue; } - if (m_gatewayStatusReportCb != nullptr) { - auto cbInfo = CC_MqttsnGatewayStatusInfo(); - cbInfo.m_gwId = info.m_gwId; - cbInfo.m_status = CC_MqttsnGwStatus_TimedOut; - m_gatewayStatusReportCb(m_gatewayStatusReportData, &cbInfo); - } + reportGwStatus(CC_MqttsnGwStatus_Removed, info); } m_clientState.m_gwInfos.erase( @@ -1215,6 +1238,60 @@ void ClientImpl::gwExpiryTimeout() } } +void ClientImpl::reportGwStatus(CC_MqttsnGwStatus status, const ClientState::GwInfo& info) +{ + if constexpr (Config::HasGatewayDiscovery) { + if (m_gatewayStatusReportCb == nullptr) { + return; + } + + auto gwInfo = CC_MqttsnGatewayInfo(); + gwInfo.m_gwId = info.m_gwId; + gwInfo.m_addr = info.m_addr.data(); + comms::cast_assign(gwInfo.m_addrLen) = info.m_addr.size(); + + m_gatewayStatusReportCb(m_gatewayStatusReportData, status, &gwInfo); + } +} + +void ClientImpl::sendGwinfo() +{ + if constexpr (Config::HasGatewayDiscovery) { + auto iter = + std::max_element( + m_clientState.m_gwInfos.begin(), m_clientState.m_gwInfos.end(), + [](auto& first, auto& second) + { + if (first.m_addr.empty()) { + // Prefer one with the address + return !second.m_addr.empty(); + } + + if (second.m_addr.empty()) { + return false; + } + + return first.m_expiryTimestamp < second.m_expiryTimestamp; + }); + + if ((iter == m_clientState.m_gwInfos.end()) || + (iter->m_addr.empty())) { + // None of the gateways have known address + return; + } + + GwinfoMsg msg; + if (msg.field_gwAdd().value().max_size() < iter->m_addr.size()) { + errorLog("Cannot fit the known gateway address into the GWINFO message address storage"); + return; + } + + msg.field_gwId().setValue(iter->m_gwId); + comms::util::assign(msg.field_gwAdd().value(), iter->m_addr.begin(), iter->m_addr.end()); + sendMessage(msg, std::max(m_pendingGwinfoBroadcastRadius, 1U)); + } +} + void ClientImpl::gwExpiryTimeoutCb(void* data) { if constexpr (Config::HasGatewayDiscovery) { @@ -1222,4 +1299,11 @@ void ClientImpl::gwExpiryTimeoutCb(void* data) } } +void ClientImpl::sendGwinfoCb(void* data) +{ + if constexpr (Config::HasGatewayDiscovery) { + reinterpret_cast(data)->sendGwinfo(); + } +} + } // namespace cc_mqttsn_client diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index 9a6d8000..1e31e9c7 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -145,6 +145,7 @@ class ClientImpl final : public ProtMsgHandler using Base::handle; #if CC_MQTTSN_HAS_GATEWAY_DISCOVERY virtual void handle(AdvertiseMsg& msg) override; + virtual void handle(SearchgwMsg& msg) override; virtual void handle(GwinfoMsg& msg) override; #endif // #if CC_MQTTSN_HAS_GATEWAY_DISCOVERY // virtual void handle(PublishMsg& msg) override; @@ -159,7 +160,7 @@ class ClientImpl final : public ProtMsgHandler // virtual void handle(PubcompMsg& msg) override; // #endif // #if CC_MQTTSN_CLIENT_MAX_QOS >= 2 -// virtual void handle(ProtMessage& msg) override; + virtual void handle(ProtMessage& msg) override; // -------------------- Ops Access API ----------------------------- @@ -286,7 +287,11 @@ class ClientImpl final : public ProtMsgHandler void monitorGatewayExpiry(); void gwExpiryTimeout(); + void reportGwStatus(CC_MqttsnGwStatus status, const ClientState::GwInfo& info); + void sendGwinfo(); + static void gwExpiryTimeoutCb(void* data); + static void sendGwinfoCb(void* data); friend class ApiEnterGuard; @@ -317,10 +322,10 @@ class ClientImpl final : public ProtMsgHandler ConfigState m_configState; ClientState m_clientState; // SessionState m_sessionState; - // ReuseState m_reuseState; TimerMgr m_timerMgr; TimerMgr::Timer m_gwDiscoveryTimer; + TimerMgr::Timer m_sendGwinfoTimer; unsigned m_apiEnterCount = 0U; OutputBuf m_buf; @@ -352,6 +357,7 @@ class ClientImpl final : public ProtMsgHandler // SendOpsList m_sendOps; OpPtrsList m_ops; + unsigned m_pendingGwinfoBroadcastRadius = 0U; bool m_opsDeleted = false; bool m_preparationLocked = false; }; diff --git a/client/lib/src/ConfigState.h b/client/lib/src/ConfigState.h index bc26662e..97ef1275 100644 --- a/client/lib/src/ConfigState.h +++ b/client/lib/src/ConfigState.h @@ -17,11 +17,14 @@ struct ConfigState static constexpr unsigned DefaultResponseTimeoutMs = 10000; static constexpr unsigned DefaultRetryCount = 3U; static constexpr unsigned DefaultBroadcastRadius = 3U; + static constexpr unsigned DefaultGwAdvTimeoutMs = 15 * 60 * 1000; // static constexpr unsigned MaxBroadcastRadius = 255U; + unsigned m_responseTimeoutMs = DefaultResponseTimeoutMs; unsigned m_retryCount = DefaultRetryCount; unsigned m_broadcastRadius = DefaultBroadcastRadius; + unsigned m_gwAdvTimeoutMs = DefaultGwAdvTimeoutMs; // CC_MqttsnPublishOrdering m_publishOrdering = CC_MqttsnPublishOrdering_SameQos; // bool m_verifyOutgoingTopic = Config::HasTopicFormatVerification; // bool m_verifyIncomingTopic = Config::HasTopicFormatVerification; diff --git a/client/lib/src/ExtConfig.h b/client/lib/src/ExtConfig.h index 3b6ec83c..90b75f5c 100644 --- a/client/lib/src/ExtConfig.h +++ b/client/lib/src/ExtConfig.h @@ -17,7 +17,7 @@ namespace cc_mqttsn_client struct ExtConfig : public Config { static constexpr unsigned KeepAliveOpsLimit = HasDynMemAlloc ? 0 : 1U; - static constexpr unsigned AdvertiseTimers = 1U; + static constexpr unsigned DiscoveryTimers = 2U; static constexpr unsigned SearchOpsLimit = HasDynMemAlloc ? 0 : 1U; static constexpr unsigned SearchOpTimers = 1U; static constexpr unsigned ConnectOpsLimit = HasDynMemAlloc ? 0 : 1U; @@ -41,7 +41,7 @@ struct ExtConfig : public Config (RecvOpsLimit > 0U) && (SendOpsLimit > 0U); static constexpr unsigned MaxTimersLimit = - (AdvertiseTimers) + + (DiscoveryTimers) + (SearchOpsLimit * SearchOpTimers) + (ConnectOpsLimit * ConnectOpTimers) + (KeepAliveOpsLimit * KeepAliveOpTimers) + diff --git a/gateway/lib/src/session_op/Encapsulate.cpp b/gateway/lib/src/session_op/Encapsulate.cpp index 769b05a3..b26f3823 100644 --- a/gateway/lib/src/session_op/Encapsulate.cpp +++ b/gateway/lib/src/session_op/Encapsulate.cpp @@ -104,6 +104,7 @@ void Encapsulate::handle(FwdMsg_SN& msg) if ((!sessionPtr->isRunning()) && (!sessionPtr->start())) { // Error failed to start session; + session().reportError("Failed to start forward encapsulated session"); session().reportFwdEncSessionDeleted(sessionPtr); m_sessions.erase(iter); iter = m_sessions.end(); From a5134f99a02fd5658155e3a4957aab6d10938126 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sat, 25 May 2024 14:56:38 +1000 Subject: [PATCH 035/106] Starting write of the doxygen documentation. --- client/lib/doxygen/main.dox | 284 +++++++++++++++++++ client/lib/include/cc_mqttsn_client/common.h | 51 ++-- client/lib/src/ClientImpl.cpp | 46 ++- client/lib/src/op/SearchOp.cpp | 2 +- client/lib/src/op/SearchOp.h | 4 +- client/lib/templ/client.cpp.templ | 48 +++- client/lib/templ/client.h.templ | 96 +++++-- 7 files changed, 466 insertions(+), 65 deletions(-) diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index e69de29b..8c3f4c18 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -0,0 +1,284 @@ +/// @mainpage MQTT-SN Client Library +/// @tableofcontents +/// @section doc_cc_mqttsn_client_overview Overview +/// The MQTT-SN Client Library from the CommsChampion Ecosystem +/// provides simple, asynchronous, non-blocking, +/// and easy to use interface to operate MQTT-SN client. The library doesn't +/// make any assumption on the system it is running on, as well as on the type +/// of I/O link being used to communicate its data to the MQTT-SN gateway. +/// +/// It is a responsibility of the calling application to manage network connectivity +/// as well as measure time required for the correct operation of the MQTT-SN protocol. +/// +/// The library allows the application to have a full control over the raw data for +/// any extra analysis and/or manipulation, such as encryption or extra framing. +/// +/// @section doc_cc_mqttsn_client_version Version of the Library +/// The version is of the library applicable to this documentation is defined in +/// the @ref common.h "cc_mqttsn_client/common.h" file using the following defines: +/// @li @ref CC_MQTTSN_CLIENT_MAJOR_VERSION +/// @li @ref CC_MQTTSN_CLIENT_MINOR_VERSION +/// @li @ref CC_MQTTSN_CLIENT_PATCH_VERSION +/// +/// @section cc_mqttsn_client_header Header +/// To use this MQTT-SN Client Library use the following single include statement: +/// @code +/// #include "cc_mqttsn_client/client.h" +/// @endcode +/// +/// @section doc_cc_mqttsn_client_allocation Client Allocation +/// The library supports multiple independent MQTT-SN client sessions. The +/// allocation of data structures relevant to a single client is performed +/// using @b cc_mqttsn_client_alloc() function. +/// @code +/// CC_MqttsnClientHandle client = cc_mqttsn_client_alloc(); +/// @endcode +/// All other functions are client specific, they receive the returned handle +/// as their first parameter. +/// +/// When work with allocated client is complete, it must be freed using +/// @b cc_mqttsn_client_free() function. +/// @code +/// cc_mqttsn_client_free(client); +/// @endcode +/// +/// When working with C++ it is advised to use a smart pointer with a custom deleter. +/// @code +/// struct MyDeleter +/// { +/// void operator()(CC_MqttsnClient* ptr) +/// { +/// ::cc_mqttsn_client_free(ptr); +/// } +/// }; +/// +/// using MyClientPtr = std::unique_ptr; +/// +/// MyClientPtr clientPtr(::cc_mqttsn_client_alloc()); +/// CC_MqttsnClientHandle client = clientPtr.get(); +/// @endcode +/// +/// @b IMPORTANT: The function @b cc_mqttsn_client_free() must @b NOT +/// be called from within a callback. Use next event loop iteration. +/// +/// @section doc_cc_mqttsn_client_callbacks "Must Have" Callbacks Registration +/// In order to properly function the library requires setting several callbacks. +/// +/// @subsection doc_cc_mqttsn_client_callbacks_send_data Sending Data To Gateway +/// To client application must assign a callback for the library to be able to send +/// binary data out to the connected gateway. +/// @code +/// void my_send_data_cb(void* data, const unsigned char* buf, unsigned bufLen, unsigned broadcastRadius) +/// { +/// if (broadcastRadius > 0) { +/// ... // broadcast the message data +/// } +/// else { +/// ... // unicast the message data to a known gateway address +/// } +/// } +/// +/// cc_mqttsn_client_set_send_output_data_callback(client, &my_send_data_cb, data); +/// @endcode +/// See also the documentation of the @ref CC_MqttsnSendOutputDataCb callback function definition. +/// +/// In the invoked callback the application is responsible to send the provided data +/// over the I/O link. The application can also perform extra data manipulation like +/// encryption. +/// +/// The reported data resides in internal data structures of the client library, +/// which can be updated / deleted right after the callback function returns. +/// It means the data may need to be copied into some other buffer, which will be +/// held intact until the send over I/O link operation is complete. +/// +/// @subsection doc_cc_mqttsn_client_callbacks_message Reporting Received Message +/// The client application must assign a callback for the library to report +/// application level messages received from the gateway. +/// @code +/// void my_message_received_cb(void* data, const CC_MqttsnMessageInfo* info) +/// { +/// ... /* handle the received message */ +/// } +/// +/// cc_mqttsn_client_set_message_report_callback(client, &my_message_received_cb, data); +/// @endcode +/// See also the documentation of the @ref CC_MqttsnMessageReportCb callback function definition. +/// +/// @section doc_cc_mqttsn_client_time Time Measurement +/// For the correct operation of the MQTT-SN client side of the protocol, the library +/// requires an ability to measure time. This responsibility is delegated to the +/// application. +/// +/// The easiest (although not very efficient or very precise) method is to periodically (say every 20ms - 50ms) +/// call the @b cc_mqttsn_client_tick() function reporting the amount of elapsed milliseconds: +/// @code +/// cc_mqttsn_client_tick(client, 50U); +/// @endcode +/// The library will check if some inner timer has expired and may initiate some +/// response via invocation one of the registered callbacks. +/// +/// Another (recommended) method is to register a callback so the library +/// may request the time measurement from the application, and when the +/// requested time expires, the application is expected to call the +/// @b cc_mqttsn_client_tick() function reporting amount of elapsed milliseconds. +/// @code +/// void my_tick_program_cb(void* data, unsigned ms) +/// { +/// ... // program appropriate timer +/// } +/// +/// cc_mqttsn_client_set_next_tick_program_callback(client, &my_tick_program_cb, data); +/// @endcode +/// It is allowed to invoke the @b cc_mqttsn_client_tick() before the actual requested timeout has +/// expired, just make sure that the correct amount of elapsed milliseconds is reported. When +/// the @b cc_mqttsn_client_tick() is invoked, it is assumed that the previously requested tick +/// programming has been cancelled and the registered callback requesting to re-program +/// the timer may be invoked again from within the +/// @b cc_mqttsn_client_tick(). +/// +/// See also the documentation of the @ref CC_MqttsnNextTickProgramCb callback function definition. +/// +/// In case of callback approach for time measurement is chosen, another callback function +/// (in addition to requesting the new timer programming) to +/// allow interruption of the previously programmed timer must also to be registered. +/// @code +/// unsigned my_cancel_tick_program_cb(void* data) +/// { +/// ... +/// return ... // return amount of elapsed milliseconds since last tick program +/// } +/// +/// cc_mqttsn_client_set_cancel_next_tick_wait_callback(client, &my_cancel_tick_program_cb, data); +/// @endcode +/// See also the documentation of the @ref CC_MqttsnCancelNextTickWaitCb callback function definition. +/// +/// Usually the callbacks of canceling the previously programmed tick and programming a new one +/// will be invoked as a side effect of other events, like report of the incoming data or +/// client requesting to perform one of the available operations. +/// +/// @section doc_cc_mqttsn_client_log Error Logging +/// Sometimes the library may exhibit unexpected behaviour, like rejecting some of the parameters. +/// To allow getting extra guidance information of what went wrong it is possible to register +/// optional error logging callback. +/// @code +/// void my_error_log_cb(void* data, const char* msg) +/// { +/// printf("ERROR: %s\n", msg); +/// } +/// +/// cc_mqttsn_client_set_error_log_callback(client, &my_error_log_cb, data); +/// @endcode +/// See also the documentation of the @ref CC_MqttsnErrorLogCb callback function definition. +/// +/// @section doc_cc_mqttsn_client_data Reporting Incoming Data +/// It is the responsibility of the application to receive data from the gateway +/// and report it to the library. The report is performed using the +/// @b cc_mqttsn_client_process_data() function. +/// @code +/// uint8_t buf[MAX_BUF_SIZE]; +/// ... // Receive data into buffer +/// unsigned bufLen = ...; // Amount of received bytes in the buffer. +/// cc_mqttsn_client_process_data(client, buf, bufLen); +/// @endcode +/// The application is responsible to maintain the input buffer. +/// The MQTT-SN protocol assumes datagram packets, where each packets +/// contains a valid single MQTT-SN message. The client library decodes +/// and processes the message at the beginning of the reported buffer and +/// discards any extra bytes that might follow. +/// +/// When new data chunk is reported the library may invoke several callbacks, +/// such as reporting received message, sending new data out, as well as canceling +/// the old and programming new tick timeout. +/// +/// @section doc_cc_mqttsn_client_concepts Operating Concepts +/// The library abstracts away multiple MQTT-SN protocol based "operations". Every such operation +/// has multiple stages: +/// @li @b prepare - The operation is "allocated" and relevant handle is returned. +/// @li @b configure - Apply one or multiple configurations to the prepared operation. +/// @li @b send - Send the configured operation message to the gateway. +/// +/// During the @b send stage the application is expected to provide the callback to +/// report to the application when the operation is complete. One of the parameters +/// of the callback is always "status" of the @ref CC_MqttsnAsyncOpStatus type. It +/// indicates whether the operation was successfully complete. In addition to the +/// status it reports some extra information reported by the gateway. The information +/// from the gateway is available if and only if the status is +/// @ref CC_MqttsnAsyncOpStatus_Complete. +/// +/// The @b send stage function also returns @ref CC_MqttsnErrorCode value to indicate +/// whether the @b send was successfully performed. The provided callback will +/// be invoked if and only if the @b send returns @ref CC_MqttsnErrorCode_Success. +/// +/// After the @b send stage the handle returned in the @b prepare stage can be discarded +/// (no explicit de-allocation is needed / supported) regardless of the return code. +/// After successful @b send the handle still remains valid until the callback invocation and can be used +/// to @b cancel the operation. Note that in case the appropriate message has already be +/// @b sent to the gateway, cancelling the outstanding operation can be dangerous. When +/// gateway sends a response and client is not expecting it any more, unexpected +/// behaviour may happen. +/// +/// In case something went wrong during the @b configure stage, it is possible to de-allocate +/// the prepared operation using the @b cancel request. After performing the @b cancel +/// stage the allocated handle is no longer valid. +/// +/// IMPORTANT LIBRARY LIMITATION: Once an operation is @b prepared, +/// it must be be immediately @b configured and @b sent (or @b cancelled) before +/// any other other operation can be @b prepared. For example: +/// @code +/// CC_MqttsnErrorCode ec = CC_MqttsnErrorCode_Success; +/// CC_MqttsnConnectHandle connect = cc_mqttsn_client_connect_prepare(...); +/// assert(connect != nullptr); +/// +/// // The following attempt to prepare the "subscribe" operation will fail because +/// // previously allocated "connect" hasn't been sent or cancelled yet. +/// CC_MqttsnSubscribeHandle subscribe = cc_mqttsn_client_subscribe_prepare(...); +/// assert(subscribe == NULL); +/// @endcode +/// +/// @section doc_cc_mqttsn_client_retry_period Default Retry Period +/// After sending any operation request to the gateway, the client library has to allow +/// some time for the gateway to process the request (@b Tretry from the spec). +/// If it takes too much time, the +/// client must retry it several times (see @ref doc_cc_mqttsn_client_retry_count) +/// and report that operation has failed via the set callback in case there is no response. +/// By default the client +/// library allows @b 10 seconds for such response to arrive. Changing this default value +/// is possible using the @b cc_mqttsn_client_set_default_retry_period() function, +/// and retrieving of the currently configured value can be done using the +/// @b cc_mqttsn_client_get_default_retry_period() function. +/// @code +/// CC_MqttsnErrorCode ec = cc_mqttsn_client_set_default_retry_period(client, 15000 /* in ms */); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// ... /* Something went wrong */ +/// } +/// @endcode +/// +/// @section doc_cc_mqttsn_client_retry_count Default Retry Count +/// As was mentioned in the @ref doc_cc_mqttsn_client_retry_period section above +/// the client library retries to send unacknowledged messaged several times +/// before giving up (@b Nretry from the spec). +/// By default the client library allows @b 3 times to re-send the message. Changing this default value +/// is possible using the @b cc_mqttsn_client_set_default_retry_count() function, +/// and retrieving of the currently configured value can be done using the +/// @b cc_mqttsn_client_get_default_retry_count() function. +/// @code +/// CC_MqttsnErrorCode ec = cc_mqttsn_client_set_default_retry_count(client, 5); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// ... /* Something went wrong */ +/// } +/// @endcode +/// +/// @section doc_cc_mqttsn_client_broadcast_radius Default Broadcast Radius +/// The MQTT-SN protocol defines several messages that are broadcasted. When +/// requesting to broadcast the message, the client library also specifies the +/// broadcast radius. The default broadcast radius value is @b 3. +/// To change the default configuration value +/// use @b cc_mqttsn_client_set_broadcast_radius() function. To retrieve +/// the current configuration use @b cc_mqttsn_client_get_broadcast_radius(). +/// @code +/// CC_MqttsnErrorCode ec = cc_mqttsn_client_set_broadcast_radius(client, 5); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// ... /* Something went wrong */ +/// } +/// @endcode +/// diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index bf5f6e0d..8e798d81 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -66,11 +66,12 @@ typedef enum /// @brief Status of the gateway typedef enum { - CC_MqttsnGwStatus_AddedByGateway, ///< Added by the @b ADVERTISE or @b GWINFO sent by the gateway messages - CC_MqttsnGwStatus_AddedByClient, ///< Added by the @b GWINFO message sent by another client. - CC_MqttsnGwStatus_UpdatedByClient, ///< The gateway's address was updated by another client. - CC_MqttsnGwStatus_Alive, ///< The @b ADVERTISE or @b GWINFO message have been received from the gateway indicating it's alive. - CC_MqttsnGwStatus_Removed, ///< The gateway hasn't advertised its presence in time, assumed no longer available. + CC_MqttsnGwStatus_AddedByGateway = 0, ///< Added by the @b ADVERTISE or @b GWINFO sent by the gateway messages + CC_MqttsnGwStatus_AddedByClient = 1, ///< Added by the @b GWINFO message sent by another client. + CC_MqttsnGwStatus_UpdatedByClient = 2, ///< The gateway's address was updated by another client. + CC_MqttsnGwStatus_Alive = 3, ///< The @b ADVERTISE or @b GWINFO message have been received from the gateway indicating it's alive. + CC_MqttsnGwStatus_Removed = 4, ///< The gateway hasn't advertised its presence in time, assumed no longer available. + CC_MqttsnGwStatus_ValuesLimit ///< Limit for the values } CC_MqttsnGwStatus; /// @brief Status of the asynchronous operation @@ -98,9 +99,18 @@ typedef enum struct CC_MqttsnClient; /// @brief Handler used to access client specific data structures. -/// @details Returned by cc_mqttsn_client_new() function. +/// @details Returned by cc_mqttsn_client_alloc() function. typedef struct CC_MqttsnClient* CC_MqttsnClientHandle; +/// @brief Declaration of the hidden structure used to define @ref CC_MqttsnSearchHandle +/// @ingroup search +struct CC_MqttsnSearch; + +/// @brief Handle for "search" operation. +/// @details Returned by @b cc_mqttsn_client_search_prepare() function. +/// @ingroup "search". +typedef struct CC_MqttsnSearch* CC_MqttsnSearchHandle; + /// @brief Type used to hold Topic ID value. typedef unsigned short CC_MqttsnTopicId; @@ -179,20 +189,6 @@ typedef void (*CC_MqttsnGwStatusReportCb)(void* data, CC_MqttsnGwStatus status, /// @param[in] reason Reason of the disconnection. typedef void (*CC_MqttsnGwDisconnectedReportCb)(void* data, CC_MqttsnGatewayDisconnectReason reason); -/// @brief Callback used to report completion of the asynchronous operation. -/// @param[in] data Pointer to user data object, passed as the last parameter to -/// the request call. -/// @param[in] status Status of the asynchronous operation. -/// @param[in] info Discovered gateway information. Not NULL if and only if @b status is @ref CC_MqttsnAsyncOpStatus_Complete. -typedef void (*CC_MqttsnSearchCompleteReportCb)(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info); - -/// @brief Callback used to report completion of the subscribe operation. -/// @param[in] data Pointer to user data object, passed as the last parameter to -/// the subscribe request. -/// @param[in] status Status of the subscribe operation. -/// @param[in] qos Maximal level of quality of service, the gateway/gateway is going to use to publish incoming messages. -typedef void (*CC_MqttsnSubscribeCompleteReportCb)(void* data, CC_MqttsnAsyncOpStatus status, CC_MqttsnQoS qos); - /// @brief Callback used to report incoming messages. /// @details The callback is set using /// cc_mqttsn_client_set_message_report_callback() function. The reported @@ -216,6 +212,21 @@ typedef void (*CC_MqttsnErrorLogCb)(void* data, const char* msg); /// @return Number of milliseconds to wait for another @b GWINFO to cancel the intended send of @b GWINFO on behalf of the gateway. typedef unsigned (*CC_MqttsnGwinfoDelayRequestCb)(void* data); +/// @brief Callback used to report completion of the asynchronous operation. +/// @param[in] data Pointer to user data object, passed as the last parameter to +/// the request call. +/// @param[in] status Status of the asynchronous operation. +/// @param[in] info Discovered gateway information. Not NULL if and only if @b status is @ref CC_MqttsnAsyncOpStatus_Complete. +typedef void (*CC_MqttsnSearchCompleteCb)(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info); + +/// @brief Callback used to report completion of the subscribe operation. +/// @param[in] data Pointer to user data object, passed as the last parameter to +/// the subscribe request. +/// @param[in] status Status of the subscribe operation. +/// @param[in] qos Maximal level of quality of service, the gateway/gateway is going to use to publish incoming messages. +typedef void (*CC_MqttsnSubscribeCompleteReportCb)(void* data, CC_MqttsnAsyncOpStatus status, CC_MqttsnQoS qos); + + #ifdef __cplusplus } #endif diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 160cfc61..df6834f2 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -506,10 +506,34 @@ void ClientImpl::handle(GwinfoMsg& msg) static_assert(Config::HasGatewayDiscovery); m_sendGwinfoTimer.cancel(); // Do not send GWINFO if pending - for (auto& op : m_searchOps) { - COMMS_ASSERT(op); - op->handle(msg); - } + CC_MqttsnGwStatus gwStatus = CC_MqttsnGwStatus_ValuesLimit; + const ClientState::GwInfo* gwInfo = nullptr; + + // Reporting the gateway status after + // dispatching to the search operation. + auto reportGwStatusOnExit = + comms::util::makeScopeGuard( + [this, &gwStatus, &gwInfo]() + { + if ((gwStatus < CC_MqttsnGwStatus_ValuesLimit) && + (gwInfo != nullptr)) { + reportGwStatus(gwStatus, *gwInfo); + } + }); + + // Dispatching to the SearchOp after processing and storing + // the gateway information in the internal data structures. + // It will allow updating of the gateway address by the application + // from within the search op callback + auto handleSearchOpOnExit = + comms::util::makeScopeGuard( + [this, &msg]() + { + for (auto& op : m_searchOps) { + COMMS_ASSERT(op); + op->handle(msg); + } + }); auto iter = std::find_if( @@ -537,14 +561,16 @@ void ClientImpl::handle(GwinfoMsg& msg) } iter->m_addr.assign(addr.begin(), addr.end()); - reportGwStatus(CC_MqttsnGwStatus_UpdatedByClient, *iter); - return; + gwStatus = CC_MqttsnGwStatus_UpdatedByClient; + gwInfo = &(*iter); + return; // Report gateway status on exit } auto& addr = msg.field_gwAdd().value(); if (addr.empty()) { - reportGwStatus(CC_MqttsnGwStatus_Alive, *iter); - return; + gwStatus = CC_MqttsnGwStatus_Alive; + gwInfo = &(*iter); + return; // Report gateway status on exit } if (m_clientState.m_gwInfos.max_size() <= m_clientState.m_gwInfos.size()) { @@ -560,7 +586,9 @@ void ClientImpl::handle(GwinfoMsg& msg) info.m_expiryTimestamp = m_clientState.m_timestamp + m_configState.m_gwAdvTimeoutMs; monitorGatewayExpiry(); - reportGwStatus(CC_MqttsnGwStatus_AddedByClient, info); + gwStatus = CC_MqttsnGwStatus_AddedByClient; + gwInfo = &info; + // Report geteway status on exit } #endif // #if CC_MQTTSN_HAS_GATEWAY_DISCOVERY diff --git a/client/lib/src/op/SearchOp.cpp b/client/lib/src/op/SearchOp.cpp index 027d24f2..e71b2d2a 100644 --- a/client/lib/src/op/SearchOp.cpp +++ b/client/lib/src/op/SearchOp.cpp @@ -39,7 +39,7 @@ SearchOp::SearchOp(ClientImpl& client) : { } -CC_MqttsnErrorCode SearchOp::send(CC_MqttsnSearchCompleteReportCb cb, void* cbData) +CC_MqttsnErrorCode SearchOp::send(CC_MqttsnSearchCompleteCb cb, void* cbData) { client().allowNextPrepare(); auto completeOnError = diff --git a/client/lib/src/op/SearchOp.h b/client/lib/src/op/SearchOp.h index 64942e4a..f3cae75e 100644 --- a/client/lib/src/op/SearchOp.h +++ b/client/lib/src/op/SearchOp.h @@ -25,7 +25,7 @@ class SearchOp final : public Op public: explicit SearchOp(ClientImpl& client); - CC_MqttsnErrorCode send(CC_MqttsnSearchCompleteReportCb cb, void* cbData); + CC_MqttsnErrorCode send(CC_MqttsnSearchCompleteCb cb, void* cbData); CC_MqttsnErrorCode cancel(); using Base::handle; @@ -45,7 +45,7 @@ class SearchOp final : public Op ConnectMsg m_connectMsg; TimerMgr::Timer m_timer; unsigned m_radius = 0U; - CC_MqttsnSearchCompleteReportCb m_cb = nullptr; + CC_MqttsnSearchCompleteCb m_cb = nullptr; void* m_cbData = nullptr; static_assert(ExtConfig::SearchOpTimers == 1U); diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index ddec5b8b..25aa5eac 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -15,6 +15,7 @@ #include struct CC_MqttsnClient {}; +struct CC_MqttsnSearch {}; namespace { @@ -35,9 +36,19 @@ inline CC_MqttsnClientHandle handleFromClient(cc_mqttsn_client::ClientImpl* clie return reinterpret_cast(client); } +inline cc_mqttsn_client::op::SearchOp* searchOpFromHandle(CC_MqttsnSearchHandle handle) +{ + return reinterpret_cast(handle); +} + +inline CC_MqttsnSearchHandle handleFromSearchOp(cc_mqttsn_client::op::SearchOp* op) +{ + return reinterpret_cast(op); +} + } // namespace -CC_MqttsnClientHandle cc_mqttsn_##NAME##client_new() +CC_MqttsnClientHandle cc_mqttsn_##NAME##client_alloc() { auto client = getClientAllocator().alloc(); return handleFromClient(client.release()); @@ -60,7 +71,7 @@ void cc_mqttsn_##NAME##client_process_data(CC_MqttsnClientHandle client, const u clientFromHandle(client)->processData(buf, bufLen); } -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_retry_period(CC_MqttsnClientHandle client, unsigned value) +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_default_retry_period(CC_MqttsnClientHandle client, unsigned value) { COMMS_ASSERT(client != nullptr); static const unsigned MaxValue = std::numeric_limits::max() / 1000U; @@ -73,13 +84,13 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_retry_period(CC_MqttsnClientHand return CC_MqttsnErrorCode_Success; } -unsigned cc_mqttsn_##NAME##client_get_retry_period(CC_MqttsnClientHandle client) +unsigned cc_mqttsn_##NAME##client_get_default_retry_period(CC_MqttsnClientHandle client) { COMMS_ASSERT(client != nullptr); return clientFromHandle(client)->configState().m_responseTimeoutMs; } -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_retry_count(CC_MqttsnClientHandle client, unsigned value) +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_default_retry_count(CC_MqttsnClientHandle client, unsigned value) { COMMS_ASSERT(client != nullptr); if (value < 1U) { @@ -92,7 +103,7 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_retry_count(CC_MqttsnClientHandl return CC_MqttsnErrorCode_Success; } -unsigned cc_mqttsn_##NAME##client_get_retry_count(CC_MqttsnClientHandle client) +unsigned cc_mqttsn_##NAME##client_get_default_retry_count(CC_MqttsnClientHandle client) { COMMS_ASSERT(client != nullptr); return clientFromHandle(client)->configState().m_retryCount; @@ -116,6 +127,24 @@ unsigned cc_mqttsn_##NAME##client_get_broadcast_radius(CC_MqttsnClientHandle cli return clientFromHandle(client)->configState().m_broadcastRadius; } +CC_MqttsnSearchHandle cc_mqttsn_##NAME##client_search_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec) +{ + COMMS_ASSERT(client != nullptr); + return handleFromSearchOp(clientFromHandle(client)->searchPrepare(ec)); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_send(CC_MqttsnSearchHandle handle, CC_MqttsnSearchCompleteCb cb, void* cbData) +{ + COMMS_ASSERT(handle != nullptr); + return searchOpFromHandle(handle)->send(cb, cbData); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_cancel(CC_MqttsnSearchHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return searchOpFromHandle(handle)->cancel(); +} + // --------------------- Callbacks --------------------- void cc_mqttsn_##NAME##client_set_next_tick_program_callback( @@ -172,6 +201,15 @@ void cc_mqttsn_##NAME##client_set_message_report_callback( clientFromHandle(client)->setMessageReceivedCallback(cb, data); } +void cc_mqttsn_##NAME##client_set_error_log_callback( + CC_MqttsnClientHandle client, + CC_MqttsnErrorLogCb cb, + void* data) +{ + COMMS_ASSERT(client != nullptr); + clientFromHandle(client)->setErrorLogCallback(cb, data); +} + void cc_mqttsn_##NAME##client_set_gwinfo_delay_request_callback( CC_MqttsnClientHandle client, CC_MqttsnGwinfoDelayRequestCb cb, diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index 62cb98c5..367d0dc4 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -21,13 +21,13 @@ extern "C" { /// function must be invoked. /// @return Handle to allocated client object. This handle needs to be passed /// as first parameter to all other API functions. -CC_MqttsnClientHandle cc_mqttsn_##NAME##client_new(); +CC_MqttsnClientHandle cc_mqttsn_##NAME##client_alloc(); /// @brief Free previously allocated client. /// @details When used communication channel to the gateway is no longer /// needed, the client data structes allocated with -/// cc_mqttsn_##NAME##client_new() must be released using this function. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// cc_mqttsn_##NAME##client_alloc() must be released using this function. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. void cc_mqttsn_##NAME##client_free(CC_MqttsnClientHandle client); /// @brief Notify client about requested time expiry. @@ -40,7 +40,7 @@ void cc_mqttsn_##NAME##client_free(CC_MqttsnClientHandle client); /// new measurement if needed. /// This call may cause invocation of some other callbacks, such as a request /// to send new data to the gateway. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[in] ms Number of elapsed @b milliseconds. void cc_mqttsn_##NAME##client_tick(CC_MqttsnClientHandle client, unsigned ms); @@ -49,7 +49,7 @@ void cc_mqttsn_##NAME##client_tick(CC_MqttsnClientHandle client, unsigned ms); /// request to cancel the currently running time measurement, send some messages to /// the gateway, report incoming application message, and (re)start time /// measurement. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[in] buf Pointer to the buffer of data to process. /// @param[in] bufLen Number of bytes in the data buffer. /// @note According to the MQTT-SN specification every message should be sent in a separate @@ -57,48 +57,78 @@ void cc_mqttsn_##NAME##client_tick(CC_MqttsnClientHandle client, unsigned ms); /// all the provided data discarding any additional bytes after the message (if exist). void cc_mqttsn_##NAME##client_process_data(CC_MqttsnClientHandle client, const unsigned char* buf, unsigned bufLen); -/// @brief Set retry period to wait between resending unacknowledged message to the gateway (@b Tretry from spec). +/// @brief Set retry period to wait between resending unacknowledged message to the gateway (@b Tretry from spec). /// @details Some messages, sent to the gateway, may require acknowledgement by /// the latter. The delay (in seconds) between such attempts to resend the /// message may be specified using this function. The default value is /// @b 10 seconds. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[in] value Number of @b seconds to wait before making an attempt to resend. -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_retry_period(CC_MqttsnClientHandle client, unsigned value); +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_default_retry_period(CC_MqttsnClientHandle client, unsigned value); -/// @brief Set configured retry period to wait between resending unacknowledged message to the gateway (@b Tretry from spec). -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @see @ref cc_mqttsn_##NAME##client_set_retry_period(). -unsigned cc_mqttsn_##NAME##client_get_retry_period(CC_MqttsnClientHandle client); +/// @brief Set configured retry period to wait between resending unacknowledged message to the gateway (@b Tretry from spec). +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @see @ref cc_mqttsn_##NAME##client_set_default_retry_period(). +unsigned cc_mqttsn_##NAME##client_get_default_retry_period(CC_MqttsnClientHandle client); -/// @brief Set number of retry attempts to perform before reporting unsuccessful result of the operation (@b Nretry from spec). +/// @brief Set number of retry attempts to perform before reporting unsuccessful result of the operation (@b Nretry from spec). /// @details Some messages, sent to the gateway, may require acknowledgement by /// the latter. The amount of retry attempts before reporting unsuccessful result /// of the operation may be specified using this function. The default value /// is @b 3. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[in] value Number of retry attempts. -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_retry_count(CC_MqttsnClientHandle client, unsigned value); +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_default_retry_count(CC_MqttsnClientHandle client, unsigned value); -/// @brief Get configured number of retry attempts to perform before reporting unsuccessful result of the operation (@b Nretry from spec). -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @see @ref cc_mqttsn_##NAME##client_set_retry_count() -unsigned cc_mqttsn_##NAME##client_get_retry_count(CC_MqttsnClientHandle client); +/// @brief Get configured number of retry attempts to perform before reporting unsuccessful result of the operation (@b Nretry from spec). +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @see @ref cc_mqttsn_##NAME##client_set_default_retry_count() +unsigned cc_mqttsn_##NAME##client_get_default_retry_count(CC_MqttsnClientHandle client); /// @brief Set broadcast radius. /// @details When searching for gateways, the client library broadcasts @b SEARCHGW /// messages. It contains the broadcast radius value. This value can be /// set using this function. Default radius value is @b 3. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[in] value Broadcast radius. /// @pre The broadcast value cannot exceed 255. CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_broadcast_radius(CC_MqttsnClientHandle client, unsigned value); /// @brief Get current broadcast radius configuration. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @see @ref cc_mqttsn_##NAME##client_set_broadcast_radius() unsigned cc_mqttsn_##NAME##client_get_broadcast_radius(CC_MqttsnClientHandle client); +/// @brief Prepare "search" operation. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[out] ec Error code reporting result of the operation. Can be NULL. +/// @return Handle of the "search" operation, will be NULL in case of failure. To analyze the reason failure use "ec" output parameter. +/// @post The "search" operation is allocated, use either @ref cc_mqttsn_##NAME##client_search_send() +/// or @ref cc_mqttsn_##NAME##client_search_cancel() to prevent memory leaks. +/// @ingroup connect +CC_MqttsnSearchHandle cc_mqttsn_##NAME##client_search_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec); + +/// @brief Send the "search" operation +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_search_prepare() function. +/// @param[in] cb Callback to be invoked when "search" operation is complete. +/// @param[in] cbData Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +/// @return Result code of the call. +/// @post The handle of the "search" operation can be discarded. +/// @post The provided callback will be invoked when the "search" operation is complete if and only if +/// the function returns @ref CC_MqttsnErrorCode_Success. +/// @ingroup connect +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_send(CC_MqttsnSearchHandle handle, CC_MqttsnSearchCompleteCb cb, void* cbData); + +/// @brief Cancel the allocated "search" operation +/// @details In case the @ref cc_mqttsn_##NAME##client_search_send() function was successfully called before, +/// the operation is cancelled @b without callback invocation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_search_prepare() function. +/// @return Result code of the call. +/// @post The handle of the "search" operation is no longer valid and must be discarded. +/// @ingroup connect +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_cancel(CC_MqttsnSearchHandle handle); + // --------------------- Callbacks --------------------- /// @brief Set callback to call when time measurement is required. @@ -106,7 +136,7 @@ unsigned cc_mqttsn_##NAME##client_get_broadcast_radius(CC_MqttsnClientHandle cli /// measurement is required, the provided callback will be invoked with /// the timeout duration in milliseconds. After requested time expires, /// the @ref cc_mqttsn_##NAME##client_tick() function must be invoked. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[in] cb Callback function. /// @param[in] data Pointer to any user data structure. It will passed as one /// of the parameters in callback invocation. May be NULL. @@ -121,7 +151,7 @@ void cc_mqttsn_##NAME##client_set_next_tick_program_callback( /// @ref cc_mqttsn_##NAME##client_set_next_tick_program_callback() function. This function /// sets appropriate callback. When invoked, it must return number of /// elapsed milliseconds since previoius time measurement request. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[in] cb Callback function. /// @param[in] data Pointer to any user data structure. It will passed as one /// of the parameters in callback invocation. May be NULL. @@ -134,7 +164,7 @@ void cc_mqttsn_##NAME##client_set_cancel_next_tick_wait_callback( /// @details The callback is invoked when there is a need to send data /// to the gateway. The callback is invoked for every single message /// that need to be sent as a single datagram. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[in] cb Callback function. /// @param[in] data Pointer to any user data structure. It will passed as one /// of the parameters in callback invocation. May be NULL. @@ -145,7 +175,7 @@ void cc_mqttsn_##NAME##client_set_send_output_data_callback( /// @brief Set callback to report status of the gateway. /// @details The callback is invoked when gateway status has changed. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[in] cb Callback function. /// @param[in] data Pointer to any user data structure. It will passed as one /// of the parameters in callback invocation. May be NULL. @@ -157,7 +187,7 @@ void cc_mqttsn_##NAME##client_set_gw_status_report_callback( /// @brief Set callback to report unsolicited disconnection of the gateway. /// @details The callback will be invoked when gateway sends unsolicited /// @b DISCONNECT message or does not reply to @b PINGREQ message. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[in] cb Callback function. /// @param[in] data Pointer to any user data structure. It will passed as one /// of the parameters in callback invocation. May be NULL. @@ -167,14 +197,24 @@ void cc_mqttsn_##NAME##client_set_gw_disconnect_report_callback( void* data); /// @brief Set callback to report incoming messages. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[in] cb Callback function. /// @param[in] data Pointer to any user data structure. It will passed as one /// of the parameters in callback invocation. May be NULL. void cc_mqttsn_##NAME##client_set_message_report_callback( CC_MqttsnClientHandle client, CC_MqttsnMessageReportCb cb, - void* data); + void* data); + +/// @brief Set callback to report error messages. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[in] cb Callback function. +/// @param[in] data Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +void cc_mqttsn_##NAME##client_set_error_log_callback( + CC_MqttsnClientHandle client, + CC_MqttsnErrorLogCb cb, + void* data); /// @brief Set callback to request a random timeout to send @b GWINFO as a response to the @b SEARCHGW from other client. /// @details According to the MQTT-SN specification, the client can send @b GWINFO message on behalf of a gateway From 627ac813524061475b5e0d145cbffe6d450435b5 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 30 May 2024 09:24:17 +1000 Subject: [PATCH 036/106] Documenting gateway discovery functionality. --- client/lib/CMakeLists.txt | 1 + client/lib/doxygen/doxygen.conf | 64 +---- client/lib/doxygen/main.dox | 252 ++++++++++++++++++- client/lib/include/cc_mqttsn_client/common.h | 7 +- client/lib/src/ClientImpl.cpp | 47 +++- client/lib/src/ClientState.h | 2 + client/lib/src/ConfigState.h | 4 +- client/lib/src/client.c | 9 + client/lib/src/op/Op.cpp | 2 +- client/lib/src/op/Op.h | 20 +- client/lib/src/op/SearchOp.cpp | 2 +- client/lib/src/op/SearchOp.h | 10 + client/lib/templ/client.cpp.templ | 211 +++++++++++++++- client/lib/templ/client.h.templ | 161 +++++++++++- 14 files changed, 706 insertions(+), 86 deletions(-) create mode 100644 client/lib/src/client.c diff --git a/client/lib/CMakeLists.txt b/client/lib/CMakeLists.txt index 031db6fd..73b59d9e 100644 --- a/client/lib/CMakeLists.txt +++ b/client/lib/CMakeLists.txt @@ -142,6 +142,7 @@ function (gen_lib_mqttsn_client config_file) set (src src/op/Op.cpp src/op/SearchOp.cpp + src/client.c src/ClientImpl.cpp src/TimerMgr.cpp ) diff --git a/client/lib/doxygen/doxygen.conf b/client/lib/doxygen/doxygen.conf index 4d8992d8..56c9c017 100644 --- a/client/lib/doxygen/doxygen.conf +++ b/client/lib/doxygen/doxygen.conf @@ -32,7 +32,7 @@ DOXYFILE_ENCODING = UTF-8 # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = "MQTT-SN Client" +PROJECT_NAME = "CommsChampion Ecosystem MQTT-SN Client" # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version @@ -58,7 +58,7 @@ PROJECT_LOGO = # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = /cc_mqttsn_client +OUTPUT_DIRECTORY = # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and @@ -475,7 +475,7 @@ NUM_PROC_THREADS = 0 # normally produced when WARNINGS is set to YES. # The default value is: NO. -EXTRACT_ALL = YES +EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will # be included in the documentation. @@ -604,7 +604,7 @@ HIDE_COMPOUND_REFERENCE= NO # the files that are included by a file in the documentation of that file. # The default value is: YES. -SHOW_INCLUDE_FILES = NO +SHOW_INCLUDE_FILES = YES # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader @@ -638,7 +638,7 @@ SORT_MEMBER_DOCS = YES # this will also influence the order of the classes in the class list. # The default value is: NO. -SORT_BRIEF_DOCS = NO +SORT_BRIEF_DOCS = YES # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the # (brief and detailed) documentation of class members so that constructors and @@ -883,51 +883,7 @@ INPUT_ENCODING = UTF-8 # *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, # *.ucf, *.qsf and *.ice. -FILE_PATTERNS = *.c \ - *.cc \ - *.cxx \ - *.cpp \ - *.c++ \ - *.java \ - *.ii \ - *.ixx \ - *.ipp \ - *.i++ \ - *.inl \ - *.idl \ - *.ddl \ - *.odl \ - *.h \ - *.hh \ - *.hxx \ - *.hpp \ - *.h++ \ - *.cs \ - *.d \ - *.php \ - *.php4 \ - *.php5 \ - *.phtml \ - *.inc \ - *.m \ - *.markdown \ - *.md \ - *.mm \ - *.dox \ - *.py \ - *.pyw \ - *.f90 \ - *.f95 \ - *.f03 \ - *.f08 \ - *.f18 \ - *.f \ - *.for \ - *.vhd \ - *.vhdl \ - *.ucf \ - *.qsf \ - *.ice +FILE_PATTERNS = # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. @@ -942,7 +898,7 @@ RECURSIVE = YES # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = test +EXCLUDE = test doc externals cmake # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -1322,7 +1278,7 @@ HTML_COLORSTYLE_GAMMA = 80 # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_TIMESTAMP = YES +HTML_TIMESTAMP = NO # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that @@ -2204,7 +2160,7 @@ ENABLE_PREPROCESSING = YES # The default value is: NO. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -MACRO_EXPANSION = NO +MACRO_EXPANSION = YES # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then # the macro expansion is limited to the macros specified with the PREDEFINED and @@ -2212,7 +2168,7 @@ MACRO_EXPANSION = NO # The default value is: NO. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -EXPAND_ONLY_PREDEF = NO +EXPAND_ONLY_PREDEF = YES # If the SEARCH_INCLUDES tag is set to YES, the include files in the # INCLUDE_PATH will be searched if a #include is found. diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index 8c3f4c18..ec748871 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -226,11 +226,11 @@ /// any other other operation can be @b prepared. For example: /// @code /// CC_MqttsnErrorCode ec = CC_MqttsnErrorCode_Success; -/// CC_MqttsnConnectHandle connect = cc_mqttsn_client_connect_prepare(...); +/// CC_MqttsnConnectHandle connect = cc_mqttsn_client_search_prepare(...); /// assert(connect != nullptr); /// /// // The following attempt to prepare the "subscribe" operation will fail because -/// // previously allocated "connect" hasn't been sent or cancelled yet. +/// // previously allocated "search" hasn't been sent or cancelled yet. /// CC_MqttsnSubscribeHandle subscribe = cc_mqttsn_client_subscribe_prepare(...); /// assert(subscribe == NULL); /// @endcode @@ -282,3 +282,251 @@ /// } /// @endcode /// +/// @section doc_cc_mqttsn_client_gateway_discovery Gateway Discovery +/// The MQTT-SN protocol supports having multiple gateway on the same network +/// and their discovery. When the application @ref doc_cc_mqttsn_client_allocation "allocates" +/// the client object, it can either connect directly to the gateway (if such is +/// known) or initiate gateway discovery by using @ref search "search" operation. +/// The MQTT-SN specification recommends to wait some time (up to @b Tsearchgw) +/// to prevent sending broadcast storm of the @b SEARCHGW messages when all +/// clients start at once. It is up to the application to decide whether and when +/// to issue the @ref search "search" operation. +/// +/// @subsection doc_cc_mqttsn_client_search_prepare Preparing "Search" Operation. +/// @code +/// CC_MqttsnErrorCode ec = CC_MqttsnErrorCode_Success; +/// CC_MqttsnSearchHandle search = cc_mqttsn_client_search_prepare(client, &ec); +/// if (search == NULL) { +/// printf("ERROR: Search allocation failed with ec=%d\n", ec); +/// } +/// @endcode +/// +/// @subsection doc_cc_mqttsn_client_search_retry_period Configuring "Search" Retry Period +/// When created, the "search" operation inherits the @ref doc_cc_mqttsn_client_retry_period +/// configuration. It can be changed for the allocated operation using the +/// @b cc_mqttsn_client_search_set_retry_period() function. +/// @code +/// ec = cc_mqttsn_client_search_set_retry_period(search, 15000); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// ... /* Something went wrong */ +/// } +/// @endcode +/// To retrieve the configured retry period use the @b cc_mqttsn_client_search_get_retry_period() function. +/// +/// @subsection doc_cc_mqttsn_client_search_retry_count Configuring "Search" Retry Count +/// When created, the "search" operation inherits the @ref doc_cc_mqttsn_client_retry_count +/// configuration. It can be changed for the allocated operation using the +/// @b cc_mqttsn_client_search_set_retry_count() function. +/// @code +/// ec = cc_mqttsn_client_search_set_retry_count(search, 5); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// ... /* Something went wrong */ +/// } +/// @endcode +/// To retrieve the configured retry count use the @b cc_mqttsn_client_search_get_retry_count() function. +/// +/// @subsection doc_cc_mqttsn_client_search_broadcast_radius Configuring "Search" Broadcast Radius +/// The "search" operation is expected to broadcast the @b SEARCHGW message. +/// When created, the "search" operation inherits the @ref doc_cc_mqttsn_client_broadcast_radius +/// configuration. It can be changed for the allocated operation using the +/// @b cc_mqttsn_client_search_set_broadcast_radius() function. +/// @code +/// ec = cc_mqttsn_client_search_set_broadcast_radius(search, 5); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// ... /* Something went wrong */ +/// } +/// @endcode +/// To retrieve the configured retry count use the @b cc_mqttsn_client_search_get_broadcast_radius() function. +/// +/// @subsection doc_cc_mqttsn_client_search_send Sending Search Gateway Request +/// When all the necessary configurations are performed for the allocated "search" +/// operation it can actually be sent on the network. To initiate sending +/// use the @b cc_mqttsn_client_search_send() function. +/// @code +/// void my_search_complete_cb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info) +/// { +/// if (status != CC_MqttsnAsyncOpStatus_Complete) { +/// printf("ERROR: The search operation has failed with status=%d\n", status); +/// ... // handle error. +/// return; +/// } +/// +/// // "info" is not NULL when status is CC_MqttsnAsyncOpStatus_Complete. +/// assert(info != NULL); +/// ... // Analyze found gateway information. +/// } +/// +/// ec = cc_mqttsn_client_search_send(search, &my_search_complete_cb, data); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to send search request with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// The provided callback will be invoked when the "search" operation is complete +/// if and only if the function returns @ref CC_MqttsnErrorCode_Success. +/// +/// The handle returned by the @b cc_mqttsn_client_search_prepare() function +/// can be discarded (there is no free / de-allocation) right after the +/// @b cc_mqttsn_client_search_send() invocation +/// regardless of the returned error code. However, the handle remains valid until +/// the callback is called (in case the @ref CC_MqttsnErrorCode_Success was returned). +/// The valid handle can be used to @ref doc_cc_mqttsn_client_search_cancel "cancel" +/// the operation before the completion callback is invoked. +/// +/// When the "search" operation completion callback is invoked the reported +/// "info" information is present if and only if the "status" is +/// @ref CC_MqttsnAsyncOpStatus_Complete. +/// +/// @b NOTE that only single "search" operation is allowed at a time, any attempt to +/// prepare a new one via @b cc_mqttsn_client_search_prepare() will be rejected +/// until the "search" operation completion callback is invoked or +/// the operation is @ref doc_cc_mqttsn_client_search_cancel "cancelled". +/// +/// The "search" operation is complete when the valid @b GWINFO message is received. +/// Such message can be sent either by the gateway itself or by another client +/// on behalf of the gateway. When they @b GWINFO message is sent by another +/// client it specifies the actual address of the gateway +/// (reported via the @ref CC_MqttsnGatewayInfo::m_addr and @ref CC_MqttsnGatewayInfo::m_addrLen). +/// When the @b GWINFO message is sent by the gateway itself, the address information +/// inside the reported @ref CC_MqttsnGatewayInfo structure will be empty. +/// +/// Upon reception of any new data packet from the I/O link, the application is expected +/// to detect and save the origin address of the sender. In case the reported +/// gateway address is empty, the application is expected to use the saved +/// origin address of the data packet as the one of the gateway. +/// The application is also expected to update the address info stored by the +/// library using the @b cc_mqttsn_client_set_available_gateway_info() function. +/// The details are in the @ref doc_cc_mqttsn_client_gateway_monitoring section below. +/// +/// @subsection doc_cc_mqttsn_client_search_cancel Cancel the "Search" Operation. +/// While the handle returned by the @b cc_mqttsn_client_search_prepare() is still +/// valid it is possible to cancel / discard the operation. +/// @code +/// CC_MqttsnErrorCode ec = cc_mqttsn_client_search_cancel(search); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to cancel search with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// In case the @b cc_mqttsn_client_search_send() function was successfully +/// called before the @b cc_mqttsn_client_search_cancel(), the operation is +/// cancelled @b without callback invocation. +/// +/// @subsection doc_cc_mqttsn_client_search_simplify Simplifying the "Search" Operation Preparation +/// In many use cases the "search" operation can be quite simple with a lot of defaults. +/// To simplify the sequence of the operation preparation and handling of errors, +/// the library provides the @b cc_mqttsn_client_search() wrapper function. +/// +/// For example: +/// @code +/// CC_MqttsnErrorCode ec = cc_mqttsn_client_search(client, &my_search_complete_cb, data); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to send search request with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// Note that the wrapper functions do NOT expose the handle returned by the +/// @b cc_mqttsn_client_search_prepare(). It means that it's not possible to +/// cancel the "search" operation before its completion. +/// +/// @section doc_cc_mqttsn_client_gateway_monitoring Monitoring and Managing Available Gateways +/// The MQTT-SN gateways are also expected to periodically advertise their presence. +/// The client library keeps track of the discovered gateways on the network. It +/// is possible to retrieve the information on the available gateways at any time. +/// @code +/// unsigned numOfAvailableGateways = cc_mqttsn_client_get_available_gateways_count(client); +/// for (idx=0; idx < numOfAvailableGateways; ++idx) { +/// CC_MqttsnGatewayInfo info; +/// CC_MqttsnErrorCode ec = cc_mqttsn_client_get_available_gateway_info(client, idx, &info); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("Something is wrong\n"); +/// continue; +/// } +/// +/// ... // Process updated info. +/// } +/// @endcode +/// +/// The application can receive ongoing notifications if a status of any gateway changes. +/// @code +/// void my_gw_status_report_cb(void* data, CC_MqttsnGwStatus status, const CC_MqttsnGatewayInfo* info) +/// { +/// ... +/// } +/// +/// cc_mqttsn_client_set_gw_status_report_callback(client, &my_gw_status_report_cb, data); +/// @endcode +/// The @b status parameter to the callback function indicates the status of the +/// gateway, while @b info reports the currently stored gateway information. +/// The callback (if registered) is invoked every time the @b ADVERTISE as well as @b GWINFO +/// messages are received or not received in time. The @ref CC_MqttsnGwStatus "status" value indicates +/// of whether the gateway information is newly added, updated or the old info is confirmed. +/// +/// When the expected @b ADVERTISE message doesn't arrive in time, it can be due to +/// packet loss on the network rather than the gateway itself going offline. The +/// library allows configuration of allowed losses of the @b ADVERTISE message before +/// the gateway is reported to be @ref CC_MqttsnGwStatus_Removed and its info removed from +/// the internal data structures. +/// @code +/// CC_MqttsnErrorCode ec = cc_mqttsn_client_set_allowed_adv_losses(client, 2); +/// @endcode +/// The configured count is how many @b ADVERTISE messages can be @b lost, i.e. when +/// configured to be @b 0, the gateway info is removed on the first missing @b ADVERTISE packet. +/// When configured to be @b 1 (or greater), the reported gateway status will +/// be @ref CC_MqttsnGwStatus_Tentative when the expected advertising packet doesn't +/// arrive in time until the inner count of allowed losses goes to @b 0 and then +/// the gateway status is reported to be @ref CC_MqttsnGwStatus_Removed. The default +/// allowed count of the @b ADVERTISE losses is @b 1. The current configuration +/// can be retrieved using @b cc_mqttsn_client_get_allowed_adv_losses(). When +/// the @b ADVERTISE or @b GWINFO from the gateway message arrive, the library reports +/// @ref CC_MqttsnGwStatus_Alive as the status gateway. +/// +/// Upon reception of the @b ADVERTISE or @b GWINFO from the gateway, the address information +/// may be missing (or wrong). The application is expected to record the origin of +/// the recent message and this information can be used to update the recorded information. +/// The application can invoke the @b cc_mqttsn_client_set_available_gateway_info() +/// function at any time to update / override the stored address information about the +/// gateway. It is also applicable to the @ref doc_cc_mqttsn_client_search_send "search" +/// operation. The @b cc_mqttsn_client_set_available_gateway_info() can be called +/// from withing the operation completion callback to update the stored gateway address. +/// @code +/// CC_MqttsnGatewayInfo info; +/// info.m_gwId = ...; +/// info.m_addr = ...; +/// info.m_addrLen = ...; +/// CC_MqttsnErrorCode ec = cc_mqttsn_client_set_available_gateway_info(client, info); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("Something is wrong"); +/// } +/// @endcode +/// +/// The application can force the library to discard the available gateway information +/// by issuing the @b cc_mqttsn_client_discard_available_gateway_info() function. +/// @code +/// CC_MqttsnErrorCode ec = cc_mqttsn_client_discard_available_gateway_info(client, 1 /* gateway id */); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("Something is wrong"); +/// } +/// @endcode +/// +/// It is possible to discard information on all the gateways in one go: +/// @code +/// CC_MqttsnErrorCode ec = cc_mqttsn_client_discard_all_gateway_infos(client); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("Something is wrong"); +/// } +/// @endcode +/// +/// When the client receives @b GWINFO message before @b ADVERTISE, the +/// advertising period of the gateway is not known yet. For the gateway tracking +/// and management functionality there is a need to assume some kind of default +/// duration. The default value is 15 minutes. To update it use +/// @b cc_mqttsn_client_set_default_gw_adv_duration() function. +/// @code +/// CC_MqttsnErrorCode ec = cc_mqttsn_client_set_default_gw_adv_duration(client, 20 * 60 * 1000 /* 20 minutes in milliseconds */); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("Something is wrong"); +/// } +/// @endcode +/// To retrieve the current configuration use @b cc_mqttsn_client_get_default_gw_adv_duration() function. +/// diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index 8e798d81..572976b4 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -17,12 +17,15 @@ extern "C" { #endif // #ifdef __cplusplus /// @brief Major verion of the library +/// @ingroup global #define CC_MQTTSN_CLIENT_MAJOR_VERSION 2U /// @brief Minor verion of the library +/// @ingroup global #define CC_MQTTSN_CLIENT_MINOR_VERSION 0U /// @brief Patch level of the library +/// @ingroup global #define CC_MQTTSN_CLIENT_PATCH_VERSION 0U /// @brief Macro to create numeric version as single unsigned number @@ -70,7 +73,8 @@ typedef enum CC_MqttsnGwStatus_AddedByClient = 1, ///< Added by the @b GWINFO message sent by another client. CC_MqttsnGwStatus_UpdatedByClient = 2, ///< The gateway's address was updated by another client. CC_MqttsnGwStatus_Alive = 3, ///< The @b ADVERTISE or @b GWINFO message have been received from the gateway indicating it's alive. - CC_MqttsnGwStatus_Removed = 4, ///< The gateway hasn't advertised its presence in time, assumed no longer available. + CC_MqttsnGwStatus_Tentative = 4, ///< The gateway hasn't advertised its presence in time, assumed packet loss. + CC_MqttsnGwStatus_Removed = 5, ///< The gateway hasn't advertised its presence in time, assumed no longer available. CC_MqttsnGwStatus_ValuesLimit ///< Limit for the values } CC_MqttsnGwStatus; @@ -217,6 +221,7 @@ typedef unsigned (*CC_MqttsnGwinfoDelayRequestCb)(void* data); /// the request call. /// @param[in] status Status of the asynchronous operation. /// @param[in] info Discovered gateway information. Not NULL if and only if @b status is @ref CC_MqttsnAsyncOpStatus_Complete. +/// @ingroup search typedef void (*CC_MqttsnSearchCompleteCb)(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info); /// @brief Callback used to report completion of the subscribe operation. diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index df6834f2..b0f54cac 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -461,8 +461,13 @@ void ClientImpl::handle(AdvertiseMsg& msg) return msg.field_gwId().value() == info.m_gwId; }); + auto duration = comms::units::getMilliseconds(msg.field_duration()); + auto nextExpiryTimestamp = m_clientState.m_timestamp + duration + m_configState.m_retryPeriod; + if (iter != m_clientState.m_gwInfos.end()) { - iter->m_expiryTimestamp = m_clientState.m_timestamp + comms::units::getMilliseconds(msg.field_duration()); + iter->m_expiryTimestamp = nextExpiryTimestamp; + iter->m_duration = duration; + iter->m_allowedAdvLosses = m_configState.m_allowedAdvLosses; reportGwStatus(CC_MqttsnGwStatus_Alive, *iter); return; } @@ -476,7 +481,9 @@ void ClientImpl::handle(AdvertiseMsg& msg) m_clientState.m_gwInfos.resize(m_clientState.m_gwInfos.size() + 1U); auto& info = m_clientState.m_gwInfos.back(); info.m_gwId = msg.field_gwId().value(); - info.m_expiryTimestamp = m_clientState.m_timestamp + comms::units::getMilliseconds(msg.field_duration()); + info.m_expiryTimestamp = nextExpiryTimestamp; + info.m_duration = duration; + info.m_allowedAdvLosses = m_configState.m_allowedAdvLosses; reportGwStatus(CC_MqttsnGwStatus_AddedByGateway, info); } @@ -489,7 +496,10 @@ void ClientImpl::handle(SearchgwMsg& msg) return; } - // TODO: check active + if (m_clientState.m_gwInfos.empty()) { + // No known gateways + return; + } auto delay = m_gwinfoDelayReqCb(m_gwinfoDelayReqData); if (delay == 0U) { @@ -546,9 +556,15 @@ void ClientImpl::handle(GwinfoMsg& msg) if (iter != m_clientState.m_gwInfos.end()) { auto& addr = msg.field_gwAdd().value(); if (addr.empty()) { + // GWINFO by the gateway itself + iter->m_allowedAdvLosses = m_configState.m_allowedAdvLosses; + gwStatus = CC_MqttsnGwStatus_Alive; + gwInfo = &(*iter); return; } + // GWINFO by another client + if (iter->m_addr.max_size() < addr.size()) { errorLog("The gateway address reported by the client doesn't fit into the dedicated address storage, ignoring"); return; @@ -566,10 +582,16 @@ void ClientImpl::handle(GwinfoMsg& msg) return; // Report gateway status on exit } + m_clientState.m_gwInfos.resize(m_clientState.m_gwInfos.size() + 1U); + auto& info = m_clientState.m_gwInfos.back(); + + info.m_gwId = msg.field_gwId().value(); + info.m_allowedAdvLosses = m_configState.m_allowedAdvLosses; + gwInfo = &info; + auto& addr = msg.field_gwAdd().value(); if (addr.empty()) { gwStatus = CC_MqttsnGwStatus_Alive; - gwInfo = &(*iter); return; // Report gateway status on exit } @@ -579,15 +601,13 @@ void ClientImpl::handle(GwinfoMsg& msg) return; } - m_clientState.m_gwInfos.resize(m_clientState.m_gwInfos.size() + 1U); - auto& info = m_clientState.m_gwInfos.back(); - info.m_gwId = msg.field_gwId().value(); + info.m_addr.assign(addr.begin(), addr.end()); - info.m_expiryTimestamp = m_clientState.m_timestamp + m_configState.m_gwAdvTimeoutMs; + info.m_expiryTimestamp = m_clientState.m_timestamp + m_configState.m_gwAdvTimeoutMs + m_configState.m_retryCount; + info.m_duration = m_configState.m_gwAdvTimeoutMs; monitorGatewayExpiry(); gwStatus = CC_MqttsnGwStatus_AddedByClient; - gwInfo = &info; // Report geteway status on exit } #endif // #if CC_MQTTSN_HAS_GATEWAY_DISCOVERY @@ -1250,7 +1270,14 @@ void ClientImpl::gwExpiryTimeout() continue; } - reportGwStatus(CC_MqttsnGwStatus_Removed, info); + if (info.m_allowedAdvLosses == 0U) { + reportGwStatus(CC_MqttsnGwStatus_Removed, info); + continue; + } + + --info.m_allowedAdvLosses; + info.m_expiryTimestamp += info.m_duration; + reportGwStatus(CC_MqttsnGwStatus_Tentative, info); } m_clientState.m_gwInfos.erase( diff --git a/client/lib/src/ClientState.h b/client/lib/src/ClientState.h index d71ad9a0..411e14e1 100644 --- a/client/lib/src/ClientState.h +++ b/client/lib/src/ClientState.h @@ -27,6 +27,8 @@ struct ClientState { Timestamp m_expiryTimestamp = 0U; GwAddr m_addr; + unsigned m_duration = 0U; + unsigned m_allowedAdvLosses = 0U; std::uint8_t m_gwId = 0; }; diff --git a/client/lib/src/ConfigState.h b/client/lib/src/ConfigState.h index 97ef1275..398e474e 100644 --- a/client/lib/src/ConfigState.h +++ b/client/lib/src/ConfigState.h @@ -18,13 +18,15 @@ struct ConfigState static constexpr unsigned DefaultRetryCount = 3U; static constexpr unsigned DefaultBroadcastRadius = 3U; static constexpr unsigned DefaultGwAdvTimeoutMs = 15 * 60 * 1000; // + static constexpr unsigned DefautlAllowedAdvLosses = 1; static constexpr unsigned MaxBroadcastRadius = 255U; - unsigned m_responseTimeoutMs = DefaultResponseTimeoutMs; + unsigned m_retryPeriod = DefaultResponseTimeoutMs; unsigned m_retryCount = DefaultRetryCount; unsigned m_broadcastRadius = DefaultBroadcastRadius; unsigned m_gwAdvTimeoutMs = DefaultGwAdvTimeoutMs; + unsigned m_allowedAdvLosses = DefautlAllowedAdvLosses; // CC_MqttsnPublishOrdering m_publishOrdering = CC_MqttsnPublishOrdering_SameQos; // bool m_verifyOutgoingTopic = Config::HasTopicFormatVerification; // bool m_verifyIncomingTopic = Config::HasTopicFormatVerification; diff --git a/client/lib/src/client.c b/client/lib/src/client.c new file mode 100644 index 00000000..d97c8279 --- /dev/null +++ b/client/lib/src/client.c @@ -0,0 +1,9 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Just check C compilation when header is included. +#include "cc_mqttsn_client/client.h" diff --git a/client/lib/src/op/Op.cpp b/client/lib/src/op/Op.cpp index de2af082..288a9ab8 100644 --- a/client/lib/src/op/Op.cpp +++ b/client/lib/src/op/Op.cpp @@ -34,7 +34,7 @@ namespace Op::Op(ClientImpl& client) : m_client(client), - m_responseTimeoutMs(client.configState().m_responseTimeoutMs), + m_retryPeriod(client.configState().m_retryPeriod), m_retryCount(client.configState().m_retryCount) { } diff --git a/client/lib/src/op/Op.h b/client/lib/src/op/Op.h index 910fe702..465509a2 100644 --- a/client/lib/src/op/Op.h +++ b/client/lib/src/op/Op.h @@ -53,14 +53,14 @@ class Op : public ProtMsgHandler terminateOpImpl(status); } - unsigned getResponseTimeout() const + unsigned getRetryPeriod() const { - return m_responseTimeoutMs; + return m_retryPeriod; } - void setResponseTimeout(unsigned ms) + void setRetryPeriod(unsigned ms) { - m_responseTimeoutMs = ms; + m_retryPeriod = ms; } unsigned getRetryCount() const @@ -79,6 +79,11 @@ class Op : public ProtMsgHandler return (qos <= static_cast(Config::MaxQos)); } + ClientImpl& client() + { + return m_client; + } + protected: explicit Op(ClientImpl& client); @@ -91,11 +96,6 @@ class Op : public ProtMsgHandler void releasePacketId(std::uint16_t id); void decRetryCount(); - ClientImpl& client() - { - return m_client; - } - const ClientImpl& client() const { return m_client; @@ -139,7 +139,7 @@ class Op : public ProtMsgHandler // bool verifyPubTopicInternal(const char* topic, bool outgoing); ClientImpl& m_client; - unsigned m_responseTimeoutMs = 0U; + unsigned m_retryPeriod = 0U; unsigned m_retryCount = 0U; }; diff --git a/client/lib/src/op/SearchOp.cpp b/client/lib/src/op/SearchOp.cpp index e71b2d2a..99370a49 100644 --- a/client/lib/src/op/SearchOp.cpp +++ b/client/lib/src/op/SearchOp.cpp @@ -115,7 +115,7 @@ void SearchOp::completeOpInternal(CC_MqttsnAsyncOpStatus status, const CC_Mqttsn void SearchOp::restartTimer() { - m_timer.wait(getResponseTimeout(), &SearchOp::opTimeoutCb, this); + m_timer.wait(getRetryPeriod(), &SearchOp::opTimeoutCb, this); } CC_MqttsnErrorCode SearchOp::sendInternal() diff --git a/client/lib/src/op/SearchOp.h b/client/lib/src/op/SearchOp.h index f3cae75e..a260412d 100644 --- a/client/lib/src/op/SearchOp.h +++ b/client/lib/src/op/SearchOp.h @@ -31,6 +31,16 @@ class SearchOp final : public Op using Base::handle; virtual void handle(GwinfoMsg& msg) override; + void setBroadcastRadius(unsigned value) + { + m_radius = value; + } + + unsigned getBroadcastRadius() const + { + return m_radius; + } + protected: virtual Type typeImpl() const override; virtual void terminateOpImpl(CC_MqttsnAsyncOpStatus status) override; diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index 25aa5eac..c8812fd7 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -74,20 +74,20 @@ void cc_mqttsn_##NAME##client_process_data(CC_MqttsnClientHandle client, const u CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_default_retry_period(CC_MqttsnClientHandle client, unsigned value) { COMMS_ASSERT(client != nullptr); - static const unsigned MaxValue = std::numeric_limits::max() / 1000U; + static const unsigned MaxValue = std::numeric_limits::max() / 1000U; if (MaxValue < value) { clientFromHandle(client)->errorLog("The retry period value is too high"); return CC_MqttsnErrorCode_BadParam; } - clientFromHandle(client)->configState().m_responseTimeoutMs = value * 1000U; + clientFromHandle(client)->configState().m_retryPeriod = value * 1000U; return CC_MqttsnErrorCode_Success; } unsigned cc_mqttsn_##NAME##client_get_default_retry_period(CC_MqttsnClientHandle client) { COMMS_ASSERT(client != nullptr); - return clientFromHandle(client)->configState().m_responseTimeoutMs; + return clientFromHandle(client)->configState().m_retryPeriod; } CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_default_retry_count(CC_MqttsnClientHandle client, unsigned value) @@ -127,24 +127,229 @@ unsigned cc_mqttsn_##NAME##client_get_broadcast_radius(CC_MqttsnClientHandle cli return clientFromHandle(client)->configState().m_broadcastRadius; } +unsigned cc_mqttsn_##NAME##client_get_available_gateways_count(CC_MqttsnClientHandle client) +{ + COMMS_ASSERT(client != nullptr); + return static_cast(clientFromHandle(client)->clientState().m_gwInfos.size()); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_get_available_gateway_info(CC_MqttsnClientHandle client, unsigned idx, CC_MqttsnGatewayInfo& info) +{ + COMMS_ASSERT(client != nullptr); + auto& gwInfos = clientFromHandle(client)->clientState().m_gwInfos; + if (gwInfos.size() <= idx) { + clientFromHandle(client)->errorLog("The gateway info index is too high"); + return CC_MqttsnErrorCode_BadParam; + } + + auto& storedInfo = gwInfos[idx]; + info = CC_MqttsnGatewayInfo(); + info.m_gwId = storedInfo.m_gwId; + if (!storedInfo.m_addr.empty()) { + info.m_addr = storedInfo.m_addr.data(); + info.m_addrLen = static_cast(storedInfo.m_addr.size()); + } + + return CC_MqttsnErrorCode_Success; +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_available_gateway_info(CC_MqttsnClientHandle client, const CC_MqttsnGatewayInfo* info) +{ + COMMS_ASSERT(client != nullptr); + auto* clientImpl = clientFromHandle(client); + if (info == nullptr) { + clientImpl->errorLog("The gateway info is not provided"); + return CC_MqttsnErrorCode_BadParam; + } + + auto& gwInfos = clientImpl->clientState().m_gwInfos; + auto iter = + std::find_if( + gwInfos.begin(), gwInfos.end(), + [info](auto& storedInfo) + { + return storedInfo.m_gwId = info->m_gwId; + }); + + auto updateFunc = + [clientImpl, info](cc_mqttsn_client::ClientState::GwInfo& storedInfo) + { + if (storedInfo.m_addr.max_size() < info->m_addrLen) { + clientImpl->errorLog("The address cannot be stored"); + return CC_MqttsnErrorCode_OutOfMemory; + } + + storedInfo.m_gwId = info->m_gwId; + storedInfo.m_addr.assign(info->m_addr, info->m_addr + info->m_addrLen); + return CC_MqttsnErrorCode_Success; + }; + + if (iter != gwInfos.end()) { + return updateFunc(*iter); + } + + if (gwInfos.max_size() <= gwInfos.size()) { + clientImpl->errorLog("Cannot store any more gateways"); + return CC_MqttsnErrorCode_OutOfMemory; + } + + gwInfos.resize(gwInfos.size() + 1U); + auto result = updateFunc(gwInfos.back()); + if (result != CC_MqttsnErrorCode_Success) { + gwInfos.pop_back(); + } + + return result; +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_discard_available_gateway_info(CC_MqttsnClientHandle client, unsigned char gwId) +{ + COMMS_ASSERT(client != nullptr); + auto* clientImpl = clientFromHandle(client); + auto& gwInfos = clientImpl->clientState().m_gwInfos; + auto iter = + std::find_if( + gwInfos.begin(), gwInfos.end(), + [gwId](auto& storedInfo) + { + return storedInfo.m_gwId = gwId; + }); + + if (iter == gwInfos.end()) { + clientImpl->errorLog("Uknown gateway ID"); + return CC_MqttsnErrorCode_BadParam; + } + + gwInfos.erase(iter); + return CC_MqttsnErrorCode_Success; +} + +void cc_mqttsn_##NAME##client_discard_all_gateway_infos(CC_MqttsnClientHandle client) +{ + COMMS_ASSERT(client != nullptr); + clientFromHandle(client)->clientState().m_gwInfos.clear(); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_default_gw_adv_duration(CC_MqttsnClientHandle client, unsigned ms) +{ + COMMS_ASSERT(client != nullptr); + auto* clientImpl = clientFromHandle(client); + if (ms == 0U) { + clientImpl->errorLog("The default gatway advertise duration must be greater than 0"); + return CC_MqttsnErrorCode_BadParam; + } + + clientImpl->configState().m_gwAdvTimeoutMs = ms; + return CC_MqttsnErrorCode_Success; +} + +unsigned cc_mqttsn_##NAME##client_get_default_gw_adv_duration(CC_MqttsnClientHandle client) +{ + COMMS_ASSERT(client != nullptr); + return clientFromHandle(client)->configState().m_gwAdvTimeoutMs; +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_allowed_adv_losses(CC_MqttsnClientHandle client, unsigned count) +{ + COMMS_ASSERT(client != nullptr); + clientFromHandle(client)->configState().m_allowedAdvLosses = count; + return CC_MqttsnErrorCode_Success; +} + +unsigned cc_mqttsn_##NAME##client_get_allowed_adv_losses(CC_MqttsnClientHandle client) +{ + COMMS_ASSERT(client != nullptr); + return clientFromHandle(client)->configState().m_allowedAdvLosses; +} + CC_MqttsnSearchHandle cc_mqttsn_##NAME##client_search_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec) { COMMS_ASSERT(client != nullptr); return handleFromSearchOp(clientFromHandle(client)->searchPrepare(ec)); } +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_set_retry_period(CC_MqttsnSearchHandle handle, unsigned ms) +{ + COMMS_ASSERT(handle != nullptr); + if (ms == 0U) { + searchOpFromHandle(handle)->client().errorLog("The retry period must be greater than 0"); + return CC_MqttsnErrorCode_BadParam; + } + + COMMS_ASSERT(handle != nullptr); + searchOpFromHandle(handle)->setRetryPeriod(ms); + return CC_MqttsnErrorCode_Success; +} + +unsigned cc_mqttsn_##NAME##client_search_get_retry_period(CC_MqttsnSearchHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return searchOpFromHandle(handle)->getRetryPeriod(); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_set_retry_count(CC_MqttsnSearchHandle handle, unsigned count) +{ + COMMS_ASSERT(handle != nullptr); + if (count == 0U) { + searchOpFromHandle(handle)->client().errorLog("The retry count must be greater than 0"); + return CC_MqttsnErrorCode_BadParam; + } + + searchOpFromHandle(handle)->setRetryCount(count); + return CC_MqttsnErrorCode_Success; +} + +unsigned cc_mqttsn_##NAME##client_search_get_retry_count(CC_MqttsnSearchHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return searchOpFromHandle(handle)->getRetryCount(); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_set_broadcast_radius(CC_MqttsnSearchHandle handle, unsigned broadcastRadius) +{ + COMMS_ASSERT(handle != nullptr); + if (broadcastRadius == 0U) { + searchOpFromHandle(handle)->client().errorLog("The broadcast radius must be greater than 0"); + return CC_MqttsnErrorCode_BadParam; + } + + searchOpFromHandle(handle)->setBroadcastRadius(broadcastRadius); + return CC_MqttsnErrorCode_Success; +} + +unsigned cc_mqttsn_##NAME##client_search_get_broadcastRadius(CC_MqttsnSearchHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return searchOpFromHandle(handle)->getBroadcastRadius(); +} + CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_send(CC_MqttsnSearchHandle handle, CC_MqttsnSearchCompleteCb cb, void* cbData) { COMMS_ASSERT(handle != nullptr); return searchOpFromHandle(handle)->send(cb, cbData); } +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search( + CC_MqttsnClientHandle handle, + CC_MqttsnSearchCompleteCb cb, + void* cbData) +{ + auto ec = CC_MqttsnErrorCode_Success; + auto search = cc_mqttsn_##NAME##client_search_prepare(handle, &ec); + if (search == nullptr) { + return ec; + } + + return cc_mqttsn_##NAME##client_search_send(search, cb, cbData); +} + CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_cancel(CC_MqttsnSearchHandle handle) { COMMS_ASSERT(handle != nullptr); return searchOpFromHandle(handle)->cancel(); } + // --------------------- Callbacks --------------------- void cc_mqttsn_##NAME##client_set_next_tick_program_callback( diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index 367d0dc4..2af792ee 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -16,11 +16,21 @@ extern "C" { #endif // #ifdef __cplusplus +/// @defgroup global "Global Data Types and Functions" +/// @defgroup client "Client Data Types and Functions" +/// @defgroup search "Search Operation Data Types and Functions" +/// @defgroup connect "Connect Operation Data Types and Functions" +/// @defgroup disconnect "Disconnect Operation Data Types and Functions" +/// @defgroup subscribe "Subscribe Operation Data Types and Functions" +/// @defgroup unsubscribe "Unsubscribe Operation Data Types and Functions" +/// @defgroup publish "Publish Operation Data Types and Functions" + /// @brief Allocate new client. /// @details When work with the client is complete, @ref cc_mqttsn_##NAME##client_free() /// function must be invoked. /// @return Handle to allocated client object. This handle needs to be passed /// as first parameter to all other API functions. +/// @ingroup client CC_MqttsnClientHandle cc_mqttsn_##NAME##client_alloc(); /// @brief Free previously allocated client. @@ -28,6 +38,7 @@ CC_MqttsnClientHandle cc_mqttsn_##NAME##client_alloc(); /// needed, the client data structes allocated with /// cc_mqttsn_##NAME##client_alloc() must be released using this function. /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @ingroup client void cc_mqttsn_##NAME##client_free(CC_MqttsnClientHandle client); /// @brief Notify client about requested time expiry. @@ -42,6 +53,7 @@ void cc_mqttsn_##NAME##client_free(CC_MqttsnClientHandle client); /// to send new data to the gateway. /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[in] ms Number of elapsed @b milliseconds. +/// @ingroup client void cc_mqttsn_##NAME##client_tick(CC_MqttsnClientHandle client, unsigned ms); /// @brief Provide data, received over I/O link, to the library for processing. @@ -55,6 +67,7 @@ void cc_mqttsn_##NAME##client_tick(CC_MqttsnClientHandle client, unsigned ms); /// @note According to the MQTT-SN specification every message should be sent in a separate /// single datagram packet. This function assumes such behaviour and "consumes" /// all the provided data discarding any additional bytes after the message (if exist). +/// @ingroup client void cc_mqttsn_##NAME##client_process_data(CC_MqttsnClientHandle client, const unsigned char* buf, unsigned bufLen); /// @brief Set retry period to wait between resending unacknowledged message to the gateway (@b Tretry from spec). @@ -64,11 +77,14 @@ void cc_mqttsn_##NAME##client_process_data(CC_MqttsnClientHandle client, const u /// @b 10 seconds. /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[in] value Number of @b seconds to wait before making an attempt to resend. +/// @return Result code of the call. +/// @ingroup client CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_default_retry_period(CC_MqttsnClientHandle client, unsigned value); /// @brief Set configured retry period to wait between resending unacknowledged message to the gateway (@b Tretry from spec). /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @see @ref cc_mqttsn_##NAME##client_set_default_retry_period(). +/// @ingroup client unsigned cc_mqttsn_##NAME##client_get_default_retry_period(CC_MqttsnClientHandle client); /// @brief Set number of retry attempts to perform before reporting unsuccessful result of the operation (@b Nretry from spec). @@ -78,11 +94,14 @@ unsigned cc_mqttsn_##NAME##client_get_default_retry_period(CC_MqttsnClientHandle /// is @b 3. /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[in] value Number of retry attempts. +/// @return Result code of the call. +/// @ingroup client CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_default_retry_count(CC_MqttsnClientHandle client, unsigned value); /// @brief Get configured number of retry attempts to perform before reporting unsuccessful result of the operation (@b Nretry from spec). /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @see @ref cc_mqttsn_##NAME##client_set_default_retry_count() +/// @ingroup client unsigned cc_mqttsn_##NAME##client_get_default_retry_count(CC_MqttsnClientHandle client); /// @brief Set broadcast radius. @@ -91,23 +110,143 @@ unsigned cc_mqttsn_##NAME##client_get_default_retry_count(CC_MqttsnClientHandle /// set using this function. Default radius value is @b 3. /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[in] value Broadcast radius. +/// @return Result code of the call. /// @pre The broadcast value cannot exceed 255. +/// @ingroup client CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_broadcast_radius(CC_MqttsnClientHandle client, unsigned value); /// @brief Get current broadcast radius configuration. /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @see @ref cc_mqttsn_##NAME##client_set_broadcast_radius() +/// @ingroup client unsigned cc_mqttsn_##NAME##client_get_broadcast_radius(CC_MqttsnClientHandle client); +/// @brief Get number of available gateways. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @see @ref cc_mqttsn_##NAME##client_get_available_gateway_info() +/// @ingroup client +unsigned cc_mqttsn_##NAME##client_get_available_gateways_count(CC_MqttsnClientHandle client); + +/// @brief Retrieve stored available gateway information +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[in] idx Index of the available gateway information. +/// @param[out] info Stored gateway information. +/// @return Result code of the call. +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_get_available_gateway_info(CC_MqttsnClientHandle client, unsigned idx, CC_MqttsnGatewayInfo* info); + +/// @brief Update stored available gateway information +/// @details The library will look for the inner data structure for the information +/// containing the reported @ref CC_MqttsnGatewayInfo::m_gwId "gateway id" and update it +/// accordingly. If no such information is present, new entry is going to be added. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[in] info Updated gateway information. +/// @return Result code of the call. +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_available_gateway_info(CC_MqttsnClientHandle client, const CC_MqttsnGatewayInfo* info); + +/// @brief Discard stored available gateway information +/// @details The library will look for the inner data structure for the information +/// containing the reported @ref CC_MqttsnGatewayInfo::m_gwId "gateway id" and remove it if dound. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[in] gwId Gateway ID. +/// @return Result code of the call. +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_discard_available_gateway_info(CC_MqttsnClientHandle client, unsigned char gwId); + +/// @brief Discard stored information on all available gateways. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +void cc_mqttsn_##NAME##client_discard_all_gateway_infos(CC_MqttsnClientHandle client); + +/// @brief Set default gateway advertise duration. +/// @details When the client receives @b GWINFO message before @b ADVERTISE, the +/// gateway is "alive", but its expiry timeout is not known yet. There is +/// a need to assume some default value. Use this function to update it. +/// Defaults to 15 * 60 * 1000 = 15 minutes. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[in] ms Default advertise duration in milliseconds. +/// @return Result code of the call. +/// @ingroup client +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_default_gw_adv_duration(CC_MqttsnClientHandle client, unsigned ms); + +/// @brief Get current default gateway advertise configuration. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @see @ref cc_mqttsn_##NAME##client_set_default_gw_adv_duration() +/// @ingroup client +unsigned cc_mqttsn_##NAME##client_get_default_gw_adv_duration(CC_MqttsnClientHandle client); + +/// @brief Set number of allowed @b ADVERTISE message misses. +/// @details When the client doesn't receive @b ADVERTISE message in time, it +/// maybe due to packet loss and not necessarily because gateway is offline. +/// This function configures number of @b ADVERTISE packet losses before the +/// gateway is considered to be offline. When set to @b 0, means gateway is +/// offline if the next expected @b ADVERTISE doesn't arrive in time. When +/// set to @b 1, means one @b ADVERTISE can be missed and if the second one +/// doesn't arrive in time the gateway is considered to be offline. +/// Defaults to @b 1. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[in] count Number of allowed @b ADVERTISE message losses. +/// @return Result code of the call. +/// @ingroup client +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_allowed_adv_losses(CC_MqttsnClientHandle client, unsigned count); + +/// @brief Get current configuration of allowed @b ADVERTISE message misses. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @see @ref cc_mqttsn_##NAME##client_set_allowed_adv_losses() +/// @ingroup client +unsigned cc_mqttsn_##NAME##client_get_allowed_adv_losses(CC_MqttsnClientHandle client); + /// @brief Prepare "search" operation. /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[out] ec Error code reporting result of the operation. Can be NULL. /// @return Handle of the "search" operation, will be NULL in case of failure. To analyze the reason failure use "ec" output parameter. /// @post The "search" operation is allocated, use either @ref cc_mqttsn_##NAME##client_search_send() /// or @ref cc_mqttsn_##NAME##client_search_cancel() to prevent memory leaks. -/// @ingroup connect +/// @ingroup search CC_MqttsnSearchHandle cc_mqttsn_##NAME##client_search_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec); +/// @brief Configure the retry period for the "search" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_search_prepare() function. +/// @param[in] ms Retry period in @b milliseconds. +/// @return Result code of the call. +/// @ingroup search +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_set_retry_period(CC_MqttsnSearchHandle handle, unsigned ms); + +/// @brief Retrieve the configured retry period for the "search" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_search_prepare() function. +/// @return Retry period duration in @b milliseconds. +/// @ingroup search +unsigned cc_mqttsn_##NAME##client_search_get_retry_period(CC_MqttsnSearchHandle handle); + +/// @brief Configure the retry count for the "search" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_search_prepare() function. +/// @param[in] count Number of retries. +/// @return Result code of the call. +/// @ingroup search +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_set_retry_count(CC_MqttsnSearchHandle handle, unsigned count); + +/// @brief Retrieve the configured retry count for the "search" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_search_prepare() function. +/// @return Number of retries. +/// @ingroup search +unsigned cc_mqttsn_##NAME##client_search_get_retry_count(CC_MqttsnSearchHandle handle); + +/// @brief Configure the broadcast radius for the "search" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_search_prepare() function. +/// @param[in] broadcastRadius Broadcast radius. +/// @return Result code of the call. +/// @ingroup search +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_set_broadcast_radius(CC_MqttsnSearchHandle handle, unsigned broadcastRadius); + +/// @brief Retrieve the configured broadcast radius for the "search" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_search_prepare() function. +/// @return Broadcast radius. +/// @ingroup search +unsigned cc_mqttsn_##NAME##client_search_get_broadcast_radius(CC_MqttsnSearchHandle handle); + +/// @brief Retrieve the configured retry count for the "search" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_search_prepare() function. +/// @return Number of retries. +/// @ingroup search +unsigned cc_mqttsn_##NAME##client_search_get_retry_count(CC_MqttsnSearchHandle handle); + /// @brief Send the "search" operation /// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_search_prepare() function. /// @param[in] cb Callback to be invoked when "search" operation is complete. @@ -117,7 +256,7 @@ CC_MqttsnSearchHandle cc_mqttsn_##NAME##client_search_prepare(CC_MqttsnClientHan /// @post The handle of the "search" operation can be discarded. /// @post The provided callback will be invoked when the "search" operation is complete if and only if /// the function returns @ref CC_MqttsnErrorCode_Success. -/// @ingroup connect +/// @ingroup search CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_send(CC_MqttsnSearchHandle handle, CC_MqttsnSearchCompleteCb cb, void* cbData); /// @brief Cancel the allocated "search" operation @@ -126,9 +265,25 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_send(CC_MqttsnSearchHandle ha /// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_search_prepare() function. /// @return Result code of the call. /// @post The handle of the "search" operation is no longer valid and must be discarded. -/// @ingroup connect +/// @ingroup search CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_cancel(CC_MqttsnSearchHandle handle); +/// @brief Prepare and send "search" request in one go +/// @details Abstracts away sequence of the following functions invocation: +/// @li @ref cc_mqttsn_##NAME##client_search_prepare() +/// @li @ref cc_mqttsn_##NAME##client_search_send() +/// +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[in] cb Callback to be invoked when "search" operation is complete. +/// @param[in] cbData Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +/// @return Result code of the call. +/// @ingroup search +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search( + CC_MqttsnClientHandle handle, + CC_MqttsnSearchCompleteCb cb, + void* cbData); + // --------------------- Callbacks --------------------- /// @brief Set callback to call when time measurement is required. From 3f2c2f24b760e5adbf4590cd6657223485263d9b Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 31 May 2024 08:08:35 +1000 Subject: [PATCH 037/106] Fixing build. --- client/lib/CMakeLists.txt | 30 +++++++++++++++++-- .../{src/client.c => templ/client.c.templ} | 2 +- client/lib/templ/client.h.templ | 6 ---- 3 files changed, 28 insertions(+), 10 deletions(-) rename client/lib/{src/client.c => templ/client.c.templ} (89%) diff --git a/client/lib/CMakeLists.txt b/client/lib/CMakeLists.txt index 73b59d9e..c7f80ad3 100644 --- a/client/lib/CMakeLists.txt +++ b/client/lib/CMakeLists.txt @@ -8,6 +8,7 @@ endif () set (HEADER_TEMPL ${CMAKE_CURRENT_SOURCE_DIR}/templ/client.h.templ) set (SRC_TEMPL ${CMAKE_CURRENT_SOURCE_DIR}/templ/client.cpp.templ) +set (C_TEMPL ${CMAKE_CURRENT_SOURCE_DIR}/templ/client.c.templ) set (CONFIG_TEMPL ${CMAKE_CURRENT_SOURCE_DIR}/templ/Config.h.templ) set (PROT_OPTS_TEMPL ${CMAKE_CURRENT_SOURCE_DIR}/templ/ProtocolOptions.h.templ) set (TEMPL_PROCESS_SCRIPT ${PROJECT_SOURCE_DIR}/cmake/ProcessTemplate.cmake) @@ -39,6 +40,7 @@ function (gen_lib_mqttsn_client config_file) set (header_output ${CMAKE_CURRENT_BINARY_DIR}/${dir}/${name}client.h) set (src_output ${CMAKE_CURRENT_BINARY_DIR}/${dir}/${name}client.cpp) + set (c_output ${CMAKE_CURRENT_BINARY_DIR}/${dir}/${name}client.c) set (config_output ${CMAKE_CURRENT_BINARY_DIR}/${dir}/Config.h) set (prot_opts_output ${CMAKE_CURRENT_BINARY_DIR}/${dir}/ProtocolOptions.h) @@ -90,6 +92,29 @@ function (gen_lib_mqttsn_client config_file) # --------------------------------- + add_custom_command( + OUTPUT "${c_output}" + COMMAND ${CMAKE_COMMAND} + -DIN_FILE="${C_TEMPL}" + -DOUT_FILE="${c_output}" + -DNAME="${name}" + -P ${TEMPL_PROCESS_SCRIPT} + DEPENDS ${C_TEMPL} ${TEMPL_PROCESS_SCRIPT} + ) + + set_source_files_properties( + ${c_output} + PROPERTIES GENERATED TRUE + ) + + set (c_tgt_name "${name}client.c.tgt") + add_custom_target( + ${c_tgt_name} + DEPENDS "${c_output}" ${C_TEMPL} ${TEMPL_PROCESS_SCRIPT} + ) + + # --------------------------------- + add_custom_command( OUTPUT "${config_output}" COMMAND ${CMAKE_COMMAND} @@ -142,11 +167,10 @@ function (gen_lib_mqttsn_client config_file) set (src src/op/Op.cpp src/op/SearchOp.cpp - src/client.c src/ClientImpl.cpp src/TimerMgr.cpp ) - add_library (${lib_name} ${src} ${src_output}) + add_library (${lib_name} ${src} ${src_output} ${c_output}) add_library (cc::${lib_name} ALIAS ${lib_name}) target_link_libraries(${lib_name} PRIVATE cc::cc_mqttsn cc::comms) target_include_directories( @@ -162,7 +186,7 @@ function (gen_lib_mqttsn_client config_file) ${lib_name} PROPERTIES INTERFACE_LINK_LIBRARIES "" ) - add_dependencies(${lib_name} ${header_tgt_name} ${src_tgt_name} ${config_tgt_name} ${prot_opts_tgt_name}) + add_dependencies(${lib_name} ${header_tgt_name} ${src_tgt_name} ${c_tgt_name} ${config_tgt_name} ${prot_opts_tgt_name}) if (CC_MQTTSN_CLIENT_LIB_FORCE_PIC) set_property(TARGET ${lib_name} PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/client/lib/src/client.c b/client/lib/templ/client.c.templ similarity index 89% rename from client/lib/src/client.c rename to client/lib/templ/client.c.templ index d97c8279..def484f2 100644 --- a/client/lib/src/client.c +++ b/client/lib/templ/client.c.templ @@ -6,4 +6,4 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. // Just check C compilation when header is included. -#include "cc_mqttsn_client/client.h" +#include "##NAME##client.h" diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index 2af792ee..0469fc09 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -241,12 +241,6 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_set_broadcast_radius(CC_Mqtts /// @ingroup search unsigned cc_mqttsn_##NAME##client_search_get_broadcast_radius(CC_MqttsnSearchHandle handle); -/// @brief Retrieve the configured retry count for the "search" operation. -/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_search_prepare() function. -/// @return Number of retries. -/// @ingroup search -unsigned cc_mqttsn_##NAME##client_search_get_retry_count(CC_MqttsnSearchHandle handle); - /// @brief Send the "search" operation /// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_search_prepare() function. /// @param[in] cb Callback to be invoked when "search" operation is complete. From ef1022d54183557b152bfe649d9f9c552e788d4a Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 4 Jun 2024 08:39:21 +1000 Subject: [PATCH 038/106] Saving work on client gateway discovery application. --- .github/workflows/actions_build.yml | 4 +- CMakeLists.txt | 6 +- client.old/src/app/CMakeLists.txt | 2 +- client/CMakeLists.txt | 2 +- client/app/CMakeLists.txt | 24 ++ client/app/common/AppClient.cpp | 405 +++++++++++++++++++ client/app/common/AppClient.h | 105 +++++ client/app/common/CMakeLists.txt | 18 + client/app/common/ProgramOptions.cpp | 119 ++++++ client/app/common/ProgramOptions.h | 58 +++ client/app/common/Session.cpp | 65 +++ client/app/common/Session.h | 87 ++++ client/app/common/UdpSession.cpp | 130 ++++++ client/app/common/UdpSession.h | 51 +++ client/app/gw_discover/CMakeLists.txt | 13 + client/app/gw_discover/GwDiscover.cpp | 161 ++++++++ client/app/gw_discover/GwDiscover.h | 38 ++ client/app/gw_discover/main.cpp | 51 +++ client/lib/include/cc_mqttsn_client/common.h | 1 + doc/BUILD.md | 2 +- gateway/CMakeLists.txt | 2 +- gateway/app/CMakeLists.txt | 2 +- 22 files changed, 1336 insertions(+), 10 deletions(-) create mode 100644 client/app/CMakeLists.txt create mode 100644 client/app/common/AppClient.cpp create mode 100644 client/app/common/AppClient.h create mode 100644 client/app/common/CMakeLists.txt create mode 100644 client/app/common/ProgramOptions.cpp create mode 100644 client/app/common/ProgramOptions.h create mode 100644 client/app/common/Session.cpp create mode 100644 client/app/common/Session.h create mode 100644 client/app/common/UdpSession.cpp create mode 100644 client/app/common/UdpSession.h create mode 100644 client/app/gw_discover/CMakeLists.txt create mode 100644 client/app/gw_discover/GwDiscover.cpp create mode 100644 client/app/gw_discover/GwDiscover.h create mode 100644 client/app/gw_discover/main.cpp diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index 21f8f98a..3839c3d6 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -178,7 +178,7 @@ jobs: cmake %GITHUB_WORKSPACE% -A ${{matrix.arch}} -DCMAKE_BUILD_TYPE=${{matrix.type}} -DCMAKE_INSTALL_PREFIX=install ^ -DCMAKE_PREFIX_PATH=${{runner.workspace}}/build/install -DCMAKE_CXX_STANDARD=${{matrix.cpp}} ^ -DBoost_USE_STATIC_LIBS=ON -DCC_MQTTSN_BUILD_UNIT_TESTS=ON ^ - -DCC_MQTTSN_BUILD_CLIENT_APPS=OFF -DCC_MQTTSN_BUILD_GATEWAY_APPS=ON + -DCC_MQTTSN_CLIENT_APPS=OFF -DCC_MQTTSN_GATEWAY_APPS=ON - name: Build Target working-directory: ${{runner.workspace}}/build @@ -235,7 +235,7 @@ jobs: cmake %GITHUB_WORKSPACE% -A ${{matrix.arch}} -DCMAKE_BUILD_TYPE=${{matrix.type}} -DCMAKE_INSTALL_PREFIX=install ^ -DCMAKE_PREFIX_PATH=${{runner.workspace}}/build/install -DCMAKE_CXX_STANDARD=${{matrix.cpp}} ^ -DBoost_USE_STATIC_LIBS=ON -DCC_MQTTSN_BUILD_UNIT_TESTS=ON ^ - -DCC_MQTTSN_BUILD_CLIENT_APPS=OFF -DCC_MQTTSN_BUILD_GATEWAY_APPS=${{env.HAS_BOOST}} + -DCC_MQTTSN_CLIENT_APPS=OFF -DCC_MQTTSN_GATEWAY_APPS=${{env.HAS_BOOST}} env: HAS_BOOST: "${{ matrix.arch == 'x64' && 'ON' || 'OFF' }}" diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ddb424e..7347d87f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,9 +3,9 @@ project ("cc_mqttsn_libs") option (CC_MQTTSN_WARN_AS_ERR "Do NOT treat warning as error" ON) option (CC_MQTTSN_CLIENT_DEFAULT_LIB "Build and install default variant of MQTT-SN Client library" ON) -option (CC_MQTTSN_BUILD_GATEWAY "Build and install MQTT-SN client library(ies) and applications" ON) -option (CC_MQTTSN_BUILD_CLIENT_APPS "Build and install client applications" ${CC_MQTTSN_CLIENT_DEFAULT_LIB}) -option (CC_MQTTSN_BUILD_GATEWAY_APPS "Build and install gateway applications" ${CC_MQTTSN_BUILD_GATEWAY}) +option (CC_MQTTSN_GATEWAY "Build and install MQTT-SN client library(ies) and applications" ON) +option (CC_MQTTSN_CLIENT_APPS "Build and install client applications" ${CC_MQTTSN_CLIENT_DEFAULT_LIB}) +option (CC_MQTTSN_GATEWAY_APPS "Build and install gateway applications" ${CC_MQTTSN_GATEWAY}) option (CC_MQTTSN_BUILD_UNIT_TESTS "Build unittests." OFF) option (CC_MQTTSN_UNIT_TEST_WITH_VALGRIND "Disable valgrind in unittests." OFF) option (CC_MQTTSN_USE_CCACHE "Use ccache on unix system" OFF) diff --git a/client.old/src/app/CMakeLists.txt b/client.old/src/app/CMakeLists.txt index c991e36c..60e72cbd 100644 --- a/client.old/src/app/CMakeLists.txt +++ b/client.old/src/app/CMakeLists.txt @@ -1,4 +1,4 @@ -if ((NOT CC_MQTTSN_CLIENT_DEFAULT_LIB) OR (NOT CC_MQTTSN_BUILD_CLIENT_APPS)) +if ((NOT CC_MQTTSN_CLIENT_DEFAULT_LIB) OR (NOT CC_MQTTSN_CLIENT_APPS)) return () endif () diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index c40d13b5..cdfef8ab 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -1,2 +1,2 @@ add_subdirectory (lib) -#add_subdirectory (app) \ No newline at end of file +add_subdirectory (app) \ No newline at end of file diff --git a/client/app/CMakeLists.txt b/client/app/CMakeLists.txt new file mode 100644 index 00000000..f022edb2 --- /dev/null +++ b/client/app/CMakeLists.txt @@ -0,0 +1,24 @@ +if (NOT CC_MQTTSN_CLIENT_APPS) + return () +endif () + +if (NOT CC_MQTTSN_CLIENT_DEFAULT_LIB) + message (WARNING "Cannot compile applications due to missing default library") + return () +endif () + +find_package (Threads REQUIRED) +find_package (Boost REQUIRED COMPONENTS system program_options) + +set (EXTRA_BOOST_TARGETS) +if (WIN32) + find_package (Boost REQUIRED COMPONENTS date_time regex) + set (EXTRA_BOOST_TARGETS Boost::date_time Boost::regex) +endif () + +set (COMMON_APPS_LIB "cc_mqttsn_client_apps_lib") + +add_subdirectory (common) +add_subdirectory (gw_discover) +#add_subdirectory (pub) +#add_subdirectory (sub) \ No newline at end of file diff --git a/client/app/common/AppClient.cpp b/client/app/common/AppClient.cpp new file mode 100644 index 00000000..018d6410 --- /dev/null +++ b/client/app/common/AppClient.cpp @@ -0,0 +1,405 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "AppClient.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cc_mqttsn_client_app +{ + +namespace +{ + +AppClient* asThis(void* data) +{ + return reinterpret_cast(data); +} + +// std::string toString(CC_MqttsnQoS val) +// { +// static const std::string Map[] = { +// /* CC_MqttsnQoS_AtMostOnceDelivery */ "QoS0 - At Most Once Delivery", +// /* CC_MqttsnQoS_AtLeastOnceDelivery */ "QoS1 - At Least Once Delivery", +// /* CC_MqttsnQoS_ExactlyOnceDelivery */ "QoS2 - Exactly Once Delivery", +// }; +// static constexpr std::size_t MapSize = std::extent::value; +// static_assert(MapSize == CC_MqttsnQoS_ValuesLimit); + +// auto idx = static_cast(val); +// if (MapSize <= idx) { +// assert(false); // Should not happen +// return std::to_string(val); +// } + +// return Map[idx] + " (" + std::to_string(val) + ')'; +// } + +// void printQos(const char* prefix, CC_MqttsnQoS val) +// { +// std::cout << "\t" << prefix << ": " << toString(val) << '\n'; +// } + +// void printBool(const char* prefix, bool val) +// { +// std::cout << '\t' << prefix << ": " << std::boolalpha << val << '\n'; +// } + +// void printConnectReturnCode(CC_MqttsnConnectReturnCode val) +// { +// std::cout << "\tReturn Code: " << AppClient::toString(val) << '\n'; +// } + +// void printSubscribeReturnCode(CC_MqttsnSubscribeReturnCode val) +// { +// std::cout << "\tReturn Code: " << AppClient::toString(val) << '\n'; +// } + +} // namespace + +bool AppClient::start(int argc, const char* argv[]) +{ + if (!m_opts.parseArgs(argc, argv)) { + logError() << "Failed to parse arguments." << std::endl; + return false; + } + + if (m_opts.helpRequested()) { + std::cout << "Usage: " << argv[0] << " [options...]" << '\n'; + m_opts.printHelp(); + io().stop(); + return true; + } + + if (!createSession()) { + return false; + } + + return startImpl(); +} + + +std::string AppClient::toString(CC_MqttsnErrorCode val) +{ + static const std::string Map[] = { + /* CC_MqttsnErrorCode_Success*/ "Success", + /* CC_MqttsnErrorCode_InternalError */ "Internal Error", + /* CC_MqttsnErrorCode_NotIntitialized */ "Not Intitialized", + /* CC_MqttsnErrorCode_Busy*/ "Busy", + /* CC_MqttsnErrorCode_NotConnected*/ "Not Connected", + /* CC_MqttsnErrorCode_AlreadyConnected */ "Already Connected", + /* CC_MqttsnErrorCode_BadParam*/ "Bad Param", + /* CC_MqttsnErrorCode_InsufficientConfig*/ "Insufficient Config", + /* CC_MqttsnErrorCode_OutOfMemory*/ "Out Of Memory", + /* CC_MqttsnErrorCode_BufferOverflow*/ "Buffer Overflow", + /* CC_MqttsnErrorCode_NotSupported*/ "Not Supported", + /* CC_MqttsnErrorCode_RetryLater*/ "Retry Later", + /* CC_MqttsnErrorCode_Disconnecting*/ "Disconnecting", + /* CC_MqttsnErrorCode_NotSleeping*/ "Not sleeping", + /* CC_MqttsnErrorCode_PreparationLocked*/ "Preparation Locked", + }; + + static constexpr std::size_t MapSize = std::extent::value; + static_assert(MapSize == CC_MqttsnErrorCode_ValuesLimit); + + auto idx = static_cast(val); + if (MapSize <= idx) { + assert(false); // Should not happen + return std::to_string(val); + } + + return Map[idx] + " (" + std::to_string(val) + ')'; +} + +// std::string AppClient::toString(const std::uint8_t* data, unsigned dataLen, bool forceBinary) +// { +// bool binary = forceBinary; +// if (!binary) { +// binary = +// std::any_of( +// data, data + dataLen, +// [](auto byte) +// { +// if (std::isprint(static_cast(byte)) == 0) { +// return true; +// } + +// if (byte > 0x7e) { +// return true; +// } + +// return false; +// }); +// } + +// if (!binary) { +// return std::string(reinterpret_cast(data), dataLen); +// } + +// std::stringstream stream; +// stream << std::hex; +// for (auto idx = 0U; idx < dataLen; ++idx) { +// stream << std::setw(2) << std::setfill('0') << static_cast(data[idx]) << ' '; +// } +// return stream.str(); +// } + +// void AppClient::print(const CC_MqttsnMessageInfo& info, bool printMessage) +// { +// std::cout << "[INFO]: Message Info:\n"; +// if (printMessage) { +// std::cout << +// "\tTopic: " << info.m_topic << '\n' << +// "\tData: " << toString(info.m_data, info.m_dataLen, m_opts.subBinary()) << '\n'; +// } + +// printQos("QoS", info.m_qos); +// printBool("Retained", info.m_retained); +// std::cout << std::endl; +// } + +AppClient::AppClient(boost::asio::io_context& io, int& result) : + m_io(io), + m_result(result), + m_timer(io), + m_client(::cc_mqttsn_client_alloc()) +{ + assert(m_client); + ::cc_mqttsn_client_set_send_output_data_callback(m_client.get(), &AppClient::sendDataCb, this); + ::cc_mqttsn_client_set_message_report_callback(m_client.get(), &AppClient::messageReceivedCb, this); + ::cc_mqttsn_client_set_error_log_callback(m_client.get(), &AppClient::logMessageCb, this); + ::cc_mqttsn_client_set_next_tick_program_callback(m_client.get(), &AppClient::nextTickProgramCb, this); + ::cc_mqttsn_client_set_cancel_next_tick_wait_callback(m_client.get(), &AppClient::cancelNextTickWaitCb, this); +} + +std::ostream& AppClient::logError() +{ + return std::cerr << "ERROR: "; +} + +void AppClient::doTerminate(int result) +{ + m_result = result; + m_io.stop(); +} + +void AppClient::doComplete() +{ + // if (m_opts.willTopic().empty()) { + // auto ec = ::cc_mqttsn_client_disconnect(m_client.get()); + // if (ec != CC_MqttsnErrorCode_Success) { + // logError() << "Failed to send disconnect with ec=" << toString(ec) << std::endl; + // doTerminate(); + // return; + // } + // } + + boost::asio::post( + m_io, + [this]() + { + doTerminate(0); + }); +} + +bool AppClient::startImpl() +{ + return true; +} + + +void AppClient::messageReceivedImpl([[maybe_unused]] const CC_MqttsnMessageInfo* info) +{ +} + +std::vector AppClient::parseBinaryData(const std::string& val) +{ + std::vector result; + result.reserve(val.size()); + auto pos = 0U; + while (pos < val.size()) { + auto ch = val[pos]; + auto addChar = + [&result, &pos, ch]() + { + result.push_back(static_cast(ch)); + ++pos; + }; + + if (ch != '\\') { + addChar(); + continue; + } + + auto nextPos = pos + 1U; + if ((val.size() <= nextPos)) { + addChar(); + continue; + } + + auto nextChar = val[nextPos]; + if (nextChar == '\\') { + // Double backslash (\\) is treated as single one + addChar(); + ++pos; + continue; + } + + if (nextChar != 'x') { + // Not hex byte prefix, treat backslash as regular character + addChar(); + continue; + } + + auto bytePos = nextPos + 1U; + auto byteLen = 2U; + if (val.size() < bytePos + byteLen) { + // Bad hex byte encoding, add characters as is + addChar(); + continue; + } + + try { + auto byte = static_cast(stoul(val.substr(bytePos, byteLen), nullptr, 16)); + result.push_back(byte); + pos = bytePos + byteLen; + continue; + } + catch (...) { + addChar(); + continue; + } + } + + return result; +} + +std::string AppClient::toString(CC_MqttsnAsyncOpStatus val) +{ + static const std::string Map[] = { + /* CC_MqttsnAsyncOpStatus_Complete */ "Complete", + /* CC_MqttsnAsyncOpStatus_InternalError */ "Internal Error", + /* CC_MqttsnAsyncOpStatus_Timeout */ "Timeout", + /* CC_MqttsnAsyncOpStatus_Aborted */ "Aborted", + /* CC_MqttsnAsyncOpStatus_OutOfMemory */ "Out of Memory", + /* CC_MqttsnAsyncOpStatus_BadParam */ "Bad Param", + }; + + static constexpr std::size_t MapSize = std::extent::value; + static_assert(MapSize == CC_MqttsnAsyncOpStatus_ValuesLimit); + + auto idx = static_cast(val); + if (MapSize <= idx) { + assert(false); // Should not happen + return std::to_string(val); + } + + return Map[idx] + " (" + std::to_string(val) + ')'; +} + +void AppClient::nextTickProgramInternal(unsigned duration) +{ + m_lastWaitProgram = Clock::now(); + m_timer.expires_after(std::chrono::milliseconds(duration)); + m_timer.async_wait( + [this, duration](const boost::system::error_code& ec) + { + if (ec == boost::asio::error::operation_aborted) { + return; + } + + if (ec) { + logError() << "Timer error: " << ec.message(); + doTerminate(); + return; + } + + ::cc_mqttsn_client_tick(m_client.get(), duration); + } + ); +} + +unsigned AppClient::cancelNextTickWaitInternal() +{ + boost::system::error_code ec; + m_timer.cancel(ec); + auto now = Clock::now(); + auto diff = std::chrono::duration_cast(now - m_lastWaitProgram).count(); + return static_cast(diff); +} + +void AppClient::sendDataInternal(const unsigned char* buf, unsigned bufLen, unsigned broadcastRadius) +{ + assert(m_session); + m_session->sendData(buf, bufLen, broadcastRadius); +} + +bool AppClient::createSession() +{ + m_session = Session::create(m_io, m_opts); + if (!m_session) { + logError() << "Failed to create network connection session." << std::endl; + return false; + } + + m_session->setDataReportCb( + [this](const Addr& addr, const std::uint8_t* buf, std::size_t bufLen) + { + assert(m_client); + m_lastAddr = addr; + ::cc_mqttsn_client_process_data(m_client.get(), buf, static_cast(bufLen)); + }); + + m_session->setNetworkErrorReportCb( + [this]() + { + assert(m_client); + doTerminate(); + } + ); + + if (!m_session->start()) { + logError() << "Failed to start session." << std::endl; + return false; + } + + return true; +} + +void AppClient::sendDataCb(void* data, const unsigned char* buf, unsigned bufLen, unsigned broadcastRadius) +{ + asThis(data)->sendDataInternal(buf, bufLen, broadcastRadius); +} + +void AppClient::messageReceivedCb(void* data, const CC_MqttsnMessageInfo* info) +{ + asThis(data)->messageReceivedImpl(info); +} + +void AppClient::logMessageCb([[maybe_unused]] void* data, const char* msg) +{ + logError() << msg << std::endl; +} + +void AppClient::nextTickProgramCb(void* data, unsigned duration) +{ + asThis(data)->nextTickProgramInternal(duration); +} + +unsigned AppClient::cancelNextTickWaitCb(void* data) +{ + return asThis(data)->cancelNextTickWaitInternal(); +} + +} // namespace cc_mqttsn_client_app diff --git a/client/app/common/AppClient.h b/client/app/common/AppClient.h new file mode 100644 index 00000000..2285162a --- /dev/null +++ b/client/app/common/AppClient.h @@ -0,0 +1,105 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "ProgramOptions.h" +#include "Session.h" + +#include "client.h" + +#include + +#include +#include + +namespace cc_mqttsn_client_app +{ + +class AppClient +{ + struct ClientDeleter + { + void operator()(CC_MqttsnClient* ptr) + { + ::cc_mqttsn_client_free(ptr); + } + }; + +public: + bool start(int argc, const char* argv[]); + + static std::string toString(CC_MqttsnErrorCode val); + //static std::string toString(CC_MqttsnAsyncOpStatus val); + //void print(const CC_MqttsnMessageInfo& info, bool printMessage = true); + +protected: + using Timer = boost::asio::steady_timer; + using Addr = Session::Addr; + + explicit AppClient(boost::asio::io_context& io, int& result); + ~AppClient() = default; + + CC_MqttsnClientHandle client() + { + return m_client.get(); + } + + boost::asio::io_context& io() + { + return m_io; + } + + ProgramOptions& opts() + { + return m_opts; + } + + static std::ostream& logError(); + + void doTerminate(int result = 1); + void doComplete(); + + virtual bool startImpl(); + virtual void messageReceivedImpl(const CC_MqttsnMessageInfo* info); + + static std::vector parseBinaryData(const std::string& val); + + const Addr& lastAddr() const + { + return m_lastAddr; + } + + static std::string toString(CC_MqttsnAsyncOpStatus val); + +private: + using ClientPtr = std::unique_ptr; + using Clock = Timer::clock_type; + using Timestamp = Timer::time_point; + + void nextTickProgramInternal(unsigned duration); + unsigned cancelNextTickWaitInternal(); + void sendDataInternal(const unsigned char* buf, unsigned bufLen, unsigned broadcastRadius); + bool createSession(); + + static void sendDataCb(void* data, const unsigned char* buf, unsigned bufLen, unsigned broadcastRadius); + static void messageReceivedCb(void* data, const CC_MqttsnMessageInfo* info); + static void logMessageCb(void* data, const char* msg); + static void nextTickProgramCb(void* data, unsigned duration); + static unsigned cancelNextTickWaitCb(void* data); + + boost::asio::io_context& m_io; + int& m_result; + Timer m_timer; + Timestamp m_lastWaitProgram; + ProgramOptions m_opts; + ClientPtr m_client; + SessionPtr m_session; + Addr m_lastAddr; +}; + +} // namespace cc_mqttsn_client_app diff --git a/client/app/common/CMakeLists.txt b/client/app/common/CMakeLists.txt new file mode 100644 index 00000000..32e4fb42 --- /dev/null +++ b/client/app/common/CMakeLists.txt @@ -0,0 +1,18 @@ +set (src + AppClient.cpp + ProgramOptions.cpp + Session.cpp + UdpSession.cpp +) + +add_library(${COMMON_APPS_LIB} STATIC ${src}) +target_link_libraries(${COMMON_APPS_LIB} PUBLIC cc::cc_mqttsn_client Boost::system Boost::program_options ${EXTRA_BOOST_TARGETS} ${CMAKE_THREAD_LIBS_INIT}) +target_include_directories( + ${COMMON_APPS_LIB} BEFORE + PUBLIC + $ +) + +target_compile_options(${COMMON_APPS_LIB} PUBLIC + $<$:/D_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS> +) diff --git a/client/app/common/ProgramOptions.cpp b/client/app/common/ProgramOptions.cpp new file mode 100644 index 00000000..a501f7a7 --- /dev/null +++ b/client/app/common/ProgramOptions.cpp @@ -0,0 +1,119 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "ProgramOptions.h" + +#include + +namespace po = boost::program_options; + +namespace cc_mqttsn_client_app +{ + +namespace +{ + +constexpr std::uint16_t DefaultPort = 1883U; + +} // namespace + + +void ProgramOptions::addCommon() +{ + po::options_description opts("Common Options"); + opts.add_options() + ("help,h", "Display help message") + ("verbose,v", "Verbose output") + ; + + m_desc.add(opts); +} + +void ProgramOptions::addNetwork() +{ + po::options_description opts("Network Options"); + opts.add_options() + ("network-gateway,g", po::value()->default_value("127.0.0.1"), "Gateway address to connect to") + ("network-broadcast,b", po::value()->default_value("255.255.255.255"), "Address to broadcast to") + ("network-port,p", po::value()->default_value(DefaultPort), "Network remove port") + ("network-local-port,P", po::value()->default_value(0), "Network local port") + ; + + m_desc.add(opts); +} + +void ProgramOptions::addDiscover() +{ + po::options_description opts("Gateway Discover Options"); + opts.add_options() + ("discover-exit-on-first,f", "Exit after first discovered gateway.") + ("discover-timeout,t", po::value()->default_value(0), "Terminate after specified number of seconds, 0 means infinite"); + ; + + m_desc.add(opts); +} + +void ProgramOptions::printHelp() +{ + std::cout << m_desc << std::endl; +} + +bool ProgramOptions::parseArgs(int argc, const char* argv[]) +{ + po::store(po::parse_command_line(argc, argv, m_desc), m_vm); + po::notify(m_vm); + + return true; +} + +bool ProgramOptions::helpRequested() const +{ + return m_vm.count("help") > 0U; +} + +bool ProgramOptions::verbose() const +{ + return m_vm.count("verbose") > 0U; +} + +ProgramOptions::ConnectionType ProgramOptions::connectionType() const +{ + // Hardcoded for now + return ConnectionType_Udp; +} + +std::string ProgramOptions::networkAddress() const +{ + return m_vm["network-gateway"].as(); +} + +std::string ProgramOptions::networkBroadcastAddress() const +{ + return m_vm["network-broadcast"].as(); +} + +std::uint16_t ProgramOptions::networkRemotePort() const +{ + return m_vm["network-port"].as(); +} + +std::uint16_t ProgramOptions::networkLocalPort() const +{ + return m_vm["network-local-port"].as(); +} + +bool ProgramOptions::discoverExitOnFirst() const +{ + return m_vm.count("discover-exit-on-first") > 0U; +} + +unsigned ProgramOptions::discoverTimeout() const +{ + return m_vm["discover-timeout"].as(); +} + +} // namespace cc_mqttsn_client_app diff --git a/client/app/common/ProgramOptions.h b/client/app/common/ProgramOptions.h new file mode 100644 index 00000000..bd7079af --- /dev/null +++ b/client/app/common/ProgramOptions.h @@ -0,0 +1,58 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include + +#include +#include +#include + +namespace cc_mqttsn_client_app +{ + +class ProgramOptions +{ +public: + using OptDesc = boost::program_options::options_description; + + enum ConnectionType + { + ConnectionType_Udp, + ConnectionType_ValuesLimit + }; + + void addCommon(); + void addNetwork(); + void addDiscover(); + + void printHelp(); + + bool parseArgs(int argc, const char* argv[]); + + // Common options + bool helpRequested() const; + bool verbose() const; + ConnectionType connectionType() const; + + // Network Options + std::string networkAddress() const; + std::string networkBroadcastAddress() const; + std::uint16_t networkRemotePort() const; + std::uint16_t networkLocalPort() const; + + // Discover Options + bool discoverExitOnFirst() const; + unsigned discoverTimeout() const; + +private: + boost::program_options::variables_map m_vm; + OptDesc m_desc; +}; + +} // namespace cc_mqttsn_client_app diff --git a/client/app/common/Session.cpp b/client/app/common/Session.cpp new file mode 100644 index 00000000..30f6424b --- /dev/null +++ b/client/app/common/Session.cpp @@ -0,0 +1,65 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "Session.h" + +#include "UdpSession.h" + +#include +#include + +namespace cc_mqttsn_client_app +{ + +Session::Ptr Session::create(boost::asio::io_context& io, const ProgramOptions& opts) +{ + using CreateFunc = Ptr (*)(boost::asio::io_context&, const ProgramOptions&); + static const CreateFunc Map[] = { + /* ConnectionType_Udp */ &UdpSession::create, + }; + static constexpr std::size_t MapSize = std::extent::value; + static_assert(MapSize == ProgramOptions::ConnectionType_ValuesLimit); + + auto idx = static_cast(opts.connectionType()); + if (MapSize <= idx) { + return Ptr(); + } + + auto func = Map[idx]; + return func(io, opts); +} + +Session::Session(boost::asio::io_context& io, const ProgramOptions& opts) : + m_io(io), + m_opts(opts) +{ +} + +std::ostream& Session::logError() +{ + return std::cerr << "ERROR: "; +} + +void Session::reportData(const Addr& addr, const std::uint8_t* buf, std::size_t bufLen) +{ + assert(m_dataReportCb); + m_dataReportCb(addr, buf, bufLen); +} + +void Session::reportNetworkError() +{ + if (m_networkError) { + return; + } + + m_networkError = true; + + assert(m_networkErrorReportCb); + m_networkErrorReportCb(); +} + +} // namespace cc_mqttsn_client_app diff --git a/client/app/common/Session.h b/client/app/common/Session.h new file mode 100644 index 00000000..006982b9 --- /dev/null +++ b/client/app/common/Session.h @@ -0,0 +1,87 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "ProgramOptions.h" + +#include + +#include +#include +#include + +namespace cc_mqttsn_client_app +{ + +class Session +{ +public: + using Ptr = std::unique_ptr; + using Addr = std::vector; + + virtual ~Session() = default; + + static Ptr create(boost::asio::io_context& io, const ProgramOptions& opts); + + bool start() + { + return startImpl(); + } + + void sendData(const std::uint8_t* buf, std::size_t bufLen, unsigned broadcastRadius) + { + sendDataImpl(buf, bufLen, broadcastRadius); + } + + using DataReportCb = std::function; + template + void setDataReportCb(TFunc&& func) + { + m_dataReportCb = std::forward(func); + } + + using NetworkErrorReportCb = std::function; + template + void setNetworkErrorReportCb(TFunc&& func) + { + m_networkErrorReportCb = std::forward(func); + } + + +protected: + Session(boost::asio::io_context& io, const ProgramOptions& opts); + + boost::asio::io_context& io() + { + return m_io; + } + + const ProgramOptions& opts() const + { + return m_opts; + } + + static std::ostream& logError(); + + void reportData(const Addr& addr, const std::uint8_t* buf, std::size_t bufLen); + void reportNetworkError(); + + virtual bool startImpl() = 0; + virtual void sendDataImpl(const std::uint8_t* buf, std::size_t bufLen, unsigned broadcastRadius) = 0; + +private: + boost::asio::io_context& m_io; + const ProgramOptions& m_opts; + DataReportCb m_dataReportCb; + NetworkErrorReportCb m_networkErrorReportCb; + bool m_networkError = false; +}; + +using SessionPtr = Session::Ptr; + +} // namespace cc_mqttsn_client_app diff --git a/client/app/common/UdpSession.cpp b/client/app/common/UdpSession.cpp new file mode 100644 index 00000000..cbfc0a48 --- /dev/null +++ b/client/app/common/UdpSession.cpp @@ -0,0 +1,130 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "UdpSession.h" + +#include +#include // TODO: remove + +namespace cc_mqttsn_client_app +{ + +Session::Ptr UdpSession::create(boost::asio::io_context& io, const ProgramOptions& opts) +{ + return Ptr(new UdpSession(io, opts)); +} + +bool UdpSession::startImpl() +{ + boost::asio::ip::udp::resolver resolver(io()); + boost::system::error_code ec; + auto remoteEndpoints = resolver.resolve(opts().networkAddress(), std::to_string(opts().networkRemotePort()), ec); + if (ec) { + logError() << "Failed to resolve remote address: " << ec.message() << std::endl; + return false; + } + + m_remoteEndpoint = *remoteEndpoints.begin(); + + auto broadcastEndpoints = resolver.resolve(opts().networkBroadcastAddress(), std::to_string(opts().networkRemotePort()), ec); + if (ec) { + logError() << "Failed to resolve broadcast address: " << ec.message() << std::endl; + return false; + } + + m_broadcastEndpoint = *broadcastEndpoints.begin(); + + auto localPort = opts().networkLocalPort(); + if (localPort > 0U) { + m_socket.open(boost::asio::ip::udp::v4(), ec); + if (ec) { + logError() << "Failed to open udp socket: " << ec.message() << std::endl; + return false; + } + + m_socket.set_option(boost::asio::socket_base::broadcast(true), ec); + if (ec) { + logError() << "Failed to enable broadcast for UDP socket: " << ec.message() << std::endl; + return false; + } + + + m_socket.bind(Endpoint(boost::asio::ip::udp::v4(), localPort), ec); + if (ec) { + logError() << "Failed to bind local port " << localPort << ": " << ec.message() << std::endl; + return false; + } + } + else { + m_socket.connect(m_remoteEndpoint, ec); + if (ec) { + logError() << "Failed to connect to remote address: " << ec.message() << std::endl; + return false; + } + } + + doRead(); + return true; +} + +void UdpSession::sendDataImpl(const std::uint8_t* buf, std::size_t bufLen, unsigned broadcastRadius) +{ + boost::system::error_code ec; + auto* endpoint = &m_remoteEndpoint; + auto ttl = 128U; + if (broadcastRadius > 0U) { + endpoint = &m_broadcastEndpoint; + ttl = broadcastRadius; + } + + m_socket.set_option(boost::asio::ip::unicast::hops(ttl), ec); + if (ec) { + logError() << "Failed to update outgoing packet TTL: " << ec.message() << std::endl; + } + + auto written = m_socket.send_to(boost::asio::buffer(buf, bufLen), *endpoint, 0, ec); + if (ec) { + if (ec == boost::asio::error::operation_aborted) { + return; + } + + logError() << "Failed to write data: " << ec.message() << std::endl; + reportNetworkError(); + return; + } + + + if (written != bufLen) { + logError() << "Not all data has been written." << std::endl; + } +} + +void UdpSession::doRead() +{ + m_socket.async_receive_from( + boost::asio::buffer(m_inBuf), + m_senderEndpoint, + [this](boost::system::error_code ec, std::size_t bytesCount) + { + if (ec == boost::asio::error::operation_aborted) { + return; + } + + if (ec) { + logError() << "UDP read error: " << ec.message() << std::endl; + reportNetworkError(); + return; + } + + auto remoteAddr = m_senderEndpoint.address().to_v4().to_bytes(); + Addr addrToReport(remoteAddr.begin(), remoteAddr.end()); + reportData(addrToReport, m_inBuf.data(), bytesCount); + doRead(); + }); +} + +} // namespace cc_mqttsn_client_app diff --git a/client/app/common/UdpSession.h b/client/app/common/UdpSession.h new file mode 100644 index 00000000..4828b583 --- /dev/null +++ b/client/app/common/UdpSession.h @@ -0,0 +1,51 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Session.h" + +#include +#include +#include + +namespace cc_mqttsn_client_app +{ + +class UdpSession final : public Session +{ + using Base = Session; +public: + static Ptr create(boost::asio::io_context& io, const ProgramOptions& opts); + +protected: + virtual bool startImpl() override; + virtual void sendDataImpl(const std::uint8_t* buf, std::size_t bufLen, unsigned broadcastRadius) override; + +private: + using Socket = boost::asio::ip::udp::socket; + using Endpoint = Socket::endpoint_type; + using InDataBuf = std::array; + using DataBuf = std::vector; + + UdpSession(boost::asio::io_context& io, const ProgramOptions& opts) : + Base(io, opts), + m_socket(io) + { + } + + void doRead(); + + Socket m_socket; + InDataBuf m_inBuf; + // DataBuf m_buf; + Endpoint m_senderEndpoint; + Endpoint m_remoteEndpoint; + Endpoint m_broadcastEndpoint; +}; + +} // namespace cc_mqttsn_client_app diff --git a/client/app/gw_discover/CMakeLists.txt b/client/app/gw_discover/CMakeLists.txt new file mode 100644 index 00000000..f7f191ed --- /dev/null +++ b/client/app/gw_discover/CMakeLists.txt @@ -0,0 +1,13 @@ +set (name "cc_mqttsn_client_gw_discover") +set (src + main.cpp + GwDiscover.cpp +) + +add_executable(${name} ${src}) +target_link_libraries(${name} ${COMMON_APPS_LIB}) + +install ( + TARGETS ${name} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) \ No newline at end of file diff --git a/client/app/gw_discover/GwDiscover.cpp b/client/app/gw_discover/GwDiscover.cpp new file mode 100644 index 00000000..c3024e29 --- /dev/null +++ b/client/app/gw_discover/GwDiscover.cpp @@ -0,0 +1,161 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla GwDiscoverlic +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "GwDiscover.h" + +#include +#include +#include +#include + +namespace cc_mqttsn_client_app +{ + +namespace +{ + +GwDiscover* asThis(void* data) +{ + return reinterpret_cast(data); +} + +} // namespace + + +GwDiscover::GwDiscover(boost::asio::io_context& io, int& result) : + Base(io, result), + m_timeoutTimer(io) +{ + opts().addCommon(); + opts().addNetwork(); + opts().addDiscover(); +} + +bool GwDiscover::startImpl() +{ + auto timeout = opts().discoverTimeout(); + if (timeout > 0U) { + m_timeoutTimer.expires_after(std::chrono::seconds(timeout)); + m_timeoutTimer.async_wait( + [this](const boost::system::error_code& ec) + { + if (ec == boost::asio::error::operation_aborted) { + return; + } + + doTerminate(0); + }); + } + + cc_mqttsn_client_set_gw_status_report_callback(client(), &GwDiscover::gwStatusReportCb, this); + auto ec = cc_mqttsn_client_search(client(), &GwDiscover::searchCompleteCb, this); + if (ec != CC_MqttsnErrorCode_Success) { + logError() << "Failed to initiate search operation" << std::endl; + } + + return true; +} + +void GwDiscover::gwStatusReportInternal(CC_MqttsnGwStatus status, const CC_MqttsnGatewayInfo* info) +{ + using Elem = std::pair; + static const Elem Map[] = { + /* CC_MqttsnGwStatus_AddedByGateway */ {'+', " (gw)"}, + /* CC_MqttsnGwStatus_AddedByClient */ {'+', " (client)" }, + /* CC_MqttsnGwStatus_UpdatedByClient */ {'=', " (client)"}, + /* CC_MqttsnGwStatus_Alive */ {'=', std::string()}, + /* CC_MqttsnGwStatus_Tentative */ {'?', std::string()}, + /* CC_MqttsnGwStatus_Removed */ {'-', std::string()}, + }; + static const std::size_t MapSize = std::extent::value; + static_assert(MapSize == CC_MqttsnGwStatus_ValuesLimit); + + auto idx = static_cast(status); + if (MapSize <= idx) { + logError() << "Unexpected gateway status: " << status << std::endl; + assert(false); + return; + } + + auto& prefixSuffixInfo = Map[idx]; + auto infoTmp = *info; + + static const CC_MqttsnGwStatus GwUpdateStatuses[] = { + CC_MqttsnGwStatus_AddedByGateway, + CC_MqttsnGwStatus_Alive, + }; + + auto iter = std::find(std::begin(GwUpdateStatuses), std::end(GwUpdateStatuses), status); + + if (iter != std::end(GwUpdateStatuses)) { + infoTmp.m_addr = lastAddr().data(); + infoTmp.m_addrLen = static_cast(lastAddr().size()); + + auto ec = cc_mqttsn_client_set_available_gateway_info(client(), &infoTmp); + if (ec != CC_MqttsnErrorCode_Success) { + logError() << "Failed to update gateway info" << std::endl; + assert(false); + } + } + + std::cout << prefixSuffixInfo.first << ' ' << static_cast(infoTmp.m_gwId) << + ": " << addrToString(infoTmp) << prefixSuffixInfo.second << std::endl; +} + +void GwDiscover::searchCompleteInternal(CC_MqttsnAsyncOpStatus status, [[maybe_unused]] const CC_MqttsnGatewayInfo* info) +{ + if (status != CC_MqttsnAsyncOpStatus_Complete) { + logError() << "The initial gateway search has failed with status: " << toString(status) << std::endl; + } + + // The gateway status report will follow +} + +std::string GwDiscover::addrToString(const CC_MqttsnGatewayInfo& info) +{ + using Func = std::string (GwDiscover::*)(const CC_MqttsnGatewayInfo&); + static const Func Map[] = { + /* ConnectionType_Udp */ &GwDiscover::addrToString_ipv4 + }; + static const std::size_t MapSize = std::extent::value; + static_assert(MapSize == ProgramOptions::ConnectionType_ValuesLimit); + + auto idx = static_cast(opts().connectionType()); + if (MapSize <= idx) { + logError() << "Unsupported connection type" << std::endl; + assert(false); + return std::string(); + } + + auto func = Map[idx]; + return (this->*func)(info); +} + +std::string GwDiscover::addrToString_ipv4(const CC_MqttsnGatewayInfo& info) +{ + std::string result; + static const unsigned MaxLen = 4U; + for (auto idx = 0U; idx < std::min(info.m_addrLen, MaxLen); ++idx) { + if (!result.empty()) { + result += '.'; + } + result += std::to_string(static_cast(info.m_addr[idx])); + } + return result; +} + +void GwDiscover::gwStatusReportCb(void* data, CC_MqttsnGwStatus status, const CC_MqttsnGatewayInfo* info) +{ + asThis(data)->gwStatusReportInternal(status, info); +} + +void GwDiscover::searchCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info) +{ + asThis(data)->searchCompleteInternal(status, info); +} + +} // namespace cc_mqttsn_client_app diff --git a/client/app/gw_discover/GwDiscover.h b/client/app/gw_discover/GwDiscover.h new file mode 100644 index 00000000..bcc0583f --- /dev/null +++ b/client/app/gw_discover/GwDiscover.h @@ -0,0 +1,38 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "AppClient.h" +#include "ProgramOptions.h" + +#include + +namespace cc_mqttsn_client_app +{ + +class GwDiscover : public AppClient +{ + using Base = AppClient; +public: + GwDiscover(boost::asio::io_context& io, int& result); + +protected: + virtual bool startImpl() override; +private: + void gwStatusReportInternal(CC_MqttsnGwStatus status, const CC_MqttsnGatewayInfo* info); + void searchCompleteInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info); + std::string addrToString(const CC_MqttsnGatewayInfo& info); + std::string addrToString_ipv4(const CC_MqttsnGatewayInfo& info); + + static void gwStatusReportCb(void* data, CC_MqttsnGwStatus status, const CC_MqttsnGatewayInfo* info); + static void searchCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info); + + Timer m_timeoutTimer; +}; + +} // namespace cc_mqttsn_client_app diff --git a/client/app/gw_discover/main.cpp b/client/app/gw_discover/main.cpp new file mode 100644 index 00000000..2c99f4be --- /dev/null +++ b/client/app/gw_discover/main.cpp @@ -0,0 +1,51 @@ + +#include "GwDiscover.h" + +#include + +#include +#include +#include + +int main(int argc, const char* argv[]) +{ + int result = 0U; + try { + boost::asio::io_context io; + + boost::asio::signal_set signals(io, SIGINT, SIGTERM); + signals.async_wait( + [&io, &result](const boost::system::error_code& ec, int sigNum) + { + if (ec == boost::asio::error::operation_aborted) { + return; + } + + if (ec) { + std::cerr << "ERROR: Unexpected error in signal handling: " << ec.message() << std::endl; + result = 150; + io.stop(); + return; + } + + std::cerr << "Terminated with signal " << sigNum << std::endl; + result = 100; + io.stop(); + }); + + cc_mqttsn_client_app::GwDiscover app(io, result); + + if (!app.start(argc, argv)) { + return -1; + } + + io.run(); + } + catch (const std::exception& ec) + { + std::cerr << "ERROR: Unexpected exception: " << ec.what() << std::endl; + result = 200; + } + + return result; +} \ No newline at end of file diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index 572976b4..3583bf4f 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -64,6 +64,7 @@ typedef enum CC_MqttsnErrorCode_Disconnecting = 12, ///< The client is in "disconnecting" state, (re)connect is required in the next iteration loop. CC_MqttsnErrorCode_NotSleeping = 13, ///< The client is not in ASLEEP mode. CC_MqttsnErrorCode_PreparationLocked = 14, ///< Another operation is being prepared, cannot create a new one without performing "send" or "cancel". + CC_MqttsnErrorCode_ValuesLimit ///< Upper limit of the values } CC_MqttsnErrorCode; /// @brief Status of the gateway diff --git a/doc/BUILD.md b/doc/BUILD.md index de8bb670..c3cd6577 100644 --- a/doc/BUILD.md +++ b/doc/BUILD.md @@ -61,7 +61,7 @@ See [custom_client_build.md](custom_client_build.md) for details on custom build configuration ``` $> cmake .. -DCMAKE_BUILD_TYPE=Release -DCC_MQTTSN_CLIENT_DEFAULT_LIB=OFF \ - -DCC_MQTTSN_BUILD_GATEWAY=OFF \ + -DCC_MQTTSN_GATEWAY=OFF \ -DCC_MQTTSN_CUSTOM_CLIENT_CONFIG_FILES=config1.cmake\;config2.cmake \ -DCMAKE_PREFIX_PATH=/path/to/comms/install\;/path/to/cc.mqttsn.generated/install ``` diff --git a/gateway/CMakeLists.txt b/gateway/CMakeLists.txt index 47428ffe..e2fb1c8b 100644 --- a/gateway/CMakeLists.txt +++ b/gateway/CMakeLists.txt @@ -1,4 +1,4 @@ -if (NOT CC_MQTTSN_BUILD_GATEWAY) +if (NOT CC_MQTTSN_GATEWAY) return () endif () diff --git a/gateway/app/CMakeLists.txt b/gateway/app/CMakeLists.txt index 8518c501..0c97658b 100644 --- a/gateway/app/CMakeLists.txt +++ b/gateway/app/CMakeLists.txt @@ -1,4 +1,4 @@ -if (NOT CC_MQTTSN_BUILD_GATEWAY_APPS) +if (NOT CC_MQTTSN_GATEWAY_APPS) return () endif () From e6f3c2d3dfc548532121338a3676ff83aa523926 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 4 Jun 2024 09:02:10 +1000 Subject: [PATCH 039/106] Gateway discovery fixes. --- client/app/common/UdpSession.cpp | 1 - client/app/gw_discover/GwDiscover.cpp | 4 ++++ client/lib/src/ClientImpl.cpp | 10 ++++++---- client/lib/src/op/SearchOp.cpp | 2 -- client/lib/templ/client.cpp.templ | 2 +- gateway/app/gateway/GatewayIoClientSocket_Udp.cpp | 1 + 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/client/app/common/UdpSession.cpp b/client/app/common/UdpSession.cpp index cbfc0a48..f563d519 100644 --- a/client/app/common/UdpSession.cpp +++ b/client/app/common/UdpSession.cpp @@ -8,7 +8,6 @@ #include "UdpSession.h" #include -#include // TODO: remove namespace cc_mqttsn_client_app { diff --git a/client/app/gw_discover/GwDiscover.cpp b/client/app/gw_discover/GwDiscover.cpp index c3024e29..0517206b 100644 --- a/client/app/gw_discover/GwDiscover.cpp +++ b/client/app/gw_discover/GwDiscover.cpp @@ -104,6 +104,10 @@ void GwDiscover::gwStatusReportInternal(CC_MqttsnGwStatus status, const CC_Mqtts std::cout << prefixSuffixInfo.first << ' ' << static_cast(infoTmp.m_gwId) << ": " << addrToString(infoTmp) << prefixSuffixInfo.second << std::endl; + + if (opts().discoverExitOnFirst()) { + doTerminate(0); + } } void GwDiscover::searchCompleteInternal(CC_MqttsnAsyncOpStatus status, [[maybe_unused]] const CC_MqttsnGatewayInfo* info) diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index b0f54cac..03b2ec46 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -453,6 +453,9 @@ void ClientImpl::handle(AdvertiseMsg& msg) monitorGatewayExpiry(); }); + // TODO: when advertise arrives before GWINFO and the search is present, + // report search completion + auto iter = std::find_if( m_clientState.m_gwInfos.begin(), m_clientState.m_gwInfos.end(), @@ -584,6 +587,8 @@ void ClientImpl::handle(GwinfoMsg& msg) m_clientState.m_gwInfos.resize(m_clientState.m_gwInfos.size() + 1U); auto& info = m_clientState.m_gwInfos.back(); + info.m_expiryTimestamp = m_clientState.m_timestamp + m_configState.m_gwAdvTimeoutMs + m_configState.m_retryPeriod; + info.m_duration = m_configState.m_gwAdvTimeoutMs; info.m_gwId = msg.field_gwId().value(); info.m_allowedAdvLosses = m_configState.m_allowedAdvLosses; @@ -591,7 +596,7 @@ void ClientImpl::handle(GwinfoMsg& msg) auto& addr = msg.field_gwAdd().value(); if (addr.empty()) { - gwStatus = CC_MqttsnGwStatus_Alive; + gwStatus = CC_MqttsnGwStatus_AddedByGateway; return; // Report gateway status on exit } @@ -601,10 +606,7 @@ void ClientImpl::handle(GwinfoMsg& msg) return; } - info.m_addr.assign(addr.begin(), addr.end()); - info.m_expiryTimestamp = m_clientState.m_timestamp + m_configState.m_gwAdvTimeoutMs + m_configState.m_retryCount; - info.m_duration = m_configState.m_gwAdvTimeoutMs; monitorGatewayExpiry(); gwStatus = CC_MqttsnGwStatus_AddedByClient; diff --git a/client/lib/src/op/SearchOp.cpp b/client/lib/src/op/SearchOp.cpp index 99370a49..767923ae 100644 --- a/client/lib/src/op/SearchOp.cpp +++ b/client/lib/src/op/SearchOp.cpp @@ -88,8 +88,6 @@ void SearchOp::handle(GwinfoMsg& msg) comms::cast_assign(info.m_addrLen) = addr.size(); } - // TODO: store gateway info - completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &info); } diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index c8812fd7..3ecceb68 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -168,7 +168,7 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_available_gateway_info(CC_Mqttsn gwInfos.begin(), gwInfos.end(), [info](auto& storedInfo) { - return storedInfo.m_gwId = info->m_gwId; + return storedInfo.m_gwId == info->m_gwId; }); auto updateFunc = diff --git a/gateway/app/gateway/GatewayIoClientSocket_Udp.cpp b/gateway/app/gateway/GatewayIoClientSocket_Udp.cpp index cde148f6..48e7023f 100644 --- a/gateway/app/gateway/GatewayIoClientSocket_Udp.cpp +++ b/gateway/app/gateway/GatewayIoClientSocket_Udp.cpp @@ -38,6 +38,7 @@ bool GatewayIoClientSocket_Udp::startImpl() boost::asio::post(io(), [this]() { + m_started = true; reportPendingData(); }); From 66477f766ed3bd085cc063eecf46a227e36abd14 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 5 Jun 2024 08:34:46 +1000 Subject: [PATCH 040/106] Preparing protocol options usage. --- client/lib/script/WriteProtocolOptions.cmake | 68 ++++---------------- client/lib/src/ClientImpl.cpp | 31 +++++++-- client/lib/src/op/SearchOp.cpp | 9 +++ client/lib/src/op/SearchOp.h | 1 + client/lib/templ/ProtocolOptions.h.templ | 56 ++++++++++++++++ 5 files changed, 103 insertions(+), 62 deletions(-) diff --git a/client/lib/script/WriteProtocolOptions.cmake b/client/lib/script/WriteProtocolOptions.cmake index eb21f4e8..205e2047 100644 --- a/client/lib/script/WriteProtocolOptions.cmake +++ b/client/lib/script/WriteProtocolOptions.cmake @@ -31,79 +31,37 @@ endmacro() ######################################### -#set_default_opt (FIELD_BIN_DATA) -#set_default_opt (FIELD_PROPERTIES_LIST) -#set_default_opt (FIELD_PROTOCOL_NAME) -#set_default_opt (FIELD_STRING) -#set_default_opt (FIELD_TOPIC) - -#set_default_opt (MESSAGE_CONNECT_FIELDS_CLIENT_ID) -#set_default_opt (MESSAGE_CONNECT_FIELDS_USERNAME) -#set_default_opt (MESSAGE_CONNECT_FIELDS_PASSWORD) -#set_default_opt (MESSAGE_CONNECT_FIELDS_WILL_TOPIC) -#set_default_opt (MESSAGE_SUBSCRIBE_FIELDS_LIST) -#set_default_opt (MESSAGE_UNSUBSCRIBE_FIELDS_LIST) +set_default_opt (FIELD_GW_ADD) set_default_opt (MAX_PACKET_SIZE) -#set_default_opt (MSG_ALLOC_OPT) +set_default_opt (MSG_ALLOC_OPT) ######################################### # Update options if (NOT ${CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC}) -# set (FIELD_PROTOCOL_NAME "comms::option::app::FixedSizeStorage<4>") -# set (MSG_ALLOC_OPT "comms::option::app::InPlaceAllocation") + set (MSG_ALLOC_OPT "comms::option::app::InPlaceAllocation") endif () -#if (NOT ${CC_MQTTSN_CLIENT_BIN_DATA_FIELD_FIXED_LEN} EQUAL 0) -# set (FIELD_BIN_DATA "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_BIN_DATA_FIELD_FIXED_LEN}>") -#endif () - -#if (NOT ${CC_MQTTSN_CLIENT_STRING_FIELD_FIXED_LEN} EQUAL 0) -# set (FIELD_STRING "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_STRING_FIELD_FIXED_LEN}>") -#endif () +if (NOT ${CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN} EQUAL 0) + set (FIELD_GW_ADD "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN}>") +elseif (NOT ${CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC}) + message (FATAL_ERROR "When dynamic memory allocation is disabled, the CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN needs to be set") +endif () if (NOT ${CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE} EQUAL 0) set (MAX_PACKET_SIZE "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE}>") +elseif (NOT ${CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC}) + message (FATAL_ERROR "When dynamic memory allocation is disabled, the CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE needs to be set") endif () -#if (NOT ${CC_MQTTSN_CLIENT_CLIENT_ID_FIELD_FIXED_LEN} EQUAL 0) -# set (MESSAGE_CONNECT_FIELDS_CLIENT_ID "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_CLIENT_ID_FIELD_FIXED_LEN}>") -#endif () - -#if (NOT ${CC_MQTTSN_CLIENT_USERNAME_FIELD_FIXED_LEN} EQUAL 0) -# set (MESSAGE_CONNECT_FIELDS_USERNAME "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_USERNAME_FIELD_FIXED_LEN}>") -#endif () - -#if (NOT ${CC_MQTTSN_CLIENT_PASSWORD_FIELD_FIXED_LEN} EQUAL 0) -# set (MESSAGE_CONNECT_FIELDS_PASSWORD "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_PASSWORD_FIELD_FIXED_LEN}>") -#endif () - -#if (NOT ${CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN} EQUAL 0) -# set (FIELD_TOPIC "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN}>") -# set (MESSAGE_CONNECT_FIELDS_WILL_TOPIC "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN}>") -#endif () - -#if (NOT ${CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT} EQUAL 0) -# set (MESSAGE_SUBSCRIBE_FIELDS_LIST "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT}>") -#endif () - -#if (NOT ${CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT} EQUAL 0) -# set (MESSAGE_UNSUBSCRIBE_FIELDS_LIST "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT}>") -#endif () - - ######################################### -#replace_in_text (FIELD_BIN_DATA) -#replace_in_text (FIELD_PROPERTIES_LIST) -#replace_in_text (FIELD_PROTOCOL_NAME) -#replace_in_text (FIELD_STRING) -#replace_in_text (FIELD_TOPIC) +replace_in_text (FIELD_GW_ADD) -#replace_in_text (MAX_PACKET_SIZE) -#replace_in_text (MSG_ALLOC_OPT) +replace_in_text (MAX_PACKET_SIZE) +replace_in_text (MSG_ALLOC_OPT) file (WRITE "${OUT_FILE}.tmp" "${text}") diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 03b2ec46..073021a8 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -446,16 +446,30 @@ void ClientImpl::handle(AdvertiseMsg& msg) m_gwDiscoveryTimer.cancel(); + CC_MqttsnGwStatus gwStatus = CC_MqttsnGwStatus_ValuesLimit; + const ClientState::GwInfo* gwInfo = nullptr; auto onExit = comms::util::makeScopeGuard( - [this]() + [this, &gwStatus, &gwInfo, &msg]() { + // When advertise arrives before GWINFO and the search is present, + // report search completion + for (auto& searchOp : m_searchOps) { + COMMS_ASSERT(searchOp); + searchOp->handle(msg); + } + + // Reporting the gateway status after + // dispatching to the search operation. + + if ((gwStatus < CC_MqttsnGwStatus_ValuesLimit) && + (gwInfo != nullptr)) { + reportGwStatus(gwStatus, *gwInfo); + } + monitorGatewayExpiry(); }); - // TODO: when advertise arrives before GWINFO and the search is present, - // report search completion - auto iter = std::find_if( m_clientState.m_gwInfos.begin(), m_clientState.m_gwInfos.end(), @@ -471,8 +485,9 @@ void ClientImpl::handle(AdvertiseMsg& msg) iter->m_expiryTimestamp = nextExpiryTimestamp; iter->m_duration = duration; iter->m_allowedAdvLosses = m_configState.m_allowedAdvLosses; - reportGwStatus(CC_MqttsnGwStatus_Alive, *iter); - return; + gwStatus = CC_MqttsnGwStatus_Alive; + gwInfo = &(*iter); + return; // Geport gateway status on exit } if (m_clientState.m_gwInfos.max_size() <= m_clientState.m_gwInfos.size()) { @@ -488,7 +503,9 @@ void ClientImpl::handle(AdvertiseMsg& msg) info.m_duration = duration; info.m_allowedAdvLosses = m_configState.m_allowedAdvLosses; - reportGwStatus(CC_MqttsnGwStatus_AddedByGateway, info); + gwStatus = CC_MqttsnGwStatus_AddedByGateway; + gwInfo = &info; + // Geport gateway status on exit } void ClientImpl::handle(SearchgwMsg& msg) diff --git a/client/lib/src/op/SearchOp.cpp b/client/lib/src/op/SearchOp.cpp index 767923ae..317e59b1 100644 --- a/client/lib/src/op/SearchOp.cpp +++ b/client/lib/src/op/SearchOp.cpp @@ -76,6 +76,15 @@ CC_MqttsnErrorCode SearchOp::cancel() return CC_MqttsnErrorCode_Success; } +void SearchOp::handle(AdvertiseMsg& msg) +{ + m_timer.cancel(); + + auto info = CC_MqttsnGatewayInfo(); + info.m_gwId = msg.field_gwId().value(); + completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &info); +} + void SearchOp::handle(GwinfoMsg& msg) { m_timer.cancel(); diff --git a/client/lib/src/op/SearchOp.h b/client/lib/src/op/SearchOp.h index a260412d..158b0661 100644 --- a/client/lib/src/op/SearchOp.h +++ b/client/lib/src/op/SearchOp.h @@ -29,6 +29,7 @@ class SearchOp final : public Op CC_MqttsnErrorCode cancel(); using Base::handle; + virtual void handle(AdvertiseMsg& msg) override; virtual void handle(GwinfoMsg& msg) override; void setBroadcastRadius(unsigned value) diff --git a/client/lib/templ/ProtocolOptions.h.templ b/client/lib/templ/ProtocolOptions.h.templ index 42e8db6a..9f92aa27 100644 --- a/client/lib/templ/ProtocolOptions.h.templ +++ b/client/lib/templ/ProtocolOptions.h.templ @@ -13,6 +13,62 @@ class ProtocolOptions : public cc_mqttsn::options::ClientDefaultOptions using BaseImpl = cc_mqttsn::options::ClientDefaultOptions; public: + struct field : public BaseImpl::field + { + /* using ClientId = + std::tuple< + comms::option::app::FixedSizeStorage, + BasImpl::field::ClientId + >;*/ + + /* using Data = + std::tuple< + comms::option::app::FixedSizeStorage, + BasImpl::field::Data + >; */ + + using GwAdd = + std::tuple< + ##FIELD_GW_ADD##, + BaseImpl::field::GwAdd + >; + + /* using TopicName = + std::tuple< + comms::option::app::FixedSizeStorage, + BasImpl::field::TopicName + >; */ + + /* using WillMsg = + std::tuple< + comms::option::app::FixedSizeStorage, + BasImpl::field::WillMsg + >; */ + + /* using WillTopic = + std::tuple< + comms::option::app::FixedSizeStorage, + BasImpl::field::WillTopic + >; */ + }; // struct field + + struct frame : public BaseImpl::frame + { + struct FrameLayers : public BaseImpl::frame::FrameLayers + { + using Data = + std::tuple< + ##MAX_PACKET_SIZE##, + BaseImpl::frame::FrameLayers::Data + >; + + using Id = + std::tuple< + ##MSG_ALLOC_OPT##, + BaseImpl::frame::FrameLayers::Id + >; + }; // struct FrameLayers + }; // struct frame }; } // namespace cc_mqttsn_client From 46b91206e745818a25aa5a346b75d63f3eb8111d Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 5 Jun 2024 09:17:42 +1000 Subject: [PATCH 041/106] Initial preparation for the "connect" operation. --- client/lib/CMakeLists.txt | 1 + client/lib/include/cc_mqttsn_client/common.h | 52 ++++++-- client/lib/src/ClientImpl.cpp | 64 +++++++++- client/lib/src/ClientImpl.h | 14 +- client/lib/src/op/ConnectOp.cpp | 128 +++++++++++++++++++ client/lib/src/op/ConnectOp.h | 55 ++++++++ client/lib/src/op/Op.h | 2 +- client/lib/src/op/SearchOp.cpp | 7 +- client/lib/src/op/SearchOp.h | 1 - client/lib/templ/client.cpp.templ | 53 ++++++++ client/lib/templ/client.h.templ | 38 +++++- 11 files changed, 391 insertions(+), 24 deletions(-) create mode 100644 client/lib/src/op/ConnectOp.cpp create mode 100644 client/lib/src/op/ConnectOp.h diff --git a/client/lib/CMakeLists.txt b/client/lib/CMakeLists.txt index c7f80ad3..7c894940 100644 --- a/client/lib/CMakeLists.txt +++ b/client/lib/CMakeLists.txt @@ -165,6 +165,7 @@ function (gen_lib_mqttsn_client config_file) message (STATUS "Defining library ${lib_name}") set (src + src/op/ConnectOp.cpp src/op/Op.cpp src/op/SearchOp.cpp src/ClientImpl.cpp diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index 3583bf4f..1b3c64dd 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -38,6 +38,7 @@ extern "C" { #define CC_MQTTSN_CLIENT_VERSION CC_MQTTSN_CLIENT_MAKE_VERSION(CC_MQTTSN_CLIENT_MAJOR_VERSION, CC_MQTTSN_CLIENT_MINOR_VERSION, CC_MQTTSN_CLIENT_PATCH_VERSION) /// @brief Quality of Service +/// @ingroup client typedef enum { CC_MqttsnQoS_NoGwPublish = -1, ///< QoS=-1. No gateway publish, used by publish only clients. @@ -47,6 +48,7 @@ typedef enum } CC_MqttsnQoS; /// @brief Error code returned by various API functions. +/// @ingroup client typedef enum { CC_MqttsnErrorCode_Success = 0, ///< The requested operation was successfully started. @@ -68,6 +70,7 @@ typedef enum } CC_MqttsnErrorCode; /// @brief Status of the gateway +/// @ingroup client typedef enum { CC_MqttsnGwStatus_AddedByGateway = 0, ///< Added by the @b ADVERTISE or @b GWINFO sent by the gateway messages @@ -80,6 +83,7 @@ typedef enum } CC_MqttsnGwStatus; /// @brief Status of the asynchronous operation +/// @ingroup client typedef enum { CC_MqttsnAsyncOpStatus_Complete = 0, ///< The requested operation has been completed, refer to reported extra details for information @@ -100,6 +104,17 @@ typedef enum CC_MqttsnGatewayDisconnectReason_ValuesLimit ///< Limit for the values } CC_MqttsnGatewayDisconnectReason; +/// @brief Return code as per MQTT-SN specification +/// @ingroup client +typedef enum +{ + CC_MqttsnReturnCode_Accepted = 0, ///< Accepted + CC_MqttsnReturnCode_Conjestion = 1, ///< Rejected due to conjesion + CC_MqttsnReturnCode_InvalidTopicId = 2, ///< Rejected due to invalid topic ID + CC_MqttsnReturnCode_NotSupported = 3, ///< Rejected as not supported + CC_MqttsnReturnCode_ValuesLimit ///< Limit for the values +} CC_MqttsnReturnCode; + /// @brief Declaration of struct for the @ref CC_MqttsnClientHandle; struct CC_MqttsnClient; @@ -116,6 +131,16 @@ struct CC_MqttsnSearch; /// @ingroup "search". typedef struct CC_MqttsnSearch* CC_MqttsnSearchHandle; +/// @brief Declaration of the hidden structure used to define @ref CC_MqttsnConnectHandle +/// @ingroup connect +struct CC_MqttsnConnect; + +/// @brief Handle for "connect" operation. +/// @details Returned by @b cc_mqttsn_client_connect_prepare() function. +/// @ingroup "connect". +typedef struct CC_MqttsnConnect* CC_MqttsnConnectHandle; + + /// @brief Type used to hold Topic ID value. typedef unsigned short CC_MqttsnTopicId; @@ -141,13 +166,21 @@ typedef struct } CC_MqttsnMessageInfo; /// @brief Gateway information +/// @ingroup client typedef struct { - unsigned char m_gwId; ///< Gateway ID - const unsigned char* m_addr; ///< Address of the gateway if known, NULL if not. - unsigned m_addrLen; ///< Length of the address + unsigned char m_gwId; ///< Gateway ID + const unsigned char* m_addr; ///< Address of the gateway if known, NULL if not. + unsigned m_addrLen; ///< Length of the address } CC_MqttsnGatewayInfo; +/// @brief Information on the "connect" operation completion +/// @ingroup connect +typedef struct +{ + CC_MqttsnReturnCode m_returnCode; ///< Return code reported by the @b CONNACK message +} CC_MqttsnConnectInfo; + /// @brief Callback used to request time measurement. /// @details The callback is set using /// cc_mqttsn_client_set_next_tick_program_callback() function. @@ -225,12 +258,15 @@ typedef unsigned (*CC_MqttsnGwinfoDelayRequestCb)(void* data); /// @ingroup search typedef void (*CC_MqttsnSearchCompleteCb)(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info); -/// @brief Callback used to report completion of the subscribe operation. +/// @brief Callback used to report completion of the connect operation. /// @param[in] data Pointer to user data object, passed as the last parameter to -/// the subscribe request. -/// @param[in] status Status of the subscribe operation. -/// @param[in] qos Maximal level of quality of service, the gateway/gateway is going to use to publish incoming messages. -typedef void (*CC_MqttsnSubscribeCompleteReportCb)(void* data, CC_MqttsnAsyncOpStatus status, CC_MqttsnQoS qos); +/// the request call. +/// @param[in] status Status of the "connect" operation. +/// @param[in] info Information about op completion. Not-NULL is reported if and onfly if +/// the "status" is equal to @ref CC_MqttsnAsyncOpStatus_Complete. +/// @post The data members of the reported response can NOT be accessed after the function returns. +/// @ingroup connect +typedef void (*CC_MqttsnConnectCompleteCb)(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info); #ifdef __cplusplus diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 073021a8..b3d3faf8 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -150,6 +150,60 @@ op::SearchOp* ClientImpl::searchPrepare(CC_MqttsnErrorCode* ec) } } +op::ConnectOp* ClientImpl::connectPrepare(CC_MqttsnErrorCode* ec) +{ + op::ConnectOp* op = nullptr; + do { + if (!m_clientState.m_initialized) { + if (m_apiEnterCount > 0U) { + errorLog("Cannot prepare connect from within callback"); + updateEc(ec, CC_MqttsnErrorCode_RetryLater); + break; + } + + auto initEc = initInternal(); + if (initEc != CC_MqttsnErrorCode_Success) { + updateEc(ec, initEc); + break; + } + } + + if (!m_connectOps.empty()) { + // Already allocated + errorLog("Another connect operation is in progress."); + updateEc(ec, CC_MqttsnErrorCode_Busy); + break; + } + + if (m_ops.max_size() <= m_ops.size()) { + errorLog("Cannot start connect operation, retry in next event loop iteration."); + updateEc(ec, CC_MqttsnErrorCode_RetryLater); + break; + } + + if (m_preparationLocked) { + errorLog("Another operation is being prepared, cannot prepare \"connect\" without \"send\" or \"cancel\" of the previous."); + updateEc(ec, CC_MqttsnErrorCode_PreparationLocked); + break; + } + + auto ptr = m_connectOpAlloc.alloc(*this); + if (!ptr) { + errorLog("Cannot allocate new connect operation."); + updateEc(ec, CC_MqttsnErrorCode_OutOfMemory); + break; + } + + m_preparationLocked = true; + m_ops.push_back(ptr.get()); + m_connectOps.push_back(std::move(ptr)); + op = m_connectOps.back().get(); + updateEc(ec, CC_MqttsnErrorCode_Success); + } while (false); + + return op; +} + // op::ConnectOp* ClientImpl::connectPrepare(CC_MqttsnErrorCode* ec) // { // op::ConnectOp* connectOp = nullptr; @@ -828,7 +882,7 @@ void ClientImpl::opComplete(const op::Op* op) using ExtraCompleteFunc = void (ClientImpl::*)(const op::Op*); static const ExtraCompleteFunc Map[] = { /* Type_Search */ &ClientImpl::opComplete_Search, - // /* Type_Connect */ &ClientImpl::opComplete_Connect, + /* Type_Connect */ &ClientImpl::opComplete_Connect, // /* Type_KeepAlive */ &ClientImpl::opComplete_KeepAlive, // /* Type_Disconnect */ &ClientImpl::opComplete_Disconnect, // /* Type_Subscribe */ &ClientImpl::opComplete_Subscribe, @@ -1218,10 +1272,10 @@ void ClientImpl::opComplete_Search(const op::Op* op) eraseFromList(op, m_searchOps); } -// void ClientImpl::opComplete_Connect(const op::Op* op) -// { -// eraseFromList(op, m_connectOps); -// } +void ClientImpl::opComplete_Connect(const op::Op* op) +{ + eraseFromList(op, m_connectOps); +} // void ClientImpl::opComplete_KeepAlive(const op::Op* op) // { diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index 1e31e9c7..59e0c1c2 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -17,7 +17,7 @@ // #include "SessionState.h" #include "TimerMgr.h" -// #include "op/ConnectOp.h" +#include "op/ConnectOp.h" // #include "op/DisconnectOp.h" // #include "op/KeepAliveOp.h" #include "op/Op.h" @@ -69,7 +69,7 @@ class ClientImpl final : public ProtMsgHandler // bool isNetworkDisconnected() const; op::SearchOp* searchPrepare(CC_MqttsnErrorCode* ec); - // op::ConnectOp* connectPrepare(CC_MqttsnErrorCode* ec); + op::ConnectOp* connectPrepare(CC_MqttsnErrorCode* ec); // op::DisconnectOp* disconnectPrepare(CC_MqttsnErrorCode* ec); // op::SubscribeOp* subscribePrepare(CC_MqttsnErrorCode* ec); // op::UnsubscribeOp* unsubscribePrepare(CC_MqttsnErrorCode* ec); @@ -231,8 +231,8 @@ class ClientImpl final : public ProtMsgHandler using SearchOpAlloc = ObjAllocator; using SearchOpsList = ObjListType; - // using ConnectOpAlloc = ObjAllocator; - // using ConnectOpsList = ObjListType; + using ConnectOpAlloc = ObjAllocator; + using ConnectOpsList = ObjListType; // using KeepAliveOpAlloc = ObjAllocator; // using KeepAliveOpsList = ObjListType; @@ -277,7 +277,7 @@ class ClientImpl final : public ProtMsgHandler // bool processPublishAckMsg(ProtMessage& msg, std::uint16_t packetId, bool pubcompAck = false); void opComplete_Search(const op::Op* op); - // void opComplete_Connect(const op::Op* op); + void opComplete_Connect(const op::Op* op); // void opComplete_KeepAlive(const op::Op* op); // void opComplete_Disconnect(const op::Op* op); // void opComplete_Subscribe(const op::Op* op); @@ -335,8 +335,8 @@ class ClientImpl final : public ProtMsgHandler SearchOpAlloc m_searchOpAlloc; SearchOpsList m_searchOps; - // ConnectOpAlloc m_connectOpAlloc; - // ConnectOpsList m_connectOps; + ConnectOpAlloc m_connectOpAlloc; + ConnectOpsList m_connectOps; // KeepAliveOpAlloc m_keepAliveOpsAlloc; // KeepAliveOpsList m_keepAliveOps; diff --git a/client/lib/src/op/ConnectOp.cpp b/client/lib/src/op/ConnectOp.cpp new file mode 100644 index 00000000..58fbc8ef --- /dev/null +++ b/client/lib/src/op/ConnectOp.cpp @@ -0,0 +1,128 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "op/ConnectOp.h" +#include "ClientImpl.h" + +#include "comms/util/assign.h" +#include "comms/util/ScopeGuard.h" +#include "comms/units.h" + +#include +#include + +namespace cc_mqttsn_client +{ + +namespace op +{ + +namespace +{ + +inline ConnectOp* asConnectOp(void* data) +{ + return reinterpret_cast(data); +} + +} // namespace + + +ConnectOp::ConnectOp(ClientImpl& client) : + Base(client), + m_timer(client.timerMgr().allocTimer()) +{ +} + +CC_MqttsnErrorCode ConnectOp::send(CC_MqttsnConnectCompleteCb cb, void* cbData) +{ + client().allowNextPrepare(); + auto completeOnError = + comms::util::makeScopeGuard( + [this]() + { + opComplete(); + }); + + if (cb == nullptr) { + errorLog("Connect completion callback is not provided."); + return CC_MqttsnErrorCode_BadParam; + } + + if (!m_timer.isValid()) { + errorLog("The library cannot allocate required number of timers."); + return CC_MqttsnErrorCode_InternalError; + } + + m_cb = cb; + m_cbData = cbData; + + auto ec = sendInternal(); + if (ec == CC_MqttsnErrorCode_Success) { + completeOnError.release(); + } + + return ec; +} + +CC_MqttsnErrorCode ConnectOp::cancel() +{ + if (m_cb == nullptr) { + // hasn't been sent yet + client().allowNextPrepare(); + } + + opComplete(); + return CC_MqttsnErrorCode_Success; +} + +Op::Type ConnectOp::typeImpl() const +{ + return Type_Connect; +} + +void ConnectOp::terminateOpImpl(CC_MqttsnAsyncOpStatus status) +{ + completeOpInternal(status); +} + +void ConnectOp::completeOpInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info) +{ + auto cb = m_cb; + auto* cbData = m_cbData; + opComplete(); // mustn't access data members after destruction + if (cb != nullptr) { + cb(cbData, status, info); + } +} + +void ConnectOp::restartTimer() +{ + m_timer.wait(getRetryPeriod(), &ConnectOp::opTimeoutCb, this); +} + +CC_MqttsnErrorCode ConnectOp::sendInternal() +{ + if (getRetryCount() == 0U) { + errorLog("All retries of the connect operation have been exhausted."); + completeOpInternal(CC_MqttsnAsyncOpStatus_Timeout); + return CC_MqttsnErrorCode_InternalError; + } + + decRetryCount(); + + return sendMessage(m_connectMsg); +} + +void ConnectOp::opTimeoutCb(void* data) +{ + asConnectOp(data)->sendInternal(); +} + +} // namespace op + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/op/ConnectOp.h b/client/lib/src/op/ConnectOp.h new file mode 100644 index 00000000..ef5b92e3 --- /dev/null +++ b/client/lib/src/op/ConnectOp.h @@ -0,0 +1,55 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "op/Op.h" +#include "ExtConfig.h" +#include "ProtocolDefs.h" + +#include "TimerMgr.h" + +namespace cc_mqttsn_client +{ + +namespace op +{ + +class ConnectOp final : public Op +{ + using Base = Op; +public: + explicit ConnectOp(ClientImpl& client); + + CC_MqttsnErrorCode send(CC_MqttsnConnectCompleteCb cb, void* cbData); + CC_MqttsnErrorCode cancel(); + + using Base::handle; + +protected: + virtual Type typeImpl() const override; + virtual void terminateOpImpl(CC_MqttsnAsyncOpStatus status) override; + +private: + void completeOpInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info = nullptr); + void restartTimer(); + CC_MqttsnErrorCode sendInternal(); + + static void opTimeoutCb(void* data); + + ConnectMsg m_connectMsg; + TimerMgr::Timer m_timer; + CC_MqttsnConnectCompleteCb m_cb = nullptr; + void* m_cbData = nullptr; + + static_assert(ExtConfig::ConnectOpTimers == 1U); +}; + +} // namespace op + + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/op/Op.h b/client/lib/src/op/Op.h index 465509a2..66d7a1fb 100644 --- a/client/lib/src/op/Op.h +++ b/client/lib/src/op/Op.h @@ -29,7 +29,7 @@ class Op : public ProtMsgHandler enum Type { Type_Search, - // Type_Connect, + Type_Connect, // Type_KeepAlive, // Type_Disconnect, // Type_Subscribe, diff --git a/client/lib/src/op/SearchOp.cpp b/client/lib/src/op/SearchOp.cpp index 317e59b1..fbae11f9 100644 --- a/client/lib/src/op/SearchOp.cpp +++ b/client/lib/src/op/SearchOp.cpp @@ -62,7 +62,12 @@ CC_MqttsnErrorCode SearchOp::send(CC_MqttsnSearchCompleteCb cb, void* cbData) m_cb = cb; m_cbData = cbData; - return sendInternal(); + auto ec = sendInternal(); + if (ec == CC_MqttsnErrorCode_Success) { + completeOnError.release(); + } + + return ec; } CC_MqttsnErrorCode SearchOp::cancel() diff --git a/client/lib/src/op/SearchOp.h b/client/lib/src/op/SearchOp.h index 158b0661..9261f677 100644 --- a/client/lib/src/op/SearchOp.h +++ b/client/lib/src/op/SearchOp.h @@ -53,7 +53,6 @@ class SearchOp final : public Op static void opTimeoutCb(void* data); - ConnectMsg m_connectMsg; TimerMgr::Timer m_timer; unsigned m_radius = 0U; CC_MqttsnSearchCompleteCb m_cb = nullptr; diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index 3ecceb68..8415219f 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -16,6 +16,7 @@ struct CC_MqttsnClient {}; struct CC_MqttsnSearch {}; +struct CC_MqttsnConnect {}; namespace { @@ -46,6 +47,16 @@ inline CC_MqttsnSearchHandle handleFromSearchOp(cc_mqttsn_client::op::SearchOp* return reinterpret_cast(op); } +inline cc_mqttsn_client::op::ConnectOp* connectOpFromHandle(CC_MqttsnConnectHandle handle) +{ + return reinterpret_cast(handle); +} + +inline CC_MqttsnConnectHandle handleFromConnectOp(cc_mqttsn_client::op::ConnectOp* op) +{ + return reinterpret_cast(op); +} + } // namespace CC_MqttsnClientHandle cc_mqttsn_##NAME##client_alloc() @@ -349,6 +360,48 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_cancel(CC_MqttsnSearchHandle return searchOpFromHandle(handle)->cancel(); } +CC_MqttsnConnectHandle cc_mqttsn_##NAME##client_connect_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec) +{ + COMMS_ASSERT(client != nullptr); + return handleFromConnectOp(clientFromHandle(client)->connectPrepare(ec)); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect_set_retry_period(CC_MqttsnConnectHandle handle, unsigned ms) +{ + COMMS_ASSERT(handle != nullptr); + if (ms == 0U) { + connectOpFromHandle(handle)->client().errorLog("The retry period must be greater than 0"); + return CC_MqttsnErrorCode_BadParam; + } + + COMMS_ASSERT(handle != nullptr); + connectOpFromHandle(handle)->setRetryPeriod(ms); + return CC_MqttsnErrorCode_Success; +} + +unsigned cc_mqttsn_##NAME##client_connect_get_retry_period(CC_MqttsnConnectHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return connectOpFromHandle(handle)->getRetryPeriod(); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect_set_retry_count(CC_MqttsnConnectHandle handle, unsigned count) +{ + COMMS_ASSERT(handle != nullptr); + if (count == 0U) { + connectOpFromHandle(handle)->client().errorLog("The retry count must be greater than 0"); + return CC_MqttsnErrorCode_BadParam; + } + + connectOpFromHandle(handle)->setRetryCount(count); + return CC_MqttsnErrorCode_Success; +} + +unsigned cc_mqttsn_##NAME##client_connect_get_retry_count(CC_MqttsnConnectHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return connectOpFromHandle(handle)->getRetryCount(); +} // --------------------- Callbacks --------------------- diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index 0469fc09..f9cc0b1a 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -276,7 +276,43 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_cancel(CC_MqttsnSearchHandle CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search( CC_MqttsnClientHandle handle, CC_MqttsnSearchCompleteCb cb, - void* cbData); + void* cbData); + +/// @brief Prepare "connect" operation. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[out] ec Error code reporting result of the operation. Can be NULL. +/// @return Handle of the "connect" operation, will be NULL in case of failure. To analyze the reason failure use "ec" output parameter. +/// @post The "connect" operation is allocated, use either @ref cc_mqttsn_##NAME##client_connect_send() +/// or @ref cc_mqttsn_##NAME##client_connect_cancel() to prevent memory leaks. +/// @ingroup connect +CC_MqttsnConnectHandle cc_mqttsn_##NAME##client_connect_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec); + +/// @brief Configure the retry period for the "connect" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_connect_prepare() function. +/// @param[in] ms Retry period in @b milliseconds. +/// @return Result code of the call. +/// @ingroup connect +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect_set_retry_period(CC_MqttsnConnectHandle handle, unsigned ms); + +/// @brief Retrieve the configured retry period for the "connect" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_connect_prepare() function. +/// @return Retry period duration in @b milliseconds. +/// @ingroup connect +unsigned cc_mqttsn_##NAME##client_connect_get_retry_period(CC_MqttsnConnectHandle handle); + +/// @brief Configure the retry count for the "connect" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_connect_prepare() function. +/// @param[in] count Number of retries. +/// @return Result code of the call. +/// @ingroup connect +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect_set_retry_count(CC_MqttsnConnectHandle handle, unsigned count); + +/// @brief Retrieve the configured retry count for the "connect" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_connect_prepare() function. +/// @return Number of retries. +/// @ingroup connect +unsigned cc_mqttsn_##NAME##client_connect_get_retry_count(CC_MqttsnConnectHandle handle); + // --------------------- Callbacks --------------------- From 576f11145a51bf89a686d3710288f862f3782fb2 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 7 Jun 2024 08:07:37 +1000 Subject: [PATCH 042/106] Finalising connect operation for client. --- client/lib/include/cc_mqttsn_client/common.h | 26 ++- .../lib/script/DefineDefaultConfigVars.cmake | 7 +- client/lib/script/WriteConfigHeader.cmake | 4 +- client/lib/script/WriteProtocolOptions.cmake | 24 +++ client/lib/src/ClientImpl.h | 20 +-- client/lib/src/SessionState.h | 23 +++ client/lib/src/op/ConnectOp.cpp | 164 +++++++++++++++++- client/lib/src/op/ConnectOp.h | 21 +++ client/lib/src/op/Op.cpp | 31 ++++ client/lib/src/op/Op.h | 1 + client/lib/src/op/SearchOp.cpp | 25 ++- client/lib/src/op/SearchOp.h | 2 + client/lib/templ/ProtocolOptions.h.templ | 28 +-- client/lib/templ/client.cpp.templ | 77 +++++++- client/lib/templ/client.h.templ | 66 +++++++ 15 files changed, 469 insertions(+), 50 deletions(-) create mode 100644 client/lib/src/SessionState.h diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index 1b3c64dd..43d373f8 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -42,9 +42,9 @@ extern "C" { typedef enum { CC_MqttsnQoS_NoGwPublish = -1, ///< QoS=-1. No gateway publish, used by publish only clients. - CC_MqttsnQoS_AtMostOnceDelivery, ///< QoS=0. At most once delivery. - CC_MqttsnQoS_AtLeastOnceDelivery, ///< QoS=1. At least once delivery. - CC_MqttsnQoS_ExactlyOnceDelivery ///< QoS=2. Exactly once delivery. + CC_MqttsnQoS_AtMostOnceDelivery = 0, ///< QoS=0. At most once delivery. + CC_MqttsnQoS_AtLeastOnceDelivery = 1, ///< QoS=1. At least once delivery. + CC_MqttsnQoS_ExactlyOnceDelivery = 2 ///< QoS=2. Exactly once delivery. } CC_MqttsnQoS; /// @brief Error code returned by various API functions. @@ -181,6 +181,26 @@ typedef struct CC_MqttsnReturnCode m_returnCode; ///< Return code reported by the @b CONNACK message } CC_MqttsnConnectInfo; +/// @brief Configuration the "connect" operation +/// @ingroup connect +typedef struct +{ + const char* m_clientId; ///< Client ID + unsigned m_duration; ///< Duration (Keep alive) configuration in seconds. Defaults to 60 when initialized. + bool m_cleanSession; ///< Clean session configuration +} CC_MqttsnConnectConfig; + +/// @brief Configuration the will for "connect" and "will" operations +/// @ingroup connect +typedef struct +{ + const char* m_topic; ///< Client ID + const unsigned char* m_data; + unsigned m_dataLen; + CC_MqttsnQoS m_qos; + bool m_retain; +} CC_MqttsnWillConfig; + /// @brief Callback used to request time measurement. /// @details The callback is set using /// cc_mqttsn_client_set_next_tick_program_callback() function. diff --git a/client/lib/script/DefineDefaultConfigVars.cmake b/client/lib/script/DefineDefaultConfigVars.cmake index 9da5f31c..2407d5a5 100644 --- a/client/lib/script/DefineDefaultConfigVars.cmake +++ b/client/lib/script/DefineDefaultConfigVars.cmake @@ -16,10 +16,9 @@ set_default_var_value(CC_MQTTSN_CLIENT_ALLOC_LIMIT 0) set_default_var_value(CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY TRUE) set_default_var_value(CC_MQTTSN_CLIENT_GATEWAY_INFOS_MAX_LIMIT 0) set_default_var_value(CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN 0) -#set_default_var_value(CC_MQTTSN_CLIENT_STRING_FIELD_FIXED_LEN 0) -#set_default_var_value(CC_MQTTSN_CLIENT_CLIENT_ID_FIELD_FIXED_LEN 0) -#set_default_var_value(CC_MQTTSN_CLIENT_USERNAME_FIELD_FIXED_LEN 0) -#set_default_var_value(CC_MQTTSN_CLIENT_PASSWORD_FIELD_FIXED_LEN 0) +set_default_var_value(CC_MQTTSN_CLIENT_CLIENT_ID_FIELD_FIXED_LEN 0) +set_default_var_value(CC_MQTTSN_CLIENT_WILL_TOPIC_FIELD_FIXED_LEN 0) +set_default_var_value(CC_MQTTSN_CLIENT_WILL_DATA_FIELD_FIXED_LEN 0) #set_default_var_value(CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN 0) #set_default_var_value(CC_MQTTSN_CLIENT_BIN_DATA_FIELD_FIXED_LEN 0) set_default_var_value(CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE 0) diff --git a/client/lib/script/WriteConfigHeader.cmake b/client/lib/script/WriteConfigHeader.cmake index c2cf7c23..5788440a 100644 --- a/client/lib/script/WriteConfigHeader.cmake +++ b/client/lib/script/WriteConfigHeader.cmake @@ -46,8 +46,6 @@ replace_in_text (CC_MQTTSN_CLIENT_ALLOC_LIMIT) replace_in_text (CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY_CPP) replace_in_text (CC_MQTTSN_CLIENT_GATEWAY_INFOS_MAX_LIMIT) replace_in_text (CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN) -#replace_in_text (CC_MQTTSN_CLIENT_STRING_FIELD_FIXED_LEN) -#replace_in_text (CC_MQTTSN_CLIENT_BIN_DATA_FIELD_FIXED_LEN) replace_in_text (CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE) replace_in_text (CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT) replace_in_text (CC_MQTTSN_CLIENT_SEND_MAX_LIMIT) @@ -65,4 +63,4 @@ execute_process( COMMAND ${CMAKE_COMMAND} -E copy_if_different "${OUT_FILE}.tmp" "${OUT_FILE}") execute_process( - COMMAND ${CMAKE_COMMAND} -E rm -rf "${OUT_FILE}.tmp") \ No newline at end of file + COMMAND ${CMAKE_COMMAND} -E rm -rf "${OUT_FILE}.tmp") diff --git a/client/lib/script/WriteProtocolOptions.cmake b/client/lib/script/WriteProtocolOptions.cmake index 205e2047..58321034 100644 --- a/client/lib/script/WriteProtocolOptions.cmake +++ b/client/lib/script/WriteProtocolOptions.cmake @@ -32,6 +32,9 @@ endmacro() ######################################### set_default_opt (FIELD_GW_ADD) +set_default_opt (FIELD_CLIENT_ID) +set_default_opt (FIELD_WILL_TOPIC) +set_default_opt (FIELD_WILL_DATA) set_default_opt (MAX_PACKET_SIZE) set_default_opt (MSG_ALLOC_OPT) @@ -50,6 +53,24 @@ elseif (NOT ${CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC}) message (FATAL_ERROR "When dynamic memory allocation is disabled, the CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN needs to be set") endif () +if (NOT ${CC_MQTTSN_CLIENT_CLIENT_ID_FIELD_FIXED_LEN} EQUAL 0) + set (FIELD_CLIENT_ID "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_CLIENT_ID_FIELD_FIXED_LEN}>") +elseif (NOT ${CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC}) + message (FATAL_ERROR "When dynamic memory allocation is disabled, the CC_MQTTSN_CLIENT_CLIENT_ID_FIELD_FIXED_LEN needs to be set") +endif () + +if (NOT ${CC_MQTTSN_CLIENT_WILL_TOPIC_FIELD_FIXED_LEN} EQUAL 0) + set (FIELD_CLIENT_ID "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_WILL_TOPIC_FIELD_FIXED_LEN}>") +elseif (NOT ${CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC}) + message (FATAL_ERROR "When dynamic memory allocation is disabled, the CC_MQTTSN_CLIENT_WILL_TOPIC_FIELD_FIXED_LEN needs to be set") +endif () + +if (NOT ${CC_MQTTSN_CLIENT_WILL_DATA_FIELD_FIXED_LEN} EQUAL 0) + set (FIELD_CLIENT_ID "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_WILL_DATA_FIELD_FIXED_LEN}>") +elseif (NOT ${CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC}) + message (FATAL_ERROR "When dynamic memory allocation is disabled, the CC_MQTTSN_CLIENT_WILL_DATA_FIELD_FIXED_LEN needs to be set") +endif () + if (NOT ${CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE} EQUAL 0) set (MAX_PACKET_SIZE "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE}>") elseif (NOT ${CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC}) @@ -59,6 +80,9 @@ endif () ######################################### replace_in_text (FIELD_GW_ADD) +replace_in_text (FIELD_CLIENT_ID) +replace_in_text (FIELD_WILL_TOPIC) +replace_in_text (FIELD_WILL_DATA) replace_in_text (MAX_PACKET_SIZE) replace_in_text (MSG_ALLOC_OPT) diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index 59e0c1c2..422cb62a 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -14,7 +14,7 @@ #include "ObjListType.h" #include "ProtocolDefs.h" // #include "ReuseState.h" -// #include "SessionState.h" +#include "SessionState.h" #include "TimerMgr.h" #include "op/ConnectOp.h" @@ -200,15 +200,15 @@ class ClientImpl final : public ProtMsgHandler return m_clientState; } - // SessionState& sessionState() - // { - // return m_sessionState; - // } + SessionState& sessionState() + { + return m_sessionState; + } - // const SessionState& sessionState() const - // { - // return m_sessionState; - // } + const SessionState& sessionState() const + { + return m_sessionState; + } // ReuseState& reuseState() // { @@ -321,7 +321,7 @@ class ClientImpl final : public ProtMsgHandler ConfigState m_configState; ClientState m_clientState; - // SessionState m_sessionState; + SessionState m_sessionState; TimerMgr m_timerMgr; TimerMgr::Timer m_gwDiscoveryTimer; diff --git a/client/lib/src/SessionState.h b/client/lib/src/SessionState.h new file mode 100644 index 00000000..d79892a3 --- /dev/null +++ b/client/lib/src/SessionState.h @@ -0,0 +1,23 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +namespace cc_mqttsn_client +{ + +struct SessionState +{ + + static constexpr unsigned DefaultKeepAlive = 60; + + unsigned m_keepAliveMs = 0U; + // bool m_connected = false; + // bool m_disconnecting = false; +}; + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/op/ConnectOp.cpp b/client/lib/src/op/ConnectOp.cpp index 58fbc8ef..6fdc3da0 100644 --- a/client/lib/src/op/ConnectOp.cpp +++ b/client/lib/src/op/ConnectOp.cpp @@ -38,6 +38,59 @@ ConnectOp::ConnectOp(ClientImpl& client) : { } +CC_MqttsnErrorCode ConnectOp::config(const CC_MqttsnConnectConfig* config) +{ + if (config == nullptr) { + errorLog("Connect configuration is not provided."); + return CC_MqttsnErrorCode_BadParam; + } + + if (config->m_clientId != nullptr) { + m_connectMsg.field_clientId().value() = config->m_clientId; + } + + comms::units::setSeconds(m_connectMsg.field_duration(), config->m_duration); + m_connectMsg.field_flags().field_mid().setBitValue_CleanSession(config->m_cleanSession); + return CC_MqttsnErrorCode_Success; +} + +CC_MqttsnErrorCode ConnectOp::willConfig(const CC_MqttsnWillConfig* config) +{ + if (config == nullptr) { + errorLog("Will configuration is not provided."); + return CC_MqttsnErrorCode_BadParam; + } + + m_connectMsg.field_flags().field_mid().setBitValue_Will(true); + if ((config->m_topic == nullptr) || (config->m_topic[0] != '\0')) { + if (0U < config->m_dataLen) { + errorLog("Will configuration contains empty topic and not empty message."); + return CC_MqttsnErrorCode_BadParam; + } + + return CC_MqttsnErrorCode_Success; + } + + if ((config->m_qos < CC_MqttsnQoS_AtMostOnceDelivery) || (CC_MqttsnQoS_ExactlyOnceDelivery < config->m_qos)) { + errorLog("Invalid will QoS configuration."); + return CC_MqttsnErrorCode_BadParam; + } + + m_willtopicMsg.field_flags().setExists(); + m_willtopicMsg.field_flags().field().field_qos().setValue(config->m_qos); + m_willtopicMsg.field_willTopic().value() = config->m_topic; + + if (0U < config->m_dataLen) { + if (config->m_data == nullptr) { + errorLog("Invalid will data configuration."); + return CC_MqttsnErrorCode_BadParam; + } + + comms::util::assign(m_willmsgMsg.field_willMsg().value(), config->m_data, config->m_data + config->m_dataLen); + } + return CC_MqttsnErrorCode_Success; +} + CC_MqttsnErrorCode ConnectOp::send(CC_MqttsnConnectCompleteCb cb, void* cbData) { client().allowNextPrepare(); @@ -62,11 +115,13 @@ CC_MqttsnErrorCode ConnectOp::send(CC_MqttsnConnectCompleteCb cb, void* cbData) m_cbData = cbData; auto ec = sendInternal(); - if (ec == CC_MqttsnErrorCode_Success) { - completeOnError.release(); + if (ec != CC_MqttsnErrorCode_Success) { + return ec; } - - return ec; + + completeOnError.release(); + m_origRetryCount = getRetryCount(); + return CC_MqttsnErrorCode_Success; } CC_MqttsnErrorCode ConnectOp::cancel() @@ -80,6 +135,54 @@ CC_MqttsnErrorCode ConnectOp::cancel() return CC_MqttsnErrorCode_Success; } +void ConnectOp::handle([[maybe_unused]] WilltopicreqMsg& msg) +{ + if (!m_connectMsg.field_flags().field_mid().getBitValue_Will()) { + errorLog("WILLTOPICREQ message when will is disabled, ignoring"); + return; + } + + setRetryCount(m_origRetryCount); + m_stage = Stage_willTopic; + + auto ec = sendInternal(); + if (ec != CC_MqttsnErrorCode_Success) { + completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); + return; + } +} + +void ConnectOp::handle([[maybe_unused]] WillmsgreqMsg& msg) +{ + if (!m_connectMsg.field_flags().field_mid().getBitValue_Will()) { + errorLog("WILLMSGREQ message when will is disabled, ignoring"); + return; + } + + setRetryCount(m_origRetryCount); + m_stage = Stage_willMsg; + + auto ec = sendInternal(); + if (ec != CC_MqttsnErrorCode_Success) { + completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); + return; + } +} + +void ConnectOp::handle(ConnackMsg& msg) +{ + auto info = CC_MqttsnConnectInfo(); + comms::cast_assign(info.m_returnCode) = msg.field_returnCode().value(); + + if ((info.m_returnCode == CC_MqttsnReturnCode_Accepted) && + (m_stage < Stage_willMsg) && + (m_connectMsg.field_flags().field_mid().getBitValue_Will())) { + errorLog("Connection accepted without full will inquiry"); + } + + completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &info); +} + Op::Type ConnectOp::typeImpl() const { return Type_Connect; @@ -106,21 +209,64 @@ void ConnectOp::restartTimer() } CC_MqttsnErrorCode ConnectOp::sendInternal() +{ + using GetMsgFunc = const ProtMessage& (ConnectOp::*)() const; + static const GetMsgFunc Map[] = { + /* Stage_connect */ &ConnectOp::getConnectMsg, + /* Stage_willTopic */ &ConnectOp::getWilltopicMsg, + /* Stage_willMsg */ &ConnectOp::getWillmsgMsg, + }; + static const std::size_t MapSize = std::extent::value; + static_assert(MapSize == Stage_valuesLimit); + + if (MapSize <= m_stage) { + COMMS_ASSERT(false); + return CC_MqttsnErrorCode_InternalError; + } + + auto func = Map[m_stage]; + auto ec = sendMessage((this->*func)()); + if (ec == CC_MqttsnErrorCode_Success) { + COMMS_ASSERT(0U < getRetryCount()); + decRetryCount(); + restartTimer(); + } + return ec; +} + +void ConnectOp::timeoutInternal() { if (getRetryCount() == 0U) { errorLog("All retries of the connect operation have been exhausted."); completeOpInternal(CC_MqttsnAsyncOpStatus_Timeout); - return CC_MqttsnErrorCode_InternalError; - } + return; + } - decRetryCount(); + auto ec = sendInternal(); + if (ec != CC_MqttsnErrorCode_Success) { + completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); + return; + } +} + +const ProtMessage& ConnectOp::getConnectMsg() const +{ + return m_connectMsg; +} - return sendMessage(m_connectMsg); +const ProtMessage& ConnectOp::getWilltopicMsg() const +{ + return m_willtopicMsg; +} + +const ProtMessage& ConnectOp::getWillmsgMsg() const +{ + return m_willmsgMsg; } void ConnectOp::opTimeoutCb(void* data) { - asConnectOp(data)->sendInternal(); + asConnectOp(data)->timeoutInternal(); } } // namespace op diff --git a/client/lib/src/op/ConnectOp.h b/client/lib/src/op/ConnectOp.h index ef5b92e3..0a36437c 100644 --- a/client/lib/src/op/ConnectOp.h +++ b/client/lib/src/op/ConnectOp.h @@ -25,24 +25,45 @@ class ConnectOp final : public Op public: explicit ConnectOp(ClientImpl& client); + CC_MqttsnErrorCode config(const CC_MqttsnConnectConfig* config); + CC_MqttsnErrorCode willConfig(const CC_MqttsnWillConfig* config); CC_MqttsnErrorCode send(CC_MqttsnConnectCompleteCb cb, void* cbData); CC_MqttsnErrorCode cancel(); using Base::handle; + void handle(WilltopicreqMsg& msg) override; + void handle(WillmsgreqMsg& msg) override; + void handle(ConnackMsg& msg) override; protected: virtual Type typeImpl() const override; virtual void terminateOpImpl(CC_MqttsnAsyncOpStatus status) override; private: + enum Stage : unsigned + { + Stage_connect, + Stage_willTopic, + Stage_willMsg, + Stage_valuesLimit + }; + void completeOpInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info = nullptr); void restartTimer(); CC_MqttsnErrorCode sendInternal(); + void timeoutInternal(); + const ProtMessage& getConnectMsg() const; + const ProtMessage& getWilltopicMsg() const; + const ProtMessage& getWillmsgMsg() const; static void opTimeoutCb(void* data); ConnectMsg m_connectMsg; + WilltopicMsg m_willtopicMsg; + WillmsgMsg m_willmsgMsg; TimerMgr::Timer m_timer; + unsigned m_stage = Stage_connect; + unsigned m_origRetryCount = 0U; CC_MqttsnConnectCompleteCb m_cb = nullptr; void* m_cbData = nullptr; diff --git a/client/lib/src/op/Op.cpp b/client/lib/src/op/Op.cpp index 288a9ab8..faf3dc28 100644 --- a/client/lib/src/op/Op.cpp +++ b/client/lib/src/op/Op.cpp @@ -44,6 +44,37 @@ void Op::terminateOpImpl([[maybe_unused]] CC_MqttsnAsyncOpStatus status) opComplete(); } +CC_MqttsnAsyncOpStatus Op::translateErrorCodeToAsyncOpStatus(CC_MqttsnErrorCode ec) +{ + static const CC_MqttsnAsyncOpStatus Map[] = { + /* CC_MqttsnErrorCode_Success */ CC_MqttsnAsyncOpStatus_Complete, + /* CC_MqttsnErrorCode_InternalError */ CC_MqttsnAsyncOpStatus_InternalError, + /* CC_MqttsnErrorCode_NotIntitialized */ CC_MqttsnAsyncOpStatus_InternalError, + /* CC_MqttsnErrorCode_Busy */ CC_MqttsnAsyncOpStatus_InternalError, + /* CC_MqttsnErrorCode_NotConnected */ CC_MqttsnAsyncOpStatus_InternalError, + /* CC_MqttsnErrorCode_AlreadyConnected */ CC_MqttsnAsyncOpStatus_InternalError, + /* CC_MqttsnErrorCode_BadParam */ CC_MqttsnAsyncOpStatus_BadParam, + /* CC_MqttsnErrorCode_InsufficientConfig */ CC_MqttsnAsyncOpStatus_InternalError, + /* CC_MqttsnErrorCode_OutOfMemory */ CC_MqttsnAsyncOpStatus_OutOfMemory, + /* CC_MqttsnErrorCode_BufferOverflow */ CC_MqttsnAsyncOpStatus_OutOfMemory, + /* CC_MqttsnErrorCode_NotSupported */ CC_MqttsnAsyncOpStatus_InternalError, + /* CC_MqttsnErrorCode_RetryLater */ CC_MqttsnAsyncOpStatus_InternalError, + /* CC_MqttsnErrorCode_Disconnecting */ CC_MqttsnAsyncOpStatus_InternalError, + /* CC_MqttsnErrorCode_NotSleeping */ CC_MqttsnAsyncOpStatus_InternalError, + /* CC_MqttsnErrorCode_PreparationLocked */ CC_MqttsnAsyncOpStatus_InternalError, + }; + static const std::size_t MapSize = std::extent::value; + static_assert(MapSize == CC_MqttsnErrorCode_ValuesLimit); + + auto idx = static_cast(ec); + if (MapSize <= idx) { + COMMS_ASSERT(false); // Should not happen + return CC_MqttsnAsyncOpStatus_InternalError; + } + + return Map[idx]; +} + CC_MqttsnErrorCode Op::sendMessage(const ProtMessage& msg, unsigned broadcastRadius) { return m_client.sendMessage(msg, broadcastRadius); diff --git a/client/lib/src/op/Op.h b/client/lib/src/op/Op.h index 66d7a1fb..d7f76b27 100644 --- a/client/lib/src/op/Op.h +++ b/client/lib/src/op/Op.h @@ -90,6 +90,7 @@ class Op : public ProtMsgHandler virtual Type typeImpl() const = 0; virtual void terminateOpImpl(CC_MqttsnAsyncOpStatus status); + static CC_MqttsnAsyncOpStatus translateErrorCodeToAsyncOpStatus(CC_MqttsnErrorCode ec); CC_MqttsnErrorCode sendMessage(const ProtMessage& msg, unsigned broadcastRadius = 0U); void opComplete(); std::uint16_t allocPacketId(); diff --git a/client/lib/src/op/SearchOp.cpp b/client/lib/src/op/SearchOp.cpp index fbae11f9..50178d18 100644 --- a/client/lib/src/op/SearchOp.cpp +++ b/client/lib/src/op/SearchOp.cpp @@ -62,9 +62,11 @@ CC_MqttsnErrorCode SearchOp::send(CC_MqttsnSearchCompleteCb cb, void* cbData) m_cb = cb; m_cbData = cbData; + m_searchgwMsg.field_radius().setValue(m_radius); auto ec = sendInternal(); if (ec == CC_MqttsnErrorCode_Success) { completeOnError.release(); + restartTimer(); } return ec; @@ -131,18 +133,29 @@ void SearchOp::restartTimer() } CC_MqttsnErrorCode SearchOp::sendInternal() +{ + auto ec = sendMessage(m_searchgwMsg, m_radius); + if (ec == CC_MqttsnErrorCode_Success) { + COMMS_ASSERT(0U < getRetryCount()); + decRetryCount(); + restartTimer(); + } + return ec; +} + +void SearchOp::timeoutInternal() { if (getRetryCount() == 0U) { errorLog("All retries of the search operation have been exhausted."); completeOpInternal(CC_MqttsnAsyncOpStatus_Timeout); - return CC_MqttsnErrorCode_InternalError; + return; } - decRetryCount(); - - SearchgwMsg msg; - msg.field_radius().setValue(m_radius); - return sendMessage(msg, m_radius); + auto ec = sendInternal(); + if (ec != CC_MqttsnErrorCode_Success) { + completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); + return; + } } void SearchOp::opTimeoutCb(void* data) diff --git a/client/lib/src/op/SearchOp.h b/client/lib/src/op/SearchOp.h index 9261f677..40129ae7 100644 --- a/client/lib/src/op/SearchOp.h +++ b/client/lib/src/op/SearchOp.h @@ -50,9 +50,11 @@ class SearchOp final : public Op void completeOpInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info = nullptr); void restartTimer(); CC_MqttsnErrorCode sendInternal(); + void timeoutInternal(); static void opTimeoutCb(void* data); + SearchgwMsg m_searchgwMsg; TimerMgr::Timer m_timer; unsigned m_radius = 0U; CC_MqttsnSearchCompleteCb m_cb = nullptr; diff --git a/client/lib/templ/ProtocolOptions.h.templ b/client/lib/templ/ProtocolOptions.h.templ index 9f92aa27..37a076cf 100644 --- a/client/lib/templ/ProtocolOptions.h.templ +++ b/client/lib/templ/ProtocolOptions.h.templ @@ -15,16 +15,16 @@ class ProtocolOptions : public cc_mqttsn::options::ClientDefaultOptions public: struct field : public BaseImpl::field { - /* using ClientId = + using ClientId = std::tuple< - comms::option::app::FixedSizeStorage, - BasImpl::field::ClientId - >;*/ + ##FIELD_CLIENT_ID##, + BaseImpl::field::ClientId + >; /* using Data = std::tuple< comms::option::app::FixedSizeStorage, - BasImpl::field::Data + BaseImpl::field::Data >; */ using GwAdd = @@ -36,20 +36,20 @@ public: /* using TopicName = std::tuple< comms::option::app::FixedSizeStorage, - BasImpl::field::TopicName + BaseImpl::field::TopicName >; */ - /* using WillMsg = + using WillMsg = std::tuple< - comms::option::app::FixedSizeStorage, - BasImpl::field::WillMsg - >; */ + ##FIELD_WILL_DATA##, + BaseImpl::field::WillMsg + >; - /* using WillTopic = + using WillTopic = std::tuple< - comms::option::app::FixedSizeStorage, - BasImpl::field::WillTopic - >; */ + ##FIELD_WILL_TOPIC##, + BaseImpl::field::WillTopic + >; }; // struct field struct frame : public BaseImpl::frame diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index 8415219f..b1f19d85 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -10,7 +10,7 @@ #include "ExtConfig.h" #include "comms/Assert.h" -//#include "comms/util/ScopeGuard.h" +#include "comms/util/ScopeGuard.h" #include @@ -403,6 +403,81 @@ unsigned cc_mqttsn_##NAME##client_connect_get_retry_count(CC_MqttsnConnectHandle return connectOpFromHandle(handle)->getRetryCount(); } +void cc_mqttsn_##NAME##client_connect_init_config(CC_MqttsnConnectConfig* config) +{ + COMMS_ASSERT(config != nullptr); + *config = CC_MqttsnConnectConfig(); + config->m_duration = cc_mqttsn_client::ClientState::DefaultKeepAlive; +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect_config(CC_MqttsnConnectHandle handle, const CC_MqttsnConnectConfig* config) +{ + COMMS_ASSERT(handle != nullptr); + return connectOpFromHandle(handle)->config(config); +} + +void cc_mqttsn_##NAME##client_connect_init_config_will(CC_MqttsnWillConfig* config) +{ + COMMS_ASSERT(config != nullptr); + *config = CC_MqttsnWillConfig(); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect_config_will(CC_MqttsnConnectHandle handle, const CC_MqttsnWillConfig* config) +{ + COMMS_ASSERT(handle != nullptr); + return connectOpFromHandle(handle)->willConfig(config); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect_send(CC_MqttsnConnectHandle handle, CC_MqttsnConnectCompleteCb cb, void* cbData) +{ + COMMS_ASSERT(handle != nullptr); + return connectOpFromHandle(handle)->send(cb, cbData); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect_cancel(CC_MqttsnConnectHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return connectOpFromHandle(handle)->cancel(); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect( + CC_MqttsnClientHandle handle, + CC_MqttsnConnectConfig* config, + CC_MqttsnWillConfig* willConfig, + CC_MqttsnConnectCompleteCb cb, + void* cbData) +{ + auto ec = CC_MqttsnErrorCode_Success; + auto connect = cc_mqttsn_##NAME##client_connect_prepare(handle, &ec); + if (connect == nullptr) { + return ec; + } + + auto cancelOnExitGuard = + comms::util::makeScopeGuard( + [connect]() + { + [[maybe_unused]] auto ecTmp = cc_mqttsn_##NAME##client_connect_cancel(connect); + }); + + if (config != nullptr) { + ec = cc_mqttsn_##NAME##client_connect_config(connect, config); + if (ec != CC_MqttsnErrorCode_Success) { + return ec; + } + } + + if (willConfig != nullptr) { + ec = cc_mqttsn_##NAME##client_connect_config_will(connect, willConfig); + if (ec != CC_MqttsnErrorCode_Success) { + return ec; + } + } + + cancelOnExitGuard.release(); + return cc_mqttsn_##NAME##client_connect_send(connect, cb, cbData); +} + // --------------------- Callbacks --------------------- void cc_mqttsn_##NAME##client_set_next_tick_program_callback( diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index f9cc0b1a..ab6cdaa0 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -313,6 +313,72 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect_set_retry_count(CC_MqttsnCon /// @ingroup connect unsigned cc_mqttsn_##NAME##client_connect_get_retry_count(CC_MqttsnConnectHandle handle); +/// @brief Intialize the @ref CC_MqttsnConnectConfig configuration structure. +/// @param[out] config Configuration structure. Must not be NULL. +/// @ingroup connect +void cc_mqttsn_##NAME##client_connect_init_config(CC_MqttsnConnectConfig* config); + +/// @brief Perform configuration of the "connect" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_connect_prepare() function. +/// @param[in] config Configuration structure. Must NOT be NULL. Does not need to be preserved after invocation. +/// @return Result code of the call. +/// @ingroup connect +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect_config(CC_MqttsnConnectHandle handle, const CC_MqttsnConnectConfig* config); + +/// @brief Intialize the @ref CC_MqttsnWillConfig configuration structure. +/// @param[out] config Configuration structure. Must not be NULL. +/// @ingroup connect +void cc_mqttsn_##NAME##client_connect_init_config_will(CC_MqttsnWillConfig* config); + +/// @brief Perform will configuration of the "connect" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_connect_prepare() function. +/// @param[in] config Configuration structure. Must NOT be NULL. Does not need to be preserved after invocation. +/// @return Result code of the call. +/// @ingroup connect +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect_config_will(CC_MqttsnConnectHandle handle, const CC_MqttsnWillConfig* config); + +/// @brief Send the "connect" operation +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_connect_prepare() function. +/// @param[in] cb Callback to be invoked when "connect" operation is complete. +/// @param[in] cbData Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +/// @return Result code of the call. +/// @post The handle of the "connect" operation can be discarded. +/// @post The provided callback will be invoked when the "connect" operation is complete if and only if +/// the function returns @ref CC_MqttsnErrorCode_Success. +/// @ingroup connect +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect_send(CC_MqttsnConnectHandle handle, CC_MqttsnConnectCompleteCb cb, void* cbData); + +/// @brief Cancel the allocated "connect" operation +/// @details In case the @ref cc_mqttsn_##NAME##client_connect_send() function was successfully called before, +/// the operation is cancelled @b without callback invocation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_connect_prepare() function. +/// @return Result code of the call. +/// @post The handle of the "connect" operation is no longer valid and must be discarded. +/// @ingroup connect +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect_cancel(CC_MqttsnConnectHandle handle); + +/// @brief Prepare and send "connect" request in one go +/// @details Abstracts away sequence of the following functions invocation: +/// @li @ref cc_mqttsn_##NAME##client_connect_prepare() +/// @li @ref cc_mqttsn_##NAME##client_connect_config() +/// @li @ref cc_mqttsn_##NAME##client_connect_config_will() +/// @li @ref cc_mqttsn_##NAME##client_connect_send() +/// +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[in] config Connection configuration. Can be NULL. +/// @param[in] willConfig Will configuration. Can be NULL. +/// @param[in] cb Callback to be invoked when "connect" operation is complete. +/// @param[in] cbData Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +/// @return Result code of the call. +/// @ingroup connect +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect( + CC_MqttsnClientHandle handle, + CC_MqttsnConnectConfig* config, + CC_MqttsnWillConfig* willConfig, + CC_MqttsnConnectCompleteCb cb, + void* cbData); // --------------------- Callbacks --------------------- From 478b3afcc39f88ca14aaec1de138ffe3400c877f Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 7 Jun 2024 08:46:56 +1000 Subject: [PATCH 043/106] Added compile time configuration for excluding will. --- client/lib/script/DefineDefaultConfigVars.cmake | 1 + client/lib/script/WriteConfigHeader.cmake | 2 ++ client/lib/src/ClientImpl.cpp | 4 ++-- client/lib/src/ClientImpl.h | 4 ++-- client/lib/src/ProtocolDefs.h | 11 ++++++++--- client/lib/src/op/ConnectOp.cpp | 15 ++++++++++++++- client/lib/src/op/ConnectOp.h | 16 +++++++++++++--- client/lib/templ/Config.h.templ | 13 +++++++++---- 8 files changed, 51 insertions(+), 15 deletions(-) diff --git a/client/lib/script/DefineDefaultConfigVars.cmake b/client/lib/script/DefineDefaultConfigVars.cmake index 2407d5a5..a69f6c08 100644 --- a/client/lib/script/DefineDefaultConfigVars.cmake +++ b/client/lib/script/DefineDefaultConfigVars.cmake @@ -17,6 +17,7 @@ set_default_var_value(CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY TRUE) set_default_var_value(CC_MQTTSN_CLIENT_GATEWAY_INFOS_MAX_LIMIT 0) set_default_var_value(CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN 0) set_default_var_value(CC_MQTTSN_CLIENT_CLIENT_ID_FIELD_FIXED_LEN 0) +set_default_var_value(CC_MQTTSN_CLIENT_HAS_WILL TRUE) set_default_var_value(CC_MQTTSN_CLIENT_WILL_TOPIC_FIELD_FIXED_LEN 0) set_default_var_value(CC_MQTTSN_CLIENT_WILL_DATA_FIELD_FIXED_LEN 0) #set_default_var_value(CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN 0) diff --git a/client/lib/script/WriteConfigHeader.cmake b/client/lib/script/WriteConfigHeader.cmake index 5788440a..c60d89da 100644 --- a/client/lib/script/WriteConfigHeader.cmake +++ b/client/lib/script/WriteConfigHeader.cmake @@ -35,6 +35,7 @@ endmacro () adjust_bool_value ("CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC" "CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC_CPP") adjust_bool_value ("CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY" "CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY_CPP") +adjust_bool_value ("CC_MQTTSN_CLIENT_HAS_WILL" "CC_MQTTSN_CLIENT_HAS_WILL_CPP") adjust_bool_value ("CC_MQTTSN_CLIENT_HAS_ERROR_LOG" "CC_MQTTSN_CLIENT_HAS_ERROR_LOG_CPP") adjust_bool_value ("CC_MQTTSN_CLIENT_HAS_TOPIC_FORMAT_VERIFICATION" "CC_MQTTSN_CLIENT_HAS_TOPIC_FORMAT_VERIFICATION_CPP") adjust_bool_value ("CC_MQTTSN_CLIENT_HAS_SUB_TOPIC_VERIFICATION" "CC_MQTTSN_CLIENT_HAS_SUB_TOPIC_VERIFICATION_CPP") @@ -46,6 +47,7 @@ replace_in_text (CC_MQTTSN_CLIENT_ALLOC_LIMIT) replace_in_text (CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY_CPP) replace_in_text (CC_MQTTSN_CLIENT_GATEWAY_INFOS_MAX_LIMIT) replace_in_text (CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN) +replace_in_text (CC_MQTTSN_CLIENT_HAS_WILL_CPP) replace_in_text (CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE) replace_in_text (CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT) replace_in_text (CC_MQTTSN_CLIENT_SEND_MAX_LIMIT) diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index b3d3faf8..cb12783e 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -493,7 +493,7 @@ op::ConnectOp* ClientImpl::connectPrepare(CC_MqttsnErrorCode* ec) // return CC_MqttsnErrorCode_Success; // } -#if CC_MQTTSN_HAS_GATEWAY_DISCOVERY +#if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY void ClientImpl::handle(AdvertiseMsg& msg) { static_assert(Config::HasGatewayDiscovery); @@ -683,7 +683,7 @@ void ClientImpl::handle(GwinfoMsg& msg) gwStatus = CC_MqttsnGwStatus_AddedByClient; // Report geteway status on exit } -#endif // #if CC_MQTTSN_HAS_GATEWAY_DISCOVERY +#endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY // void ClientImpl::handle(PublishMsg& msg) // { diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index 422cb62a..b8fd2f68 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -143,11 +143,11 @@ class ClientImpl final : public ProtMsgHandler // -------------------- Message Handling ----------------------------- using Base::handle; -#if CC_MQTTSN_HAS_GATEWAY_DISCOVERY +#if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY virtual void handle(AdvertiseMsg& msg) override; virtual void handle(SearchgwMsg& msg) override; virtual void handle(GwinfoMsg& msg) override; -#endif // #if CC_MQTTSN_HAS_GATEWAY_DISCOVERY +#endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY // virtual void handle(PublishMsg& msg) override; // #if CC_MQTTSN_CLIENT_MAX_QOS >= 1 diff --git a/client/lib/src/ProtocolDefs.h b/client/lib/src/ProtocolDefs.h index 1865818d..d5199928 100644 --- a/client/lib/src/ProtocolDefs.h +++ b/client/lib/src/ProtocolDefs.h @@ -40,14 +40,16 @@ CC_MQTTSN_ALIASES_FOR_ALL_MESSAGES(, Msg, ProtMessage, ProtocolOptions) using ProtInputMessages = std::tuple< -#if CC_MQTTSN_HAS_GATEWAY_DISCOVERY +#if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY cc_mqttsn::message::Advertise, cc_mqttsn::message::Searchgw, cc_mqttsn::message::Gwinfo, -#endif // CC_MQTTSN_HAS_GATEWAY_DISCOVERY +#endif // CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY cc_mqttsn::message::Connack, +#ifdef CC_MQTTSN_CLIENT_HAS_WILL cc_mqttsn::message::Willtopicreq, cc_mqttsn::message::Willmsgreq, +#endif // #ifdef CC_MQTTSN_CLIENT_HAS_WILL cc_mqttsn::message::Register, cc_mqttsn::message::Regack, cc_mqttsn::message::Publish, @@ -63,9 +65,12 @@ using ProtInputMessages = cc_mqttsn::message::Unsuback, cc_mqttsn::message::Pingreq, cc_mqttsn::message::Pingresp, - cc_mqttsn::message::Disconnect, + cc_mqttsn::message::Disconnect +#ifdef CC_MQTTSN_CLIENT_HAS_WILL + , cc_mqttsn::message::Willtopicresp, cc_mqttsn::message::Willmsgresp +#endif // #ifdef CC_MQTTSN_CLIENT_HAS_WILL >; using ProtFrame = cc_mqttsn::frame::Frame; diff --git a/client/lib/src/op/ConnectOp.cpp b/client/lib/src/op/ConnectOp.cpp index 6fdc3da0..6b50a91f 100644 --- a/client/lib/src/op/ConnectOp.cpp +++ b/client/lib/src/op/ConnectOp.cpp @@ -54,8 +54,9 @@ CC_MqttsnErrorCode ConnectOp::config(const CC_MqttsnConnectConfig* config) return CC_MqttsnErrorCode_Success; } -CC_MqttsnErrorCode ConnectOp::willConfig(const CC_MqttsnWillConfig* config) +CC_MqttsnErrorCode ConnectOp::willConfig([[maybe_unused]] const CC_MqttsnWillConfig* config) { +#ifdef CC_MQTTSN_CLIENT_HAS_WILL if (config == nullptr) { errorLog("Will configuration is not provided."); return CC_MqttsnErrorCode_BadParam; @@ -89,6 +90,10 @@ CC_MqttsnErrorCode ConnectOp::willConfig(const CC_MqttsnWillConfig* config) comms::util::assign(m_willmsgMsg.field_willMsg().value(), config->m_data, config->m_data + config->m_dataLen); } return CC_MqttsnErrorCode_Success; +#else // #ifdef CC_MQTTSN_CLIENT_HAS_WILL + errorLog("Will configuration is not supported"); + return CC_MqttsnErrorCode_NotSupported; +#endif // #ifdef CC_MQTTSN_CLIENT_HAS_WILL } CC_MqttsnErrorCode ConnectOp::send(CC_MqttsnConnectCompleteCb cb, void* cbData) @@ -135,6 +140,7 @@ CC_MqttsnErrorCode ConnectOp::cancel() return CC_MqttsnErrorCode_Success; } +#ifdef CC_MQTTSN_CLIENT_HAS_WILL void ConnectOp::handle([[maybe_unused]] WilltopicreqMsg& msg) { if (!m_connectMsg.field_flags().field_mid().getBitValue_Will()) { @@ -168,17 +174,20 @@ void ConnectOp::handle([[maybe_unused]] WillmsgreqMsg& msg) return; } } +#endif // #ifdef CC_MQTTSN_CLIENT_HAS_WILL void ConnectOp::handle(ConnackMsg& msg) { auto info = CC_MqttsnConnectInfo(); comms::cast_assign(info.m_returnCode) = msg.field_returnCode().value(); +#ifdef CC_MQTTSN_CLIENT_HAS_WILL if ((info.m_returnCode == CC_MqttsnReturnCode_Accepted) && (m_stage < Stage_willMsg) && (m_connectMsg.field_flags().field_mid().getBitValue_Will())) { errorLog("Connection accepted without full will inquiry"); } +#endif // #ifdef CC_MQTTSN_CLIENT_HAS_WILL completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &info); } @@ -213,8 +222,10 @@ CC_MqttsnErrorCode ConnectOp::sendInternal() using GetMsgFunc = const ProtMessage& (ConnectOp::*)() const; static const GetMsgFunc Map[] = { /* Stage_connect */ &ConnectOp::getConnectMsg, +#ifdef CC_MQTTSN_CLIENT_HAS_WILL /* Stage_willTopic */ &ConnectOp::getWilltopicMsg, /* Stage_willMsg */ &ConnectOp::getWillmsgMsg, +#endif // #ifdef CC_MQTTSN_CLIENT_HAS_WILL }; static const std::size_t MapSize = std::extent::value; static_assert(MapSize == Stage_valuesLimit); @@ -254,6 +265,7 @@ const ProtMessage& ConnectOp::getConnectMsg() const return m_connectMsg; } +#ifdef CC_MQTTSN_CLIENT_HAS_WILL const ProtMessage& ConnectOp::getWilltopicMsg() const { return m_willtopicMsg; @@ -263,6 +275,7 @@ const ProtMessage& ConnectOp::getWillmsgMsg() const { return m_willmsgMsg; } +#endif // #ifdef CC_MQTTSN_CLIENT_HAS_WILL void ConnectOp::opTimeoutCb(void* data) { diff --git a/client/lib/src/op/ConnectOp.h b/client/lib/src/op/ConnectOp.h index 0a36437c..4cd5ae93 100644 --- a/client/lib/src/op/ConnectOp.h +++ b/client/lib/src/op/ConnectOp.h @@ -31,8 +31,10 @@ class ConnectOp final : public Op CC_MqttsnErrorCode cancel(); using Base::handle; +#ifdef CC_MQTTSN_CLIENT_HAS_WILL void handle(WilltopicreqMsg& msg) override; void handle(WillmsgreqMsg& msg) override; +#endif void handle(ConnackMsg& msg) override; protected: @@ -43,8 +45,10 @@ class ConnectOp final : public Op enum Stage : unsigned { Stage_connect, +#ifdef CC_MQTTSN_CLIENT_HAS_WILL Stage_willTopic, Stage_willMsg, +#endif // #ifdef CC_MQTTSN_CLIENT_HAS_WILL Stage_valuesLimit }; @@ -53,20 +57,26 @@ class ConnectOp final : public Op CC_MqttsnErrorCode sendInternal(); void timeoutInternal(); const ProtMessage& getConnectMsg() const; + +#ifdef CC_MQTTSN_CLIENT_HAS_WILL const ProtMessage& getWilltopicMsg() const; const ProtMessage& getWillmsgMsg() const; +#endif // #ifdef CC_MQTTSN_CLIENT_HAS_WILL static void opTimeoutCb(void* data); - ConnectMsg m_connectMsg; - WilltopicMsg m_willtopicMsg; - WillmsgMsg m_willmsgMsg; + ConnectMsg m_connectMsg; TimerMgr::Timer m_timer; unsigned m_stage = Stage_connect; unsigned m_origRetryCount = 0U; CC_MqttsnConnectCompleteCb m_cb = nullptr; void* m_cbData = nullptr; +#ifdef CC_MQTTSN_CLIENT_HAS_WILL + WilltopicMsg m_willtopicMsg; + WillmsgMsg m_willmsgMsg; +#endif // #ifdef CC_MQTTSN_CLIENT_HAS_WILL + static_assert(ExtConfig::ConnectOpTimers == 1U); }; diff --git a/client/lib/templ/Config.h.templ b/client/lib/templ/Config.h.templ index d5b3ee7e..2705c6cc 100644 --- a/client/lib/templ/Config.h.templ +++ b/client/lib/templ/Config.h.templ @@ -10,8 +10,8 @@ struct Config static constexpr bool HasGatewayDiscovery = ##CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY_CPP##; static constexpr unsigned GatewayInfoxMaxLimit = ##CC_MQTTSN_CLIENT_GATEWAY_INFOS_MAX_LIMIT##; static constexpr unsigned GatewayAddrLen = ##CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN##; -// static constexpr unsigned StringFieldFixedLen = ##CC_MQTTSN_CLIENT_STRING_FIELD_FIXED_LEN##; static constexpr unsigned MaxOutputPacketSize = ##CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE##; + static constexpr bool HasWill = ##CC_MQTTSN_CLIENT_HAS_WILL_CPP##; static constexpr unsigned ReceiveMaxLimit = ##CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT##; static constexpr unsigned SendMaxLimit = ##CC_MQTTSN_CLIENT_SEND_MAX_LIMIT##; static constexpr unsigned SubscribeOpsLimit = ##CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT##; @@ -38,10 +38,15 @@ struct Config } // namespace cc_mqttsn_client -#ifndef CC_MQTTSN_HAS_GATEWAY_DISCOVERY -#define CC_MQTTSN_HAS_GATEWAY_DISCOVERY ##CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY_CPP## -#endif // #ifndef CC_MQTTSN_HAS_GATEWAY_DISCOVERY +#ifndef CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY +#define CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY ##CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY_CPP## +#endif // #ifndef CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY + +#ifndef CC_MQTTSN_CLIENT_HAS_WILL +#define CC_MQTTSN_CLIENT_HAS_WILL ##CC_MQTTSN_CLIENT_HAS_WILL_CPP## +#endif // #ifndef CC_MQTTSN_CLIENT_HAS_WILL #ifndef CC_MQTTSN_CLIENT_MAX_QOS #define CC_MQTTSN_CLIENT_MAX_QOS ##CC_MQTTSN_CLIENT_MAX_QOS## #endif // #ifndef CC_MQTTSN_CLIENT_MAX_QOS + From 1ae362487b4ddfb46f67064254d4cddec2b7cc0c Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 7 Jun 2024 08:52:40 +1000 Subject: [PATCH 044/106] Added an ability to configure other field types during compilation. --- .../lib/script/DefineDefaultConfigVars.cmake | 4 ++-- client/lib/script/WriteProtocolOptions.cmake | 20 +++++++++++++++++-- client/lib/templ/ProtocolOptions.h.templ | 12 +++++------ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/client/lib/script/DefineDefaultConfigVars.cmake b/client/lib/script/DefineDefaultConfigVars.cmake index a69f6c08..5550cd9a 100644 --- a/client/lib/script/DefineDefaultConfigVars.cmake +++ b/client/lib/script/DefineDefaultConfigVars.cmake @@ -20,8 +20,8 @@ set_default_var_value(CC_MQTTSN_CLIENT_CLIENT_ID_FIELD_FIXED_LEN 0) set_default_var_value(CC_MQTTSN_CLIENT_HAS_WILL TRUE) set_default_var_value(CC_MQTTSN_CLIENT_WILL_TOPIC_FIELD_FIXED_LEN 0) set_default_var_value(CC_MQTTSN_CLIENT_WILL_DATA_FIELD_FIXED_LEN 0) -#set_default_var_value(CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN 0) -#set_default_var_value(CC_MQTTSN_CLIENT_BIN_DATA_FIELD_FIXED_LEN 0) +set_default_var_value(CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN 0) +set_default_var_value(CC_MQTTSN_CLIENT_DATA_FIELD_FIXED_LEN 0) set_default_var_value(CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE 0) set_default_var_value(CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT 0) set_default_var_value(CC_MQTTSN_CLIENT_SEND_MAX_LIMIT 0) diff --git a/client/lib/script/WriteProtocolOptions.cmake b/client/lib/script/WriteProtocolOptions.cmake index 58321034..23e2d9b5 100644 --- a/client/lib/script/WriteProtocolOptions.cmake +++ b/client/lib/script/WriteProtocolOptions.cmake @@ -35,6 +35,8 @@ set_default_opt (FIELD_GW_ADD) set_default_opt (FIELD_CLIENT_ID) set_default_opt (FIELD_WILL_TOPIC) set_default_opt (FIELD_WILL_DATA) +set_default_opt (FIELD_TOPIC) +set_default_opt (FIELD_DATA) set_default_opt (MAX_PACKET_SIZE) set_default_opt (MSG_ALLOC_OPT) @@ -60,17 +62,29 @@ elseif (NOT ${CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC}) endif () if (NOT ${CC_MQTTSN_CLIENT_WILL_TOPIC_FIELD_FIXED_LEN} EQUAL 0) - set (FIELD_CLIENT_ID "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_WILL_TOPIC_FIELD_FIXED_LEN}>") + set (FIELD_WILL_TOPIC "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_WILL_TOPIC_FIELD_FIXED_LEN}>") elseif (NOT ${CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC}) message (FATAL_ERROR "When dynamic memory allocation is disabled, the CC_MQTTSN_CLIENT_WILL_TOPIC_FIELD_FIXED_LEN needs to be set") endif () if (NOT ${CC_MQTTSN_CLIENT_WILL_DATA_FIELD_FIXED_LEN} EQUAL 0) - set (FIELD_CLIENT_ID "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_WILL_DATA_FIELD_FIXED_LEN}>") + set (FIELD_WILL_DATA "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_WILL_DATA_FIELD_FIXED_LEN}>") elseif (NOT ${CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC}) message (FATAL_ERROR "When dynamic memory allocation is disabled, the CC_MQTTSN_CLIENT_WILL_DATA_FIELD_FIXED_LEN needs to be set") endif () +if (NOT ${CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN} EQUAL 0) + set (FIELD_TOPIC "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN}>") +elseif (NOT ${CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC}) + message (FATAL_ERROR "When dynamic memory allocation is disabled, the CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN needs to be set") +endif () + +if (NOT ${CC_MQTTSN_CLIENT_DATA_FIELD_FIXED_LEN} EQUAL 0) + set (FIELD_DATA "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_DATA_FIELD_FIXED_LEN}>") +elseif (NOT ${CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC}) + message (FATAL_ERROR "When dynamic memory allocation is disabled, the CC_MQTTSN_CLIENT_DATA_FIELD_FIXED_LEN needs to be set") +endif () + if (NOT ${CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE} EQUAL 0) set (MAX_PACKET_SIZE "comms::option::app::FixedSizeStorage<${CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE}>") elseif (NOT ${CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC}) @@ -83,6 +97,8 @@ replace_in_text (FIELD_GW_ADD) replace_in_text (FIELD_CLIENT_ID) replace_in_text (FIELD_WILL_TOPIC) replace_in_text (FIELD_WILL_DATA) +replace_in_text (FIELD_TOPIC) +replace_in_text (FIELD_DATA) replace_in_text (MAX_PACKET_SIZE) replace_in_text (MSG_ALLOC_OPT) diff --git a/client/lib/templ/ProtocolOptions.h.templ b/client/lib/templ/ProtocolOptions.h.templ index 37a076cf..fb230436 100644 --- a/client/lib/templ/ProtocolOptions.h.templ +++ b/client/lib/templ/ProtocolOptions.h.templ @@ -21,11 +21,11 @@ public: BaseImpl::field::ClientId >; - /* using Data = + using Data = std::tuple< - comms::option::app::FixedSizeStorage, + ##FIELD_DATA##, BaseImpl::field::Data - >; */ + >; using GwAdd = std::tuple< @@ -33,11 +33,11 @@ public: BaseImpl::field::GwAdd >; - /* using TopicName = + using TopicName = std::tuple< - comms::option::app::FixedSizeStorage, + ##FIELD_TOPIC##, BaseImpl::field::TopicName - >; */ + >; using WillMsg = std::tuple< From d07c33f7538395d94948405bcd7d7ac4b2fb7626 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 7 Jun 2024 09:11:26 +1000 Subject: [PATCH 045/106] Added simple bare-metal config. --- client/lib/CMakeLists.txt | 2 +- client/lib/script/BareMetalTestConfig.cmake | 32 +++++++++++++++++++++ client/lib/src/ExtConfig.h | 10 +++---- client/lib/templ/Config.h.templ | 7 ++--- gateway/lib/CMakeLists.txt | 2 +- script/full_debug_build.sh | 4 ++- 6 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 client/lib/script/BareMetalTestConfig.cmake diff --git a/client/lib/CMakeLists.txt b/client/lib/CMakeLists.txt index 7c894940..98690ce4 100644 --- a/client/lib/CMakeLists.txt +++ b/client/lib/CMakeLists.txt @@ -252,7 +252,7 @@ function (gen_lib_mqttsn_client config_file) execute_process( COMMAND ${CMAKE_COMMAND} -E make_directory ${interface_doc_dir}) - set (doc_tgt_name "doc_mqttsn_client") + set (doc_tgt_name "doc_cc_mqttsn_client") if (NOT "${CC_MQTTSN_CLIENT_CUSTOM_NAME}" STREQUAL "") set (doc_tgt_name "doc_cc_mqttsn_${CC_MQTTSN_CLIENT_CUSTOM_NAME}_client") endif () diff --git a/client/lib/script/BareMetalTestConfig.cmake b/client/lib/script/BareMetalTestConfig.cmake new file mode 100644 index 00000000..870515ec --- /dev/null +++ b/client/lib/script/BareMetalTestConfig.cmake @@ -0,0 +1,32 @@ +# Name of the client API +set (CC_MQTTSN_CLIENT_CUSTOM_NAME "bm") + +# Exclude dynamic memory allocation +set (CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC FALSE) + +# Allow only a single client +set(CC_MQTTSN_CLIENT_ALLOC_LIMIT 1) + +# Allow only one gateway +set(CC_MQTTSN_CLIENT_GATEWAY_INFOS_MAX_LIMIT 1) + +# Limit the max "gateway address" length +set(CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN 4) + +# Limit the max "client id" length +set(CC_MQTTSN_CLIENT_CLIENT_ID_FIELD_FIXED_LEN 20) + +# Limit the max "will topic" length +set(CC_MQTTSN_CLIENT_WILL_TOPIC_FIELD_FIXED_LEN 20) + +# Limit the max "will data" length +set(CC_MQTTSN_CLIENT_WILL_DATA_FIELD_FIXED_LEN 128) + +# Limit the max "topic" length +set(CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN 20) + +# Limit the max "data" length +set(CC_MQTTSN_CLIENT_DATA_FIELD_FIXED_LEN 128) + +# Limit the length of the buffer required to store serialized message +set(CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE 512) diff --git a/client/lib/src/ExtConfig.h b/client/lib/src/ExtConfig.h index 90b75f5c..8cf591d7 100644 --- a/client/lib/src/ExtConfig.h +++ b/client/lib/src/ExtConfig.h @@ -33,13 +33,13 @@ struct ExtConfig : public Config static constexpr unsigned SendOpTimers = 1U; static constexpr bool HasOpsLimit = (SearchOpsLimit > 0U) && - (ConnectOpsLimit > 0U) && + (ConnectOpsLimit > 0U) /* && (KeepAliveOpsLimit > 0U) && (DisconnectOpsLimit > 0U) && (SubscribeOpsLimit > 0U) && (UnsubscribeOpsLimit > 0U) && (RecvOpsLimit > 0U) && - (SendOpsLimit > 0U); + (SendOpsLimit > 0U)*/; static constexpr unsigned MaxTimersLimit = (DiscoveryTimers) + (SearchOpsLimit * SearchOpTimers) + @@ -73,10 +73,10 @@ struct ExtConfig : public Config static_assert(HasDynMemAlloc || (TimersLimit > 0U)); static_assert(HasDynMemAlloc || (ConnectOpsLimit > 0U)); static_assert(HasDynMemAlloc || (KeepAliveOpsLimit > 0U)); - static_assert(HasDynMemAlloc || (RecvOpsLimit > 0U)); - static_assert(HasDynMemAlloc || (SendOpsLimit > 0U)); + // static_assert(HasDynMemAlloc || (RecvOpsLimit > 0U)); + // static_assert(HasDynMemAlloc || (SendOpsLimit > 0U)); static_assert(HasDynMemAlloc || (OpsLimit > 0U)); - static_assert(HasDynMemAlloc || (PacketIdsLimit > 0U)); + // static_assert(HasDynMemAlloc || (PacketIdsLimit > 0U)); }; } // namespace cc_mqttsn_client diff --git a/client/lib/templ/Config.h.templ b/client/lib/templ/Config.h.templ index 2705c6cc..5a4783cd 100644 --- a/client/lib/templ/Config.h.templ +++ b/client/lib/templ/Config.h.templ @@ -25,12 +25,11 @@ struct Config static_assert(HasDynMemAlloc || (ClientAllocLimit > 0U), "Must use CC_MQTTSN_CLIENT_ALLOC_LIMIT in configuration to limit number of clients"); static_assert(HasDynMemAlloc || (!HasGatewayDiscovery) || (GatewayAddrLen > 0U), "Must use CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN in configuration to limit length of the gateway addr"); static_assert(HasDynMemAlloc || (!HasGatewayDiscovery) || (GatewayInfoxMaxLimit > 0U), "Must use CC_MQTTSN_CLIENT_GATEWAY_INFOS_MAX_LIMIT in configuration to limit amount of gateways to store"); -// static_assert(HasDynMemAlloc || (StringFieldFixedLen > 0U), "Must use CC_MQTTSN_CLIENT_STRING_FIELD_FIXED_LEN in configuration to limit string field length"); static_assert(HasDynMemAlloc || (MaxOutputPacketSize > 0U), "Must use CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE in configuration to limit packet size"); // static_assert(HasDynMemAlloc || (ReceiveMaxLimit > 0U) || (MaxQos < 2), "Must use CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT in configuration to limit amount of messages to receive"); - static_assert(HasDynMemAlloc || (SendMaxLimit > 0U), "Must use CC_MQTTSN_CLIENT_SEND_MAX_LIMIT in configuration to limit amount of messages to send"); - static_assert(HasDynMemAlloc || (SubscribeOpsLimit > 0U), "Must use CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT in configuration to limit amount of unfinished subscribes."); - static_assert(HasDynMemAlloc || (UnsubscribeOpsLimit > 0U), "Must use CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT in configuration to limit amount of unfinished unsubscribes."); +// static_assert(HasDynMemAlloc || (SendMaxLimit > 0U), "Must use CC_MQTTSN_CLIENT_SEND_MAX_LIMIT in configuration to limit amount of messages to send"); +// static_assert(HasDynMemAlloc || (SubscribeOpsLimit > 0U), "Must use CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT in configuration to limit amount of unfinished subscribes."); +// static_assert(HasDynMemAlloc || (UnsubscribeOpsLimit > 0U), "Must use CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT in configuration to limit amount of unfinished unsubscribes."); //static_assert(HasDynMemAlloc || (!HasSubTopicVerification) || (SubFiltersLimit > 0U), "Must use CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT in configuration to limit amount of subscribe filters"); static_assert(MaxQos <= 2, "Not supported QoS value"); diff --git a/gateway/lib/CMakeLists.txt b/gateway/lib/CMakeLists.txt index ad523471..0afe60d4 100644 --- a/gateway/lib/CMakeLists.txt +++ b/gateway/lib/CMakeLists.txt @@ -37,7 +37,7 @@ if (DOXYGEN_FOUND) string (REGEX REPLACE "${match_str}" "${replacement_str}" modified_config_text "${config_text}") file (WRITE "${output_file}" "${modified_config_text}") - add_custom_target ("doc_mqttsn_gateway" + add_custom_target ("doc_cc_mqttsn_gateway" COMMAND ${DOXYGEN_EXECUTABLE} ${output_file} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) endif () diff --git a/script/full_debug_build.sh b/script/full_debug_build.sh index 121a0df8..5e4b38bf 100755 --- a/script/full_debug_build.sh +++ b/script/full_debug_build.sh @@ -18,7 +18,9 @@ ${SCRIPT_DIR}/prepare_externals.sh cd ${BUILD_DIR} cmake .. -DCMAKE_INSTALL_PREFIX=${COMMON_INSTALL_DIR} -DCMAKE_BUILD_TYPE=Debug \ -DCC_MQTTSN_USE_CCACHE=ON \ - -DCC_MQTTSN_WITH_SANITIZERS=ON -DCC_MQTTSN_BUILD_UNIT_TESTS=ON "$@" + -DCC_MQTTSN_WITH_SANITIZERS=ON -DCC_MQTTSN_BUILD_UNIT_TESTS=ON \ + -DCC_MQTTSN_CUSTOM_CLIENT_CONFIG_FILES="${ROOT_DIR}/client/lib/script/BareMetalTestConfig.cmake" \ + "$@" procs=$(nproc) if [ -n "${procs}" ]; then From 68c441030376ea35263a6f58a3f1293015fa0b4d Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Mon, 10 Jun 2024 08:42:16 +1000 Subject: [PATCH 046/106] Finalising no gateway discovery support. --- client/lib/include/cc_mqttsn_client/common.h | 1 - client/lib/script/NoGwDiscoverConfig.cmake | 5 + client/lib/src/ClientImpl.cpp | 96 ++++++++++---------- client/lib/src/ClientImpl.h | 4 + client/lib/src/op/SearchOp.cpp | 5 + client/lib/src/op/SearchOp.h | 6 +- client/lib/templ/client.cpp.templ | 87 ++++++++++++++++-- script/full_debug_build.sh | 5 +- 8 files changed, 147 insertions(+), 62 deletions(-) create mode 100644 client/lib/script/NoGwDiscoverConfig.cmake diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index 43d373f8..785a5b5a 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -41,7 +41,6 @@ extern "C" { /// @ingroup client typedef enum { - CC_MqttsnQoS_NoGwPublish = -1, ///< QoS=-1. No gateway publish, used by publish only clients. CC_MqttsnQoS_AtMostOnceDelivery = 0, ///< QoS=0. At most once delivery. CC_MqttsnQoS_AtLeastOnceDelivery = 1, ///< QoS=1. At least once delivery. CC_MqttsnQoS_ExactlyOnceDelivery = 2 ///< QoS=2. Exactly once delivery. diff --git a/client/lib/script/NoGwDiscoverConfig.cmake b/client/lib/script/NoGwDiscoverConfig.cmake new file mode 100644 index 00000000..a686bbce --- /dev/null +++ b/client/lib/script/NoGwDiscoverConfig.cmake @@ -0,0 +1,5 @@ +# Name of the client API +set (CC_MQTTSN_CLIENT_CUSTOM_NAME "no_gw") + +# Disallow gateway discovery +set (CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY FALSE) diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index cb12783e..92142346 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -58,7 +58,6 @@ ClientImpl::ClientImpl() : m_sendGwinfoTimer(m_timerMgr.allocTimer()) { // TODO: check validity of timer in during intialization - static_cast(m_searchOpAlloc); } ClientImpl::~ClientImpl() @@ -89,66 +88,61 @@ void ClientImpl::processData(const std::uint8_t* iter, unsigned len) } } +#if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY op::SearchOp* ClientImpl::searchPrepare(CC_MqttsnErrorCode* ec) { - if constexpr (Config::HasGatewayDiscovery) { - op::SearchOp* op = nullptr; - do { - if (!m_clientState.m_initialized) { - if (m_apiEnterCount > 0U) { - errorLog("Cannot prepare search from within callback"); - updateEc(ec, CC_MqttsnErrorCode_RetryLater); - break; - } - - auto initEc = initInternal(); - if (initEc != CC_MqttsnErrorCode_Success) { - updateEc(ec, initEc); - break; - } - } - - if (!m_searchOps.empty()) { - // Already allocated - errorLog("Another search operation is in progress."); - updateEc(ec, CC_MqttsnErrorCode_Busy); - break; - } - - if (m_ops.max_size() <= m_ops.size()) { - errorLog("Cannot start search operation, retry in next event loop iteration."); + op::SearchOp* op = nullptr; + do { + if (!m_clientState.m_initialized) { + if (m_apiEnterCount > 0U) { + errorLog("Cannot prepare search from within callback"); updateEc(ec, CC_MqttsnErrorCode_RetryLater); break; } - if (m_preparationLocked) { - errorLog("Another operation is being prepared, cannot prepare \"search\" without \"send\" or \"cancel\" of the previous."); - updateEc(ec, CC_MqttsnErrorCode_PreparationLocked); + auto initEc = initInternal(); + if (initEc != CC_MqttsnErrorCode_Success) { + updateEc(ec, initEc); break; } + } + + if (!m_searchOps.empty()) { + // Already allocated + errorLog("Another search operation is in progress."); + updateEc(ec, CC_MqttsnErrorCode_Busy); + break; + } - auto ptr = m_searchOpAlloc.alloc(*this); - if (!ptr) { - errorLog("Cannot allocate new search operation."); - updateEc(ec, CC_MqttsnErrorCode_OutOfMemory); - break; - } + if (m_ops.max_size() <= m_ops.size()) { + errorLog("Cannot start search operation, retry in next event loop iteration."); + updateEc(ec, CC_MqttsnErrorCode_RetryLater); + break; + } - m_preparationLocked = true; - m_ops.push_back(ptr.get()); - m_searchOps.push_back(std::move(ptr)); - op = m_searchOps.back().get(); - updateEc(ec, CC_MqttsnErrorCode_Success); - } while (false); + if (m_preparationLocked) { + errorLog("Another operation is being prepared, cannot prepare \"search\" without \"send\" or \"cancel\" of the previous."); + updateEc(ec, CC_MqttsnErrorCode_PreparationLocked); + break; + } - return op; - } - else { - errorLog("Gateway discovery support for excluded from compilation"); - updateEc(ec, CC_MqttsnErrorCode_NotSupported); - return nullptr; - } + auto ptr = m_searchOpAlloc.alloc(*this); + if (!ptr) { + errorLog("Cannot allocate new search operation."); + updateEc(ec, CC_MqttsnErrorCode_OutOfMemory); + break; + } + + m_preparationLocked = true; + m_ops.push_back(ptr.get()); + m_searchOps.push_back(std::move(ptr)); + op = m_searchOps.back().get(); + updateEc(ec, CC_MqttsnErrorCode_Success); + } while (false); + + return op; } +#endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY op::ConnectOp* ClientImpl::connectPrepare(CC_MqttsnErrorCode* ec) { @@ -1267,9 +1261,11 @@ CC_MqttsnErrorCode ClientImpl::initInternal() // return true; // } -void ClientImpl::opComplete_Search(const op::Op* op) +void ClientImpl::opComplete_Search([[maybe_unused]] const op::Op* op) { +#if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY eraseFromList(op, m_searchOps); +#endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY } void ClientImpl::opComplete_Connect(const op::Op* op) diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index b8fd2f68..b1333e73 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -228,8 +228,10 @@ class ClientImpl final : public ProtMsgHandler // } private: +#if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY using SearchOpAlloc = ObjAllocator; using SearchOpsList = ObjListType; +#endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY using ConnectOpAlloc = ObjAllocator; using ConnectOpsList = ObjListType; @@ -332,8 +334,10 @@ class ClientImpl final : public ProtMsgHandler ProtFrame m_frame; +#if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY SearchOpAlloc m_searchOpAlloc; SearchOpsList m_searchOps; +#endif ConnectOpAlloc m_connectOpAlloc; ConnectOpsList m_connectOps; diff --git a/client/lib/src/op/SearchOp.cpp b/client/lib/src/op/SearchOp.cpp index 50178d18..854cfe7f 100644 --- a/client/lib/src/op/SearchOp.cpp +++ b/client/lib/src/op/SearchOp.cpp @@ -5,7 +5,10 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. + #include "op/SearchOp.h" + +#if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY #include "ClientImpl.h" #include "comms/util/assign.h" @@ -166,3 +169,5 @@ void SearchOp::opTimeoutCb(void* data) } // namespace op } // namespace cc_mqttsn_client + +#endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY \ No newline at end of file diff --git a/client/lib/src/op/SearchOp.h b/client/lib/src/op/SearchOp.h index 40129ae7..103ed32c 100644 --- a/client/lib/src/op/SearchOp.h +++ b/client/lib/src/op/SearchOp.h @@ -19,6 +19,9 @@ namespace cc_mqttsn_client namespace op { +class SearchOp; + +#if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY class SearchOp final : public Op { using Base = Op; @@ -62,8 +65,9 @@ class SearchOp final : public Op static_assert(ExtConfig::SearchOpTimers == 1U); }; +#endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY } // namespace op - } // namespace cc_mqttsn_client + diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index b1f19d85..171220bd 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -37,10 +37,12 @@ inline CC_MqttsnClientHandle handleFromClient(cc_mqttsn_client::ClientImpl* clie return reinterpret_cast(client); } +#if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY inline cc_mqttsn_client::op::SearchOp* searchOpFromHandle(CC_MqttsnSearchHandle handle) { return reinterpret_cast(handle); } +#endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY inline CC_MqttsnSearchHandle handleFromSearchOp(cc_mqttsn_client::op::SearchOp* op) { @@ -273,14 +275,28 @@ unsigned cc_mqttsn_##NAME##client_get_allowed_adv_losses(CC_MqttsnClientHandle c return clientFromHandle(client)->configState().m_allowedAdvLosses; } -CC_MqttsnSearchHandle cc_mqttsn_##NAME##client_search_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec) +CC_MqttsnSearchHandle cc_mqttsn_##NAME##client_search_prepare( + [[maybe_unused]] CC_MqttsnClientHandle client, + [[maybe_unused]] CC_MqttsnErrorCode* ec) { +#if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY COMMS_ASSERT(client != nullptr); return handleFromSearchOp(clientFromHandle(client)->searchPrepare(ec)); +#else // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY + clientFromHandle(client)->errorLog("Gateway discovery support for excluded from compilation"); + if (ec != nullptr) { + *ec = CC_MqttsnErrorCode_NotSupported; + } + return nullptr; +#endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY } -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_set_retry_period(CC_MqttsnSearchHandle handle, unsigned ms) +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_set_retry_period( + [[maybe_unused]] CC_MqttsnSearchHandle handle, + [[maybe_unused]] unsigned ms) { +#if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY + COMMS_ASSERT(handle != nullptr); if (ms == 0U) { searchOpFromHandle(handle)->client().errorLog("The retry period must be greater than 0"); @@ -290,16 +306,32 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_set_retry_period(CC_MqttsnSea COMMS_ASSERT(handle != nullptr); searchOpFromHandle(handle)->setRetryPeriod(ms); return CC_MqttsnErrorCode_Success; + +#else // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY + + return CC_MqttsnErrorCode_NotSupported; + +#endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY } -unsigned cc_mqttsn_##NAME##client_search_get_retry_period(CC_MqttsnSearchHandle handle) +unsigned cc_mqttsn_##NAME##client_search_get_retry_period([[maybe_unused]] CC_MqttsnSearchHandle handle) { + +#if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY COMMS_ASSERT(handle != nullptr); return searchOpFromHandle(handle)->getRetryPeriod(); +#else // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY + COMMS_ASSERT(false); // Should not be called + return 0U; +#endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY } -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_set_retry_count(CC_MqttsnSearchHandle handle, unsigned count) +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_set_retry_count( + [[maybe_unused]] CC_MqttsnSearchHandle handle, + [[maybe_unused]] unsigned count) { +#if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY + COMMS_ASSERT(handle != nullptr); if (count == 0U) { searchOpFromHandle(handle)->client().errorLog("The retry count must be greater than 0"); @@ -308,16 +340,31 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_set_retry_count(CC_MqttsnSear searchOpFromHandle(handle)->setRetryCount(count); return CC_MqttsnErrorCode_Success; + +#else // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY + + return CC_MqttsnErrorCode_NotSupported; + +#endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY } -unsigned cc_mqttsn_##NAME##client_search_get_retry_count(CC_MqttsnSearchHandle handle) +unsigned cc_mqttsn_##NAME##client_search_get_retry_count([[maybe_unused]] CC_MqttsnSearchHandle handle) { +#if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY COMMS_ASSERT(handle != nullptr); return searchOpFromHandle(handle)->getRetryCount(); +#else // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY + COMMS_ASSERT(false); // Should not be called + return 0U; +#endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY } -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_set_broadcast_radius(CC_MqttsnSearchHandle handle, unsigned broadcastRadius) +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_set_broadcast_radius( + [[maybe_unused]] CC_MqttsnSearchHandle handle, + [[maybe_unused]] unsigned broadcastRadius) { +#if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY + COMMS_ASSERT(handle != nullptr); if (broadcastRadius == 0U) { searchOpFromHandle(handle)->client().errorLog("The broadcast radius must be greater than 0"); @@ -326,18 +373,36 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_set_broadcast_radius(CC_Mqtts searchOpFromHandle(handle)->setBroadcastRadius(broadcastRadius); return CC_MqttsnErrorCode_Success; + +#else // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY + + return CC_MqttsnErrorCode_NotSupported; + +#endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY } -unsigned cc_mqttsn_##NAME##client_search_get_broadcastRadius(CC_MqttsnSearchHandle handle) +unsigned cc_mqttsn_##NAME##client_search_get_broadcastRadius([[maybe_unused]] CC_MqttsnSearchHandle handle) { +#if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY COMMS_ASSERT(handle != nullptr); return searchOpFromHandle(handle)->getBroadcastRadius(); +#else // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY + COMMS_ASSERT(false); // Should not be called + return 0U; +#endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY } -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_send(CC_MqttsnSearchHandle handle, CC_MqttsnSearchCompleteCb cb, void* cbData) +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_send( + [[maybe_unused]] CC_MqttsnSearchHandle handle, + [[maybe_unused]] CC_MqttsnSearchCompleteCb cb, + [[maybe_unused]] void* cbData) { +#if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY COMMS_ASSERT(handle != nullptr); return searchOpFromHandle(handle)->send(cb, cbData); +#else // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY + return CC_MqttsnErrorCode_NotSupported; +#endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY } CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search( @@ -354,10 +419,14 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search( return cc_mqttsn_##NAME##client_search_send(search, cb, cbData); } -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_cancel(CC_MqttsnSearchHandle handle) +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_cancel([[maybe_unused]] CC_MqttsnSearchHandle handle) { +#if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY COMMS_ASSERT(handle != nullptr); return searchOpFromHandle(handle)->cancel(); +#else // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY + return CC_MqttsnErrorCode_NotSupported; +#endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY } CC_MqttsnConnectHandle cc_mqttsn_##NAME##client_connect_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec) diff --git a/script/full_debug_build.sh b/script/full_debug_build.sh index 5e4b38bf..93fa3baa 100755 --- a/script/full_debug_build.sh +++ b/script/full_debug_build.sh @@ -15,11 +15,14 @@ mkdir -p ${BUILD_DIR} ${SCRIPT_DIR}/prepare_externals.sh +CONFIGS_DIR="${ROOT_DIR}/client/lib/script" +CONFIGS="${CONFIGS_DIR}/BareMetalTestConfig.cmake;${CONFIGS_DIR}/NoGwDiscoverConfig.cmake" + cd ${BUILD_DIR} cmake .. -DCMAKE_INSTALL_PREFIX=${COMMON_INSTALL_DIR} -DCMAKE_BUILD_TYPE=Debug \ -DCC_MQTTSN_USE_CCACHE=ON \ -DCC_MQTTSN_WITH_SANITIZERS=ON -DCC_MQTTSN_BUILD_UNIT_TESTS=ON \ - -DCC_MQTTSN_CUSTOM_CLIENT_CONFIG_FILES="${ROOT_DIR}/client/lib/script/BareMetalTestConfig.cmake" \ + -DCC_MQTTSN_CUSTOM_CLIENT_CONFIG_FILES="${CONFIGS}" \ "$@" procs=$(nproc) From 5ce348681f76373e415644a05680e2370c02cab6 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 11 Jun 2024 09:27:38 +1000 Subject: [PATCH 047/106] New client testing infrastructure. --- client.old/templ/client.cpp.templ | 2 +- client.old/templ/client.h.templ | 2 +- client.old/test/CommonTestClient.cpp | 2 +- client.old/test/CommonTestClient.h | 2 +- .../bare_metal_app/test_client_build/main.c | 2 +- client/lib/CMakeLists.txt | 2 +- client/lib/doxygen/main.dox | 6 +- client/lib/templ/client.cpp.templ | 26 +- client/lib/templ/client.h.templ | 6 +- client/lib/test/CMakeLists.txt | 105 ++++++++ client/lib/test/UnitTestCommonBase.cpp | 250 ++++++++++++++++++ client/lib/test/UnitTestCommonBase.h | 143 ++++++++++ client/lib/test/UnitTestDefaultBase.cpp | 48 ++++ client/lib/test/UnitTestDefaultBase.h | 16 ++ client/lib/test/UnitTestGwDiscover.th | 71 +++++ client/lib/test/UnitTestProtocolDefs.h | 39 +++ 16 files changed, 700 insertions(+), 22 deletions(-) create mode 100644 client/lib/test/CMakeLists.txt create mode 100644 client/lib/test/UnitTestCommonBase.cpp create mode 100644 client/lib/test/UnitTestCommonBase.h create mode 100644 client/lib/test/UnitTestDefaultBase.cpp create mode 100644 client/lib/test/UnitTestDefaultBase.h create mode 100644 client/lib/test/UnitTestGwDiscover.th create mode 100644 client/lib/test/UnitTestProtocolDefs.h diff --git a/client.old/templ/client.cpp.templ b/client.old/templ/client.cpp.templ index fdca8697..9d88a751 100644 --- a/client.old/templ/client.cpp.templ +++ b/client.old/templ/client.cpp.templ @@ -137,7 +137,7 @@ void cc_mqttsn_##NAME##client_set_retry_count(CC_MqttsnClientHandle client, unsi clientObj->setRetryCount(value); } -void cc_mqttsn_##NAME##client_set_broadcast_radius(CC_MqttsnClientHandle client, unsigned char value) +void cc_mqttsn_##NAME##client_set_default_broadcast_radius(CC_MqttsnClientHandle client, unsigned char value) { auto* clientObj = reinterpret_cast(client.m_ptr); clientObj->setBroadcastRadius(value); diff --git a/client.old/templ/client.h.templ b/client.old/templ/client.h.templ index 99cf9906..a8ac00ba 100644 --- a/client.old/templ/client.h.templ +++ b/client.old/templ/client.h.templ @@ -172,7 +172,7 @@ void cc_mqttsn_##NAME##client_set_retry_count(CC_MqttsnClientHandle client, unsi /// set using this function. Default radius value is 0. /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. /// @param[in] value Broadcast radius. -void cc_mqttsn_##NAME##client_set_broadcast_radius(CC_MqttsnClientHandle client, unsigned char value); +void cc_mqttsn_##NAME##client_set_default_broadcast_radius(CC_MqttsnClientHandle client, unsigned char value); /// @brief Enable/Disable search for gateways. /// @details According to @b MQTT-SN protocol specification the client may diff --git a/client.old/test/CommonTestClient.cpp b/client.old/test/CommonTestClient.cpp index 419863cd..c971165e 100644 --- a/client.old/test/CommonTestClient.cpp +++ b/client.old/test/CommonTestClient.cpp @@ -29,7 +29,7 @@ ClientLibFuncs createDefaultLibFuncs() funcs.m_tickFunc = &cc_mqttsn_client_tick; funcs.m_setRetryPeriodFunc = &cc_mqttsn_client_set_retry_period; funcs.m_setRetryCountFunc = &cc_mqttsn_client_set_retry_count; - funcs.m_setBroadcastRadius = &cc_mqttsn_client_set_broadcast_radius; + funcs.m_setBroadcastRadius = &cc_mqttsn_client_set_default_broadcast_radius; funcs.m_setSearchgwEnabledFunc = &cc_mqttsn_client_set_searchgw_enabled; funcs.m_cancelFunc = &cc_mqttsn_client_cancel; funcs.m_connectFunc = &cc_mqttsn_client_connect; diff --git a/client.old/test/CommonTestClient.h b/client.old/test/CommonTestClient.h index 8dc01f8e..a900a794 100644 --- a/client.old/test/CommonTestClient.h +++ b/client.old/test/CommonTestClient.h @@ -31,7 +31,7 @@ typedef decltype(&cc_mqttsn_client_process_data) ProcessDataFunc; typedef decltype(&cc_mqttsn_client_tick) TickFunc; typedef decltype(&cc_mqttsn_client_set_retry_period) SetRetryPeriodFunc; typedef decltype(&cc_mqttsn_client_set_retry_count) SetRetryCountFunc; -typedef decltype(&cc_mqttsn_client_set_broadcast_radius) SetBroadcastRadiusFunc; +typedef decltype(&cc_mqttsn_client_set_default_broadcast_radius) SetBroadcastRadiusFunc; typedef decltype(&cc_mqttsn_client_set_searchgw_enabled) SetSearchgwEnabledFunc; typedef decltype(&cc_mqttsn_client_cancel) CancelFunc; typedef decltype(&cc_mqttsn_client_connect) ConnectFunc; diff --git a/client.old/test/bare_metal_app/test_client_build/main.c b/client.old/test/bare_metal_app/test_client_build/main.c index 0d69cc05..e72fcee7 100644 --- a/client.old/test/bare_metal_app/test_client_build/main.c +++ b/client.old/test/bare_metal_app/test_client_build/main.c @@ -152,7 +152,7 @@ int main(int argc, const char** argv) cc_mqttsn_test_bare_metal_client_set_retry_period(client, 10); cc_mqttsn_test_bare_metal_client_set_retry_count(client, 3); - cc_mqttsn_test_bare_metal_client_set_broadcast_radius(client, 0); + cc_mqttsn_test_bare_metal_client_set_default_broadcast_radius(client, 0); cc_mqttsn_test_bare_metal_client_set_searchgw_enabled(client, true); cc_mqttsn_test_bare_metal_client_search_gw(client); cc_mqttsn_test_bare_metal_client_discard_gw(client, 0); diff --git a/client/lib/CMakeLists.txt b/client/lib/CMakeLists.txt index 98690ce4..d312e50d 100644 --- a/client/lib/CMakeLists.txt +++ b/client/lib/CMakeLists.txt @@ -292,4 +292,4 @@ if (NOT "${CC_MQTTSN_CUSTOM_CLIENT_CONFIG_FILES}" STREQUAL "") endforeach () endif () -#add_subdirectory(test) +add_subdirectory(test) diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index ec748871..ecadbac3 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -273,10 +273,10 @@ /// requesting to broadcast the message, the client library also specifies the /// broadcast radius. The default broadcast radius value is @b 3. /// To change the default configuration value -/// use @b cc_mqttsn_client_set_broadcast_radius() function. To retrieve -/// the current configuration use @b cc_mqttsn_client_get_broadcast_radius(). +/// use @b cc_mqttsn_client_set_default_broadcast_radius() function. To retrieve +/// the current configuration use @b cc_mqttsn_client_get_default_broadcast_radius(). /// @code -/// CC_MqttsnErrorCode ec = cc_mqttsn_client_set_broadcast_radius(client, 5); +/// CC_MqttsnErrorCode ec = cc_mqttsn_client_set_default_broadcast_radius(client, 5); /// if (ec != CC_MqttsnErrorCode_Success) { /// ... /* Something went wrong */ /// } diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index 171220bd..da005c42 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -122,7 +122,7 @@ unsigned cc_mqttsn_##NAME##client_get_default_retry_count(CC_MqttsnClientHandle return clientFromHandle(client)->configState().m_retryCount; } -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_broadcast_radius(CC_MqttsnClientHandle client, unsigned value) +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_default_broadcast_radius(CC_MqttsnClientHandle client, unsigned value) { COMMS_ASSERT(client != nullptr); if (cc_mqttsn_client::ConfigState::MaxBroadcastRadius < value) { @@ -134,7 +134,7 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_broadcast_radius(CC_MqttsnClient return CC_MqttsnErrorCode_Success; } -unsigned cc_mqttsn_##NAME##client_get_broadcast_radius(CC_MqttsnClientHandle client) +unsigned cc_mqttsn_##NAME##client_get_default_broadcast_radius(CC_MqttsnClientHandle client) { COMMS_ASSERT(client != nullptr); return clientFromHandle(client)->configState().m_broadcastRadius; @@ -146,21 +146,27 @@ unsigned cc_mqttsn_##NAME##client_get_available_gateways_count(CC_MqttsnClientHa return static_cast(clientFromHandle(client)->clientState().m_gwInfos.size()); } -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_get_available_gateway_info(CC_MqttsnClientHandle client, unsigned idx, CC_MqttsnGatewayInfo& info) +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_get_available_gateway_info(CC_MqttsnClientHandle client, unsigned idx, CC_MqttsnGatewayInfo* info) { COMMS_ASSERT(client != nullptr); - auto& gwInfos = clientFromHandle(client)->clientState().m_gwInfos; + auto clientPtr = clientFromHandle(client); + if (info == nullptr) { + clientPtr->errorLog("The provided gateway info pointer is invalid"); + return CC_MqttsnErrorCode_BadParam; + } + + auto& gwInfos = clientPtr->clientState().m_gwInfos; if (gwInfos.size() <= idx) { - clientFromHandle(client)->errorLog("The gateway info index is too high"); + clientPtr->errorLog("The gateway info index is too high"); return CC_MqttsnErrorCode_BadParam; } auto& storedInfo = gwInfos[idx]; - info = CC_MqttsnGatewayInfo(); - info.m_gwId = storedInfo.m_gwId; + *info = CC_MqttsnGatewayInfo(); + info->m_gwId = storedInfo.m_gwId; if (!storedInfo.m_addr.empty()) { - info.m_addr = storedInfo.m_addr.data(); - info.m_addrLen = static_cast(storedInfo.m_addr.size()); + info->m_addr = storedInfo.m_addr.data(); + info->m_addrLen = static_castm_addrLen)>(storedInfo.m_addr.size()); } return CC_MqttsnErrorCode_Success; @@ -381,7 +387,7 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_set_broadcast_radius( #endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY } -unsigned cc_mqttsn_##NAME##client_search_get_broadcastRadius([[maybe_unused]] CC_MqttsnSearchHandle handle) +unsigned cc_mqttsn_##NAME##client_search_get_broadcast_radius([[maybe_unused]] CC_MqttsnSearchHandle handle) { #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY COMMS_ASSERT(handle != nullptr); diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index ab6cdaa0..eabb038e 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -113,13 +113,13 @@ unsigned cc_mqttsn_##NAME##client_get_default_retry_count(CC_MqttsnClientHandle /// @return Result code of the call. /// @pre The broadcast value cannot exceed 255. /// @ingroup client -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_broadcast_radius(CC_MqttsnClientHandle client, unsigned value); +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_default_broadcast_radius(CC_MqttsnClientHandle client, unsigned value); /// @brief Get current broadcast radius configuration. /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. -/// @see @ref cc_mqttsn_##NAME##client_set_broadcast_radius() +/// @see @ref cc_mqttsn_##NAME##client_set_default_broadcast_radius() /// @ingroup client -unsigned cc_mqttsn_##NAME##client_get_broadcast_radius(CC_MqttsnClientHandle client); +unsigned cc_mqttsn_##NAME##client_get_default_broadcast_radius(CC_MqttsnClientHandle client); /// @brief Get number of available gateways. /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. diff --git a/client/lib/test/CMakeLists.txt b/client/lib/test/CMakeLists.txt new file mode 100644 index 00000000..ed4957dd --- /dev/null +++ b/client/lib/test/CMakeLists.txt @@ -0,0 +1,105 @@ +if (NOT CC_MQTTSN_BUILD_UNIT_TESTS) + return () +endif () + +################################## +set (COMMON_BASE_LIB_NAME "UnitTestCommonBase") +set (COMMON_BASE_SRC + "UnitTestCommonBase.cpp") + +add_library(${COMMON_BASE_LIB_NAME} STATIC ${COMMON_BASE_SRC}) +target_link_libraries(${COMMON_BASE_LIB_NAME} PUBLIC cc::cc_mqttsn) +target_include_directories( + ${COMMON_BASE_LIB_NAME} + INTERFACE + $ + PUBLIC + $ +) + +################################## + +function (cc_mqttsn_client_add_unit_test name test_lib) + set (src ${CMAKE_CURRENT_SOURCE_DIR}/${name}.th) + cc_cxxtest_add_test (NAME unit.${name} SRC ${src}) + target_link_libraries(unit.${name} PRIVATE ${test_lib} cxxtest::cxxtest) +endfunction () + +################################## + +if (TARGET cc::cc_mqttsn_client) + set (DEFAULT_BASE_LIB_NAME "UnitTestDefaultBase") + set (DEFAULT_BASE_SRC + "UnitTestDefaultBase.cpp") + + add_library(${DEFAULT_BASE_LIB_NAME} STATIC ${DEFAULT_BASE_SRC}) + target_link_libraries(${DEFAULT_BASE_LIB_NAME} PUBLIC ${COMMON_BASE_LIB_NAME} cc::cc_mqttsn_client) + target_include_directories( + ${DEFAULT_BASE_LIB_NAME} INTERFACE + $ + ) + + cc_mqttsn_client_add_unit_test(UnitTestGwDiscover ${DEFAULT_BASE_LIB_NAME}) + +# cc_mqttsn_client_add_unit_test(UnitTestConnect ${DEFAULT_BASE_LIB_NAME}) +# cc_mqttsn_client_add_unit_test(UnitTestDisconnect ${DEFAULT_BASE_LIB_NAME}) +# cc_mqttsn_client_add_unit_test(UnitTestPublish ${DEFAULT_BASE_LIB_NAME}) +# cc_mqttsn_client_add_unit_test(UnitTestReceive ${DEFAULT_BASE_LIB_NAME}) +# cc_mqttsn_client_add_unit_test(UnitTestSubscribe ${DEFAULT_BASE_LIB_NAME}) +# cc_mqttsn_client_add_unit_test(UnitTestUnsubscribe ${DEFAULT_BASE_LIB_NAME}) +endif () + +# TODO +return () + +if (TARGET cc::cc_mqttsn_bm_client) + set (BM_BASE_LIB_NAME "UnitTestBmBase") + set (BM_BASE_SRC + "UnitTestBmBase.cpp") + + add_library(${BM_BASE_LIB_NAME} STATIC ${BM_BASE_SRC}) + target_link_libraries(${BM_BASE_LIB_NAME} PUBLIC ${COMMON_BASE_LIB_NAME} cc::cc_mqttsn_bm_client) + target_include_directories( + ${BM_BASE_LIB_NAME} INTERFACE + $ + ) + + cc_mqttsn_client_add_unit_test(UnitTestBmClient ${BM_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(UnitTestBmConnect ${BM_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(UnitTestBmPublish ${BM_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(UnitTestBmReceive ${BM_BASE_LIB_NAME}) +endif () + +if (TARGET cc::cc_mqttsn_qos1_client) + set (QOS1_BASE_LIB_NAME "UnitTestQos1Base") + set (QOS1_BASE_SRC + "UnitTestQos1Base.cpp") + + add_library(${QOS1_BASE_LIB_NAME} STATIC ${QOS1_BASE_SRC}) + target_link_libraries(${QOS1_BASE_LIB_NAME} PUBLIC ${COMMON_BASE_LIB_NAME} cc::cc_mqttsn_qos1_client) + target_include_directories( + ${QOS1_BASE_LIB_NAME} INTERFACE + $ + ) + + cc_mqttsn_client_add_unit_test(UnitTestQos1Publish ${QOS1_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(UnitTestQos1Receive ${QOS1_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(UnitTestQos1Subscribe ${QOS1_BASE_LIB_NAME}) +endif () + +if (TARGET cc::cc_mqttsn_qos0_client) + set (QOS0_BASE_LIB_NAME "UnitTestQos0Base") + set (QOS0_BASE_SRC + "UnitTestQos0Base.cpp") + + add_library(${QOS0_BASE_LIB_NAME} STATIC ${QOS0_BASE_SRC}) + target_link_libraries(${QOS0_BASE_LIB_NAME} PUBLIC ${COMMON_BASE_LIB_NAME} cc::cc_mqttsn_qos0_client) + target_include_directories( + ${QOS0_BASE_LIB_NAME} INTERFACE + $ + ) + + cc_mqttsn_client_add_unit_test(UnitTestQos0Publish ${QOS0_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(UnitTestQos0Receive ${QOS0_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(UnitTestQos0Subscribe ${QOS0_BASE_LIB_NAME}) +endif () diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp new file mode 100644 index 00000000..05f2a7ed --- /dev/null +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -0,0 +1,250 @@ +#include "UnitTestCommonBase.h" + +#include "comms/iterator.h" + +#include +#include + +namespace +{ + +#define test_assert(cond_) \ + assert(cond_); \ + if (!(cond_)) { \ + std::cerr << "\nAssertion failure (" << #cond_ << ") in " << __FILE__ << ":" << __LINE__ << std::endl; \ + std::exit(1); \ + } + +UnitTestCommonBase* asThis(void* data) +{ + return reinterpret_cast(data); +} + +} // namespace + + +UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : + m_funcs(funcs) +{ + test_assert(m_funcs.m_alloc != nullptr); + test_assert(m_funcs.m_free != nullptr); + test_assert(m_funcs.m_tick != nullptr); + test_assert(m_funcs.m_process_data != nullptr); + test_assert(m_funcs.m_set_default_retry_period != nullptr); + test_assert(m_funcs.m_get_default_retry_period != nullptr); + test_assert(m_funcs.m_set_default_retry_count != nullptr); + test_assert(m_funcs.m_get_default_retry_count != nullptr); + test_assert(m_funcs.m_set_default_broadcast_radius != nullptr); + test_assert(m_funcs.m_get_available_gateway_info != nullptr); + test_assert(m_funcs.m_set_available_gateway_info != nullptr); + test_assert(m_funcs.m_discard_available_gateway_info != nullptr); + test_assert(m_funcs.m_discard_all_gateway_infos != nullptr); + test_assert(m_funcs.m_set_default_gw_adv_duration != nullptr); + test_assert(m_funcs.m_get_default_gw_adv_duration != nullptr); + test_assert(m_funcs.m_set_allowed_adv_losses != nullptr); + test_assert(m_funcs.m_get_allowed_adv_losses != nullptr); + test_assert(m_funcs.m_search_prepare != nullptr); + test_assert(m_funcs.m_search_set_retry_period != nullptr); + test_assert(m_funcs.m_search_get_retry_period != nullptr); + test_assert(m_funcs.m_search_set_retry_count != nullptr); + test_assert(m_funcs.m_search_get_retry_count != nullptr); + test_assert(m_funcs.m_search_set_broadcast_radius != nullptr); + test_assert(m_funcs.m_search_get_broadcast_radius != nullptr); + test_assert(m_funcs.m_search_send != nullptr); + test_assert(m_funcs.m_search_cancel != nullptr); + test_assert(m_funcs.m_search != nullptr); + + test_assert(m_funcs.m_set_next_tick_program_callback != nullptr); + test_assert(m_funcs.m_set_cancel_next_tick_wait_callback != nullptr); + test_assert(m_funcs.m_set_send_output_data_callback != nullptr); + test_assert(m_funcs.m_set_gw_status_report_callback != nullptr); + test_assert(m_funcs.m_set_gw_disconnect_report_callback != nullptr); + test_assert(m_funcs.m_set_message_report_callback != nullptr); + test_assert(m_funcs.m_set_error_log_callback != nullptr); + test_assert(m_funcs.m_set_gwinfo_delay_request_callback != nullptr); +} + +UnitTestCommonBase::UnitTestGwInfo& UnitTestCommonBase::UnitTestGwInfo::operator=(const CC_MqttsnGatewayInfo& info) +{ + m_gwId = info.m_gwId; + if (info.m_addrLen > 0U) { + test_assert(info.m_addr != nullptr); + m_addr.assign(info.m_addr, info.m_addr + info.m_addrLen); + } + + return *this; +} + +UnitTestCommonBase::UnitTestGwInfoReport::UnitTestGwInfoReport(CC_MqttsnGwStatus status, const CC_MqttsnGatewayInfo* info) : + m_status(status) +{ + if (info != nullptr) { + m_info = *info; + } +} + +void UnitTestCommonBase::unitTestSetUp() +{ +} + +void UnitTestCommonBase::unitTestTearDown() +{ +} + +UnitTestCommonBase::UnitTestClientPtr UnitTestCommonBase::unitTestAllocClient(bool enableLog) +{ + UnitTestClientPtr client(m_funcs.m_alloc(), UnitTestDeleter(m_funcs)); + m_funcs.m_set_next_tick_program_callback(client.get(), &UnitTestCommonBase::unitTestTickProgramCb, this); + m_funcs.m_set_cancel_next_tick_wait_callback(client.get(), &UnitTestCommonBase::unitTestCancelTickWaitCb, this); + m_funcs.m_set_send_output_data_callback(client.get(), &UnitTestCommonBase::unitTestSendOutputDataCb, this); + m_funcs.m_set_gw_status_report_callback(client.get(), &UnitTestCommonBase::unitTestGwStatusReportCb, this); + m_funcs.m_set_gw_disconnect_report_callback(client.get(), &UnitTestCommonBase::unitTestGwDisconnectReportCb, this); + m_funcs.m_set_message_report_callback(client.get(), &UnitTestCommonBase::unitTestMessageReportCb, this); + m_funcs.m_set_gwinfo_delay_request_callback(client.get(), &UnitTestCommonBase::unitTestGwinfoDelayRequestCb, this); + + if (enableLog) { + m_funcs.m_set_error_log_callback(client.get(), &UnitTestCommonBase::unitTestErrorLogCb, this); + } + + return client; +} + +void UnitTestCommonBase::unitTestClientInputData(CC_MqttsnClient* client, const UnitTestData& data) +{ + apiProcessData(client, data.data(), static_cast(data.size())); +} + +void UnitTestCommonBase::unitTestClientInputMessage(CC_MqttsnClient* client, const UnitTestMessage& msg) +{ + UnitTestData data; + UnitTestsFrame frame; + data.resize(frame.length(msg)); + auto writeIter = comms::writeIteratorFor(data.data()); + auto ec = frame.write(msg, writeIter, data.size()); + test_assert(ec == comms::ErrorStatus::Success); + unitTestClientInputData(client, data); +} + +bool UnitTestCommonBase::unitTestHasTickReq() const +{ + return !m_ticks.empty(); +} + +const UnitTestCommonBase::UnitTestTickInfo* UnitTestCommonBase::unitTestTickInfo(bool mustExist) const +{ + if (!unitTestHasTickReq()) { + test_assert(!mustExist); + return nullptr; + } + + return &m_ticks.front(); +} + +void UnitTestCommonBase::unitTestTick(CC_MqttsnClient* client, unsigned ms) +{ + test_assert(!m_ticks.empty()); + auto& info = m_ticks.front(); + if (ms == 0U) { + ms = info.m_req; + } + + if (ms < info.m_req) { + info.m_elapsed = ms; + return; + } + + auto msToReport = info.m_req; + m_ticks.pop_front(); + m_funcs.m_tick(client, msToReport); +} + +bool UnitTestCommonBase::unitTestHasGwInfoReport() const +{ + return !m_gwInfoReports.empty(); +} + +const UnitTestCommonBase::UnitTestGwInfoReport* UnitTestCommonBase::unitTestGetGwInfoReport(bool mustExist) const +{ + if (!unitTestHasGwInfoReport()) { + test_assert(!mustExist); + return nullptr; + } + + return &m_gwInfoReports.front(); +} + +void UnitTestCommonBase::unitTestPopGwInfoReport() +{ + test_assert(!m_gwInfoReports.empty()); + m_gwInfoReports.pop_front(); +} + +void UnitTestCommonBase::apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen) +{ + m_funcs.m_process_data(client, buf, bufLen); +} + +void UnitTestCommonBase::unitTestTickProgramCb(void* data, unsigned duration) +{ + auto* thisPtr = asThis(data); + if (thisPtr->m_ticks.empty()) { + asThis(data)->m_ticks.emplace_back(duration); + return; + } + + auto& info = thisPtr->m_ticks.front(); + test_assert(info.m_req == 0U); + info.m_req = duration; +} + +unsigned UnitTestCommonBase::unitTestCancelTickWaitCb(void* data) +{ + auto* thisPtr = asThis(data); + test_assert(!thisPtr->m_ticks.empty()); + auto result = thisPtr->m_ticks.front().m_elapsed; + thisPtr->m_ticks.pop_front(); + return result; +} + +void UnitTestCommonBase::unitTestSendOutputDataCb(void* data, const unsigned char* buf, unsigned bufLen, unsigned broadcastRadius) +{ + // TODO: + static_cast(data); + static_cast(buf); + static_cast(bufLen); + static_cast(broadcastRadius); + test_assert(false); +} + +void UnitTestCommonBase::unitTestGwStatusReportCb(void* data, CC_MqttsnGwStatus status, const CC_MqttsnGatewayInfo* info) +{ + asThis(data)->m_gwInfoReports.emplace_back(status, info); +} + +void UnitTestCommonBase::unitTestGwDisconnectReportCb(void* data, CC_MqttsnGatewayDisconnectReason reason) +{ + // TODO: + static_cast(data); + static_cast(reason); + test_assert(false); +} + +void UnitTestCommonBase::unitTestMessageReportCb(void* data, const CC_MqttsnMessageInfo* msgInfo) +{ + // TODO: + static_cast(data); + static_cast(msgInfo); + test_assert(false); +} + +unsigned UnitTestCommonBase::unitTestGwinfoDelayRequestCb(void* data) +{ + // TODO: + static_cast(data); + test_assert(false); +} + +void UnitTestCommonBase::unitTestErrorLogCb([[maybe_unused]] void* data, const char* msg) +{ + std::cout << "ERROR: " << msg << std::endl; +} \ No newline at end of file diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h new file mode 100644 index 00000000..0e2eed4f --- /dev/null +++ b/client/lib/test/UnitTestCommonBase.h @@ -0,0 +1,143 @@ +#pragma once + +#include "cc_mqttsn_client/common.h" + +#include "UnitTestProtocolDefs.h" + +#include + +class UnitTestCommonBase +{ +public: + using UnitTestData = std::vector; + + struct LibFuncs + { + CC_MqttsnClientHandle (*m_alloc)() = nullptr; + void (*m_free)(CC_MqttsnClientHandle) = nullptr; + void (*m_tick)(CC_MqttsnClientHandle, unsigned) = nullptr; + void (*m_process_data)(CC_MqttsnClientHandle, const unsigned char*, unsigned) = nullptr; + CC_MqttsnErrorCode (*m_set_default_retry_period)(CC_MqttsnClientHandle, unsigned) = nullptr; + unsigned (*m_get_default_retry_period)(CC_MqttsnClientHandle) = nullptr; + CC_MqttsnErrorCode (*m_set_default_retry_count)(CC_MqttsnClientHandle, unsigned) = nullptr; + unsigned (*m_get_default_retry_count)(CC_MqttsnClientHandle) = nullptr; + CC_MqttsnErrorCode (*m_set_default_broadcast_radius)(CC_MqttsnClientHandle, unsigned) = nullptr; + unsigned (*m_get_default_broadcast_radius)(CC_MqttsnClientHandle) = nullptr; + unsigned (*m_get_available_gateways_count)(CC_MqttsnClientHandle) = nullptr; + CC_MqttsnErrorCode (*m_get_available_gateway_info)(CC_MqttsnClientHandle, unsigned, CC_MqttsnGatewayInfo*) = nullptr; + CC_MqttsnErrorCode (*m_set_available_gateway_info)(CC_MqttsnClientHandle, const CC_MqttsnGatewayInfo*) = nullptr; + CC_MqttsnErrorCode (*m_discard_available_gateway_info)(CC_MqttsnClientHandle, unsigned char) = nullptr; + void (*m_discard_all_gateway_infos)(CC_MqttsnClientHandle) = nullptr; + CC_MqttsnErrorCode (*m_set_default_gw_adv_duration)(CC_MqttsnClientHandle, unsigned) = nullptr; + unsigned (*m_get_default_gw_adv_duration)(CC_MqttsnClientHandle) = nullptr; + CC_MqttsnErrorCode (*m_set_allowed_adv_losses)(CC_MqttsnClientHandle, unsigned) = nullptr; + unsigned (*m_get_allowed_adv_losses)(CC_MqttsnClientHandle) = nullptr; + CC_MqttsnSearchHandle (*m_search_prepare)(CC_MqttsnClientHandle, CC_MqttsnErrorCode*) = nullptr; + CC_MqttsnErrorCode (*m_search_set_retry_period)(CC_MqttsnSearchHandle, unsigned) = nullptr; + unsigned (*m_search_get_retry_period)(CC_MqttsnSearchHandle) = nullptr; + CC_MqttsnErrorCode (*m_search_set_retry_count)(CC_MqttsnSearchHandle, unsigned) = nullptr; + unsigned (*m_search_get_retry_count)(CC_MqttsnSearchHandle) = nullptr; + CC_MqttsnErrorCode (*m_search_set_broadcast_radius)(CC_MqttsnSearchHandle, unsigned) = nullptr; + unsigned (*m_search_get_broadcast_radius)(CC_MqttsnSearchHandle) = nullptr; + CC_MqttsnErrorCode (*m_search_send)(CC_MqttsnSearchHandle, CC_MqttsnSearchCompleteCb, void*) = nullptr; + CC_MqttsnErrorCode (*m_search_cancel)(CC_MqttsnSearchHandle) = nullptr; + CC_MqttsnErrorCode (*m_search)(CC_MqttsnClientHandle, CC_MqttsnSearchCompleteCb, void*) = nullptr; + + + void (*m_set_next_tick_program_callback)(CC_MqttsnClientHandle, CC_MqttsnNextTickProgramCb, void*) = nullptr; + void (*m_set_cancel_next_tick_wait_callback)(CC_MqttsnClientHandle, CC_MqttsnCancelNextTickWaitCb, void*) = nullptr; + void (*m_set_send_output_data_callback)(CC_MqttsnClientHandle, CC_MqttsnSendOutputDataCb, void*) = nullptr; + void (*m_set_gw_status_report_callback)(CC_MqttsnClientHandle, CC_MqttsnGwStatusReportCb, void*) = nullptr; + void (*m_set_gw_disconnect_report_callback)(CC_MqttsnClientHandle, CC_MqttsnGwDisconnectedReportCb, void*) = nullptr; + void (*m_set_message_report_callback)(CC_MqttsnClientHandle, CC_MqttsnMessageReportCb, void*) = nullptr; + void (*m_set_error_log_callback)(CC_MqttsnClientHandle, CC_MqttsnErrorLogCb, void*) = nullptr; + void (*m_set_gwinfo_delay_request_callback)(CC_MqttsnClientHandle, CC_MqttsnGwinfoDelayRequestCb, void*) = nullptr; + + }; + + struct UnitTestDeleter + { + UnitTestDeleter() = default; + explicit UnitTestDeleter(const LibFuncs& ops) : + m_free(ops.m_free) + { + } + + void operator()(CC_MqttsnClient* ptr) + { + m_free(ptr); + } + + private: + void (*m_free)(CC_MqttsnClientHandle) = nullptr; + }; + + struct UnitTestTickInfo + { + unsigned m_req = 0U; + unsigned m_elapsed = 0U; + + UnitTestTickInfo() = default; + explicit UnitTestTickInfo(unsigned req) : m_req(req) {} + }; + + using UnitTestTickInfosList = std::list; + + struct UnitTestGwInfo + { + unsigned m_gwId = 0U; + UnitTestData m_addr; + + UnitTestGwInfo() = default; + UnitTestGwInfo(const UnitTestGwInfo&) = default; + UnitTestGwInfo& operator=(const UnitTestGwInfo&) = default; + UnitTestGwInfo& operator=(const CC_MqttsnGatewayInfo& info); + }; + + struct UnitTestGwInfoReport + { + CC_MqttsnGwStatus m_status = CC_MqttsnGwStatus_ValuesLimit; + UnitTestGwInfo m_info; + + UnitTestGwInfoReport(CC_MqttsnGwStatus status, const CC_MqttsnGatewayInfo* info); + }; + + using UnitTestGwInfoReportsList = std::list; + + using UnitTestClientPtr = std::unique_ptr; + + void unitTestSetUp(); + void unitTestTearDown(); + + UnitTestClientPtr unitTestAllocClient(bool enableLog = false); + void unitTestClientInputData(CC_MqttsnClient* client, const UnitTestData& data); + void unitTestClientInputMessage(CC_MqttsnClient* client, const UnitTestMessage& msg); + + bool unitTestHasTickReq() const; + const UnitTestTickInfo* unitTestTickInfo(bool mustExist = true) const; + void unitTestTick(CC_MqttsnClient* client, unsigned ms = 0U); + + bool unitTestHasGwInfoReport() const; + const UnitTestGwInfoReport* unitTestGetGwInfoReport(bool mustExist = true) const; + void unitTestPopGwInfoReport(); + + void apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen); + +protected: + explicit UnitTestCommonBase(const LibFuncs& funcs); + +private: + + static void unitTestTickProgramCb(void* data, unsigned duration); + static unsigned unitTestCancelTickWaitCb(void* data); + static void unitTestSendOutputDataCb(void* data, const unsigned char* buf, unsigned bufLen, unsigned broadcastRadius); + static void unitTestGwStatusReportCb(void* data, CC_MqttsnGwStatus status, const CC_MqttsnGatewayInfo* info); + static void unitTestGwDisconnectReportCb(void* data, CC_MqttsnGatewayDisconnectReason reason); + static void unitTestMessageReportCb(void* data, const CC_MqttsnMessageInfo* msgInfo); + static unsigned unitTestGwinfoDelayRequestCb(void* data); + static void unitTestErrorLogCb(void* data, const char* msg); + + LibFuncs m_funcs; + UnitTestTickInfosList m_ticks; + UnitTestGwInfoReportsList m_gwInfoReports; +}; \ No newline at end of file diff --git a/client/lib/test/UnitTestDefaultBase.cpp b/client/lib/test/UnitTestDefaultBase.cpp new file mode 100644 index 00000000..21ef424e --- /dev/null +++ b/client/lib/test/UnitTestDefaultBase.cpp @@ -0,0 +1,48 @@ +#include "UnitTestDefaultBase.h" + +#include "client.h" + +const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() +{ + static LibFuncs funcs; + funcs.m_alloc = &cc_mqttsn_client_alloc; + funcs.m_free = &cc_mqttsn_client_free; + funcs.m_tick = &cc_mqttsn_client_tick; + funcs.m_process_data = &cc_mqttsn_client_process_data; + funcs.m_set_default_retry_period = &cc_mqttsn_client_set_default_retry_period; + funcs.m_get_default_retry_period = &cc_mqttsn_client_get_default_retry_period; + funcs.m_set_default_retry_count = &cc_mqttsn_client_set_default_retry_count; + funcs.m_get_default_retry_count = &cc_mqttsn_client_get_default_retry_count; + funcs.m_set_default_broadcast_radius = &cc_mqttsn_client_set_default_broadcast_radius; + funcs.m_get_default_broadcast_radius = &cc_mqttsn_client_get_default_broadcast_radius; + funcs.m_get_available_gateways_count = &cc_mqttsn_client_get_available_gateways_count; + funcs.m_get_available_gateway_info = &cc_mqttsn_client_get_available_gateway_info; + funcs.m_set_available_gateway_info = &cc_mqttsn_client_set_available_gateway_info; + funcs.m_discard_available_gateway_info = &cc_mqttsn_client_discard_available_gateway_info; + funcs.m_discard_all_gateway_infos = &cc_mqttsn_client_discard_all_gateway_infos; + funcs.m_set_default_gw_adv_duration = &cc_mqttsn_client_set_default_gw_adv_duration; + funcs.m_get_default_gw_adv_duration = &cc_mqttsn_client_get_default_gw_adv_duration; + funcs.m_set_allowed_adv_losses = &cc_mqttsn_client_set_allowed_adv_losses; + funcs.m_get_allowed_adv_losses = &cc_mqttsn_client_get_allowed_adv_losses; + funcs.m_search_prepare = &cc_mqttsn_client_search_prepare; + funcs.m_search_set_retry_period = &cc_mqttsn_client_search_set_retry_period; + funcs.m_search_get_retry_period = &cc_mqttsn_client_search_get_retry_period; + funcs.m_search_set_retry_count = &cc_mqttsn_client_search_set_retry_count; + funcs.m_search_get_retry_count = &cc_mqttsn_client_search_get_retry_count; + funcs.m_search_set_broadcast_radius = &cc_mqttsn_client_search_set_broadcast_radius; + funcs.m_search_get_broadcast_radius = &cc_mqttsn_client_search_get_broadcast_radius; + funcs.m_search_send = &cc_mqttsn_client_search_send; + funcs.m_search_cancel = &cc_mqttsn_client_search_cancel; + funcs.m_search = &cc_mqttsn_client_search; + + funcs.m_set_next_tick_program_callback = &cc_mqttsn_client_set_next_tick_program_callback; + funcs.m_set_cancel_next_tick_wait_callback = &cc_mqttsn_client_set_cancel_next_tick_wait_callback; + funcs.m_set_send_output_data_callback = &cc_mqttsn_client_set_send_output_data_callback; + funcs.m_set_gw_status_report_callback = &cc_mqttsn_client_set_gw_status_report_callback; + funcs.m_set_gw_disconnect_report_callback = &cc_mqttsn_client_set_gw_disconnect_report_callback; + funcs.m_set_message_report_callback = &cc_mqttsn_client_set_message_report_callback; + funcs.m_set_error_log_callback = &cc_mqttsn_client_set_error_log_callback; + funcs.m_set_gwinfo_delay_request_callback = &cc_mqttsn_client_set_gwinfo_delay_request_callback; + + return funcs; +} diff --git a/client/lib/test/UnitTestDefaultBase.h b/client/lib/test/UnitTestDefaultBase.h new file mode 100644 index 00000000..b0aa521d --- /dev/null +++ b/client/lib/test/UnitTestDefaultBase.h @@ -0,0 +1,16 @@ +#pragma once + +#include "UnitTestCommonBase.h" + +class UnitTestDefaultBase : public UnitTestCommonBase +{ + using Base = UnitTestCommonBase; +protected: + + UnitTestDefaultBase(): + Base(getFuncs()) + { + } + + static const LibFuncs& getFuncs(); +}; \ No newline at end of file diff --git a/client/lib/test/UnitTestGwDiscover.th b/client/lib/test/UnitTestGwDiscover.th new file mode 100644 index 00000000..5a677888 --- /dev/null +++ b/client/lib/test/UnitTestGwDiscover.th @@ -0,0 +1,71 @@ +#include "UnitTestDefaultBase.h" +#include "UnitTestProtocolDefs.h" + +#include "comms/units.h" + +#include + +class UnitTestGwDiscover : public CxxTest::TestSuite, public UnitTestDefaultBase +{ +public: + void test1(); + +private: + virtual void setUp() override + { + unitTestSetUp(); + } + + virtual void tearDown() override + { + unitTestTearDown(); + } +}; + +void UnitTestGwDiscover::test1() +{ + // Testing gw advertisement + + auto clientPtr = unitTestAllocClient(); + [[maybe_unused]] auto* client = clientPtr.get(); + + const std::uint8_t GwId = 1U; + const unsigned AdvDurationMin = 10U; + + UnitTestAdvertiseMsg advertiseMsg; + advertiseMsg.field_gwId().setValue(GwId); + comms::units::setMinutes(advertiseMsg.field_duration(), AdvDurationMin); + unitTestClientInputMessage(client, advertiseMsg); + + { + auto* gwInfoReport = unitTestGetGwInfoReport(); + TS_ASSERT_EQUALS(gwInfoReport->m_status, CC_MqttsnGwStatus_AddedByGateway); + TS_ASSERT_EQUALS(gwInfoReport->m_info.m_gwId, GwId); + TS_ASSERT(gwInfoReport->m_info.m_addr.empty()); + unitTestPopGwInfoReport(); + TS_ASSERT(!unitTestHasGwInfoReport()); + } + + auto* tickReq = unitTestTickInfo(); + TS_ASSERT_LESS_THAN(AdvDurationMin * 60 * 1000, tickReq->m_req); // Extra buffer after expiry is expected + + unitTestTick(client); + { + auto* gwInfoReport = unitTestGetGwInfoReport(); + TS_ASSERT_EQUALS(gwInfoReport->m_status, CC_MqttsnGwStatus_Tentative); + TS_ASSERT_EQUALS(gwInfoReport->m_info.m_gwId, GwId); + TS_ASSERT(gwInfoReport->m_info.m_addr.empty()); + unitTestPopGwInfoReport(); + TS_ASSERT(!unitTestHasGwInfoReport()); + } + + unitTestTick(client); + { + auto* gwInfoReport = unitTestGetGwInfoReport(); + TS_ASSERT_EQUALS(gwInfoReport->m_status, CC_MqttsnGwStatus_Removed); + TS_ASSERT_EQUALS(gwInfoReport->m_info.m_gwId, GwId); + TS_ASSERT(gwInfoReport->m_info.m_addr.empty()); + unitTestPopGwInfoReport(); + TS_ASSERT(!unitTestHasGwInfoReport()); + } +} \ No newline at end of file diff --git a/client/lib/test/UnitTestProtocolDefs.h b/client/lib/test/UnitTestProtocolDefs.h new file mode 100644 index 00000000..c2f20e3a --- /dev/null +++ b/client/lib/test/UnitTestProtocolDefs.h @@ -0,0 +1,39 @@ +// +// Copyright 2023 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "cc_mqttsn/Message.h" +#include "cc_mqttsn/frame/Frame.h" +#include "cc_mqttsn/input/AllMessages.h" +#include "comms/GenericHandler.h" + +#include +#include + +class UnitTestMsgHandler; + +using UnitTestMessage = cc_mqttsn::Message< + comms::option::app::ReadIterator, + comms::option::app::WriteIterator, + comms::option::app::LengthInfoInterface, + comms::option::app::IdInfoInterface, + comms::option::app::Handler +>; + +CC_MQTTSN_ALIASES_FOR_ALL_MESSAGES_DEFAULT_OPTIONS(UnitTest, Msg, UnitTestMessage) + +using UnitTestsFrame = cc_mqttsn::frame::Frame; +using UniTestsMsgPtr = UnitTestsFrame::MsgPtr; + +class UnitTestMsgHandler : public comms::GenericHandler > +{ +protected: + UnitTestMsgHandler() = default; + ~UnitTestMsgHandler() noexcept = default; +}; + From 854feb5d9d430dd3638dae7d1dbdf7000c6476f1 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 12 Jun 2024 09:20:22 +1000 Subject: [PATCH 048/106] More gateway discovery unittests. --- client/lib/src/ClientImpl.cpp | 22 ++- client/lib/src/op/ConnectOp.cpp | 1 + client/lib/src/op/SearchOp.cpp | 3 +- client/lib/test/UnitTestCommonBase.cpp | 180 ++++++++++++++++++++++--- client/lib/test/UnitTestCommonBase.h | 49 ++++++- client/lib/test/UnitTestGwDiscover.th | 84 +++++++++++- 6 files changed, 301 insertions(+), 38 deletions(-) diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 92142346..b28316e3 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -650,6 +650,12 @@ void ClientImpl::handle(GwinfoMsg& msg) return; // Report gateway status on exit } + if (m_clientState.m_gwInfos.max_size() <= m_clientState.m_gwInfos.size()) { + // Not enough space + errorLog("Failed to store the new gateway information, due to insufficient storage"); + return; + } + m_clientState.m_gwInfos.resize(m_clientState.m_gwInfos.size() + 1U); auto& info = m_clientState.m_gwInfos.back(); info.m_expiryTimestamp = m_clientState.m_timestamp + m_configState.m_gwAdvTimeoutMs + m_configState.m_retryPeriod; @@ -658,23 +664,15 @@ void ClientImpl::handle(GwinfoMsg& msg) info.m_gwId = msg.field_gwId().value(); info.m_allowedAdvLosses = m_configState.m_allowedAdvLosses; gwInfo = &info; + gwStatus = CC_MqttsnGwStatus_AddedByGateway; auto& addr = msg.field_gwAdd().value(); - if (addr.empty()) { - gwStatus = CC_MqttsnGwStatus_AddedByGateway; - return; // Report gateway status on exit + if (!addr.empty()) { + info.m_addr.assign(addr.begin(), addr.end()); + gwStatus = CC_MqttsnGwStatus_AddedByClient; } - if (m_clientState.m_gwInfos.max_size() <= m_clientState.m_gwInfos.size()) { - // Not enough space - errorLog("Failed to store the new gateway information, due to insufficient storage"); - return; - } - - info.m_addr.assign(addr.begin(), addr.end()); monitorGatewayExpiry(); - - gwStatus = CC_MqttsnGwStatus_AddedByClient; // Report geteway status on exit } #endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY diff --git a/client/lib/src/op/ConnectOp.cpp b/client/lib/src/op/ConnectOp.cpp index 6b50a91f..3cdcae9d 100644 --- a/client/lib/src/op/ConnectOp.cpp +++ b/client/lib/src/op/ConnectOp.cpp @@ -116,6 +116,7 @@ CC_MqttsnErrorCode ConnectOp::send(CC_MqttsnConnectCompleteCb cb, void* cbData) return CC_MqttsnErrorCode_InternalError; } + auto guard = client().apiEnter(); m_cb = cb; m_cbData = cbData; diff --git a/client/lib/src/op/SearchOp.cpp b/client/lib/src/op/SearchOp.cpp index 854cfe7f..005e2c7f 100644 --- a/client/lib/src/op/SearchOp.cpp +++ b/client/lib/src/op/SearchOp.cpp @@ -60,8 +60,9 @@ CC_MqttsnErrorCode SearchOp::send(CC_MqttsnSearchCompleteCb cb, void* cbData) if (!m_timer.isValid()) { errorLog("The library cannot allocate required number of timers."); return CC_MqttsnErrorCode_InternalError; - } + } + auto guard = client().apiEnter(); m_cb = cb; m_cbData = cbData; diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index 05f2a7ed..15940923 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -64,6 +64,12 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_set_gwinfo_delay_request_callback != nullptr); } +UnitTestCommonBase::UnitTestOutputDataInfo::UnitTestOutputDataInfo(const std::uint8_t* buf, unsigned bufLen, unsigned broadcastRadius) : + m_data(buf, buf + bufLen), + m_broadcastRadius(broadcastRadius) +{ +} + UnitTestCommonBase::UnitTestGwInfo& UnitTestCommonBase::UnitTestGwInfo::operator=(const CC_MqttsnGatewayInfo& info) { m_gwId = info.m_gwId; @@ -83,12 +89,21 @@ UnitTestCommonBase::UnitTestGwInfoReport::UnitTestGwInfoReport(CC_MqttsnGwStatus } } +UnitTestCommonBase::UnitTestSearchCompleteReport::UnitTestSearchCompleteReport(CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info) : + m_status(status) +{ + if (info != nullptr) { + m_info = *info; + } +} + void UnitTestCommonBase::unitTestSetUp() { } void UnitTestCommonBase::unitTestTearDown() { + m_data = ClientData(); } UnitTestCommonBase::UnitTestClientPtr UnitTestCommonBase::unitTestAllocClient(bool enableLog) @@ -127,7 +142,7 @@ void UnitTestCommonBase::unitTestClientInputMessage(CC_MqttsnClient* client, con bool UnitTestCommonBase::unitTestHasTickReq() const { - return !m_ticks.empty(); + return !m_data.m_ticks.empty(); } const UnitTestCommonBase::UnitTestTickInfo* UnitTestCommonBase::unitTestTickInfo(bool mustExist) const @@ -137,13 +152,13 @@ const UnitTestCommonBase::UnitTestTickInfo* UnitTestCommonBase::unitTestTickInfo return nullptr; } - return &m_ticks.front(); + return &m_data.m_ticks.front(); } void UnitTestCommonBase::unitTestTick(CC_MqttsnClient* client, unsigned ms) { - test_assert(!m_ticks.empty()); - auto& info = m_ticks.front(); + test_assert(!m_data.m_ticks.empty()); + auto& info = m_data.m_ticks.front(); if (ms == 0U) { ms = info.m_req; } @@ -154,13 +169,74 @@ void UnitTestCommonBase::unitTestTick(CC_MqttsnClient* client, unsigned ms) } auto msToReport = info.m_req; - m_ticks.pop_front(); + m_data.m_ticks.pop_front(); m_funcs.m_tick(client, msToReport); } +bool UnitTestCommonBase::unitTestHasOutputData() const +{ + return !m_data.m_outData.empty(); +} + +const UnitTestCommonBase::UnitTestOutputDataInfo* UnitTestCommonBase::unitTestOutputDataInfo(bool mustExist) const +{ + if (!unitTestHasOutputData()) { + test_assert(!mustExist); + return nullptr; + } + + return &m_data.m_outData.front(); +} + +void UnitTestCommonBase::unitTestPopOutputData() +{ + test_assert(unitTestHasOutputData()); + m_data.m_outData.pop_front(); +} + +std::vector UnitTestCommonBase::unitTestPopAllOuputMessages(bool mustExist) +{ + std::vector result; + do { + if (!unitTestHasOutputData()) { + break; + } + + auto readPtr = comms::readIteratorFor(m_data.m_outData.front().m_data.data()); + auto endPtr = readPtr + m_data.m_outData.front().m_data.size(); + UnitTestsFrame frame; + while (readPtr < endPtr) { + UniTestsMsgPtr msg; + auto remLen = static_cast(std::distance(readPtr, endPtr)); + auto es = frame.read(msg, readPtr, remLen); + if (es != comms::ErrorStatus::Success) { + break; + } + + result.push_back(std::move(msg)); + // readPtr is advanced in read operation above + } + + } while (false); + + test_assert((!mustExist) || (!result.empty())) + return result; +} + +UniTestsMsgPtr UnitTestCommonBase::unitTestPopOutputMessage(bool mustExist) +{ + auto allMessages = unitTestPopAllOuputMessages(mustExist); + if (allMessages.empty()) { + return UniTestsMsgPtr(); + } + + test_assert(allMessages.size() == 1U); + return std::move(allMessages.front()); +} + bool UnitTestCommonBase::unitTestHasGwInfoReport() const { - return !m_gwInfoReports.empty(); + return !m_data.m_gwInfoReports.empty(); } const UnitTestCommonBase::UnitTestGwInfoReport* UnitTestCommonBase::unitTestGetGwInfoReport(bool mustExist) const @@ -170,13 +246,52 @@ const UnitTestCommonBase::UnitTestGwInfoReport* UnitTestCommonBase::unitTestGetG return nullptr; } - return &m_gwInfoReports.front(); + return &m_data.m_gwInfoReports.front(); } void UnitTestCommonBase::unitTestPopGwInfoReport() { - test_assert(!m_gwInfoReports.empty()); - m_gwInfoReports.pop_front(); + test_assert(!m_data.m_gwInfoReports.empty()); + m_data.m_gwInfoReports.pop_front(); +} + +bool UnitTestCommonBase::unitTestHasSearchCompleteReport() const +{ + return !m_data.m_searchCompleteReports.empty(); +} + +const UnitTestCommonBase::UnitTestSearchCompleteReport* UnitTestCommonBase::unitTestSearchCompleteReport(bool mustExist) const +{ + if (!unitTestHasSearchCompleteReport()) { + test_assert(!mustExist); + return nullptr; + } + + return &m_data.m_searchCompleteReports.front(); +} + +void UnitTestCommonBase::unitTestPopSearchCompletereport() +{ + test_assert(unitTestHasSearchCompleteReport()); + m_data.m_searchCompleteReports.pop_front(); +} + +void UnitTestCommonBase::unitTestSearchSend(CC_MqttsnSearchHandle search, UnitTestSearchCompleteCb&& cb) +{ + if (cb) { + m_data.m_searchCompleteCallbacks.push_back(std::move(cb)); + } + + m_funcs.m_search_send(search, &UnitTestCommonBase::unitTestSearchCompleteCb, this); +} + +void UnitTestCommonBase::unitTestSearch(CC_MqttsnClient* client, UnitTestSearchCompleteCb&& cb) +{ + if (cb) { + m_data.m_searchCompleteCallbacks.push_back(std::move(cb)); + } + + m_funcs.m_search(client, &UnitTestCommonBase::unitTestSearchCompleteCb, this); } void UnitTestCommonBase::apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen) @@ -184,15 +299,20 @@ void UnitTestCommonBase::apiProcessData(CC_MqttsnClient* client, const unsigned m_funcs.m_process_data(client, buf, bufLen); } +CC_MqttsnSearchHandle UnitTestCommonBase::apiSearchPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec) +{ + return m_funcs.m_search_prepare(client, ec); +} + void UnitTestCommonBase::unitTestTickProgramCb(void* data, unsigned duration) { auto* thisPtr = asThis(data); - if (thisPtr->m_ticks.empty()) { - asThis(data)->m_ticks.emplace_back(duration); + if (thisPtr->m_data.m_ticks.empty()) { + asThis(data)->m_data.m_ticks.emplace_back(duration); return; } - auto& info = thisPtr->m_ticks.front(); + auto& info = thisPtr->m_data.m_ticks.front(); test_assert(info.m_req == 0U); info.m_req = duration; } @@ -200,25 +320,21 @@ void UnitTestCommonBase::unitTestTickProgramCb(void* data, unsigned duration) unsigned UnitTestCommonBase::unitTestCancelTickWaitCb(void* data) { auto* thisPtr = asThis(data); - test_assert(!thisPtr->m_ticks.empty()); - auto result = thisPtr->m_ticks.front().m_elapsed; - thisPtr->m_ticks.pop_front(); + test_assert(!thisPtr->m_data.m_ticks.empty()); + auto result = thisPtr->m_data.m_ticks.front().m_elapsed; + thisPtr->m_data.m_ticks.pop_front(); return result; } void UnitTestCommonBase::unitTestSendOutputDataCb(void* data, const unsigned char* buf, unsigned bufLen, unsigned broadcastRadius) { - // TODO: - static_cast(data); - static_cast(buf); - static_cast(bufLen); - static_cast(broadcastRadius); - test_assert(false); + auto* thisPtr = asThis(data); + thisPtr->m_data.m_outData.emplace_back(buf, bufLen, broadcastRadius); } void UnitTestCommonBase::unitTestGwStatusReportCb(void* data, CC_MqttsnGwStatus status, const CC_MqttsnGatewayInfo* info) { - asThis(data)->m_gwInfoReports.emplace_back(status, info); + asThis(data)->m_data.m_gwInfoReports.emplace_back(status, info); } void UnitTestCommonBase::unitTestGwDisconnectReportCb(void* data, CC_MqttsnGatewayDisconnectReason reason) @@ -247,4 +363,24 @@ unsigned UnitTestCommonBase::unitTestGwinfoDelayRequestCb(void* data) void UnitTestCommonBase::unitTestErrorLogCb([[maybe_unused]] void* data, const char* msg) { std::cout << "ERROR: " << msg << std::endl; +} + +void UnitTestCommonBase::unitTestSearchCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info) +{ + auto* thisPtr = asThis(data); + thisPtr->m_data.m_searchCompleteReports.emplace_back(status, info); + + if (thisPtr->m_data.m_searchCompleteCallbacks.empty()) { + return; + } + + auto& func = thisPtr->m_data.m_searchCompleteCallbacks.front(); + test_assert(func); + + bool popReport = func(thisPtr->m_data.m_searchCompleteReports.back()); + thisPtr->m_data.m_searchCompleteCallbacks.pop_front(); + + if (popReport) { + thisPtr->m_data.m_searchCompleteReports.pop_back(); + } } \ No newline at end of file diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index 0e2eed4f..b4be3d44 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -4,6 +4,7 @@ #include "UnitTestProtocolDefs.h" +#include #include class UnitTestCommonBase @@ -83,6 +84,16 @@ class UnitTestCommonBase using UnitTestTickInfosList = std::list; + struct UnitTestOutputDataInfo + { + UnitTestData m_data; + unsigned m_broadcastRadius = 0U; + + UnitTestOutputDataInfo(const std::uint8_t* buf, unsigned bufLen, unsigned broadcastRadius); + }; + + using UnitTestOutputDataInfosList = std::list; + struct UnitTestGwInfo { unsigned m_gwId = 0U; @@ -104,6 +115,18 @@ class UnitTestCommonBase using UnitTestGwInfoReportsList = std::list; + struct UnitTestSearchCompleteReport + { + CC_MqttsnAsyncOpStatus m_status = CC_MqttsnAsyncOpStatus_ValuesLimit; + UnitTestGwInfo m_info; + + UnitTestSearchCompleteReport(CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info); + }; + using UnitTestSearchCompleteReportsList = std::list; + + using UnitTestSearchCompleteCb = std::function; + using UnitTestSearchCompleteCbList = std::list; + using UnitTestClientPtr = std::unique_ptr; void unitTestSetUp(); @@ -117,16 +140,38 @@ class UnitTestCommonBase const UnitTestTickInfo* unitTestTickInfo(bool mustExist = true) const; void unitTestTick(CC_MqttsnClient* client, unsigned ms = 0U); + bool unitTestHasOutputData() const; + const UnitTestOutputDataInfo* unitTestOutputDataInfo(bool mustExist = true) const; + void unitTestPopOutputData(); + std::vector unitTestPopAllOuputMessages(bool mustExist = true); + UniTestsMsgPtr unitTestPopOutputMessage(bool mustExist = true); + bool unitTestHasGwInfoReport() const; const UnitTestGwInfoReport* unitTestGetGwInfoReport(bool mustExist = true) const; void unitTestPopGwInfoReport(); + bool unitTestHasSearchCompleteReport() const; + const UnitTestSearchCompleteReport* unitTestSearchCompleteReport(bool mustExist = true) const; + void unitTestPopSearchCompletereport(); + + void unitTestSearchSend(CC_MqttsnSearchHandle search, UnitTestSearchCompleteCb&& cb = UnitTestSearchCompleteCb()); + void unitTestSearch(CC_MqttsnClient* client, UnitTestSearchCompleteCb&& cb = UnitTestSearchCompleteCb()); + void apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen); + CC_MqttsnSearchHandle apiSearchPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec = nullptr); protected: explicit UnitTestCommonBase(const LibFuncs& funcs); private: + struct ClientData + { + UnitTestTickInfosList m_ticks; + UnitTestOutputDataInfosList m_outData; + UnitTestGwInfoReportsList m_gwInfoReports; + UnitTestSearchCompleteReportsList m_searchCompleteReports; + UnitTestSearchCompleteCbList m_searchCompleteCallbacks; + }; static void unitTestTickProgramCb(void* data, unsigned duration); static unsigned unitTestCancelTickWaitCb(void* data); @@ -136,8 +181,8 @@ class UnitTestCommonBase static void unitTestMessageReportCb(void* data, const CC_MqttsnMessageInfo* msgInfo); static unsigned unitTestGwinfoDelayRequestCb(void* data); static void unitTestErrorLogCb(void* data, const char* msg); + static void unitTestSearchCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info); LibFuncs m_funcs; - UnitTestTickInfosList m_ticks; - UnitTestGwInfoReportsList m_gwInfoReports; + ClientData m_data; }; \ No newline at end of file diff --git a/client/lib/test/UnitTestGwDiscover.th b/client/lib/test/UnitTestGwDiscover.th index 5a677888..77c69002 100644 --- a/client/lib/test/UnitTestGwDiscover.th +++ b/client/lib/test/UnitTestGwDiscover.th @@ -9,6 +9,8 @@ class UnitTestGwDiscover : public CxxTest::TestSuite, public UnitTestDefaultBase { public: void test1(); + void test2(); + void test3(); private: virtual void setUp() override @@ -27,7 +29,7 @@ void UnitTestGwDiscover::test1() // Testing gw advertisement auto clientPtr = unitTestAllocClient(); - [[maybe_unused]] auto* client = clientPtr.get(); + auto* client = clientPtr.get(); const std::uint8_t GwId = 1U; const unsigned AdvDurationMin = 10U; @@ -68,4 +70,84 @@ void UnitTestGwDiscover::test1() unitTestPopGwInfoReport(); TS_ASSERT(!unitTestHasGwInfoReport()); } +} + +void UnitTestGwDiscover::test2() +{ + // Testing search, response from GW + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + unitTestSearch(client); + TS_ASSERT(unitTestHasOutputData()); + auto broadcastRadius = unitTestOutputDataInfo()->m_broadcastRadius; + TS_ASSERT_LESS_THAN(0U, broadcastRadius); + + auto sentMsg = unitTestPopOutputMessage(); + auto* searchMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(searchMsg, nullptr); + TS_ASSERT_EQUALS(searchMsg->field_radius().value(), broadcastRadius); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + const std::uint8_t GwId = 1U; + UnitTestGwinfoMsg gwinfoMsg; + gwinfoMsg.field_gwId().setValue(GwId); + unitTestClientInputMessage(client, gwinfoMsg); + + TS_ASSERT(unitTestHasSearchCompleteReport()); + auto* searchCompleteReport = unitTestSearchCompleteReport(); + TS_ASSERT_EQUALS(searchCompleteReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(searchCompleteReport->m_info.m_gwId, GwId); + TS_ASSERT(searchCompleteReport->m_info.m_addr.empty()); + + auto* gwInfoReport = unitTestGetGwInfoReport(); + TS_ASSERT_EQUALS(gwInfoReport->m_status, CC_MqttsnGwStatus_AddedByGateway); + TS_ASSERT_EQUALS(gwInfoReport->m_info.m_gwId, GwId); + TS_ASSERT(gwInfoReport->m_info.m_addr.empty()); + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestGwDiscover::test3() +{ + // Testing search, response from another client + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + unitTestSearch(client); + TS_ASSERT(unitTestHasOutputData()); + auto broadcastRadius = unitTestOutputDataInfo()->m_broadcastRadius; + TS_ASSERT_LESS_THAN(0U, broadcastRadius); + + auto sentMsg = unitTestPopOutputMessage(); + auto* searchMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(searchMsg, nullptr); + TS_ASSERT_EQUALS(searchMsg->field_radius().value(), broadcastRadius); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + const std::uint8_t GwId = 1U; + const UnitTestData Addr = {0, 1, 2, 3}; + UnitTestGwinfoMsg gwinfoMsg; + gwinfoMsg.field_gwId().setValue(GwId); + comms::util::assign(gwinfoMsg.field_gwAdd().value(), Addr.begin(), Addr.end()); + unitTestClientInputMessage(client, gwinfoMsg); + + TS_ASSERT(unitTestHasSearchCompleteReport()); + auto* searchCompleteReport = unitTestSearchCompleteReport(); + TS_ASSERT_EQUALS(searchCompleteReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(searchCompleteReport->m_info.m_gwId, GwId); + TS_ASSERT_EQUALS(searchCompleteReport->m_info.m_addr, Addr); + + auto* gwInfoReport = unitTestGetGwInfoReport(); + TS_ASSERT_EQUALS(gwInfoReport->m_status, CC_MqttsnGwStatus_AddedByClient); + TS_ASSERT_EQUALS(gwInfoReport->m_info.m_gwId, GwId); + TS_ASSERT_EQUALS(gwInfoReport->m_info.m_addr, Addr); + + TS_ASSERT(unitTestHasTickReq()); } \ No newline at end of file From cf995e52e34e580bea4b525eda252746e16b685f Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 13 Jun 2024 08:49:18 +1000 Subject: [PATCH 049/106] More gateway discovery unittests for client library. --- client/lib/doxygen/main.dox | 6 +- client/lib/src/op/SearchOp.cpp | 2 +- client/lib/templ/client.cpp.templ | 6 + client/lib/templ/client.h.templ | 6 + client/lib/test/UnitTestCommonBase.cpp | 62 ++++++++- client/lib/test/UnitTestCommonBase.h | 13 +- client/lib/test/UnitTestDefaultBase.cpp | 1 + client/lib/test/UnitTestGwDiscover.th | 176 +++++++++++++++++++++++- 8 files changed, 266 insertions(+), 6 deletions(-) diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index ecadbac3..5e0a54fe 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -491,14 +491,18 @@ /// from withing the operation completion callback to update the stored gateway address. /// @code /// CC_MqttsnGatewayInfo info; +/// cc_mqttsn_client_init_gateway_info(&info); /// info.m_gwId = ...; /// info.m_addr = ...; /// info.m_addrLen = ...; -/// CC_MqttsnErrorCode ec = cc_mqttsn_client_set_available_gateway_info(client, info); +/// CC_MqttsnErrorCode ec = cc_mqttsn_client_set_available_gateway_info(client, &info); /// if (ec != CC_MqttsnErrorCode_Success) { /// printf("Something is wrong"); /// } /// @endcode +/// It is recommended to initialize @ref CC_MqttsnGatewayInfo structure using the +/// @b cc_mqttsn_client_init_gateway_info() function before update, even though all +/// the member fields are assigned new values. /// /// The application can force the library to discard the available gateway information /// by issuing the @b cc_mqttsn_client_discard_available_gateway_info() function. diff --git a/client/lib/src/op/SearchOp.cpp b/client/lib/src/op/SearchOp.cpp index 005e2c7f..977b5041 100644 --- a/client/lib/src/op/SearchOp.cpp +++ b/client/lib/src/op/SearchOp.cpp @@ -164,7 +164,7 @@ void SearchOp::timeoutInternal() void SearchOp::opTimeoutCb(void* data) { - asSearchOp(data)->sendInternal(); + asSearchOp(data)->timeoutInternal(); } } // namespace op diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index da005c42..7e7e501b 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -146,6 +146,12 @@ unsigned cc_mqttsn_##NAME##client_get_available_gateways_count(CC_MqttsnClientHa return static_cast(clientFromHandle(client)->clientState().m_gwInfos.size()); } +void cc_mqttsn_##NAME##client_init_gateway_info(CC_MqttsnGatewayInfo* info) +{ + COMMS_ASSERT(info != nullptr); + *info = CC_MqttsnGatewayInfo(); +} + CC_MqttsnErrorCode cc_mqttsn_##NAME##client_get_available_gateway_info(CC_MqttsnClientHandle client, unsigned idx, CC_MqttsnGatewayInfo* info) { COMMS_ASSERT(client != nullptr); diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index eabb038e..c7e7f52b 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -127,11 +127,16 @@ unsigned cc_mqttsn_##NAME##client_get_default_broadcast_radius(CC_MqttsnClientHa /// @ingroup client unsigned cc_mqttsn_##NAME##client_get_available_gateways_count(CC_MqttsnClientHandle client); +/// @brief Initialize the @ref CC_MqttsnGatewayInfo structure. +/// @details Zeroes all the member fields. +void cc_mqttsn_##NAME##client_init_gateway_info(CC_MqttsnGatewayInfo* info); + /// @brief Retrieve stored available gateway information /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[in] idx Index of the available gateway information. /// @param[out] info Stored gateway information. /// @return Result code of the call. +/// @ingroup client CC_MqttsnErrorCode cc_mqttsn_##NAME##client_get_available_gateway_info(CC_MqttsnClientHandle client, unsigned idx, CC_MqttsnGatewayInfo* info); /// @brief Update stored available gateway information @@ -141,6 +146,7 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_get_available_gateway_info(CC_Mqttsn /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[in] info Updated gateway information. /// @return Result code of the call. +/// @ingroup client CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_available_gateway_info(CC_MqttsnClientHandle client, const CC_MqttsnGatewayInfo* info); /// @brief Discard stored available gateway information diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index 15940923..9720194c 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -35,6 +35,7 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_set_default_retry_count != nullptr); test_assert(m_funcs.m_get_default_retry_count != nullptr); test_assert(m_funcs.m_set_default_broadcast_radius != nullptr); + test_assert(m_funcs.m_init_gateway_info != nullptr); test_assert(m_funcs.m_get_available_gateway_info != nullptr); test_assert(m_funcs.m_set_available_gateway_info != nullptr); test_assert(m_funcs.m_discard_available_gateway_info != nullptr); @@ -97,6 +98,13 @@ UnitTestCommonBase::UnitTestSearchCompleteReport::UnitTestSearchCompleteReport(C } } +void UnitTestCommonBase::UnitTestSearchCompleteReport::assignInfo(CC_MqttsnGatewayInfo& info) const +{ + info.m_gwId = m_info.m_gwId; + info.m_addr = m_info.m_addr.data(); + info.m_addrLen = static_cast(m_info.m_addr.size()); +} + void UnitTestCommonBase::unitTestSetUp() { } @@ -217,6 +225,7 @@ std::vector UnitTestCommonBase::unitTestPopAllOuputMessages(bool // readPtr is advanced in read operation above } + m_data.m_outData.pop_front(); } while (false); test_assert((!mustExist) || (!result.empty())) @@ -270,7 +279,7 @@ const UnitTestCommonBase::UnitTestSearchCompleteReport* UnitTestCommonBase::unit return &m_data.m_searchCompleteReports.front(); } -void UnitTestCommonBase::unitTestPopSearchCompletereport() +void UnitTestCommonBase::unitTestPopSearchCompleteReport() { test_assert(unitTestHasSearchCompleteReport()); m_data.m_searchCompleteReports.pop_front(); @@ -294,16 +303,67 @@ void UnitTestCommonBase::unitTestSearch(CC_MqttsnClient* client, UnitTestSearchC m_funcs.m_search(client, &UnitTestCommonBase::unitTestSearchCompleteCb, this); } +void UnitTestCommonBase::unitTestSearchUpdateAddr(CC_MqttsnClient* client, const UnitTestData& addr) +{ + unitTestSearch( + client, + [this, client, addr](const UnitTestSearchCompleteReport& report) + { + if (report.m_status != CC_MqttsnAsyncOpStatus_Complete) { + return false; + } + + auto prevCount = m_funcs.m_get_available_gateways_count(client); + + CC_MqttsnGatewayInfo updInfo; + m_funcs.m_init_gateway_info(&updInfo); + updInfo.m_gwId = report.m_info.m_gwId; + updInfo.m_addr = addr.data(); + updInfo.m_addrLen = static_cast(addr.size()); + auto ec = m_funcs.m_set_available_gateway_info(client, &updInfo); + test_assert(ec == CC_MqttsnErrorCode_Success); + + auto afterUpdateCount = m_funcs.m_get_available_gateways_count(client); + test_assert(prevCount == afterUpdateCount); // Mustn't change + return false; + }); +} + void UnitTestCommonBase::apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen) { m_funcs.m_process_data(client, buf, bufLen); } +CC_MqttsnErrorCode UnitTestCommonBase::apiSetDefaultRetryPeriod(CC_MqttsnClient* client, unsigned value) +{ + return m_funcs.m_set_default_retry_period(client, value); +} + +CC_MqttsnErrorCode UnitTestCommonBase::apiSetDefaultRetryCount(CC_MqttsnClient* client, unsigned value) +{ + return m_funcs.m_set_default_retry_count(client, value); +} + CC_MqttsnSearchHandle UnitTestCommonBase::apiSearchPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec) { return m_funcs.m_search_prepare(client, ec); } +CC_MqttsnErrorCode UnitTestCommonBase::apiSearchSetRetryPeriod(CC_MqttsnSearchHandle search, unsigned value) +{ + return m_funcs.m_search_set_retry_period(search, value); +} + +CC_MqttsnErrorCode UnitTestCommonBase::apiSearchSetRetryCount(CC_MqttsnSearchHandle search, unsigned value) +{ + return m_funcs.m_search_set_retry_count(search, value); +} + +CC_MqttsnErrorCode UnitTestCommonBase::apiSearchSetBroadcastRadius(CC_MqttsnSearchHandle search, unsigned value) +{ + return m_funcs.m_search_set_broadcast_radius(search, value); +} + void UnitTestCommonBase::unitTestTickProgramCb(void* data, unsigned duration) { auto* thisPtr = asThis(data); diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index b4be3d44..5bbbae77 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -25,6 +25,7 @@ class UnitTestCommonBase CC_MqttsnErrorCode (*m_set_default_broadcast_radius)(CC_MqttsnClientHandle, unsigned) = nullptr; unsigned (*m_get_default_broadcast_radius)(CC_MqttsnClientHandle) = nullptr; unsigned (*m_get_available_gateways_count)(CC_MqttsnClientHandle) = nullptr; + void (*m_init_gateway_info)(CC_MqttsnGatewayInfo* info) = nullptr; CC_MqttsnErrorCode (*m_get_available_gateway_info)(CC_MqttsnClientHandle, unsigned, CC_MqttsnGatewayInfo*) = nullptr; CC_MqttsnErrorCode (*m_set_available_gateway_info)(CC_MqttsnClientHandle, const CC_MqttsnGatewayInfo*) = nullptr; CC_MqttsnErrorCode (*m_discard_available_gateway_info)(CC_MqttsnClientHandle, unsigned char) = nullptr; @@ -96,7 +97,7 @@ class UnitTestCommonBase struct UnitTestGwInfo { - unsigned m_gwId = 0U; + std::uint8_t m_gwId = 0U; UnitTestData m_addr; UnitTestGwInfo() = default; @@ -121,6 +122,7 @@ class UnitTestCommonBase UnitTestGwInfo m_info; UnitTestSearchCompleteReport(CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info); + void assignInfo(CC_MqttsnGatewayInfo& info) const; }; using UnitTestSearchCompleteReportsList = std::list; @@ -152,13 +154,20 @@ class UnitTestCommonBase bool unitTestHasSearchCompleteReport() const; const UnitTestSearchCompleteReport* unitTestSearchCompleteReport(bool mustExist = true) const; - void unitTestPopSearchCompletereport(); + void unitTestPopSearchCompleteReport(); void unitTestSearchSend(CC_MqttsnSearchHandle search, UnitTestSearchCompleteCb&& cb = UnitTestSearchCompleteCb()); void unitTestSearch(CC_MqttsnClient* client, UnitTestSearchCompleteCb&& cb = UnitTestSearchCompleteCb()); + void unitTestSearchUpdateAddr(CC_MqttsnClient* client, const UnitTestData& addr); void apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen); + CC_MqttsnErrorCode apiSetDefaultRetryPeriod(CC_MqttsnClient* client, unsigned value); + CC_MqttsnErrorCode apiSetDefaultRetryCount(CC_MqttsnClient* client, unsigned value); CC_MqttsnSearchHandle apiSearchPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec = nullptr); + CC_MqttsnErrorCode apiSearchSetRetryPeriod(CC_MqttsnSearchHandle search, unsigned value); + CC_MqttsnErrorCode apiSearchSetRetryCount(CC_MqttsnSearchHandle search, unsigned value); + CC_MqttsnErrorCode apiSearchSetBroadcastRadius(CC_MqttsnSearchHandle search, unsigned value); + protected: explicit UnitTestCommonBase(const LibFuncs& funcs); diff --git a/client/lib/test/UnitTestDefaultBase.cpp b/client/lib/test/UnitTestDefaultBase.cpp index 21ef424e..672e1865 100644 --- a/client/lib/test/UnitTestDefaultBase.cpp +++ b/client/lib/test/UnitTestDefaultBase.cpp @@ -16,6 +16,7 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_set_default_broadcast_radius = &cc_mqttsn_client_set_default_broadcast_radius; funcs.m_get_default_broadcast_radius = &cc_mqttsn_client_get_default_broadcast_radius; funcs.m_get_available_gateways_count = &cc_mqttsn_client_get_available_gateways_count; + funcs.m_init_gateway_info = &cc_mqttsn_client_init_gateway_info; funcs.m_get_available_gateway_info = &cc_mqttsn_client_get_available_gateway_info; funcs.m_set_available_gateway_info = &cc_mqttsn_client_set_available_gateway_info; funcs.m_discard_available_gateway_info = &cc_mqttsn_client_discard_available_gateway_info; diff --git a/client/lib/test/UnitTestGwDiscover.th b/client/lib/test/UnitTestGwDiscover.th index 77c69002..1d9e5012 100644 --- a/client/lib/test/UnitTestGwDiscover.th +++ b/client/lib/test/UnitTestGwDiscover.th @@ -11,6 +11,9 @@ public: void test1(); void test2(); void test3(); + void test4(); + void test5(); + void test6(); private: virtual void setUp() override @@ -114,7 +117,6 @@ void UnitTestGwDiscover::test2() void UnitTestGwDiscover::test3() { // Testing search, response from another client - auto clientPtr = unitTestAllocClient(); auto* client = clientPtr.get(); @@ -150,4 +152,176 @@ void UnitTestGwDiscover::test3() TS_ASSERT_EQUALS(gwInfoReport->m_info.m_addr, Addr); TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestGwDiscover::test4() +{ + // Testing search completed by ADVERTISE + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const UnitTestData Addr = {0, 1, 2, 3}; + + unitTestSearchUpdateAddr(client, Addr); + TS_ASSERT(unitTestHasOutputData()); + auto broadcastRadius = unitTestOutputDataInfo()->m_broadcastRadius; + TS_ASSERT_LESS_THAN(0U, broadcastRadius); + + auto sentMsg = unitTestPopOutputMessage(); + auto* searchMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(searchMsg, nullptr); + TS_ASSERT_EQUALS(searchMsg->field_radius().value(), broadcastRadius); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + const std::uint8_t GwId = 1U; + const unsigned AdvDurationMin = 10U; + + UnitTestAdvertiseMsg advertiseMsg; + advertiseMsg.field_gwId().setValue(GwId); + comms::units::setMinutes(advertiseMsg.field_duration(), AdvDurationMin); + unitTestClientInputMessage(client, advertiseMsg); + + TS_ASSERT(unitTestHasSearchCompleteReport()); + auto* searchCompleteReport = unitTestSearchCompleteReport(); + TS_ASSERT_EQUALS(searchCompleteReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(searchCompleteReport->m_info.m_gwId, GwId); + TS_ASSERT(searchCompleteReport->m_info.m_addr.empty()); + + auto* gwInfoReport = unitTestGetGwInfoReport(); + TS_ASSERT_EQUALS(gwInfoReport->m_status, CC_MqttsnGwStatus_AddedByGateway); + TS_ASSERT_EQUALS(gwInfoReport->m_info.m_gwId, GwId); + TS_ASSERT_EQUALS(gwInfoReport->m_info.m_addr, Addr); + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestGwDiscover::test5() +{ + // Testing search without response + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const unsigned RetryPeriod = 2000; + const unsigned BroadcastRadius = 5; + + auto search = apiSearchPrepare(client); + TS_ASSERT_DIFFERS(search, nullptr); + + auto ec = apiSearchSetRetryPeriod(search, RetryPeriod); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + ec = apiSearchSetRetryCount(search, 2U); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + ec = apiSearchSetBroadcastRadius(search, BroadcastRadius); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + unitTestSearchSend(search); + + TS_ASSERT_EQUALS(unitTestOutputDataInfo()->m_broadcastRadius, BroadcastRadius); + + { + auto sentMsg = unitTestPopOutputMessage(); + auto* searchMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(searchMsg, nullptr); + TS_ASSERT_EQUALS(searchMsg->field_radius().value(), BroadcastRadius); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + TS_ASSERT_EQUALS(unitTestTickInfo()->m_req, RetryPeriod); + unitTestTick(client); + + TS_ASSERT(!unitTestHasSearchCompleteReport()); + TS_ASSERT(unitTestHasOutputData()); + + { + auto sentMsg = unitTestPopOutputMessage(); + auto* searchMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(searchMsg, nullptr); + TS_ASSERT_EQUALS(searchMsg->field_radius().value(), BroadcastRadius); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + TS_ASSERT_EQUALS(unitTestTickInfo()->m_req, RetryPeriod); + unitTestTick(client); + + TS_ASSERT(unitTestHasSearchCompleteReport()); + auto* report = unitTestSearchCompleteReport(); + TS_ASSERT_EQUALS(report->m_status, CC_MqttsnAsyncOpStatus_Timeout); + unitTestPopSearchCompleteReport(); + + TS_ASSERT(!unitTestHasTickReq()); +} + + +void UnitTestGwDiscover::test6() +{ + // Testing search with packet loss + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const unsigned RetryPeriod = 2000; + const unsigned BroadcastRadius = 5; + + auto search = apiSearchPrepare(client); + TS_ASSERT_DIFFERS(search, nullptr); + + auto ec = apiSearchSetRetryPeriod(search, RetryPeriod); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + ec = apiSearchSetRetryCount(search, 2U); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + ec = apiSearchSetBroadcastRadius(search, BroadcastRadius); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + unitTestSearchSend(search); + + TS_ASSERT_EQUALS(unitTestOutputDataInfo()->m_broadcastRadius, BroadcastRadius); + + { + auto sentMsg = unitTestPopOutputMessage(); + auto* searchMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(searchMsg, nullptr); + TS_ASSERT_EQUALS(searchMsg->field_radius().value(), BroadcastRadius); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + TS_ASSERT_EQUALS(unitTestTickInfo()->m_req, RetryPeriod); + unitTestTick(client); + + TS_ASSERT(!unitTestHasSearchCompleteReport()); + TS_ASSERT(unitTestHasOutputData()); + + { + auto sentMsg = unitTestPopOutputMessage(); + auto* searchMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(searchMsg, nullptr); + TS_ASSERT_EQUALS(searchMsg->field_radius().value(), BroadcastRadius); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + TS_ASSERT_EQUALS(unitTestTickInfo()->m_req, RetryPeriod); + unitTestTick(client, 100); + + + const std::uint8_t GwId = 1U; + UnitTestGwinfoMsg gwinfoMsg; + gwinfoMsg.field_gwId().setValue(GwId); + unitTestClientInputMessage(client, gwinfoMsg); + + TS_ASSERT(unitTestHasSearchCompleteReport()); + auto* searchCompleteReport = unitTestSearchCompleteReport(); + TS_ASSERT_EQUALS(searchCompleteReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(searchCompleteReport->m_info.m_gwId, GwId); + TS_ASSERT(searchCompleteReport->m_info.m_addr.empty()); + + auto* gwInfoReport = unitTestGetGwInfoReport(); + TS_ASSERT_EQUALS(gwInfoReport->m_status, CC_MqttsnGwStatus_AddedByGateway); + TS_ASSERT_EQUALS(gwInfoReport->m_info.m_gwId, GwId); + TS_ASSERT(gwInfoReport->m_info.m_addr.empty()); + + TS_ASSERT(unitTestHasTickReq()); // For the ADVERTISE } \ No newline at end of file From 18e5c8cb774043b9871e86b39a2c05fc017c5d18 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 14 Jun 2024 08:39:47 +1000 Subject: [PATCH 050/106] Handling pings in the new client. --- client/app/common/AppClient.cpp | 1 + client/lib/CMakeLists.txt | 1 + client/lib/include/cc_mqttsn_client/common.h | 1 + client/lib/src/ClientImpl.cpp | 262 ++++++------------- client/lib/src/ClientImpl.h | 25 +- client/lib/src/ExtConfig.h | 4 +- client/lib/src/SessionState.h | 4 +- client/lib/src/op/ConnectOp.cpp | 20 ++ client/lib/src/op/KeepAliveOp.cpp | 126 +++++++++ client/lib/src/op/KeepAliveOp.h | 57 ++++ client/lib/src/op/Op.h | 2 +- client/lib/templ/client.cpp.templ | 8 +- client/lib/templ/client.h.templ | 8 +- client/lib/test/UnitTestCommonBase.cpp | 12 + client/lib/test/UnitTestCommonBase.h | 13 +- client/lib/test/UnitTestDefaultBase.cpp | 12 + 16 files changed, 347 insertions(+), 209 deletions(-) create mode 100644 client/lib/src/op/KeepAliveOp.cpp create mode 100644 client/lib/src/op/KeepAliveOp.h diff --git a/client/app/common/AppClient.cpp b/client/app/common/AppClient.cpp index 018d6410..9cef20a2 100644 --- a/client/app/common/AppClient.cpp +++ b/client/app/common/AppClient.cpp @@ -294,6 +294,7 @@ std::string AppClient::toString(CC_MqttsnAsyncOpStatus val) /* CC_MqttsnAsyncOpStatus_Aborted */ "Aborted", /* CC_MqttsnAsyncOpStatus_OutOfMemory */ "Out of Memory", /* CC_MqttsnAsyncOpStatus_BadParam */ "Bad Param", + /* CC_MqttsnAsyncOpStatus_GatewayDisconnected */ "Gateway Disconnected", }; static constexpr std::size_t MapSize = std::extent::value; diff --git a/client/lib/CMakeLists.txt b/client/lib/CMakeLists.txt index d312e50d..88e6653f 100644 --- a/client/lib/CMakeLists.txt +++ b/client/lib/CMakeLists.txt @@ -166,6 +166,7 @@ function (gen_lib_mqttsn_client config_file) message (STATUS "Defining library ${lib_name}") set (src src/op/ConnectOp.cpp + src/op/KeepAliveOp.cpp src/op/Op.cpp src/op/SearchOp.cpp src/ClientImpl.cpp diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index 785a5b5a..d154ac26 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -91,6 +91,7 @@ typedef enum CC_MqttsnAsyncOpStatus_Aborted = 3, ///< The operation has been aborted before completion due to client's side operation. CC_MqttsnAsyncOpStatus_OutOfMemory = 4, ///< The client library wasn't able to allocate necessary memory. CC_MqttsnAsyncOpStatus_BadParam = 5, ///< Bad value has been returned from the relevant callback. + CC_MqttsnAsyncOpStatus_GatewayDisconnected = 6, ///< Gateway disconnection detected during the operation execution. CC_MqttsnAsyncOpStatus_ValuesLimit ///< Limit for the values } CC_MqttsnAsyncOpStatus; diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index b28316e3..cb2bb1ee 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -169,6 +169,18 @@ op::ConnectOp* ClientImpl::connectPrepare(CC_MqttsnErrorCode* ec) break; } + if (m_sessionState.m_disconnecting) { + errorLog("Session disconnection is in progress, cannot initiate connection."); + updateEc(ec, CC_MqttsnErrorCode_Disconnecting); + break; + } + + if (m_sessionState.m_connected) { + errorLog("Client is already connected."); + updateEc(ec, CC_MqttsnErrorCode_AlreadyConnected); + break; + } + if (m_ops.max_size() <= m_ops.size()) { errorLog("Cannot start connect operation, retry in next event loop iteration."); updateEc(ec, CC_MqttsnErrorCode_RetryLater); @@ -198,74 +210,6 @@ op::ConnectOp* ClientImpl::connectPrepare(CC_MqttsnErrorCode* ec) return op; } -// op::ConnectOp* ClientImpl::connectPrepare(CC_MqttsnErrorCode* ec) -// { -// op::ConnectOp* connectOp = nullptr; -// do { -// m_clientState.m_networkDisconnected = false; - -// if (!m_clientState.m_initialized) { -// if (m_apiEnterCount > 0U) { -// errorLog("Cannot prepare connect from within callback"); -// updateEc(ec, CC_MqttsnErrorCode_RetryLater); -// break; -// } - -// auto initEc = initInternal(); -// if (initEc != CC_MqttsnErrorCode_Success) { -// updateEc(ec, initEc); -// break; -// } -// } - -// if (!m_connectOps.empty()) { -// // Already allocated -// errorLog("Another connect operation is in progress."); -// updateEc(ec, CC_MqttsnErrorCode_Busy); -// break; -// } - -// if (m_sessionState.m_disconnecting) { -// errorLog("Session disconnection is in progress, cannot initiate connection."); -// updateEc(ec, CC_MqttsnErrorCode_Disconnecting); -// break; -// } - -// if (m_sessionState.m_connected) { -// errorLog("Client is already connected."); -// updateEc(ec, CC_MqttsnErrorCode_AlreadyConnected); -// break; -// } - -// if (m_ops.max_size() <= m_ops.size()) { -// errorLog("Cannot start connect operation, retry in next event loop iteration."); -// updateEc(ec, CC_MqttsnErrorCode_RetryLater); -// break; -// } - -// if (m_preparationLocked) { -// errorLog("Another operation is being prepared, cannot prepare \"connect\" without \"send\" or \"cancel\" of the previous."); -// updateEc(ec, CC_MqttsnErrorCode_PreparationLocked); -// break; -// } - -// auto ptr = m_connectOpAlloc.alloc(*this); -// if (!ptr) { -// errorLog("Cannot allocate new connect operation."); -// updateEc(ec, CC_MqttsnErrorCode_OutOfMemory); -// break; -// } - -// m_preparationLocked = true; -// m_ops.push_back(ptr.get()); -// m_connectOps.push_back(std::move(ptr)); -// connectOp = m_connectOps.back().get(); -// updateEc(ec, CC_MqttsnErrorCode_Success); -// } while (false); - -// return connectOp; -// } - // op::DisconnectOp* ClientImpl::disconnectPrepare(CC_MqttsnErrorCode* ec) // { // op::DisconnectOp* disconnectOp = nullptr; @@ -729,7 +673,7 @@ void ClientImpl::handle(GwinfoMsg& msg) // if (!msg.transportField_flags().field_dup().getBitValue_bit()) { // errorLog("Non duplicate PUBLISH with packet ID in use"); -// brokerDisconnected(CC_MqttsnBrokerDisconnectReason_ProtocolError); +// gatewayDisconnected(CC_MqttsnGatewayDisconnectReason_ProtocolError); // return; // } // else { @@ -800,12 +744,21 @@ void ClientImpl::handle(GwinfoMsg& msg) // #endif // #if CC_MQTTSN_CLIENT_MAX_QOS >= 2 -void ClientImpl::handle(ProtMessage& msg) +void ClientImpl::handle([[maybe_unused]] PingreqMsg& msg) +{ + if ((m_sessionState.m_disconnecting) || (!m_sessionState.m_connected)) { + return; + } + + PingrespMsg respMsg; + sendMessage(respMsg); +} + +void ClientImpl::handle([[maybe_unused]] ProtMessage& msg) { - static_cast(msg); - // if (m_sessionState.m_disconnecting) { - // return; - // } + if (m_sessionState.m_disconnecting) { + return; + } // During the dispatch to callbacks can be called and new ops issues, // the m_ops vector can be resized and iterators invalidated. @@ -826,9 +779,9 @@ void ClientImpl::handle(ProtMessage& msg) // After message dispatching the whole session may be in terminating state // Don't continue iteration - // if (m_sessionState.m_disconnecting) { - // break; - // } + if (m_sessionState.m_disconnecting) { + break; + } } } @@ -853,9 +806,9 @@ CC_MqttsnErrorCode ClientImpl::sendMessage(const ProtMessage& msg, unsigned broa COMMS_ASSERT(m_sendOutputDataCb != nullptr); m_sendOutputDataCb(m_sendOutputDataData, &m_buf[0], static_cast(len), broadcastRadius); - // for (auto& opPtr : m_keepAliveOps) { - // opPtr->messageSent(); - // } + for (auto& opPtr : m_keepAliveOps) { + opPtr->messageSent(); + } return CC_MqttsnErrorCode_Success; } @@ -875,7 +828,7 @@ void ClientImpl::opComplete(const op::Op* op) static const ExtraCompleteFunc Map[] = { /* Type_Search */ &ClientImpl::opComplete_Search, /* Type_Connect */ &ClientImpl::opComplete_Connect, - // /* Type_KeepAlive */ &ClientImpl::opComplete_KeepAlive, + /* Type_KeepAlive */ &ClientImpl::opComplete_KeepAlive, // /* Type_Disconnect */ &ClientImpl::opComplete_Disconnect, // /* Type_Subscribe */ &ClientImpl::opComplete_Subscribe, // /* Type_Unsubscribe */ &ClientImpl::opComplete_Unsubscribe, @@ -895,77 +848,28 @@ void ClientImpl::opComplete(const op::Op* op) (this->*func)(op); } -// void ClientImpl::brokerConnected(bool sessionPresent) -// { -// static_cast(sessionPresent); -// m_clientState.m_firstConnect = false; -// m_sessionState.m_connected = true; - -// do { -// if (sessionPresent) { -// for (auto& sendOpPtr : m_sendOps) { -// sendOpPtr->postReconnectionResend(); -// } - -// for (auto& recvOpPtr : m_recvOps) { -// recvOpPtr->postReconnectionResume(); -// } - -// auto resumeUntilIdx = m_sendOps.size(); -// auto resumeFromIdx = resumeUntilIdx; -// for (auto count = resumeUntilIdx; count > 0U; --count) { -// auto idx = count - 1U; -// auto& sendOpPtr = m_sendOps[idx]; -// if (!sendOpPtr->isPaused()) { -// break; -// } - -// resumeFromIdx = idx; -// } - -// if (resumeFromIdx < resumeUntilIdx) { -// resumeSendOpsSince(static_cast(resumeFromIdx)); -// } - -// break; -// } - -// // Old stored session, terminate pending ops -// for (auto* op : m_ops) { -// auto opType = op->type(); -// if ((opType != op::Op::Type::Type_Send) && -// (opType != op::Op::Type::Type_Recv)) { -// continue; -// } - -// op->terminateOp(CC_MqttsnAsyncOpStatus_Aborted); -// } -// } while (false); - -// createKeepAliveOpIfNeeded(); -// } - -// void ClientImpl::brokerDisconnected( -// CC_MqttsnBrokerDisconnectReason reason, -// CC_MqttsnAsyncOpStatus status) -// { -// m_clientState.m_initialized = false; // Require re-initialization -// m_sessionState.m_connected = false; +void ClientImpl::gatewayConnected() +{ + m_clientState.m_firstConnect = false; + m_sessionState.m_connected = true; + createKeepAliveOpIfNeeded(); +} -// m_sessionState.m_disconnecting = true; -// terminateOps(status, TerminateMode_KeepSendRecvOps); +void ClientImpl::gatewayDisconnected( + CC_MqttsnGatewayDisconnectReason reason, + CC_MqttsnAsyncOpStatus status) +{ + m_clientState.m_initialized = false; // Require re-initialization + m_sessionState.m_connected = false; -// for (auto* op : m_ops) { -// if (op != nullptr) { -// op->connectivityChanged(); -// } -// } + m_sessionState.m_disconnecting = true; + terminateOps(status); -// if (reason < CC_MqttsnBrokerDisconnectReason_ValuesLimit) { -// COMMS_ASSERT(m_brokerDisconnectReportCb != nullptr); -// m_brokerDisconnectReportCb(m_brokerDisconnectReportData, reason); -// } -// } + if (reason < CC_MqttsnGatewayDisconnectReason_ValuesLimit) { + COMMS_ASSERT(m_gatewayDisconnectedReportCb != nullptr); + m_gatewayDisconnectedReportCb(m_gatewayDisconnectedReportData, reason); + } +} // void ClientImpl::reportMsgInfo(const CC_MqttsnMessageInfo& info) // { @@ -1060,40 +964,32 @@ void ClientImpl::doApiExit() m_nextTickProgramCb(m_nextTickProgramData, nextWait); } -// void ClientImpl::createKeepAliveOpIfNeeded() -// { -// if (!m_keepAliveOps.empty()) { -// return; -// } - -// auto ptr = m_keepAliveOpsAlloc.alloc(*this); -// if (!ptr) { -// COMMS_ASSERT(false); // Should not happen -// return; -// } - -// m_ops.push_back(ptr.get()); -// m_keepAliveOps.push_back(std::move(ptr)); -// } +void ClientImpl::createKeepAliveOpIfNeeded() +{ + if (!m_keepAliveOps.empty()) { + return; + } -// void ClientImpl::terminateOps(CC_MqttsnAsyncOpStatus status, TerminateMode mode) -// { -// for (auto* op : m_ops) { -// if (op == nullptr) { -// continue; -// } + auto ptr = m_keepAliveOpsAlloc.alloc(*this); + if (!ptr) { + COMMS_ASSERT(false); // Should not happen + return; + } -// if (mode == TerminateMode_KeepSendRecvOps) { -// auto opType = op->type(); + m_ops.push_back(ptr.get()); + m_keepAliveOps.push_back(std::move(ptr)); +} -// if ((opType == op::Op::Type_Recv) || (opType == op::Op::Type_Send)) { -// continue; -// } -// } +void ClientImpl::terminateOps(CC_MqttsnAsyncOpStatus status) +{ + for (auto* op : m_ops) { + if (op == nullptr) { + continue; + } -// op->terminateOp(status); -// } -// } + op->terminateOp(status); + } +} void ClientImpl::cleanOps() { @@ -1271,10 +1167,10 @@ void ClientImpl::opComplete_Connect(const op::Op* op) eraseFromList(op, m_connectOps); } -// void ClientImpl::opComplete_KeepAlive(const op::Op* op) -// { -// eraseFromList(op, m_keepAliveOps); -// } +void ClientImpl::opComplete_KeepAlive(const op::Op* op) +{ + eraseFromList(op, m_keepAliveOps); +} // void ClientImpl::opComplete_Disconnect(const op::Op* op) // { diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index b1333e73..192e49cd 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -19,7 +19,7 @@ #include "op/ConnectOp.h" // #include "op/DisconnectOp.h" -// #include "op/KeepAliveOp.h" +#include "op/KeepAliveOp.h" #include "op/Op.h" // #include "op/RecvOp.h" #include "op/SearchOp.h" @@ -160,16 +160,17 @@ class ClientImpl final : public ProtMsgHandler // virtual void handle(PubcompMsg& msg) override; // #endif // #if CC_MQTTSN_CLIENT_MAX_QOS >= 2 + virtual void handle(PingreqMsg& msg) override; virtual void handle(ProtMessage& msg) override; // -------------------- Ops Access API ----------------------------- CC_MqttsnErrorCode sendMessage(const ProtMessage& msg, unsigned broadcastRadius = 0); void opComplete(const op::Op* op); - // void brokerConnected(bool sessionPresent); - // void brokerDisconnected( - // CC_MqttsnBrokerDisconnectReason reason = CC_MqttsnBrokerDisconnectReason_ValuesLimit, - // CC_MqttsnAsyncOpStatus status = CC_MqttsnAsyncOpStatus_BrokerDisconnected); + void gatewayConnected(); + void gatewayDisconnected( + CC_MqttsnGatewayDisconnectReason reason = CC_MqttsnGatewayDisconnectReason_ValuesLimit, + CC_MqttsnAsyncOpStatus status = CC_MqttsnAsyncOpStatus_GatewayDisconnected); // void reportMsgInfo(const CC_MqttsnMessageInfo& info); // bool hasPausedSendsBefore(const op::SendOp* sendOp) const; // bool hasHigherQosSendsBefore(const op::SendOp* sendOp, op::Op::Qos qos) const; @@ -236,8 +237,8 @@ class ClientImpl final : public ProtMsgHandler using ConnectOpAlloc = ObjAllocator; using ConnectOpsList = ObjListType; - // using KeepAliveOpAlloc = ObjAllocator; - // using KeepAliveOpsList = ObjListType; + using KeepAliveOpAlloc = ObjAllocator; + using KeepAliveOpsList = ObjListType; // using DisconnectOpAlloc = ObjAllocator; // using DisconnectOpsList = ObjListType; @@ -267,8 +268,8 @@ class ClientImpl final : public ProtMsgHandler void doApiEnter(); void doApiExit(); - // void createKeepAliveOpIfNeeded(); - // void terminateOps(CC_MqttsnAsyncOpStatus status, TerminateMode mode); + void createKeepAliveOpIfNeeded(); + void terminateOps(CC_MqttsnAsyncOpStatus status); void cleanOps(); void errorLogInternal(const char* msg); CC_MqttsnErrorCode initInternal(); @@ -280,7 +281,7 @@ class ClientImpl final : public ProtMsgHandler void opComplete_Search(const op::Op* op); void opComplete_Connect(const op::Op* op); - // void opComplete_KeepAlive(const op::Op* op); + void opComplete_KeepAlive(const op::Op* op); // void opComplete_Disconnect(const op::Op* op); // void opComplete_Subscribe(const op::Op* op); // void opComplete_Unsubscribe(const op::Op* op); @@ -342,8 +343,8 @@ class ClientImpl final : public ProtMsgHandler ConnectOpAlloc m_connectOpAlloc; ConnectOpsList m_connectOps; - // KeepAliveOpAlloc m_keepAliveOpsAlloc; - // KeepAliveOpsList m_keepAliveOps; + KeepAliveOpAlloc m_keepAliveOpsAlloc; + KeepAliveOpsList m_keepAliveOps; // DisconnectOpAlloc m_disconnectOpsAlloc; // DisconnectOpsList m_disconnectOps; diff --git a/client/lib/src/ExtConfig.h b/client/lib/src/ExtConfig.h index 8cf591d7..4a1aa1e3 100644 --- a/client/lib/src/ExtConfig.h +++ b/client/lib/src/ExtConfig.h @@ -33,8 +33,8 @@ struct ExtConfig : public Config static constexpr unsigned SendOpTimers = 1U; static constexpr bool HasOpsLimit = (SearchOpsLimit > 0U) && - (ConnectOpsLimit > 0U) /* && - (KeepAliveOpsLimit > 0U) && + (ConnectOpsLimit > 0U) && + (KeepAliveOpsLimit > 0U) /* && (DisconnectOpsLimit > 0U) && (SubscribeOpsLimit > 0U) && (UnsubscribeOpsLimit > 0U) && diff --git a/client/lib/src/SessionState.h b/client/lib/src/SessionState.h index d79892a3..8e6e0dfc 100644 --- a/client/lib/src/SessionState.h +++ b/client/lib/src/SessionState.h @@ -16,8 +16,8 @@ struct SessionState static constexpr unsigned DefaultKeepAlive = 60; unsigned m_keepAliveMs = 0U; - // bool m_connected = false; - // bool m_disconnecting = false; + bool m_connected = false; + bool m_disconnecting = false; }; } // namespace cc_mqttsn_client diff --git a/client/lib/src/op/ConnectOp.cpp b/client/lib/src/op/ConnectOp.cpp index 3cdcae9d..e4f4049b 100644 --- a/client/lib/src/op/ConnectOp.cpp +++ b/client/lib/src/op/ConnectOp.cpp @@ -45,6 +45,16 @@ CC_MqttsnErrorCode ConnectOp::config(const CC_MqttsnConnectConfig* config) return CC_MqttsnErrorCode_BadParam; } + if (client().clientState().m_firstConnect && (!config->m_cleanSession)) { + errorLog("First connect must force clean session"); + return CC_MqttsnErrorCode_BadParam; + } + + if (config->m_duration == 0U) { + errorLog("The connect duration value must be greater than 0"); + return CC_MqttsnErrorCode_BadParam; + } + if (config->m_clientId != nullptr) { m_connectMsg.field_clientId().value() = config->m_clientId; } @@ -111,6 +121,11 @@ CC_MqttsnErrorCode ConnectOp::send(CC_MqttsnConnectCompleteCb cb, void* cbData) return CC_MqttsnErrorCode_BadParam; } + if (m_connectMsg.field_duration().value() == 0U) { + errorLog("The connect operation hasn't been configured properly"); + return CC_MqttsnErrorCode_InsufficientConfig; + } + if (!m_timer.isValid()) { errorLog("The library cannot allocate required number of timers."); return CC_MqttsnErrorCode_InternalError; @@ -190,6 +205,11 @@ void ConnectOp::handle(ConnackMsg& msg) } #endif // #ifdef CC_MQTTSN_CLIENT_HAS_WILL + if (info.m_returnCode == CC_MqttsnReturnCode_Accepted) { + client().sessionState().m_keepAliveMs = comms::units::getMilliseconds(m_connectMsg.field_duration()); + client().gatewayConnected(); + } + completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &info); } diff --git a/client/lib/src/op/KeepAliveOp.cpp b/client/lib/src/op/KeepAliveOp.cpp new file mode 100644 index 00000000..e2f67c42 --- /dev/null +++ b/client/lib/src/op/KeepAliveOp.cpp @@ -0,0 +1,126 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "op/KeepAliveOp.h" +#include "ClientImpl.h" + +namespace cc_mqttsn_client +{ + +namespace op +{ + +namespace +{ + +inline KeepAliveOp* asKeepAliveOp(void* data) +{ + return reinterpret_cast(data); +} + +} // namespace + +KeepAliveOp::KeepAliveOp(ClientImpl& client) : + Base(client), + m_pingTimer(client.timerMgr().allocTimer()), + m_recvTimer(client.timerMgr().allocTimer()), + m_respTimer(client.timerMgr().allocTimer()) +{ + COMMS_ASSERT(m_pingTimer.isValid()); + COMMS_ASSERT(m_recvTimer.isValid()); + COMMS_ASSERT(m_respTimer.isValid()); + + restartPingTimer(); +} + +void KeepAliveOp::messageSent() +{ + restartPingTimer(); +} + +void KeepAliveOp::handle([[maybe_unused]] PingrespMsg& msg) +{ + m_respTimer.cancel(); + COMMS_ASSERT(!m_respTimer.isActive()); + setRetryCount(client().configState().m_retryCount); + restartRecvTimer(); +} + +void KeepAliveOp::handle([[maybe_unused]] ProtMessage& msg) +{ + restartRecvTimer(); +} + +Op::Type KeepAliveOp::typeImpl() const +{ + return Type_KeepAlive; +} + +void KeepAliveOp::restartPingTimer() +{ + auto& state = client().sessionState(); + if (state.m_keepAliveMs == 0U) { + return; + } + + m_pingTimer.wait(state.m_keepAliveMs, &KeepAliveOp::sendPingCb, this); +} + +void KeepAliveOp::restartRecvTimer() +{ + auto& state = client().sessionState(); + if (state.m_keepAliveMs == 0U) { + return; + } + + m_recvTimer.wait(state.m_keepAliveMs, &KeepAliveOp::recvTimeoutCb, this); +} + +void KeepAliveOp::sendPing() +{ + if (m_respTimer.isActive()) { + return; // Ping has already been sent, waiting for response + } + + PingreqMsg msg; + client().sendMessage(msg); + auto& state = client().configState(); + m_respTimer.wait(state.m_retryPeriod, &KeepAliveOp::pingTimeoutCb, this); +} + +void KeepAliveOp::pingTimeoutInternal() +{ + COMMS_ASSERT(!m_respTimer.isActive()); + auto remRetries = getRetryCount(); + if (remRetries == 0U) { + errorLog("The gateway did not respond to PING(s)"); + client().gatewayDisconnected(CC_MqttsnGatewayDisconnectReason_NoGatewayResponse); + return; + } + + setRetryCount(remRetries - 1U); + sendPing(); +} + +void KeepAliveOp::sendPingCb(void* data) +{ + asKeepAliveOp(data)->sendPing(); +} + +void KeepAliveOp::recvTimeoutCb(void* data) +{ + asKeepAliveOp(data)->sendPing(); +} + +void KeepAliveOp::pingTimeoutCb(void* data) +{ + asKeepAliveOp(data)->pingTimeoutInternal(); +} + +} // namespace op + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/op/KeepAliveOp.h b/client/lib/src/op/KeepAliveOp.h new file mode 100644 index 00000000..d773c52f --- /dev/null +++ b/client/lib/src/op/KeepAliveOp.h @@ -0,0 +1,57 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "op/Op.h" +#include "ExtConfig.h" +#include "ProtocolDefs.h" + +#include "TimerMgr.h" + +namespace cc_mqttsn_client +{ + +namespace op +{ + +class KeepAliveOp final : public Op +{ + using Base = Op; +public: + explicit KeepAliveOp(ClientImpl& client); + + void messageSent(); + + using Base::handle; + virtual void handle(PingrespMsg& msg) override; + virtual void handle(ProtMessage& msg) override; + +protected: + virtual Type typeImpl() const override; + +private: + void restartPingTimer(); + void restartRecvTimer(); + void sendPing(); + void pingTimeoutInternal(); + + static void sendPingCb(void* data); + static void recvTimeoutCb(void* data); + static void pingTimeoutCb(void* data); + + TimerMgr::Timer m_pingTimer; + TimerMgr::Timer m_recvTimer; + TimerMgr::Timer m_respTimer; + + static_assert(ExtConfig::KeepAliveOpTimers == 3U); +}; + +} // namespace op + + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/op/Op.h b/client/lib/src/op/Op.h index d7f76b27..858a22f2 100644 --- a/client/lib/src/op/Op.h +++ b/client/lib/src/op/Op.h @@ -30,7 +30,7 @@ class Op : public ProtMsgHandler { Type_Search, Type_Connect, - // Type_KeepAlive, + Type_KeepAlive, // Type_Disconnect, // Type_Subscribe, // Type_Unsubscribe, diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index 7e7e501b..a9d7f01b 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -522,14 +522,14 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect_cancel(CC_MqttsnConnectHandl } CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect( - CC_MqttsnClientHandle handle, - CC_MqttsnConnectConfig* config, - CC_MqttsnWillConfig* willConfig, + CC_MqttsnClientHandle client, + const CC_MqttsnConnectConfig* config, + const CC_MqttsnWillConfig* willConfig, CC_MqttsnConnectCompleteCb cb, void* cbData) { auto ec = CC_MqttsnErrorCode_Success; - auto connect = cc_mqttsn_##NAME##client_connect_prepare(handle, &ec); + auto connect = cc_mqttsn_##NAME##client_connect_prepare(client, &ec); if (connect == nullptr) { return ec; } diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index c7e7f52b..d8e76042 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -371,7 +371,7 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect_cancel(CC_MqttsnConnectHandl /// @li @ref cc_mqttsn_##NAME##client_connect_config_will() /// @li @ref cc_mqttsn_##NAME##client_connect_send() /// -/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[in] config Connection configuration. Can be NULL. /// @param[in] willConfig Will configuration. Can be NULL. /// @param[in] cb Callback to be invoked when "connect" operation is complete. @@ -380,9 +380,9 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect_cancel(CC_MqttsnConnectHandl /// @return Result code of the call. /// @ingroup connect CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect( - CC_MqttsnClientHandle handle, - CC_MqttsnConnectConfig* config, - CC_MqttsnWillConfig* willConfig, + CC_MqttsnClientHandle client, + const CC_MqttsnConnectConfig* config, + const CC_MqttsnWillConfig* willConfig, CC_MqttsnConnectCompleteCb cb, void* cbData); diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index 9720194c..14ba906b 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -54,6 +54,18 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_search_send != nullptr); test_assert(m_funcs.m_search_cancel != nullptr); test_assert(m_funcs.m_search != nullptr); + test_assert(m_funcs.m_connect_prepare != nullptr); + test_assert(m_funcs.m_connect_set_retry_period != nullptr); + test_assert(m_funcs.m_connect_get_retry_period != nullptr); + test_assert(m_funcs.m_connect_set_retry_count != nullptr); + test_assert(m_funcs.m_connect_get_retry_count != nullptr); + test_assert(m_funcs.m_connect_init_config != nullptr); + test_assert(m_funcs.m_connect_config != nullptr); + test_assert(m_funcs.m_connect_init_config_will != nullptr); + test_assert(m_funcs.m_connect_config_will != nullptr); + test_assert(m_funcs.m_connect_send != nullptr); + test_assert(m_funcs.m_connect_cancel != nullptr); + test_assert(m_funcs.m_connect != nullptr); test_assert(m_funcs.m_set_next_tick_program_callback != nullptr); test_assert(m_funcs.m_set_cancel_next_tick_wait_callback != nullptr); diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index 5bbbae77..bcef9630 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -44,8 +44,19 @@ class UnitTestCommonBase CC_MqttsnErrorCode (*m_search_send)(CC_MqttsnSearchHandle, CC_MqttsnSearchCompleteCb, void*) = nullptr; CC_MqttsnErrorCode (*m_search_cancel)(CC_MqttsnSearchHandle) = nullptr; CC_MqttsnErrorCode (*m_search)(CC_MqttsnClientHandle, CC_MqttsnSearchCompleteCb, void*) = nullptr; + CC_MqttsnConnectHandle (*m_connect_prepare)(CC_MqttsnClientHandle, CC_MqttsnErrorCode*) = nullptr; + CC_MqttsnErrorCode (*m_connect_set_retry_period)(CC_MqttsnConnectHandle, unsigned ms) = nullptr; + unsigned (*m_connect_get_retry_period)(CC_MqttsnConnectHandle) = nullptr; + CC_MqttsnErrorCode (*m_connect_set_retry_count)(CC_MqttsnConnectHandle, unsigned count) = nullptr; + unsigned (*m_connect_get_retry_count)(CC_MqttsnConnectHandle) = nullptr; + void (*m_connect_init_config)(CC_MqttsnConnectConfig* config) = nullptr; + CC_MqttsnErrorCode (*m_connect_config)(CC_MqttsnConnectHandle, const CC_MqttsnConnectConfig*) = nullptr; + void (*m_connect_init_config_will)(CC_MqttsnWillConfig*) = nullptr; + CC_MqttsnErrorCode (*m_connect_config_will)(CC_MqttsnConnectHandle, const CC_MqttsnWillConfig*) = nullptr; + CC_MqttsnErrorCode (*m_connect_send)(CC_MqttsnConnectHandle, CC_MqttsnConnectCompleteCb, void*) = nullptr; + CC_MqttsnErrorCode (*m_connect_cancel)(CC_MqttsnConnectHandle) = nullptr; + CC_MqttsnErrorCode (*m_connect)(CC_MqttsnClientHandle, const CC_MqttsnConnectConfig*, const CC_MqttsnWillConfig*, CC_MqttsnConnectCompleteCb, void*) = nullptr; - void (*m_set_next_tick_program_callback)(CC_MqttsnClientHandle, CC_MqttsnNextTickProgramCb, void*) = nullptr; void (*m_set_cancel_next_tick_wait_callback)(CC_MqttsnClientHandle, CC_MqttsnCancelNextTickWaitCb, void*) = nullptr; void (*m_set_send_output_data_callback)(CC_MqttsnClientHandle, CC_MqttsnSendOutputDataCb, void*) = nullptr; diff --git a/client/lib/test/UnitTestDefaultBase.cpp b/client/lib/test/UnitTestDefaultBase.cpp index 672e1865..bb133a0c 100644 --- a/client/lib/test/UnitTestDefaultBase.cpp +++ b/client/lib/test/UnitTestDefaultBase.cpp @@ -35,6 +35,18 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_search_send = &cc_mqttsn_client_search_send; funcs.m_search_cancel = &cc_mqttsn_client_search_cancel; funcs.m_search = &cc_mqttsn_client_search; + funcs.m_connect_prepare = &cc_mqttsn_client_connect_prepare; + funcs.m_connect_set_retry_period = &cc_mqttsn_client_connect_set_retry_period; + funcs.m_connect_get_retry_period = &cc_mqttsn_client_connect_get_retry_period; + funcs.m_connect_set_retry_count = &cc_mqttsn_client_connect_set_retry_count; + funcs.m_connect_get_retry_count = &cc_mqttsn_client_connect_get_retry_count; + funcs.m_connect_init_config = &cc_mqttsn_client_connect_init_config; + funcs.m_connect_config = &cc_mqttsn_client_connect_config; + funcs.m_connect_init_config_will = &cc_mqttsn_client_connect_init_config_will; + funcs.m_connect_config_will = &cc_mqttsn_client_connect_config_will; + funcs.m_connect_send = &cc_mqttsn_client_connect_send; + funcs.m_connect_cancel = &cc_mqttsn_client_connect_cancel; + funcs.m_connect = &cc_mqttsn_client_connect; funcs.m_set_next_tick_program_callback = &cc_mqttsn_client_set_next_tick_program_callback; funcs.m_set_cancel_next_tick_wait_callback = &cc_mqttsn_client_set_cancel_next_tick_wait_callback; From d1c483bf38607e2c6c59cda5b8905672ad6366a1 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Mon, 17 Jun 2024 09:27:28 +1000 Subject: [PATCH 051/106] Supporing PINGREQ from gateway to client when no data from client for too long. --- gateway/lib/src/CMakeLists.txt | 1 + gateway/lib/src/SessionImpl.cpp | 9 ++ gateway/lib/src/SessionImpl.h | 2 + gateway/lib/src/SessionOp.cpp | 4 + gateway/lib/src/SessionOp.h | 6 + gateway/lib/src/session_op/Asleep.cpp | 3 + gateway/lib/src/session_op/Asleep.h | 2 +- gateway/lib/src/session_op/AsleepMonitor.cpp | 6 +- gateway/lib/src/session_op/AsleepMonitor.h | 4 +- gateway/lib/src/session_op/Connect.cpp | 4 + gateway/lib/src/session_op/Connect.h | 2 +- gateway/lib/src/session_op/Disconnect.cpp | 3 + gateway/lib/src/session_op/Disconnect.h | 2 +- gateway/lib/src/session_op/Encapsulate.h | 2 +- gateway/lib/src/session_op/Forward.h | 2 +- gateway/lib/src/session_op/Ping.cpp | 70 +++++++++ gateway/lib/src/session_op/Ping.h | 47 ++++++ gateway/lib/src/session_op/PubRecv.h | 2 +- gateway/lib/src/session_op/PubSend.h | 2 +- gateway/lib/src/session_op/WillUpdate.h | 2 +- gateway/lib/test/Session.th | 144 ++++++++++++++++++- 21 files changed, 303 insertions(+), 16 deletions(-) create mode 100644 gateway/lib/src/session_op/Ping.cpp create mode 100644 gateway/lib/src/session_op/Ping.h diff --git a/gateway/lib/src/CMakeLists.txt b/gateway/lib/src/CMakeLists.txt index 0936d8c8..edf771b0 100644 --- a/gateway/lib/src/CMakeLists.txt +++ b/gateway/lib/src/CMakeLists.txt @@ -18,6 +18,7 @@ function (lib_mqttsn_gateway) session_op/AsleepMonitor.cpp session_op/Encapsulate.cpp session_op/Forward.cpp + session_op/Ping.cpp session_op/PubRecv.cpp session_op/PubSend.cpp session_op/WillUpdate.cpp diff --git a/gateway/lib/src/SessionImpl.cpp b/gateway/lib/src/SessionImpl.cpp index 2258c7a6..d8c667ef 100644 --- a/gateway/lib/src/SessionImpl.cpp +++ b/gateway/lib/src/SessionImpl.cpp @@ -19,6 +19,7 @@ #include "session_op/Asleep.h" #include "session_op/AsleepMonitor.h" #include "session_op/Encapsulate.h" +#include "session_op/Ping.h" #include "session_op/PubRecv.h" #include "session_op/PubSend.h" #include "session_op/Forward.h" @@ -52,6 +53,7 @@ SessionImpl::SessionImpl() m_ops.emplace_back(new session_op::PubSend(*this)); m_ops.emplace_back(new session_op::Forward(*this)); m_ops.emplace_back(new session_op::WillUpdate(*this)); + m_ops.emplace_back(new session_op::Ping(*this)); m_ops.emplace_back(new session_op::Encapsulate(*this)); m_encapsulateOp = static_cast(m_ops.back().get()); @@ -266,6 +268,13 @@ void SessionImpl::clientConnectedReport(const std::string& clientId) } } +void SessionImpl::connStatusUpdated() +{ + for (auto& op : m_ops) { + op->connStatusUpdated(); + } +} + SessionImpl::AuthInfo SessionImpl::authInfoRequest(const std::string& clientId) { if (!m_authInfoReqCb) { diff --git a/gateway/lib/src/SessionImpl.h b/gateway/lib/src/SessionImpl.h index b3e2465f..efb200ae 100644 --- a/gateway/lib/src/SessionImpl.h +++ b/gateway/lib/src/SessionImpl.h @@ -17,6 +17,7 @@ #include "MsgHandler.h" #include "SessionOp.h" #include "session_op/Encapsulate.h" +#include "session_op/Ping.h" #include "common.h" #include "comms/util/ScopeGuard.h" @@ -216,6 +217,7 @@ class SessionImpl : public MsgHandler void termRequest(); void brokerReconnectRequest(); void clientConnectedReport(const std::string& clientId); + void connStatusUpdated(); AuthInfo authInfoRequest(const std::string& clientId); void reportError(const char* str); diff --git a/gateway/lib/src/SessionOp.cpp b/gateway/lib/src/SessionOp.cpp index 643fd4ce..525466e4 100644 --- a/gateway/lib/src/SessionOp.cpp +++ b/gateway/lib/src/SessionOp.cpp @@ -81,4 +81,8 @@ void SessionOp::brokerConnectionUpdatedImpl() { } +void SessionOp::connStatusUpdatedImpl() +{ +} + } // namespace cc_mqttsn_gateway diff --git a/gateway/lib/src/SessionOp.h b/gateway/lib/src/SessionOp.h index 2d7cc724..4b1b4c6d 100644 --- a/gateway/lib/src/SessionOp.h +++ b/gateway/lib/src/SessionOp.h @@ -42,6 +42,11 @@ class SessionOp : public MsgHandler brokerConnectionUpdatedImpl(); } + void connStatusUpdated() + { + connStatusUpdatedImpl(); + } + protected: SessionOp(SessionImpl& session) : m_session(session) @@ -75,6 +80,7 @@ class SessionOp : public MsgHandler virtual void tickImpl(); virtual void startImpl(); virtual void brokerConnectionUpdatedImpl(); + virtual void connStatusUpdatedImpl(); private: SessionImpl& m_session; diff --git a/gateway/lib/src/session_op/Asleep.cpp b/gateway/lib/src/session_op/Asleep.cpp index da98e862..99f09b1c 100644 --- a/gateway/lib/src/session_op/Asleep.cpp +++ b/gateway/lib/src/session_op/Asleep.cpp @@ -7,6 +7,8 @@ #include "Asleep.h" +#include "SessionImpl.h" + #include #include @@ -52,6 +54,7 @@ void Asleep::handle(DisconnectMsg_SN& msg) sendDisconnectToClient(); state().m_connStatus = ConnectionStatus::Asleep; + session().connStatusUpdated(); m_attempt = 0; doPing(); } diff --git a/gateway/lib/src/session_op/Asleep.h b/gateway/lib/src/session_op/Asleep.h index 111a0931..a3e6c705 100644 --- a/gateway/lib/src/session_op/Asleep.h +++ b/gateway/lib/src/session_op/Asleep.h @@ -16,7 +16,7 @@ namespace cc_mqttsn_gateway namespace session_op { -class Asleep : public SessionOp +class Asleep final : public SessionOp { typedef SessionOp Base; diff --git a/gateway/lib/src/session_op/AsleepMonitor.cpp b/gateway/lib/src/session_op/AsleepMonitor.cpp index f33cbb28..b47d0f38 100644 --- a/gateway/lib/src/session_op/AsleepMonitor.cpp +++ b/gateway/lib/src/session_op/AsleepMonitor.cpp @@ -46,7 +46,7 @@ void AsleepMonitor::handle(DisconnectMsg_SN& msg) m_lastPing = state().m_timestamp; m_duration = ((static_cast(msg.field_duration().field().value()) * 3000) / 2); - reqNextTick(); + reqNextTickInternal(); } void AsleepMonitor::handle([[maybe_unused]] PingreqMsg_SN& msg) @@ -54,7 +54,7 @@ void AsleepMonitor::handle([[maybe_unused]] PingreqMsg_SN& msg) m_lastPing = state().m_timestamp; cancelTick(); if (state().m_connStatus == ConnectionStatus::Asleep) { - reqNextTick(); + reqNextTickInternal(); } } @@ -75,7 +75,7 @@ void AsleepMonitor::checkTickRequired() } } -void AsleepMonitor::reqNextTick() +void AsleepMonitor::reqNextTickInternal() { assert(0 < m_lastPing); diff --git a/gateway/lib/src/session_op/AsleepMonitor.h b/gateway/lib/src/session_op/AsleepMonitor.h index 7dc05b9d..c7e74b6c 100644 --- a/gateway/lib/src/session_op/AsleepMonitor.h +++ b/gateway/lib/src/session_op/AsleepMonitor.h @@ -16,7 +16,7 @@ namespace cc_mqttsn_gateway namespace session_op { -class AsleepMonitor : public SessionOp +class AsleepMonitor final : public SessionOp { typedef SessionOp Base; @@ -35,7 +35,7 @@ class AsleepMonitor : public SessionOp virtual void handle(MqttMessage& msg) override; void checkTickRequired(); - void reqNextTick(); + void reqNextTickInternal(); Timestamp m_lastPing = 0; unsigned m_duration = 0U; diff --git a/gateway/lib/src/session_op/Connect.cpp b/gateway/lib/src/session_op/Connect.cpp index ae67013b..44aa073b 100644 --- a/gateway/lib/src/session_op/Connect.cpp +++ b/gateway/lib/src/session_op/Connect.cpp @@ -58,6 +58,7 @@ void Connect::handle(ConnectMsg_SN& msg) session().reportError("Different client id reconnection in the same session"); sendDisconnectToClient(); state().m_connStatus = ConnectionStatus::Disconnected; + session().connStatusUpdated(); termRequest(); return; } @@ -191,6 +192,7 @@ void Connect::doNextStep() if (state().m_retryCount <= m_internalState.m_attempt) { m_clientId.clear(); state().m_connStatus = ConnectionStatus::Disconnected; + session().connStatusUpdated(); return; } @@ -372,6 +374,7 @@ void Connect::processAck(ConnackMsg::Field_returnCode::ValueType respCode) sessionState.m_username = std::move(m_authInfo.first); sessionState.m_password = std::move(m_authInfo.second); clearInternalState(); + session().connStatusUpdated(); if (m_clean) { sessionState.m_regMgr.clearRegistrations(); @@ -389,6 +392,7 @@ void Connect::clearConnectionInfo(bool clearClientId) sessionState.m_connStatus = ConnectionStatus::Disconnected; sessionState.m_keepAlive = 0; sessionState.m_will = WillInfo(); + session().connStatusUpdated(); } void Connect::clearInternalState() diff --git a/gateway/lib/src/session_op/Connect.h b/gateway/lib/src/session_op/Connect.h index f22877f4..0593bc87 100644 --- a/gateway/lib/src/session_op/Connect.h +++ b/gateway/lib/src/session_op/Connect.h @@ -17,7 +17,7 @@ namespace cc_mqttsn_gateway namespace session_op { -class Connect : public SessionOp +class Connect final : public SessionOp { using Base = SessionOp; diff --git a/gateway/lib/src/session_op/Disconnect.cpp b/gateway/lib/src/session_op/Disconnect.cpp index a690c2b8..ac416e96 100644 --- a/gateway/lib/src/session_op/Disconnect.cpp +++ b/gateway/lib/src/session_op/Disconnect.cpp @@ -7,6 +7,8 @@ #include "Disconnect.h" +#include "SessionImpl.h" + #include namespace cc_mqttsn_gateway @@ -57,6 +59,7 @@ void Disconnect::sendDisconnectSn() { Base::sendDisconnectToClient(); state().m_connStatus = ConnectionStatus::Disconnected; + session().connStatusUpdated(); } } // namespace session_op diff --git a/gateway/lib/src/session_op/Disconnect.h b/gateway/lib/src/session_op/Disconnect.h index d38e161f..da996e4a 100644 --- a/gateway/lib/src/session_op/Disconnect.h +++ b/gateway/lib/src/session_op/Disconnect.h @@ -16,7 +16,7 @@ namespace cc_mqttsn_gateway namespace session_op { -class Disconnect : public SessionOp +class Disconnect final : public SessionOp { typedef SessionOp Base; diff --git a/gateway/lib/src/session_op/Encapsulate.h b/gateway/lib/src/session_op/Encapsulate.h index 1055e54a..db22cd0f 100644 --- a/gateway/lib/src/session_op/Encapsulate.h +++ b/gateway/lib/src/session_op/Encapsulate.h @@ -24,7 +24,7 @@ namespace cc_mqttsn_gateway namespace session_op { -class Encapsulate : public SessionOp +class Encapsulate final : public SessionOp { using Base = SessionOp; diff --git a/gateway/lib/src/session_op/Forward.h b/gateway/lib/src/session_op/Forward.h index a2fec114..88f64f33 100644 --- a/gateway/lib/src/session_op/Forward.h +++ b/gateway/lib/src/session_op/Forward.h @@ -19,7 +19,7 @@ namespace cc_mqttsn_gateway namespace session_op { -class Forward : public SessionOp +class Forward final : public SessionOp { typedef SessionOp Base; diff --git a/gateway/lib/src/session_op/Ping.cpp b/gateway/lib/src/session_op/Ping.cpp new file mode 100644 index 00000000..00924386 --- /dev/null +++ b/gateway/lib/src/session_op/Ping.cpp @@ -0,0 +1,70 @@ +// +// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "Ping.h" + +#include + +namespace cc_mqttsn_gateway +{ + +namespace session_op +{ + +Ping::Ping(SessionImpl& session) : + Base(session) +{ +} + +Ping::~Ping() = default; + +void Ping::tickImpl() +{ + sendPing(); +} + +void Ping::connStatusUpdatedImpl() +{ + restartPingTimer(); +} + +void Ping::handle([[maybe_unused]] MqttsnMessage& msg) +{ + restartPingTimer(); +} + +void Ping::sendPing() +{ + auto& st = state(); + + if (st.m_retryCount <= m_attempt) { + termRequest(); + return; + } + + ++m_attempt; + sendToClient(PingreqMsg_SN()); + nextTickReq(st.m_retryPeriod); +} + +void Ping::restartPingTimer() +{ + auto& st = state(); + if (st.m_connStatus != ConnectionStatus::Connected) { + cancelTick(); + return; + } + + m_attempt = 0U; + assert(0U < st.m_keepAlive); + auto nextPingTick = (st.m_keepAlive * 1000); + nextTickReq(nextPingTick); +} + +} // namespace session_op + +} // namespace cc_mqttsn_gateway diff --git a/gateway/lib/src/session_op/Ping.h b/gateway/lib/src/session_op/Ping.h new file mode 100644 index 00000000..552a8966 --- /dev/null +++ b/gateway/lib/src/session_op/Ping.h @@ -0,0 +1,47 @@ +// +// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "SessionOp.h" +#include "common.h" + +namespace cc_mqttsn_gateway +{ + +namespace session_op +{ + +class Ping final : public SessionOp +{ + typedef SessionOp Base; + +public: + explicit Ping(SessionImpl& session); + ~Ping(); + + void clientConnected(); + +protected: + virtual void tickImpl() override; + virtual void connStatusUpdatedImpl() override; + +private: + using Base::handle; + virtual void handle(MqttsnMessage& msg) override; + + void sendPing(); + void restartPingTimer(); + + unsigned m_attempt = 0; +}; + +} // namespace session_op + +} // namespace cc_mqttsn_gateway + + diff --git a/gateway/lib/src/session_op/PubRecv.h b/gateway/lib/src/session_op/PubRecv.h index f989f061..69e054a8 100644 --- a/gateway/lib/src/session_op/PubRecv.h +++ b/gateway/lib/src/session_op/PubRecv.h @@ -16,7 +16,7 @@ namespace cc_mqttsn_gateway namespace session_op { -class PubRecv : public SessionOp +class PubRecv final : public SessionOp { typedef SessionOp Base; diff --git a/gateway/lib/src/session_op/PubSend.h b/gateway/lib/src/session_op/PubSend.h index 72f71300..92d473d3 100644 --- a/gateway/lib/src/session_op/PubSend.h +++ b/gateway/lib/src/session_op/PubSend.h @@ -16,7 +16,7 @@ namespace cc_mqttsn_gateway namespace session_op { -class PubSend : public SessionOp +class PubSend final : public SessionOp { typedef SessionOp Base; diff --git a/gateway/lib/src/session_op/WillUpdate.h b/gateway/lib/src/session_op/WillUpdate.h index 31d028fc..67e49b58 100644 --- a/gateway/lib/src/session_op/WillUpdate.h +++ b/gateway/lib/src/session_op/WillUpdate.h @@ -16,7 +16,7 @@ namespace cc_mqttsn_gateway namespace session_op { -class WillUpdate : public SessionOp +class WillUpdate final : public SessionOp { typedef SessionOp Base; diff --git a/gateway/lib/test/Session.th b/gateway/lib/test/Session.th index d90d82a2..29b4eaee 100644 --- a/gateway/lib/test/Session.th +++ b/gateway/lib/test/Session.th @@ -435,6 +435,10 @@ private: return; } + if (value == 0U) { + return; + } + TS_ASSERT_EQUALS(state.m_tickReq.front(), value); } @@ -1422,7 +1426,7 @@ private: } } - static void verifyTickReq(State& state, unsigned value) + static void verifyTickReq(State& state, unsigned value = 0U) { checkTickValue(state, value); if (!state.m_tickReq.empty()) { @@ -1540,6 +1544,7 @@ private: if (expectClientReport) { verifyConnectedClient(state, DefaultClientId); } + verifyTickReq(state, DefaultKeepAlivePeriod * 1000); verifyNoOtherEvent(state, handler); } @@ -1590,6 +1595,7 @@ void SessionTest::test2() dataFromBroker(*session, connackMsg, "CONNACK"); verifySentToClient_ConnackMsg(state, handler, cc_mqttsn::field::ReturnCodeVal::Accepted); verifyConnectedClient(state, ClientId); + verifyTickReq(state, KeepAlive * 1000); verifyNoOtherEvent(state, handler); } @@ -1616,12 +1622,17 @@ void SessionTest::test3() dataFromBroker(*session, connackMsg, "CONNACK"); verifySentToClient_ConnackMsg(state, handler, cc_mqttsn::field::ReturnCodeVal::Accepted); verifyConnectedClient(state, ClientId); + verifyTickReq(state, KeepAlive * 1000); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(500); + dataFromClient(*session, connectMsg, "CONNECT"); verifySentToClient_ConnackMsg(state, handler, cc_mqttsn::field::ReturnCodeVal::Accepted); + verifyTickReq(state, KeepAlive * 1000); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(500); static const std::uint16_t KeepAlive2 = 120; static const bool CleanSession2 = false; @@ -1645,8 +1656,10 @@ void SessionTest::test3() dataFromBroker(*session, connackMsg, "CONNACK"); verifySentToClient_ConnackMsg(state, handler, cc_mqttsn::field::ReturnCodeVal::Accepted); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(500); static const std::string ClientId2 = "blablabla"; auto connectMsg3 = handler.prepareClientConnect(ClientId2, KeepAlive, false, CleanSession); dataFromClient(*session, connectMsg3, "CONNECT"); @@ -1707,6 +1720,8 @@ void SessionTest::test5() auto session = allocSession(state, handler); doConnect(*session, state, handler); + + state.m_elapsed.push_back(1000); doBrokerDisconnect(*session); verifySentToClient_DisconnectMsg(state, handler); verifyTermReq(state); @@ -1721,6 +1736,8 @@ void SessionTest::test6() doConnect(*session, state, handler); + state.m_elapsed.push_back(1000); + static const std::uint16_t SleepDuration = 30 * 60; auto disconnectSnMsg = handler.prepareClientDisconnect(SleepDuration); @@ -1760,6 +1777,7 @@ void SessionTest::test7() auto session = allocSession(state, handler); doConnect(*session, state, handler); + state.m_elapsed.push_back(1000); static const std::uint16_t SleepDuration = 30 * 60; @@ -1782,6 +1800,7 @@ void SessionTest::test7() dataFromClient(*session, connectMsg, "CONNECT"); verifySentToClient_ConnackMsg(state, handler, cc_mqttsn::field::ReturnCodeVal::Accepted); verifySentToBroker_PingreqMsg(state, handler); + verifyTickReq(state); verifyNoOtherEvent(state, handler); } @@ -1802,6 +1821,7 @@ void SessionTest::test8() willInfo.m_retain = WillRetain; doConnect(*session, state, handler, &willInfo); + state.m_elapsed.push_back(1000); static const std::uint16_t SleepDuration = 30 * 60; @@ -1843,6 +1863,7 @@ void SessionTest::test8() auto connackMsg = handler.prepareBrokerConnack(ConnackResponseCodeVal::Accepted); dataFromBroker(*session, connackMsg, "CONNACK"); verifySentToClient_ConnackMsg(state, handler, cc_mqttsn::field::ReturnCodeVal::Accepted); + verifyTickReq(state); verifyNoOtherEvent(state, handler); } @@ -1853,6 +1874,7 @@ void SessionTest::test9() auto session = allocSession(state, handler); doConnect(*session, state, handler); + state.m_elapsed.push_back(1000); static const std::string Topic("this/is/topic"); static const std::uint16_t MsgId = 0x1122; @@ -1862,13 +1884,16 @@ void SessionTest::test9() verifySentToBroker_PingreqMsg(state, handler); TS_ASSERT_LESS_THAN_EQUALS(DefaultMinTopicId, topicId); TS_ASSERT_LESS_THAN_EQUALS(topicId, DefaultMaxTopicId); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); auto registerMsg2 = handler.prepareClientRegister(Topic, static_cast(MsgId + 1)); dataFromClient(*session, registerMsg2, "REGISTER"); auto topicId2 = verifySentToClient_RegackMsg(state, handler, static_cast(MsgId + 1), cc_mqttsn::field::ReturnCodeVal::Accepted); verifySentToBroker_PingreqMsg(state, handler); TS_ASSERT_EQUALS(topicId, topicId2); + verifyTickReq(state); verifyNoOtherEvent(state, handler); } @@ -1883,6 +1908,7 @@ void SessionTest::test10() session->addPredefinedTopic(Topic1, TopicId1); doConnect(*session, state, handler); + state.m_elapsed.push_back(1000); static const std::string Topic2("this/is/topic2"); static const std::uint16_t MsgId = 0x1122; @@ -1893,13 +1919,16 @@ void SessionTest::test10() TS_ASSERT_LESS_THAN_EQUALS(DefaultMinTopicId, topicId); TS_ASSERT_LESS_THAN_EQUALS(topicId, DefaultMaxTopicId); TS_ASSERT_DIFFERS(topicId, TopicId1); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); auto registerMsg2 = handler.prepareClientRegister(Topic1, static_cast(MsgId + 1)); dataFromClient(*session, registerMsg2, "REGISTER"); auto topicId2 = verifySentToClient_RegackMsg(state, handler, static_cast(MsgId + 1), cc_mqttsn::field::ReturnCodeVal::Accepted); verifySentToBroker_PingreqMsg(state, handler); TS_ASSERT_EQUALS(topicId2, TopicId1); + verifyTickReq(state); verifyNoOtherEvent(state, handler); } @@ -1914,6 +1943,7 @@ void SessionTest::test11() session->addPredefinedTopic(Topic, TopicId); doConnect(*session, state, handler); + state.m_elapsed.push_back(1000); static const DataBuf Data = {0, 1, 2, 3, 4 }; static const std::uint16_t MsgId = 1234; @@ -1923,6 +1953,7 @@ void SessionTest::test11() auto publishMsg = handler.prepareBrokerPublish(Topic, Data, MsgId, Qos, Retain, Dup); dataFromBroker(*session, publishMsg, "PUBLISH"); verifySentToClient_PublishMsg(state, handler, TopicId, Data, TopicIdTypeVal::PredefinedTopicId, translateQos(Qos), Retain, Dup); + verifyTickReq(state); verifyNoOtherEvent(state, handler); } @@ -1937,6 +1968,7 @@ void SessionTest::test12() session->addPredefinedTopic(Topic, TopicId); doConnect(*session, state, handler); + state.m_elapsed.push_back(1000); static const DataBuf Data = {0, 1, 2, 3, 4 }; static const std::uint16_t MsgId = 1234; @@ -1978,6 +2010,7 @@ void SessionTest::test13() auto session = allocSession(state, handler); doConnect(*session, state, handler); + state.m_elapsed.push_back(1000); static const std::string Topic("topic/bla/bla"); static const DataBuf Data = {0, 1, 2, 3, 4}; @@ -1988,8 +2021,10 @@ void SessionTest::test13() auto publishMsg = handler.prepareBrokerPublish(Topic, Data, MsgId, Qos, Retain, false); dataFromBroker(*session, publishMsg, "PUBLISH"); verifySentToBroker_PubrecMsg(state, handler, MsgId); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); auto pubrelMsg = handler.prepareBrokerPubrel(MsgId); dataFromBroker(*session, pubrelMsg, "PUBREL"); verifySentToBroker_PubcompMsg(state, handler, MsgId); @@ -2038,6 +2073,7 @@ void SessionTest::test13() state.m_elapsed.push_back(1000); pubcompMsg = handler.prepareClientPubcomp(pubMsgId); dataFromClient(*session, pubcompMsg, "PUBCOMP"); + verifyTickReq(state); verifyNoOtherEvent(state, handler); } @@ -2048,6 +2084,7 @@ void SessionTest::test14() auto session = allocSession(state, handler); doConnect(*session, state, handler); + state.m_elapsed.push_back(1000); static const std::string Topic("topic/bla/bla"); static const DataBuf Data = {0, 1, 2, 3, 4}; @@ -2067,12 +2104,16 @@ void SessionTest::test14() auto regackMsg = handler.prepareClientRegack(topicId, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); dataFromClient(*session, regackMsg, "REGACK"); verifySentToClient_PublishMsg(state, handler, topicId, Data, TopicIdTypeVal::Normal, translateQos(Qos), Retain, false); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); dataFromBroker(*session, publishMsg, "PUBLISH"); verifySentToClient_PublishMsg(state, handler, topicId, Data, TopicIdTypeVal::Normal, translateQos(Qos), Retain, false); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); static const auto Qos2 = cc_mqtt311::field::QosVal::AtLeastOnceDelivery; publishMsg = handler.prepareBrokerPublish(Topic, Data, MsgId, Qos2, Retain, false); dataFromBroker(*session, publishMsg, "PUBLISH"); @@ -2098,6 +2139,7 @@ void SessionTest::test14() state.m_elapsed.push_back(1000); pubackMsg = handler.prepareClientPuback(topicId, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); dataFromClient(*session, pubackMsg, "PUBACK"); + verifyTickReq(state); verifyNoOtherEvent(state, handler); } @@ -2108,6 +2150,7 @@ void SessionTest::test15() auto session = allocSession(state, handler); doConnect(*session, state, handler); + state.m_elapsed.push_back(1000); static const std::string Topic("topic/bla/bla"); static const DataBuf Data = {0, 1, 2, 3, 4}; @@ -2163,6 +2206,7 @@ void SessionTest::test15() state.m_elapsed.push_back(1000); pubackMsg = handler.prepareClientPuback(topicId, msgId, cc_mqttsn::field::ReturnCodeVal::InvalidTopicId); dataFromClient(*session, pubackMsg, "PUBACK"); + verifyTickReq(state); verifyNoOtherEvent(state, handler); // discarding the publish is expected } @@ -2177,6 +2221,7 @@ void SessionTest::test16() session->addPredefinedTopic(Topic, TopicId); doConnect(*session, state, handler); + state.m_elapsed.push_back(1000); static const std::uint16_t SleepDuration = 30 * 60; @@ -2267,6 +2312,7 @@ void SessionTest::test17() session->addPredefinedTopic(PredefinedTopic, PredefinedTopicId); doConnect(*session, state, handler); + state.m_elapsed.push_back(1000); static const std::string Topic("this/is/topic"); static const std::uint16_t MsgId = 0x1122; @@ -2276,29 +2322,36 @@ void SessionTest::test17() verifySentToBroker_PingreqMsg(state, handler); TS_ASSERT_LESS_THAN_EQUALS(DefaultMinTopicId, topicId); TS_ASSERT_LESS_THAN_EQUALS(topicId, DefaultMaxTopicId); + verifyTickReq(state); verifyNoOtherEvent(state, handler); - static const DataBuf Data = {0, 1, 2, 3, 4, 5, 6}; static const auto Qos = cc_mqttsn::field::QosVal::AtLeastOnceDelivery; static const bool Retain = false; + state.m_elapsed.push_back(1000); auto publishMsg = handler.prepareClientPublish(Data, static_cast(topicId + 1), MsgId, TopicIdTypeVal::Normal, Qos, Retain, false); dataFromClient(*session, publishMsg, "PUBLISH"); verifySentToClient_PubackMsg(state, handler, static_cast(topicId + 1), MsgId, cc_mqttsn::field::ReturnCodeVal::InvalidTopicId); verifySentToBroker_PingreqMsg(state, handler); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); publishMsg = handler.prepareClientPublish(Data, topicId, MsgId, TopicIdTypeVal::Normal, Qos, Retain, false); dataFromClient(*session, publishMsg, "PUBLISH"); verifySentToBroker_PublishMsg(state, handler, Topic, Data, MsgId, translateQos(Qos), Retain, false); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); auto pubackMsg = handler.prepareBrokerPuback(MsgId); dataFromBroker(*session, pubackMsg, "PUBACK"); verifySentToClient_PubackMsg(state, handler, topicId, MsgId, cc_mqttsn::field::ReturnCodeVal::Accepted); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); static const std::uint16_t SleepDuration = 30 * 60; auto disconnectSnMsg = handler.prepareClientDisconnect(SleepDuration); dataFromClient(*session, disconnectSnMsg, "DISCONNECT"); @@ -2319,16 +2372,21 @@ void SessionTest::test17() dataFromClient(*session, connectMsg, "CONNECT"); verifySentToClient_ConnackMsg(state, handler, cc_mqttsn::field::ReturnCodeVal::Accepted); verifySentToBroker_PingreqMsg(state, handler); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); dataFromClient(*session, publishMsg, "PUBLISH"); verifySentToClient_PubackMsg(state, handler, topicId, MsgId, cc_mqttsn::field::ReturnCodeVal::InvalidTopicId); verifySentToBroker_PingreqMsg(state, handler); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); publishMsg = handler.prepareClientPublish(Data, PredefinedTopicId, MsgId, TopicIdTypeVal::PredefinedTopicId, Qos, Retain, false); dataFromClient(*session, publishMsg, "PUBLISH"); verifySentToBroker_PublishMsg(state, handler, PredefinedTopic, Data, MsgId, translateQos(Qos), Retain, false); + verifyTickReq(state); verifyNoOtherEvent(state, handler); } @@ -2343,6 +2401,7 @@ void SessionTest::test18() session->addPredefinedTopic(PredefinedTopic, PredefinedTopicId); doConnect(*session, state, handler); + state.m_elapsed.push_back(1000); static const std::string Topic("this/is/topic"); static const std::uint16_t MsgId = 0x1122; @@ -2352,6 +2411,7 @@ void SessionTest::test18() verifySentToBroker_PingreqMsg(state, handler); TS_ASSERT_LESS_THAN_EQUALS(DefaultMinTopicId, topicId); TS_ASSERT_LESS_THAN_EQUALS(topicId, DefaultMaxTopicId); + verifyTickReq(state); verifyNoOtherEvent(state, handler); static const DataBuf Data = {0, 1, 2, 3, 4, 5, 6}; @@ -2359,34 +2419,46 @@ void SessionTest::test18() static const auto Qos2 = cc_mqttsn::field::QosVal::ExactlyOnceDelivery; static const bool Retain = false; + state.m_elapsed.push_back(1000); auto publishMsg = handler.prepareClientPublish(Data, topicId, MsgId, TopicIdTypeVal::Normal, Qos1, Retain, false); dataFromClient(*session, publishMsg, "PUBLISH"); verifySentToBroker_PublishMsg(state, handler, Topic, Data, MsgId, translateQos(Qos1), Retain, false); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(100); auto pubackMsg = handler.prepareBrokerPuback(MsgId); dataFromBroker(*session, pubackMsg, "PUBACK"); verifySentToClient_PubackMsg(state, handler, topicId, MsgId, cc_mqttsn::field::ReturnCodeVal::Accepted); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); publishMsg = handler.prepareClientPublish(Data, PredefinedTopicId, MsgId, TopicIdTypeVal::PredefinedTopicId, Qos2, Retain, true); dataFromClient(*session, publishMsg, "PUBLISH"); verifySentToBroker_PublishMsg(state, handler, PredefinedTopic, Data, MsgId, translateQos(Qos2), Retain, true); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(100); auto pubrecMsg = handler.prepareBrokerPubrec(MsgId); dataFromBroker(*session, pubrecMsg, "PUBREC"); verifySentToClient_PubrecMsg(state, handler, MsgId); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(100); auto pubrelMsg = handler.prepareClientPubrel(MsgId); dataFromClient(*session, pubrelMsg, "PUBREL"); verifySentToBroker_PubrelMsg(state, handler, MsgId); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(100); auto pubcompMsg = handler.prepareBrokerPubcomp(MsgId); dataFromBroker(*session, pubcompMsg, "PUBCOMP"); verifySentToClient_PubcompMsg(state, handler, MsgId); + verifyTickReq(state); verifyNoOtherEvent(state, handler); } @@ -2401,6 +2473,7 @@ void SessionTest::test19() session->addPredefinedTopic(Topic, TopicId); doConnect(*session, state, handler); + state.m_elapsed.push_back(1000); static const DataBuf Data1 = {0, 1, 2, 3, 4, 5, 6}; static const DataBuf Data2 = {10, 11, 12, 13, 14, 15, 16}; @@ -2420,8 +2493,10 @@ void SessionTest::test19() auto pub1 = handler.prepareBrokerPublish(Topic, Data1, MsgId1, Qos2, Retain, Dup); dataFromBroker(*session, pub1, "PUBLISH"); verifySentToBroker_PubrecMsg(state, handler, MsgId1); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(100); auto pub2 = handler.prepareBrokerPublish(Topic, Data2, MsgId2, Qos1, Retain, Dup); dataFromBroker(*session, pub2, "PUBLISH"); verifySentToBroker_PubackMsg(state, handler, MsgId2); @@ -2446,8 +2521,10 @@ void SessionTest::test19() auto ack1 = handler.prepareClientPuback(TopicId, msgId1, cc_mqttsn::field::ReturnCodeVal::Accepted); dataFromClient(*session, ack1, "PUBACK"); verifySentToClient_PublishMsg(state, handler, TopicId, Data3, TopicIdTypeVal::PredefinedTopicId, translateQos(Qos0), Retain, Dup); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(100); auto b_rec = handler.prepareBrokerPubrel(MsgId1); dataFromBroker(*session, b_rec, "PUBREC"); verifySentToBroker_PubcompMsg(state, handler, MsgId1); @@ -2486,6 +2563,7 @@ void SessionTest::test19() state.m_elapsed.push_back(1000); auto comp2 = handler.prepareClientPubcomp(msgId3); dataFromClient(*session, comp2, "PUBCOMP"); + verifyTickReq(state); verifyNoOtherEvent(state, handler); } @@ -2500,18 +2578,24 @@ void SessionTest::test20() verifyNoOtherEvent(state, handler); doConnect(*session, state, handler); + state.m_elapsed.push_back(1000); dataFromClient(*session, cReq, "PINGREQ"); verifySentToBroker_PingreqMsg(state, handler); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(100); auto bResp = handler.prepareBrokerPingresp(); dataFromBroker(*session, bResp, "PINGRESP"); verifySentToClient_PingrespMsg(state, handler); + verifyTickReq(state); verifyNoOtherEvent(state, handler); // should be ignored + state.m_elapsed.push_back(100); dataFromBroker(*session, bResp, "PINGRESP"); + verifyTickReq(state); verifyNoOtherEvent(state, handler); } @@ -2526,116 +2610,151 @@ void SessionTest::test21() session->addPredefinedTopic(PredefinedTopic, PredefinedTopicId); doConnect(*session, state, handler); + state.m_elapsed.push_back(1000); static const auto Qos = cc_mqttsn::field::QosVal::ExactlyOnceDelivery; static const std::uint16_t SubMsgId1 = 0x1234; auto subMsg1 = handler.prepareClientSubscribe(PredefinedTopicId, SubMsgId1, Qos); dataFromClient(*session, subMsg1, "SUBSCRIBE"); verifySentToBroker_SubscribeMsg(state, handler, PredefinedTopic, translateQos(Qos), SubMsgId1); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(100); using SubackReturnCodeVal = SubackMsg::Field_list::ValueType::value_type::ValueType; auto subackMsg1 = handler.prepareBrokerSuback(SubMsgId1, SubackReturnCodeVal::Qos2); dataFromBroker(*session, subackMsg1, "SUBACK"); auto subackTopicId1 = verifySentToClient_SubackMsg(state, handler, SubMsgId1, Qos, cc_mqttsn::field::ReturnCodeVal::Accepted); TS_ASSERT_EQUALS(subackTopicId1, PredefinedTopicId); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); static const std::string Topic("this/is/topic"); static const std::uint16_t SubMsgId2 = 0x5555; auto subMsg2 = handler.prepareClientSubscribe(Topic, SubMsgId2, Qos); dataFromClient(*session, subMsg2, "SUBSCRIBE"); verifySentToBroker_SubscribeMsg(state, handler, Topic, translateQos(Qos), SubMsgId2); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(100); using SubackReturnCodeVal = SubackMsg::Field_list::ValueType::value_type::ValueType; auto subackMsg2 = handler.prepareBrokerSuback(SubMsgId2, SubackReturnCodeVal::Qos1); dataFromBroker(*session, subackMsg2, "SUBACK"); auto subackTopicId2 = verifySentToClient_SubackMsg(state, handler, SubMsgId2, cc_mqttsn::field::QosVal::AtLeastOnceDelivery, cc_mqttsn::field::ReturnCodeVal::Accepted); TS_ASSERT_DIFFERS(subackTopicId2, 0U); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); static const std::uint16_t SubMsgId3 = 0x6000; auto subMsg3 = handler.prepareClientSubscribe(Topic, SubMsgId3, Qos); dataFromClient(*session, subMsg3, "SUBSCRIBE"); verifySentToBroker_SubscribeMsg(state, handler, Topic, translateQos(Qos), SubMsgId3); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(100); auto subackMsg3 = handler.prepareBrokerSuback(SubMsgId3, SubackReturnCodeVal::Qos2); dataFromBroker(*session, subackMsg3, "SUBACK"); auto subackTopicId3 = verifySentToClient_SubackMsg(state, handler, SubMsgId3, Qos, cc_mqttsn::field::ReturnCodeVal::Accepted); TS_ASSERT_DIFFERS(subackTopicId3, 0U); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); static const std::string ShortTopic = "ab"; static const std::uint16_t SubMsgId4 = 0x7000; auto subMsg4 = handler.prepareClientSubscribe(ShortTopic, SubMsgId4, Qos); dataFromClient(*session, subMsg4, "SUBSCRIBE"); verifySentToBroker_SubscribeMsg(state, handler, ShortTopic, translateQos(Qos), SubMsgId4); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(100); auto subackMsg4 = handler.prepareBrokerSuback(SubMsgId4, SubackReturnCodeVal::Qos2); dataFromBroker(*session, subackMsg4, "SUBACK"); auto subackTopicId4 = verifySentToClient_SubackMsg(state, handler, SubMsgId4, Qos, cc_mqttsn::field::ReturnCodeVal::Accepted); TS_ASSERT_EQUALS(subackTopicId4, shortTopicNameToId(ShortTopic)); + verifyTickReq(state); verifyNoOtherEvent(state, handler); - + state.m_elapsed.push_back(1000); static const std::string WildcardTopic = "#"; static const std::uint16_t SubMsgId5 = 0x8000; auto subMsg5 = handler.prepareClientSubscribe(WildcardTopic, SubMsgId5, Qos); dataFromClient(*session, subMsg5, "SUBSCRIBE"); verifySentToBroker_SubscribeMsg(state, handler, WildcardTopic, translateQos(Qos), SubMsgId5); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(100); auto subackMsg5 = handler.prepareBrokerSuback(SubMsgId5, SubackReturnCodeVal::Qos2); dataFromBroker(*session, subackMsg5, "SUBACK"); auto subackTopicId5 = verifySentToClient_SubackMsg(state, handler, SubMsgId5, Qos, cc_mqttsn::field::ReturnCodeVal::Accepted); TS_ASSERT_EQUALS(subackTopicId5, 0U); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); static const std::uint16_t UnsubMsgId1 = 0x0001; auto unsubMsg1 = handler.prepareClientUnsubscribe(PredefinedTopicId, UnsubMsgId1); dataFromClient(*session, unsubMsg1, "UNSUBSCRIBE"); verifySentToBroker_UnsubscribeMsg(state, handler, PredefinedTopic, UnsubMsgId1); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(100); auto unsubackMsg1 = handler.prepareBrokerUnsuback(UnsubMsgId1); dataFromBroker(*session, unsubackMsg1, "UNSUBACK"); verifySentToClient_UnsubackMsg(state, handler, UnsubMsgId1); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); static const std::uint16_t UnsubMsgId2 = 0x0004; auto unsubMsg2 = handler.prepareClientUnsubscribe(Topic, UnsubMsgId2); dataFromClient(*session, unsubMsg2, "UNSUBSCRIBE"); verifySentToBroker_UnsubscribeMsg(state, handler, Topic, UnsubMsgId2); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(100); auto unsubackMsg2 = handler.prepareBrokerUnsuback(UnsubMsgId2); dataFromBroker(*session, unsubackMsg2, "UNSUBACK"); verifySentToClient_UnsubackMsg(state, handler, UnsubMsgId2); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); static const std::uint16_t UnsubMsgId3 = 0x0005; auto unsubMsg3 = handler.prepareClientUnsubscribe(ShortTopic, UnsubMsgId3); dataFromClient(*session, unsubMsg3, "UNSUBSCRIBE"); verifySentToBroker_UnsubscribeMsg(state, handler, ShortTopic, UnsubMsgId3); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(100); auto unsubackMsg3 = handler.prepareBrokerUnsuback(UnsubMsgId3); dataFromBroker(*session, unsubackMsg3, "UNSUBACK"); verifySentToClient_UnsubackMsg(state, handler, UnsubMsgId3); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); static const std::uint16_t UnsubMsgId4 = 0x0006; auto unsubMsg4 = handler.prepareClientUnsubscribe(WildcardTopic, UnsubMsgId4); dataFromClient(*session, unsubMsg4, "UNSUBSCRIBE"); verifySentToBroker_UnsubscribeMsg(state, handler, WildcardTopic, UnsubMsgId4); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(100); auto unsubackMsg4 = handler.prepareBrokerUnsuback(UnsubMsgId4); dataFromBroker(*session, unsubackMsg4, "UNSUBACK"); verifySentToClient_UnsubackMsg(state, handler, UnsubMsgId4); + verifyTickReq(state); verifyNoOtherEvent(state, handler); } @@ -2659,6 +2778,7 @@ void SessionTest::test22() doConnect(*session, state, handler, &will1); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); WillInfo will2; doConnect(*session, state, handler, &will2, false, true, false); } @@ -2683,12 +2803,15 @@ void SessionTest::test23() doConnect(*session, state, handler, &will1, true, false, true, Username, Pass); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); auto willTopicUpdMsg1 = handler.prepareClientWilltopicupd(WillTopic1, translateQos(WillQos1), WillRetain1); dataFromClient(*session, willTopicUpdMsg1, "WILLTOPICUPD"); verifySentToClient_WilltopicrespMsg(state, handler, cc_mqttsn::field::ReturnCodeVal::Accepted); + verifyTickReq(state); verifySentToBroker_PingreqMsg(state, handler); + state.m_elapsed.push_back(1000); static const std::string WillTopic2("will/topic/2"); auto willTopicUpdMsg2 = handler.prepareClientWilltopicupd(WillTopic2, translateQos(WillQos1), WillRetain1); dataFromClient(*session, willTopicUpdMsg2, "WILLTOPICUPD"); @@ -2718,8 +2841,10 @@ void SessionTest::test23() auto connackMsg1 = handler.prepareBrokerConnack(ConnackResponseCodeVal::Accepted, true); dataFromBroker(*session, connackMsg1, "CONNACK"); verifySentToClient_WilltopicrespMsg(state, handler, cc_mqttsn::field::ReturnCodeVal::Accepted); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); static const auto WillQos2 = cc_mqtt311::field::QosVal::ExactlyOnceDelivery; auto willTopicUpdMsg3 = handler.prepareClientWilltopicupd(WillTopic2, translateQos(WillQos2), WillRetain1); dataFromClient(*session, willTopicUpdMsg3, "WILLTOPICUPD"); @@ -2760,6 +2885,7 @@ void SessionTest::test23() doConnect(*session, state, handler, &will1); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); auto willTopicUpdMsg4 = handler.prepareClientWilltopicupd(WillTopic1, translateQos(WillQos1), !WillRetain1); dataFromClient(*session, willTopicUpdMsg4, "WILLTOPICUPD"); @@ -2809,12 +2935,15 @@ void SessionTest::test24() doConnect(*session, state, handler, &will, true, false, true, Username, Pass); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); auto willMsgUpdMsg1 = handler.prepareClientWillmsgupd(WillData1); dataFromClient(*session, willMsgUpdMsg1, "WILLMSGUPD"); verifySentToClient_WillmsgrespMsg(state, handler, cc_mqttsn::field::ReturnCodeVal::Accepted); + verifyTickReq(state); verifySentToBroker_PingreqMsg(state, handler); + state.m_elapsed.push_back(100); static const DataBuf WillData2 = {10, 11, 12}; auto willMsgUpdMsg2 = handler.prepareClientWillmsgupd(WillData2); dataFromClient(*session, willMsgUpdMsg2, "WILLMSGUPD"); @@ -2840,8 +2969,10 @@ void SessionTest::test24() auto connackMsg1 = handler.prepareBrokerConnack(ConnackResponseCodeVal::Accepted, true); dataFromBroker(*session, connackMsg1, "CONNACK"); verifySentToClient_WillmsgrespMsg(state, handler, cc_mqttsn::field::ReturnCodeVal::Accepted); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); dataFromClient(*session, willMsgUpdMsg1, "WILLMSGUPD"); verifySentToBroker_DisconnectMsg(state, handler); verifyBrokerReconnectReq(state); @@ -2870,6 +3001,7 @@ void SessionTest::test24() session = allocSession(state, handler); doConnect(*session, state, handler, &will); + state.m_elapsed.push_back(1000); dataFromClient(*session, willMsgUpdMsg2, "WILLMSGUPD"); verifySentToBroker_DisconnectMsg(state, handler); @@ -2910,6 +3042,7 @@ void SessionTest::test25() session->setSleepingClientMsgLimit(3U); doConnect(*session, state, handler); + state.m_elapsed.push_back(1000); static const std::uint16_t SleepDuration = 30 * 60; @@ -3013,10 +3146,13 @@ void SessionTest::test26() verifySentToBroker_PublishMsg(state, handler, Topic, Data1, 0, cc_mqtt311::field::QosVal::AtMostOnceDelivery, false, false); verifySentToBroker_PublishMsg(state, handler, Topic, Data2, 0, cc_mqtt311::field::QosVal::AtMostOnceDelivery, false, false); verifyConnectedClient(state, ClientId); + verifyTickReq(state); verifyNoOtherEvent(state, handler); + state.m_elapsed.push_back(1000); dataFromClient(*session, pub1, "PUBLISH"); verifySentToBroker_PublishMsg(state, handler, Topic, Data1, 0, cc_mqtt311::field::QosVal::AtMostOnceDelivery, false, false); + verifyTickReq(state); verifyNoOtherEvent(state, handler); } @@ -3043,6 +3179,7 @@ void SessionTest::test27() dataFromBroker(*session, connackMsg, "CONNACK"); verifySentToClient_ConnackMsg(state, handler, cc_mqttsn::field::ReturnCodeVal::Accepted); verifyConnectedClient(state, ClientId); + verifyTickReq(state); verifyNoOtherEvent(state, handler); } @@ -3078,6 +3215,7 @@ void SessionTest::test28() dataFromBroker(*session, connackMsg, "CONNACK"); verifySentToClient_ConnackMsg(state, handler, cc_mqttsn::field::ReturnCodeVal::Accepted); verifyConnectedClient(state, DefaultClientId); + verifyTickReq(state); verifyNoOtherEvent(state, handler); } From 01111db7ae66c8ca28390d7f55ea0bdb56da70d7 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 4 Jul 2024 09:03:18 +1000 Subject: [PATCH 052/106] Unittesting for client connect operation. --- client/lib/doxygen/main.dox | 6 + client/lib/src/op/ConnectOp.cpp | 10 +- client/lib/src/op/SearchOp.cpp | 6 +- client/lib/templ/client.cpp.templ | 17 +- client/lib/templ/client.h.templ | 5 + client/lib/test/CMakeLists.txt | 3 +- client/lib/test/UnitTestCommonBase.cpp | 86 +++- client/lib/test/UnitTestCommonBase.h | 39 +- client/lib/test/UnitTestConnect.th | 515 ++++++++++++++++++++++++ client/lib/test/UnitTestDefaultBase.cpp | 1 + client/lib/test/UnitTestGwDiscover.th | 2 +- 11 files changed, 665 insertions(+), 25 deletions(-) create mode 100644 client/lib/test/UnitTestConnect.th diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index 5e0a54fe..d4d6c770 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -267,6 +267,12 @@ /// ... /* Something went wrong */ /// } /// @endcode +/// Note that the function specify number of @b retries, i.e. when number of retries is @b 1 +/// the relevant message will be sent twice before reporting failure (in case there is no response of course): +/// once when the send is explicitly requested and the second time is an actual retry. +/// Following the same logic, when the configured number of retries is @b 0, the message will be send only +/// once (when the request is issued) and will not try to re-send it when the +/// response is not received in time. /// /// @section doc_cc_mqttsn_client_broadcast_radius Default Broadcast Radius /// The MQTT-SN protocol defines several messages that are broadcasted. When diff --git a/client/lib/src/op/ConnectOp.cpp b/client/lib/src/op/ConnectOp.cpp index e4f4049b..e69d748e 100644 --- a/client/lib/src/op/ConnectOp.cpp +++ b/client/lib/src/op/ConnectOp.cpp @@ -73,7 +73,7 @@ CC_MqttsnErrorCode ConnectOp::willConfig([[maybe_unused]] const CC_MqttsnWillCon } m_connectMsg.field_flags().field_mid().setBitValue_Will(true); - if ((config->m_topic == nullptr) || (config->m_topic[0] != '\0')) { + if ((config->m_topic == nullptr) || (config->m_topic[0] == '\0')) { if (0U < config->m_dataLen) { errorLog("Will configuration contains empty topic and not empty message."); return CC_MqttsnErrorCode_BadParam; @@ -89,6 +89,7 @@ CC_MqttsnErrorCode ConnectOp::willConfig([[maybe_unused]] const CC_MqttsnWillCon m_willtopicMsg.field_flags().setExists(); m_willtopicMsg.field_flags().field().field_qos().setValue(config->m_qos); + m_willtopicMsg.field_flags().field().field_mid().setBitValue_Retain(config->m_retain); m_willtopicMsg.field_willTopic().value() = config->m_topic; if (0U < config->m_dataLen) { @@ -135,13 +136,13 @@ CC_MqttsnErrorCode ConnectOp::send(CC_MqttsnConnectCompleteCb cb, void* cbData) m_cb = cb; m_cbData = cbData; + m_origRetryCount = getRetryCount(); auto ec = sendInternal(); if (ec != CC_MqttsnErrorCode_Success) { return ec; } completeOnError.release(); - m_origRetryCount = getRetryCount(); return CC_MqttsnErrorCode_Success; } @@ -259,10 +260,9 @@ CC_MqttsnErrorCode ConnectOp::sendInternal() auto func = Map[m_stage]; auto ec = sendMessage((this->*func)()); if (ec == CC_MqttsnErrorCode_Success) { - COMMS_ASSERT(0U < getRetryCount()); - decRetryCount(); restartTimer(); } + return ec; } @@ -279,6 +279,8 @@ void ConnectOp::timeoutInternal() completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); return; } + + decRetryCount(); } const ProtMessage& ConnectOp::getConnectMsg() const diff --git a/client/lib/src/op/SearchOp.cpp b/client/lib/src/op/SearchOp.cpp index 977b5041..70fb29ac 100644 --- a/client/lib/src/op/SearchOp.cpp +++ b/client/lib/src/op/SearchOp.cpp @@ -140,8 +140,6 @@ CC_MqttsnErrorCode SearchOp::sendInternal() { auto ec = sendMessage(m_searchgwMsg, m_radius); if (ec == CC_MqttsnErrorCode_Success) { - COMMS_ASSERT(0U < getRetryCount()); - decRetryCount(); restartTimer(); } return ec; @@ -159,7 +157,9 @@ void SearchOp::timeoutInternal() if (ec != CC_MqttsnErrorCode_Success) { completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); return; - } + } + + decRetryCount(); } void SearchOp::opTimeoutCb(void* data) diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index a9d7f01b..78595dfb 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -343,13 +343,7 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_set_retry_count( [[maybe_unused]] unsigned count) { #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY - COMMS_ASSERT(handle != nullptr); - if (count == 0U) { - searchOpFromHandle(handle)->client().errorLog("The retry count must be greater than 0"); - return CC_MqttsnErrorCode_BadParam; - } - searchOpFromHandle(handle)->setRetryCount(count); return CC_MqttsnErrorCode_Success; @@ -469,11 +463,6 @@ unsigned cc_mqttsn_##NAME##client_connect_get_retry_period(CC_MqttsnConnectHandl CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect_set_retry_count(CC_MqttsnConnectHandle handle, unsigned count) { COMMS_ASSERT(handle != nullptr); - if (count == 0U) { - connectOpFromHandle(handle)->client().errorLog("The retry count must be greater than 0"); - return CC_MqttsnErrorCode_BadParam; - } - connectOpFromHandle(handle)->setRetryCount(count); return CC_MqttsnErrorCode_Success; } @@ -559,6 +548,12 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect( return cc_mqttsn_##NAME##client_connect_send(connect, cb, cbData); } +bool cc_mqttsn_##NAME##client_is_connected(CC_MqttsnClientHandle client) +{ + COMMS_ASSERT(client != nullptr); + return clientFromHandle(client)->sessionState().m_connected; +} + // --------------------- Callbacks --------------------- void cc_mqttsn_##NAME##client_set_next_tick_program_callback( diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index d8e76042..1a70f27d 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -386,6 +386,11 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect( CC_MqttsnConnectCompleteCb cb, void* cbData); +/// @brief Check the inner state of the library of whether it's connected to the gateway. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @ingroup connect +bool cc_mqttsn_##NAME##client_is_connected(CC_MqttsnClientHandle client); + // --------------------- Callbacks --------------------- /// @brief Set callback to call when time measurement is required. diff --git a/client/lib/test/CMakeLists.txt b/client/lib/test/CMakeLists.txt index ed4957dd..2c593cb3 100644 --- a/client/lib/test/CMakeLists.txt +++ b/client/lib/test/CMakeLists.txt @@ -40,8 +40,7 @@ if (TARGET cc::cc_mqttsn_client) ) cc_mqttsn_client_add_unit_test(UnitTestGwDiscover ${DEFAULT_BASE_LIB_NAME}) - -# cc_mqttsn_client_add_unit_test(UnitTestConnect ${DEFAULT_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(UnitTestConnect ${DEFAULT_BASE_LIB_NAME}) # cc_mqttsn_client_add_unit_test(UnitTestDisconnect ${DEFAULT_BASE_LIB_NAME}) # cc_mqttsn_client_add_unit_test(UnitTestPublish ${DEFAULT_BASE_LIB_NAME}) # cc_mqttsn_client_add_unit_test(UnitTestReceive ${DEFAULT_BASE_LIB_NAME}) diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index 14ba906b..5ab04c21 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -66,6 +66,7 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_connect_send != nullptr); test_assert(m_funcs.m_connect_cancel != nullptr); test_assert(m_funcs.m_connect != nullptr); + test_assert(m_funcs.m_is_connected != nullptr); test_assert(m_funcs.m_set_next_tick_program_callback != nullptr); test_assert(m_funcs.m_set_cancel_next_tick_wait_callback != nullptr); @@ -117,6 +118,20 @@ void UnitTestCommonBase::UnitTestSearchCompleteReport::assignInfo(CC_MqttsnGatew info.m_addrLen = static_cast(m_info.m_addr.size()); } +UnitTestCommonBase::UnitTestConnectInfo& UnitTestCommonBase::UnitTestConnectInfo::operator=(const CC_MqttsnConnectInfo& info) +{ + m_returnCode = info.m_returnCode; + return *this; +} + +UnitTestCommonBase::UnitTestConnectCompleteReport::UnitTestConnectCompleteReport(CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info) : + m_status(status) +{ + if (info != nullptr) { + m_info = *info; + } +} + void UnitTestCommonBase::unitTestSetUp() { } @@ -297,13 +312,13 @@ void UnitTestCommonBase::unitTestPopSearchCompleteReport() m_data.m_searchCompleteReports.pop_front(); } -void UnitTestCommonBase::unitTestSearchSend(CC_MqttsnSearchHandle search, UnitTestSearchCompleteCb&& cb) +CC_MqttsnErrorCode UnitTestCommonBase::unitTestSearchSend(CC_MqttsnSearchHandle search, UnitTestSearchCompleteCb&& cb) { if (cb) { m_data.m_searchCompleteCallbacks.push_back(std::move(cb)); } - m_funcs.m_search_send(search, &UnitTestCommonBase::unitTestSearchCompleteCb, this); + return m_funcs.m_search_send(search, &UnitTestCommonBase::unitTestSearchCompleteCb, this); } void UnitTestCommonBase::unitTestSearch(CC_MqttsnClient* client, UnitTestSearchCompleteCb&& cb) @@ -341,6 +356,32 @@ void UnitTestCommonBase::unitTestSearchUpdateAddr(CC_MqttsnClient* client, const }); } +bool UnitTestCommonBase::unitTestHasConnectCompleteReport() const +{ + return !m_data.m_connectCompleteReports.empty(); +} + +const UnitTestCommonBase::UnitTestConnectCompleteReport* UnitTestCommonBase::unitTestConnectCompleteReport(bool mustExist) const +{ + if (!unitTestHasConnectCompleteReport()) { + test_assert(!mustExist); + return nullptr; + } + + return &m_data.m_connectCompleteReports.front(); +} + +void UnitTestCommonBase::unitTestPopConnectCompleteReport() +{ + test_assert(unitTestHasConnectCompleteReport()); + m_data.m_connectCompleteReports.pop_front(); +} + +CC_MqttsnErrorCode UnitTestCommonBase::unitTestConnectSend(CC_MqttsnConnectHandle connect) +{ + return m_funcs.m_connect_send(connect, &UnitTestCommonBase::unitTestConnectCompleteCb, this); +} + void UnitTestCommonBase::apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen) { m_funcs.m_process_data(client, buf, bufLen); @@ -376,6 +417,41 @@ CC_MqttsnErrorCode UnitTestCommonBase::apiSearchSetBroadcastRadius(CC_MqttsnSear return m_funcs.m_search_set_broadcast_radius(search, value); } +CC_MqttsnConnectHandle UnitTestCommonBase::apiConnectPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec) +{ + return m_funcs.m_connect_prepare(client, ec); +} + +CC_MqttsnErrorCode UnitTestCommonBase::apiConnectSetRetryCount(CC_MqttsnConnectHandle connect, unsigned count) +{ + return m_funcs.m_connect_set_retry_count(connect, count); +} + +void UnitTestCommonBase::apiConnectInitConfig(CC_MqttsnConnectConfig* config) +{ + m_funcs.m_connect_init_config(config); +} + +void UnitTestCommonBase::apiConnectInitConfigWill(CC_MqttsnWillConfig* config) +{ + m_funcs.m_connect_init_config_will(config); +} + +CC_MqttsnErrorCode UnitTestCommonBase::apiConnectConfig(CC_MqttsnConnectHandle connect, const CC_MqttsnConnectConfig* config) +{ + return m_funcs.m_connect_config(connect, config); +} + +CC_MqttsnErrorCode UnitTestCommonBase::apiConnectConfigWill(CC_MqttsnConnectHandle connect, const CC_MqttsnWillConfig* config) +{ + return m_funcs.m_connect_config_will(connect, config); +} + +bool UnitTestCommonBase::apiIsConnected(CC_MqttsnClient* client) +{ + return m_funcs.m_is_connected(client); +} + void UnitTestCommonBase::unitTestTickProgramCb(void* data, unsigned duration) { auto* thisPtr = asThis(data); @@ -455,4 +531,10 @@ void UnitTestCommonBase::unitTestSearchCompleteCb(void* data, CC_MqttsnAsyncOpSt if (popReport) { thisPtr->m_data.m_searchCompleteReports.pop_back(); } +} + +void UnitTestCommonBase::unitTestConnectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info) +{ + auto* thisPtr = asThis(data); + thisPtr->m_data.m_connectCompleteReports.emplace_back(status, info); } \ No newline at end of file diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index bcef9630..19d23a28 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -56,6 +56,7 @@ class UnitTestCommonBase CC_MqttsnErrorCode (*m_connect_send)(CC_MqttsnConnectHandle, CC_MqttsnConnectCompleteCb, void*) = nullptr; CC_MqttsnErrorCode (*m_connect_cancel)(CC_MqttsnConnectHandle) = nullptr; CC_MqttsnErrorCode (*m_connect)(CC_MqttsnClientHandle, const CC_MqttsnConnectConfig*, const CC_MqttsnWillConfig*, CC_MqttsnConnectCompleteCb, void*) = nullptr; + bool (*m_is_connected)(CC_MqttsnClientHandle) = nullptr; void (*m_set_next_tick_program_callback)(CC_MqttsnClientHandle, CC_MqttsnNextTickProgramCb, void*) = nullptr; void (*m_set_cancel_next_tick_wait_callback)(CC_MqttsnClientHandle, CC_MqttsnCancelNextTickWaitCb, void*) = nullptr; @@ -140,6 +141,26 @@ class UnitTestCommonBase using UnitTestSearchCompleteCb = std::function; using UnitTestSearchCompleteCbList = std::list; + struct UnitTestConnectInfo + { + CC_MqttsnReturnCode m_returnCode = CC_MqttsnReturnCode_ValuesLimit; + + UnitTestConnectInfo() = default; + UnitTestConnectInfo(const UnitTestConnectInfo&) = default; + UnitTestConnectInfo& operator=(const UnitTestConnectInfo&) = default; + UnitTestConnectInfo& operator=(const CC_MqttsnConnectInfo& info); + }; + + struct UnitTestConnectCompleteReport + { + CC_MqttsnAsyncOpStatus m_status = CC_MqttsnAsyncOpStatus_ValuesLimit; + UnitTestConnectInfo m_info; + + UnitTestConnectCompleteReport(CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info); + }; + + using UnitTestConnectCompleteReportList = std::list; + using UnitTestClientPtr = std::unique_ptr; void unitTestSetUp(); @@ -167,10 +188,16 @@ class UnitTestCommonBase const UnitTestSearchCompleteReport* unitTestSearchCompleteReport(bool mustExist = true) const; void unitTestPopSearchCompleteReport(); - void unitTestSearchSend(CC_MqttsnSearchHandle search, UnitTestSearchCompleteCb&& cb = UnitTestSearchCompleteCb()); + CC_MqttsnErrorCode unitTestSearchSend(CC_MqttsnSearchHandle search, UnitTestSearchCompleteCb&& cb = UnitTestSearchCompleteCb()); void unitTestSearch(CC_MqttsnClient* client, UnitTestSearchCompleteCb&& cb = UnitTestSearchCompleteCb()); void unitTestSearchUpdateAddr(CC_MqttsnClient* client, const UnitTestData& addr); + bool unitTestHasConnectCompleteReport() const; + const UnitTestConnectCompleteReport* unitTestConnectCompleteReport(bool mustExist = true) const; + void unitTestPopConnectCompleteReport(); + + CC_MqttsnErrorCode unitTestConnectSend(CC_MqttsnConnectHandle connect); + void apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen); CC_MqttsnErrorCode apiSetDefaultRetryPeriod(CC_MqttsnClient* client, unsigned value); CC_MqttsnErrorCode apiSetDefaultRetryCount(CC_MqttsnClient* client, unsigned value); @@ -178,7 +205,13 @@ class UnitTestCommonBase CC_MqttsnErrorCode apiSearchSetRetryPeriod(CC_MqttsnSearchHandle search, unsigned value); CC_MqttsnErrorCode apiSearchSetRetryCount(CC_MqttsnSearchHandle search, unsigned value); CC_MqttsnErrorCode apiSearchSetBroadcastRadius(CC_MqttsnSearchHandle search, unsigned value); - + CC_MqttsnConnectHandle apiConnectPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec = nullptr); + CC_MqttsnErrorCode apiConnectSetRetryCount(CC_MqttsnConnectHandle connect, unsigned count); + void apiConnectInitConfig(CC_MqttsnConnectConfig* config); + void apiConnectInitConfigWill(CC_MqttsnWillConfig* config); + CC_MqttsnErrorCode apiConnectConfig(CC_MqttsnConnectHandle connect, const CC_MqttsnConnectConfig* config); + CC_MqttsnErrorCode apiConnectConfigWill(CC_MqttsnConnectHandle connect, const CC_MqttsnWillConfig* config); + bool apiIsConnected(CC_MqttsnClient* client); protected: explicit UnitTestCommonBase(const LibFuncs& funcs); @@ -191,6 +224,7 @@ class UnitTestCommonBase UnitTestGwInfoReportsList m_gwInfoReports; UnitTestSearchCompleteReportsList m_searchCompleteReports; UnitTestSearchCompleteCbList m_searchCompleteCallbacks; + UnitTestConnectCompleteReportList m_connectCompleteReports; }; static void unitTestTickProgramCb(void* data, unsigned duration); @@ -202,6 +236,7 @@ class UnitTestCommonBase static unsigned unitTestGwinfoDelayRequestCb(void* data); static void unitTestErrorLogCb(void* data, const char* msg); static void unitTestSearchCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info); + static void unitTestConnectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info); LibFuncs m_funcs; ClientData m_data; diff --git a/client/lib/test/UnitTestConnect.th b/client/lib/test/UnitTestConnect.th new file mode 100644 index 00000000..ec7a5e41 --- /dev/null +++ b/client/lib/test/UnitTestConnect.th @@ -0,0 +1,515 @@ +#include "UnitTestDefaultBase.h" +#include "UnitTestProtocolDefs.h" + +#include "comms/units.h" + +#include + +class UnitTestConnect : public CxxTest::TestSuite, public UnitTestDefaultBase +{ +public: + void test1(); + void test2(); + void test3(); + void test4(); + void test5(); + void test6(); + +private: + virtual void setUp() override + { + unitTestSetUp(); + } + + virtual void tearDown() override + { + unitTestTearDown(); + } +}; + +void UnitTestConnect::test1() +{ + // Testing basic connect + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + auto connect = apiConnectPrepare(client); + TS_ASSERT_DIFFERS(connect, nullptr); + + const std::string ClientId("bla"); + + CC_MqttsnConnectConfig config; + apiConnectInitConfig(&config); + TS_ASSERT_EQUALS(config.m_duration, 60U); + config.m_clientId = ClientId.c_str(); + config.m_cleanSession = true; + + auto ec = apiConnectConfig(connect, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + ec = unitTestConnectSend(connect); + + { + auto sentMsg = unitTestPopOutputMessage(); + auto* connectMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(connectMsg, nullptr); + TS_ASSERT_EQUALS(connectMsg->field_clientId().value(), ClientId); + TS_ASSERT_EQUALS(connectMsg->field_duration().value(), 60U); + TS_ASSERT(connectMsg->field_flags().field_mid().getBitValue_CleanSession()); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(!unitTestHasConnectCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestConnackMsg connackMsg; + connackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, connackMsg); + } + + TS_ASSERT(unitTestHasConnectCompleteReport()); + auto* connectReport = unitTestConnectCompleteReport(); + TS_ASSERT_EQUALS(connectReport->m_status, CC_MqttsnAsyncOpStatus_Complete) + TS_ASSERT_EQUALS(connectReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted) + unitTestPopConnectCompleteReport(); + + TS_ASSERT(unitTestHasTickReq()); // For keep alive + TS_ASSERT(apiIsConnected(client)); +} + +void UnitTestConnect::test2() +{ + // Testing basic connect with will + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + auto connect = apiConnectPrepare(client); + TS_ASSERT_DIFFERS(connect, nullptr); + + const std::string ClientId("bla"); + + CC_MqttsnConnectConfig config; + apiConnectInitConfig(&config); + TS_ASSERT_EQUALS(config.m_duration, 60U); + config.m_clientId = ClientId.c_str(); + config.m_cleanSession = true; + + auto ec = apiConnectConfig(connect, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + const std::string WillTopic("will/topic"); + const std::vector WillData = {0x1, 0x2, 0x3}; + const CC_MqttsnQoS WillQos = CC_MqttsnQoS_AtLeastOnceDelivery; + bool WillRetain = true; + + CC_MqttsnWillConfig willConfig; + apiConnectInitConfigWill(&willConfig); + willConfig.m_topic = WillTopic.c_str(); + willConfig.m_data = WillData.data(); + willConfig.m_dataLen = static_cast(WillData.size()); + willConfig.m_qos = WillQos; + willConfig.m_retain = WillRetain; + + ec = apiConnectConfigWill(connect, &willConfig); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + ec = unitTestConnectSend(connect); + + { + auto sentMsg = unitTestPopOutputMessage(); + auto* connectMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(connectMsg, nullptr); + TS_ASSERT_EQUALS(connectMsg->field_clientId().value(), ClientId); + TS_ASSERT_EQUALS(connectMsg->field_duration().value(), 60U); + TS_ASSERT(connectMsg->field_flags().field_mid().getBitValue_CleanSession()); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(!unitTestHasConnectCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestWilltopicreqMsg willtopicreqMsg; + unitTestClientInputMessage(client, willtopicreqMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willtopicMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willtopicMsg, nullptr); + TS_ASSERT(willtopicMsg->field_flags().doesExist()); + TS_ASSERT_EQUALS(static_cast(willtopicMsg->field_flags().field().field_qos().getValue()), WillQos); + TS_ASSERT_EQUALS(willtopicMsg->field_flags().field().field_mid().getBitValue_Retain(), WillRetain); + TS_ASSERT_EQUALS(willtopicMsg->field_willTopic().value(), WillTopic); + } + + TS_ASSERT(!unitTestHasConnectCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestWillmsgreqMsg willmsgreqMsg; + unitTestClientInputMessage(client, willmsgreqMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willmsgMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willmsgMsg, nullptr); + TS_ASSERT_EQUALS(willmsgMsg->field_willMsg().value(), WillData); + } + + TS_ASSERT(!unitTestHasConnectCompleteReport()); + TS_ASSERT(!apiIsConnected(client)); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestConnackMsg connackMsg; + connackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, connackMsg); + } + + TS_ASSERT(unitTestHasConnectCompleteReport()); + auto* connectReport = unitTestConnectCompleteReport(); + TS_ASSERT_EQUALS(connectReport->m_status, CC_MqttsnAsyncOpStatus_Complete) + TS_ASSERT_EQUALS(connectReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted) + unitTestPopConnectCompleteReport(); + + TS_ASSERT(unitTestHasTickReq()); // For keep alive + TS_ASSERT(apiIsConnected(client)); +} + +void UnitTestConnect::test3() +{ + // Testing timeout on connect + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + auto connect = apiConnectPrepare(client); + TS_ASSERT_DIFFERS(connect, nullptr); + + auto ec = apiConnectSetRetryCount(connect, 1U); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + const std::string ClientId("bla"); + + CC_MqttsnConnectConfig config; + apiConnectInitConfig(&config); + TS_ASSERT_EQUALS(config.m_duration, 60U); + config.m_clientId = ClientId.c_str(); + config.m_cleanSession = true; + + ec = apiConnectConfig(connect, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + ec = unitTestConnectSend(connect); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* connectMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(connectMsg, nullptr); + TS_ASSERT_EQUALS(connectMsg->field_clientId().value(), ClientId); + TS_ASSERT_EQUALS(connectMsg->field_duration().value(), 60U); + TS_ASSERT(connectMsg->field_flags().field_mid().getBitValue_CleanSession()); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(!unitTestHasConnectCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); + TS_ASSERT(!unitTestHasConnectCompleteReport()); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* connectMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(connectMsg, nullptr); + TS_ASSERT_EQUALS(connectMsg->field_clientId().value(), ClientId); + TS_ASSERT_EQUALS(connectMsg->field_duration().value(), 60U); + TS_ASSERT(connectMsg->field_flags().field_mid().getBitValue_CleanSession()); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(!unitTestHasConnectCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); + + TS_ASSERT(unitTestHasConnectCompleteReport()); + auto* connectReport = unitTestConnectCompleteReport(); + TS_ASSERT_EQUALS(connectReport->m_status, CC_MqttsnAsyncOpStatus_Timeout) + unitTestPopConnectCompleteReport(); + + TS_ASSERT(!unitTestHasTickReq()); + TS_ASSERT(!apiIsConnected(client)); +} + +void UnitTestConnect::test4() +{ + // Testing timeout on connect when there is no response after WILLTOPIC + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + auto connect = apiConnectPrepare(client); + TS_ASSERT_DIFFERS(connect, nullptr); + + auto ec = apiConnectSetRetryCount(connect, 1U); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + const std::string ClientId("bla"); + + CC_MqttsnConnectConfig config; + apiConnectInitConfig(&config); + TS_ASSERT_EQUALS(config.m_duration, 60U); + config.m_clientId = ClientId.c_str(); + config.m_cleanSession = true; + + ec = apiConnectConfig(connect, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + const std::string WillTopic("will/topic"); + const std::vector WillData = {0x1, 0x2, 0x3}; + const CC_MqttsnQoS WillQos = CC_MqttsnQoS_AtLeastOnceDelivery; + bool WillRetain = true; + + CC_MqttsnWillConfig willConfig; + apiConnectInitConfigWill(&willConfig); + willConfig.m_topic = WillTopic.c_str(); + willConfig.m_data = WillData.data(); + willConfig.m_dataLen = static_cast(WillData.size()); + willConfig.m_qos = WillQos; + willConfig.m_retain = WillRetain; + + ec = apiConnectConfigWill(connect, &willConfig); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + ec = unitTestConnectSend(connect); + + { + auto sentMsg = unitTestPopOutputMessage(); + auto* connectMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(connectMsg, nullptr); + TS_ASSERT_EQUALS(connectMsg->field_clientId().value(), ClientId); + TS_ASSERT_EQUALS(connectMsg->field_duration().value(), 60U); + TS_ASSERT(connectMsg->field_flags().field_mid().getBitValue_CleanSession()); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(!unitTestHasConnectCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestWilltopicreqMsg willtopicreqMsg; + unitTestClientInputMessage(client, willtopicreqMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willtopicMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willtopicMsg, nullptr); + TS_ASSERT(willtopicMsg->field_flags().doesExist()); + TS_ASSERT_EQUALS(static_cast(willtopicMsg->field_flags().field().field_qos().getValue()), WillQos); + TS_ASSERT_EQUALS(willtopicMsg->field_flags().field().field_mid().getBitValue_Retain(), WillRetain); + TS_ASSERT_EQUALS(willtopicMsg->field_willTopic().value(), WillTopic); + } + + TS_ASSERT(!unitTestHasConnectCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willtopicMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willtopicMsg, nullptr); + TS_ASSERT(willtopicMsg->field_flags().doesExist()); + TS_ASSERT_EQUALS(static_cast(willtopicMsg->field_flags().field().field_qos().getValue()), WillQos); + TS_ASSERT_EQUALS(willtopicMsg->field_flags().field().field_mid().getBitValue_Retain(), WillRetain); + TS_ASSERT_EQUALS(willtopicMsg->field_willTopic().value(), WillTopic); + } + + TS_ASSERT(!unitTestHasConnectCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); + + TS_ASSERT(unitTestHasConnectCompleteReport()); + auto* connectReport = unitTestConnectCompleteReport(); + TS_ASSERT_EQUALS(connectReport->m_status, CC_MqttsnAsyncOpStatus_Timeout) + unitTestPopConnectCompleteReport(); + + TS_ASSERT(!unitTestHasTickReq()); + TS_ASSERT(!apiIsConnected(client)); +} + + +void UnitTestConnect::test5() +{ + // Testing timeout on connect when there is no response after WILLMSG + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + auto connect = apiConnectPrepare(client); + TS_ASSERT_DIFFERS(connect, nullptr); + + auto ec = apiConnectSetRetryCount(connect, 1U); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + const std::string ClientId("bla"); + + CC_MqttsnConnectConfig config; + apiConnectInitConfig(&config); + TS_ASSERT_EQUALS(config.m_duration, 60U); + config.m_clientId = ClientId.c_str(); + config.m_cleanSession = true; + + ec = apiConnectConfig(connect, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + const std::string WillTopic("will/topic"); + const std::vector WillData = {0x1, 0x2, 0x3}; + const CC_MqttsnQoS WillQos = CC_MqttsnQoS_AtLeastOnceDelivery; + bool WillRetain = true; + + CC_MqttsnWillConfig willConfig; + apiConnectInitConfigWill(&willConfig); + willConfig.m_topic = WillTopic.c_str(); + willConfig.m_data = WillData.data(); + willConfig.m_dataLen = static_cast(WillData.size()); + willConfig.m_qos = WillQos; + willConfig.m_retain = WillRetain; + + ec = apiConnectConfigWill(connect, &willConfig); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + ec = unitTestConnectSend(connect); + + { + auto sentMsg = unitTestPopOutputMessage(); + auto* connectMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(connectMsg, nullptr); + TS_ASSERT_EQUALS(connectMsg->field_clientId().value(), ClientId); + TS_ASSERT_EQUALS(connectMsg->field_duration().value(), 60U); + TS_ASSERT(connectMsg->field_flags().field_mid().getBitValue_CleanSession()); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(!unitTestHasConnectCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestWilltopicreqMsg willtopicreqMsg; + unitTestClientInputMessage(client, willtopicreqMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willtopicMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willtopicMsg, nullptr); + TS_ASSERT(willtopicMsg->field_flags().doesExist()); + TS_ASSERT_EQUALS(static_cast(willtopicMsg->field_flags().field().field_qos().getValue()), WillQos); + TS_ASSERT_EQUALS(willtopicMsg->field_flags().field().field_mid().getBitValue_Retain(), WillRetain); + TS_ASSERT_EQUALS(willtopicMsg->field_willTopic().value(), WillTopic); + } + + TS_ASSERT(!unitTestHasConnectCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestWillmsgreqMsg willmsgreqMsg; + unitTestClientInputMessage(client, willmsgreqMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willmsgMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willmsgMsg, nullptr); + TS_ASSERT_EQUALS(willmsgMsg->field_willMsg().value(), WillData); + } + + TS_ASSERT(!unitTestHasConnectCompleteReport()); + TS_ASSERT(!apiIsConnected(client)); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willmsgMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willmsgMsg, nullptr); + TS_ASSERT_EQUALS(willmsgMsg->field_willMsg().value(), WillData); + } + + TS_ASSERT(!unitTestHasConnectCompleteReport()); + TS_ASSERT(!apiIsConnected(client)); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); + + TS_ASSERT(unitTestHasConnectCompleteReport()); + auto* connectReport = unitTestConnectCompleteReport(); + TS_ASSERT_EQUALS(connectReport->m_status, CC_MqttsnAsyncOpStatus_Timeout) + unitTestPopConnectCompleteReport(); + + TS_ASSERT(!unitTestHasTickReq()); + TS_ASSERT(!apiIsConnected(client)); +} + +void UnitTestConnect::test6() +{ + // Testing timeout on connect without retries + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + auto connect = apiConnectPrepare(client); + TS_ASSERT_DIFFERS(connect, nullptr); + + auto ec = apiConnectSetRetryCount(connect, 0U); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + const std::string ClientId("bla"); + + CC_MqttsnConnectConfig config; + apiConnectInitConfig(&config); + TS_ASSERT_EQUALS(config.m_duration, 60U); + config.m_clientId = ClientId.c_str(); + config.m_cleanSession = true; + + ec = apiConnectConfig(connect, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + ec = unitTestConnectSend(connect); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* connectMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(connectMsg, nullptr); + TS_ASSERT_EQUALS(connectMsg->field_clientId().value(), ClientId); + TS_ASSERT_EQUALS(connectMsg->field_duration().value(), 60U); + TS_ASSERT(connectMsg->field_flags().field_mid().getBitValue_CleanSession()); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(!unitTestHasConnectCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); + + TS_ASSERT(unitTestHasConnectCompleteReport()); + auto* connectReport = unitTestConnectCompleteReport(); + TS_ASSERT_EQUALS(connectReport->m_status, CC_MqttsnAsyncOpStatus_Timeout) + unitTestPopConnectCompleteReport(); + + TS_ASSERT(!unitTestHasTickReq()); + TS_ASSERT(!apiIsConnected(client)); +} \ No newline at end of file diff --git a/client/lib/test/UnitTestDefaultBase.cpp b/client/lib/test/UnitTestDefaultBase.cpp index bb133a0c..612915f3 100644 --- a/client/lib/test/UnitTestDefaultBase.cpp +++ b/client/lib/test/UnitTestDefaultBase.cpp @@ -47,6 +47,7 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_connect_send = &cc_mqttsn_client_connect_send; funcs.m_connect_cancel = &cc_mqttsn_client_connect_cancel; funcs.m_connect = &cc_mqttsn_client_connect; + funcs.m_is_connected = &cc_mqttsn_client_is_connected; funcs.m_set_next_tick_program_callback = &cc_mqttsn_client_set_next_tick_program_callback; funcs.m_set_cancel_next_tick_wait_callback = &cc_mqttsn_client_set_cancel_next_tick_wait_callback; diff --git a/client/lib/test/UnitTestGwDiscover.th b/client/lib/test/UnitTestGwDiscover.th index 1d9e5012..16947177 100644 --- a/client/lib/test/UnitTestGwDiscover.th +++ b/client/lib/test/UnitTestGwDiscover.th @@ -212,7 +212,7 @@ void UnitTestGwDiscover::test5() auto ec = apiSearchSetRetryPeriod(search, RetryPeriod); TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); - ec = apiSearchSetRetryCount(search, 2U); + ec = apiSearchSetRetryCount(search, 1U); TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); ec = apiSearchSetBroadcastRadius(search, BroadcastRadius); TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); From 72582d697234d545f2f49768dfaa997f35d35952 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 5 Jul 2024 09:01:06 +1000 Subject: [PATCH 053/106] Added "disconnect" operation to the new client. --- client/lib/CMakeLists.txt | 1 + client/lib/include/cc_mqttsn_client/common.h | 17 ++ client/lib/src/ClientImpl.cpp | 114 ++++++------ client/lib/src/ClientImpl.h | 14 +- client/lib/src/ExtConfig.h | 8 +- client/lib/src/op/DisconnectOp.cpp | 159 ++++++++++++++++ client/lib/src/op/DisconnectOp.h | 58 ++++++ client/lib/src/op/Op.h | 2 +- client/lib/templ/client.cpp.templ | 75 ++++++++ client/lib/templ/client.h.templ | 72 ++++++++ client/lib/test/CMakeLists.txt | 2 +- client/lib/test/UnitTestCommonBase.cpp | 130 +++++++++++++ client/lib/test/UnitTestCommonBase.h | 31 ++++ client/lib/test/UnitTestDefaultBase.cpp | 8 + client/lib/test/UnitTestDisconnect.th | 184 +++++++++++++++++++ 15 files changed, 810 insertions(+), 65 deletions(-) create mode 100644 client/lib/src/op/DisconnectOp.cpp create mode 100644 client/lib/src/op/DisconnectOp.h create mode 100644 client/lib/test/UnitTestDisconnect.th diff --git a/client/lib/CMakeLists.txt b/client/lib/CMakeLists.txt index 88e6653f..ee1cdb06 100644 --- a/client/lib/CMakeLists.txt +++ b/client/lib/CMakeLists.txt @@ -166,6 +166,7 @@ function (gen_lib_mqttsn_client config_file) message (STATUS "Defining library ${lib_name}") set (src src/op/ConnectOp.cpp + src/op/DisconnectOp.cpp src/op/KeepAliveOp.cpp src/op/Op.cpp src/op/SearchOp.cpp diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index d154ac26..ee6ee27e 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -140,6 +140,15 @@ struct CC_MqttsnConnect; /// @ingroup "connect". typedef struct CC_MqttsnConnect* CC_MqttsnConnectHandle; +/// @brief Declaration of the hidden structure used to define @ref CC_MqttsnDisconnectHandle +/// @ingroup disconnect +struct CC_MqttsnDisconnect; + +/// @brief Handle for "disconnect" operation. +/// @details Returned by @b cc_mqttsn_client_disconnect_prepare() function. +/// @ingroup "disconnect". +typedef struct CC_MqttsnDisconnect* CC_MqttsnDisconnectHandle; + /// @brief Type used to hold Topic ID value. typedef unsigned short CC_MqttsnTopicId; @@ -288,6 +297,14 @@ typedef void (*CC_MqttsnSearchCompleteCb)(void* data, CC_MqttsnAsyncOpStatus sta /// @ingroup connect typedef void (*CC_MqttsnConnectCompleteCb)(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info); +/// @brief Callback used to report completion of the disconnect operation. +/// @param[in] data Pointer to user data object, passed as the last parameter to +/// the request call. +/// @param[in] status Status of the "disconnect" operation. +/// @post The data members of the reported response can NOT be accessed after the function returns. +/// @ingroup disconnect +typedef void (*CC_MqttsnDisconnectCompleteCb)(void* data, CC_MqttsnAsyncOpStatus status); + #ifdef __cplusplus } diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index cb2bb1ee..dc7b1bf3 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -169,6 +169,13 @@ op::ConnectOp* ClientImpl::connectPrepare(CC_MqttsnErrorCode* ec) break; } + if (!m_disconnectOps.empty()) { + // Already allocated + errorLog("Another disconnect operation is in progress."); + updateEc(ec, CC_MqttsnErrorCode_Busy); + break; + } + if (m_sessionState.m_disconnecting) { errorLog("Session disconnection is in progress, cannot initiate connection."); updateEc(ec, CC_MqttsnErrorCode_Disconnecting); @@ -210,62 +217,63 @@ op::ConnectOp* ClientImpl::connectPrepare(CC_MqttsnErrorCode* ec) return op; } -// op::DisconnectOp* ClientImpl::disconnectPrepare(CC_MqttsnErrorCode* ec) -// { -// op::DisconnectOp* disconnectOp = nullptr; -// do { -// if (!m_sessionState.m_connected) { -// errorLog("Client must be connected to allow disconnect."); -// updateEc(ec, CC_MqttsnErrorCode_NotConnected); -// break; -// } +op::DisconnectOp* ClientImpl::disconnectPrepare(CC_MqttsnErrorCode* ec) +{ + op::DisconnectOp* op = nullptr; + do { + if (!m_sessionState.m_connected) { + errorLog("Client must be connected to allow disconnect."); + updateEc(ec, CC_MqttsnErrorCode_NotConnected); + break; + } -// if (!m_disconnectOps.empty()) { -// errorLog("Another disconnect operation is in progress."); -// updateEc(ec, CC_MqttsnErrorCode_Busy); -// break; -// } + if (!m_disconnectOps.empty()) { + errorLog("Another disconnect operation is in progress."); + updateEc(ec, CC_MqttsnErrorCode_Busy); + break; + } -// if (m_sessionState.m_disconnecting) { -// errorLog("Session disconnection is in progress, cannot initiate disconnection."); -// updateEc(ec, CC_MqttsnErrorCode_Disconnecting); -// break; -// } + if (!m_connectOps.empty()) { + // Already allocated + errorLog("Another connect operation is in progress."); + updateEc(ec, CC_MqttsnErrorCode_Busy); + break; + } -// if (m_clientState.m_networkDisconnected) { -// errorLog("Network is disconnected."); -// updateEc(ec, CC_MqttsnErrorCode_NetworkDisconnected); -// break; -// } + if (m_sessionState.m_disconnecting) { + errorLog("Session disconnection is in progress, cannot initiate disconnection."); + updateEc(ec, CC_MqttsnErrorCode_Disconnecting); + break; + } -// if (m_ops.max_size() <= m_ops.size()) { -// errorLog("Cannot start disconnect operation, retry in next event loop iteration."); -// updateEc(ec, CC_MqttsnErrorCode_RetryLater); -// break; -// } + if (m_ops.max_size() <= m_ops.size()) { + errorLog("Cannot start disconnect operation, retry in next event loop iteration."); + updateEc(ec, CC_MqttsnErrorCode_RetryLater); + break; + } -// if (m_preparationLocked) { -// errorLog("Another operation is being prepared, cannot prepare \"disconnect\" without \"send\" or \"cancel\" of the previous."); -// updateEc(ec, CC_MqttsnErrorCode_PreparationLocked); -// break; -// } + if (m_preparationLocked) { + errorLog("Another operation is being prepared, cannot prepare \"disconnect\" without \"send\" or \"cancel\" of the previous."); + updateEc(ec, CC_MqttsnErrorCode_PreparationLocked); + break; + } -// auto ptr = m_disconnectOpsAlloc.alloc(*this); -// if (!ptr) { -// errorLog("Cannot allocate new disconnect operation."); -// updateEc(ec, CC_MqttsnErrorCode_OutOfMemory); -// break; -// } + auto ptr = m_disconnectOpsAlloc.alloc(*this); + if (!ptr) { + errorLog("Cannot allocate new disconnect operation."); + updateEc(ec, CC_MqttsnErrorCode_OutOfMemory); + break; + } -// m_preparationLocked = true; -// m_ops.push_back(ptr.get()); -// m_disconnectOps.push_back(std::move(ptr)); -// disconnectOp = m_disconnectOps.back().get(); -// updateEc(ec, CC_MqttsnErrorCode_Success); -// } while (false); + m_preparationLocked = true; + m_ops.push_back(ptr.get()); + m_disconnectOps.push_back(std::move(ptr)); + op = m_disconnectOps.back().get(); + updateEc(ec, CC_MqttsnErrorCode_Success); + } while (false); -// return disconnectOp; -// } + return op; +} // op::SubscribeOp* ClientImpl::subscribePrepare(CC_MqttsnErrorCode* ec) // { @@ -829,7 +837,7 @@ void ClientImpl::opComplete(const op::Op* op) /* Type_Search */ &ClientImpl::opComplete_Search, /* Type_Connect */ &ClientImpl::opComplete_Connect, /* Type_KeepAlive */ &ClientImpl::opComplete_KeepAlive, - // /* Type_Disconnect */ &ClientImpl::opComplete_Disconnect, + /* Type_Disconnect */ &ClientImpl::opComplete_Disconnect, // /* Type_Subscribe */ &ClientImpl::opComplete_Subscribe, // /* Type_Unsubscribe */ &ClientImpl::opComplete_Unsubscribe, // /* Type_Recv */ &ClientImpl::opComplete_Recv, @@ -1172,10 +1180,10 @@ void ClientImpl::opComplete_KeepAlive(const op::Op* op) eraseFromList(op, m_keepAliveOps); } -// void ClientImpl::opComplete_Disconnect(const op::Op* op) -// { -// eraseFromList(op, m_disconnectOps); -// } +void ClientImpl::opComplete_Disconnect(const op::Op* op) +{ + eraseFromList(op, m_disconnectOps); +} // void ClientImpl::opComplete_Subscribe(const op::Op* op) // { diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index 192e49cd..38fea9b9 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -18,7 +18,7 @@ #include "TimerMgr.h" #include "op/ConnectOp.h" -// #include "op/DisconnectOp.h" +#include "op/DisconnectOp.h" #include "op/KeepAliveOp.h" #include "op/Op.h" // #include "op/RecvOp.h" @@ -70,7 +70,7 @@ class ClientImpl final : public ProtMsgHandler op::SearchOp* searchPrepare(CC_MqttsnErrorCode* ec); op::ConnectOp* connectPrepare(CC_MqttsnErrorCode* ec); - // op::DisconnectOp* disconnectPrepare(CC_MqttsnErrorCode* ec); + op::DisconnectOp* disconnectPrepare(CC_MqttsnErrorCode* ec); // op::SubscribeOp* subscribePrepare(CC_MqttsnErrorCode* ec); // op::UnsubscribeOp* unsubscribePrepare(CC_MqttsnErrorCode* ec); // op::SendOp* publishPrepare(CC_MqttsnErrorCode* ec); @@ -240,8 +240,8 @@ class ClientImpl final : public ProtMsgHandler using KeepAliveOpAlloc = ObjAllocator; using KeepAliveOpsList = ObjListType; - // using DisconnectOpAlloc = ObjAllocator; - // using DisconnectOpsList = ObjListType; + using DisconnectOpAlloc = ObjAllocator; + using DisconnectOpsList = ObjListType; // using SubscribeOpAlloc = ObjAllocator; // using SubscribeOpsList = ObjListType; @@ -282,7 +282,7 @@ class ClientImpl final : public ProtMsgHandler void opComplete_Search(const op::Op* op); void opComplete_Connect(const op::Op* op); void opComplete_KeepAlive(const op::Op* op); - // void opComplete_Disconnect(const op::Op* op); + void opComplete_Disconnect(const op::Op* op); // void opComplete_Subscribe(const op::Op* op); // void opComplete_Unsubscribe(const op::Op* op); // void opComplete_Recv(const op::Op* op); @@ -346,8 +346,8 @@ class ClientImpl final : public ProtMsgHandler KeepAliveOpAlloc m_keepAliveOpsAlloc; KeepAliveOpsList m_keepAliveOps; - // DisconnectOpAlloc m_disconnectOpsAlloc; - // DisconnectOpsList m_disconnectOps; + DisconnectOpAlloc m_disconnectOpsAlloc; + DisconnectOpsList m_disconnectOps; // SubscribeOpAlloc m_subscribeOpsAlloc; // SubscribeOpsList m_subscribeOps; diff --git a/client/lib/src/ExtConfig.h b/client/lib/src/ExtConfig.h index 4a1aa1e3..a45259b9 100644 --- a/client/lib/src/ExtConfig.h +++ b/client/lib/src/ExtConfig.h @@ -24,7 +24,7 @@ struct ExtConfig : public Config static constexpr unsigned ConnectOpTimers = 1U; static constexpr unsigned KeepAliveOpTimers = 3U; static constexpr unsigned DisconnectOpsLimit = HasDynMemAlloc ? 0 : 1U; - static constexpr unsigned DisconnectOpTimers = 0U; + static constexpr unsigned DisconnectOpTimers = 1U; static constexpr unsigned SubscribeOpTimers = 1U; static constexpr unsigned UnsubscribeOpTimers = 1U; static constexpr unsigned RecvOpsLimit = MaxQos < 2 ? 1U : (ReceiveMaxLimit == 0U ? 0U : ReceiveMaxLimit + 1U); @@ -34,8 +34,8 @@ struct ExtConfig : public Config static constexpr bool HasOpsLimit = (SearchOpsLimit > 0U) && (ConnectOpsLimit > 0U) && - (KeepAliveOpsLimit > 0U) /* && - (DisconnectOpsLimit > 0U) && + (KeepAliveOpsLimit > 0U) && + (DisconnectOpsLimit > 0U) /* && (SubscribeOpsLimit > 0U) && (UnsubscribeOpsLimit > 0U) && (RecvOpsLimit > 0U) && @@ -44,6 +44,7 @@ struct ExtConfig : public Config (DiscoveryTimers) + (SearchOpsLimit * SearchOpTimers) + (ConnectOpsLimit * ConnectOpTimers) + + (DisconnectOpsLimit * DisconnectOpTimers) + (KeepAliveOpsLimit * KeepAliveOpTimers) + (DisconnectOpsLimit * DisconnectOpTimers) + (SubscribeOpsLimit * SubscribeOpTimers) + @@ -73,6 +74,7 @@ struct ExtConfig : public Config static_assert(HasDynMemAlloc || (TimersLimit > 0U)); static_assert(HasDynMemAlloc || (ConnectOpsLimit > 0U)); static_assert(HasDynMemAlloc || (KeepAliveOpsLimit > 0U)); + static_assert(HasDynMemAlloc || (DisconnectOpsLimit > 0U)); // static_assert(HasDynMemAlloc || (RecvOpsLimit > 0U)); // static_assert(HasDynMemAlloc || (SendOpsLimit > 0U)); static_assert(HasDynMemAlloc || (OpsLimit > 0U)); diff --git a/client/lib/src/op/DisconnectOp.cpp b/client/lib/src/op/DisconnectOp.cpp new file mode 100644 index 00000000..4c6766cb --- /dev/null +++ b/client/lib/src/op/DisconnectOp.cpp @@ -0,0 +1,159 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "op/DisconnectOp.h" +#include "ClientImpl.h" + +#include "comms/util/assign.h" +#include "comms/util/ScopeGuard.h" +#include "comms/units.h" + +#include +#include + +namespace cc_mqttsn_client +{ + +namespace op +{ + +namespace +{ + +inline DisconnectOp* asDisconnectOp(void* data) +{ + return reinterpret_cast(data); +} + +} // namespace + + +DisconnectOp::DisconnectOp(ClientImpl& client) : + Base(client), + m_timer(client.timerMgr().allocTimer()) +{ +} + +CC_MqttsnErrorCode DisconnectOp::send(CC_MqttsnDisconnectCompleteCb cb, void* cbData) +{ + client().allowNextPrepare(); + auto completeOnError = + comms::util::makeScopeGuard( + [this]() + { + opComplete(); + }); + + if (cb == nullptr) { + errorLog("Disconnect completion callback is not provided."); + return CC_MqttsnErrorCode_BadParam; + } + + if (!m_timer.isValid()) { + errorLog("The library cannot allocate required number of timers."); + return CC_MqttsnErrorCode_InternalError; + } + + auto guard = client().apiEnter(); + m_cb = cb; + m_cbData = cbData; + + auto ec = sendInternal(); + if (ec != CC_MqttsnErrorCode_Success) { + return ec; + } + + completeOnError.release(); + return CC_MqttsnErrorCode_Success; +} + +CC_MqttsnErrorCode DisconnectOp::cancel() +{ + if (m_cb == nullptr) { + // hasn't been sent yet + client().allowNextPrepare(); + } + + opComplete(); + return CC_MqttsnErrorCode_Success; +} + +void DisconnectOp::handle([[maybe_unused]] DisconnectMsg& msg) +{ + m_timer.cancel(); + + auto& cl = client(); + auto onExit = + comms::util::makeScopeGuard( + [&cl]() + { + cl.gatewayDisconnected(); + }); + + completeOpInternal(CC_MqttsnAsyncOpStatus_Complete); +} + +Op::Type DisconnectOp::typeImpl() const +{ + return Type_Disconnect; +} + +void DisconnectOp::terminateOpImpl(CC_MqttsnAsyncOpStatus status) +{ + completeOpInternal(status); +} + +void DisconnectOp::completeOpInternal(CC_MqttsnAsyncOpStatus status) +{ + auto cb = m_cb; + auto* cbData = m_cbData; + opComplete(); // mustn't access data members after destruction + if (cb != nullptr) { + cb(cbData, status); + } +} + +void DisconnectOp::restartTimer() +{ + m_timer.wait(getRetryPeriod(), &DisconnectOp::opTimeoutCb, this); +} + +CC_MqttsnErrorCode DisconnectOp::sendInternal() +{ + auto ec = sendMessage(m_disconnectMsg); + if (ec == CC_MqttsnErrorCode_Success) { + restartTimer(); + } + + return ec; +} + +void DisconnectOp::timeoutInternal() +{ + if (getRetryCount() == 0U) { + errorLog("All retries of the disconnect operation have been exhausted."); + completeOpInternal(CC_MqttsnAsyncOpStatus_Timeout); + return; + } + + auto ec = sendInternal(); + if (ec != CC_MqttsnErrorCode_Success) { + completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); + return; + } + + decRetryCount(); +} + +void DisconnectOp::opTimeoutCb(void* data) +{ + asDisconnectOp(data)->timeoutInternal(); +} + +} // namespace op + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/op/DisconnectOp.h b/client/lib/src/op/DisconnectOp.h new file mode 100644 index 00000000..9b8b4972 --- /dev/null +++ b/client/lib/src/op/DisconnectOp.h @@ -0,0 +1,58 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "op/Op.h" +#include "ExtConfig.h" +#include "ProtocolDefs.h" + +#include "TimerMgr.h" + +namespace cc_mqttsn_client +{ + +namespace op +{ + +class DisconnectOp final : public Op +{ + using Base = Op; +public: + explicit DisconnectOp(ClientImpl& client); + + CC_MqttsnErrorCode send(CC_MqttsnDisconnectCompleteCb cb, void* cbData); + CC_MqttsnErrorCode cancel(); + + using Base::handle; + void handle(DisconnectMsg& msg) override; + +protected: + virtual Type typeImpl() const override; + virtual void terminateOpImpl(CC_MqttsnAsyncOpStatus status) override; + +private: + + void completeOpInternal(CC_MqttsnAsyncOpStatus status); + void restartTimer(); + CC_MqttsnErrorCode sendInternal(); + void timeoutInternal(); + + static void opTimeoutCb(void* data); + + DisconnectMsg m_disconnectMsg; + TimerMgr::Timer m_timer; + CC_MqttsnDisconnectCompleteCb m_cb = nullptr; + void* m_cbData = nullptr; + + static_assert(ExtConfig::DisconnectOpTimers == 1U); +}; + +} // namespace op + + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/op/Op.h b/client/lib/src/op/Op.h index 858a22f2..c473fe96 100644 --- a/client/lib/src/op/Op.h +++ b/client/lib/src/op/Op.h @@ -31,7 +31,7 @@ class Op : public ProtMsgHandler Type_Search, Type_Connect, Type_KeepAlive, - // Type_Disconnect, + Type_Disconnect, // Type_Subscribe, // Type_Unsubscribe, // Type_Recv, diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index 78595dfb..849ea7c5 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -17,6 +17,7 @@ struct CC_MqttsnClient {}; struct CC_MqttsnSearch {}; struct CC_MqttsnConnect {}; +struct CC_MqttsnDisconnect {}; namespace { @@ -59,6 +60,16 @@ inline CC_MqttsnConnectHandle handleFromConnectOp(cc_mqttsn_client::op::ConnectO return reinterpret_cast(op); } +inline cc_mqttsn_client::op::DisconnectOp* disconnectOpFromHandle(CC_MqttsnDisconnectHandle handle) +{ + return reinterpret_cast(handle); +} + +inline CC_MqttsnDisconnectHandle handleFromDisconnectOp(cc_mqttsn_client::op::DisconnectOp* op) +{ + return reinterpret_cast(op); +} + } // namespace CC_MqttsnClientHandle cc_mqttsn_##NAME##client_alloc() @@ -554,6 +565,70 @@ bool cc_mqttsn_##NAME##client_is_connected(CC_MqttsnClientHandle client) return clientFromHandle(client)->sessionState().m_connected; } +CC_MqttsnDisconnectHandle cc_mqttsn_##NAME##client_disconnect_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec) +{ + COMMS_ASSERT(client != nullptr); + return handleFromDisconnectOp(clientFromHandle(client)->disconnectPrepare(ec)); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect_set_retry_period(CC_MqttsnDisconnectHandle handle, unsigned ms) +{ + COMMS_ASSERT(handle != nullptr); + if (ms == 0U) { + disconnectOpFromHandle(handle)->client().errorLog("The retry period must be greater than 0"); + return CC_MqttsnErrorCode_BadParam; + } + + COMMS_ASSERT(handle != nullptr); + disconnectOpFromHandle(handle)->setRetryPeriod(ms); + return CC_MqttsnErrorCode_Success; +} + +unsigned cc_mqttsn_##NAME##client_disconnect_get_retry_period(CC_MqttsnDisconnectHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return disconnectOpFromHandle(handle)->getRetryPeriod(); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect_set_retry_count(CC_MqttsnDisconnectHandle handle, unsigned count) +{ + COMMS_ASSERT(handle != nullptr); + disconnectOpFromHandle(handle)->setRetryCount(count); + return CC_MqttsnErrorCode_Success; +} + +unsigned cc_mqttsn_##NAME##client_disconnect_get_retry_count(CC_MqttsnDisconnectHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return disconnectOpFromHandle(handle)->getRetryCount(); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect_send(CC_MqttsnDisconnectHandle handle, CC_MqttsnDisconnectCompleteCb cb, void* cbData) +{ + COMMS_ASSERT(handle != nullptr); + return disconnectOpFromHandle(handle)->send(cb, cbData); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect_cancel(CC_MqttsnDisconnectHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return disconnectOpFromHandle(handle)->cancel(); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect( + CC_MqttsnClientHandle client, + CC_MqttsnDisconnectCompleteCb cb, + void* cbData) +{ + auto ec = CC_MqttsnErrorCode_Success; + auto disconnect = cc_mqttsn_##NAME##client_disconnect_prepare(client, &ec); + if (disconnect == nullptr) { + return ec; + } + + return cc_mqttsn_##NAME##client_disconnect_send(disconnect, cb, cbData); +} + // --------------------- Callbacks --------------------- void cc_mqttsn_##NAME##client_set_next_tick_program_callback( diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index 1a70f27d..c5432e9e 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -391,6 +391,78 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect( /// @ingroup connect bool cc_mqttsn_##NAME##client_is_connected(CC_MqttsnClientHandle client); +/// @brief Prepare "disconnect" operation. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[out] ec Error code reporting result of the operation. Can be NULL. +/// @return Handle of the "disconnect" operation, will be NULL in case of failure. To analyze the reason failure use "ec" output parameter. +/// @post The "disconnect" operation is allocated, use either @ref cc_mqttsn_##NAME##client_disconnect_send() +/// or @ref cc_mqttsn_##NAME##client_disconnect_cancel() to prevent memory leaks. +/// @ingroup disconnect +CC_MqttsnDisconnectHandle cc_mqttsn_##NAME##client_disconnect_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec); + +/// @brief Configure the retry period for the "disconnect" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_disconnect_prepare() function. +/// @param[in] ms Retry period in @b milliseconds. +/// @return Result code of the call. +/// @ingroup disconnect +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect_set_retry_period(CC_MqttsnDisconnectHandle handle, unsigned ms); + +/// @brief Retrieve the configured retry period for the "disconnect" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_disconnect_prepare() function. +/// @return Retry period duration in @b milliseconds. +/// @ingroup disconnect +unsigned cc_mqttsn_##NAME##client_disconnect_get_retry_period(CC_MqttsnDisconnectHandle handle); + +/// @brief Configure the retry count for the "disconnect" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_disconnect_prepare() function. +/// @param[in] count Number of retries. +/// @return Result code of the call. +/// @ingroup disconnect +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect_set_retry_count(CC_MqttsnDisconnectHandle handle, unsigned count); + +/// @brief Retrieve the configured retry count for the "disconnect" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_disconnect_prepare() function. +/// @return Number of retries. +/// @ingroup disconnect +unsigned cc_mqttsn_##NAME##client_disconnect_get_retry_count(CC_MqttsnDisconnectHandle handle); + +/// @brief Send the "disconnect" operation +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_disconnect_prepare() function. +/// @param[in] cb Callback to be invoked when "disconnect" operation is complete. +/// @param[in] cbData Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +/// @return Result code of the call. +/// @post The handle of the "disconnect" operation can be discarded. +/// @post The provided callback will be invoked when the "disconnect" operation is complete if and only if +/// the function returns @ref CC_MqttsnErrorCode_Success. +/// @ingroup disconnect +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect_send(CC_MqttsnDisconnectHandle handle, CC_MqttsnDisconnectCompleteCb cb, void* cbData); + +/// @brief Cancel the allocated "disconnect" operation +/// @details In case the @ref cc_mqttsn_##NAME##client_disconnect_send() function was successfully called before, +/// the operation is cancelled @b without callback invocation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_disconnect_prepare() function. +/// @return Result code of the call. +/// @post The handle of the "disconnect" operation is no longer valid and must be discarded. +/// @ingroup disconnect +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect_cancel(CC_MqttsnDisconnectHandle handle); + +/// @brief Prepare and send "disconnect" request in one go +/// @details Abstracts away sequence of the following functions invocation: +/// @li @ref cc_mqttsn_##NAME##client_disconnect_prepare() +/// @li @ref cc_mqttsn_##NAME##client_disconnect_send() +/// +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[in] cb Callback to be invoked when "disconnect" operation is complete. +/// @param[in] cbData Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +/// @return Result code of the call. +/// @ingroup disconnect +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect( + CC_MqttsnClientHandle client, + CC_MqttsnDisconnectCompleteCb cb, + void* cbData); + // --------------------- Callbacks --------------------- /// @brief Set callback to call when time measurement is required. diff --git a/client/lib/test/CMakeLists.txt b/client/lib/test/CMakeLists.txt index 2c593cb3..622af98f 100644 --- a/client/lib/test/CMakeLists.txt +++ b/client/lib/test/CMakeLists.txt @@ -41,7 +41,7 @@ if (TARGET cc::cc_mqttsn_client) cc_mqttsn_client_add_unit_test(UnitTestGwDiscover ${DEFAULT_BASE_LIB_NAME}) cc_mqttsn_client_add_unit_test(UnitTestConnect ${DEFAULT_BASE_LIB_NAME}) -# cc_mqttsn_client_add_unit_test(UnitTestDisconnect ${DEFAULT_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(UnitTestDisconnect ${DEFAULT_BASE_LIB_NAME}) # cc_mqttsn_client_add_unit_test(UnitTestPublish ${DEFAULT_BASE_LIB_NAME}) # cc_mqttsn_client_add_unit_test(UnitTestReceive ${DEFAULT_BASE_LIB_NAME}) # cc_mqttsn_client_add_unit_test(UnitTestSubscribe ${DEFAULT_BASE_LIB_NAME}) diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index 5ab04c21..f5ccfd95 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -67,6 +67,14 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_connect_cancel != nullptr); test_assert(m_funcs.m_connect != nullptr); test_assert(m_funcs.m_is_connected != nullptr); + test_assert(m_funcs.m_disconnect_prepare != nullptr); + test_assert(m_funcs.m_disconnect_set_retry_period != nullptr); + test_assert(m_funcs.m_disconnect_get_retry_period != nullptr); + test_assert(m_funcs.m_disconnect_set_retry_count != nullptr); + test_assert(m_funcs.m_disconnect_get_retry_count != nullptr); + test_assert(m_funcs.m_disconnect_send != nullptr); + test_assert(m_funcs.m_disconnect_cancel != nullptr); + test_assert(m_funcs.m_disconnect != nullptr); test_assert(m_funcs.m_set_next_tick_program_callback != nullptr); test_assert(m_funcs.m_set_cancel_next_tick_wait_callback != nullptr); @@ -382,6 +390,112 @@ CC_MqttsnErrorCode UnitTestCommonBase::unitTestConnectSend(CC_MqttsnConnectHandl return m_funcs.m_connect_send(connect, &UnitTestCommonBase::unitTestConnectCompleteCb, this); } +void UnitTestCommonBase::unitTestDoConnect(CC_MqttsnClient* client, const CC_MqttsnConnectConfig* config, const CC_MqttsnWillConfig* willConfig) +{ + auto ec = m_funcs.m_connect(client, config, willConfig, &UnitTestCommonBase::unitTestConnectCompleteCb, this); + test_assert(ec == CC_MqttsnErrorCode_Success); + + { + test_assert(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* connectMsg = dynamic_cast(sentMsg.get()); + test_assert(connectMsg != nullptr); + if (config != nullptr) { + test_assert(connectMsg->field_clientId().value() == config->m_clientId); + test_assert(connectMsg->field_duration().value() == config->m_duration); + test_assert(connectMsg->field_flags().field_mid().getBitValue_CleanSession() == config->m_cleanSession); + } + test_assert(!unitTestHasOutputData()); + } + + test_assert(!unitTestHasConnectCompleteReport()); + test_assert(unitTestHasTickReq()); + unitTestTick(client, 100); + + if (willConfig != nullptr) { + UnitTestWilltopicreqMsg willtopicreqMsg; + unitTestClientInputMessage(client, willtopicreqMsg); + + { + test_assert(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willtopicMsg = dynamic_cast(sentMsg.get()); + test_assert(willtopicMsg != nullptr); + test_assert(willtopicMsg->field_flags().doesExist()); + test_assert(static_cast(willtopicMsg->field_flags().field().field_qos().getValue()) == willConfig->m_qos); + test_assert(willtopicMsg->field_flags().field().field_mid().getBitValue_Retain() == willConfig->m_retain); + test_assert((willConfig->m_topic == nullptr) || (willtopicMsg->field_willTopic().value() == willConfig->m_topic)); + test_assert(!unitTestHasOutputData()); + } + + test_assert(!unitTestHasConnectCompleteReport()); + test_assert(unitTestHasTickReq()); + unitTestTick(client, 100); + + UnitTestWillmsgreqMsg willmsgreqMsg; + unitTestClientInputMessage(client, willmsgreqMsg); + + { + test_assert(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willmsgMsg = dynamic_cast(sentMsg.get()); + test_assert(willmsgMsg != nullptr); + test_assert((willConfig->m_data == nullptr) || (willmsgMsg->field_willMsg().value() == UnitTestData(willConfig->m_data, willConfig->m_data + willConfig->m_dataLen))); + } + + test_assert(!unitTestHasConnectCompleteReport()); + test_assert(!apiIsConnected(client)); + test_assert(unitTestHasTickReq()); + unitTestTick(client, 100); + } + + UnitTestConnackMsg connackMsg; + connackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, connackMsg); + + test_assert(unitTestHasConnectCompleteReport()); + auto* connectReport = unitTestConnectCompleteReport(); + test_assert(connectReport->m_status== CC_MqttsnAsyncOpStatus_Complete) + test_assert(connectReport->m_info.m_returnCode == CC_MqttsnReturnCode_Accepted) + unitTestPopConnectCompleteReport(); + test_assert(apiIsConnected(client)); +} + +void UnitTestCommonBase::unitTestDoConnectBasic(CC_MqttsnClient* client, const std::string& clientId, bool cleanSession) +{ + CC_MqttsnConnectConfig config; + apiConnectInitConfig(&config); + config.m_clientId = clientId.c_str(); + config.m_cleanSession = cleanSession; + unitTestDoConnect(client, &config, nullptr); +} + +bool UnitTestCommonBase::unitTestHasDisconnectCompleteReport() const +{ + return !m_data.m_disconnectCompleteReports.empty(); +} + +const UnitTestCommonBase::UnitTestDisconnectCompleteReport* UnitTestCommonBase::unitTestDisconnectCompleteReport(bool mustExist) const +{ + if (!unitTestHasDisconnectCompleteReport()) { + test_assert(!mustExist); + return nullptr; + } + + return &m_data.m_disconnectCompleteReports.front(); +} + +void UnitTestCommonBase::unitTestPopDisconnectCompleteReport() +{ + test_assert(unitTestHasDisconnectCompleteReport()); + m_data.m_disconnectCompleteReports.pop_front(); +} + +CC_MqttsnErrorCode UnitTestCommonBase::unitTestDisconnectSend(CC_MqttsnDisconnectHandle disconnect) +{ + return m_funcs.m_disconnect_send(disconnect, &UnitTestCommonBase::unitTestDisconnectCompleteCb, this); +} + void UnitTestCommonBase::apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen) { m_funcs.m_process_data(client, buf, bufLen); @@ -452,6 +566,16 @@ bool UnitTestCommonBase::apiIsConnected(CC_MqttsnClient* client) return m_funcs.m_is_connected(client); } +CC_MqttsnDisconnectHandle UnitTestCommonBase::apiDisconnectPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec) +{ + return m_funcs.m_disconnect_prepare(client, ec); +} + +CC_MqttsnErrorCode UnitTestCommonBase::apiDisconnectSetRetryCount(CC_MqttsnDisconnectHandle disconnect, unsigned count) +{ + return m_funcs.m_disconnect_set_retry_count(disconnect, count); +} + void UnitTestCommonBase::unitTestTickProgramCb(void* data, unsigned duration) { auto* thisPtr = asThis(data); @@ -537,4 +661,10 @@ void UnitTestCommonBase::unitTestConnectCompleteCb(void* data, CC_MqttsnAsyncOpS { auto* thisPtr = asThis(data); thisPtr->m_data.m_connectCompleteReports.emplace_back(status, info); +} + +void UnitTestCommonBase::unitTestDisconnectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status) +{ + auto* thisPtr = asThis(data); + thisPtr->m_data.m_disconnectCompleteReports.emplace_back(status); } \ No newline at end of file diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index 19d23a28..48a55b2f 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -57,6 +57,14 @@ class UnitTestCommonBase CC_MqttsnErrorCode (*m_connect_cancel)(CC_MqttsnConnectHandle) = nullptr; CC_MqttsnErrorCode (*m_connect)(CC_MqttsnClientHandle, const CC_MqttsnConnectConfig*, const CC_MqttsnWillConfig*, CC_MqttsnConnectCompleteCb, void*) = nullptr; bool (*m_is_connected)(CC_MqttsnClientHandle) = nullptr; + CC_MqttsnDisconnectHandle (*m_disconnect_prepare)(CC_MqttsnClientHandle, CC_MqttsnErrorCode*) = nullptr; + CC_MqttsnErrorCode (*m_disconnect_set_retry_period)(CC_MqttsnDisconnectHandle, unsigned ms) = nullptr; + unsigned (*m_disconnect_get_retry_period)(CC_MqttsnDisconnectHandle) = nullptr; + CC_MqttsnErrorCode (*m_disconnect_set_retry_count)(CC_MqttsnDisconnectHandle, unsigned count) = nullptr; + unsigned (*m_disconnect_get_retry_count)(CC_MqttsnDisconnectHandle) = nullptr; + CC_MqttsnErrorCode (*m_disconnect_send)(CC_MqttsnDisconnectHandle, CC_MqttsnDisconnectCompleteCb, void*) = nullptr; + CC_MqttsnErrorCode (*m_disconnect_cancel)(CC_MqttsnDisconnectHandle) = nullptr; + CC_MqttsnErrorCode (*m_disconnect)(CC_MqttsnClientHandle, CC_MqttsnDisconnectCompleteCb, void*) = nullptr; void (*m_set_next_tick_program_callback)(CC_MqttsnClientHandle, CC_MqttsnNextTickProgramCb, void*) = nullptr; void (*m_set_cancel_next_tick_wait_callback)(CC_MqttsnClientHandle, CC_MqttsnCancelNextTickWaitCb, void*) = nullptr; @@ -161,6 +169,15 @@ class UnitTestCommonBase using UnitTestConnectCompleteReportList = std::list; + struct UnitTestDisconnectCompleteReport + { + CC_MqttsnAsyncOpStatus m_status = CC_MqttsnAsyncOpStatus_ValuesLimit; + + UnitTestDisconnectCompleteReport(CC_MqttsnAsyncOpStatus status) : m_status(status) {}; + }; + + using UnitTestDisconnectCompleteReportList = std::list; + using UnitTestClientPtr = std::unique_ptr; void unitTestSetUp(); @@ -197,6 +214,14 @@ class UnitTestCommonBase void unitTestPopConnectCompleteReport(); CC_MqttsnErrorCode unitTestConnectSend(CC_MqttsnConnectHandle connect); + void unitTestDoConnect(CC_MqttsnClient* client, const CC_MqttsnConnectConfig* config, const CC_MqttsnWillConfig* willConfig); + void unitTestDoConnectBasic(CC_MqttsnClient* client, const std::string& clientId = std::string(), bool cleanSession = true); + + bool unitTestHasDisconnectCompleteReport() const; + const UnitTestDisconnectCompleteReport* unitTestDisconnectCompleteReport(bool mustExist = true) const; + void unitTestPopDisconnectCompleteReport(); + + CC_MqttsnErrorCode unitTestDisconnectSend(CC_MqttsnDisconnectHandle disconnect); void apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen); CC_MqttsnErrorCode apiSetDefaultRetryPeriod(CC_MqttsnClient* client, unsigned value); @@ -205,6 +230,7 @@ class UnitTestCommonBase CC_MqttsnErrorCode apiSearchSetRetryPeriod(CC_MqttsnSearchHandle search, unsigned value); CC_MqttsnErrorCode apiSearchSetRetryCount(CC_MqttsnSearchHandle search, unsigned value); CC_MqttsnErrorCode apiSearchSetBroadcastRadius(CC_MqttsnSearchHandle search, unsigned value); + CC_MqttsnConnectHandle apiConnectPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec = nullptr); CC_MqttsnErrorCode apiConnectSetRetryCount(CC_MqttsnConnectHandle connect, unsigned count); void apiConnectInitConfig(CC_MqttsnConnectConfig* config); @@ -213,6 +239,9 @@ class UnitTestCommonBase CC_MqttsnErrorCode apiConnectConfigWill(CC_MqttsnConnectHandle connect, const CC_MqttsnWillConfig* config); bool apiIsConnected(CC_MqttsnClient* client); + CC_MqttsnDisconnectHandle apiDisconnectPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec = nullptr); + CC_MqttsnErrorCode apiDisconnectSetRetryCount(CC_MqttsnDisconnectHandle disconnect, unsigned count); + protected: explicit UnitTestCommonBase(const LibFuncs& funcs); @@ -225,6 +254,7 @@ class UnitTestCommonBase UnitTestSearchCompleteReportsList m_searchCompleteReports; UnitTestSearchCompleteCbList m_searchCompleteCallbacks; UnitTestConnectCompleteReportList m_connectCompleteReports; + UnitTestDisconnectCompleteReportList m_disconnectCompleteReports; }; static void unitTestTickProgramCb(void* data, unsigned duration); @@ -237,6 +267,7 @@ class UnitTestCommonBase static void unitTestErrorLogCb(void* data, const char* msg); static void unitTestSearchCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info); static void unitTestConnectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info); + static void unitTestDisconnectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status); LibFuncs m_funcs; ClientData m_data; diff --git a/client/lib/test/UnitTestDefaultBase.cpp b/client/lib/test/UnitTestDefaultBase.cpp index 612915f3..403be6a1 100644 --- a/client/lib/test/UnitTestDefaultBase.cpp +++ b/client/lib/test/UnitTestDefaultBase.cpp @@ -48,6 +48,14 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_connect_cancel = &cc_mqttsn_client_connect_cancel; funcs.m_connect = &cc_mqttsn_client_connect; funcs.m_is_connected = &cc_mqttsn_client_is_connected; + funcs.m_disconnect_prepare = &cc_mqttsn_client_disconnect_prepare; + funcs.m_disconnect_set_retry_period = &cc_mqttsn_client_disconnect_set_retry_period; + funcs.m_disconnect_get_retry_period = &cc_mqttsn_client_disconnect_get_retry_period; + funcs.m_disconnect_set_retry_count = &cc_mqttsn_client_disconnect_set_retry_count; + funcs.m_disconnect_get_retry_count = &cc_mqttsn_client_disconnect_get_retry_count; + funcs.m_disconnect_send = &cc_mqttsn_client_disconnect_send; + funcs.m_disconnect_cancel = &cc_mqttsn_client_disconnect_cancel; + funcs.m_disconnect = &cc_mqttsn_client_disconnect; funcs.m_set_next_tick_program_callback = &cc_mqttsn_client_set_next_tick_program_callback; funcs.m_set_cancel_next_tick_wait_callback = &cc_mqttsn_client_set_cancel_next_tick_wait_callback; diff --git a/client/lib/test/UnitTestDisconnect.th b/client/lib/test/UnitTestDisconnect.th new file mode 100644 index 00000000..957a896f --- /dev/null +++ b/client/lib/test/UnitTestDisconnect.th @@ -0,0 +1,184 @@ +#include "UnitTestDefaultBase.h" +#include "UnitTestProtocolDefs.h" + +#include "comms/units.h" + +#include + +class UnitTestDisconnect : public CxxTest::TestSuite, public UnitTestDefaultBase +{ +public: + void test1(); + void test2(); + void test3(); + +private: + virtual void setUp() override + { + unitTestSetUp(); + } + + virtual void tearDown() override + { + unitTestTearDown(); + } +}; + +void UnitTestDisconnect::test1() +{ + // Testing basic disconnect + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + auto disconnect = apiDisconnectPrepare(client); + TS_ASSERT_DIFFERS(disconnect, nullptr); + + unitTestDisconnectSend(disconnect); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* disconnectMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(disconnectMsg, nullptr); + TS_ASSERT(disconnectMsg->field_duration().isMissing()); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestDisconnectMsg disconnectMsg; + unitTestClientInputMessage(client, disconnectMsg); + } + + TS_ASSERT(unitTestHasDisconnectCompleteReport()); + auto* disconnectReport = unitTestDisconnectCompleteReport(); + TS_ASSERT_EQUALS(disconnectReport->m_status, CC_MqttsnAsyncOpStatus_Complete) + unitTestPopDisconnectCompleteReport(); + + TS_ASSERT(!unitTestHasTickReq()); // No more keep alive + TS_ASSERT(!apiIsConnected(client)); +} + +void UnitTestDisconnect::test2() +{ + // Testing timeout disconnect + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + auto disconnect = apiDisconnectPrepare(client); + TS_ASSERT_DIFFERS(disconnect, nullptr); + auto ec = apiDisconnectSetRetryCount(disconnect, 1U); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestDisconnectSend(disconnect); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* disconnectMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(disconnectMsg, nullptr); + TS_ASSERT(disconnectMsg->field_duration().isMissing()); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* disconnectMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(disconnectMsg, nullptr); + TS_ASSERT(disconnectMsg->field_duration().isMissing()); + TS_ASSERT(!unitTestHasOutputData()); + } + + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + + TS_ASSERT(unitTestHasDisconnectCompleteReport()); + auto* disconnectReport = unitTestDisconnectCompleteReport(); + TS_ASSERT_EQUALS(disconnectReport->m_status, CC_MqttsnAsyncOpStatus_Timeout) + unitTestPopDisconnectCompleteReport(); + + TS_ASSERT(unitTestHasTickReq()); // Still hase keep alive + TS_ASSERT(apiIsConnected(client)); // Still connected +} + +void UnitTestDisconnect::test3() +{ + // Testing disconnect with retry + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + auto disconnect = apiDisconnectPrepare(client); + TS_ASSERT_DIFFERS(disconnect, nullptr); + auto ec = apiDisconnectSetRetryCount(disconnect, 1U); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestDisconnectSend(disconnect); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* disconnectMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(disconnectMsg, nullptr); + TS_ASSERT(disconnectMsg->field_duration().isMissing()); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* disconnectMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(disconnectMsg, nullptr); + TS_ASSERT(disconnectMsg->field_duration().isMissing()); + TS_ASSERT(!unitTestHasOutputData()); + } + + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestDisconnectMsg disconnectMsg; + unitTestClientInputMessage(client, disconnectMsg); + } + + TS_ASSERT(unitTestHasDisconnectCompleteReport()); + auto* disconnectReport = unitTestDisconnectCompleteReport(); + TS_ASSERT_EQUALS(disconnectReport->m_status, CC_MqttsnAsyncOpStatus_Complete) + unitTestPopDisconnectCompleteReport(); + + TS_ASSERT(!unitTestHasTickReq()); // No more keep alive + TS_ASSERT(!apiIsConnected(client)); +} From d21f86602aafa2607928ef718f87f7e661c604b5 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 5 Jul 2024 09:01:38 +1000 Subject: [PATCH 054/106] Using comms (v5.2.5) and cc.mqtt311.generated (v2.8) in github actions. --- .github/workflows/actions_build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index 3839c3d6..4ed5b663 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -3,9 +3,9 @@ name: Github Actions Build on: [push] env: - COMMS_TAG: v5.2.3 + COMMS_TAG: v5.2.5 CC_MQTTSN_TAG: develop - CC_MQTT311_TAG: v2.7 + CC_MQTT311_TAG: v2.8 jobs: build_gcc_ubuntu_22_04: From 3442dcf6f3c87222afc5d92f404b95002bc84d39 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 5 Jul 2024 09:02:20 +1000 Subject: [PATCH 055/106] Removed appveyor configuration. --- .appveyor.yml | 60 ------------------------------- script/appveyor_install.bat | 72 ------------------------------------- 2 files changed, 132 deletions(-) delete mode 100644 .appveyor.yml delete mode 100644 script/appveyor_install.bat diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index e7f6d129..00000000 --- a/.appveyor.yml +++ /dev/null @@ -1,60 +0,0 @@ -image: - - Visual Studio 2019 - - Visual Studio 2022 - -init: - - git config --global core.autocrlf input - -clone_folder: c:\projects\cc_mqttsn -shallow_clone: true - -platform: - - x64 - - x86 - -configuration: - - Debug - - Release - -environment: - COMMS_TAG: v5.2.3 - CC_MQTTSN_TAG: v2.7 - CC_MQTT311_TAG: v2.7 - - matrix: - - CPP_STD: 17 - - CPP_STD: 20 - -matrix: - fast_finish: false - exclude: - - image: Visual Studio 2019 - CPP_STD: 20 - -install: - - call script\appveyor_install.bat - - set PATH=%PATH%;%QTDIR%\bin - - set BUILD_DIR=%APPVEYOR_BUILD_FOLDER%\build.%PLATFORM%.%CONFIGURATION%.%TOOLCHAIN% - - if exist %BUILD_DIR% rmdir /S /Q %BUILD_DIR% - - set COMMS_TAG=%COMMS_TAG% - - set CC_MQTTSN_TAG=%CC_MQTTSN_TAG% - - set CC_MQTT311_TAG=%CC_MQTT311_TAG% - - set COMMON_INSTALL_DIR=%BUILD_DIR%\install - - set COMMON_BUILD_TYPE=%CONFIGURATION% - - set COMMON_CXX_STANDARD=%CPP_STD% - - set GENERATOR="%CMAKE_GENERATOR%" - - set PLATFORM="%CMAKE_PLATFORM%" - - call script\prepare_externals.bat - -build_script: - - echo ------------------------- Building Project ------------------------- - - cd %BUILD_DIR% - - cmake .. -DCMAKE_BUILD_TYPE=%CONFIGURATION% -G "%CMAKE_GENERATOR%" %PLATFORM_PARAM% -DCMAKE_INSTALL_PREFIX=install ^ - -DCMAKE_CXX_STANDARD=%CPP_STD% -DCMAKE_PREFIX_PATH="%COMMON_INSTALL_DIR%" ^ - -DCC_MQTTSN_BUILD_UNIT_TESTS=ON - - cmake --build . --config %CONFIGURATION% --target install --parallel %NUMBER_OF_PROCESSORS% - -test_script: - - echo ------------------------- Testing ------------------------- - - ctest -V - diff --git a/script/appveyor_install.bat b/script/appveyor_install.bat deleted file mode 100644 index 5496519f..00000000 --- a/script/appveyor_install.bat +++ /dev/null @@ -1,72 +0,0 @@ -IF "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" ( - set TOOLCHAIN=msvc14 - set QT_SUBDIR=msvc2015 - set QT_VER=5.6 - set CMAKE_GENERATOR=NMake Makefiles - IF "%PLATFORM%"=="x86" ( - echo Performing x86 build in VS2015 - call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86 - ) ELSE ( - echo Performing amd64 build in VS2015 - call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 - ) -) ELSE IF "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" ( - set TOOLCHAIN=msvc15 - set QT_SUBDIR=msvc2017 - set QT_VER=5.13 - set CMAKE_GENERATOR=NMake Makefiles - IF "%PLATFORM%"=="x86" ( - echo Performing x86 build in VS2017 - call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars32.bat" - ) ELSE ( - echo Performing amd64 build in VS2017 - call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" - ) - -) ELSE IF "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2019" ( - set TOOLCHAIN=msvc16 - set QT_SUBDIR=msvc2019 - set QT_VER=5.15 - set CMAKE_GENERATOR=Visual Studio 16 2019 - IF "%PLATFORM%"=="x86" ( - echo Performing x86 build in VS2019 - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars32.bat" - set CMAKE_PLATFORM=Win32 - ) ELSE ( - echo Performing amd64 build in VS2019 - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat" - set CMAKE_PLATFORM=x64 - ) -) ELSE IF "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2022" ( - set TOOLCHAIN=msvc17 - set QT_SUBDIR=msvc2019 - set QT_VER=5.15 - set CMAKE_GENERATOR=Visual Studio 17 2022 - IF "%PLATFORM%"=="x86" ( - echo Performing x86 build in VS2022 - call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars32.bat" - set CMAKE_PLATFORM=Win32 - ) ELSE ( - echo Performing amd64 build in VS2022 - call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" - set CMAKE_PLATFORM=x64 - ) -) ELSE ( - echo Toolchain %TOOLCHAIN% is not supported - exit -1 -) - -set QTDIR_PREFIX=C:/Qt/%QT_VER% -IF "%PLATFORM%"=="x86" ( - set QTDIR_SUFFIX= -) ELSE ( - set QTDIR_SUFFIX=_64 -) - -set QTDIR=%QTDIR_PREFIX%/%QT_SUBDIR%%QTDIR_SUFFIX% -IF NOT EXIST %QTDIR% ( - echo WARNING: %QTDIR% does not exist! - set QTDIR=%QTDIR_PREFIX%/msvc2015%QTDIR_SUFFIX% -) - -echo Using Qt from %QTDIR% From bf8e3ac64addfd78e08fc240e0f6661bd567fff1 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Mon, 8 Jul 2024 08:20:23 +1000 Subject: [PATCH 056/106] Added "connect" / "disconnect" operations documentation. --- client/lib/doxygen/main.dox | 334 +++++++++++++++++++ client/lib/include/cc_mqttsn_client/common.h | 10 +- 2 files changed, 339 insertions(+), 5 deletions(-) diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index d4d6c770..2a047d52 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -91,6 +91,22 @@ /// It means the data may need to be copied into some other buffer, which will be /// held intact until the send over I/O link operation is complete. /// +/// @subsection doc_cc_mqttsn_client_callbacks_gateway_disconnect Reporting Unsolicited Gateway Disconnection +/// The client application must assign a callback for the library to report +/// discovered gateway disconnection. +/// @code +/// void my_gateway_disconnected_cb(void* data, CC_MqttsnGatewayDisconnectReason reason) +/// { +/// ... /* handle gatewau disconnection */ +/// } +/// +/// cc_mqttsn_client_set_gw_disconnect_report_callback(client, &my_gateway_disconnected_cb, data); +/// @endcode +/// See also the documentation of the @ref CC_MqttsnGwDisconnectedReportCb callback function definition. +/// +/// See also @ref doc_cc_mqttsn_client_unsolicited_disconnect +/// below for details. +/// /// @subsection doc_cc_mqttsn_client_callbacks_message Reporting Received Message /// The client application must assign a callback for the library to report /// application level messages received from the gateway. @@ -540,3 +556,321 @@ /// @endcode /// To retrieve the current configuration use @b cc_mqttsn_client_get_default_gw_adv_duration() function. /// +/// @section doc_cc_mqttsn_client_connect Connecting to Gateway +/// To connect to gateway use @ref connect "connect" operation. +/// +/// @subsection doc_cc_mqttsn_client_connect_prepare Preparing "Connect" Operation. +/// @code +/// CC_MqttsnErrorCode ec = CC_MqttsnErrorCode_Success; +/// CC_MqttsnConnectHandle connect = cc_mqttsn_client_connect_prepare(client, &ec); +/// if (connect == NULL) { +/// printf("ERROR: Connect allocation failed with ec=%d\n", ec); +/// } +/// @endcode +/// @b NOTE that the @b cc_mqttsn_client_connect_prepare() cannot be called from within a callback. +/// For example, if the gateway disconnection is reported via @ref doc_cc_mqttsn_client_callbacks_gateway_disconnect "callback" +/// then the @b cc_mqttsn_client_connect_prepare() cannot be invoked right away. +/// It needs to be postponed until the next event loop iteration. +/// +/// @subsection doc_cc_mqttsn_client_connect_retry_period Configuring "Connect" Retry Period +/// When created, the "connect" operation inherits the @ref doc_cc_mqttsn_client_retry_period +/// configuration. It can be changed for the allocated operation using the +/// @b cc_mqttsn_client_connect_set_retry_period() function. +/// @code +/// ec = cc_mqttsn_client_connect_set_retry_period(connect, 1000); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// ... /* Something went wrong */ +/// } +/// @endcode +/// To retrieve the configured response timeout use the @b cc_mqttsn_client_connect_get_retry_period() function. +/// +/// @subsection doc_cc_mqttsn_client_connect_retry_count Configuring "Connect" Retry Count +/// When created, the "connect" operation inherits the @ref doc_cc_mqttsn_client_retry_count +/// configuration. It can be changed for the allocated operation using the +/// @b cc_mqttsn_client_connect_set_retry_count() function. +/// @code +/// ec = cc_mqttsn_client_connect_set_retry_count(connect, 2); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// ... /* Something went wrong */ +/// } +/// @endcode +/// To retrieve the configured response timeout use the @b cc_mqttsn_client_connect_get_retry_count() function. +/// +/// @subsection doc_cc_mqttsn_client_connect_basic Configuration of "Connect" Operation +/// To configure "connect" operation use @b cc_mqttsn_client_connect_config() function. +/// @code +/// CC_MqttsnConnectConfig config; +/// +/// // Assign default values to the "config" +/// cc_mqttsn_client_connect_init_config(&config); +/// +/// // Update the values if needed: +/// config.m_clientId = "some_client"; +/// config.m_cleanSession = true; +/// +/// // Perform the configuration +/// ec = cc_mqttsn_client_connect_config(connect, &config); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Basic configuration failed with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// +/// **IMPORTANT**: MQTT-SN specification allows reconnection to the gateway while +/// requesting previous session restoration (via "clean session" bit). By default, +/// the client library verifies that the message received from the gateway was +/// actually subscribed to before reporting the message to the application +/// (see @ref doc_cc_mqttsn_client_receive for details). To prevent +/// potential errors of the client and gateway inner states being out of sync, the +/// @b first "connect" operation requires setting the @ref CC_MqttsnConnectConfig::m_cleanSession +/// value to @b true. The only exception to this rule is when the subscription +/// verification on message reception was disabled (described in the +/// @ref doc_cc_mqttsn_client_receive section below). In case the subscription +/// verification is still enabled and the @ref CC_MqttsnConnectConfig::m_cleanSession +/// value is @b NOT set to @b true, the function rejects the configuration +/// with the @ref CC_MqttsnErrorCode_BadParam error code. Any subsequent reconnection attempts will allow +/// setting the value to @b false. +/// +/// See also documentation of the @ref CC_MqttsnConnectConfig structure. +/// +/// @subsection doc_cc_mqttsn_client_connect_will Will Configuration +/// To perform will configuration use the @b cc_mqttsn_client_connect_config_will() function. +/// @code +/// CC_MqttsnWillConfig willConfig; +/// +/// // Assign default values to the configuration +/// cc_mqttsn_client_connect_init_config_will(&willConfig); +/// +/// // Update values if needed +/// willConfig.m_topic = "some/topic"; +/// willConfig.m_data = ...; +/// willConfig.m_dataLen = ...; +/// ... +/// +/// // Perform the configuration +/// ec = cc_mqttsn_client_connect_config_will(connect, &willConfig); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Will configuration failed with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// See also documentation of the @ref CC_MqttsnWillConfig structure. +/// +/// @subsection doc_cc_mqttsn_client_connect_send Sending Connection Request +/// When all the necessary configurations are performed for the allocated "connect" +/// operation it can actually be sent to the gateway. To initiate sending +/// use the @b cc_mqttsn_client_connect_send() function. +/// @code +/// void my_connect_complete_cb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info) +/// { +/// if (status != CC_MqttsnAsyncOpStatus_Complete) { +/// printf("ERROR: The connection operation has failed with status=%d\n", status); +/// ... // handle error. +/// return; +/// } +/// +/// // "info" is not NULL when status is CC_MqttsnAsyncOpStatus_Complete. +/// assert(info != NULL); +/// ... // Analyze info values. +/// } +/// +/// ec = cc_mqttsn_client_connect_send(connect, &my_connect_complete_cb, data); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to send connect request with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// The provided callback will be invoked when the "connect" operation is complete +/// if and only if the function returns @ref CC_MqttsnErrorCode_Success. +/// +/// The handle returned by the @b cc_mqttsn_client_connect_prepare() function +/// can be discarded (there is no free / de-allocation) right after the +/// @b cc_mqttsn_client_connect_send() invocation +/// regardless of the returned error code. However, the handle remains valid until +/// the callback is called (in case the @ref CC_MqttsnErrorCode_Success was returned). +/// The valid handle can be used to @ref doc_cc_mqttsn_client_connect_cancel "cancel" +/// the operation before the completion callback is invoked. +/// +/// When the "connect" operation completion callback is invoked the reported +/// "response" information is present if and only if the "status" is +/// @ref CC_MqttsnAsyncOpStatus_Complete. +/// +/// @b NOTE that only single "connect" / "disconnect" operation is allowed at a time, any attempt to +/// prepare a new one via @b cc_mqttsn_client_connect_prepare() will be rejected +/// until the "connect" operation completion callback is invoked or +/// the operation is @ref doc_cc_mqttsn_client_connect_cancel "cancelled". +/// +/// When the callback reporting the connection status is invoked, it is responsibility +/// of the application to check the @ref CC_MqttsnConnectInfo::m_returnCode value. +/// If it's not @ref CC_MqttsnReturnCode_Accepted, the application +/// is responsible retry the "connect" operation later. The same should be +/// done when the "connect" operation is not properly completed, i.e. the +/// reported status is @b NOT @ref CC_MqttsnAsyncOpStatus_Complete. +/// +/// @subsection doc_cc_mqttsn_client_connect_cancel Cancel the "Connect" Operation. +/// While the handle returned by the @b cc_mqttsn_client_connect_prepare() is still +/// valid it is possible to cancel / discard the operation. +/// @code +/// ec = cc_mqttsn_client_connect_cancel(connect); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to cancel connect with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// In case the @b cc_mqttsn_client_connect_send() function was successfully +/// called before the @b cc_mqttsn_client_connect_cancel(), the operation is +/// cancelled @b without callback invocation. +/// +/// @subsection doc_cc_mqttsn_client_connect_simplify Simplifying the "Connect" Operation Preparation. +/// In many use cases the "connect" operation can be quite simple with a lot of defaults. +/// To simplify the sequence of the operation preparation and handling of errors, +/// the library provides wrapper function(s) that can be used: +/// @li @b cc_mqttsn_client_connect() +/// +/// For example: +/// @code +/// CC_MqttsnConnectConfig config; +/// +/// // Assign default values to the "config" +/// cc_mqttsn_client_connect_init_config(&config); +/// +/// // Update the values if needed: +/// config.m_clientId = "some_client"; +/// config.m_cleanSession = true; +/// +/// ec = cc_mqttsn_client_connect(client, config, null, &my_connect_complete_cb, data); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to send connect request with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// Note that the wrapper function does NOT expose the handle returned by the +/// @b cc_mqttsn_client_connect_prepare(). It means that it's not possible to +/// cancel the "connect" operation before its completion. +/// +/// @subsection doc_cc_mqttsn_client_connect_check Check The Library Remains Connected +/// At any time it is possible to check the internal state of the library of +/// whether it's properly connected to the gateway. +/// @code +/// bool isConnected = cc_mqttsn_client_is_connected(client); +/// @endcode +/// +/// +/// @section doc_cc_mqttsn_client_disconnect Disconnecting From Gateway +/// To intentionally disconnect from gateway use @ref disconnect "disconnect" operation. The +/// unsolicited disconnection from the gateway is described in ref +/// @ref doc_cc_mqttsn_client_unsolicited_disconnect section below. +/// +/// @subsection doc_cc_mqttsn_client_disconnect_prepare Preparing "Disconnect" Operation. +/// @code +/// CC_MqttsnErrorCode ec = CC_MqttsnErrorCode_Success; +/// CC_MqttsnDisconnectHandle disconnect = cc_mqttsn_client_disconnect_prepare(client, &ec); +/// if (disconnect == NULL) { +/// printf("ERROR: Disconnect allocation failed with ec=%d\n", ec); +/// } +/// @endcode +/// +/// @subsection doc_cc_mqttsn_client_disconnect_retry_period Configuring "Disconnect" Retry Period +/// When created, the "disconnect" operation inherits the @ref doc_cc_mqttsn_client_retry_period +/// configuration. It can be changed for the allocated operation using the +/// @b cc_mqttsn_client_disconnect_set_retry_period() function. +/// @code +/// ec = cc_mqttsn_client_disconnect_set_retry_period(connect, 1000); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// ... /* Something went wrong */ +/// } +/// @endcode +/// To retrieve the configured response timeout use the @b cc_mqttsn_client_disconnect_get_retry_period() function. +/// +/// @subsection doc_cc_mqttsn_client_disconnect_retry_count Configuring "Disconnect" Retry Count +/// When created, the "disconnect" operation inherits the @ref doc_cc_mqttsn_client_retry_count +/// configuration. It can be changed for the allocated operation using the +/// @b cc_mqttsn_client_disconnect_set_retry_count() function. +/// @code +/// ec = cc_mqttsn_client_disconnect_set_retry_count(disconnect, 2); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// ... /* Something went wrong */ +/// } +/// @endcode +/// To retrieve the configured response timeout use the @b cc_mqttsn_client_disconnect_get_retry_count() function. +/// +/// @subsection doc_cc_mqttsn_client_disconnect_send Sending Disconnection Request +/// When the necessary configuration is performed for the allocated "disconnect" +/// operation it can be sent to the gateway. To initiate sending +/// use the @b cc_mqttsn_client_disconnect_send() function. +/// @code +/// void my_disconnect_complete_cb(void* data, CC_MqttsnAsyncOpStatus status) +/// { +/// if (status != CC_MqttsnAsyncOpStatus_Complete) { +/// printf("ERROR: The disconnection operation has failed with status=%d\n", status); +/// ... // handle error. +/// return; +/// } +/// ... +/// } +/// +/// ec = cc_mqttsn_client_disconnect_send(connect, &my_disconnect_complete_cb, data); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to send disconnect request with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// The provided callback will be invoked when the "disconnect" operation is complete +/// if and only if the function returns @ref CC_MqttsnErrorCode_Success. +/// +/// The handle returned by the @b cc_mqttsn_client_disconnect_prepare() function +/// can be discarded (there is no free / de-allocation) right after the +/// @b cc_mqttsn_client_disconnect_send() invocation +/// regardless of the returned error code. However, the handle remains valid until +/// the callback is called (in case the @ref CC_MqttsnErrorCode_Success was returned). +/// The valid handle can be used to @ref doc_cc_mqttsn_client_disconnect_cancel "cancel" +/// the operation before the completion callback is invoked. +/// +/// @b NOTE that only single "connect" / "disconnect" operation is allowed at a time, any attempt to +/// prepare a new one via @b cc_mqttsn_client_disconnect_prepare() will be rejected +/// until the "connect" operation completion callback is invoked or +/// the operation is @ref doc_cc_mqttsn_client_disconnect_cancel "cancelled". +/// +/// In case there are other asynchronous operations that hasn't been completed yet, +/// their completion callback is automatically invoked with @ref CC_MqttsnAsyncOpStatus_Aborted +/// status. +/// +/// @b IMPORTANT: In case of sending the explicit disconnection request the +/// @ref doc_cc_mqttsn_client_callbacks_gateway_disconnect "registered unsolicited disconnection callback" +/// is @b NOT invoked. +/// +/// After the disconnection the application can re-establish network connection +/// to the gateway (if needed) and perform the @ref doc_cc_mqttsn_client_connect "connect" operation +/// again. +/// +/// @subsection doc_cc_mqttsn_client_disconnect_cancel Cancel the "Disconnect" Operation. +/// While the handle returned by the @b cc_mqttsn_client_disconnect_prepare() is still +/// valid it is possible to cancel / discard the operation. +/// @code +/// ec = cc_mqttsn_client_disconnect_cancel(disconnect); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to cancel disconnect with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// +/// @subsection doc_cc_mqttsn_client_disconnect_simplify Simplifying the "Disconnect" Operation Preparation. +/// In many use cases the "disconnect" operation can be quite simple. +/// To simplify the sequence of the operation preparation and handling of errors, +/// the library provides wrapper function(s) that can be used: +/// @li @b cc_mqttsn_client_disconnect() +/// +/// For example: +/// @code +/// ec = cc_mqttsn_client_disconnect(client, &my_disconnect_complete_cb, data); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to send disconnect request with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// +/// @section doc_cc_mqttsn_client_receive Receiving Messages +/// TODO +/// +/// @section doc_cc_mqttsn_client_unsolicited_disconnect Unsolicited Gateway Disconnection +/// TODO diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index ee6ee27e..9571aad1 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -203,11 +203,11 @@ typedef struct /// @ingroup connect typedef struct { - const char* m_topic; ///< Client ID - const unsigned char* m_data; - unsigned m_dataLen; - CC_MqttsnQoS m_qos; - bool m_retain; + const char* m_topic; ///< Will topic. + const unsigned char* m_data; ///< Will data (message). + unsigned m_dataLen; ///< Will data (message) length. + CC_MqttsnQoS m_qos; ///< Will message QoS. + bool m_retain; ///< Will message retain configuration. } CC_MqttsnWillConfig; /// @brief Callback used to request time measurement. From fb4190183066e2ef8d10befa31d0b8aa9e26088d Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Mon, 8 Jul 2024 09:01:57 +1000 Subject: [PATCH 057/106] Added unsolicited gateway disconnection testing. --- client/lib/include/cc_mqttsn_client/common.h | 2 +- client/lib/src/ClientImpl.cpp | 21 +++++- client/lib/src/ClientImpl.h | 1 + client/lib/src/op/KeepAliveOp.cpp | 7 ++ client/lib/src/op/KeepAliveOp.h | 1 + client/lib/test/UnitTestCommonBase.cpp | 25 +++++-- client/lib/test/UnitTestCommonBase.h | 14 ++++ client/lib/test/UnitTestDisconnect.th | 76 ++++++++++++++++++++ 8 files changed, 140 insertions(+), 7 deletions(-) diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index 9571aad1..5b90fc7d 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -100,7 +100,7 @@ typedef enum typedef enum { CC_MqttsnGatewayDisconnectReason_DisconnectMsg = 0, ///< Gateway sent @b DISCONNECT message. - CC_MqttsnGatewayDisconnectReason_NoGatewayResponse = 0, ///< No messages from the gateway and no response to @b PINGREQ. + CC_MqttsnGatewayDisconnectReason_NoGatewayResponse = 1, ///< No messages from the gateway and no response to @b PINGREQ. CC_MqttsnGatewayDisconnectReason_ValuesLimit ///< Limit for the values } CC_MqttsnGatewayDisconnectReason; diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index dc7b1bf3..b32decf8 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -758,8 +758,25 @@ void ClientImpl::handle([[maybe_unused]] PingreqMsg& msg) return; } - PingrespMsg respMsg; - sendMessage(respMsg); + for (auto& opPtr : m_keepAliveOps) { + msg.dispatch(*opPtr); + } +} + +void ClientImpl::handle(DisconnectMsg& msg) +{ + if ((m_sessionState.m_disconnecting) || (!m_sessionState.m_connected)) { + return; + } + + if (m_disconnectOps.empty()) { + gatewayDisconnected(CC_MqttsnGatewayDisconnectReason_DisconnectMsg); + return; + } + + for (auto& opPtr : m_disconnectOps) { + msg.dispatch(*opPtr); + } } void ClientImpl::handle([[maybe_unused]] ProtMessage& msg) diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index 38fea9b9..99d70c23 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -161,6 +161,7 @@ class ClientImpl final : public ProtMsgHandler // #endif // #if CC_MQTTSN_CLIENT_MAX_QOS >= 2 virtual void handle(PingreqMsg& msg) override; + virtual void handle(DisconnectMsg& msg) override; virtual void handle(ProtMessage& msg) override; // -------------------- Ops Access API ----------------------------- diff --git a/client/lib/src/op/KeepAliveOp.cpp b/client/lib/src/op/KeepAliveOp.cpp index e2f67c42..3615efad 100644 --- a/client/lib/src/op/KeepAliveOp.cpp +++ b/client/lib/src/op/KeepAliveOp.cpp @@ -42,6 +42,13 @@ void KeepAliveOp::messageSent() restartPingTimer(); } +void KeepAliveOp::handle([[maybe_unused]] PingreqMsg& msg) +{ + PingrespMsg respMsg; + sendMessage(respMsg); + restartRecvTimer(); +} + void KeepAliveOp::handle([[maybe_unused]] PingrespMsg& msg) { m_respTimer.cancel(); diff --git a/client/lib/src/op/KeepAliveOp.h b/client/lib/src/op/KeepAliveOp.h index d773c52f..db3cd63d 100644 --- a/client/lib/src/op/KeepAliveOp.h +++ b/client/lib/src/op/KeepAliveOp.h @@ -28,6 +28,7 @@ class KeepAliveOp final : public Op void messageSent(); using Base::handle; + virtual void handle(PingreqMsg& msg) override; virtual void handle(PingrespMsg& msg) override; virtual void handle(ProtMessage& msg) override; diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index f5ccfd95..4b3993bd 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -299,6 +299,26 @@ void UnitTestCommonBase::unitTestPopGwInfoReport() m_data.m_gwInfoReports.pop_front(); } +bool UnitTestCommonBase::unitTestHasGwDisconnectReport() const +{ + return !m_data.m_gwDisconnectReports.empty(); +} +const UnitTestCommonBase::UnitTestGwDisconnectReport* UnitTestCommonBase::unitTestGetGwDisconnectReport(bool mustExist) const +{ + if (!unitTestHasGwDisconnectReport()) { + test_assert(!mustExist); + return nullptr; + } + + return &m_data.m_gwDisconnectReports.front(); +} + +void UnitTestCommonBase::unitTestPopGwDisconnectReport() +{ + test_assert(!m_data.m_gwDisconnectReports.empty()); + m_data.m_gwDisconnectReports.pop_front(); +} + bool UnitTestCommonBase::unitTestHasSearchCompleteReport() const { return !m_data.m_searchCompleteReports.empty(); @@ -611,10 +631,7 @@ void UnitTestCommonBase::unitTestGwStatusReportCb(void* data, CC_MqttsnGwStatus void UnitTestCommonBase::unitTestGwDisconnectReportCb(void* data, CC_MqttsnGatewayDisconnectReason reason) { - // TODO: - static_cast(data); - static_cast(reason); - test_assert(false); + asThis(data)->m_data.m_gwDisconnectReports.emplace_back(reason); } void UnitTestCommonBase::unitTestMessageReportCb(void* data, const CC_MqttsnMessageInfo* msgInfo) diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index 48a55b2f..e9cf2f1c 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -136,6 +136,15 @@ class UnitTestCommonBase using UnitTestGwInfoReportsList = std::list; + struct UnitTestGwDisconnectReport + { + CC_MqttsnGatewayDisconnectReason m_reason = CC_MqttsnGatewayDisconnectReason_ValuesLimit; + + UnitTestGwDisconnectReport(CC_MqttsnGatewayDisconnectReason reason) : m_reason(reason) {} + }; + + using UnitTestGwDisconnectReportsList = std::list; + struct UnitTestSearchCompleteReport { CC_MqttsnAsyncOpStatus m_status = CC_MqttsnAsyncOpStatus_ValuesLimit; @@ -201,6 +210,10 @@ class UnitTestCommonBase const UnitTestGwInfoReport* unitTestGetGwInfoReport(bool mustExist = true) const; void unitTestPopGwInfoReport(); + bool unitTestHasGwDisconnectReport() const; + const UnitTestGwDisconnectReport* unitTestGetGwDisconnectReport(bool mustExist = true) const; + void unitTestPopGwDisconnectReport(); + bool unitTestHasSearchCompleteReport() const; const UnitTestSearchCompleteReport* unitTestSearchCompleteReport(bool mustExist = true) const; void unitTestPopSearchCompleteReport(); @@ -251,6 +264,7 @@ class UnitTestCommonBase UnitTestTickInfosList m_ticks; UnitTestOutputDataInfosList m_outData; UnitTestGwInfoReportsList m_gwInfoReports; + UnitTestGwDisconnectReportsList m_gwDisconnectReports; UnitTestSearchCompleteReportsList m_searchCompleteReports; UnitTestSearchCompleteCbList m_searchCompleteCallbacks; UnitTestConnectCompleteReportList m_connectCompleteReports; diff --git a/client/lib/test/UnitTestDisconnect.th b/client/lib/test/UnitTestDisconnect.th index 957a896f..565e6029 100644 --- a/client/lib/test/UnitTestDisconnect.th +++ b/client/lib/test/UnitTestDisconnect.th @@ -11,6 +11,8 @@ public: void test1(); void test2(); void test3(); + void test4(); + void test5(); private: virtual void setUp() override @@ -182,3 +184,77 @@ void UnitTestDisconnect::test3() TS_ASSERT(!unitTestHasTickReq()); // No more keep alive TS_ASSERT(!apiIsConnected(client)); } + +void UnitTestDisconnect::test4() +{ + // Testing unsolicited DISCONNECT message from the gateway + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + UnitTestDisconnectMsg disconnectMsg; + unitTestClientInputMessage(client, disconnectMsg); + } + + TS_ASSERT(unitTestHasGwDisconnectReport()); + auto* disconnectReport = unitTestGetGwDisconnectReport(); + TS_ASSERT_EQUALS(disconnectReport->m_reason, CC_MqttsnGatewayDisconnectReason_DisconnectMsg); + unitTestPopGwDisconnectReport(); + + TS_ASSERT(!unitTestHasTickReq()); // No more keep alive + TS_ASSERT(!apiIsConnected(client)); +} + +void UnitTestDisconnect::test5() +{ + // Testing lack of messages from the gateway + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + auto ec = apiSetDefaultRetryCount(client, 1); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pingreqMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pingreqMsg, nullptr); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pingreqMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pingreqMsg, nullptr); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); + + TS_ASSERT(unitTestHasGwDisconnectReport()); + auto* disconnectReport = unitTestGetGwDisconnectReport(); + TS_ASSERT_EQUALS(disconnectReport->m_reason, CC_MqttsnGatewayDisconnectReason_NoGatewayResponse); + unitTestPopGwDisconnectReport(); + + TS_ASSERT(!unitTestHasTickReq()); // No more keep alive + TS_ASSERT(!apiIsConnected(client)); +} From 7324ab369aa3652eca2bdf37a08b564bf3d45980 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 10 Jul 2024 08:43:29 +1000 Subject: [PATCH 058/106] Initial implementation of the subscribe operation. --- client/lib/CMakeLists.txt | 1 + client/lib/include/cc_mqttsn_client/common.h | 50 ++- client/lib/script/BareMetalTestConfig.cmake | 12 + .../lib/script/DefineDefaultConfigVars.cmake | 3 +- client/lib/script/WriteConfigHeader.cmake | 3 +- client/lib/src/ClientImpl.cpp | 100 +++--- client/lib/src/ClientImpl.h | 25 +- client/lib/src/ConfigState.h | 7 +- client/lib/src/ExtConfig.h | 6 +- client/lib/src/ReuseState.h | 21 ++ client/lib/src/TopicFilterDefs.h | 31 ++ client/lib/src/op/ConnectOp.cpp | 5 + client/lib/src/op/Op.cpp | 293 ++++++++++-------- client/lib/src/op/Op.h | 48 +-- client/lib/src/op/SubscribeOp.cpp | 233 ++++++++++++++ client/lib/src/op/SubscribeOp.h | 66 ++++ client/lib/templ/Config.h.templ | 10 +- client/lib/templ/client.cpp.templ | 173 +++++++++++ client/lib/templ/client.h.templ | 121 ++++++++ 19 files changed, 980 insertions(+), 228 deletions(-) create mode 100644 client/lib/src/ReuseState.h create mode 100644 client/lib/src/TopicFilterDefs.h create mode 100644 client/lib/src/op/SubscribeOp.cpp create mode 100644 client/lib/src/op/SubscribeOp.h diff --git a/client/lib/CMakeLists.txt b/client/lib/CMakeLists.txt index ee1cdb06..cadb796a 100644 --- a/client/lib/CMakeLists.txt +++ b/client/lib/CMakeLists.txt @@ -170,6 +170,7 @@ function (gen_lib_mqttsn_client config_file) src/op/KeepAliveOp.cpp src/op/Op.cpp src/op/SearchOp.cpp + src/op/SubscribeOp.cpp src/ClientImpl.cpp src/TimerMgr.cpp ) diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index 5b90fc7d..72a7b53d 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -149,6 +149,15 @@ struct CC_MqttsnDisconnect; /// @ingroup "disconnect". typedef struct CC_MqttsnDisconnect* CC_MqttsnDisconnectHandle; +/// @brief Declaration of the hidden structure used to define @ref CC_MqttsnSubscribeHandle +/// @ingroup subscribe +struct CC_MqttsnSubscribe; + +/// @brief Handle for "subscribe" operation. +/// @details Returned by @b cc_mqttsn_client_subscribe_prepare() function. +/// @ingroup "subscribe". +typedef struct CC_MqttsnSubscribe* CC_MqttsnSubscribeHandle; + /// @brief Type used to hold Topic ID value. typedef unsigned short CC_MqttsnTopicId; @@ -183,13 +192,6 @@ typedef struct unsigned m_addrLen; ///< Length of the address } CC_MqttsnGatewayInfo; -/// @brief Information on the "connect" operation completion -/// @ingroup connect -typedef struct -{ - CC_MqttsnReturnCode m_returnCode; ///< Return code reported by the @b CONNACK message -} CC_MqttsnConnectInfo; - /// @brief Configuration the "connect" operation /// @ingroup connect typedef struct @@ -199,6 +201,13 @@ typedef struct bool m_cleanSession; ///< Clean session configuration } CC_MqttsnConnectConfig; +/// @brief Information on the "connect" operation completion +/// @ingroup connect +typedef struct +{ + CC_MqttsnReturnCode m_returnCode; ///< Return code reported by the @b CONNACK message +} CC_MqttsnConnectInfo; + /// @brief Configuration the will for "connect" and "will" operations /// @ingroup connect typedef struct @@ -210,6 +219,24 @@ typedef struct bool m_retain; ///< Will message retain configuration. } CC_MqttsnWillConfig; +/// @brief Configuration the "subscribe" operation +/// @ingroup subscribe +typedef struct +{ + const char* m_topic; ///< Subscription topic, can be NULL when pre-defined topic ID is used. + CC_MqttsnTopicId m_topicId; ///< Pre-defined topic ID, should be @b 0 when topic is not NULL. + CC_MqttsnQoS m_qos; ///< Max QoS value +} CC_MqttsnSubscribeConfig; + +/// @brief Information on the "subscribe" operation completion +/// @ingroup subscribe +typedef struct +{ + CC_MqttsnReturnCode m_returnCode; ///< Return code reported by the @b SUBACK message + CC_MqttsnTopicId m_topicId; ///< Granted topic ID (if applicable). + CC_MqttsnQoS m_qos; ///< Granted max QoS value +} CC_MqttsnSubscribeInfo; + /// @brief Callback used to request time measurement. /// @details The callback is set using /// cc_mqttsn_client_set_next_tick_program_callback() function. @@ -305,6 +332,15 @@ typedef void (*CC_MqttsnConnectCompleteCb)(void* data, CC_MqttsnAsyncOpStatus st /// @ingroup disconnect typedef void (*CC_MqttsnDisconnectCompleteCb)(void* data, CC_MqttsnAsyncOpStatus status); +/// @brief Callback used to report completion of the subscribe operation. +/// @param[in] data Pointer to user data object, passed as the last parameter to +/// the request call. +/// @param[in] status Status of the "subscribe" operation. +/// @param[in] info Information about op completion. Not-NULL is reported if and onfly if +/// the "status" is equal to @ref CC_MqttsnAsyncOpStatus_Complete. +/// @post The data members of the reported response can NOT be accessed after the function returns. +/// @ingroup subscribe +typedef void (*CC_MqttsnSubscribeCompleteCb)(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info); #ifdef __cplusplus } diff --git a/client/lib/script/BareMetalTestConfig.cmake b/client/lib/script/BareMetalTestConfig.cmake index 870515ec..7398bc96 100644 --- a/client/lib/script/BareMetalTestConfig.cmake +++ b/client/lib/script/BareMetalTestConfig.cmake @@ -30,3 +30,15 @@ set(CC_MQTTSN_CLIENT_DATA_FIELD_FIXED_LEN 128) # Limit the length of the buffer required to store serialized message set(CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE 512) + +# Limit the amount of outstanding subscribe operations +set(CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT 1) + +# Limit the amount of outstanding unsubscribe operations +set(CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT 1) + +# Limit the amount of stored subscribed topic filters +set(CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT 10) + +# Limit the amount of input registered topics +set(CC_MQTTSN_CLIENT_IN_REG_TOPICS_LIMIT 20) diff --git a/client/lib/script/DefineDefaultConfigVars.cmake b/client/lib/script/DefineDefaultConfigVars.cmake index 5550cd9a..fecef7a9 100644 --- a/client/lib/script/DefineDefaultConfigVars.cmake +++ b/client/lib/script/DefineDefaultConfigVars.cmake @@ -30,5 +30,6 @@ set_default_var_value(CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT 0) set_default_var_value(CC_MQTTSN_CLIENT_HAS_ERROR_LOG TRUE) set_default_var_value(CC_MQTTSN_CLIENT_HAS_TOPIC_FORMAT_VERIFICATION TRUE) set_default_var_value(CC_MQTTSN_CLIENT_HAS_SUB_TOPIC_VERIFICATION TRUE) -#set_default_var_value(CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT 0) +set_default_var_value(CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT 0) +set_default_var_value(CC_MQTTSN_CLIENT_IN_REG_TOPICS_LIMIT 0) set_default_var_value(CC_MQTTSN_CLIENT_MAX_QOS 2) \ No newline at end of file diff --git a/client/lib/script/WriteConfigHeader.cmake b/client/lib/script/WriteConfigHeader.cmake index c60d89da..225238fc 100644 --- a/client/lib/script/WriteConfigHeader.cmake +++ b/client/lib/script/WriteConfigHeader.cmake @@ -56,7 +56,8 @@ replace_in_text (CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT) replace_in_text (CC_MQTTSN_CLIENT_HAS_ERROR_LOG_CPP) replace_in_text (CC_MQTTSN_CLIENT_HAS_TOPIC_FORMAT_VERIFICATION_CPP) replace_in_text (CC_MQTTSN_CLIENT_HAS_SUB_TOPIC_VERIFICATION_CPP) -#replace_in_text (CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT) +replace_in_text (CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT) +replace_in_text (CC_MQTTSN_CLIENT_IN_REG_TOPICS_LIMIT) replace_in_text (CC_MQTTSN_CLIENT_MAX_QOS) file (WRITE "${OUT_FILE}.tmp" "${text}") diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index b32decf8..4043ee44 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -275,56 +275,56 @@ op::DisconnectOp* ClientImpl::disconnectPrepare(CC_MqttsnErrorCode* ec) return op; } -// op::SubscribeOp* ClientImpl::subscribePrepare(CC_MqttsnErrorCode* ec) -// { -// op::SubscribeOp* subOp = nullptr; -// do { -// if (!m_sessionState.m_connected) { -// errorLog("Client must be connected to allow subscription."); -// updateEc(ec, CC_MqttsnErrorCode_NotConnected); -// break; -// } +op::SubscribeOp* ClientImpl::subscribePrepare(CC_MqttsnErrorCode* ec) +{ + op::SubscribeOp* op = nullptr; + do { + if (!m_sessionState.m_connected) { + errorLog("Client must be connected to allow subscription."); + updateEc(ec, CC_MqttsnErrorCode_NotConnected); + break; + } -// if (m_sessionState.m_disconnecting) { -// errorLog("Session disconnection is in progress, cannot initiate subscription."); -// updateEc(ec, CC_MqttsnErrorCode_Disconnecting); -// break; -// } + if (m_sessionState.m_disconnecting) { + errorLog("Session disconnection is in progress, cannot initiate subscription."); + updateEc(ec, CC_MqttsnErrorCode_Disconnecting); + break; + } -// if (m_clientState.m_networkDisconnected) { -// errorLog("Network is disconnected."); -// updateEc(ec, CC_MqttsnErrorCode_NetworkDisconnected); -// break; -// } + if (m_ops.max_size() <= m_ops.size()) { + errorLog("Cannot start subscribe operation, retry in next event loop iteration."); + updateEc(ec, CC_MqttsnErrorCode_RetryLater); + break; + } -// if (m_ops.max_size() <= m_ops.size()) { -// errorLog("Cannot start subscribe operation, retry in next event loop iteration."); -// updateEc(ec, CC_MqttsnErrorCode_RetryLater); -// break; -// } + if (m_preparationLocked) { + errorLog("Another operation is being prepared, cannot prepare \"subscribe\" without \"send\" or \"cancel\" of the previous."); + updateEc(ec, CC_MqttsnErrorCode_PreparationLocked); + break; + } -// if (m_preparationLocked) { -// errorLog("Another operation is being prepared, cannot prepare \"subscribe\" without \"send\" or \"cancel\" of the previous."); -// updateEc(ec, CC_MqttsnErrorCode_PreparationLocked); -// break; -// } + auto ptr = m_subscribeOpsAlloc.alloc(*this); + if (!ptr) { + errorLog("Cannot allocate new subscribe operation."); + updateEc(ec, CC_MqttsnErrorCode_OutOfMemory); + break; + } -// auto ptr = m_subscribeOpsAlloc.alloc(*this); -// if (!ptr) { -// errorLog("Cannot allocate new subscribe operation."); -// updateEc(ec, CC_MqttsnErrorCode_OutOfMemory); -// break; -// } + m_preparationLocked = true; + m_ops.push_back(ptr.get()); + m_subscribeOps.push_back(std::move(ptr)); + op = m_subscribeOps.back().get(); -// m_preparationLocked = true; -// m_ops.push_back(ptr.get()); -// m_subscribeOps.push_back(std::move(ptr)); -// subOp = m_subscribeOps.back().get(); -// updateEc(ec, CC_MqttsnErrorCode_Success); -// } while (false); + if (1U < m_subscribeOps.size()) { + // Only one SUBSCRIBE transaction is allowed at a time by the specification + op->suspend(); + } -// return subOp; -// } + updateEc(ec, CC_MqttsnErrorCode_Success); + } while (false); + + return op; +} // op::UnsubscribeOp* ClientImpl::unsubscribePrepare(CC_MqttsnErrorCode* ec) // { @@ -855,7 +855,7 @@ void ClientImpl::opComplete(const op::Op* op) /* Type_Connect */ &ClientImpl::opComplete_Connect, /* Type_KeepAlive */ &ClientImpl::opComplete_KeepAlive, /* Type_Disconnect */ &ClientImpl::opComplete_Disconnect, - // /* Type_Subscribe */ &ClientImpl::opComplete_Subscribe, + /* Type_Subscribe */ &ClientImpl::opComplete_Subscribe, // /* Type_Unsubscribe */ &ClientImpl::opComplete_Unsubscribe, // /* Type_Recv */ &ClientImpl::opComplete_Recv, // /* Type_Send */ &ClientImpl::opComplete_Send, @@ -1202,10 +1202,14 @@ void ClientImpl::opComplete_Disconnect(const op::Op* op) eraseFromList(op, m_disconnectOps); } -// void ClientImpl::opComplete_Subscribe(const op::Op* op) -// { -// eraseFromList(op, m_subscribeOps); -// } +void ClientImpl::opComplete_Subscribe(const op::Op* op) +{ + eraseFromList(op, m_subscribeOps); + if (!m_subscribeOps.empty()) { + COMMS_ASSERT(m_subscribeOps.front()); + m_subscribeOps.front()->resume(); + } +} // void ClientImpl::opComplete_Unsubscribe(const op::Op* op) // { diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index 99d70c23..77157495 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -13,7 +13,7 @@ #include "ObjAllocator.h" #include "ObjListType.h" #include "ProtocolDefs.h" -// #include "ReuseState.h" +#include "ReuseState.h" #include "SessionState.h" #include "TimerMgr.h" @@ -24,7 +24,7 @@ // #include "op/RecvOp.h" #include "op/SearchOp.h" // #include "op/SendOp.h" -// #include "op/SubscribeOp.h" +#include "op/SubscribeOp.h" // #include "op/UnsubscribeOp.h" #include "cc_mqttsn_client/common.h" @@ -71,7 +71,7 @@ class ClientImpl final : public ProtMsgHandler op::SearchOp* searchPrepare(CC_MqttsnErrorCode* ec); op::ConnectOp* connectPrepare(CC_MqttsnErrorCode* ec); op::DisconnectOp* disconnectPrepare(CC_MqttsnErrorCode* ec); - // op::SubscribeOp* subscribePrepare(CC_MqttsnErrorCode* ec); + op::SubscribeOp* subscribePrepare(CC_MqttsnErrorCode* ec); // op::UnsubscribeOp* unsubscribePrepare(CC_MqttsnErrorCode* ec); // op::SendOp* publishPrepare(CC_MqttsnErrorCode* ec); @@ -212,10 +212,10 @@ class ClientImpl final : public ProtMsgHandler return m_sessionState; } - // ReuseState& reuseState() - // { - // return m_reuseState; - // } + ReuseState& reuseState() + { + return m_reuseState; + } inline void errorLog(const char* msg) { @@ -244,8 +244,8 @@ class ClientImpl final : public ProtMsgHandler using DisconnectOpAlloc = ObjAllocator; using DisconnectOpsList = ObjListType; - // using SubscribeOpAlloc = ObjAllocator; - // using SubscribeOpsList = ObjListType; + using SubscribeOpAlloc = ObjAllocator; + using SubscribeOpsList = ObjListType; // using UnsubscribeOpAlloc = ObjAllocator; // using UnsubscribeOpsList = ObjListType; @@ -284,7 +284,7 @@ class ClientImpl final : public ProtMsgHandler void opComplete_Connect(const op::Op* op); void opComplete_KeepAlive(const op::Op* op); void opComplete_Disconnect(const op::Op* op); - // void opComplete_Subscribe(const op::Op* op); + void opComplete_Subscribe(const op::Op* op); // void opComplete_Unsubscribe(const op::Op* op); // void opComplete_Recv(const op::Op* op); // void opComplete_Send(const op::Op* op); @@ -326,6 +326,7 @@ class ClientImpl final : public ProtMsgHandler ConfigState m_configState; ClientState m_clientState; SessionState m_sessionState; + ReuseState m_reuseState; TimerMgr m_timerMgr; TimerMgr::Timer m_gwDiscoveryTimer; @@ -350,8 +351,8 @@ class ClientImpl final : public ProtMsgHandler DisconnectOpAlloc m_disconnectOpsAlloc; DisconnectOpsList m_disconnectOps; - // SubscribeOpAlloc m_subscribeOpsAlloc; - // SubscribeOpsList m_subscribeOps; + SubscribeOpAlloc m_subscribeOpsAlloc; + SubscribeOpsList m_subscribeOps; // UnsubscribeOpAlloc m_unsubscribeOpsAlloc; // UnsubscribeOpsList m_unsubscribeOps; diff --git a/client/lib/src/ConfigState.h b/client/lib/src/ConfigState.h index 398e474e..0c88fdc2 100644 --- a/client/lib/src/ConfigState.h +++ b/client/lib/src/ConfigState.h @@ -27,10 +27,9 @@ struct ConfigState unsigned m_broadcastRadius = DefaultBroadcastRadius; unsigned m_gwAdvTimeoutMs = DefaultGwAdvTimeoutMs; unsigned m_allowedAdvLosses = DefautlAllowedAdvLosses; - // CC_MqttsnPublishOrdering m_publishOrdering = CC_MqttsnPublishOrdering_SameQos; - // bool m_verifyOutgoingTopic = Config::HasTopicFormatVerification; - // bool m_verifyIncomingTopic = Config::HasTopicFormatVerification; - // bool m_verifySubFilter = Config::HasSubTopicVerification; + bool m_verifyOutgoingTopic = Config::HasTopicFormatVerification; + bool m_verifyIncomingTopic = Config::HasTopicFormatVerification; + bool m_verifySubFilter = Config::HasSubTopicVerification; }; } // namespace cc_mqttsn_client diff --git a/client/lib/src/ExtConfig.h b/client/lib/src/ExtConfig.h index a45259b9..d335fd7e 100644 --- a/client/lib/src/ExtConfig.h +++ b/client/lib/src/ExtConfig.h @@ -35,9 +35,9 @@ struct ExtConfig : public Config (SearchOpsLimit > 0U) && (ConnectOpsLimit > 0U) && (KeepAliveOpsLimit > 0U) && - (DisconnectOpsLimit > 0U) /* && + (DisconnectOpsLimit > 0U) && (SubscribeOpsLimit > 0U) && - (UnsubscribeOpsLimit > 0U) && + (UnsubscribeOpsLimit > 0U) /* && (RecvOpsLimit > 0U) && (SendOpsLimit > 0U)*/; static constexpr unsigned MaxTimersLimit = @@ -78,7 +78,7 @@ struct ExtConfig : public Config // static_assert(HasDynMemAlloc || (RecvOpsLimit > 0U)); // static_assert(HasDynMemAlloc || (SendOpsLimit > 0U)); static_assert(HasDynMemAlloc || (OpsLimit > 0U)); - // static_assert(HasDynMemAlloc || (PacketIdsLimit > 0U)); + static_assert(HasDynMemAlloc || (PacketIdsLimit > 0U)); }; } // namespace cc_mqttsn_client diff --git a/client/lib/src/ReuseState.h b/client/lib/src/ReuseState.h new file mode 100644 index 00000000..71060b20 --- /dev/null +++ b/client/lib/src/ReuseState.h @@ -0,0 +1,21 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "TopicFilterDefs.h" + +namespace cc_mqttsn_client +{ + +struct ReuseState +{ + SubFiltersMap m_subFilters; + InRegTopicsMap m_inRegTopics; +}; + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/TopicFilterDefs.h b/client/lib/src/TopicFilterDefs.h new file mode 100644 index 00000000..75f2ce86 --- /dev/null +++ b/client/lib/src/TopicFilterDefs.h @@ -0,0 +1,31 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "cc_mqttsn_client/common.h" + +#include "Config.h" +#include "ObjListType.h" +#include "ProtocolDefs.h" + + +namespace cc_mqttsn_client +{ + +using TopicNameStr = SubscribeMsg::Field_topicName::Field::ValueType; +using SubFiltersMap = ObjListType; + +struct RegTopicInfo +{ + TopicNameStr m_topic; + CC_MqttsnTopicId m_topicId = 0U; // key +}; + +using InRegTopicsMap = ObjListType; + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/op/ConnectOp.cpp b/client/lib/src/op/ConnectOp.cpp index e69d748e..188c8b2d 100644 --- a/client/lib/src/op/ConnectOp.cpp +++ b/client/lib/src/op/ConnectOp.cpp @@ -142,6 +142,11 @@ CC_MqttsnErrorCode ConnectOp::send(CC_MqttsnConnectCompleteCb cb, void* cbData) return ec; } + if (m_connectMsg.field_flags().field_mid().getBitValue_CleanSession()) { + // Don't wait for acknowledgement, assume state cleared upon send + client().reuseState() = ReuseState(); + } + completeOnError.release(); return CC_MqttsnErrorCode_Success; } diff --git a/client/lib/src/op/Op.cpp b/client/lib/src/op/Op.cpp index faf3dc28..c91d43e1 100644 --- a/client/lib/src/op/Op.cpp +++ b/client/lib/src/op/Op.cpp @@ -8,6 +8,7 @@ #include "op/Op.h" #include "ClientImpl.h" +#include "TopicFilterDefs.h" #include "comms/util/ScopeGuard.h" #include "comms/cast.h" @@ -24,9 +25,20 @@ namespace op namespace { -// static constexpr char TopicSep = '/'; -// static constexpr char MultLevelWildcard = '#'; -// static constexpr char SingleLevelWildcard = '+'; +static constexpr char TopicSep = '/'; +static constexpr char MultLevelWildcard = '#'; +static constexpr char SingleLevelWildcard = '+'; + +template +typename TMap::iterator findRegTopicInfo(CC_MqttsnTopicId topicId, TMap& map) +{ + return + std::lower_bound( + map.begin(), map.end(), topicId, + [](auto& info, CC_MqttsnTopicId topicIdParam) { + return info.m_topicId < topicIdParam; + }); +} } // namespace @@ -145,6 +157,35 @@ void Op::decRetryCount() --m_retryCount; } +void Op::storeInRegTopic(const char* topic, CC_MqttsnTopicId topicId) +{ + auto& map = m_client.reuseState().m_inRegTopics; + auto iter = findRegTopicInfo(topicId, map); + if ((iter != map.end()) && (iter->m_topicId == topicId)) { + iter->m_topic = topic; + return; + } + + map.insert(iter, RegTopicInfo{topic, topicId}); +} + +bool Op::isValidTopicId(CC_MqttsnTopicId id) +{ + return (id != 0U) && (id != 0xffff); +} + +bool Op::isShortTopic(const char* topic) +{ + COMMS_ASSERT(topic != nullptr); + for (auto idx = 0U; idx < sizeof(std::uint16_t); ++idx) { + if (topic[idx] == '\0') { + return false; + } + } + + return topic[sizeof(std::uint16_t)] == '\0'; +} + void Op::errorLogInternal(const char* msg) { if constexpr (Config::HasErrorLog) { @@ -152,130 +193,130 @@ void Op::errorLogInternal(const char* msg) } } -// bool Op::verifySubFilterInternal(const char* filter) -// { -// if (Config::HasTopicFormatVerification) { -// if (!m_client.configState().m_verifyOutgoingTopic) { -// return true; -// } - -// COMMS_ASSERT(filter != nullptr); -// if (filter[0] == '\0') { -// return false; -// } - -// auto pos = 0U; -// int lastSep = -1; -// while (filter[pos] != '\0') { -// auto incPosGuard = -// comms::util::makeScopeGuard( -// [&pos]() -// { -// ++pos; -// }); - -// auto ch = filter[pos]; - -// if (ch == TopicSep) { -// comms::cast_assign(lastSep) = pos; -// continue; -// } - -// if (ch == MultLevelWildcard) { +bool Op::verifySubFilterInternal(const char* filter) +{ + if (Config::HasTopicFormatVerification) { + if (!m_client.configState().m_verifyOutgoingTopic) { + return true; + } + + COMMS_ASSERT(filter != nullptr); + if (filter[0] == '\0') { + return false; + } + + auto pos = 0U; + int lastSep = -1; + while (filter[pos] != '\0') { + auto incPosGuard = + comms::util::makeScopeGuard( + [&pos]() + { + ++pos; + }); + + auto ch = filter[pos]; + + if (ch == TopicSep) { + comms::cast_assign(lastSep) = pos; + continue; + } + + if (ch == MultLevelWildcard) { -// if (filter[pos + 1] != '\0') { -// errorLog("Multi-level wildcard \'#\' must be last."); -// return false; -// } - -// if (pos == 0U) { -// return true; -// } - -// if ((lastSep < 0) || (static_cast(pos - 1U) != lastSep)) { -// errorLog("Multi-level wildcard \'#\' must follow separator."); -// return false; -// } - -// return true; -// } - -// if (ch != SingleLevelWildcard) { -// continue; -// } - -// auto nextCh = filter[pos + 1]; -// if ((nextCh != '\0') && (nextCh != TopicSep)) { -// errorLog("Single-level wildcard \'+\' must be last of followed by /."); -// return false; -// } - -// if (pos == 0U) { -// continue; -// } - -// if ((lastSep < 0) || (static_cast(pos - 1U) != lastSep)) { -// errorLog("Single-level wildcard \'+\' must follow separator."); -// return false; -// } -// } - -// return true; -// } -// else { -// [[maybe_unused]] static constexpr bool ShouldNotBeCalled = false; -// COMMS_ASSERT(ShouldNotBeCalled); -// return false; -// } -// } - -// bool Op::verifyPubTopicInternal(const char* topic, bool outgoing) -// { -// if (Config::HasTopicFormatVerification) { -// if (outgoing && (!m_client.configState().m_verifyOutgoingTopic)) { -// return true; -// } - -// if ((!outgoing) && (!m_client.configState().m_verifyIncomingTopic)) { -// return true; -// } - -// COMMS_ASSERT(topic != nullptr); -// if (topic[0] == '\0') { -// return false; -// } - -// if (outgoing && (topic[0] == '$')) { -// errorLog("Cannot start topic with \'$\'."); -// return false; -// } - -// auto pos = 0U; -// while (topic[pos] != '\0') { -// auto incPosGuard = -// comms::util::makeScopeGuard( -// [&pos]() -// { -// ++pos; -// }); - -// auto ch = topic[pos]; - -// if ((ch == MultLevelWildcard) || -// (ch == SingleLevelWildcard)) { -// errorLog("Wildcards cannot be used in publish topic"); -// return false; -// } -// } - -// return true; -// } -// else { -// [[maybe_unused]] static constexpr bool ShouldNotBeCalled = false; -// COMMS_ASSERT(ShouldNotBeCalled); -// return false; -// } -// } + if (filter[pos + 1] != '\0') { + errorLog("Multi-level wildcard \'#\' must be last."); + return false; + } + + if (pos == 0U) { + return true; + } + + if ((lastSep < 0) || (static_cast(pos - 1U) != lastSep)) { + errorLog("Multi-level wildcard \'#\' must follow separator."); + return false; + } + + return true; + } + + if (ch != SingleLevelWildcard) { + continue; + } + + auto nextCh = filter[pos + 1]; + if ((nextCh != '\0') && (nextCh != TopicSep)) { + errorLog("Single-level wildcard \'+\' must be last of followed by /."); + return false; + } + + if (pos == 0U) { + continue; + } + + if ((lastSep < 0) || (static_cast(pos - 1U) != lastSep)) { + errorLog("Single-level wildcard \'+\' must follow separator."); + return false; + } + } + + return true; + } + else { + [[maybe_unused]] static constexpr bool ShouldNotBeCalled = false; + COMMS_ASSERT(ShouldNotBeCalled); + return false; + } +} + +bool Op::verifyPubTopicInternal(const char* topic, bool outgoing) +{ + if (Config::HasTopicFormatVerification) { + if (outgoing && (!m_client.configState().m_verifyOutgoingTopic)) { + return true; + } + + if ((!outgoing) && (!m_client.configState().m_verifyIncomingTopic)) { + return true; + } + + COMMS_ASSERT(topic != nullptr); + if (topic[0] == '\0') { + return false; + } + + if (outgoing && (topic[0] == '$')) { + errorLog("Cannot start topic with \'$\'."); + return false; + } + + auto pos = 0U; + while (topic[pos] != '\0') { + auto incPosGuard = + comms::util::makeScopeGuard( + [&pos]() + { + ++pos; + }); + + auto ch = topic[pos]; + + if ((ch == MultLevelWildcard) || + (ch == SingleLevelWildcard)) { + errorLog("Wildcards cannot be used in publish topic"); + return false; + } + } + + return true; + } + else { + [[maybe_unused]] static constexpr bool ShouldNotBeCalled = false; + COMMS_ASSERT(ShouldNotBeCalled); + return false; + } +} } // namespace op diff --git a/client/lib/src/op/Op.h b/client/lib/src/op/Op.h index c473fe96..01328b67 100644 --- a/client/lib/src/op/Op.h +++ b/client/lib/src/op/Op.h @@ -10,6 +10,7 @@ #include "ExtConfig.h" #include "ObjListType.h" #include "ProtocolDefs.h" +#include "ReuseState.h" #include "cc_mqttsn_client/common.h" @@ -32,7 +33,7 @@ class Op : public ProtMsgHandler Type_Connect, Type_KeepAlive, Type_Disconnect, - // Type_Subscribe, + Type_Subscribe, // Type_Unsubscribe, // Type_Recv, // Type_Send, @@ -96,6 +97,9 @@ class Op : public ProtMsgHandler std::uint16_t allocPacketId(); void releasePacketId(std::uint16_t id); void decRetryCount(); + void storeInRegTopic(const char* topic, CC_MqttsnTopicId topicId); + static bool isValidTopicId(CC_MqttsnTopicId id); + static bool isShortTopic(const char* topic); const ClientImpl& client() const { @@ -109,25 +113,25 @@ class Op : public ProtMsgHandler } } - // inline bool verifySubFilter(const char* filter) - // { - // if (Config::HasTopicFormatVerification) { - // return verifySubFilterInternal(filter); - // } - // else { - // return true; - // } - // } - - // inline bool verifyPubTopic(const char* topic, bool outgoing) - // { - // if (Config::HasTopicFormatVerification) { - // return verifyPubTopicInternal(topic, outgoing); - // } - // else { - // return true; - // } - // } + inline bool verifySubFilter(const char* filter) + { + if constexpr (Config::HasTopicFormatVerification) { + return verifySubFilterInternal(filter); + } + else { + return true; + } + } + + inline bool verifyPubTopic(const char* topic, bool outgoing) + { + if (Config::HasTopicFormatVerification) { + return verifyPubTopicInternal(topic, outgoing); + } + else { + return true; + } + } static constexpr std::size_t maxStringLen() { @@ -136,8 +140,8 @@ class Op : public ProtMsgHandler private: void errorLogInternal(const char* msg); - // bool verifySubFilterInternal(const char* filter); - // bool verifyPubTopicInternal(const char* topic, bool outgoing); + bool verifySubFilterInternal(const char* filter); + bool verifyPubTopicInternal(const char* topic, bool outgoing); ClientImpl& m_client; unsigned m_retryPeriod = 0U; diff --git a/client/lib/src/op/SubscribeOp.cpp b/client/lib/src/op/SubscribeOp.cpp new file mode 100644 index 00000000..df7896a1 --- /dev/null +++ b/client/lib/src/op/SubscribeOp.cpp @@ -0,0 +1,233 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "op/SubscribeOp.h" +#include "ClientImpl.h" + +#include "comms/util/assign.h" +#include "comms/util/ScopeGuard.h" +#include "comms/units.h" + +#include +#include + +namespace cc_mqttsn_client +{ + +namespace op +{ + +namespace +{ + +inline SubscribeOp* asSubscribeOp(void* data) +{ + return reinterpret_cast(data); +} + +} // namespace + + +SubscribeOp::SubscribeOp(ClientImpl& client) : + Base(client), + m_timer(client.timerMgr().allocTimer()) +{ +} + +CC_MqttsnErrorCode SubscribeOp::config(const CC_MqttsnSubscribeConfig* config) +{ + if (config == nullptr) { + errorLog("Subscribe configuration is not provided."); + return CC_MqttsnErrorCode_BadParam; + } + + bool emptyTopic = + (config->m_topic == nullptr) || + (config->m_topic[0] == '\0'); + + if (emptyTopic && (!isValidTopicId(config->m_topicId))) { + errorLog("Neither topic nor pre-defined topic ID are provided in SUBSCRIBE configuration."); + return CC_MqttsnErrorCode_BadParam; + } + + if (static_castm_qos)>(Config::MaxQos) < config->m_qos) { + errorLog("Bad subscription qos value."); + return CC_MqttsnErrorCode_BadParam; + } + + if ((!emptyTopic) && (!verifySubFilter(config->m_topic))) { + errorLog("Bad topic filter format in subscribe."); + return CC_MqttsnErrorCode_BadParam; + } + + m_subscribeMsg.field_flags().field_qos().setValue(config->m_qos); + + using TopicIdType = SubscribeMsg::Field_flags::Field_topicIdType::ValueType; + if (emptyTopic) { + m_subscribeMsg.field_topicId().field().setValue(config->m_topicId); + m_subscribeMsg.field_flags().field_topicIdType().value() = TopicIdType::PredefinedTopicId; + return CC_MqttsnErrorCode_Success; + } + + if (isShortTopic(config->m_topic)) { + auto topicId = + (static_cast(config->m_topic[0]) << 8U) | + (static_cast(config->m_topic[1])); + m_subscribeMsg.field_topicId().field().setValue(topicId); + m_subscribeMsg.field_flags().field_topicIdType().value() = TopicIdType::ShortTopicName; + return CC_MqttsnErrorCode_Success; + } + + m_subscribeMsg.field_topicName().field().value() = config->m_topic; + m_subscribeMsg.field_flags().field_topicIdType().value() = TopicIdType::Normal; + return CC_MqttsnErrorCode_Success; +} + +CC_MqttsnErrorCode SubscribeOp::send(CC_MqttsnSubscribeCompleteCb cb, void* cbData) +{ + client().allowNextPrepare(); + auto completeOnError = + comms::util::makeScopeGuard( + [this]() + { + opComplete(); + }); + + if (cb == nullptr) { + errorLog("Subscribe completion callback is not provided."); + return CC_MqttsnErrorCode_BadParam; + } + + if (!m_timer.isValid()) { + errorLog("The library cannot allocate required number of timers."); + return CC_MqttsnErrorCode_InternalError; + } + + auto guard = client().apiEnter(); + m_cb = cb; + m_cbData = cbData; + + m_subscribeMsg.field_msgId().setValue(allocPacketId()); + m_subscribeMsg.doRefresh(); // Update optionals + + auto ec = sendInternal(); + if (ec != CC_MqttsnErrorCode_Success) { + return ec; + } + + completeOnError.release(); + return CC_MqttsnErrorCode_Success; +} + +CC_MqttsnErrorCode SubscribeOp::cancel() +{ + if (m_cb == nullptr) { + // hasn't been sent yet + client().allowNextPrepare(); + } + + opComplete(); + return CC_MqttsnErrorCode_Success; +} + +void SubscribeOp::handle(SubackMsg& msg) +{ + if (m_suspended || (msg.field_msgId().value() != m_subscribeMsg.field_msgId().value())) { + errorLog("Unexpected SUBACK message received"); + return; + } + + m_timer.cancel(); + + auto info = CC_MqttsnSubscribeInfo(); + info.m_returnCode = static_cast(msg.field_returnCode().value()); + info.m_qos = static_cast(msg.field_flags().field_qos().value()); + info.m_topicId = msg.field_topicId().value(); + + if ((info.m_topicId != 0U) && (!m_subscribeMsg.field_topicName().field().value().empty())) { + storeInRegTopic(m_subscribeMsg.field_topicName().field().value().c_str(), info.m_topicId); + } + + completeOpInternal(CC_MqttsnAsyncOpStatus_Complete); +} + +void SubscribeOp::resume() +{ + COMMS_ASSERT(m_suspended); + m_suspended = false; + auto ec = sendInternal(); + if (ec != CC_MqttsnErrorCode_Success) { + errorLog("Failed to send SUBSCRIBE, after prev SUBSCRIBE completion"); + completeOpInternal(CC_MqttsnAsyncOpStatus_InternalError); + return; + } +} + +Op::Type SubscribeOp::typeImpl() const +{ + return Type_Subscribe; +} + +void SubscribeOp::terminateOpImpl(CC_MqttsnAsyncOpStatus status) +{ + completeOpInternal(status); +} + +void SubscribeOp::completeOpInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info) +{ + auto cb = m_cb; + auto* cbData = m_cbData; + opComplete(); // mustn't access data members after destruction + if (cb != nullptr) { + cb(cbData, status, info); + } +} + +void SubscribeOp::restartTimer() +{ + m_timer.wait(getRetryPeriod(), &SubscribeOp::opTimeoutCb, this); +} + +CC_MqttsnErrorCode SubscribeOp::sendInternal() +{ + if (m_suspended) { + return CC_MqttsnErrorCode_Success; // Send after resume + } + + auto ec = sendMessage(m_subscribeMsg); + if (ec == CC_MqttsnErrorCode_Success) { + restartTimer(); + } + + return ec; +} + +void SubscribeOp::timeoutInternal() +{ + if (getRetryCount() == 0U) { + errorLog("All retries of the subscribe operation have been exhausted."); + completeOpInternal(CC_MqttsnAsyncOpStatus_Timeout); + return; + } + + auto ec = sendInternal(); + if (ec != CC_MqttsnErrorCode_Success) { + completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); + return; + } + + decRetryCount(); +} + +void SubscribeOp::opTimeoutCb(void* data) +{ + asSubscribeOp(data)->timeoutInternal(); +} + +} // namespace op + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/op/SubscribeOp.h b/client/lib/src/op/SubscribeOp.h new file mode 100644 index 00000000..a99f0a01 --- /dev/null +++ b/client/lib/src/op/SubscribeOp.h @@ -0,0 +1,66 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "op/Op.h" +#include "ExtConfig.h" +#include "ProtocolDefs.h" + +#include "TimerMgr.h" + +namespace cc_mqttsn_client +{ + +namespace op +{ + +class SubscribeOp final : public Op +{ + using Base = Op; +public: + explicit SubscribeOp(ClientImpl& client); + + CC_MqttsnErrorCode config(const CC_MqttsnSubscribeConfig* config); + CC_MqttsnErrorCode send(CC_MqttsnSubscribeCompleteCb cb, void* cbData); + CC_MqttsnErrorCode cancel(); + + using Base::handle; + void handle(SubackMsg& msg) override; + + void suspend() + { + m_suspended = true; + } + + void resume(); + +protected: + virtual Type typeImpl() const override; + virtual void terminateOpImpl(CC_MqttsnAsyncOpStatus status) override; + +private: + void completeOpInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info = nullptr); + void restartTimer(); + CC_MqttsnErrorCode sendInternal(); + void timeoutInternal(); + + static void opTimeoutCb(void* data); + + SubscribeMsg m_subscribeMsg; + TimerMgr::Timer m_timer; + CC_MqttsnSubscribeCompleteCb m_cb = nullptr; + void* m_cbData = nullptr; + bool m_suspended = false; + + static_assert(ExtConfig::SubscribeOpTimers == 1U); +}; + +} // namespace op + + +} // namespace cc_mqttsn_client diff --git a/client/lib/templ/Config.h.templ b/client/lib/templ/Config.h.templ index 5a4783cd..4c9f25fd 100644 --- a/client/lib/templ/Config.h.templ +++ b/client/lib/templ/Config.h.templ @@ -19,7 +19,8 @@ struct Config static constexpr bool HasErrorLog = ##CC_MQTTSN_CLIENT_HAS_ERROR_LOG_CPP##; static constexpr bool HasTopicFormatVerification = ##CC_MQTTSN_CLIENT_HAS_TOPIC_FORMAT_VERIFICATION_CPP##; static constexpr bool HasSubTopicVerification = ##CC_MQTTSN_CLIENT_HAS_SUB_TOPIC_VERIFICATION_CPP##; -// static constexpr unsigned SubFiltersLimit = ##CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT##; + static constexpr unsigned SubFiltersLimit = ##CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT##; + static constexpr unsigned InRegTopicsLimit = ##CC_MQTTSN_CLIENT_IN_REG_TOPICS_LIMIT##; static constexpr unsigned MaxQos = ##CC_MQTTSN_CLIENT_MAX_QOS##; static_assert(HasDynMemAlloc || (ClientAllocLimit > 0U), "Must use CC_MQTTSN_CLIENT_ALLOC_LIMIT in configuration to limit number of clients"); @@ -28,9 +29,10 @@ struct Config static_assert(HasDynMemAlloc || (MaxOutputPacketSize > 0U), "Must use CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE in configuration to limit packet size"); // static_assert(HasDynMemAlloc || (ReceiveMaxLimit > 0U) || (MaxQos < 2), "Must use CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT in configuration to limit amount of messages to receive"); // static_assert(HasDynMemAlloc || (SendMaxLimit > 0U), "Must use CC_MQTTSN_CLIENT_SEND_MAX_LIMIT in configuration to limit amount of messages to send"); -// static_assert(HasDynMemAlloc || (SubscribeOpsLimit > 0U), "Must use CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT in configuration to limit amount of unfinished subscribes."); -// static_assert(HasDynMemAlloc || (UnsubscribeOpsLimit > 0U), "Must use CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT in configuration to limit amount of unfinished unsubscribes."); - //static_assert(HasDynMemAlloc || (!HasSubTopicVerification) || (SubFiltersLimit > 0U), "Must use CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT in configuration to limit amount of subscribe filters"); + static_assert(HasDynMemAlloc || (SubscribeOpsLimit > 0U), "Must use CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT in configuration to limit amount of unfinished subscribes."); + static_assert(HasDynMemAlloc || (UnsubscribeOpsLimit > 0U), "Must use CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT in configuration to limit amount of unfinished unsubscribes."); + static_assert(HasDynMemAlloc || (!HasSubTopicVerification) || (SubFiltersLimit > 0U), "Must use CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT in configuration to limit amount of subscribe filters"); + static_assert(HasDynMemAlloc || (InRegTopicsLimit > 0U), "Must use CC_MQTTSN_CLIENT_IN_REG_TOPICS_LIMIT in configuration to limit amount of registered topics"); static_assert(MaxQos <= 2, "Not supported QoS value"); }; diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index 849ea7c5..51539650 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -18,6 +18,7 @@ struct CC_MqttsnClient {}; struct CC_MqttsnSearch {}; struct CC_MqttsnConnect {}; struct CC_MqttsnDisconnect {}; +struct CC_MqttsnSubscribe {}; namespace { @@ -70,6 +71,16 @@ inline CC_MqttsnDisconnectHandle handleFromDisconnectOp(cc_mqttsn_client::op::Di return reinterpret_cast(op); } +inline cc_mqttsn_client::op::SubscribeOp* subscribeOpFromHandle(CC_MqttsnSubscribeHandle handle) +{ + return reinterpret_cast(handle); +} + +inline CC_MqttsnSubscribeHandle handleFromSubscribeOp(cc_mqttsn_client::op::SubscribeOp* op) +{ + return reinterpret_cast(op); +} + } // namespace CC_MqttsnClientHandle cc_mqttsn_##NAME##client_alloc() @@ -298,6 +309,75 @@ unsigned cc_mqttsn_##NAME##client_get_allowed_adv_losses(CC_MqttsnClientHandle c return clientFromHandle(client)->configState().m_allowedAdvLosses; } +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_verify_outgoing_topic_enabled(CC_MqttsnClientHandle client, bool enabled) +{ + if constexpr (cc_mqttsn_client::Config::HasTopicFormatVerification) { + COMMS_ASSERT(client != nullptr); + clientFromHandle(client)->configState().m_verifyOutgoingTopic = enabled; + return CC_MqttsnErrorCode_Success; + } + else { + return CC_MqttsnErrorCode_NotSupported; + } +} + +bool cc_mqttsn_##NAME##client_get_verify_outgoing_topic_enabled(CC_MqttsnClientHandle client) +{ + if constexpr (cc_mqttsn_client::Config::HasTopicFormatVerification) { + COMMS_ASSERT(client != nullptr); + return clientFromHandle(client)->configState().m_verifyOutgoingTopic; + } + else { + return false; + } +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_verify_incoming_topic_enabled(CC_MqttsnClientHandle client, bool enabled) +{ + if constexpr (cc_mqttsn_client::Config::HasTopicFormatVerification) { + COMMS_ASSERT(client != nullptr); + clientFromHandle(client)->configState().m_verifyIncomingTopic = enabled; + return CC_MqttsnErrorCode_Success; + } + else { + return CC_MqttsnErrorCode_NotSupported; + } +} + +bool cc_mqttsn_##NAME##client_get_verify_incoming_topic_enabled(CC_MqttsnClientHandle client) +{ + if constexpr (cc_mqttsn_client::Config::HasTopicFormatVerification) { + COMMS_ASSERT(client != nullptr); + return clientFromHandle(client)->configState().m_verifyIncomingTopic; + } + else { + return false; + } +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_verify_incoming_msg_subscribed(CC_MqttsnClientHandle client, bool enabled) +{ + if constexpr (cc_mqttsn_client::Config::HasSubTopicVerification) { + COMMS_ASSERT(client != nullptr); + clientFromHandle(client)->configState().m_verifySubFilter = enabled; + return CC_MqttsnErrorCode_Success; + } + else { + return CC_MqttsnErrorCode_NotSupported; + } +} + +bool cc_mqttsn_##NAME##client_get_verify_incoming_msg_subscribed(CC_MqttsnClientHandle client) +{ + if constexpr (cc_mqttsn_client::Config::HasSubTopicVerification) { + COMMS_ASSERT(client != nullptr); + return clientFromHandle(client)->configState().m_verifySubFilter; + } + else { + return false; + } +} + CC_MqttsnSearchHandle cc_mqttsn_##NAME##client_search_prepare( [[maybe_unused]] CC_MqttsnClientHandle client, [[maybe_unused]] CC_MqttsnErrorCode* ec) @@ -629,6 +709,99 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect( return cc_mqttsn_##NAME##client_disconnect_send(disconnect, cb, cbData); } +CC_MqttsnSubscribeHandle cc_mqttsn_##NAME##client_subscribe_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec) +{ + COMMS_ASSERT(client != nullptr); + return handleFromSubscribeOp(clientFromHandle(client)->subscribePrepare(ec)); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_subscribe_set_retry_period(CC_MqttsnSubscribeHandle handle, unsigned ms) +{ + COMMS_ASSERT(handle != nullptr); + if (ms == 0U) { + subscribeOpFromHandle(handle)->client().errorLog("The retry period must be greater than 0"); + return CC_MqttsnErrorCode_BadParam; + } + + COMMS_ASSERT(handle != nullptr); + subscribeOpFromHandle(handle)->setRetryPeriod(ms); + return CC_MqttsnErrorCode_Success; +} + +unsigned cc_mqttsn_##NAME##client_subscribe_get_retry_period(CC_MqttsnSubscribeHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return subscribeOpFromHandle(handle)->getRetryPeriod(); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_subscribe_set_retry_count(CC_MqttsnSubscribeHandle handle, unsigned count) +{ + COMMS_ASSERT(handle != nullptr); + subscribeOpFromHandle(handle)->setRetryCount(count); + return CC_MqttsnErrorCode_Success; +} + +unsigned cc_mqttsn_##NAME##client_subscribe_get_retry_count(CC_MqttsnSubscribeHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return subscribeOpFromHandle(handle)->getRetryCount(); +} + +void cc_mqttsn_##NAME##client_subscribe_init_config(CC_MqttsnSubscribeConfig* config) +{ + COMMS_ASSERT(config != nullptr); + *config = CC_MqttsnSubscribeConfig(); + config->m_qos = static_castm_qos)>(cc_mqttsn_client::Config::MaxQos); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_subscribe_config(CC_MqttsnSubscribeHandle handle, const CC_MqttsnSubscribeConfig* config) +{ + COMMS_ASSERT(handle != nullptr); + return subscribeOpFromHandle(handle)->config(config); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_subscribe_send(CC_MqttsnSubscribeHandle handle, CC_MqttsnSubscribeCompleteCb cb, void* cbData) +{ + COMMS_ASSERT(handle != nullptr); + return subscribeOpFromHandle(handle)->send(cb, cbData); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_subscribe_cancel(CC_MqttsnSubscribeHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return subscribeOpFromHandle(handle)->cancel(); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_subscribe( + CC_MqttsnClientHandle client, + const CC_MqttsnSubscribeConfig* config, + CC_MqttsnSubscribeCompleteCb cb, + void* cbData) +{ + auto ec = CC_MqttsnErrorCode_Success; + auto subscribe = cc_mqttsn_##NAME##client_subscribe_prepare(client, &ec); + if (subscribe == nullptr) { + return ec; + } + + auto cancelOnExitGuard = + comms::util::makeScopeGuard( + [subscribe]() + { + [[maybe_unused]] auto ecTmp = cc_mqttsn_##NAME##client_subscribe_cancel(subscribe); + }); + + if (config != nullptr) { + ec = cc_mqttsn_##NAME##client_subscribe_config(subscribe, config); + if (ec != CC_MqttsnErrorCode_Success) { + return ec; + } + } + + cancelOnExitGuard.release(); + return cc_mqttsn_##NAME##client_subscribe_send(subscribe, cb, cbData); +} + // --------------------- Callbacks --------------------- void cc_mqttsn_##NAME##client_set_next_tick_program_callback( diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index c5432e9e..d0678109 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -24,6 +24,7 @@ extern "C" { /// @defgroup subscribe "Subscribe Operation Data Types and Functions" /// @defgroup unsubscribe "Unsubscribe Operation Data Types and Functions" /// @defgroup publish "Publish Operation Data Types and Functions" +/// @defgroup will "Will Update Operation Data Types and Functions" /// @brief Allocate new client. /// @details When work with the client is complete, @ref cc_mqttsn_##NAME##client_free() @@ -199,6 +200,39 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_allowed_adv_losses(CC_MqttsnClie /// @ingroup client unsigned cc_mqttsn_##NAME##client_get_allowed_adv_losses(CC_MqttsnClientHandle client); +/// @brief Control outgoing topic format verification +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[in] enabled @b true to enable topic format verification, @b false to disable. +/// @return Error code of the operation +/// @ingroup client +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_verify_outgoing_topic_enabled(CC_MqttsnClientHandle client, bool enabled); + +/// @brief Retrieve current outgoing topic format verification control +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @return @b true when enabled, @b false when disabled +/// @ingroup client +bool cc_mqttsn_##NAME##client_get_verify_outgoing_topic_enabled(CC_MqttsnClientHandle client); + +/// @brief Control incoming topic format verification +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[in] enabled @b true to enable topic format verification, @b false to disable. +/// @return Error code of the operation +/// @ingroup client +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_verify_incoming_topic_enabled(CC_MqttsnClientHandle client, bool enabled); + +/// @brief Retrieve current incoming topic format verification control +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @return @b true when enabled, @b false when disabled +/// @ingroup client +bool cc_mqttsn_##NAME##client_get_verify_incoming_topic_enabled(CC_MqttsnClientHandle client); + +/// @brief Control verification of the incoming message being correctly subscribed. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[in] enabled @b true to enable topic format verification, @b false to disable. +/// @return Error code of the operation +/// @ingroup client +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_verify_incoming_msg_subscribed(CC_MqttsnClientHandle client, bool enabled); + /// @brief Prepare "search" operation. /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[out] ec Error code reporting result of the operation. Can be NULL. @@ -463,6 +497,93 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect( CC_MqttsnDisconnectCompleteCb cb, void* cbData); +/// @brief Prepare "subscribe" operation. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[out] ec Error code reporting result of the operation. Can be NULL. +/// @return Handle of the "subscribe" operation, will be NULL in case of failure. To analyze the reason failure use "ec" output parameter. +/// @post The "subscribe" operation is allocated, use either @ref cc_mqttsn_##NAME##client_subscribe_send() +/// or @ref cc_mqttsn_##NAME##client_subscribe_cancel() to prevent memory leaks. +/// @ingroup subscribe +CC_MqttsnSubscribeHandle cc_mqttsn_##NAME##client_subscribe_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec); + +/// @brief Configure the retry period for the "subscribe" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_subscribe_prepare() function. +/// @param[in] ms Retry period in @b milliseconds. +/// @return Result code of the call. +/// @ingroup subscribe +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_subscribe_set_retry_period(CC_MqttsnSubscribeHandle handle, unsigned ms); + +/// @brief Retrieve the configured retry period for the "subscribe" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_subscribe_prepare() function. +/// @return Retry period duration in @b milliseconds. +/// @ingroup subscribe +unsigned cc_mqttsn_##NAME##client_subscribe_get_retry_period(CC_MqttsnSubscribeHandle handle); + +/// @brief Configure the retry count for the "subscribe" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_subscribe_prepare() function. +/// @param[in] count Number of retries. +/// @return Result code of the call. +/// @ingroup subscribe +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_subscribe_set_retry_count(CC_MqttsnSubscribeHandle handle, unsigned count); + +/// @brief Retrieve the configured retry count for the "subscribe" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_subscribe_prepare() function. +/// @return Number of retries. +/// @ingroup subscribe +unsigned cc_mqttsn_##NAME##client_subscribe_get_retry_count(CC_MqttsnSubscribeHandle handle); + +/// @brief Intialize the @ref CC_MqttsnSubscribeConfig configuration structure. +/// @param[out] config Configuration structure. Must not be NULL. +/// @ingroup subscribe +void cc_mqttsn_##NAME##client_subscribe_init_config(CC_MqttsnSubscribeConfig* config); + +/// @brief Perform configuration of the "subscribe" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_subscribe_prepare() function. +/// @param[in] config Configuration structure. Must NOT be NULL. Does not need to be preserved after invocation. +/// @return Result code of the call. +/// @ingroup subscribe +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_subscribe_config(CC_MqttsnSubscribeHandle handle, const CC_MqttsnSubscribeConfig* config); + +/// @brief Send the "subscribe" operation +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_subscribe_prepare() function. +/// @param[in] cb Callback to be invoked when "subscribe" operation is complete. +/// @param[in] cbData Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +/// @return Result code of the call. +/// @post The handle of the "subscribe" operation can be discarded. +/// @post The provided callback will be invoked when the "subscribe" operation is complete if and only if +/// the function returns @ref CC_MqttsnErrorCode_Success. +/// @ingroup subscribe +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_subscribe_send(CC_MqttsnSubscribeHandle handle, CC_MqttsnSubscribeCompleteCb cb, void* cbData); + +/// @brief Cancel the allocated "subscribe" operation +/// @details In case the @ref cc_mqttsn_##NAME##client_subscribe_send() function was successfully called before, +/// the operation is cancelled @b without callback invocation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_subscribe_prepare() function. +/// @return Result code of the call. +/// @post The handle of the "subscribe" operation is no longer valid and must be discarded. +/// @ingroup subscribe +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_subscribe_cancel(CC_MqttsnSubscribeHandle handle); + +/// @brief Prepare and send "subscribe" request in one go +/// @details Abstracts away sequence of the following functions invocation: +/// @li @ref cc_mqttsn_##NAME##client_subscribe_prepare() +/// @li @ref cc_mqttsn_##NAME##client_subscribe_config() +/// @li @ref cc_mqttsn_##NAME##client_subscribe_send() +/// +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[in] config Subscription configuration. +/// @param[in] cb Callback to be invoked when "subscribe" operation is complete. +/// @param[in] cbData Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +/// @return Result code of the call. +/// @ingroup subscribe +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_subscribe( + CC_MqttsnClientHandle client, + const CC_MqttsnSubscribeConfig* config, + CC_MqttsnSubscribeCompleteCb cb, + void* cbData); + // --------------------- Callbacks --------------------- /// @brief Set callback to call when time measurement is required. From b3a8f9d56f798b49f3dc3073adac84911594aea3 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 11 Jul 2024 09:29:07 +1000 Subject: [PATCH 059/106] Saving work on client subscribe unit-testing. --- client/lib/src/op/Op.cpp | 5 + client/lib/src/op/SubscribeOp.cpp | 26 +- client/lib/test/CMakeLists.txt | 6 +- client/lib/test/UnitTestCommonBase.cpp | 156 ++++--- client/lib/test/UnitTestCommonBase.h | 78 +++- client/lib/test/UnitTestConnect.th | 18 +- client/lib/test/UnitTestDefaultBase.cpp | 12 +- client/lib/test/UnitTestDisconnect.th | 15 +- client/lib/test/UnitTestGwDiscover.th | 28 +- client/lib/test/UnitTestSubscribe.th | 538 ++++++++++++++++++++++++ cmake/Compile.cmake | 2 +- 11 files changed, 776 insertions(+), 108 deletions(-) create mode 100644 client/lib/test/UnitTestSubscribe.th diff --git a/client/lib/src/op/Op.cpp b/client/lib/src/op/Op.cpp index c91d43e1..6915df8b 100644 --- a/client/lib/src/op/Op.cpp +++ b/client/lib/src/op/Op.cpp @@ -166,6 +166,11 @@ void Op::storeInRegTopic(const char* topic, CC_MqttsnTopicId topicId) return; } + if (topic == nullptr) { + map.insert(iter, RegTopicInfo{TopicNameStr(), topicId}); + return; + } + map.insert(iter, RegTopicInfo{topic, topicId}); } diff --git a/client/lib/src/op/SubscribeOp.cpp b/client/lib/src/op/SubscribeOp.cpp index df7896a1..3cc8e7fd 100644 --- a/client/lib/src/op/SubscribeOp.cpp +++ b/client/lib/src/op/SubscribeOp.cpp @@ -136,7 +136,11 @@ CC_MqttsnErrorCode SubscribeOp::cancel() void SubscribeOp::handle(SubackMsg& msg) { - if (m_suspended || (msg.field_msgId().value() != m_subscribeMsg.field_msgId().value())) { + if (m_suspended) { + return; + } + + if (msg.field_msgId().value() != m_subscribeMsg.field_msgId().value()) { errorLog("Unexpected SUBACK message received"); return; } @@ -148,11 +152,29 @@ void SubscribeOp::handle(SubackMsg& msg) info.m_qos = static_cast(msg.field_flags().field_qos().value()); info.m_topicId = msg.field_topicId().value(); + do { + if (info.m_topicId == 0U) { + break; + } + + auto& topicStr = m_subscribeMsg.field_topicName().field().value(); + if (!topicStr.empty()) { + storeInRegTopic(topicStr.c_str(), info.m_topicId); + break; + } + + if constexpr (Config::HasSubTopicVerification) { + storeInRegTopic(nullptr, info.m_topicId); + break; + } + + } while (false); + if ((info.m_topicId != 0U) && (!m_subscribeMsg.field_topicName().field().value().empty())) { storeInRegTopic(m_subscribeMsg.field_topicName().field().value().c_str(), info.m_topicId); } - completeOpInternal(CC_MqttsnAsyncOpStatus_Complete); + completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &info); } void SubscribeOp::resume() diff --git a/client/lib/test/CMakeLists.txt b/client/lib/test/CMakeLists.txt index 622af98f..1f15ad58 100644 --- a/client/lib/test/CMakeLists.txt +++ b/client/lib/test/CMakeLists.txt @@ -21,8 +21,8 @@ target_include_directories( function (cc_mqttsn_client_add_unit_test name test_lib) set (src ${CMAKE_CURRENT_SOURCE_DIR}/${name}.th) - cc_cxxtest_add_test (NAME unit.${name} SRC ${src}) - target_link_libraries(unit.${name} PRIVATE ${test_lib} cxxtest::cxxtest) + cc_cxxtest_add_test (NAME cc.mqttsn.client.${name} SRC ${src}) + target_link_libraries(cc.mqttsn.client.${name} PRIVATE ${test_lib} cxxtest::cxxtest) endfunction () ################################## @@ -44,7 +44,7 @@ if (TARGET cc::cc_mqttsn_client) cc_mqttsn_client_add_unit_test(UnitTestDisconnect ${DEFAULT_BASE_LIB_NAME}) # cc_mqttsn_client_add_unit_test(UnitTestPublish ${DEFAULT_BASE_LIB_NAME}) # cc_mqttsn_client_add_unit_test(UnitTestReceive ${DEFAULT_BASE_LIB_NAME}) -# cc_mqttsn_client_add_unit_test(UnitTestSubscribe ${DEFAULT_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(UnitTestSubscribe ${DEFAULT_BASE_LIB_NAME}) # cc_mqttsn_client_add_unit_test(UnitTestUnsubscribe ${DEFAULT_BASE_LIB_NAME}) endif () diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index 4b3993bd..9f4bdbcc 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -74,7 +74,17 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_disconnect_get_retry_count != nullptr); test_assert(m_funcs.m_disconnect_send != nullptr); test_assert(m_funcs.m_disconnect_cancel != nullptr); - test_assert(m_funcs.m_disconnect != nullptr); + test_assert(m_funcs.m_disconnect != nullptr); + test_assert(m_funcs.m_subscribe_prepare != nullptr); + test_assert(m_funcs.m_subscribe_set_retry_period != nullptr); + test_assert(m_funcs.m_subscribe_get_retry_period != nullptr); + test_assert(m_funcs.m_subscribe_set_retry_count != nullptr); + test_assert(m_funcs.m_subscribe_get_retry_count != nullptr); + test_assert(m_funcs.m_subscribe_init_config != nullptr); + test_assert(m_funcs.m_subscribe_config != nullptr); + test_assert(m_funcs.m_subscribe_send != nullptr); + test_assert(m_funcs.m_subscribe_cancel != nullptr); + test_assert(m_funcs.m_subscribe != nullptr); test_assert(m_funcs.m_set_next_tick_program_callback != nullptr); test_assert(m_funcs.m_set_cancel_next_tick_wait_callback != nullptr); @@ -140,6 +150,23 @@ UnitTestCommonBase::UnitTestConnectCompleteReport::UnitTestConnectCompleteReport } } +UnitTestCommonBase::UnitTestSubscribeInfo& UnitTestCommonBase::UnitTestSubscribeInfo::operator=(const CC_MqttsnSubscribeInfo& info) +{ + m_returnCode = info.m_returnCode; + m_topicId = info.m_topicId; + m_qos = info.m_qos; + return *this; +} + +UnitTestCommonBase::UnitTestSubscribeCompleteReport::UnitTestSubscribeCompleteReport(CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info) : + m_status(status) +{ + if (info != nullptr) { + m_info = *info; + } +} + + void UnitTestCommonBase::unitTestSetUp() { } @@ -283,40 +310,33 @@ bool UnitTestCommonBase::unitTestHasGwInfoReport() const return !m_data.m_gwInfoReports.empty(); } -const UnitTestCommonBase::UnitTestGwInfoReport* UnitTestCommonBase::unitTestGetGwInfoReport(bool mustExist) const +UnitTestCommonBase::UnitTestGwInfoReportPtr UnitTestCommonBase::unitTestGetGwInfoReport(bool mustExist) { if (!unitTestHasGwInfoReport()) { test_assert(!mustExist); - return nullptr; + return UnitTestGwInfoReportPtr(); } - return &m_data.m_gwInfoReports.front(); -} - -void UnitTestCommonBase::unitTestPopGwInfoReport() -{ - test_assert(!m_data.m_gwInfoReports.empty()); + auto ptr = std::move(m_data.m_gwInfoReports.front()); m_data.m_gwInfoReports.pop_front(); + return ptr; } bool UnitTestCommonBase::unitTestHasGwDisconnectReport() const { return !m_data.m_gwDisconnectReports.empty(); } -const UnitTestCommonBase::UnitTestGwDisconnectReport* UnitTestCommonBase::unitTestGetGwDisconnectReport(bool mustExist) const + +UnitTestCommonBase::UnitTestGwDisconnectReportPtr UnitTestCommonBase::unitTestGetGwDisconnectReport(bool mustExist) { if (!unitTestHasGwDisconnectReport()) { test_assert(!mustExist); - return nullptr; + return UnitTestGwDisconnectReportPtr(); } - return &m_data.m_gwDisconnectReports.front(); -} - -void UnitTestCommonBase::unitTestPopGwDisconnectReport() -{ - test_assert(!m_data.m_gwDisconnectReports.empty()); + auto ptr = std::move(m_data.m_gwDisconnectReports.front()); m_data.m_gwDisconnectReports.pop_front(); + return ptr; } bool UnitTestCommonBase::unitTestHasSearchCompleteReport() const @@ -324,20 +344,16 @@ bool UnitTestCommonBase::unitTestHasSearchCompleteReport() const return !m_data.m_searchCompleteReports.empty(); } -const UnitTestCommonBase::UnitTestSearchCompleteReport* UnitTestCommonBase::unitTestSearchCompleteReport(bool mustExist) const +UnitTestCommonBase::UnitTestSearchCompleteReportPtr UnitTestCommonBase::unitTestSearchCompleteReport(bool mustExist) { if (!unitTestHasSearchCompleteReport()) { test_assert(!mustExist); - return nullptr; + return UnitTestSearchCompleteReportPtr(); } - return &m_data.m_searchCompleteReports.front(); -} - -void UnitTestCommonBase::unitTestPopSearchCompleteReport() -{ - test_assert(unitTestHasSearchCompleteReport()); + auto ptr = std::move(m_data.m_searchCompleteReports.front()); m_data.m_searchCompleteReports.pop_front(); + return ptr; } CC_MqttsnErrorCode UnitTestCommonBase::unitTestSearchSend(CC_MqttsnSearchHandle search, UnitTestSearchCompleteCb&& cb) @@ -389,20 +405,16 @@ bool UnitTestCommonBase::unitTestHasConnectCompleteReport() const return !m_data.m_connectCompleteReports.empty(); } -const UnitTestCommonBase::UnitTestConnectCompleteReport* UnitTestCommonBase::unitTestConnectCompleteReport(bool mustExist) const +UnitTestCommonBase::UnitTestConnectCompleteReportPtr UnitTestCommonBase::unitTestConnectCompleteReport(bool mustExist) { if (!unitTestHasConnectCompleteReport()) { test_assert(!mustExist); - return nullptr; + return UnitTestConnectCompleteReportPtr(); } - return &m_data.m_connectCompleteReports.front(); -} - -void UnitTestCommonBase::unitTestPopConnectCompleteReport() -{ - test_assert(unitTestHasConnectCompleteReport()); + auto ptr = std::move(m_data.m_connectCompleteReports.front()); m_data.m_connectCompleteReports.pop_front(); + return ptr; } CC_MqttsnErrorCode UnitTestCommonBase::unitTestConnectSend(CC_MqttsnConnectHandle connect) @@ -474,10 +486,9 @@ void UnitTestCommonBase::unitTestDoConnect(CC_MqttsnClient* client, const CC_Mqt unitTestClientInputMessage(client, connackMsg); test_assert(unitTestHasConnectCompleteReport()); - auto* connectReport = unitTestConnectCompleteReport(); + auto connectReport = unitTestConnectCompleteReport(); test_assert(connectReport->m_status== CC_MqttsnAsyncOpStatus_Complete) test_assert(connectReport->m_info.m_returnCode == CC_MqttsnReturnCode_Accepted) - unitTestPopConnectCompleteReport(); test_assert(apiIsConnected(client)); } @@ -495,20 +506,17 @@ bool UnitTestCommonBase::unitTestHasDisconnectCompleteReport() const return !m_data.m_disconnectCompleteReports.empty(); } -const UnitTestCommonBase::UnitTestDisconnectCompleteReport* UnitTestCommonBase::unitTestDisconnectCompleteReport(bool mustExist) const +UnitTestCommonBase::UnitTestDisconnectCompleteReportPtr UnitTestCommonBase::unitTestDisconnectCompleteReport(bool mustExist) { if (!unitTestHasDisconnectCompleteReport()) { test_assert(!mustExist); return nullptr; } - return &m_data.m_disconnectCompleteReports.front(); -} + auto ptr = std::move(m_data.m_disconnectCompleteReports.front()); + m_data.m_disconnectCompleteReports.pop_front(); + return ptr; -void UnitTestCommonBase::unitTestPopDisconnectCompleteReport() -{ - test_assert(unitTestHasDisconnectCompleteReport()); - m_data.m_disconnectCompleteReports.pop_front(); } CC_MqttsnErrorCode UnitTestCommonBase::unitTestDisconnectSend(CC_MqttsnDisconnectHandle disconnect) @@ -516,6 +524,28 @@ CC_MqttsnErrorCode UnitTestCommonBase::unitTestDisconnectSend(CC_MqttsnDisconnec return m_funcs.m_disconnect_send(disconnect, &UnitTestCommonBase::unitTestDisconnectCompleteCb, this); } +bool UnitTestCommonBase::unitTestHasSubscribeCompleteReport() const +{ + return !m_data.m_subscribeCompleteReports.empty(); +} + +UnitTestCommonBase::UnitTestSubscribeCompleteReportPtr UnitTestCommonBase::unitTestSubscribeCompleteReport(bool mustExist) +{ + if (!unitTestHasSubscribeCompleteReport()) { + test_assert(!mustExist); + return UnitTestSubscribeCompleteReportPtr(); + } + + auto ptr = std::move(m_data.m_subscribeCompleteReports.front()); + m_data.m_subscribeCompleteReports.pop_front(); + return ptr; +} + +CC_MqttsnErrorCode UnitTestCommonBase::unitTestSubscribeSend(CC_MqttsnSubscribeHandle subscribe) +{ + return m_funcs.m_subscribe_send(subscribe, &UnitTestCommonBase::unitTestSubscribeCompleteCb, this); +} + void UnitTestCommonBase::apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen) { m_funcs.m_process_data(client, buf, bufLen); @@ -626,12 +656,32 @@ void UnitTestCommonBase::unitTestSendOutputDataCb(void* data, const unsigned cha void UnitTestCommonBase::unitTestGwStatusReportCb(void* data, CC_MqttsnGwStatus status, const CC_MqttsnGatewayInfo* info) { - asThis(data)->m_data.m_gwInfoReports.emplace_back(status, info); + asThis(data)->m_data.m_gwInfoReports.push_back(std::make_unique(status, info)); } void UnitTestCommonBase::unitTestGwDisconnectReportCb(void* data, CC_MqttsnGatewayDisconnectReason reason) { - asThis(data)->m_data.m_gwDisconnectReports.emplace_back(reason); + asThis(data)->m_data.m_gwDisconnectReports.push_back(std::make_unique(reason)); +} + +CC_MqttsnSubscribeHandle UnitTestCommonBase::apiSubscribePrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec) +{ + return m_funcs.m_subscribe_prepare(client, ec); +} + +CC_MqttsnErrorCode UnitTestCommonBase::apiSubscribeSetRetryCount(CC_MqttsnSubscribeHandle subscribe, unsigned count) +{ + return m_funcs.m_subscribe_set_retry_count(subscribe, count); +} + +void UnitTestCommonBase::apiSubscribeInitConfig(CC_MqttsnSubscribeConfig* config) +{ + m_funcs.m_subscribe_init_config(config); +} + +CC_MqttsnErrorCode UnitTestCommonBase::apiSubscribeConfig(CC_MqttsnSubscribeHandle subscribe, const CC_MqttsnSubscribeConfig* config) +{ + return m_funcs.m_subscribe_config(subscribe, config); } void UnitTestCommonBase::unitTestMessageReportCb(void* data, const CC_MqttsnMessageInfo* msgInfo) @@ -656,8 +706,10 @@ void UnitTestCommonBase::unitTestErrorLogCb([[maybe_unused]] void* data, const c void UnitTestCommonBase::unitTestSearchCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info) { + test_assert((status != CC_MqttsnAsyncOpStatus_Complete) || (info != nullptr)); + auto* thisPtr = asThis(data); - thisPtr->m_data.m_searchCompleteReports.emplace_back(status, info); + thisPtr->m_data.m_searchCompleteReports.push_back(std::make_unique(status, info)); if (thisPtr->m_data.m_searchCompleteCallbacks.empty()) { return; @@ -666,7 +718,7 @@ void UnitTestCommonBase::unitTestSearchCompleteCb(void* data, CC_MqttsnAsyncOpSt auto& func = thisPtr->m_data.m_searchCompleteCallbacks.front(); test_assert(func); - bool popReport = func(thisPtr->m_data.m_searchCompleteReports.back()); + bool popReport = func(*thisPtr->m_data.m_searchCompleteReports.back()); thisPtr->m_data.m_searchCompleteCallbacks.pop_front(); if (popReport) { @@ -676,12 +728,20 @@ void UnitTestCommonBase::unitTestSearchCompleteCb(void* data, CC_MqttsnAsyncOpSt void UnitTestCommonBase::unitTestConnectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info) { + test_assert((status != CC_MqttsnAsyncOpStatus_Complete) || (info != nullptr)); auto* thisPtr = asThis(data); - thisPtr->m_data.m_connectCompleteReports.emplace_back(status, info); + thisPtr->m_data.m_connectCompleteReports.push_back(std::make_unique(status, info)); } void UnitTestCommonBase::unitTestDisconnectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status) { auto* thisPtr = asThis(data); - thisPtr->m_data.m_disconnectCompleteReports.emplace_back(status); + thisPtr->m_data.m_disconnectCompleteReports.push_back(std::make_unique(status)); +} + +void UnitTestCommonBase::unitTestSubscribeCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info) +{ + test_assert((status != CC_MqttsnAsyncOpStatus_Complete) || (info != nullptr)); + auto* thisPtr = asThis(data); + thisPtr->m_data.m_subscribeCompleteReports.push_back(std::make_unique(status, info)); } \ No newline at end of file diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index e9cf2f1c..7d15d2ba 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -65,6 +65,16 @@ class UnitTestCommonBase CC_MqttsnErrorCode (*m_disconnect_send)(CC_MqttsnDisconnectHandle, CC_MqttsnDisconnectCompleteCb, void*) = nullptr; CC_MqttsnErrorCode (*m_disconnect_cancel)(CC_MqttsnDisconnectHandle) = nullptr; CC_MqttsnErrorCode (*m_disconnect)(CC_MqttsnClientHandle, CC_MqttsnDisconnectCompleteCb, void*) = nullptr; + CC_MqttsnSubscribeHandle (*m_subscribe_prepare)(CC_MqttsnClientHandle, CC_MqttsnErrorCode*) = nullptr; + CC_MqttsnErrorCode (*m_subscribe_set_retry_period)(CC_MqttsnSubscribeHandle, unsigned) = nullptr; + unsigned (*m_subscribe_get_retry_period)(CC_MqttsnSubscribeHandle) = nullptr; + CC_MqttsnErrorCode (*m_subscribe_set_retry_count)(CC_MqttsnSubscribeHandle, unsigned) = nullptr; + unsigned (*m_subscribe_get_retry_count)(CC_MqttsnSubscribeHandle) = nullptr; + void (*m_subscribe_init_config)(CC_MqttsnSubscribeConfig*) = nullptr; + CC_MqttsnErrorCode (*m_subscribe_config)(CC_MqttsnSubscribeHandle, const CC_MqttsnSubscribeConfig*) = nullptr; + CC_MqttsnErrorCode (*m_subscribe_send)(CC_MqttsnSubscribeHandle, CC_MqttsnSubscribeCompleteCb, void*) = nullptr; + CC_MqttsnErrorCode (*m_subscribe_cancel)(CC_MqttsnSubscribeHandle) = nullptr; + CC_MqttsnErrorCode (*m_subscribe)(CC_MqttsnClientHandle, const CC_MqttsnSubscribeConfig*, CC_MqttsnSubscribeCompleteCb, void* cbData) = nullptr; void (*m_set_next_tick_program_callback)(CC_MqttsnClientHandle, CC_MqttsnNextTickProgramCb, void*) = nullptr; void (*m_set_cancel_next_tick_wait_callback)(CC_MqttsnClientHandle, CC_MqttsnCancelNextTickWaitCb, void*) = nullptr; @@ -134,7 +144,8 @@ class UnitTestCommonBase UnitTestGwInfoReport(CC_MqttsnGwStatus status, const CC_MqttsnGatewayInfo* info); }; - using UnitTestGwInfoReportsList = std::list; + using UnitTestGwInfoReportPtr = std::unique_ptr; + using UnitTestGwInfoReportsList = std::list; struct UnitTestGwDisconnectReport { @@ -143,7 +154,8 @@ class UnitTestCommonBase UnitTestGwDisconnectReport(CC_MqttsnGatewayDisconnectReason reason) : m_reason(reason) {} }; - using UnitTestGwDisconnectReportsList = std::list; + using UnitTestGwDisconnectReportPtr = std::unique_ptr; + using UnitTestGwDisconnectReportsList = std::list; struct UnitTestSearchCompleteReport { @@ -153,7 +165,9 @@ class UnitTestCommonBase UnitTestSearchCompleteReport(CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info); void assignInfo(CC_MqttsnGatewayInfo& info) const; }; - using UnitTestSearchCompleteReportsList = std::list; + + using UnitTestSearchCompleteReportPtr = std::unique_ptr; + using UnitTestSearchCompleteReportsList = std::list; using UnitTestSearchCompleteCb = std::function; using UnitTestSearchCompleteCbList = std::list; @@ -176,7 +190,8 @@ class UnitTestCommonBase UnitTestConnectCompleteReport(CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info); }; - using UnitTestConnectCompleteReportList = std::list; + using UnitTestConnectCompleteReportPtr = std::unique_ptr; + using UnitTestConnectCompleteReportList = std::list; struct UnitTestDisconnectCompleteReport { @@ -185,7 +200,32 @@ class UnitTestCommonBase UnitTestDisconnectCompleteReport(CC_MqttsnAsyncOpStatus status) : m_status(status) {}; }; - using UnitTestDisconnectCompleteReportList = std::list; + using UnitTestDisconnectCompleteReportPtr = std::unique_ptr; + using UnitTestDisconnectCompleteReportList = std::list; + + struct UnitTestSubscribeInfo + { + CC_MqttsnReturnCode m_returnCode = CC_MqttsnReturnCode_ValuesLimit; + CC_MqttsnTopicId m_topicId; + CC_MqttsnQoS m_qos; + UnitTestSubscribeInfo() = default; + UnitTestSubscribeInfo(const UnitTestSubscribeInfo&) = default; + UnitTestSubscribeInfo& operator=(const UnitTestSubscribeInfo&) = default; + UnitTestSubscribeInfo& operator=(const CC_MqttsnSubscribeInfo& info); + }; + + struct UnitTestSubscribeCompleteReport + { + CC_MqttsnAsyncOpStatus m_status = CC_MqttsnAsyncOpStatus_ValuesLimit; + UnitTestSubscribeInfo m_info; + + UnitTestSubscribeCompleteReport(CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info); + UnitTestSubscribeCompleteReport(UnitTestSubscribeCompleteReport&&) = default; + UnitTestSubscribeCompleteReport& operator=(const UnitTestSubscribeCompleteReport&) = default; + }; + + using UnitTestSubscribeCompleteReportPtr = std::unique_ptr; + using UnitTestSubscribeCompleteReportList = std::list; using UnitTestClientPtr = std::unique_ptr; @@ -207,35 +247,35 @@ class UnitTestCommonBase UniTestsMsgPtr unitTestPopOutputMessage(bool mustExist = true); bool unitTestHasGwInfoReport() const; - const UnitTestGwInfoReport* unitTestGetGwInfoReport(bool mustExist = true) const; - void unitTestPopGwInfoReport(); + UnitTestGwInfoReportPtr unitTestGetGwInfoReport(bool mustExist = true); bool unitTestHasGwDisconnectReport() const; - const UnitTestGwDisconnectReport* unitTestGetGwDisconnectReport(bool mustExist = true) const; - void unitTestPopGwDisconnectReport(); + UnitTestGwDisconnectReportPtr unitTestGetGwDisconnectReport(bool mustExist = true); bool unitTestHasSearchCompleteReport() const; - const UnitTestSearchCompleteReport* unitTestSearchCompleteReport(bool mustExist = true) const; - void unitTestPopSearchCompleteReport(); + UnitTestSearchCompleteReportPtr unitTestSearchCompleteReport(bool mustExist = true); CC_MqttsnErrorCode unitTestSearchSend(CC_MqttsnSearchHandle search, UnitTestSearchCompleteCb&& cb = UnitTestSearchCompleteCb()); void unitTestSearch(CC_MqttsnClient* client, UnitTestSearchCompleteCb&& cb = UnitTestSearchCompleteCb()); void unitTestSearchUpdateAddr(CC_MqttsnClient* client, const UnitTestData& addr); bool unitTestHasConnectCompleteReport() const; - const UnitTestConnectCompleteReport* unitTestConnectCompleteReport(bool mustExist = true) const; - void unitTestPopConnectCompleteReport(); + UnitTestConnectCompleteReportPtr unitTestConnectCompleteReport(bool mustExist = true); CC_MqttsnErrorCode unitTestConnectSend(CC_MqttsnConnectHandle connect); void unitTestDoConnect(CC_MqttsnClient* client, const CC_MqttsnConnectConfig* config, const CC_MqttsnWillConfig* willConfig); void unitTestDoConnectBasic(CC_MqttsnClient* client, const std::string& clientId = std::string(), bool cleanSession = true); bool unitTestHasDisconnectCompleteReport() const; - const UnitTestDisconnectCompleteReport* unitTestDisconnectCompleteReport(bool mustExist = true) const; - void unitTestPopDisconnectCompleteReport(); + UnitTestDisconnectCompleteReportPtr unitTestDisconnectCompleteReport(bool mustExist = true); CC_MqttsnErrorCode unitTestDisconnectSend(CC_MqttsnDisconnectHandle disconnect); + bool unitTestHasSubscribeCompleteReport() const; + UnitTestSubscribeCompleteReportPtr unitTestSubscribeCompleteReport(bool mustExist = true); + + CC_MqttsnErrorCode unitTestSubscribeSend(CC_MqttsnSubscribeHandle subscribe); + void apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen); CC_MqttsnErrorCode apiSetDefaultRetryPeriod(CC_MqttsnClient* client, unsigned value); CC_MqttsnErrorCode apiSetDefaultRetryCount(CC_MqttsnClient* client, unsigned value); @@ -255,6 +295,12 @@ class UnitTestCommonBase CC_MqttsnDisconnectHandle apiDisconnectPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec = nullptr); CC_MqttsnErrorCode apiDisconnectSetRetryCount(CC_MqttsnDisconnectHandle disconnect, unsigned count); + CC_MqttsnSubscribeHandle apiSubscribePrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec = nullptr); + CC_MqttsnErrorCode apiSubscribeSetRetryCount(CC_MqttsnSubscribeHandle subscribe, unsigned count); + void apiSubscribeInitConfig(CC_MqttsnSubscribeConfig* config); + CC_MqttsnErrorCode apiSubscribeConfig(CC_MqttsnSubscribeHandle subscribe, const CC_MqttsnSubscribeConfig* config); + + protected: explicit UnitTestCommonBase(const LibFuncs& funcs); @@ -269,6 +315,7 @@ class UnitTestCommonBase UnitTestSearchCompleteCbList m_searchCompleteCallbacks; UnitTestConnectCompleteReportList m_connectCompleteReports; UnitTestDisconnectCompleteReportList m_disconnectCompleteReports; + UnitTestSubscribeCompleteReportList m_subscribeCompleteReports; }; static void unitTestTickProgramCb(void* data, unsigned duration); @@ -282,6 +329,7 @@ class UnitTestCommonBase static void unitTestSearchCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info); static void unitTestConnectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info); static void unitTestDisconnectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status); + static void unitTestSubscribeCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info); LibFuncs m_funcs; ClientData m_data; diff --git a/client/lib/test/UnitTestConnect.th b/client/lib/test/UnitTestConnect.th index ec7a5e41..ba7ae660 100644 --- a/client/lib/test/UnitTestConnect.th +++ b/client/lib/test/UnitTestConnect.th @@ -70,10 +70,9 @@ void UnitTestConnect::test1() } TS_ASSERT(unitTestHasConnectCompleteReport()); - auto* connectReport = unitTestConnectCompleteReport(); + auto connectReport = unitTestConnectCompleteReport(); TS_ASSERT_EQUALS(connectReport->m_status, CC_MqttsnAsyncOpStatus_Complete) TS_ASSERT_EQUALS(connectReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted) - unitTestPopConnectCompleteReport(); TS_ASSERT(unitTestHasTickReq()); // For keep alive TS_ASSERT(apiIsConnected(client)); @@ -176,10 +175,9 @@ void UnitTestConnect::test2() } TS_ASSERT(unitTestHasConnectCompleteReport()); - auto* connectReport = unitTestConnectCompleteReport(); + auto connectReport = unitTestConnectCompleteReport(); TS_ASSERT_EQUALS(connectReport->m_status, CC_MqttsnAsyncOpStatus_Complete) TS_ASSERT_EQUALS(connectReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted) - unitTestPopConnectCompleteReport(); TS_ASSERT(unitTestHasTickReq()); // For keep alive TS_ASSERT(apiIsConnected(client)); @@ -242,9 +240,8 @@ void UnitTestConnect::test3() unitTestTick(client); TS_ASSERT(unitTestHasConnectCompleteReport()); - auto* connectReport = unitTestConnectCompleteReport(); + auto connectReport = unitTestConnectCompleteReport(); TS_ASSERT_EQUALS(connectReport->m_status, CC_MqttsnAsyncOpStatus_Timeout) - unitTestPopConnectCompleteReport(); TS_ASSERT(!unitTestHasTickReq()); TS_ASSERT(!apiIsConnected(client)); @@ -341,9 +338,8 @@ void UnitTestConnect::test4() unitTestTick(client); TS_ASSERT(unitTestHasConnectCompleteReport()); - auto* connectReport = unitTestConnectCompleteReport(); + auto connectReport = unitTestConnectCompleteReport(); TS_ASSERT_EQUALS(connectReport->m_status, CC_MqttsnAsyncOpStatus_Timeout) - unitTestPopConnectCompleteReport(); TS_ASSERT(!unitTestHasTickReq()); TS_ASSERT(!apiIsConnected(client)); @@ -457,9 +453,8 @@ void UnitTestConnect::test5() unitTestTick(client); TS_ASSERT(unitTestHasConnectCompleteReport()); - auto* connectReport = unitTestConnectCompleteReport(); + auto connectReport = unitTestConnectCompleteReport(); TS_ASSERT_EQUALS(connectReport->m_status, CC_MqttsnAsyncOpStatus_Timeout) - unitTestPopConnectCompleteReport(); TS_ASSERT(!unitTestHasTickReq()); TS_ASSERT(!apiIsConnected(client)); @@ -506,9 +501,8 @@ void UnitTestConnect::test6() unitTestTick(client); TS_ASSERT(unitTestHasConnectCompleteReport()); - auto* connectReport = unitTestConnectCompleteReport(); + auto connectReport = unitTestConnectCompleteReport(); TS_ASSERT_EQUALS(connectReport->m_status, CC_MqttsnAsyncOpStatus_Timeout) - unitTestPopConnectCompleteReport(); TS_ASSERT(!unitTestHasTickReq()); TS_ASSERT(!apiIsConnected(client)); diff --git a/client/lib/test/UnitTestDefaultBase.cpp b/client/lib/test/UnitTestDefaultBase.cpp index 403be6a1..e0584cda 100644 --- a/client/lib/test/UnitTestDefaultBase.cpp +++ b/client/lib/test/UnitTestDefaultBase.cpp @@ -55,7 +55,17 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_disconnect_get_retry_count = &cc_mqttsn_client_disconnect_get_retry_count; funcs.m_disconnect_send = &cc_mqttsn_client_disconnect_send; funcs.m_disconnect_cancel = &cc_mqttsn_client_disconnect_cancel; - funcs.m_disconnect = &cc_mqttsn_client_disconnect; + funcs.m_disconnect = &cc_mqttsn_client_disconnect; + funcs.m_subscribe_prepare = &cc_mqttsn_client_subscribe_prepare; + funcs.m_subscribe_set_retry_period = &cc_mqttsn_client_subscribe_set_retry_period; + funcs.m_subscribe_get_retry_period = &cc_mqttsn_client_subscribe_get_retry_period; + funcs.m_subscribe_set_retry_count = &cc_mqttsn_client_subscribe_set_retry_count; + funcs.m_subscribe_get_retry_count = &cc_mqttsn_client_subscribe_get_retry_count; + funcs.m_subscribe_init_config = &cc_mqttsn_client_subscribe_init_config; + funcs.m_subscribe_config = &cc_mqttsn_client_subscribe_config; + funcs.m_subscribe_send = &cc_mqttsn_client_subscribe_send; + funcs.m_subscribe_cancel = &cc_mqttsn_client_subscribe_cancel; + funcs.m_subscribe = &cc_mqttsn_client_subscribe; funcs.m_set_next_tick_program_callback = &cc_mqttsn_client_set_next_tick_program_callback; funcs.m_set_cancel_next_tick_wait_callback = &cc_mqttsn_client_set_cancel_next_tick_wait_callback; diff --git a/client/lib/test/UnitTestDisconnect.th b/client/lib/test/UnitTestDisconnect.th index 565e6029..57ca0a18 100644 --- a/client/lib/test/UnitTestDisconnect.th +++ b/client/lib/test/UnitTestDisconnect.th @@ -62,9 +62,8 @@ void UnitTestDisconnect::test1() } TS_ASSERT(unitTestHasDisconnectCompleteReport()); - auto* disconnectReport = unitTestDisconnectCompleteReport(); + auto disconnectReport = unitTestDisconnectCompleteReport(); TS_ASSERT_EQUALS(disconnectReport->m_status, CC_MqttsnAsyncOpStatus_Complete) - unitTestPopDisconnectCompleteReport(); TS_ASSERT(!unitTestHasTickReq()); // No more keep alive TS_ASSERT(!apiIsConnected(client)); @@ -117,9 +116,8 @@ void UnitTestDisconnect::test2() unitTestTick(client); // timeout TS_ASSERT(unitTestHasDisconnectCompleteReport()); - auto* disconnectReport = unitTestDisconnectCompleteReport(); + auto disconnectReport = unitTestDisconnectCompleteReport(); TS_ASSERT_EQUALS(disconnectReport->m_status, CC_MqttsnAsyncOpStatus_Timeout) - unitTestPopDisconnectCompleteReport(); TS_ASSERT(unitTestHasTickReq()); // Still hase keep alive TS_ASSERT(apiIsConnected(client)); // Still connected @@ -177,9 +175,8 @@ void UnitTestDisconnect::test3() } TS_ASSERT(unitTestHasDisconnectCompleteReport()); - auto* disconnectReport = unitTestDisconnectCompleteReport(); + auto disconnectReport = unitTestDisconnectCompleteReport(); TS_ASSERT_EQUALS(disconnectReport->m_status, CC_MqttsnAsyncOpStatus_Complete) - unitTestPopDisconnectCompleteReport(); TS_ASSERT(!unitTestHasTickReq()); // No more keep alive TS_ASSERT(!apiIsConnected(client)); @@ -204,9 +201,8 @@ void UnitTestDisconnect::test4() } TS_ASSERT(unitTestHasGwDisconnectReport()); - auto* disconnectReport = unitTestGetGwDisconnectReport(); + auto disconnectReport = unitTestGetGwDisconnectReport(); TS_ASSERT_EQUALS(disconnectReport->m_reason, CC_MqttsnGatewayDisconnectReason_DisconnectMsg); - unitTestPopGwDisconnectReport(); TS_ASSERT(!unitTestHasTickReq()); // No more keep alive TS_ASSERT(!apiIsConnected(client)); @@ -251,9 +247,8 @@ void UnitTestDisconnect::test5() unitTestTick(client); TS_ASSERT(unitTestHasGwDisconnectReport()); - auto* disconnectReport = unitTestGetGwDisconnectReport(); + auto disconnectReport = unitTestGetGwDisconnectReport(); TS_ASSERT_EQUALS(disconnectReport->m_reason, CC_MqttsnGatewayDisconnectReason_NoGatewayResponse); - unitTestPopGwDisconnectReport(); TS_ASSERT(!unitTestHasTickReq()); // No more keep alive TS_ASSERT(!apiIsConnected(client)); diff --git a/client/lib/test/UnitTestGwDiscover.th b/client/lib/test/UnitTestGwDiscover.th index 16947177..b21c3d95 100644 --- a/client/lib/test/UnitTestGwDiscover.th +++ b/client/lib/test/UnitTestGwDiscover.th @@ -43,11 +43,10 @@ void UnitTestGwDiscover::test1() unitTestClientInputMessage(client, advertiseMsg); { - auto* gwInfoReport = unitTestGetGwInfoReport(); + auto gwInfoReport = unitTestGetGwInfoReport(); TS_ASSERT_EQUALS(gwInfoReport->m_status, CC_MqttsnGwStatus_AddedByGateway); TS_ASSERT_EQUALS(gwInfoReport->m_info.m_gwId, GwId); TS_ASSERT(gwInfoReport->m_info.m_addr.empty()); - unitTestPopGwInfoReport(); TS_ASSERT(!unitTestHasGwInfoReport()); } @@ -56,21 +55,19 @@ void UnitTestGwDiscover::test1() unitTestTick(client); { - auto* gwInfoReport = unitTestGetGwInfoReport(); + auto gwInfoReport = unitTestGetGwInfoReport(); TS_ASSERT_EQUALS(gwInfoReport->m_status, CC_MqttsnGwStatus_Tentative); TS_ASSERT_EQUALS(gwInfoReport->m_info.m_gwId, GwId); TS_ASSERT(gwInfoReport->m_info.m_addr.empty()); - unitTestPopGwInfoReport(); TS_ASSERT(!unitTestHasGwInfoReport()); } unitTestTick(client); { - auto* gwInfoReport = unitTestGetGwInfoReport(); + auto gwInfoReport = unitTestGetGwInfoReport(); TS_ASSERT_EQUALS(gwInfoReport->m_status, CC_MqttsnGwStatus_Removed); TS_ASSERT_EQUALS(gwInfoReport->m_info.m_gwId, GwId); TS_ASSERT(gwInfoReport->m_info.m_addr.empty()); - unitTestPopGwInfoReport(); TS_ASSERT(!unitTestHasGwInfoReport()); } } @@ -101,12 +98,12 @@ void UnitTestGwDiscover::test2() unitTestClientInputMessage(client, gwinfoMsg); TS_ASSERT(unitTestHasSearchCompleteReport()); - auto* searchCompleteReport = unitTestSearchCompleteReport(); + auto searchCompleteReport = unitTestSearchCompleteReport(); TS_ASSERT_EQUALS(searchCompleteReport->m_status, CC_MqttsnAsyncOpStatus_Complete); TS_ASSERT_EQUALS(searchCompleteReport->m_info.m_gwId, GwId); TS_ASSERT(searchCompleteReport->m_info.m_addr.empty()); - auto* gwInfoReport = unitTestGetGwInfoReport(); + auto gwInfoReport = unitTestGetGwInfoReport(); TS_ASSERT_EQUALS(gwInfoReport->m_status, CC_MqttsnGwStatus_AddedByGateway); TS_ASSERT_EQUALS(gwInfoReport->m_info.m_gwId, GwId); TS_ASSERT(gwInfoReport->m_info.m_addr.empty()); @@ -141,12 +138,12 @@ void UnitTestGwDiscover::test3() unitTestClientInputMessage(client, gwinfoMsg); TS_ASSERT(unitTestHasSearchCompleteReport()); - auto* searchCompleteReport = unitTestSearchCompleteReport(); + auto searchCompleteReport = unitTestSearchCompleteReport(); TS_ASSERT_EQUALS(searchCompleteReport->m_status, CC_MqttsnAsyncOpStatus_Complete); TS_ASSERT_EQUALS(searchCompleteReport->m_info.m_gwId, GwId); TS_ASSERT_EQUALS(searchCompleteReport->m_info.m_addr, Addr); - auto* gwInfoReport = unitTestGetGwInfoReport(); + auto gwInfoReport = unitTestGetGwInfoReport(); TS_ASSERT_EQUALS(gwInfoReport->m_status, CC_MqttsnGwStatus_AddedByClient); TS_ASSERT_EQUALS(gwInfoReport->m_info.m_gwId, GwId); TS_ASSERT_EQUALS(gwInfoReport->m_info.m_addr, Addr); @@ -184,12 +181,12 @@ void UnitTestGwDiscover::test4() unitTestClientInputMessage(client, advertiseMsg); TS_ASSERT(unitTestHasSearchCompleteReport()); - auto* searchCompleteReport = unitTestSearchCompleteReport(); + auto searchCompleteReport = unitTestSearchCompleteReport(); TS_ASSERT_EQUALS(searchCompleteReport->m_status, CC_MqttsnAsyncOpStatus_Complete); TS_ASSERT_EQUALS(searchCompleteReport->m_info.m_gwId, GwId); TS_ASSERT(searchCompleteReport->m_info.m_addr.empty()); - auto* gwInfoReport = unitTestGetGwInfoReport(); + auto gwInfoReport = unitTestGetGwInfoReport(); TS_ASSERT_EQUALS(gwInfoReport->m_status, CC_MqttsnGwStatus_AddedByGateway); TS_ASSERT_EQUALS(gwInfoReport->m_info.m_gwId, GwId); TS_ASSERT_EQUALS(gwInfoReport->m_info.m_addr, Addr); @@ -248,9 +245,8 @@ void UnitTestGwDiscover::test5() unitTestTick(client); TS_ASSERT(unitTestHasSearchCompleteReport()); - auto* report = unitTestSearchCompleteReport(); + auto report = unitTestSearchCompleteReport(); TS_ASSERT_EQUALS(report->m_status, CC_MqttsnAsyncOpStatus_Timeout); - unitTestPopSearchCompleteReport(); TS_ASSERT(!unitTestHasTickReq()); } @@ -313,12 +309,12 @@ void UnitTestGwDiscover::test6() unitTestClientInputMessage(client, gwinfoMsg); TS_ASSERT(unitTestHasSearchCompleteReport()); - auto* searchCompleteReport = unitTestSearchCompleteReport(); + auto searchCompleteReport = unitTestSearchCompleteReport(); TS_ASSERT_EQUALS(searchCompleteReport->m_status, CC_MqttsnAsyncOpStatus_Complete); TS_ASSERT_EQUALS(searchCompleteReport->m_info.m_gwId, GwId); TS_ASSERT(searchCompleteReport->m_info.m_addr.empty()); - auto* gwInfoReport = unitTestGetGwInfoReport(); + auto gwInfoReport = unitTestGetGwInfoReport(); TS_ASSERT_EQUALS(gwInfoReport->m_status, CC_MqttsnGwStatus_AddedByGateway); TS_ASSERT_EQUALS(gwInfoReport->m_info.m_gwId, GwId); TS_ASSERT(gwInfoReport->m_info.m_addr.empty()); diff --git a/client/lib/test/UnitTestSubscribe.th b/client/lib/test/UnitTestSubscribe.th new file mode 100644 index 00000000..603d80a8 --- /dev/null +++ b/client/lib/test/UnitTestSubscribe.th @@ -0,0 +1,538 @@ +#include "UnitTestDefaultBase.h" +#include "UnitTestProtocolDefs.h" + +#include "comms/units.h" + +#include + +class UnitTestSubscribe : public CxxTest::TestSuite, public UnitTestDefaultBase +{ +public: + void test1(); + void test2(); + void test3(); + void test4(); + void test5(); + void test6(); + +private: + virtual void setUp() override + { + unitTestSetUp(); + } + + virtual void tearDown() override + { + unitTestTearDown(); + } + + using TopicIdType = UnitTestSubscribeMsg::Field_flags::Field_topicIdType::ValueType; +}; + +void UnitTestSubscribe::test1() +{ + // Testing basic subscribe + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + auto subscribe = apiSubscribePrepare(client); + TS_ASSERT_DIFFERS(subscribe, nullptr); + + const std::string Topic("#"); + + CC_MqttsnSubscribeConfig config; + apiSubscribeInitConfig(&config); + TS_ASSERT_EQUALS(config.m_qos, CC_MqttsnQoS_ExactlyOnceDelivery); + + config.m_topic = Topic.c_str(); + + auto ec = apiSubscribeConfig(subscribe, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestSubscribeSend(subscribe); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + auto subMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* subscribeMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(subscribeMsg, nullptr); + TS_ASSERT_EQUALS(subscribeMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(subscribeMsg->field_flags().field_qos().value()), config.m_qos); + TS_ASSERT(subscribeMsg->field_topicId().isMissing()); + TS_ASSERT(subscribeMsg->field_topicName().doesExist()); + TS_ASSERT_EQUALS(subscribeMsg->field_topicName().field().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + subMsgId = subscribeMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(subMsgId, 0U); + } + + TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + const auto AckQos = CC_MqttsnQoS_AtLeastOnceDelivery; + { + UnitTestSubackMsg subackMsg; + subackMsg.field_flags().field_qos().setValue(AckQos); + subackMsg.field_msgId().setValue(subMsgId); + subackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, subackMsg); + } + + TS_ASSERT(unitTestHasSubscribeCompleteReport()); + auto subscribeReport = unitTestSubscribeCompleteReport(); + TS_ASSERT_EQUALS(subscribeReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(subscribeReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted); + TS_ASSERT_EQUALS(subscribeReport->m_info.m_topicId, 0U); + TS_ASSERT_EQUALS(subscribeReport->m_info.m_qos, AckQos); + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestSubscribe::test2() +{ + // Testing basic short topic name subscribe + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + auto subscribe = apiSubscribePrepare(client); + TS_ASSERT_DIFFERS(subscribe, nullptr); + + const std::string Topic("ab"); + + CC_MqttsnSubscribeConfig config; + apiSubscribeInitConfig(&config); + TS_ASSERT_EQUALS(config.m_qos, CC_MqttsnQoS_ExactlyOnceDelivery); + + config.m_topic = Topic.c_str(); + + auto ec = apiSubscribeConfig(subscribe, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestSubscribeSend(subscribe); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + auto subMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* subscribeMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(subscribeMsg, nullptr); + TS_ASSERT_EQUALS(subscribeMsg->field_flags().field_topicIdType().value(), TopicIdType::ShortTopicName); + TS_ASSERT_EQUALS(static_cast(subscribeMsg->field_flags().field_qos().value()), config.m_qos); + TS_ASSERT(subscribeMsg->field_topicId().doesExist()); + TS_ASSERT(subscribeMsg->field_topicName().isMissing()); + + auto topicId = subscribeMsg->field_topicId().field().value(); + TS_ASSERT_EQUALS((topicId >> 8U) & 0xff, static_cast(Topic[0])); + TS_ASSERT_EQUALS(topicId & 0xff, static_cast(Topic[1])); + TS_ASSERT(!unitTestHasOutputData()); + subMsgId = subscribeMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(subMsgId, 0U); + } + + TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + const unsigned AckTopicId = 16; + const auto AckQos = CC_MqttsnQoS_AtMostOnceDelivery; + { + UnitTestSubackMsg subackMsg; + subackMsg.field_flags().field_qos().setValue(AckQos); + subackMsg.field_topicId().setValue(AckTopicId); + subackMsg.field_msgId().setValue(subMsgId); + subackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, subackMsg); + } + + TS_ASSERT(unitTestHasSubscribeCompleteReport()); + auto subscribeReport = unitTestSubscribeCompleteReport(); + TS_ASSERT_EQUALS(subscribeReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(subscribeReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted); + TS_ASSERT_EQUALS(subscribeReport->m_info.m_topicId, AckTopicId); + TS_ASSERT_EQUALS(subscribeReport->m_info.m_qos, AckQos); + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestSubscribe::test3() +{ + // Testing pre-defined topic ID subscribe + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + auto subscribe = apiSubscribePrepare(client); + TS_ASSERT_DIFFERS(subscribe, nullptr); + + const CC_MqttsnTopicId TopicId = 100; + + CC_MqttsnSubscribeConfig config; + apiSubscribeInitConfig(&config); + TS_ASSERT_EQUALS(config.m_qos, CC_MqttsnQoS_ExactlyOnceDelivery); + + config.m_topicId = TopicId; + + auto ec = apiSubscribeConfig(subscribe, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestSubscribeSend(subscribe); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + auto subMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* subscribeMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(subscribeMsg, nullptr); + TS_ASSERT_EQUALS(subscribeMsg->field_flags().field_topicIdType().value(), TopicIdType::PredefinedTopicId); + TS_ASSERT_EQUALS(static_cast(subscribeMsg->field_flags().field_qos().value()), config.m_qos); + TS_ASSERT(subscribeMsg->field_topicId().doesExist()); + TS_ASSERT(subscribeMsg->field_topicName().isMissing()); + TS_ASSERT_EQUALS(subscribeMsg->field_topicId().field().value(), TopicId); + TS_ASSERT(!unitTestHasOutputData()); + subMsgId = subscribeMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(subMsgId, 0U); + } + + TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + const auto AckQos = CC_MqttsnQoS_ExactlyOnceDelivery; + { + UnitTestSubackMsg subackMsg; + subackMsg.field_flags().field_qos().setValue(AckQos); + subackMsg.field_topicId().setValue(TopicId); + subackMsg.field_msgId().setValue(subMsgId); + subackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, subackMsg); + } + + TS_ASSERT(unitTestHasSubscribeCompleteReport()); + auto subscribeReport = unitTestSubscribeCompleteReport(); + TS_ASSERT_EQUALS(subscribeReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(subscribeReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted); + TS_ASSERT_EQUALS(subscribeReport->m_info.m_topicId, TopicId); + TS_ASSERT_EQUALS(subscribeReport->m_info.m_qos, AckQos); + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestSubscribe::test4() +{ + // Testing subscribe with lost packet + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + auto subscribe = apiSubscribePrepare(client); + TS_ASSERT_DIFFERS(subscribe, nullptr); + + const std::string Topic("#"); + + CC_MqttsnSubscribeConfig config; + apiSubscribeInitConfig(&config); + TS_ASSERT_EQUALS(config.m_qos, CC_MqttsnQoS_ExactlyOnceDelivery); + + config.m_topic = Topic.c_str(); + + auto ec = apiSubscribeConfig(subscribe, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestSubscribeSend(subscribe); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + auto subMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* subscribeMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(subscribeMsg, nullptr); + TS_ASSERT_EQUALS(subscribeMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(subscribeMsg->field_flags().field_qos().value()), config.m_qos); + TS_ASSERT(subscribeMsg->field_topicId().isMissing()); + TS_ASSERT(subscribeMsg->field_topicName().doesExist()); + TS_ASSERT_EQUALS(subscribeMsg->field_topicName().field().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + subMsgId = subscribeMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(subMsgId, 0U); + } + + TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* subscribeMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(subscribeMsg, nullptr); + TS_ASSERT_EQUALS(subscribeMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(subscribeMsg->field_flags().field_qos().value()), config.m_qos); + TS_ASSERT(subscribeMsg->field_topicId().isMissing()); + TS_ASSERT(subscribeMsg->field_topicName().doesExist()); + TS_ASSERT_EQUALS(subscribeMsg->field_topicName().field().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + TS_ASSERT_EQUALS(subscribeMsg->field_msgId().value(), subMsgId); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + const auto AckQos = CC_MqttsnQoS_AtLeastOnceDelivery; + { + UnitTestSubackMsg subackMsg; + subackMsg.field_flags().field_qos().setValue(AckQos); + subackMsg.field_msgId().setValue(subMsgId); + subackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, subackMsg); + } + + TS_ASSERT(unitTestHasSubscribeCompleteReport()); + auto subscribeReport = unitTestSubscribeCompleteReport(); + TS_ASSERT_EQUALS(subscribeReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(subscribeReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted); + TS_ASSERT_EQUALS(subscribeReport->m_info.m_topicId, 0U); + TS_ASSERT_EQUALS(subscribeReport->m_info.m_qos, AckQos); + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestSubscribe::test5() +{ + // Testing subscribe with timeout + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + auto ec = apiSetDefaultRetryCount(client, 1U); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + auto subscribe = apiSubscribePrepare(client); + TS_ASSERT_DIFFERS(subscribe, nullptr); + + const std::string Topic("#"); + + CC_MqttsnSubscribeConfig config; + apiSubscribeInitConfig(&config); + TS_ASSERT_EQUALS(config.m_qos, CC_MqttsnQoS_ExactlyOnceDelivery); + + config.m_topic = Topic.c_str(); + + ec = apiSubscribeConfig(subscribe, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestSubscribeSend(subscribe); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + auto subMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* subscribeMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(subscribeMsg, nullptr); + TS_ASSERT_EQUALS(subscribeMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(subscribeMsg->field_flags().field_qos().value()), config.m_qos); + TS_ASSERT(subscribeMsg->field_topicId().isMissing()); + TS_ASSERT(subscribeMsg->field_topicName().doesExist()); + TS_ASSERT_EQUALS(subscribeMsg->field_topicName().field().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + subMsgId = subscribeMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(subMsgId, 0U); + } + + TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* subscribeMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(subscribeMsg, nullptr); + TS_ASSERT_EQUALS(subscribeMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(subscribeMsg->field_flags().field_qos().value()), config.m_qos); + TS_ASSERT(subscribeMsg->field_topicId().isMissing()); + TS_ASSERT(subscribeMsg->field_topicName().doesExist()); + TS_ASSERT_EQUALS(subscribeMsg->field_topicName().field().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + TS_ASSERT_EQUALS(subscribeMsg->field_msgId().value(), subMsgId); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + + TS_ASSERT(unitTestHasSubscribeCompleteReport()); + auto subscribeReport = unitTestSubscribeCompleteReport(); + TS_ASSERT_EQUALS(subscribeReport->m_status, CC_MqttsnAsyncOpStatus_Timeout); + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestSubscribe::test6() +{ + // Testing multiple subscribes + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic1("a/+"); + const std::string Topic2("c/+"); + const std::string Topic3("e/+"); + + { + auto subscribe1 = apiSubscribePrepare(client); + TS_ASSERT_DIFFERS(subscribe1, nullptr); + + CC_MqttsnSubscribeConfig config1; + apiSubscribeInitConfig(&config1); + TS_ASSERT_EQUALS(config1.m_qos, CC_MqttsnQoS_ExactlyOnceDelivery); + + config1.m_topic = Topic1.c_str(); + config1.m_qos = CC_MqttsnQoS_AtLeastOnceDelivery; + + auto ec = apiSubscribeConfig(subscribe1, &config1); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestSubscribeSend(subscribe1); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + } + + auto subMsgId1 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* subscribeMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(subscribeMsg, nullptr); + TS_ASSERT_EQUALS(subscribeMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(subscribeMsg->field_flags().field_qos().value()), CC_MqttsnQoS_AtLeastOnceDelivery); + TS_ASSERT(subscribeMsg->field_topicId().isMissing()); + TS_ASSERT(subscribeMsg->field_topicName().doesExist()); + TS_ASSERT_EQUALS(subscribeMsg->field_topicName().field().value(), Topic1); + TS_ASSERT(!unitTestHasOutputData()); + subMsgId1 = subscribeMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(subMsgId1, 0U); + } + + TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 10); + + { + auto subscribe2 = apiSubscribePrepare(client); + TS_ASSERT_DIFFERS(subscribe2, nullptr); + + CC_MqttsnSubscribeConfig config2; + apiSubscribeInitConfig(&config2); + TS_ASSERT_EQUALS(config2.m_qos, CC_MqttsnQoS_ExactlyOnceDelivery); + + config2.m_topic = Topic2.c_str(); + config2.m_qos = CC_MqttsnQoS_AtLeastOnceDelivery; + + auto ec = apiSubscribeConfig(subscribe2, &config2); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestSubscribeSend(subscribe2); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + TS_ASSERT(!unitTestHasOutputData()); // Will wait until previous subscribe is complete + } + + TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 10); + + { + auto subscribe3 = apiSubscribePrepare(client); + TS_ASSERT_DIFFERS(subscribe3, nullptr); + + CC_MqttsnSubscribeConfig config3; + apiSubscribeInitConfig(&config3); + TS_ASSERT_EQUALS(config3.m_qos, CC_MqttsnQoS_ExactlyOnceDelivery); + + config3.m_topic = Topic3.c_str(); + config3.m_qos = CC_MqttsnQoS_AtMostOnceDelivery; + + auto ec = apiSubscribeConfig(subscribe3, &config3); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestSubscribeSend(subscribe3); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + TS_ASSERT(!unitTestHasOutputData()); // Will wait until previous subscribe is complete + } + + const auto AckQos = CC_MqttsnQoS_AtLeastOnceDelivery; + { + UnitTestSubackMsg subackMsg; + subackMsg.field_flags().field_qos().setValue(AckQos); + subackMsg.field_msgId().setValue(subMsgId1); + subackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, subackMsg); + } + + { + TS_ASSERT(unitTestHasSubscribeCompleteReport()); + auto subscribeReport = unitTestSubscribeCompleteReport(); + TS_ASSERT_EQUALS(subscribeReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(subscribeReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted); + TS_ASSERT_EQUALS(subscribeReport->m_info.m_topicId, 0U); + TS_ASSERT_EQUALS(subscribeReport->m_info.m_qos, AckQos); + } + + auto subMsgId2 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* subscribeMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(subscribeMsg, nullptr); + TS_ASSERT_EQUALS(subscribeMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(subscribeMsg->field_flags().field_qos().value()), CC_MqttsnQoS_AtLeastOnceDelivery); + TS_ASSERT(subscribeMsg->field_topicId().isMissing()); + TS_ASSERT(subscribeMsg->field_topicName().doesExist()); + TS_ASSERT_EQUALS(subscribeMsg->field_topicName().field().value(), Topic2); + TS_ASSERT(!unitTestHasOutputData()); + subMsgId2 = subscribeMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(subMsgId2, 0U); + } + + // TODO +} \ No newline at end of file diff --git a/cmake/Compile.cmake b/cmake/Compile.cmake index 26cae7c1..a762c353 100644 --- a/cmake/Compile.cmake +++ b/cmake/Compile.cmake @@ -8,7 +8,7 @@ macro (cc_mqttsn_compile) list (APPEND compile_opts WARN_AS_ERR) endif () - if (CC_MQTTSN_WITH_DEFAULT_SANITIZERS) + if (CC_MQTTSN_WITH_SANITIZERS) list (APPEND compile_opts DEFAULT_SANITIZERS) endif () From 1b726e14298365b46a63ff2a72f68d765c2ffa81 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 12 Jul 2024 08:13:11 +1000 Subject: [PATCH 060/106] More subscribe testing and work. --- client/lib/include/cc_mqttsn_client/common.h | 6 +- client/lib/src/op/SubscribeOp.cpp | 17 +++--- client/lib/test/UnitTestCommonBase.cpp | 7 ++- client/lib/test/UnitTestCommonBase.h | 5 +- client/lib/test/UnitTestSubscribe.th | 63 +++++++++++++++++++- 5 files changed, 83 insertions(+), 15 deletions(-) diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index 72a7b53d..011f5d5c 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -335,12 +335,16 @@ typedef void (*CC_MqttsnDisconnectCompleteCb)(void* data, CC_MqttsnAsyncOpStatus /// @brief Callback used to report completion of the subscribe operation. /// @param[in] data Pointer to user data object, passed as the last parameter to /// the request call. +/// @param[in] handle Handle returned by @b cc_mqttsn_client_subscribe_prepare() function. When the +/// callback is invoked the handle is already invalid and cannot be used in any relevant +/// function invocation, but it allows end application to identify the original "subscribe" operation +/// and use the same callback function in parallel requests. /// @param[in] status Status of the "subscribe" operation. /// @param[in] info Information about op completion. Not-NULL is reported if and onfly if /// the "status" is equal to @ref CC_MqttsnAsyncOpStatus_Complete. /// @post The data members of the reported response can NOT be accessed after the function returns. /// @ingroup subscribe -typedef void (*CC_MqttsnSubscribeCompleteCb)(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info); +typedef void (*CC_MqttsnSubscribeCompleteCb)(void* data, CC_MqttsnSubscribeHandle handle, CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info); #ifdef __cplusplus } diff --git a/client/lib/src/op/SubscribeOp.cpp b/client/lib/src/op/SubscribeOp.cpp index 3cc8e7fd..1d65f66f 100644 --- a/client/lib/src/op/SubscribeOp.cpp +++ b/client/lib/src/op/SubscribeOp.cpp @@ -29,6 +29,11 @@ inline SubscribeOp* asSubscribeOp(void* data) return reinterpret_cast(data); } +inline CC_MqttsnSubscribeHandle asHandle(SubscribeOp* op) +{ + return reinterpret_cast(op); +} + } // namespace @@ -136,12 +141,7 @@ CC_MqttsnErrorCode SubscribeOp::cancel() void SubscribeOp::handle(SubackMsg& msg) { - if (m_suspended) { - return; - } - - if (msg.field_msgId().value() != m_subscribeMsg.field_msgId().value()) { - errorLog("Unexpected SUBACK message received"); + if ((m_suspended) || (msg.field_msgId().value() != m_subscribeMsg.field_msgId().value())) { return; } @@ -184,7 +184,7 @@ void SubscribeOp::resume() auto ec = sendInternal(); if (ec != CC_MqttsnErrorCode_Success) { errorLog("Failed to send SUBSCRIBE, after prev SUBSCRIBE completion"); - completeOpInternal(CC_MqttsnAsyncOpStatus_InternalError); + completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); return; } } @@ -201,11 +201,12 @@ void SubscribeOp::terminateOpImpl(CC_MqttsnAsyncOpStatus status) void SubscribeOp::completeOpInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info) { + auto handle = asHandle(this); auto cb = m_cb; auto* cbData = m_cbData; opComplete(); // mustn't access data members after destruction if (cb != nullptr) { - cb(cbData, status, info); + cb(cbData, handle, status, info); } } diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index 9f4bdbcc..314c930a 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -158,7 +158,8 @@ UnitTestCommonBase::UnitTestSubscribeInfo& UnitTestCommonBase::UnitTestSubscribe return *this; } -UnitTestCommonBase::UnitTestSubscribeCompleteReport::UnitTestSubscribeCompleteReport(CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info) : +UnitTestCommonBase::UnitTestSubscribeCompleteReport::UnitTestSubscribeCompleteReport(CC_MqttsnSubscribeHandle handle, CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info) : + m_handle(handle), m_status(status) { if (info != nullptr) { @@ -739,9 +740,9 @@ void UnitTestCommonBase::unitTestDisconnectCompleteCb(void* data, CC_MqttsnAsync thisPtr->m_data.m_disconnectCompleteReports.push_back(std::make_unique(status)); } -void UnitTestCommonBase::unitTestSubscribeCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info) +void UnitTestCommonBase::unitTestSubscribeCompleteCb(void* data, CC_MqttsnSubscribeHandle handle, CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info) { test_assert((status != CC_MqttsnAsyncOpStatus_Complete) || (info != nullptr)); auto* thisPtr = asThis(data); - thisPtr->m_data.m_subscribeCompleteReports.push_back(std::make_unique(status, info)); + thisPtr->m_data.m_subscribeCompleteReports.push_back(std::make_unique(handle, status, info)); } \ No newline at end of file diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index 7d15d2ba..e80eebde 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -216,10 +216,11 @@ class UnitTestCommonBase struct UnitTestSubscribeCompleteReport { + CC_MqttsnSubscribeHandle m_handle = nullptr; CC_MqttsnAsyncOpStatus m_status = CC_MqttsnAsyncOpStatus_ValuesLimit; UnitTestSubscribeInfo m_info; - UnitTestSubscribeCompleteReport(CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info); + UnitTestSubscribeCompleteReport(CC_MqttsnSubscribeHandle handle, CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info); UnitTestSubscribeCompleteReport(UnitTestSubscribeCompleteReport&&) = default; UnitTestSubscribeCompleteReport& operator=(const UnitTestSubscribeCompleteReport&) = default; }; @@ -329,7 +330,7 @@ class UnitTestCommonBase static void unitTestSearchCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info); static void unitTestConnectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info); static void unitTestDisconnectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status); - static void unitTestSubscribeCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info); + static void unitTestSubscribeCompleteCb(void* data, CC_MqttsnSubscribeHandle handle, CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info); LibFuncs m_funcs; ClientData m_data; diff --git a/client/lib/test/UnitTestSubscribe.th b/client/lib/test/UnitTestSubscribe.th index 603d80a8..1c78ff71 100644 --- a/client/lib/test/UnitTestSubscribe.th +++ b/client/lib/test/UnitTestSubscribe.th @@ -532,7 +532,68 @@ void UnitTestSubscribe::test6() TS_ASSERT(!unitTestHasOutputData()); subMsgId2 = subscribeMsg->field_msgId().value(); TS_ASSERT_DIFFERS(subMsgId2, 0U); + TS_ASSERT_DIFFERS(subMsgId2, subMsgId1); } - // TODO + TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 10); + + { + UnitTestSubackMsg subackMsg; + subackMsg.field_flags().field_qos().setValue(AckQos); + subackMsg.field_msgId().setValue(subMsgId2); + subackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, subackMsg); + } + + { + TS_ASSERT(unitTestHasSubscribeCompleteReport()); + auto subscribeReport = unitTestSubscribeCompleteReport(); + TS_ASSERT_EQUALS(subscribeReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(subscribeReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted); + TS_ASSERT_EQUALS(subscribeReport->m_info.m_topicId, 0U); + TS_ASSERT_EQUALS(subscribeReport->m_info.m_qos, AckQos); + } + + auto subMsgId3 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* subscribeMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(subscribeMsg, nullptr); + TS_ASSERT_EQUALS(subscribeMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(subscribeMsg->field_flags().field_qos().value()), CC_MqttsnQoS_AtMostOnceDelivery); + TS_ASSERT(subscribeMsg->field_topicId().isMissing()); + TS_ASSERT(subscribeMsg->field_topicName().doesExist()); + TS_ASSERT_EQUALS(subscribeMsg->field_topicName().field().value(), Topic3); + TS_ASSERT(!unitTestHasOutputData()); + subMsgId3 = subscribeMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(subMsgId3, 0U); + TS_ASSERT_DIFFERS(subMsgId3, subMsgId2); + TS_ASSERT_DIFFERS(subMsgId3, subMsgId1); + } + + TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 10); + + { + UnitTestSubackMsg subackMsg; + subackMsg.field_flags().field_qos().setValue(AckQos); + subackMsg.field_msgId().setValue(subMsgId3); + subackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, subackMsg); + } + + { + TS_ASSERT(unitTestHasSubscribeCompleteReport()); + auto subscribeReport = unitTestSubscribeCompleteReport(); + TS_ASSERT_EQUALS(subscribeReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(subscribeReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted); + TS_ASSERT_EQUALS(subscribeReport->m_info.m_topicId, 0U); + TS_ASSERT_EQUALS(subscribeReport->m_info.m_qos, AckQos); + } + + TS_ASSERT(unitTestHasTickReq()); } \ No newline at end of file From 35a66196644d29fdadb5062fec2decea250de88d Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 12 Jul 2024 08:41:11 +1000 Subject: [PATCH 061/106] Documenting the client "subscribe" operation. --- client/lib/doxygen/main.dox | 188 +++++++++++++++++++++++++++++++++++- 1 file changed, 187 insertions(+), 1 deletion(-) diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index 2a047d52..e598348b 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -775,7 +775,7 @@ /// configuration. It can be changed for the allocated operation using the /// @b cc_mqttsn_client_disconnect_set_retry_period() function. /// @code -/// ec = cc_mqttsn_client_disconnect_set_retry_period(connect, 1000); +/// ec = cc_mqttsn_client_disconnect_set_retry_period(disconnect, 1000); /// if (ec != CC_MqttsnErrorCode_Success) { /// ... /* Something went wrong */ /// } @@ -869,6 +869,192 @@ /// } /// @endcode /// +/// @section doc_cc_mqttsn_client_subscribe Subscribing to Receive Messages +/// To subscribe to receive incoming messages use @ref subscribe "subscribe" operation. +/// The application can issue multiple "subscribe" operations in parallel. +/// +/// @subsection doc_cc_mqttsn_client_subscribe_prepare Preparing "Subscribe" Operation. +/// @code +/// CC_MqttsnErrorCode ec = CC_MqttsnErrorCode_Success; +/// CC_MqttsnSubscribeHandle subscribe = cc_mqttsn_client_subscribe_prepare(client, &ec); +/// if (subscribe == NULL) { +/// printf("ERROR: Subscribe allocation failed with ec=%d\n", ec); +/// } +/// @endcode +/// +/// @subsection doc_cc_mqttsn_client_subscribe_retry_period Configuring "Subscribe" Retry Period +/// When created, the "subscribe" operation inherits the @ref doc_cc_mqttsn_client_retry_period +/// configuration. It can be changed for the allocated operation using the +/// @b cc_mqttsn_client_subscribe_set_retry_period() function. +/// @code +/// ec = cc_mqttsn_client_subscribe_set_retry_period(connect, 1000); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// ... /* Something went wrong */ +/// } +/// @endcode +/// To retrieve the configured response timeout use the @b cc_mqttsn_client_subscribe_get_retry_period() function. +/// +/// @subsection doc_cc_mqttsn_client_subscribe_retry_count Configuring "Subscribe" Retry Count +/// When created, the "subscribe" operation inherits the @ref doc_cc_mqttsn_client_retry_count +/// configuration. It can be changed for the allocated operation using the +/// @b cc_mqttsn_client_subscribe_set_retry_count() function. +/// @code +/// ec = cc_mqttsn_client_subscribe_set_retry_count(subscribe, 2); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// ... /* Something went wrong */ +/// } +/// @endcode +/// To retrieve the configured response timeout use the @b cc_mqttsn_client_subscribe_get_retry_count() function. +/// +/// @subsection doc_cc_mqttsn_client_subscribe_config Subscribe Configuration +/// To configure "subscribe" operation use @b cc_mqttsn_client_subscribe_config() function. +/// @code +/// CC_MqttsnSubscribeConfig config; +/// +/// // Assign default values to the configuration +/// cc_mqttsn_client_subscribe_init_config(&config); +/// assert(config.m_qos == CC_MqttsnQoS_ExactlyOnceDelivery); // Initialization puts the maximum allowed QoS +/// +/// // Update values if needed +/// config.m_topic = "some/topic"; +/// +/// // Perform the configuration +/// ec = cc_mqttsn_client_subscribe_config(subscribe, &config); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Topic configuration failed with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// When there is a need to use a predefined topic id instead of the topic string +/// use @ref CC_MqttsnSubscribeConfig::m_topicId "m_topicId" member instead +/// of @ref CC_MqttsnSubscribeConfig::m_topic "m_topic". +/// @code +/// // Assign default values to the configuration +/// cc_mqttsn_client_subscribe_init_config(&config); +/// +/// // Update values if needed +/// config.m_topicId = 123; +/// @endcode +/// See also documentation of the @ref CC_MqttsnSubscribeConfig structure. +/// +/// The MQTT-SN specification also specifies short topics of 2 byte length. The +/// library detects this case by analysing the string assigned to the +/// @ref CC_MqttsnSubscribeConfig::m_topic "m_topic" member and uses appropriate +/// message configuration if needed. +/// +/// By default the library will perform the analysis of the submitted topic format and +/// reject it if topic format is incorrect. However, for performance reasons +/// it is possible to disable such verification when client application +/// ensures that no invalid topics are used. +/// @code +/// ec = cc_mqttsn_client_set_verify_outgoing_topic_enabled(client, false); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// ... /* Something is wrong */ +/// } +/// @endcode +/// @b NOTE that the configuration is global per client and not per "subscribe" +/// operation. +/// +/// Also @b note that the same function controls the verification of the +/// "subscribe", "unsubscribe" and "publish" topic formats. +/// +/// To retrieve the current configuration use @b cc_mqttsn_client_get_verify_outgoing_topic_enabled() +/// function. +/// +/// @subsection doc_cc_mqttsn_client_subscribe_send Sending Subscription Request +/// When all the necessary configurations are performed for the allocated "subscribe" +/// operation it can actually be sent to the broker. To initiate sending +/// use the @b cc_mqttsn_client_subscribe_send() function. +/// @code +/// void my_subscribe_complete_cb(void* data, CC_MqttsnSubscribeHandle handle, CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info) +/// { +/// if (status != CC_MqttsnAsyncOpStatus_Complete) { +/// printf("ERROR: The subscription operation has failed with status=%d\n", status); +/// ... // handle error. +/// return; +/// } +/// +/// // "info" is not NULL when status is CC_MqttsnAsyncOpStatus_Complete. +/// assert(info != NULL); +/// ... // Analyze response values. +/// } +/// +/// ec = cc_mqttsn_client_subscribe_send(subscribe, &my_subscribe_complete_cb, data); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to send subscribe request with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// The provided callback will be invoked when the "subscribe" operation is complete +/// if and only if the function returns @ref CC_MqttsnErrorCode_Success. +/// +/// The handle returned by the @b cc_mqttsn_client_subscribe_prepare() function +/// can be discarded (there is no free / de-allocation) right after the +/// @b cc_mqttsn_client_subscribe_send() invocation +/// regardless of the returned error code. However, the handle remains valid until +/// the callback is called (in case the @ref CC_MqttsnErrorCode_Success was returned). +/// The valid handle can be used to @ref doc_cc_mqttsn_client_subscribe_cancel "cancel" +/// the operation before the completion callback is invoked. +/// +/// The MQTT-SN spec demands that the @b SUBSCRIBE transactions be issued one at +/// a time. However, the library allows @ref doc_cc_mqttsn_client_subscribe_prepare "preparing" +/// and @ref doc_cc_mqttsn_client_subscribe_send "sending" multiple subscription +/// requests in parallel before completion of the first one. The library will +/// retain the requested "subscribe" operation requests internally and will +/// issue them one after another to comply with the specification. +/// +/// Note that the callback function receives the "subscribe" operation handle as +/// its second parameter. Although the handle is already invalid and cannot be +/// used in any other function, it allows the application to identify the +/// original "subscribe" request if multiple have been issued in parallel +/// and use the same callback function for all of them. +/// +/// When the "subscribe" operation completion callback is invoked the reported +/// response information is present if and only if the "status" is +/// @ref CC_MqttsnAsyncOpStatus_Complete. +/// +/// @subsection doc_cc_mqttsn_client_subscribe_cancel Cancel the "Subscribe" Operation. +/// While the handle returned by the @b cc_mqttsn_client_subscribe_prepare() is still +/// valid it is possible to cancel / discard the operation. +/// @code +/// ec = cc_mqttsn_client_subscribe_cancel(subscribe); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to cancel subscribe with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// In case the @b cc_mqttsn_client_subscribe_send() function was successfully +/// called before the @b cc_mqttsn_client_subscribe_cancel(), the operation is +/// cancelled @b without callback invocation. +/// +/// @subsection doc_cc_mqttsn_client_subscribe_simplify Simplifying the "Subscribe" Operation Preparation. +/// In many use cases the "subscribe" operation can be quite simple with a lot of defaults. +/// To simplify the sequence of the operation preparation and handling of errors, +/// the library provides wrapper function that can be used: +/// @li @b cc_mqttsn_client_subscribe() +/// +/// For example: +/// @code +/// CC_MqttsnSubscribeConfig config; +/// +/// // Assign default values to the configuration +/// cc_mqttsn_client_subscribe_init_config(&config); +/// +/// // Update values if needed +/// config.m_topic = "some/topic"; +/// +/// ec = cc_mqttsn_client_subscribe(client, &config, &my_subscribe_complete_cb, data); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to send subscribe request with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// Note that the wrapper function does NOT expose the handle returned by the +/// @b cc_mqttsn_client_subscribe_prepare(). It means that it's not possible to +/// cancel the "subscribe" operation before its completion or identify the +/// subscribe operation by the reported handle when the completion callback +/// is invoked. +/// /// @section doc_cc_mqttsn_client_receive Receiving Messages /// TODO /// From b8067bae07d526003ffd038f6ad88269a65da57a Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 12 Jul 2024 09:17:48 +1000 Subject: [PATCH 062/106] Saving work on "unsubscribe" operation. --- client/lib/CMakeLists.txt | 1 + client/lib/include/cc_mqttsn_client/common.h | 29 +++ client/lib/src/ClientImpl.cpp | 146 +++++++----- client/lib/src/ClientImpl.h | 15 +- client/lib/src/op/Op.cpp | 79 ++++++- client/lib/src/op/Op.h | 3 +- client/lib/src/op/UnsubscribeOp.cpp | 230 +++++++++++++++++++ client/lib/src/op/UnsubscribeOp.h | 66 ++++++ 8 files changed, 495 insertions(+), 74 deletions(-) create mode 100644 client/lib/src/op/UnsubscribeOp.cpp create mode 100644 client/lib/src/op/UnsubscribeOp.h diff --git a/client/lib/CMakeLists.txt b/client/lib/CMakeLists.txt index cadb796a..cbe1c675 100644 --- a/client/lib/CMakeLists.txt +++ b/client/lib/CMakeLists.txt @@ -171,6 +171,7 @@ function (gen_lib_mqttsn_client config_file) src/op/Op.cpp src/op/SearchOp.cpp src/op/SubscribeOp.cpp + src/op/UnsubscribeOp.cpp src/ClientImpl.cpp src/TimerMgr.cpp ) diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index 011f5d5c..73ceba1f 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -158,6 +158,14 @@ struct CC_MqttsnSubscribe; /// @ingroup "subscribe". typedef struct CC_MqttsnSubscribe* CC_MqttsnSubscribeHandle; +/// @brief Declaration of the hidden structure used to define @ref CC_MqttsnUnsubscribeHandle +/// @ingroup unsubscribe +struct CC_MqttsnUnsubscribe; + +/// @brief Handle for "unsubscribe" operation. +/// @details Returned by @b cc_mqttsn_client_unsubscribe_prepare() function. +/// @ingroup "subscribe". +typedef struct CC_MqttsnUnsubscribe* CC_MqttsnUnsubscribeHandle; /// @brief Type used to hold Topic ID value. typedef unsigned short CC_MqttsnTopicId; @@ -237,6 +245,15 @@ typedef struct CC_MqttsnQoS m_qos; ///< Granted max QoS value } CC_MqttsnSubscribeInfo; +/// @brief Configuration the "unsubscribe" operation +/// @ingroup unsubscribe +typedef struct +{ + const char* m_topic; ///< Subscription topic, can be NULL when pre-defined topic ID is used. + CC_MqttsnTopicId m_topicId; ///< Pre-defined topic ID, should be @b 0 when topic is not NULL. + CC_MqttsnQoS m_qos; ///< Max QoS value +} CC_MqttsnUnsubscribeConfig; + /// @brief Callback used to request time measurement. /// @details The callback is set using /// cc_mqttsn_client_set_next_tick_program_callback() function. @@ -346,6 +363,18 @@ typedef void (*CC_MqttsnDisconnectCompleteCb)(void* data, CC_MqttsnAsyncOpStatus /// @ingroup subscribe typedef void (*CC_MqttsnSubscribeCompleteCb)(void* data, CC_MqttsnSubscribeHandle handle, CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info); +/// @brief Callback used to report completion of the unsubscribe operation. +/// @param[in] data Pointer to user data object, passed as the last parameter to +/// the request call. +/// @param[in] handle Handle returned by @b cc_mqttsn_client_unsubscribe_prepare() function. When the +/// callback is invoked the handle is already invalid and cannot be used in any relevant +/// function invocation, but it allows end application to identify the original "unsubscribe" operation +/// and use the same callback function in parallel requests. +/// @param[in] status Status of the "unsubscribe" operation. +/// @post The data members of the reported response can NOT be accessed after the function returns. +/// @ingroup unsubscribe +typedef void (*CC_MqttsnUnsubscribeCompleteCb)(void* data, CC_MqttsnUnsubscribeHandle handle, CC_MqttsnAsyncOpStatus status); + #ifdef __cplusplus } #endif diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 4043ee44..4c55d1c7 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -315,8 +315,8 @@ op::SubscribeOp* ClientImpl::subscribePrepare(CC_MqttsnErrorCode* ec) m_subscribeOps.push_back(std::move(ptr)); op = m_subscribeOps.back().get(); - if (1U < m_subscribeOps.size()) { - // Only one SUBSCRIBE transaction is allowed at a time by the specification + if ((1U < m_subscribeOps.size()) || (!m_unsubscribeOps.empty())) { + // Only one SUBSCRIBE / UNSUBSCRIBE transaction is allowed at a time by the specification op->suspend(); } @@ -326,56 +326,56 @@ op::SubscribeOp* ClientImpl::subscribePrepare(CC_MqttsnErrorCode* ec) return op; } -// op::UnsubscribeOp* ClientImpl::unsubscribePrepare(CC_MqttsnErrorCode* ec) -// { -// op::UnsubscribeOp* unsubOp = nullptr; -// do { -// if (!m_sessionState.m_connected) { -// errorLog("Client must be connected to allow unsubscription."); -// updateEc(ec, CC_MqttsnErrorCode_NotConnected); -// break; -// } +op::UnsubscribeOp* ClientImpl::unsubscribePrepare(CC_MqttsnErrorCode* ec) +{ + op::UnsubscribeOp* op = nullptr; + do { + if (!m_sessionState.m_connected) { + errorLog("Client must be connected to allow subscription."); + updateEc(ec, CC_MqttsnErrorCode_NotConnected); + break; + } -// if (m_sessionState.m_disconnecting) { -// errorLog("Session disconnection is in progress, cannot initiate unsubscription."); -// updateEc(ec, CC_MqttsnErrorCode_Disconnecting); -// break; -// } + if (m_sessionState.m_disconnecting) { + errorLog("Session disconnection is in progress, cannot initiate subscription."); + updateEc(ec, CC_MqttsnErrorCode_Disconnecting); + break; + } -// if (m_clientState.m_networkDisconnected) { -// errorLog("Network is disconnected."); -// updateEc(ec, CC_MqttsnErrorCode_NetworkDisconnected); -// break; -// } + if (m_ops.max_size() <= m_ops.size()) { + errorLog("Cannot start unsubscribe operation, retry in next event loop iteration."); + updateEc(ec, CC_MqttsnErrorCode_RetryLater); + break; + } -// if (m_ops.max_size() <= m_ops.size()) { -// errorLog("Cannot start subscribe operation, retry in next event loop iteration."); -// updateEc(ec, CC_MqttsnErrorCode_RetryLater); -// break; -// } + if (m_preparationLocked) { + errorLog("Another operation is being prepared, cannot prepare \"unsubscribe\" without \"send\" or \"cancel\" of the previous."); + updateEc(ec, CC_MqttsnErrorCode_PreparationLocked); + break; + } -// if (m_preparationLocked) { -// errorLog("Another operation is being prepared, cannot prepare \"unsubscribe\" without \"send\" or \"cancel\" of the previous."); -// updateEc(ec, CC_MqttsnErrorCode_PreparationLocked); -// break; -// } + auto ptr = m_unsubscribeOpsAlloc.alloc(*this); + if (!ptr) { + errorLog("Cannot allocate new unsubscribe operation."); + updateEc(ec, CC_MqttsnErrorCode_OutOfMemory); + break; + } -// auto ptr = m_unsubscribeOpsAlloc.alloc(*this); -// if (!ptr) { -// errorLog("Cannot allocate new unsubscribe operation."); -// updateEc(ec, CC_MqttsnErrorCode_OutOfMemory); -// break; -// } + m_preparationLocked = true; + m_ops.push_back(ptr.get()); + m_unsubscribeOps.push_back(std::move(ptr)); + op = m_unsubscribeOps.back().get(); -// m_preparationLocked = true; -// m_ops.push_back(ptr.get()); -// m_unsubscribeOps.push_back(std::move(ptr)); -// unsubOp = m_unsubscribeOps.back().get(); -// updateEc(ec, CC_MqttsnErrorCode_Success); -// } while (false); + if ((1U < m_unsubscribeOps.size()) || (!m_subscribeOps.empty())) { + // Only one SUBSCRIBE / UNSUBSCRIBE transaction is allowed at a time by the specification + op->suspend(); + } -// return unsubOp; -// } + updateEc(ec, CC_MqttsnErrorCode_Success); + } while (false); + + return op; +} // op::SendOp* ClientImpl::publishPrepare(CC_MqttsnErrorCode* ec) // { @@ -856,7 +856,7 @@ void ClientImpl::opComplete(const op::Op* op) /* Type_KeepAlive */ &ClientImpl::opComplete_KeepAlive, /* Type_Disconnect */ &ClientImpl::opComplete_Disconnect, /* Type_Subscribe */ &ClientImpl::opComplete_Subscribe, - // /* Type_Unsubscribe */ &ClientImpl::opComplete_Unsubscribe, + /* Type_Unsubscribe */ &ClientImpl::opComplete_Unsubscribe, // /* Type_Recv */ &ClientImpl::opComplete_Recv, // /* Type_Send */ &ClientImpl::opComplete_Send, }; @@ -1205,16 +1205,14 @@ void ClientImpl::opComplete_Disconnect(const op::Op* op) void ClientImpl::opComplete_Subscribe(const op::Op* op) { eraseFromList(op, m_subscribeOps); - if (!m_subscribeOps.empty()) { - COMMS_ASSERT(m_subscribeOps.front()); - m_subscribeOps.front()->resume(); - } + finaliseSupUnsubOp(); } -// void ClientImpl::opComplete_Unsubscribe(const op::Op* op) -// { -// eraseFromList(op, m_unsubscribeOps); -// } +void ClientImpl::opComplete_Unsubscribe(const op::Op* op) +{ + eraseFromList(op, m_unsubscribeOps); + finaliseSupUnsubOp(); +} // void ClientImpl::opComplete_Recv(const op::Op* op) // { @@ -1231,6 +1229,46 @@ void ClientImpl::opComplete_Subscribe(const op::Op* op) // resumeSendOpsSince(idx); // } +void ClientImpl::finaliseSupUnsubOp() +{ + if (m_subscribeOps.empty() && m_unsubscribeOps.empty()) { + return; + } + + if ((!m_subscribeOps.empty()) && (m_unsubscribeOps.empty())) { + COMMS_ASSERT(m_subscribeOps.front()); + m_subscribeOps.front()->resume(); + return; + } + + if (m_subscribeOps.empty()) { + COMMS_ASSERT(!m_unsubscribeOps.empty()); + COMMS_ASSERT(m_unsubscribeOps.front()); + m_unsubscribeOps.front()->resume(); + return; + } + + COMMS_ASSERT(!m_subscribeOps.empty()); + COMMS_ASSERT(!m_unsubscribeOps.empty()); + for (auto* op : m_ops) { + if (op == nullptr) { + continue; + } + + if (op->type() == op::Op::Type_Subscribe) { + auto* subscribeOp = static_cast(op); + subscribeOp->resume(); + return; + } + + if (op->type() == op::Op::Type_Unsubscribe) { + auto* unsubscribeOp = static_cast(op); + unsubscribeOp->resume(); + return; + } + } +} + void ClientImpl::monitorGatewayExpiry() { if constexpr (Config::HasGatewayDiscovery) { diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index 77157495..0a845ae5 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -25,7 +25,7 @@ #include "op/SearchOp.h" // #include "op/SendOp.h" #include "op/SubscribeOp.h" -// #include "op/UnsubscribeOp.h" +#include "op/UnsubscribeOp.h" #include "cc_mqttsn_client/common.h" @@ -72,7 +72,7 @@ class ClientImpl final : public ProtMsgHandler op::ConnectOp* connectPrepare(CC_MqttsnErrorCode* ec); op::DisconnectOp* disconnectPrepare(CC_MqttsnErrorCode* ec); op::SubscribeOp* subscribePrepare(CC_MqttsnErrorCode* ec); - // op::UnsubscribeOp* unsubscribePrepare(CC_MqttsnErrorCode* ec); + op::UnsubscribeOp* unsubscribePrepare(CC_MqttsnErrorCode* ec); // op::SendOp* publishPrepare(CC_MqttsnErrorCode* ec); // std::size_t sendsCount() const @@ -247,8 +247,8 @@ class ClientImpl final : public ProtMsgHandler using SubscribeOpAlloc = ObjAllocator; using SubscribeOpsList = ObjListType; - // using UnsubscribeOpAlloc = ObjAllocator; - // using UnsubscribeOpsList = ObjListType; + using UnsubscribeOpAlloc = ObjAllocator; + using UnsubscribeOpsList = ObjListType; // using RecvOpAlloc = ObjAllocator; // using RecvOpsList = ObjListType; @@ -285,10 +285,11 @@ class ClientImpl final : public ProtMsgHandler void opComplete_KeepAlive(const op::Op* op); void opComplete_Disconnect(const op::Op* op); void opComplete_Subscribe(const op::Op* op); - // void opComplete_Unsubscribe(const op::Op* op); + void opComplete_Unsubscribe(const op::Op* op); // void opComplete_Recv(const op::Op* op); // void opComplete_Send(const op::Op* op); + void finaliseSupUnsubOp(); void monitorGatewayExpiry(); void gwExpiryTimeout(); void reportGwStatus(CC_MqttsnGwStatus status, const ClientState::GwInfo& info); @@ -354,8 +355,8 @@ class ClientImpl final : public ProtMsgHandler SubscribeOpAlloc m_subscribeOpsAlloc; SubscribeOpsList m_subscribeOps; - // UnsubscribeOpAlloc m_unsubscribeOpsAlloc; - // UnsubscribeOpsList m_unsubscribeOps; + UnsubscribeOpAlloc m_unsubscribeOpsAlloc; + UnsubscribeOpsList m_unsubscribeOps; // RecvOpAlloc m_recvOpsAlloc; // RecvOpsList m_recvOps; diff --git a/client/lib/src/op/Op.cpp b/client/lib/src/op/Op.cpp index 6915df8b..cbabf442 100644 --- a/client/lib/src/op/Op.cpp +++ b/client/lib/src/op/Op.cpp @@ -29,6 +29,11 @@ static constexpr char TopicSep = '/'; static constexpr char MultLevelWildcard = '#'; static constexpr char SingleLevelWildcard = '+'; +bool isValidTopicIdInternal(CC_MqttsnTopicId id) +{ + return (id != 0U) && (id != 0xffff); +} + template typename TMap::iterator findRegTopicInfo(CC_MqttsnTopicId topicId, TMap& map) { @@ -40,6 +45,61 @@ typename TMap::iterator findRegTopicInfo(CC_MqttsnTopicId topicId, TMap& map) }); } +template +typename TMap::iterator findRegTopicInfo(const char* topic, TMap& map) +{ + return + std::find_if( + map.begin(), map.end(), + [topic](auto& info) + { + return info.m_topic == topic; + }); +} + +template +void storeRegTopic(const char* topic, CC_MqttsnTopicId topicId, TMap& map) +{ + auto iter = findRegTopicInfo(topicId, map); + if ((iter != map.end()) && (iter->m_topicId == topicId)) { + iter->m_topic = topic; + return; + } + + if (topic == nullptr) { + map.insert(iter, RegTopicInfo{TopicNameStr(), topicId}); + return; + } + + map.insert(iter, RegTopicInfo{topic, topicId}); +} + +template +bool removeRegTopic(const char* topic, CC_MqttsnTopicId topicId, TMap& map) +{ + if (isValidTopicIdInternal(topicId)) { + auto iter = findRegTopicInfo(topicId, map); + if ((iter != map.end()) && (iter->m_topicId == topicId)) { + map.erase(iter); + return true; + } + + return false; + } + + if (topic == nullptr) { + return false; + } + + auto iter = findRegTopicInfo(topic, map); + if (iter == map.end()) { + return false; + } + + map.erase(iter); + return true; +} + } // namespace @@ -160,23 +220,18 @@ void Op::decRetryCount() void Op::storeInRegTopic(const char* topic, CC_MqttsnTopicId topicId) { auto& map = m_client.reuseState().m_inRegTopics; - auto iter = findRegTopicInfo(topicId, map); - if ((iter != map.end()) && (iter->m_topicId == topicId)) { - iter->m_topic = topic; - return; - } - - if (topic == nullptr) { - map.insert(iter, RegTopicInfo{TopicNameStr(), topicId}); - return; - } + storeRegTopic(topic, topicId, map); +} - map.insert(iter, RegTopicInfo{topic, topicId}); +bool Op::removeInRegTopic(const char* topic, CC_MqttsnTopicId topicId) +{ + auto& map = m_client.reuseState().m_inRegTopics; + return removeRegTopic(topic, topicId, map); } bool Op::isValidTopicId(CC_MqttsnTopicId id) { - return (id != 0U) && (id != 0xffff); + return isValidTopicIdInternal(id); } bool Op::isShortTopic(const char* topic) diff --git a/client/lib/src/op/Op.h b/client/lib/src/op/Op.h index 01328b67..4efed56e 100644 --- a/client/lib/src/op/Op.h +++ b/client/lib/src/op/Op.h @@ -34,7 +34,7 @@ class Op : public ProtMsgHandler Type_KeepAlive, Type_Disconnect, Type_Subscribe, - // Type_Unsubscribe, + Type_Unsubscribe, // Type_Recv, // Type_Send, Type_NumOfValues // Must be last @@ -98,6 +98,7 @@ class Op : public ProtMsgHandler void releasePacketId(std::uint16_t id); void decRetryCount(); void storeInRegTopic(const char* topic, CC_MqttsnTopicId topicId); + bool removeInRegTopic(const char* topic, CC_MqttsnTopicId topicId); static bool isValidTopicId(CC_MqttsnTopicId id); static bool isShortTopic(const char* topic); diff --git a/client/lib/src/op/UnsubscribeOp.cpp b/client/lib/src/op/UnsubscribeOp.cpp new file mode 100644 index 00000000..5035e0e6 --- /dev/null +++ b/client/lib/src/op/UnsubscribeOp.cpp @@ -0,0 +1,230 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "op/UnsubscribeOp.h" +#include "ClientImpl.h" + +#include "comms/util/assign.h" +#include "comms/util/ScopeGuard.h" +#include "comms/units.h" + +#include +#include + +namespace cc_mqttsn_client +{ + +namespace op +{ + +namespace +{ + +inline UnsubscribeOp* asUnsubscribeOp(void* data) +{ + return reinterpret_cast(data); +} + +inline CC_MqttsnUnsubscribeHandle asHandle(UnsubscribeOp* op) +{ + return reinterpret_cast(op); +} + +} // namespace + + +UnsubscribeOp::UnsubscribeOp(ClientImpl& client) : + Base(client), + m_timer(client.timerMgr().allocTimer()) +{ +} + +CC_MqttsnErrorCode UnsubscribeOp::config(const CC_MqttsnUnsubscribeConfig* config) +{ + if (config == nullptr) { + errorLog("Unsubscribe configuration is not provided."); + return CC_MqttsnErrorCode_BadParam; + } + + bool emptyTopic = + (config->m_topic == nullptr) || + (config->m_topic[0] == '\0'); + + if (emptyTopic && (!isValidTopicId(config->m_topicId))) { + errorLog("Neither topic nor pre-defined topic ID are provided in UNSUBSCRIBE configuration."); + return CC_MqttsnErrorCode_BadParam; + } + + if (static_castm_qos)>(Config::MaxQos) < config->m_qos) { + errorLog("Bad subscription qos value."); + return CC_MqttsnErrorCode_BadParam; + } + + if ((!emptyTopic) && (!verifySubFilter(config->m_topic))) { + errorLog("Bad topic filter format in unsubscribe."); + return CC_MqttsnErrorCode_BadParam; + } + + m_unsubscribeMsg.field_flags().field_qos().setValue(config->m_qos); + + using TopicIdType = UnsubscribeMsg::Field_flags::Field_topicIdType::ValueType; + if (emptyTopic) { + m_unsubscribeMsg.field_topicId().field().setValue(config->m_topicId); + m_unsubscribeMsg.field_flags().field_topicIdType().value() = TopicIdType::PredefinedTopicId; + return CC_MqttsnErrorCode_Success; + } + + if (isShortTopic(config->m_topic)) { + auto topicId = + (static_cast(config->m_topic[0]) << 8U) | + (static_cast(config->m_topic[1])); + m_unsubscribeMsg.field_topicId().field().setValue(topicId); + m_unsubscribeMsg.field_flags().field_topicIdType().value() = TopicIdType::ShortTopicName; + return CC_MqttsnErrorCode_Success; + } + + m_unsubscribeMsg.field_topicName().field().value() = config->m_topic; + m_unsubscribeMsg.field_flags().field_topicIdType().value() = TopicIdType::Normal; + return CC_MqttsnErrorCode_Success; +} + +CC_MqttsnErrorCode UnsubscribeOp::send(CC_MqttsnUnsubscribeCompleteCb cb, void* cbData) +{ + client().allowNextPrepare(); + auto completeOnError = + comms::util::makeScopeGuard( + [this]() + { + opComplete(); + }); + + if (cb == nullptr) { + errorLog("Unsubscribe completion callback is not provided."); + return CC_MqttsnErrorCode_BadParam; + } + + if (!m_timer.isValid()) { + errorLog("The library cannot allocate required number of timers."); + return CC_MqttsnErrorCode_InternalError; + } + + auto guard = client().apiEnter(); + m_cb = cb; + m_cbData = cbData; + + m_unsubscribeMsg.field_msgId().setValue(allocPacketId()); + m_unsubscribeMsg.doRefresh(); // Update optionals + + auto ec = sendInternal(); + if (ec != CC_MqttsnErrorCode_Success) { + return ec; + } + + // TODO: remove the record + + completeOnError.release(); + return CC_MqttsnErrorCode_Success; +} + +CC_MqttsnErrorCode UnsubscribeOp::cancel() +{ + if (m_cb == nullptr) { + // hasn't been sent yet + client().allowNextPrepare(); + } + + opComplete(); + return CC_MqttsnErrorCode_Success; +} + +void UnsubscribeOp::handle(UnsubackMsg& msg) +{ + if ((m_suspended) || (msg.field_msgId().value() != m_unsubscribeMsg.field_msgId().value())) { + return; + } + + m_timer.cancel(); + completeOpInternal(CC_MqttsnAsyncOpStatus_Complete); +} + +void UnsubscribeOp::resume() +{ + COMMS_ASSERT(m_suspended); + m_suspended = false; + auto ec = sendInternal(); + if (ec != CC_MqttsnErrorCode_Success) { + errorLog("Failed to send UNSUBSCRIBE, after prev UNSUBSCRIBE completion"); + completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); + return; + } +} + +Op::Type UnsubscribeOp::typeImpl() const +{ + return Type_Unsubscribe; +} + +void UnsubscribeOp::terminateOpImpl(CC_MqttsnAsyncOpStatus status) +{ + completeOpInternal(status); +} + +void UnsubscribeOp::completeOpInternal(CC_MqttsnAsyncOpStatus status) +{ + auto handle = asHandle(this); + auto cb = m_cb; + auto* cbData = m_cbData; + opComplete(); // mustn't access data members after destruction + if (cb != nullptr) { + cb(cbData, handle, status); + } +} + +void UnsubscribeOp::restartTimer() +{ + m_timer.wait(getRetryPeriod(), &UnsubscribeOp::opTimeoutCb, this); +} + +CC_MqttsnErrorCode UnsubscribeOp::sendInternal() +{ + if (m_suspended) { + return CC_MqttsnErrorCode_Success; // Send after resume + } + + auto ec = sendMessage(m_unsubscribeMsg); + if (ec == CC_MqttsnErrorCode_Success) { + restartTimer(); + } + + return ec; +} + +void UnsubscribeOp::timeoutInternal() +{ + if (getRetryCount() == 0U) { + errorLog("All retries of the unsubscribe operation have been exhausted."); + completeOpInternal(CC_MqttsnAsyncOpStatus_Timeout); + return; + } + + auto ec = sendInternal(); + if (ec != CC_MqttsnErrorCode_Success) { + completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); + return; + } + + decRetryCount(); +} + +void UnsubscribeOp::opTimeoutCb(void* data) +{ + asUnsubscribeOp(data)->timeoutInternal(); +} + +} // namespace op + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/op/UnsubscribeOp.h b/client/lib/src/op/UnsubscribeOp.h new file mode 100644 index 00000000..2de880c8 --- /dev/null +++ b/client/lib/src/op/UnsubscribeOp.h @@ -0,0 +1,66 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "op/Op.h" +#include "ExtConfig.h" +#include "ProtocolDefs.h" + +#include "TimerMgr.h" + +namespace cc_mqttsn_client +{ + +namespace op +{ + +class UnsubscribeOp final : public Op +{ + using Base = Op; +public: + explicit UnsubscribeOp(ClientImpl& client); + + CC_MqttsnErrorCode config(const CC_MqttsnUnsubscribeConfig* config); + CC_MqttsnErrorCode send(CC_MqttsnUnsubscribeCompleteCb cb, void* cbData); + CC_MqttsnErrorCode cancel(); + + using Base::handle; + void handle(UnsubackMsg& msg) override; + + void suspend() + { + m_suspended = true; + } + + void resume(); + +protected: + virtual Type typeImpl() const override; + virtual void terminateOpImpl(CC_MqttsnAsyncOpStatus status) override; + +private: + void completeOpInternal(CC_MqttsnAsyncOpStatus status); + void restartTimer(); + CC_MqttsnErrorCode sendInternal(); + void timeoutInternal(); + + static void opTimeoutCb(void* data); + + UnsubscribeMsg m_unsubscribeMsg; + TimerMgr::Timer m_timer; + CC_MqttsnUnsubscribeCompleteCb m_cb = nullptr; + void* m_cbData = nullptr; + bool m_suspended = false; + + static_assert(ExtConfig::UnsubscribeOpTimers == 1U); +}; + +} // namespace op + + +} // namespace cc_mqttsn_client From f421c86bff3d5e2346fe1fcb7b4c5dfd7d0d912d Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Mon, 15 Jul 2024 09:18:43 +1000 Subject: [PATCH 063/106] Initial support for client unsubscribe. --- client/lib/include/cc_mqttsn_client/common.h | 1 - client/lib/src/TopicFilterDefs.h | 12 ++- client/lib/src/op/SubscribeOp.cpp | 53 +++++++---- client/lib/src/op/UnsubscribeOp.cpp | 97 +++++++++++++++++++- client/lib/src/op/UnsubscribeOp.h | 1 + client/lib/test/UnitTestCommonBase.cpp | 1 - client/lib/test/UnitTestCommonBase.h | 1 - client/lib/test/UnitTestSubscribe.th | 7 -- 8 files changed, 142 insertions(+), 31 deletions(-) diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index 73ceba1f..2461e9b8 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -241,7 +241,6 @@ typedef struct typedef struct { CC_MqttsnReturnCode m_returnCode; ///< Return code reported by the @b SUBACK message - CC_MqttsnTopicId m_topicId; ///< Granted topic ID (if applicable). CC_MqttsnQoS m_qos; ///< Granted max QoS value } CC_MqttsnSubscribeInfo; diff --git a/client/lib/src/TopicFilterDefs.h b/client/lib/src/TopicFilterDefs.h index 75f2ce86..225c5ce7 100644 --- a/client/lib/src/TopicFilterDefs.h +++ b/client/lib/src/TopicFilterDefs.h @@ -18,14 +18,20 @@ namespace cc_mqttsn_client { using TopicNameStr = SubscribeMsg::Field_topicName::Field::ValueType; -using SubFiltersMap = ObjListType; struct RegTopicInfo { TopicNameStr m_topic; - CC_MqttsnTopicId m_topicId = 0U; // key + CC_MqttsnTopicId m_topicId = 0U; + + template + RegTopicInfo(T&& topic, CC_MqttsnTopicId topicId) : m_topic(std::forward(topic)), m_topicId(topicId) {} + + RegTopicInfo(const char* topic) : m_topic(topic) {} + RegTopicInfo(CC_MqttsnTopicId topicId) : m_topicId(topicId) {} }; -using InRegTopicsMap = ObjListType; +using SubFiltersMap = ObjListType; // key is m_topic +using InRegTopicsMap = ObjListType; // key is m_topicId; } // namespace cc_mqttsn_client diff --git a/client/lib/src/op/SubscribeOp.cpp b/client/lib/src/op/SubscribeOp.cpp index 1d65f66f..26435479 100644 --- a/client/lib/src/op/SubscribeOp.cpp +++ b/client/lib/src/op/SubscribeOp.cpp @@ -150,29 +150,50 @@ void SubscribeOp::handle(SubackMsg& msg) auto info = CC_MqttsnSubscribeInfo(); info.m_returnCode = static_cast(msg.field_returnCode().value()); info.m_qos = static_cast(msg.field_flags().field_qos().value()); - info.m_topicId = msg.field_topicId().value(); + auto topicId = static_cast(msg.field_topicId().value()); + auto& topicStr = m_subscribeMsg.field_topicName().field().value(); + auto* topicPtr = topicStr.c_str(); + if (topicStr.empty()) { + topicPtr = nullptr; + } + + if (topicId != 0U) { + storeInRegTopic(topicPtr, topicId); + } + + COMMS_ASSERT((topicId != 0U) || (topicPtr != nullptr)); + auto& filtersMap = client().reuseState().m_subFilters; do { - if (info.m_topicId == 0U) { - break; - } + if (topicPtr != nullptr) { + auto iter = + std::lower_bound( + filtersMap.begin(), filtersMap.end(), topicPtr, + [](auto& elem, const char* topicParam) + { + return elem.m_topic < topicParam; + }); + + if ((iter == filtersMap.end()) || (iter->m_topic != topicPtr)) { + filtersMap.emplace(iter, topicPtr); + } - auto& topicStr = m_subscribeMsg.field_topicName().field().value(); - if (!topicStr.empty()) { - storeInRegTopic(topicStr.c_str(), info.m_topicId); break; } - if constexpr (Config::HasSubTopicVerification) { - storeInRegTopic(nullptr, info.m_topicId); - break; - } - - } while (false); + COMMS_ASSERT(m_subscribeMsg.field_topicId().doesExist()); + COMMS_ASSERT(m_subscribeMsg.field_topicId().field().value() != 0U); - if ((info.m_topicId != 0U) && (!m_subscribeMsg.field_topicName().field().value().empty())) { - storeInRegTopic(m_subscribeMsg.field_topicName().field().value().c_str(), info.m_topicId); - } + auto iter = + std::find_if( + filtersMap.begin(), filtersMap.end(), + [](auto& elem) + { + return !elem.m_topic.empty(); + }); + + filtersMap.emplace(iter, static_cast(m_subscribeMsg.field_topicId().field().value())); + } while (false); completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &info); } diff --git a/client/lib/src/op/UnsubscribeOp.cpp b/client/lib/src/op/UnsubscribeOp.cpp index 5035e0e6..c461160c 100644 --- a/client/lib/src/op/UnsubscribeOp.cpp +++ b/client/lib/src/op/UnsubscribeOp.cpp @@ -67,7 +67,47 @@ CC_MqttsnErrorCode UnsubscribeOp::config(const CC_MqttsnUnsubscribeConfig* confi if ((!emptyTopic) && (!verifySubFilter(config->m_topic))) { errorLog("Bad topic filter format in unsubscribe."); return CC_MqttsnErrorCode_BadParam; - } + } + + if constexpr (Config::HasSubTopicVerification) { + do { + if (!client().configState().m_verifySubFilter) { + break; + } + + auto& filtersMap = client().reuseState().m_subFilters; + if (!emptyTopic) { + auto iter = + std::lower_bound( + filtersMap.begin(), filtersMap.end(), config->m_topic, + [](auto& elem, const char* topicParam) + { + return elem.m_topic < topicParam; + }); + + if ((iter == filtersMap.end()) || (iter->m_topic != config->m_topic)) { + errorLog("Requested unsubscribe topic hasn't been used for subscription before"); + return CC_MqttsnErrorCode_BadParam; + } + + break; + } + + COMMS_ASSERT(isValidTopicId(config->m_topicId)); + auto iter = + std::find_if( + filtersMap.begin(), filtersMap.end(), + [config](auto& elem) + { + return config->m_topicId == elem.m_topicId; + }); + + if (iter == filtersMap.end()) { + errorLog("Requested unsubscribe topic ID hasn't been used for subscription before"); + return CC_MqttsnErrorCode_BadParam; + } + } while (false); + } m_unsubscribeMsg.field_flags().field_qos().setValue(config->m_qos); @@ -124,7 +164,60 @@ CC_MqttsnErrorCode UnsubscribeOp::send(CC_MqttsnUnsubscribeCompleteCb cb, void* return ec; } - // TODO: remove the record + // Remove record on first send rather than acknowledgement allowing message + // to get lost. + do { + if (m_recordRemoved) { + break; + } + + m_recordRemoved = true; + + auto& topicStr = m_unsubscribeMsg.field_topicName().field().value(); + auto* topicPtr = topicStr.c_str(); + if (m_unsubscribeMsg.field_topicName().isMissing()) { + topicPtr = nullptr; + } + + auto topicId = m_unsubscribeMsg.field_topicId().field().value(); + COMMS_ASSERT(m_unsubscribeMsg.field_topicId().doesExist() || (topicId == 0U)); + COMMS_ASSERT((topicPtr == nullptr) || (topicId == 0U)); + COMMS_ASSERT((topicPtr != nullptr) || (topicId != 0U)); + + removeInRegTopic(topicPtr, topicId); + + if constexpr (Config::HasSubTopicVerification) { + auto& filtersMap = client().reuseState().m_subFilters; + if (topicPtr != nullptr) { + auto iter = + std::lower_bound( + filtersMap.begin(), filtersMap.end(), topicPtr, + [](auto& elem, const char* topicParam) + { + return elem.m_topic < topicParam; + }); + + if ((iter != filtersMap.end()) && (iter->m_topic != topicPtr)) { + filtersMap.erase(iter); + } + + break; + } + + COMMS_ASSERT(topicId != 0U); + + auto iter = + std::find_if( + filtersMap.begin(), filtersMap.end(), + [topicId](auto& elem) { + return elem.m_topicId == topicId; + }); + + if (iter != filtersMap.end()) { + filtersMap.erase(iter); + } + } + } while (false); completeOnError.release(); return CC_MqttsnErrorCode_Success; diff --git a/client/lib/src/op/UnsubscribeOp.h b/client/lib/src/op/UnsubscribeOp.h index 2de880c8..0d5253fd 100644 --- a/client/lib/src/op/UnsubscribeOp.h +++ b/client/lib/src/op/UnsubscribeOp.h @@ -56,6 +56,7 @@ class UnsubscribeOp final : public Op CC_MqttsnUnsubscribeCompleteCb m_cb = nullptr; void* m_cbData = nullptr; bool m_suspended = false; + bool m_recordRemoved = false; static_assert(ExtConfig::UnsubscribeOpTimers == 1U); }; diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index 314c930a..6e627542 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -153,7 +153,6 @@ UnitTestCommonBase::UnitTestConnectCompleteReport::UnitTestConnectCompleteReport UnitTestCommonBase::UnitTestSubscribeInfo& UnitTestCommonBase::UnitTestSubscribeInfo::operator=(const CC_MqttsnSubscribeInfo& info) { m_returnCode = info.m_returnCode; - m_topicId = info.m_topicId; m_qos = info.m_qos; return *this; } diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index e80eebde..c26f936c 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -206,7 +206,6 @@ class UnitTestCommonBase struct UnitTestSubscribeInfo { CC_MqttsnReturnCode m_returnCode = CC_MqttsnReturnCode_ValuesLimit; - CC_MqttsnTopicId m_topicId; CC_MqttsnQoS m_qos; UnitTestSubscribeInfo() = default; UnitTestSubscribeInfo(const UnitTestSubscribeInfo&) = default; diff --git a/client/lib/test/UnitTestSubscribe.th b/client/lib/test/UnitTestSubscribe.th index 1c78ff71..ccc6ad2c 100644 --- a/client/lib/test/UnitTestSubscribe.th +++ b/client/lib/test/UnitTestSubscribe.th @@ -92,7 +92,6 @@ void UnitTestSubscribe::test1() auto subscribeReport = unitTestSubscribeCompleteReport(); TS_ASSERT_EQUALS(subscribeReport->m_status, CC_MqttsnAsyncOpStatus_Complete); TS_ASSERT_EQUALS(subscribeReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted); - TS_ASSERT_EQUALS(subscribeReport->m_info.m_topicId, 0U); TS_ASSERT_EQUALS(subscribeReport->m_info.m_qos, AckQos); TS_ASSERT(unitTestHasTickReq()); @@ -166,7 +165,6 @@ void UnitTestSubscribe::test2() auto subscribeReport = unitTestSubscribeCompleteReport(); TS_ASSERT_EQUALS(subscribeReport->m_status, CC_MqttsnAsyncOpStatus_Complete); TS_ASSERT_EQUALS(subscribeReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted); - TS_ASSERT_EQUALS(subscribeReport->m_info.m_topicId, AckTopicId); TS_ASSERT_EQUALS(subscribeReport->m_info.m_qos, AckQos); TS_ASSERT(unitTestHasTickReq()); @@ -236,7 +234,6 @@ void UnitTestSubscribe::test3() auto subscribeReport = unitTestSubscribeCompleteReport(); TS_ASSERT_EQUALS(subscribeReport->m_status, CC_MqttsnAsyncOpStatus_Complete); TS_ASSERT_EQUALS(subscribeReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted); - TS_ASSERT_EQUALS(subscribeReport->m_info.m_topicId, TopicId); TS_ASSERT_EQUALS(subscribeReport->m_info.m_qos, AckQos); TS_ASSERT(unitTestHasTickReq()); @@ -322,7 +319,6 @@ void UnitTestSubscribe::test4() auto subscribeReport = unitTestSubscribeCompleteReport(); TS_ASSERT_EQUALS(subscribeReport->m_status, CC_MqttsnAsyncOpStatus_Complete); TS_ASSERT_EQUALS(subscribeReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted); - TS_ASSERT_EQUALS(subscribeReport->m_info.m_topicId, 0U); TS_ASSERT_EQUALS(subscribeReport->m_info.m_qos, AckQos); TS_ASSERT(unitTestHasTickReq()); @@ -514,7 +510,6 @@ void UnitTestSubscribe::test6() auto subscribeReport = unitTestSubscribeCompleteReport(); TS_ASSERT_EQUALS(subscribeReport->m_status, CC_MqttsnAsyncOpStatus_Complete); TS_ASSERT_EQUALS(subscribeReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted); - TS_ASSERT_EQUALS(subscribeReport->m_info.m_topicId, 0U); TS_ASSERT_EQUALS(subscribeReport->m_info.m_qos, AckQos); } @@ -552,7 +547,6 @@ void UnitTestSubscribe::test6() auto subscribeReport = unitTestSubscribeCompleteReport(); TS_ASSERT_EQUALS(subscribeReport->m_status, CC_MqttsnAsyncOpStatus_Complete); TS_ASSERT_EQUALS(subscribeReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted); - TS_ASSERT_EQUALS(subscribeReport->m_info.m_topicId, 0U); TS_ASSERT_EQUALS(subscribeReport->m_info.m_qos, AckQos); } @@ -591,7 +585,6 @@ void UnitTestSubscribe::test6() auto subscribeReport = unitTestSubscribeCompleteReport(); TS_ASSERT_EQUALS(subscribeReport->m_status, CC_MqttsnAsyncOpStatus_Complete); TS_ASSERT_EQUALS(subscribeReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted); - TS_ASSERT_EQUALS(subscribeReport->m_info.m_topicId, 0U); TS_ASSERT_EQUALS(subscribeReport->m_info.m_qos, AckQos); } From 74ebd4b8d0e6e428f9dfdf4458c6f5971250b86a Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 16 Jul 2024 09:05:03 +1000 Subject: [PATCH 064/106] Added client library unsubscribe unittesting. --- client/lib/include/cc_mqttsn_client/common.h | 1 - client/lib/src/op/SubscribeOp.cpp | 19 +- client/lib/src/op/UnsubscribeOp.cpp | 44 +-- client/lib/src/op/UnsubscribeOp.h | 1 - client/lib/templ/client.cpp.templ | 104 +++++++ client/lib/templ/client.h.templ | 94 ++++++ client/lib/test/CMakeLists.txt | 2 +- client/lib/test/UnitTestCommonBase.cpp | 135 ++++++++- client/lib/test/UnitTestCommonBase.h | 52 +++- client/lib/test/UnitTestDefaultBase.cpp | 16 + client/lib/test/UnitTestSubscribe.th | 67 ++++- client/lib/test/UnitTestUnsubscribe.th | 295 +++++++++++++++++++ 12 files changed, 787 insertions(+), 43 deletions(-) create mode 100644 client/lib/test/UnitTestUnsubscribe.th diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index 2461e9b8..a23a3222 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -250,7 +250,6 @@ typedef struct { const char* m_topic; ///< Subscription topic, can be NULL when pre-defined topic ID is used. CC_MqttsnTopicId m_topicId; ///< Pre-defined topic ID, should be @b 0 when topic is not NULL. - CC_MqttsnQoS m_qos; ///< Max QoS value } CC_MqttsnUnsubscribeConfig; /// @brief Callback used to request time measurement. diff --git a/client/lib/src/op/SubscribeOp.cpp b/client/lib/src/op/SubscribeOp.cpp index 26435479..9399cb90 100644 --- a/client/lib/src/op/SubscribeOp.cpp +++ b/client/lib/src/op/SubscribeOp.cpp @@ -153,16 +153,29 @@ void SubscribeOp::handle(SubackMsg& msg) auto topicId = static_cast(msg.field_topicId().value()); auto& topicStr = m_subscribeMsg.field_topicName().field().value(); + char shortTopic[sizeof(std::uint16_t) + 1] = {0}; + auto* topicPtr = topicStr.c_str(); - if (topicStr.empty()) { + do { + if (!topicStr.empty()) { + break; + } + + using TopicIdType = SubscribeMsg::Field_flags::Field_topicIdType::ValueType; + if (m_subscribeMsg.field_flags().field_topicIdType().value() == TopicIdType::ShortTopicName) { + shortTopic[0] = static_cast(m_subscribeMsg.field_topicId().field().value() >> 8U); + shortTopic[1] = static_cast(m_subscribeMsg.field_topicId().field().value() & 0xff); + topicPtr = &shortTopic[0]; + break; + } + topicPtr = nullptr; - } + } while (false); if (topicId != 0U) { storeInRegTopic(topicPtr, topicId); } - COMMS_ASSERT((topicId != 0U) || (topicPtr != nullptr)); auto& filtersMap = client().reuseState().m_subFilters; do { if (topicPtr != nullptr) { diff --git a/client/lib/src/op/UnsubscribeOp.cpp b/client/lib/src/op/UnsubscribeOp.cpp index c461160c..a907b486 100644 --- a/client/lib/src/op/UnsubscribeOp.cpp +++ b/client/lib/src/op/UnsubscribeOp.cpp @@ -59,11 +59,6 @@ CC_MqttsnErrorCode UnsubscribeOp::config(const CC_MqttsnUnsubscribeConfig* confi return CC_MqttsnErrorCode_BadParam; } - if (static_castm_qos)>(Config::MaxQos) < config->m_qos) { - errorLog("Bad subscription qos value."); - return CC_MqttsnErrorCode_BadParam; - } - if ((!emptyTopic) && (!verifySubFilter(config->m_topic))) { errorLog("Bad topic filter format in unsubscribe."); return CC_MqttsnErrorCode_BadParam; @@ -109,8 +104,6 @@ CC_MqttsnErrorCode UnsubscribeOp::config(const CC_MqttsnUnsubscribeConfig* confi } while (false); } - m_unsubscribeMsg.field_flags().field_qos().setValue(config->m_qos); - using TopicIdType = UnsubscribeMsg::Field_flags::Field_topicIdType::ValueType; if (emptyTopic) { m_unsubscribeMsg.field_topicId().field().setValue(config->m_topicId); @@ -164,29 +157,24 @@ CC_MqttsnErrorCode UnsubscribeOp::send(CC_MqttsnUnsubscribeCompleteCb cb, void* return ec; } - // Remove record on first send rather than acknowledgement allowing message + // Remove record on send attempt rather than acknowledgement allowing message // to get lost. - do { - if (m_recordRemoved) { - break; - } - - m_recordRemoved = true; - - auto& topicStr = m_unsubscribeMsg.field_topicName().field().value(); - auto* topicPtr = topicStr.c_str(); - if (m_unsubscribeMsg.field_topicName().isMissing()) { - topicPtr = nullptr; - } - auto topicId = m_unsubscribeMsg.field_topicId().field().value(); - COMMS_ASSERT(m_unsubscribeMsg.field_topicId().doesExist() || (topicId == 0U)); - COMMS_ASSERT((topicPtr == nullptr) || (topicId == 0U)); - COMMS_ASSERT((topicPtr != nullptr) || (topicId != 0U)); + auto& topicStr = m_unsubscribeMsg.field_topicName().field().value(); + auto* topicPtr = topicStr.c_str(); + if (m_unsubscribeMsg.field_topicName().isMissing()) { + topicPtr = nullptr; + } - removeInRegTopic(topicPtr, topicId); + auto topicId = m_unsubscribeMsg.field_topicId().field().value(); + COMMS_ASSERT(m_unsubscribeMsg.field_topicId().doesExist() || (topicId == 0U)); + COMMS_ASSERT((topicPtr == nullptr) || (topicId == 0U)); + COMMS_ASSERT((topicPtr != nullptr) || (topicId != 0U)); - if constexpr (Config::HasSubTopicVerification) { + removeInRegTopic(topicPtr, topicId); + + if constexpr (Config::HasSubTopicVerification) { + do { auto& filtersMap = client().reuseState().m_subFilters; if (topicPtr != nullptr) { auto iter = @@ -216,8 +204,8 @@ CC_MqttsnErrorCode UnsubscribeOp::send(CC_MqttsnUnsubscribeCompleteCb cb, void* if (iter != filtersMap.end()) { filtersMap.erase(iter); } - } - } while (false); + } while (false); + } completeOnError.release(); return CC_MqttsnErrorCode_Success; diff --git a/client/lib/src/op/UnsubscribeOp.h b/client/lib/src/op/UnsubscribeOp.h index 0d5253fd..2de880c8 100644 --- a/client/lib/src/op/UnsubscribeOp.h +++ b/client/lib/src/op/UnsubscribeOp.h @@ -56,7 +56,6 @@ class UnsubscribeOp final : public Op CC_MqttsnUnsubscribeCompleteCb m_cb = nullptr; void* m_cbData = nullptr; bool m_suspended = false; - bool m_recordRemoved = false; static_assert(ExtConfig::UnsubscribeOpTimers == 1U); }; diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index 51539650..4c355a76 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -19,6 +19,7 @@ struct CC_MqttsnSearch {}; struct CC_MqttsnConnect {}; struct CC_MqttsnDisconnect {}; struct CC_MqttsnSubscribe {}; +struct CC_MqttsnUnsubscribe {}; namespace { @@ -81,6 +82,16 @@ inline CC_MqttsnSubscribeHandle handleFromSubscribeOp(cc_mqttsn_client::op::Subs return reinterpret_cast(op); } +inline cc_mqttsn_client::op::UnsubscribeOp* unsubscribeOpFromHandle(CC_MqttsnUnsubscribeHandle handle) +{ + return reinterpret_cast(handle); +} + +inline CC_MqttsnUnsubscribeHandle handleFromUnsubscribeOp(cc_mqttsn_client::op::UnsubscribeOp* op) +{ + return reinterpret_cast(op); +} + } // namespace CC_MqttsnClientHandle cc_mqttsn_##NAME##client_alloc() @@ -802,6 +813,99 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_subscribe( return cc_mqttsn_##NAME##client_subscribe_send(subscribe, cb, cbData); } +CC_MqttsnUnsubscribeHandle cc_mqttsn_##NAME##client_unsubscribe_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec) +{ + COMMS_ASSERT(client != nullptr); + return handleFromUnsubscribeOp(clientFromHandle(client)->unsubscribePrepare(ec)); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_unsubscribe_set_retry_period(CC_MqttsnUnsubscribeHandle handle, unsigned ms) +{ + COMMS_ASSERT(handle != nullptr); + if (ms == 0U) { + unsubscribeOpFromHandle(handle)->client().errorLog("The retry period must be greater than 0"); + return CC_MqttsnErrorCode_BadParam; + } + + COMMS_ASSERT(handle != nullptr); + unsubscribeOpFromHandle(handle)->setRetryPeriod(ms); + return CC_MqttsnErrorCode_Success; +} + +unsigned cc_mqttsn_##NAME##client_unsubscribe_get_retry_period(CC_MqttsnUnsubscribeHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return unsubscribeOpFromHandle(handle)->getRetryPeriod(); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_unsubscribe_set_retry_count(CC_MqttsnUnsubscribeHandle handle, unsigned count) +{ + COMMS_ASSERT(handle != nullptr); + unsubscribeOpFromHandle(handle)->setRetryCount(count); + return CC_MqttsnErrorCode_Success; +} + +unsigned cc_mqttsn_##NAME##client_unsubscribe_get_retry_count(CC_MqttsnUnsubscribeHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return unsubscribeOpFromHandle(handle)->getRetryCount(); +} + +void cc_mqttsn_##NAME##client_unsubscribe_init_config(CC_MqttsnUnsubscribeConfig* config) +{ + COMMS_ASSERT(config != nullptr); + *config = CC_MqttsnUnsubscribeConfig(); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_unsubscribe_config(CC_MqttsnUnsubscribeHandle handle, const CC_MqttsnUnsubscribeConfig* config) +{ + COMMS_ASSERT(handle != nullptr); + return unsubscribeOpFromHandle(handle)->config(config); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_unsubscribe_send(CC_MqttsnUnsubscribeHandle handle, CC_MqttsnUnsubscribeCompleteCb cb, void* cbData) +{ + COMMS_ASSERT(handle != nullptr); + return unsubscribeOpFromHandle(handle)->send(cb, cbData); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_unsubscribe_cancel(CC_MqttsnUnsubscribeHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return unsubscribeOpFromHandle(handle)->cancel(); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_unsubscribe( + CC_MqttsnClientHandle client, + const CC_MqttsnUnsubscribeConfig* config, + CC_MqttsnUnsubscribeCompleteCb cb, + void* cbData) +{ + auto ec = CC_MqttsnErrorCode_Success; + auto unsubscribe = cc_mqttsn_##NAME##client_unsubscribe_prepare(client, &ec); + if (unsubscribe == nullptr) { + return ec; + } + + auto cancelOnExitGuard = + comms::util::makeScopeGuard( + [unsubscribe]() + { + [[maybe_unused]] auto ecTmp = cc_mqttsn_##NAME##client_unsubscribe_cancel(unsubscribe); + }); + + if (config != nullptr) { + ec = cc_mqttsn_##NAME##client_unsubscribe_config(unsubscribe, config); + if (ec != CC_MqttsnErrorCode_Success) { + return ec; + } + } + + cancelOnExitGuard.release(); + return cc_mqttsn_##NAME##client_unsubscribe_send(unsubscribe, cb, cbData); +} + + // --------------------- Callbacks --------------------- void cc_mqttsn_##NAME##client_set_next_tick_program_callback( diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index d0678109..693f4520 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -233,6 +233,12 @@ bool cc_mqttsn_##NAME##client_get_verify_incoming_topic_enabled(CC_MqttsnClientH /// @ingroup client CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_verify_incoming_msg_subscribed(CC_MqttsnClientHandle client, bool enabled); +/// @brief Retrieve current incoming message being correctly subscribed control. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @return @b true when enabled, @b false when disabled +/// @ingroup client +bool cc_mqttsn_##NAME##client_get_verify_incoming_msg_subscribed(CC_MqttsnClientHandle handle); + /// @brief Prepare "search" operation. /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[out] ec Error code reporting result of the operation. Can be NULL. @@ -584,6 +590,94 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_subscribe( CC_MqttsnSubscribeCompleteCb cb, void* cbData); +/// @brief Prepare "unsubscribe" operation. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[out] ec Error code reporting result of the operation. Can be NULL. +/// @return Handle of the "unsubscribe" operation, will be NULL in case of failure. To analyze the reason failure use "ec" output parameter. +/// @post The "unsubscribe" operation is allocated, use either @ref cc_mqttsn_##NAME##client_unsubscribe_send() +/// or @ref cc_mqttsn_##NAME##client_unsubscribe_cancel() to prevent memory leaks. +/// @ingroup unsubscribe +CC_MqttsnUnsubscribeHandle cc_mqttsn_##NAME##client_unsubscribe_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec); + +/// @brief Configure the retry period for the "unsubscribe" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_unsubscribe_prepare() function. +/// @param[in] ms Retry period in @b milliseconds. +/// @return Result code of the call. +/// @ingroup unsubscribe +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_unsubscribe_set_retry_period(CC_MqttsnUnsubscribeHandle handle, unsigned ms); + +/// @brief Retrieve the configured retry period for the "unsubscribe" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_unsubscribe_prepare() function. +/// @return Retry period duration in @b milliseconds. +/// @ingroup unsubscribe +unsigned cc_mqttsn_##NAME##client_unsubscribe_get_retry_period(CC_MqttsnUnsubscribeHandle handle); + +/// @brief Configure the retry count for the "unsubscribe" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_unsubscribe_prepare() function. +/// @param[in] count Number of retries. +/// @return Result code of the call. +/// @ingroup unsubscribe +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_unsubscribe_set_retry_count(CC_MqttsnUnsubscribeHandle handle, unsigned count); + +/// @brief Retrieve the configured retry count for the "unsubscribe" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_unsubscribe_prepare() function. +/// @return Number of retries. +/// @ingroup unsubscribe +unsigned cc_mqttsn_##NAME##client_unsubscribe_get_retry_count(CC_MqttsnUnsubscribeHandle handle); + +/// @brief Intialize the @ref CC_MqttsnUnsubscribeConfig configuration structure. +/// @param[out] config Configuration structure. Must not be NULL. +/// @ingroup unsubscribe +void cc_mqttsn_##NAME##client_unsubscribe_init_config(CC_MqttsnUnsubscribeConfig* config); + +/// @brief Perform configuration of the "unsubscribe" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_unsubscribe_prepare() function. +/// @param[in] config Configuration structure. Must NOT be NULL. Does not need to be preserved after invocation. +/// @return Result code of the call. +/// @ingroup unsubscribe +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_unsubscribe_config(CC_MqttsnUnsubscribeHandle handle, const CC_MqttsnUnsubscribeConfig* config); + +/// @brief Send the "unsubscribe" operation +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_unsubscribe_prepare() function. +/// @param[in] cb Callback to be invoked when "unsubscribe" operation is complete. +/// @param[in] cbData Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +/// @return Result code of the call. +/// @post The handle of the "unsubscribe" operation can be discarded. +/// @post The provided callback will be invoked when the "unsubscribe" operation is complete if and only if +/// the function returns @ref CC_MqttsnErrorCode_Success. +/// @ingroup unsubscribe +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_unsubscribe_send(CC_MqttsnUnsubscribeHandle handle, CC_MqttsnUnsubscribeCompleteCb cb, void* cbData); + +/// @brief Cancel the allocated "unsubscribe" operation +/// @details In case the @ref cc_mqttsn_##NAME##client_unsubscribe_send() function was successfully called before, +/// the operation is cancelled @b without callback invocation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_unsubscribe_prepare() function. +/// @return Result code of the call. +/// @post The handle of the "unsubscribe" operation is no longer valid and must be discarded. +/// @ingroup unsubscribe +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_unsubscribe_cancel(CC_MqttsnUnsubscribeHandle handle); + +/// @brief Prepare and send "unsubscribe" request in one go +/// @details Abstracts away sequence of the following functions invocation: +/// @li @ref cc_mqttsn_##NAME##client_unsubscribe_prepare() +/// @li @ref cc_mqttsn_##NAME##client_unsubscribe_config() +/// @li @ref cc_mqttsn_##NAME##client_unsubscribe_send() +/// +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[in] config Subscription configuration. +/// @param[in] cb Callback to be invoked when "unsubscribe" operation is complete. +/// @param[in] cbData Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +/// @return Result code of the call. +/// @ingroup unsubscribe +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_unsubscribe( + CC_MqttsnClientHandle client, + const CC_MqttsnUnsubscribeConfig* config, + CC_MqttsnUnsubscribeCompleteCb cb, + void* cbData); + + // --------------------- Callbacks --------------------- /// @brief Set callback to call when time measurement is required. diff --git a/client/lib/test/CMakeLists.txt b/client/lib/test/CMakeLists.txt index 1f15ad58..1d351ce5 100644 --- a/client/lib/test/CMakeLists.txt +++ b/client/lib/test/CMakeLists.txt @@ -45,7 +45,7 @@ if (TARGET cc::cc_mqttsn_client) # cc_mqttsn_client_add_unit_test(UnitTestPublish ${DEFAULT_BASE_LIB_NAME}) # cc_mqttsn_client_add_unit_test(UnitTestReceive ${DEFAULT_BASE_LIB_NAME}) cc_mqttsn_client_add_unit_test(UnitTestSubscribe ${DEFAULT_BASE_LIB_NAME}) -# cc_mqttsn_client_add_unit_test(UnitTestUnsubscribe ${DEFAULT_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(UnitTestUnsubscribe ${DEFAULT_BASE_LIB_NAME}) endif () # TODO diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index 6e627542..f9a32f43 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -44,6 +44,12 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_get_default_gw_adv_duration != nullptr); test_assert(m_funcs.m_set_allowed_adv_losses != nullptr); test_assert(m_funcs.m_get_allowed_adv_losses != nullptr); + test_assert(m_funcs.m_set_verify_outgoing_topic_enabled != nullptr); + test_assert(m_funcs.m_get_verify_outgoing_topic_enabled != nullptr); + test_assert(m_funcs.m_set_verify_incoming_topic_enabled != nullptr); + test_assert(m_funcs.m_get_verify_incoming_topic_enabled != nullptr); + test_assert(m_funcs.m_set_verify_incoming_msg_subscribed != nullptr); + test_assert(m_funcs.m_get_verify_incoming_msg_subscribed != nullptr); test_assert(m_funcs.m_search_prepare != nullptr); test_assert(m_funcs.m_search_set_retry_period != nullptr); test_assert(m_funcs.m_search_get_retry_period != nullptr); @@ -84,7 +90,17 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_subscribe_config != nullptr); test_assert(m_funcs.m_subscribe_send != nullptr); test_assert(m_funcs.m_subscribe_cancel != nullptr); - test_assert(m_funcs.m_subscribe != nullptr); + test_assert(m_funcs.m_subscribe != nullptr); + test_assert(m_funcs.m_unsubscribe_prepare != nullptr); + test_assert(m_funcs.m_unsubscribe_set_retry_period != nullptr); + test_assert(m_funcs.m_unsubscribe_get_retry_period != nullptr); + test_assert(m_funcs.m_unsubscribe_set_retry_count != nullptr); + test_assert(m_funcs.m_unsubscribe_get_retry_count != nullptr); + test_assert(m_funcs.m_unsubscribe_init_config != nullptr); + test_assert(m_funcs.m_unsubscribe_config != nullptr); + test_assert(m_funcs.m_unsubscribe_send != nullptr); + test_assert(m_funcs.m_unsubscribe_cancel != nullptr); + test_assert(m_funcs.m_unsubscribe != nullptr); test_assert(m_funcs.m_set_next_tick_program_callback != nullptr); test_assert(m_funcs.m_set_cancel_next_tick_wait_callback != nullptr); @@ -166,6 +182,11 @@ UnitTestCommonBase::UnitTestSubscribeCompleteReport::UnitTestSubscribeCompleteRe } } +UnitTestCommonBase::UnitTestUnsubscribeCompleteReport::UnitTestUnsubscribeCompleteReport(CC_MqttsnUnsubscribeHandle handle, CC_MqttsnAsyncOpStatus status) : + m_handle(handle), + m_status(status) +{ +} void UnitTestCommonBase::unitTestSetUp() { @@ -546,6 +567,77 @@ CC_MqttsnErrorCode UnitTestCommonBase::unitTestSubscribeSend(CC_MqttsnSubscribeH return m_funcs.m_subscribe_send(subscribe, &UnitTestCommonBase::unitTestSubscribeCompleteCb, this); } +void UnitTestCommonBase::unitTestDoSubscribe(CC_MqttsnClient* client, const CC_MqttsnSubscribeConfig* config) +{ + auto ec = m_funcs.m_subscribe(client, config, &UnitTestCommonBase::unitTestSubscribeCompleteCb, this); + test_assert(ec == CC_MqttsnErrorCode_Success); + + auto subMsgId = 0U; + test_assert(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* subscribeMsg = dynamic_cast(sentMsg.get()); + test_assert(subscribeMsg != nullptr); + test_assert(static_cast(subscribeMsg->field_flags().field_qos().value()) == config->m_qos); + test_assert(!unitTestHasOutputData()); + subMsgId = subscribeMsg->field_msgId().value(); + test_assert(subMsgId != 0U); + + test_assert(unitTestHasTickReq()); + unitTestTick(client, 100); + + UnitTestSubackMsg subackMsg; + subackMsg.field_flags().field_qos().setValue(config->m_qos); + subackMsg.field_msgId().setValue(subMsgId); + subackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, subackMsg); + + test_assert(unitTestHasSubscribeCompleteReport()); + auto subscribeReport = unitTestSubscribeCompleteReport(); + test_assert(subscribeReport->m_status == CC_MqttsnAsyncOpStatus_Complete); + test_assert(subscribeReport->m_info.m_returnCode == CC_MqttsnReturnCode_Accepted); + test_assert(subscribeReport->m_info.m_qos == config->m_qos); +} + +void UnitTestCommonBase::unitTestDoSubscribeTopic(CC_MqttsnClient* client, const std::string& topic, CC_MqttsnQoS qos) +{ + CC_MqttsnSubscribeConfig config; + m_funcs.m_subscribe_init_config(&config); + config.m_topic = topic.c_str(); + config.m_qos = qos; + unitTestDoSubscribe(client, &config); +} + +void UnitTestCommonBase::unitTestDoSubscribeTopicId(CC_MqttsnClient* client, CC_MqttsnTopicId topicId, CC_MqttsnQoS qos) +{ + CC_MqttsnSubscribeConfig config; + m_funcs.m_subscribe_init_config(&config); + config.m_topicId = topicId; + config.m_qos = qos; + unitTestDoSubscribe(client, &config); +} + +bool UnitTestCommonBase::unitTestHasUnsubscribeCompleteReport() const +{ + return !m_data.m_unsubscribeCompleteReports.empty(); +} + +UnitTestCommonBase::UnitTestUnsubscribeCompleteReportPtr UnitTestCommonBase::unitTestUnsubscribeCompleteReport(bool mustExist) +{ + if (!unitTestHasUnsubscribeCompleteReport()) { + test_assert(!mustExist); + return UnitTestUnsubscribeCompleteReportPtr(); + } + + auto ptr = std::move(m_data.m_unsubscribeCompleteReports.front()); + m_data.m_unsubscribeCompleteReports.pop_front(); + return ptr; +} + +CC_MqttsnErrorCode UnitTestCommonBase::unitTestUnsubscribeSend(CC_MqttsnUnsubscribeHandle unsubscribe) +{ + return m_funcs.m_unsubscribe_send(unsubscribe, &UnitTestCommonBase::unitTestUnsubscribeCompleteCb, this); +} + void UnitTestCommonBase::apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen) { m_funcs.m_process_data(client, buf, bufLen); @@ -561,6 +653,11 @@ CC_MqttsnErrorCode UnitTestCommonBase::apiSetDefaultRetryCount(CC_MqttsnClient* return m_funcs.m_set_default_retry_count(client, value); } +CC_MqttsnErrorCode UnitTestCommonBase::apiSetVerifyIncomingMsgSubscribed(CC_MqttsnClient* client, bool enabled) +{ + return m_funcs.m_set_verify_incoming_msg_subscribed(client, enabled); +} + CC_MqttsnSearchHandle UnitTestCommonBase::apiSearchPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec) { return m_funcs.m_search_prepare(client, ec); @@ -684,6 +781,36 @@ CC_MqttsnErrorCode UnitTestCommonBase::apiSubscribeConfig(CC_MqttsnSubscribeHand return m_funcs.m_subscribe_config(subscribe, config); } +CC_MqttsnErrorCode UnitTestCommonBase::apiSubscribeCancel(CC_MqttsnSubscribeHandle subscribe) +{ + return m_funcs.m_subscribe_cancel(subscribe); +} + +CC_MqttsnUnsubscribeHandle UnitTestCommonBase::apiUnsubscribePrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec) +{ + return m_funcs.m_unsubscribe_prepare(client, ec); +} + +CC_MqttsnErrorCode UnitTestCommonBase::apiUnsubscribeSetRetryCount(CC_MqttsnUnsubscribeHandle unsubscribe, unsigned count) +{ + return m_funcs.m_unsubscribe_set_retry_count(unsubscribe, count); +} + +void UnitTestCommonBase::apiUnsubscribeInitConfig(CC_MqttsnUnsubscribeConfig* config) +{ + m_funcs.m_unsubscribe_init_config(config); +} + +CC_MqttsnErrorCode UnitTestCommonBase::apiUnsubscribeConfig(CC_MqttsnUnsubscribeHandle unsubscribe, const CC_MqttsnUnsubscribeConfig* config) +{ + return m_funcs.m_unsubscribe_config(unsubscribe, config); +} + +CC_MqttsnErrorCode UnitTestCommonBase::apiUnsubscribeCancel(CC_MqttsnUnsubscribeHandle unsubscribe) +{ + return m_funcs.m_unsubscribe_cancel(unsubscribe); +} + void UnitTestCommonBase::unitTestMessageReportCb(void* data, const CC_MqttsnMessageInfo* msgInfo) { // TODO: @@ -744,4 +871,10 @@ void UnitTestCommonBase::unitTestSubscribeCompleteCb(void* data, CC_MqttsnSubscr test_assert((status != CC_MqttsnAsyncOpStatus_Complete) || (info != nullptr)); auto* thisPtr = asThis(data); thisPtr->m_data.m_subscribeCompleteReports.push_back(std::make_unique(handle, status, info)); +} + +void UnitTestCommonBase::unitTestUnsubscribeCompleteCb(void* data, CC_MqttsnUnsubscribeHandle handle, CC_MqttsnAsyncOpStatus status) +{ + auto* thisPtr = asThis(data); + thisPtr->m_data.m_unsubscribeCompleteReports.push_back(std::make_unique(handle, status)); } \ No newline at end of file diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index c26f936c..39e4e114 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -34,6 +34,12 @@ class UnitTestCommonBase unsigned (*m_get_default_gw_adv_duration)(CC_MqttsnClientHandle) = nullptr; CC_MqttsnErrorCode (*m_set_allowed_adv_losses)(CC_MqttsnClientHandle, unsigned) = nullptr; unsigned (*m_get_allowed_adv_losses)(CC_MqttsnClientHandle) = nullptr; + CC_MqttsnErrorCode (*m_set_verify_outgoing_topic_enabled)(CC_MqttsnClientHandle, bool) = nullptr; + bool (*m_get_verify_outgoing_topic_enabled)(CC_MqttsnClientHandle) = nullptr; + CC_MqttsnErrorCode (*m_set_verify_incoming_topic_enabled)(CC_MqttsnClientHandle, bool) = nullptr; + bool (*m_get_verify_incoming_topic_enabled)(CC_MqttsnClientHandle) = nullptr; + CC_MqttsnErrorCode (*m_set_verify_incoming_msg_subscribed)(CC_MqttsnClientHandle, bool) = nullptr; + bool (*m_get_verify_incoming_msg_subscribed)(CC_MqttsnClientHandle) = nullptr; CC_MqttsnSearchHandle (*m_search_prepare)(CC_MqttsnClientHandle, CC_MqttsnErrorCode*) = nullptr; CC_MqttsnErrorCode (*m_search_set_retry_period)(CC_MqttsnSearchHandle, unsigned) = nullptr; unsigned (*m_search_get_retry_period)(CC_MqttsnSearchHandle) = nullptr; @@ -75,6 +81,17 @@ class UnitTestCommonBase CC_MqttsnErrorCode (*m_subscribe_send)(CC_MqttsnSubscribeHandle, CC_MqttsnSubscribeCompleteCb, void*) = nullptr; CC_MqttsnErrorCode (*m_subscribe_cancel)(CC_MqttsnSubscribeHandle) = nullptr; CC_MqttsnErrorCode (*m_subscribe)(CC_MqttsnClientHandle, const CC_MqttsnSubscribeConfig*, CC_MqttsnSubscribeCompleteCb, void* cbData) = nullptr; + CC_MqttsnUnsubscribeHandle (*m_unsubscribe_prepare)(CC_MqttsnClientHandle, CC_MqttsnErrorCode*) = nullptr; + CC_MqttsnErrorCode (*m_unsubscribe_set_retry_period)(CC_MqttsnUnsubscribeHandle, unsigned) = nullptr; + unsigned (*m_unsubscribe_get_retry_period)(CC_MqttsnUnsubscribeHandle) = nullptr; + CC_MqttsnErrorCode (*m_unsubscribe_set_retry_count)(CC_MqttsnUnsubscribeHandle, unsigned) = nullptr; + unsigned (*m_unsubscribe_get_retry_count)(CC_MqttsnUnsubscribeHandle) = nullptr; + void (*m_unsubscribe_init_config)(CC_MqttsnUnsubscribeConfig*) = nullptr; + CC_MqttsnErrorCode (*m_unsubscribe_config)(CC_MqttsnUnsubscribeHandle, const CC_MqttsnUnsubscribeConfig*) = nullptr; + CC_MqttsnErrorCode (*m_unsubscribe_send)(CC_MqttsnUnsubscribeHandle, CC_MqttsnUnsubscribeCompleteCb, void*) = nullptr; + CC_MqttsnErrorCode (*m_unsubscribe_cancel)(CC_MqttsnUnsubscribeHandle) = nullptr; + CC_MqttsnErrorCode (*m_unsubscribe)(CC_MqttsnClientHandle, const CC_MqttsnUnsubscribeConfig*, CC_MqttsnUnsubscribeCompleteCb, void* cbData) = nullptr; + void (*m_set_next_tick_program_callback)(CC_MqttsnClientHandle, CC_MqttsnNextTickProgramCb, void*) = nullptr; void (*m_set_cancel_next_tick_wait_callback)(CC_MqttsnClientHandle, CC_MqttsnCancelNextTickWaitCb, void*) = nullptr; @@ -225,7 +242,21 @@ class UnitTestCommonBase }; using UnitTestSubscribeCompleteReportPtr = std::unique_ptr; - using UnitTestSubscribeCompleteReportList = std::list; + using UnitTestSubscribeCompleteReportList = std::list; + + + struct UnitTestUnsubscribeCompleteReport + { + CC_MqttsnUnsubscribeHandle m_handle = nullptr; + CC_MqttsnAsyncOpStatus m_status = CC_MqttsnAsyncOpStatus_ValuesLimit; + + UnitTestUnsubscribeCompleteReport(CC_MqttsnUnsubscribeHandle handle, CC_MqttsnAsyncOpStatus status); + UnitTestUnsubscribeCompleteReport(UnitTestUnsubscribeCompleteReport&&) = default; + UnitTestUnsubscribeCompleteReport& operator=(const UnitTestUnsubscribeCompleteReport&) = default; + }; + + using UnitTestUnsubscribeCompleteReportPtr = std::unique_ptr; + using UnitTestUnsubscribeCompleteReportList = std::list; using UnitTestClientPtr = std::unique_ptr; @@ -275,10 +306,20 @@ class UnitTestCommonBase UnitTestSubscribeCompleteReportPtr unitTestSubscribeCompleteReport(bool mustExist = true); CC_MqttsnErrorCode unitTestSubscribeSend(CC_MqttsnSubscribeHandle subscribe); + void unitTestDoSubscribe(CC_MqttsnClient* client, const CC_MqttsnSubscribeConfig* config); + void unitTestDoSubscribeTopic(CC_MqttsnClient* client, const std::string& topic, CC_MqttsnQoS qos = CC_MqttsnQoS_ExactlyOnceDelivery); + void unitTestDoSubscribeTopicId(CC_MqttsnClient* client, CC_MqttsnTopicId topicId, CC_MqttsnQoS qos = CC_MqttsnQoS_ExactlyOnceDelivery); + + bool unitTestHasUnsubscribeCompleteReport() const; + UnitTestUnsubscribeCompleteReportPtr unitTestUnsubscribeCompleteReport(bool mustExist = true); + + CC_MqttsnErrorCode unitTestUnsubscribeSend(CC_MqttsnUnsubscribeHandle unsubscribe); void apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen); CC_MqttsnErrorCode apiSetDefaultRetryPeriod(CC_MqttsnClient* client, unsigned value); CC_MqttsnErrorCode apiSetDefaultRetryCount(CC_MqttsnClient* client, unsigned value); + CC_MqttsnErrorCode apiSetVerifyIncomingMsgSubscribed(CC_MqttsnClient* client, bool enabled); + CC_MqttsnSearchHandle apiSearchPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec = nullptr); CC_MqttsnErrorCode apiSearchSetRetryPeriod(CC_MqttsnSearchHandle search, unsigned value); CC_MqttsnErrorCode apiSearchSetRetryCount(CC_MqttsnSearchHandle search, unsigned value); @@ -299,6 +340,13 @@ class UnitTestCommonBase CC_MqttsnErrorCode apiSubscribeSetRetryCount(CC_MqttsnSubscribeHandle subscribe, unsigned count); void apiSubscribeInitConfig(CC_MqttsnSubscribeConfig* config); CC_MqttsnErrorCode apiSubscribeConfig(CC_MqttsnSubscribeHandle subscribe, const CC_MqttsnSubscribeConfig* config); + CC_MqttsnErrorCode apiSubscribeCancel(CC_MqttsnSubscribeHandle subscribe); + + CC_MqttsnUnsubscribeHandle apiUnsubscribePrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec = nullptr); + CC_MqttsnErrorCode apiUnsubscribeSetRetryCount(CC_MqttsnUnsubscribeHandle unsubscribe, unsigned count); + void apiUnsubscribeInitConfig(CC_MqttsnUnsubscribeConfig* config); + CC_MqttsnErrorCode apiUnsubscribeConfig(CC_MqttsnUnsubscribeHandle unsubscribe, const CC_MqttsnUnsubscribeConfig* config); + CC_MqttsnErrorCode apiUnsubscribeCancel(CC_MqttsnUnsubscribeHandle unsubscribe); protected: @@ -316,6 +364,7 @@ class UnitTestCommonBase UnitTestConnectCompleteReportList m_connectCompleteReports; UnitTestDisconnectCompleteReportList m_disconnectCompleteReports; UnitTestSubscribeCompleteReportList m_subscribeCompleteReports; + UnitTestUnsubscribeCompleteReportList m_unsubscribeCompleteReports; }; static void unitTestTickProgramCb(void* data, unsigned duration); @@ -330,6 +379,7 @@ class UnitTestCommonBase static void unitTestConnectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info); static void unitTestDisconnectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status); static void unitTestSubscribeCompleteCb(void* data, CC_MqttsnSubscribeHandle handle, CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info); + static void unitTestUnsubscribeCompleteCb(void* data, CC_MqttsnUnsubscribeHandle handle, CC_MqttsnAsyncOpStatus status); LibFuncs m_funcs; ClientData m_data; diff --git a/client/lib/test/UnitTestDefaultBase.cpp b/client/lib/test/UnitTestDefaultBase.cpp index e0584cda..7ba6601e 100644 --- a/client/lib/test/UnitTestDefaultBase.cpp +++ b/client/lib/test/UnitTestDefaultBase.cpp @@ -25,6 +25,12 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_get_default_gw_adv_duration = &cc_mqttsn_client_get_default_gw_adv_duration; funcs.m_set_allowed_adv_losses = &cc_mqttsn_client_set_allowed_adv_losses; funcs.m_get_allowed_adv_losses = &cc_mqttsn_client_get_allowed_adv_losses; + funcs.m_set_verify_outgoing_topic_enabled = &cc_mqttsn_client_set_verify_outgoing_topic_enabled; + funcs.m_get_verify_outgoing_topic_enabled = &cc_mqttsn_client_get_verify_outgoing_topic_enabled; + funcs.m_set_verify_incoming_topic_enabled = &cc_mqttsn_client_set_verify_incoming_topic_enabled; + funcs.m_get_verify_incoming_topic_enabled = &cc_mqttsn_client_get_verify_incoming_topic_enabled; + funcs.m_set_verify_incoming_msg_subscribed = &cc_mqttsn_client_set_verify_incoming_msg_subscribed; + funcs.m_get_verify_incoming_msg_subscribed = &cc_mqttsn_client_get_verify_incoming_msg_subscribed; funcs.m_search_prepare = &cc_mqttsn_client_search_prepare; funcs.m_search_set_retry_period = &cc_mqttsn_client_search_set_retry_period; funcs.m_search_get_retry_period = &cc_mqttsn_client_search_get_retry_period; @@ -66,6 +72,16 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_subscribe_send = &cc_mqttsn_client_subscribe_send; funcs.m_subscribe_cancel = &cc_mqttsn_client_subscribe_cancel; funcs.m_subscribe = &cc_mqttsn_client_subscribe; + funcs.m_unsubscribe_prepare = &cc_mqttsn_client_unsubscribe_prepare; + funcs.m_unsubscribe_set_retry_period = &cc_mqttsn_client_unsubscribe_set_retry_period; + funcs.m_unsubscribe_get_retry_period = &cc_mqttsn_client_unsubscribe_get_retry_period; + funcs.m_unsubscribe_set_retry_count = &cc_mqttsn_client_unsubscribe_set_retry_count; + funcs.m_unsubscribe_get_retry_count = &cc_mqttsn_client_unsubscribe_get_retry_count; + funcs.m_unsubscribe_init_config = &cc_mqttsn_client_unsubscribe_init_config; + funcs.m_unsubscribe_config = &cc_mqttsn_client_unsubscribe_config; + funcs.m_unsubscribe_send = &cc_mqttsn_client_unsubscribe_send; + funcs.m_unsubscribe_cancel = &cc_mqttsn_client_unsubscribe_cancel; + funcs.m_unsubscribe = &cc_mqttsn_client_unsubscribe; funcs.m_set_next_tick_program_callback = &cc_mqttsn_client_set_next_tick_program_callback; funcs.m_set_cancel_next_tick_wait_callback = &cc_mqttsn_client_set_cancel_next_tick_wait_callback; diff --git a/client/lib/test/UnitTestSubscribe.th b/client/lib/test/UnitTestSubscribe.th index ccc6ad2c..9232b666 100644 --- a/client/lib/test/UnitTestSubscribe.th +++ b/client/lib/test/UnitTestSubscribe.th @@ -14,6 +14,7 @@ public: void test4(); void test5(); void test6(); + void test7(); private: virtual void setUp() override @@ -75,7 +76,7 @@ void UnitTestSubscribe::test1() TS_ASSERT_DIFFERS(subMsgId, 0U); } - TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(!unitTestHasSubscribeCompleteReport()); TS_ASSERT(unitTestHasTickReq()); unitTestTick(client, 100); @@ -146,7 +147,7 @@ void UnitTestSubscribe::test2() TS_ASSERT_DIFFERS(subMsgId, 0U); } - TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(!unitTestHasSubscribeCompleteReport()); TS_ASSERT(unitTestHasTickReq()); unitTestTick(client, 100); @@ -216,7 +217,7 @@ void UnitTestSubscribe::test3() TS_ASSERT_DIFFERS(subMsgId, 0U); } - TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(!unitTestHasSubscribeCompleteReport()); TS_ASSERT(unitTestHasTickReq()); unitTestTick(client, 100); @@ -285,7 +286,7 @@ void UnitTestSubscribe::test4() TS_ASSERT_DIFFERS(subMsgId, 0U); } - TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(!unitTestHasSubscribeCompleteReport()); TS_ASSERT(unitTestHasTickReq()); unitTestTick(client); // timeout @@ -372,7 +373,7 @@ void UnitTestSubscribe::test5() TS_ASSERT_DIFFERS(subMsgId, 0U); } - TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(!unitTestHasSubscribeCompleteReport()); TS_ASSERT(unitTestHasTickReq()); unitTestTick(client); // timeout @@ -450,7 +451,7 @@ void UnitTestSubscribe::test6() TS_ASSERT_DIFFERS(subMsgId1, 0U); } - TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(!unitTestHasSubscribeCompleteReport()); TS_ASSERT(unitTestHasTickReq()); unitTestTick(client, 10); @@ -589,4 +590,56 @@ void UnitTestSubscribe::test6() } TS_ASSERT(unitTestHasTickReq()); -} \ No newline at end of file +} + +void UnitTestSubscribe::test7() +{ + // Testing invalid topics subscribe + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + auto subscribe = apiSubscribePrepare(client); + TS_ASSERT_DIFFERS(subscribe, nullptr); + + CC_MqttsnSubscribeConfig config; + apiSubscribeInitConfig(&config); + TS_ASSERT_EQUALS(config.m_qos, CC_MqttsnQoS_ExactlyOnceDelivery); + + config.m_topic = "++"; + auto ec = apiSubscribeConfig(subscribe, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_BadParam); + + config.m_topic = "#+"; + ec = apiSubscribeConfig(subscribe, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_BadParam); + + config.m_topic = "#/hello"; + ec = apiSubscribeConfig(subscribe, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_BadParam); + + config.m_topic = "hello+/bla"; + ec = apiSubscribeConfig(subscribe, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_BadParam); + + config.m_topic = "#hello"; + ec = apiSubscribeConfig(subscribe, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_BadParam); + + config.m_topic = "+hello"; + ec = apiSubscribeConfig(subscribe, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_BadParam); + + config.m_topic = ""; + ec = apiSubscribeConfig(subscribe, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_BadParam); + + ec = apiSubscribeCancel(subscribe); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); +} diff --git a/client/lib/test/UnitTestUnsubscribe.th b/client/lib/test/UnitTestUnsubscribe.th new file mode 100644 index 00000000..040d9bfb --- /dev/null +++ b/client/lib/test/UnitTestUnsubscribe.th @@ -0,0 +1,295 @@ +#include "UnitTestDefaultBase.h" +#include "UnitTestProtocolDefs.h" + +#include "comms/units.h" + +#include + +class UnitTestUnsubscribe : public CxxTest::TestSuite, public UnitTestDefaultBase +{ +public: + void test1(); + void test2(); + void test3(); + void test4(); + +private: + virtual void setUp() override + { + unitTestSetUp(); + } + + virtual void tearDown() override + { + unitTestTearDown(); + } + + using TopicIdType = UnitTestUnsubscribeMsg::Field_flags::Field_topicIdType::ValueType; +}; + +void UnitTestUnsubscribe::test1() +{ + // Testing basic topic unsubscribe + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic("#"); + + unitTestDoSubscribeTopic(client, Topic); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + auto unsubscribe = apiUnsubscribePrepare(client); + TS_ASSERT_DIFFERS(unsubscribe, nullptr); + + CC_MqttsnUnsubscribeConfig config; + apiUnsubscribeInitConfig(&config); + config.m_topic = Topic.c_str(); + + auto ec = apiUnsubscribeConfig(unsubscribe, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestUnsubscribeSend(unsubscribe); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + auto unsubMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* unsubscribeMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(unsubscribeMsg, nullptr); + TS_ASSERT_EQUALS(unsubscribeMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT(unsubscribeMsg->field_topicId().isMissing()); + TS_ASSERT(unsubscribeMsg->field_topicName().doesExist()); + TS_ASSERT_EQUALS(unsubscribeMsg->field_topicName().field().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + unsubMsgId = unsubscribeMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(unsubMsgId, 0U); + } + + TS_ASSERT(!unitTestHasUnsubscribeCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestUnsubackMsg unsubackMsg; + unsubackMsg.field_msgId().setValue(unsubMsgId); + unitTestClientInputMessage(client, unsubackMsg); + } + + TS_ASSERT(unitTestHasUnsubscribeCompleteReport()); + auto unsubscribeReport = unitTestUnsubscribeCompleteReport(); + TS_ASSERT_EQUALS(unsubscribeReport->m_handle, unsubscribe); + TS_ASSERT_EQUALS(unsubscribeReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestUnsubscribe::test2() +{ + // Testing basic topic id unsubscribe + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const CC_MqttsnTopicId TopicId = 123; + + unitTestDoSubscribeTopicId(client, TopicId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + auto unsubscribe = apiUnsubscribePrepare(client); + TS_ASSERT_DIFFERS(unsubscribe, nullptr); + + CC_MqttsnUnsubscribeConfig config; + apiUnsubscribeInitConfig(&config); + config.m_topicId = TopicId; + + auto ec = apiUnsubscribeConfig(unsubscribe, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestUnsubscribeSend(unsubscribe); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + auto unsubMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* unsubscribeMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(unsubscribeMsg, nullptr); + TS_ASSERT_EQUALS(unsubscribeMsg->field_flags().field_topicIdType().value(), TopicIdType::PredefinedTopicId); + TS_ASSERT(unsubscribeMsg->field_topicId().doesExist()); + TS_ASSERT(unsubscribeMsg->field_topicName().isMissing()); + TS_ASSERT_EQUALS(unsubscribeMsg->field_topicId().field().value(), TopicId); + TS_ASSERT(!unitTestHasOutputData()); + unsubMsgId = unsubscribeMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(unsubMsgId, 0U); + } + + TS_ASSERT(!unitTestHasUnsubscribeCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestUnsubackMsg unsubackMsg; + unsubackMsg.field_msgId().setValue(unsubMsgId); + unitTestClientInputMessage(client, unsubackMsg); + } + + TS_ASSERT(unitTestHasUnsubscribeCompleteReport()); + auto unsubscribeReport = unitTestUnsubscribeCompleteReport(); + TS_ASSERT_EQUALS(unsubscribeReport->m_handle, unsubscribe); + TS_ASSERT_EQUALS(unsubscribeReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestUnsubscribe::test3() +{ + // Testing short topic name unsubscribe + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic("ab"); + + unitTestDoSubscribeTopic(client, Topic); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + auto unsubscribe = apiUnsubscribePrepare(client); + TS_ASSERT_DIFFERS(unsubscribe, nullptr); + + CC_MqttsnUnsubscribeConfig config; + apiUnsubscribeInitConfig(&config); + config.m_topic = Topic.c_str(); + + auto ec = apiUnsubscribeConfig(unsubscribe, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestUnsubscribeSend(unsubscribe); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + auto unsubMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* unsubscribeMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(unsubscribeMsg, nullptr); + TS_ASSERT_EQUALS(unsubscribeMsg->field_flags().field_topicIdType().value(), TopicIdType::ShortTopicName); + TS_ASSERT(unsubscribeMsg->field_topicId().doesExist()); + TS_ASSERT(unsubscribeMsg->field_topicName().isMissing()); + auto topicId = unsubscribeMsg->field_topicId().field().value(); + TS_ASSERT_EQUALS((topicId >> 8U) & 0xff, static_cast(Topic[0])); + TS_ASSERT_EQUALS(topicId & 0xff, static_cast(Topic[1])); + TS_ASSERT(!unitTestHasOutputData()); + unsubMsgId = unsubscribeMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(unsubMsgId, 0U); + } + + TS_ASSERT(!unitTestHasUnsubscribeCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestUnsubackMsg unsubackMsg; + unsubackMsg.field_msgId().setValue(unsubMsgId); + unitTestClientInputMessage(client, unsubackMsg); + } + + TS_ASSERT(unitTestHasUnsubscribeCompleteReport()); + auto unsubscribeReport = unitTestUnsubscribeCompleteReport(); + TS_ASSERT_EQUALS(unsubscribeReport->m_handle, unsubscribe); + TS_ASSERT_EQUALS(unsubscribeReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestUnsubscribe::test4() +{ + // Testing unsubscribe that hasn't been subscribed before + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic("ab"); + + auto unsubscribe = apiUnsubscribePrepare(client); + TS_ASSERT_DIFFERS(unsubscribe, nullptr); + + CC_MqttsnUnsubscribeConfig config; + apiUnsubscribeInitConfig(&config); + config.m_topic = Topic.c_str(); + + auto ec = apiUnsubscribeConfig(unsubscribe, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_BadParam); + + ec = apiSetVerifyIncomingMsgSubscribed(client, false); + ec = apiUnsubscribeConfig(unsubscribe, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestUnsubscribeSend(unsubscribe); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + auto unsubMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* unsubscribeMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(unsubscribeMsg, nullptr); + TS_ASSERT_EQUALS(unsubscribeMsg->field_flags().field_topicIdType().value(), TopicIdType::ShortTopicName); + TS_ASSERT(unsubscribeMsg->field_topicId().doesExist()); + TS_ASSERT(unsubscribeMsg->field_topicName().isMissing()); + auto topicId = unsubscribeMsg->field_topicId().field().value(); + TS_ASSERT_EQUALS((topicId >> 8U) & 0xff, static_cast(Topic[0])); + TS_ASSERT_EQUALS(topicId & 0xff, static_cast(Topic[1])); + TS_ASSERT(!unitTestHasOutputData()); + unsubMsgId = unsubscribeMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(unsubMsgId, 0U); + } + + TS_ASSERT(!unitTestHasUnsubscribeCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestUnsubackMsg unsubackMsg; + unsubackMsg.field_msgId().setValue(unsubMsgId); + unitTestClientInputMessage(client, unsubackMsg); + } + + TS_ASSERT(unitTestHasUnsubscribeCompleteReport()); + auto unsubscribeReport = unitTestUnsubscribeCompleteReport(); + TS_ASSERT_EQUALS(unsubscribeReport->m_handle, unsubscribe); + TS_ASSERT_EQUALS(unsubscribeReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + + TS_ASSERT(unitTestHasTickReq()); +} From e6149a6a050d42ce96788a72c2c5fc085e6141d3 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 17 Jul 2024 08:14:35 +1000 Subject: [PATCH 065/106] More client unsubscribe unit testing. --- client/lib/test/UnitTestSubscribe.th | 9 +- client/lib/test/UnitTestUnsubscribe.th | 304 +++++++++++++++++++++++++ 2 files changed, 308 insertions(+), 5 deletions(-) diff --git a/client/lib/test/UnitTestSubscribe.th b/client/lib/test/UnitTestSubscribe.th index 9232b666..8b833889 100644 --- a/client/lib/test/UnitTestSubscribe.th +++ b/client/lib/test/UnitTestSubscribe.th @@ -414,7 +414,7 @@ void UnitTestSubscribe::test6() unitTestTick(client, 1000); const std::string Topic1("a/+"); - const std::string Topic2("c/+"); + const std::string Topic2("cd"); const std::string Topic3("e/+"); { @@ -520,11 +520,10 @@ void UnitTestSubscribe::test6() auto sentMsg = unitTestPopOutputMessage(); auto* subscribeMsg = dynamic_cast(sentMsg.get()); TS_ASSERT_DIFFERS(subscribeMsg, nullptr); - TS_ASSERT_EQUALS(subscribeMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(subscribeMsg->field_flags().field_topicIdType().value(), TopicIdType::ShortTopicName); TS_ASSERT_EQUALS(static_cast(subscribeMsg->field_flags().field_qos().value()), CC_MqttsnQoS_AtLeastOnceDelivery); - TS_ASSERT(subscribeMsg->field_topicId().isMissing()); - TS_ASSERT(subscribeMsg->field_topicName().doesExist()); - TS_ASSERT_EQUALS(subscribeMsg->field_topicName().field().value(), Topic2); + TS_ASSERT(subscribeMsg->field_topicId().doesExist()); + TS_ASSERT(subscribeMsg->field_topicName().isMissing()); TS_ASSERT(!unitTestHasOutputData()); subMsgId2 = subscribeMsg->field_msgId().value(); TS_ASSERT_DIFFERS(subMsgId2, 0U); diff --git a/client/lib/test/UnitTestUnsubscribe.th b/client/lib/test/UnitTestUnsubscribe.th index 040d9bfb..42dc8279 100644 --- a/client/lib/test/UnitTestUnsubscribe.th +++ b/client/lib/test/UnitTestUnsubscribe.th @@ -12,6 +12,8 @@ public: void test2(); void test3(); void test4(); + void test5(); + void test6(); private: virtual void setUp() override @@ -293,3 +295,305 @@ void UnitTestUnsubscribe::test4() TS_ASSERT(unitTestHasTickReq()); } + +void UnitTestUnsubscribe::test5() +{ + // Testing multiple unsubscribe operations. + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic1("abcde"); + const std::string Topic2("fg"); + const CC_MqttsnTopicId TopicId3 = 123; + + unitTestDoSubscribeTopic(client, Topic1); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + unitTestDoSubscribeTopic(client, Topic2); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + unitTestDoSubscribeTopicId(client, TopicId3); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + auto unsubscribe1 = apiUnsubscribePrepare(client); + TS_ASSERT_DIFFERS(unsubscribe1, nullptr); + + CC_MqttsnUnsubscribeConfig config1; + apiUnsubscribeInitConfig(&config1); + + config1.m_topic = Topic1.c_str(); + + auto ec = apiUnsubscribeConfig(unsubscribe1, &config1); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestUnsubscribeSend(unsubscribe1); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + } + + auto unsubMsgId1 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* unsubscribeMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(unsubscribeMsg, nullptr); + TS_ASSERT_EQUALS(unsubscribeMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT(unsubscribeMsg->field_topicId().isMissing()); + TS_ASSERT(unsubscribeMsg->field_topicName().doesExist()); + TS_ASSERT_EQUALS(unsubscribeMsg->field_topicName().field().value(), Topic1); + TS_ASSERT(!unitTestHasOutputData()); + unsubMsgId1 = unsubscribeMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(unsubMsgId1, 0U); + } + + TS_ASSERT(!unitTestHasUnsubscribeCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 10); + + { + auto unsubscribe2 = apiUnsubscribePrepare(client); + TS_ASSERT_DIFFERS(unsubscribe2, nullptr); + + CC_MqttsnUnsubscribeConfig config2; + apiUnsubscribeInitConfig(&config2); + + config2.m_topic = Topic2.c_str(); + + auto ec = apiUnsubscribeConfig(unsubscribe2, &config2); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestUnsubscribeSend(unsubscribe2); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + TS_ASSERT(!unitTestHasOutputData()); // Will wait until previous unsubscribe is complete + } + + TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 10); + + { + auto unsubscribe3 = apiUnsubscribePrepare(client); + TS_ASSERT_DIFFERS(unsubscribe3, nullptr); + + CC_MqttsnUnsubscribeConfig config3; + apiUnsubscribeInitConfig(&config3); + + config3.m_topicId = TopicId3; + + auto ec = apiUnsubscribeConfig(unsubscribe3, &config3); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestUnsubscribeSend(unsubscribe3); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + TS_ASSERT(!unitTestHasOutputData()); // Will wait until previous unsubscribe is complete + } + + { + UnitTestUnsubackMsg unsubackMsg1; + unsubackMsg1.field_msgId().setValue(unsubMsgId1); + unitTestClientInputMessage(client, unsubackMsg1); + } + + { + TS_ASSERT(unitTestHasUnsubscribeCompleteReport()); + auto unsubscribeReport = unitTestUnsubscribeCompleteReport(); + TS_ASSERT_EQUALS(unsubscribeReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + } + + auto unsubMsgId2 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* unsubscribeMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(unsubscribeMsg, nullptr); + TS_ASSERT_EQUALS(unsubscribeMsg->field_flags().field_topicIdType().value(), TopicIdType::ShortTopicName); + TS_ASSERT(unsubscribeMsg->field_topicId().doesExist()); + TS_ASSERT(unsubscribeMsg->field_topicName().isMissing()); + TS_ASSERT(!unitTestHasOutputData()); + unsubMsgId2 = unsubscribeMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(unsubMsgId2, 0U); + TS_ASSERT_DIFFERS(unsubMsgId2, unsubMsgId1); + } + + TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 10); + + { + UnitTestUnsubackMsg unsubackMsg2; + unsubackMsg2.field_msgId().setValue(unsubMsgId2); + unitTestClientInputMessage(client, unsubackMsg2); + } + + { + TS_ASSERT(unitTestHasUnsubscribeCompleteReport()); + auto unsubscribeReport = unitTestUnsubscribeCompleteReport(); + TS_ASSERT_EQUALS(unsubscribeReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + } + + auto unsubMsgId3 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* unsubscribeMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(unsubscribeMsg, nullptr); + TS_ASSERT_EQUALS(unsubscribeMsg->field_flags().field_topicIdType().value(), TopicIdType::PredefinedTopicId); + TS_ASSERT_EQUALS(static_cast(unsubscribeMsg->field_flags().field_qos().value()), CC_MqttsnQoS_AtMostOnceDelivery); + TS_ASSERT(unsubscribeMsg->field_topicId().doesExist()); + TS_ASSERT(unsubscribeMsg->field_topicName().isMissing()); + TS_ASSERT_EQUALS(unsubscribeMsg->field_topicId().field().value(), TopicId3); + TS_ASSERT(!unitTestHasOutputData()); + unsubMsgId3 = unsubscribeMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(unsubMsgId3, 0U); + TS_ASSERT_DIFFERS(unsubMsgId3, unsubMsgId2); + TS_ASSERT_DIFFERS(unsubMsgId3, unsubMsgId1); + } + + TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 10); + + { + UnitTestUnsubackMsg unsubackMsg; + unsubackMsg.field_msgId().setValue(unsubMsgId3); + unitTestClientInputMessage(client, unsubackMsg); + } + + { + TS_ASSERT(unitTestHasUnsubscribeCompleteReport()); + auto unsubscribeReport = unitTestUnsubscribeCompleteReport(); + TS_ASSERT_EQUALS(unsubscribeReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + } + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestUnsubscribe::test6() +{ + // Testing subscribe and unsubscribe issues one after another + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic1("abcde"); + const std::string Topic2("a/+"); + + unitTestDoSubscribeTopic(client, Topic1); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + auto subscribe = apiSubscribePrepare(client); + TS_ASSERT_DIFFERS(subscribe, nullptr); + + CC_MqttsnSubscribeConfig config; + apiSubscribeInitConfig(&config); + TS_ASSERT_EQUALS(config.m_qos, CC_MqttsnQoS_ExactlyOnceDelivery); + + config.m_topic = Topic2.c_str(); + + auto ec = apiSubscribeConfig(subscribe, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestSubscribeSend(subscribe); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + } + + auto subMsgId1 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* subscribeMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(subscribeMsg, nullptr); + TS_ASSERT_EQUALS(subscribeMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT(subscribeMsg->field_topicId().isMissing()); + TS_ASSERT(subscribeMsg->field_topicName().doesExist()); + TS_ASSERT_EQUALS(subscribeMsg->field_topicName().field().value(), Topic2); + TS_ASSERT(!unitTestHasOutputData()); + subMsgId1 = subscribeMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(subMsgId1, 0U); + } + + TS_ASSERT(!unitTestHasSubscribeCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 10); + + { + auto unsubscribe = apiUnsubscribePrepare(client); + TS_ASSERT_DIFFERS(unsubscribe, nullptr); + + CC_MqttsnUnsubscribeConfig config; + apiUnsubscribeInitConfig(&config); + + config.m_topic = Topic1.c_str(); + + auto ec = apiUnsubscribeConfig(unsubscribe, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestUnsubscribeSend(unsubscribe); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + TS_ASSERT(!unitTestHasOutputData()); // Will wait until previous subscribe is complete + } + + const auto AckQos = CC_MqttsnQoS_AtLeastOnceDelivery; + { + UnitTestSubackMsg subackMsg; + subackMsg.field_flags().field_qos().setValue(AckQos); + subackMsg.field_msgId().setValue(subMsgId1); + subackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, subackMsg); + } + + { + TS_ASSERT(unitTestHasSubscribeCompleteReport()); + auto subscribeReport = unitTestSubscribeCompleteReport(); + TS_ASSERT_EQUALS(subscribeReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(subscribeReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted); + TS_ASSERT_EQUALS(subscribeReport->m_info.m_qos, AckQos); + } + + auto unsubMsgId1 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* unsubscribeMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(unsubscribeMsg, nullptr); + TS_ASSERT_EQUALS(unsubscribeMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT(unsubscribeMsg->field_topicId().isMissing()); + TS_ASSERT(unsubscribeMsg->field_topicName().doesExist()); + TS_ASSERT_EQUALS(unsubscribeMsg->field_topicName().field().value(), Topic1); + TS_ASSERT(!unitTestHasOutputData()); + unsubMsgId1 = unsubscribeMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(unsubMsgId1, 0U); + TS_ASSERT_DIFFERS(unsubMsgId1, subMsgId1); + } + + { + UnitTestUnsubackMsg unsubackMsg; + unsubackMsg.field_msgId().setValue(unsubMsgId1); + unitTestClientInputMessage(client, unsubackMsg); + } + + { + TS_ASSERT(unitTestHasUnsubscribeCompleteReport()); + auto unsubscribeReport = unitTestUnsubscribeCompleteReport(); + TS_ASSERT_EQUALS(unsubscribeReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + } +} + From 5aea81f7367a32d53bec26686a5d318a0f5f3d28 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 17 Jul 2024 08:26:23 +1000 Subject: [PATCH 066/106] Added doxygen documentation of the client "unsubscribe" operation. --- client/lib/doxygen/main.dox | 177 ++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index e598348b..e288d3c2 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -1055,6 +1055,183 @@ /// subscribe operation by the reported handle when the completion callback /// is invoked. /// +/// @section doc_cc_mqttsn_client_unsubscribe Unsubscribing from Receiving Messages +/// To unsubscribe to receive incoming messages use @ref unsubscribe "unsubscribe" operation. +/// The application can issue multiple "unsubscribe" operations in parallel. +/// +/// @subsection doc_cc_mqttsn_client_unsubscribe_prepare Preparing "Unsubscribe" Operation. +/// @code +/// CC_MqttsnErrorCode ec = CC_MqttsnErrorCode_Success; +/// CC_MqttsnUnsubscribeHandle unsubscribe = cc_mqttsn_client_unsubscribe_prepare(client, &ec); +/// if (unsubscribe == NULL) { +/// printf("ERROR: Unsubscribe allocation failed with ec=%d\n", ec); +/// } +/// @endcode +/// +/// @subsection doc_cc_mqttsn_client_unsubscribe_retry_period Configuring "Unsubscribe" Retry Period +/// When created, the "unsubscribe" operation inherits the @ref doc_cc_mqttsn_client_retry_period +/// configuration. It can be changed for the allocated operation using the +/// @b cc_mqttsn_client_unsubscribe_set_retry_period() function. +/// @code +/// ec = cc_mqttsn_client_unsubscribe_set_retry_period(connect, 1000); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// ... /* Something went wrong */ +/// } +/// @endcode +/// To retrieve the configured response timeout use the @b cc_mqttsn_client_unsubscribe_get_retry_period() function. +/// +/// @subsection doc_cc_mqttsn_client_unsubscribe_retry_count Configuring "Unsubscribe" Retry Count +/// When created, the "unsubscribe" operation inherits the @ref doc_cc_mqttsn_client_retry_count +/// configuration. It can be changed for the allocated operation using the +/// @b cc_mqttsn_client_unsubscribe_set_retry_count() function. +/// @code +/// ec = cc_mqttsn_client_unsubscribe_set_retry_count(unsubscribe, 2); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// ... /* Something went wrong */ +/// } +/// @endcode +/// To retrieve the configured response timeout use the @b cc_mqttsn_client_unsubscribe_get_retry_count() function. +/// +/// @subsection doc_cc_mqttsn_client_unsubscribe_config Unsubscribe Configuration +/// To configure "unsubscribe" operation use @b cc_mqttsn_client_unsubscribe_config() function. +/// @code +/// CC_MqttsnUnsubscribeConfig config; +/// +/// // Assign default values to the configuration +/// cc_mqttsn_client_unsubscribe_init_config(&config); +/// +/// // Update values if needed +/// config.m_topic = "some/topic"; +/// +/// // Perform the configuration +/// ec = cc_mqttsn_client_unsubscribe_config(unsubscribe, &config); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Topic configuration failed with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// When there is a need to use a predefined topic id instead of the topic string +/// use @ref CC_MqttsnUnsubscribeConfig::m_topicId "m_topicId" member instead +/// of @ref CC_MqttsnUnsubscribeConfig::m_topic "m_topic". +/// @code +/// // Assign default values to the configuration +/// cc_mqttsn_client_unsubscribe_init_config(&config); +/// +/// // Update values if needed +/// config.m_topicId = 123; +/// @endcode +/// See also documentation of the @ref CC_MqttsnUnsubscribeConfig structure. +/// +/// The MQTT-SN specification also specifies short topics of 2 byte length. The +/// library detects this case by analysing the string assigned to the +/// @ref CC_MqttsnUnsubscribeConfig::m_topic "m_topic" member and uses appropriate +/// message configuration if needed. +/// +/// By default the library will perform the analysis of the submitted topic format and +/// reject it if topic format is incorrect. However, for performance reasons +/// it is possible to disable such verification when client application +/// ensures that no invalid topics are used. +/// @code +/// ec = cc_mqttsn_client_set_verify_outgoing_topic_enabled(client, false); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// ... /* Something is wrong */ +/// } +/// @endcode +/// @b NOTE that the configuration is global per client and not per "unsubscribe" +/// operation. +/// +/// Also @b note that the same function controls the verification of the +/// "subscribe", "subscribe" and "publish" topic formats. +/// +/// To retrieve the current configuration use @b cc_mqttsn_client_get_verify_outgoing_topic_enabled() +/// function. +/// +/// @subsection doc_cc_mqttsn_client_unsubscribe_send Sending Unsubscription Request +/// When all the necessary configurations are performed for the allocated "unsubscribe" +/// operation it can actually be sent to the broker. To initiate sending +/// use the @b cc_mqttsn_client_unsubscribe_send() function. +/// @code +/// void my_unsubscribe_complete_cb(void* data, CC_MqttsnUnsubscribeHandle handle, CC_MqttsnAsyncOpStatus status) +/// { +/// if (status != CC_MqttsnAsyncOpStatus_Complete) { +/// printf("ERROR: The subscription operation has failed with status=%d\n", status); +/// ... // handle error. +/// return; +/// } +/// } +/// +/// ec = cc_mqttsn_client_unsubscribe_send(unsubscribe, &my_unsubscribe_complete_cb, data); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to send unsubscribe request with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// The provided callback will be invoked when the "unsubscribe" operation is complete +/// if and only if the function returns @ref CC_MqttsnErrorCode_Success. +/// +/// The handle returned by the @b cc_mqttsn_client_unsubscribe_prepare() function +/// can be discarded (there is no free / de-allocation) right after the +/// @b cc_mqttsn_client_unsubscribe_send() invocation +/// regardless of the returned error code. However, the handle remains valid until +/// the callback is called (in case the @ref CC_MqttsnErrorCode_Success was returned). +/// The valid handle can be used to @ref doc_cc_mqttsn_client_unsubscribe_cancel "cancel" +/// the operation before the completion callback is invoked. +/// +/// The MQTT-SN spec demands that the @b UNSUBSCRIBE transactions be issued one at +/// a time. However, the library allows @ref doc_cc_mqttsn_client_unsubscribe_prepare "preparing" +/// and @ref doc_cc_mqttsn_client_unsubscribe_send "sending" multiple unsubscription +/// requests in parallel before completion of the first one. The library will +/// retain the requested "unsubscribe" operation requests internally and will +/// issue them one after another to comply with the specification. +/// +/// Note that the callback function receives the "unsubscribe" operation handle as +/// its second parameter. Although the handle is already invalid and cannot be +/// used in any other function, it allows the application to identify the +/// original "unsubscribe" request if multiple have been issued in parallel +/// and use the same callback function for all of them. +/// +/// @subsection doc_cc_mqttsn_client_unsubscribe_cancel Cancel the "Unsubscribe" Operation. +/// While the handle returned by the @b cc_mqttsn_client_unsubscribe_prepare() is still +/// valid it is possible to cancel / discard the operation. +/// @code +/// ec = cc_mqttsn_client_unsubscribe_cancel(unsubscribe); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to cancel unsubscribe with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// In case the @b cc_mqttsn_client_unsubscribe_send() function was successfully +/// called before the @b cc_mqttsn_client_unsubscribe_cancel(), the operation is +/// cancelled @b without callback invocation. +/// +/// @subsection doc_cc_mqttsn_client_unsubscribe_simplify Simplifying the "Unsubscribe" Operation Preparation. +/// In many use cases the "unsubscribe" operation can be quite simple with a lot of defaults. +/// To simplify the sequence of the operation preparation and handling of errors, +/// the library provides wrapper function that can be used: +/// @li @b cc_mqttsn_client_unsubscribe() +/// +/// For example: +/// @code +/// CC_MqttsnUnsubscribeConfig config; +/// +/// // Assign default values to the configuration +/// cc_mqttsn_client_unsubscribe_init_config(&config); +/// +/// // Update values if needed +/// config.m_topic = "some/topic"; +/// +/// ec = cc_mqttsn_client_unsubscribe(client, &config, &my_unsubscribe_complete_cb, data); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to send unsubscribe request with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// Note that the wrapper function does NOT expose the handle returned by the +/// @b cc_mqttsn_client_unsubscribe_prepare(). It means that it's not possible to +/// cancel the "unsubscribe" operation before its completion or identify the +/// unsubscribe operation by the reported handle when the completion callback +/// is invoked. +/// /// @section doc_cc_mqttsn_client_receive Receiving Messages /// TODO /// From 39ac7432f22b11624f77f78523996c79eb2e5f66 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 17 Jul 2024 08:55:20 +1000 Subject: [PATCH 067/106] More gateway discovery testing. --- client/lib/test/UnitTestCommonBase.cpp | 27 ++++++++++-- client/lib/test/UnitTestCommonBase.h | 5 +++ client/lib/test/UnitTestGwDiscover.th | 58 +++++++++++++++++++++++++- 3 files changed, 86 insertions(+), 4 deletions(-) diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index f9a32f43..79d4ce51 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -231,6 +231,11 @@ void UnitTestCommonBase::unitTestClientInputMessage(CC_MqttsnClient* client, con unitTestClientInputData(client, data); } +void UnitTestCommonBase::unitTestPushSearchgwResponseDelay(unsigned val) +{ + m_data.m_searchgwResponseDelays.push_back(val); +} + bool UnitTestCommonBase::unitTestHasTickReq() const { return !m_data.m_ticks.empty(); @@ -658,6 +663,16 @@ CC_MqttsnErrorCode UnitTestCommonBase::apiSetVerifyIncomingMsgSubscribed(CC_Mqtt return m_funcs.m_set_verify_incoming_msg_subscribed(client, enabled); } +void UnitTestCommonBase::apiInitGatewayInfo(CC_MqttsnGatewayInfo* info) +{ + m_funcs.m_init_gateway_info(info); +} + +CC_MqttsnErrorCode UnitTestCommonBase::apiSetAvailableGatewayInfo(CC_MqttsnClient* client, const CC_MqttsnGatewayInfo* info) +{ + return m_funcs.m_set_available_gateway_info(client, info); +} + CC_MqttsnSearchHandle UnitTestCommonBase::apiSearchPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec) { return m_funcs.m_search_prepare(client, ec); @@ -821,9 +836,15 @@ void UnitTestCommonBase::unitTestMessageReportCb(void* data, const CC_MqttsnMess unsigned UnitTestCommonBase::unitTestGwinfoDelayRequestCb(void* data) { - // TODO: - static_cast(data); - test_assert(false); + auto* thisPtr = asThis(data); + test_assert(!thisPtr->m_data.m_searchgwResponseDelays.empty()); + if (thisPtr->m_data.m_searchgwResponseDelays.empty()) { + return 0U; + } + + auto result = thisPtr->m_data.m_searchgwResponseDelays.front(); + thisPtr->m_data.m_searchgwResponseDelays.pop_front(); + return result; } void UnitTestCommonBase::unitTestErrorLogCb([[maybe_unused]] void* data, const char* msg) diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index 39e4e114..86f43f6f 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -188,6 +188,7 @@ class UnitTestCommonBase using UnitTestSearchCompleteCb = std::function; using UnitTestSearchCompleteCbList = std::list; + using UnitTestSearchgwResponseDelayList = std::list; struct UnitTestConnectInfo { @@ -266,6 +267,7 @@ class UnitTestCommonBase UnitTestClientPtr unitTestAllocClient(bool enableLog = false); void unitTestClientInputData(CC_MqttsnClient* client, const UnitTestData& data); void unitTestClientInputMessage(CC_MqttsnClient* client, const UnitTestMessage& msg); + void unitTestPushSearchgwResponseDelay(unsigned val); bool unitTestHasTickReq() const; const UnitTestTickInfo* unitTestTickInfo(bool mustExist = true) const; @@ -319,6 +321,8 @@ class UnitTestCommonBase CC_MqttsnErrorCode apiSetDefaultRetryPeriod(CC_MqttsnClient* client, unsigned value); CC_MqttsnErrorCode apiSetDefaultRetryCount(CC_MqttsnClient* client, unsigned value); CC_MqttsnErrorCode apiSetVerifyIncomingMsgSubscribed(CC_MqttsnClient* client, bool enabled); + void apiInitGatewayInfo(CC_MqttsnGatewayInfo* info); + CC_MqttsnErrorCode apiSetAvailableGatewayInfo(CC_MqttsnClient* client, const CC_MqttsnGatewayInfo* info); CC_MqttsnSearchHandle apiSearchPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec = nullptr); CC_MqttsnErrorCode apiSearchSetRetryPeriod(CC_MqttsnSearchHandle search, unsigned value); @@ -361,6 +365,7 @@ class UnitTestCommonBase UnitTestGwDisconnectReportsList m_gwDisconnectReports; UnitTestSearchCompleteReportsList m_searchCompleteReports; UnitTestSearchCompleteCbList m_searchCompleteCallbacks; + UnitTestSearchgwResponseDelayList m_searchgwResponseDelays; UnitTestConnectCompleteReportList m_connectCompleteReports; UnitTestDisconnectCompleteReportList m_disconnectCompleteReports; UnitTestSubscribeCompleteReportList m_subscribeCompleteReports; diff --git a/client/lib/test/UnitTestGwDiscover.th b/client/lib/test/UnitTestGwDiscover.th index b21c3d95..c391d338 100644 --- a/client/lib/test/UnitTestGwDiscover.th +++ b/client/lib/test/UnitTestGwDiscover.th @@ -14,6 +14,7 @@ public: void test4(); void test5(); void test6(); + void test7(); private: virtual void setUp() override @@ -251,7 +252,6 @@ void UnitTestGwDiscover::test5() TS_ASSERT(!unitTestHasTickReq()); } - void UnitTestGwDiscover::test6() { // Testing search with packet loss @@ -320,4 +320,60 @@ void UnitTestGwDiscover::test6() TS_ASSERT(gwInfoReport->m_info.m_addr.empty()); TS_ASSERT(unitTestHasTickReq()); // For the ADVERTISE +} + +void UnitTestGwDiscover::test7() +{ + // Testing response on behalf of other gateway + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const std::uint8_t GwId = 1U; + const unsigned AdvDurationMin = 10U; + + { + UnitTestAdvertiseMsg advertiseMsg; + advertiseMsg.field_gwId().setValue(GwId); + comms::units::setMinutes(advertiseMsg.field_duration(), AdvDurationMin); + unitTestClientInputMessage(client, advertiseMsg); + } + + { + auto gwInfoReport = unitTestGetGwInfoReport(); + TS_ASSERT_EQUALS(gwInfoReport->m_status, CC_MqttsnGwStatus_AddedByGateway); + TS_ASSERT_EQUALS(gwInfoReport->m_info.m_gwId, GwId); + TS_ASSERT(gwInfoReport->m_info.m_addr.empty()); + TS_ASSERT(!unitTestHasGwInfoReport()); + } + + const UnitTestData GwAddr = {1, 2, 3, 4}; + CC_MqttsnGatewayInfo gwInfo; + apiInitGatewayInfo(&gwInfo); + gwInfo.m_gwId = GwId; + gwInfo.m_addr = GwAddr.data(); + gwInfo.m_addrLen = static_cast(GwAddr.size()); + apiSetAvailableGatewayInfo(client, &gwInfo); + + auto* tickReq = unitTestTickInfo(); + TS_ASSERT_LESS_THAN(AdvDurationMin * 60 * 1000, tickReq->m_req); // Extra buffer after expiry is expected + unitTestTick(client, 1000); + + unitTestPushSearchgwResponseDelay(1000); + const unsigned BroadcastRadius = 3; + { + UnitTestSearchgwMsg searchgwMsg; + searchgwMsg.field_radius().value() = BroadcastRadius; + unitTestClientInputMessage(client, searchgwMsg); + } + + unitTestTick(client); // Tiemout to send GWINFO + + { + auto sentMsg = unitTestPopOutputMessage(); + auto* gwinfoMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_EQUALS(gwinfoMsg->field_gwId().value(), GwId); + TS_ASSERT_EQUALS(gwinfoMsg->field_gwAdd().value(), GwAddr); + } + } \ No newline at end of file From d98c28558f43fafeae35a80c6433f8ecb709da08 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 18 Jul 2024 08:09:32 +1000 Subject: [PATCH 068/106] Preparing support of "publish" operation in the client. --- client/lib/include/cc_mqttsn_client/common.h | 39 +++++++++++++++++++ client/lib/script/BareMetalTestConfig.cmake | 9 +++++ .../lib/script/DefineDefaultConfigVars.cmake | 3 +- client/lib/script/WriteConfigHeader.cmake | 3 +- client/lib/src/ClientImpl.cpp | 14 ++++--- client/lib/src/ExtConfig.h | 9 ++--- client/lib/src/ReuseState.h | 1 + client/lib/src/TopicFilterDefs.h | 1 + client/lib/templ/Config.h.templ | 8 ++-- client/lib/templ/client.h.templ | 11 ++++++ 10 files changed, 83 insertions(+), 15 deletions(-) diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index a23a3222..dee9560f 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -167,6 +167,15 @@ struct CC_MqttsnUnsubscribe; /// @ingroup "subscribe". typedef struct CC_MqttsnUnsubscribe* CC_MqttsnUnsubscribeHandle; +/// @brief Declaration of the hidden structure used to define @ref CC_MqttsnPublishHandle +/// @ingroup publish +struct CC_MqttsnPublish; + +/// @brief Handle for "publish" operation. +/// @details Returned by @b cc_mqttsn_client_publish_prepare() function. +/// @ingroup "publish". +typedef struct CC_MqttsnPublish* CC_MqttsnPublishHandle; + /// @brief Type used to hold Topic ID value. typedef unsigned short CC_MqttsnTopicId; @@ -252,6 +261,25 @@ typedef struct CC_MqttsnTopicId m_topicId; ///< Pre-defined topic ID, should be @b 0 when topic is not NULL. } CC_MqttsnUnsubscribeConfig; +/// @brief Configuration the will for "publish" operations +/// @ingroup publish +typedef struct +{ + const char* m_topic; ///< Publish topic. + CC_MqttsnTopicId m_topicId; ///< Pre-defined topic ID, should be @b 0 when topic is not NULL. + const unsigned char* m_data; ///< Publish data (message). + unsigned m_dataLen; ///< Publish data (message) length. + CC_MqttsnQoS m_qos; ///< Publish message QoS. + bool m_retain; ///< Publish message retain configuration. +} CC_MqttsnPublishConfig; + +/// @brief Information on the "publish" operation completion +/// @ingroup publish +typedef struct +{ + CC_MqttsnReturnCode m_returnCode; ///< Return code reported by the @b PUBACK message +} CC_MqttsnPublishInfo; + /// @brief Callback used to request time measurement. /// @details The callback is set using /// cc_mqttsn_client_set_next_tick_program_callback() function. @@ -373,6 +401,17 @@ typedef void (*CC_MqttsnSubscribeCompleteCb)(void* data, CC_MqttsnSubscribeHandl /// @ingroup unsubscribe typedef void (*CC_MqttsnUnsubscribeCompleteCb)(void* data, CC_MqttsnUnsubscribeHandle handle, CC_MqttsnAsyncOpStatus status); +/// @brief Callback used to report completion of the publish operation. +/// @param[in] data Pointer to user data object, passed as the last parameter to +/// the request call. +/// @param[in] status Status of the "publish" operation. +/// @param[in] info Information about op completion. Not-NULL is reported onfly if +/// the "status" is equal to @ref CC_MqttsnAsyncOpStatus_Complete. When QoS2 publish +/// is successfully performed the "info" can still be NULL. +/// @post The data members of the reported response can NOT be accessed after the function returns. +/// @ingroup publish +typedef void (*CC_MqttsnPublishCompleteCb)(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnPublishInfo* info); + #ifdef __cplusplus } #endif diff --git a/client/lib/script/BareMetalTestConfig.cmake b/client/lib/script/BareMetalTestConfig.cmake index 7398bc96..a53da946 100644 --- a/client/lib/script/BareMetalTestConfig.cmake +++ b/client/lib/script/BareMetalTestConfig.cmake @@ -31,6 +31,12 @@ set(CC_MQTTSN_CLIENT_DATA_FIELD_FIXED_LEN 128) # Limit the length of the buffer required to store serialized message set(CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE 512) +# Limit the amount of incomplete QoS2 messages being received in parallel +set (CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT 4) + +# Limit the amount of outstanding publish (send) operations +set (CC_MQTTSN_CLIENT_ASYNC_SEND_LIMIT 6) + # Limit the amount of outstanding subscribe operations set(CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT 1) @@ -42,3 +48,6 @@ set(CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT 10) # Limit the amount of input registered topics set(CC_MQTTSN_CLIENT_IN_REG_TOPICS_LIMIT 20) + +# Limit the amount of output registered topics +set(CC_MQTTSN_CLIENT_OUT_REG_TOPICS_LIMIT 20) diff --git a/client/lib/script/DefineDefaultConfigVars.cmake b/client/lib/script/DefineDefaultConfigVars.cmake index fecef7a9..5619d282 100644 --- a/client/lib/script/DefineDefaultConfigVars.cmake +++ b/client/lib/script/DefineDefaultConfigVars.cmake @@ -24,7 +24,7 @@ set_default_var_value(CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN 0) set_default_var_value(CC_MQTTSN_CLIENT_DATA_FIELD_FIXED_LEN 0) set_default_var_value(CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE 0) set_default_var_value(CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT 0) -set_default_var_value(CC_MQTTSN_CLIENT_SEND_MAX_LIMIT 0) +set_default_var_value(CC_MQTTSN_CLIENT_ASYNC_SEND_LIMIT 0) set_default_var_value(CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT 0) set_default_var_value(CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT 0) set_default_var_value(CC_MQTTSN_CLIENT_HAS_ERROR_LOG TRUE) @@ -32,4 +32,5 @@ set_default_var_value(CC_MQTTSN_CLIENT_HAS_TOPIC_FORMAT_VERIFICATION TRUE) set_default_var_value(CC_MQTTSN_CLIENT_HAS_SUB_TOPIC_VERIFICATION TRUE) set_default_var_value(CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT 0) set_default_var_value(CC_MQTTSN_CLIENT_IN_REG_TOPICS_LIMIT 0) +set_default_var_value(CC_MQTTSN_CLIENT_OUT_REG_TOPICS_LIMIT 0) set_default_var_value(CC_MQTTSN_CLIENT_MAX_QOS 2) \ No newline at end of file diff --git a/client/lib/script/WriteConfigHeader.cmake b/client/lib/script/WriteConfigHeader.cmake index 225238fc..6394a7cf 100644 --- a/client/lib/script/WriteConfigHeader.cmake +++ b/client/lib/script/WriteConfigHeader.cmake @@ -50,7 +50,7 @@ replace_in_text (CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN) replace_in_text (CC_MQTTSN_CLIENT_HAS_WILL_CPP) replace_in_text (CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE) replace_in_text (CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT) -replace_in_text (CC_MQTTSN_CLIENT_SEND_MAX_LIMIT) +replace_in_text (CC_MQTTSN_CLIENT_ASYNC_SEND_LIMIT) replace_in_text (CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT) replace_in_text (CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT) replace_in_text (CC_MQTTSN_CLIENT_HAS_ERROR_LOG_CPP) @@ -58,6 +58,7 @@ replace_in_text (CC_MQTTSN_CLIENT_HAS_TOPIC_FORMAT_VERIFICATION_CPP) replace_in_text (CC_MQTTSN_CLIENT_HAS_SUB_TOPIC_VERIFICATION_CPP) replace_in_text (CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT) replace_in_text (CC_MQTTSN_CLIENT_IN_REG_TOPICS_LIMIT) +replace_in_text (CC_MQTTSN_CLIENT_OUT_REG_TOPICS_LIMIT) replace_in_text (CC_MQTTSN_CLIENT_MAX_QOS) file (WRITE "${OUT_FILE}.tmp" "${text}") diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 4c55d1c7..1d6b7384 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -57,13 +57,12 @@ ClientImpl::ClientImpl() : m_gwDiscoveryTimer(m_timerMgr.allocTimer()), m_sendGwinfoTimer(m_timerMgr.allocTimer()) { - // TODO: check validity of timer in during intialization } ClientImpl::~ClientImpl() { COMMS_ASSERT(m_apiEnterCount == 0U); - // terminateOps(CC_MqttsnAsyncOpStatus_Aborted, TerminateMode_AbortSendRecvOps); + terminateOps(CC_MqttsnAsyncOpStatus_Aborted); } @@ -1070,9 +1069,14 @@ CC_MqttsnErrorCode ClientImpl::initInternal() } } - // TODO: - // terminateOps(CC_MqttsnAsyncOpStatus_Aborted, TerminateMode_KeepSendRecvOps); - // m_sessionState = SessionState(); + if ((!m_gwDiscoveryTimer.isValid()) || + (!m_sendGwinfoTimer.isValid())) { + errorLog("Some timers haven't been allocated properly"); + return CC_MqttsnErrorCode_OutOfMemory; + } + + terminateOps(CC_MqttsnAsyncOpStatus_Aborted); + m_sessionState = SessionState(); m_clientState.m_initialized = true; return CC_MqttsnErrorCode_Success; } diff --git a/client/lib/src/ExtConfig.h b/client/lib/src/ExtConfig.h index d335fd7e..e29bc9d8 100644 --- a/client/lib/src/ExtConfig.h +++ b/client/lib/src/ExtConfig.h @@ -29,7 +29,6 @@ struct ExtConfig : public Config static constexpr unsigned UnsubscribeOpTimers = 1U; static constexpr unsigned RecvOpsLimit = MaxQos < 2 ? 1U : (ReceiveMaxLimit == 0U ? 0U : ReceiveMaxLimit + 1U); static constexpr unsigned RecvOpTimers = 1U; - static constexpr unsigned SendOpsLimit = SendMaxLimit == 0U ? 0U : SendMaxLimit + 1U; static constexpr unsigned SendOpTimers = 1U; static constexpr bool HasOpsLimit = (SearchOpsLimit > 0U) && @@ -37,9 +36,9 @@ struct ExtConfig : public Config (KeepAliveOpsLimit > 0U) && (DisconnectOpsLimit > 0U) && (SubscribeOpsLimit > 0U) && - (UnsubscribeOpsLimit > 0U) /* && + (UnsubscribeOpsLimit > 0U) && (RecvOpsLimit > 0U) && - (SendOpsLimit > 0U)*/; + (SendOpsLimit > 0U); static constexpr unsigned MaxTimersLimit = (DiscoveryTimers) + (SearchOpsLimit * SearchOpTimers) + @@ -75,8 +74,8 @@ struct ExtConfig : public Config static_assert(HasDynMemAlloc || (ConnectOpsLimit > 0U)); static_assert(HasDynMemAlloc || (KeepAliveOpsLimit > 0U)); static_assert(HasDynMemAlloc || (DisconnectOpsLimit > 0U)); - // static_assert(HasDynMemAlloc || (RecvOpsLimit > 0U)); - // static_assert(HasDynMemAlloc || (SendOpsLimit > 0U)); + static_assert(HasDynMemAlloc || (RecvOpsLimit > 0U)); + static_assert(HasDynMemAlloc || (SendOpsLimit > 0U)); static_assert(HasDynMemAlloc || (OpsLimit > 0U)); static_assert(HasDynMemAlloc || (PacketIdsLimit > 0U)); }; diff --git a/client/lib/src/ReuseState.h b/client/lib/src/ReuseState.h index 71060b20..adc57ad9 100644 --- a/client/lib/src/ReuseState.h +++ b/client/lib/src/ReuseState.h @@ -16,6 +16,7 @@ struct ReuseState { SubFiltersMap m_subFilters; InRegTopicsMap m_inRegTopics; + OutRegTopicsMap m_outRegTopics; }; } // namespace cc_mqttsn_client diff --git a/client/lib/src/TopicFilterDefs.h b/client/lib/src/TopicFilterDefs.h index 225c5ce7..5b7b2f96 100644 --- a/client/lib/src/TopicFilterDefs.h +++ b/client/lib/src/TopicFilterDefs.h @@ -33,5 +33,6 @@ struct RegTopicInfo using SubFiltersMap = ObjListType; // key is m_topic using InRegTopicsMap = ObjListType; // key is m_topicId; +using OutRegTopicsMap = ObjListType; // key is m_topicId; } // namespace cc_mqttsn_client diff --git a/client/lib/templ/Config.h.templ b/client/lib/templ/Config.h.templ index 4c9f25fd..75728134 100644 --- a/client/lib/templ/Config.h.templ +++ b/client/lib/templ/Config.h.templ @@ -13,7 +13,7 @@ struct Config static constexpr unsigned MaxOutputPacketSize = ##CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE##; static constexpr bool HasWill = ##CC_MQTTSN_CLIENT_HAS_WILL_CPP##; static constexpr unsigned ReceiveMaxLimit = ##CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT##; - static constexpr unsigned SendMaxLimit = ##CC_MQTTSN_CLIENT_SEND_MAX_LIMIT##; + static constexpr unsigned SendOpsLimit = ##CC_MQTTSN_CLIENT_ASYNC_SEND_LIMIT##; static constexpr unsigned SubscribeOpsLimit = ##CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT##; static constexpr unsigned UnsubscribeOpsLimit = ##CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT##; static constexpr bool HasErrorLog = ##CC_MQTTSN_CLIENT_HAS_ERROR_LOG_CPP##; @@ -21,18 +21,20 @@ struct Config static constexpr bool HasSubTopicVerification = ##CC_MQTTSN_CLIENT_HAS_SUB_TOPIC_VERIFICATION_CPP##; static constexpr unsigned SubFiltersLimit = ##CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT##; static constexpr unsigned InRegTopicsLimit = ##CC_MQTTSN_CLIENT_IN_REG_TOPICS_LIMIT##; + static constexpr unsigned OutRegTopicsLimit = ##CC_MQTTSN_CLIENT_OUT_REG_TOPICS_LIMIT##; static constexpr unsigned MaxQos = ##CC_MQTTSN_CLIENT_MAX_QOS##; static_assert(HasDynMemAlloc || (ClientAllocLimit > 0U), "Must use CC_MQTTSN_CLIENT_ALLOC_LIMIT in configuration to limit number of clients"); static_assert(HasDynMemAlloc || (!HasGatewayDiscovery) || (GatewayAddrLen > 0U), "Must use CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN in configuration to limit length of the gateway addr"); static_assert(HasDynMemAlloc || (!HasGatewayDiscovery) || (GatewayInfoxMaxLimit > 0U), "Must use CC_MQTTSN_CLIENT_GATEWAY_INFOS_MAX_LIMIT in configuration to limit amount of gateways to store"); static_assert(HasDynMemAlloc || (MaxOutputPacketSize > 0U), "Must use CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE in configuration to limit packet size"); -// static_assert(HasDynMemAlloc || (ReceiveMaxLimit > 0U) || (MaxQos < 2), "Must use CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT in configuration to limit amount of messages to receive"); -// static_assert(HasDynMemAlloc || (SendMaxLimit > 0U), "Must use CC_MQTTSN_CLIENT_SEND_MAX_LIMIT in configuration to limit amount of messages to send"); + static_assert(HasDynMemAlloc || (ReceiveMaxLimit > 0U) || (MaxQos < 2), "Must use CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT in configuration to limit amount of messages to receive"); + static_assert(HasDynMemAlloc || (SendOpsLimit > 0U), "Must use CC_MQTTSN_CLIENT_ASYNC_SEND_LIMIT in configuration to limit amount of messages to send"); static_assert(HasDynMemAlloc || (SubscribeOpsLimit > 0U), "Must use CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT in configuration to limit amount of unfinished subscribes."); static_assert(HasDynMemAlloc || (UnsubscribeOpsLimit > 0U), "Must use CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT in configuration to limit amount of unfinished unsubscribes."); static_assert(HasDynMemAlloc || (!HasSubTopicVerification) || (SubFiltersLimit > 0U), "Must use CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT in configuration to limit amount of subscribe filters"); static_assert(HasDynMemAlloc || (InRegTopicsLimit > 0U), "Must use CC_MQTTSN_CLIENT_IN_REG_TOPICS_LIMIT in configuration to limit amount of registered topics"); + static_assert(HasDynMemAlloc || (OutRegTopicsLimit > 0U), "Must use CC_MQTTSN_CLIENT_OUT_REG_TOPICS_LIMIT in configuration to limit amount of registered topics"); static_assert(MaxQos <= 2, "Not supported QoS value"); }; diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index 693f4520..6c73f87d 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -130,6 +130,7 @@ unsigned cc_mqttsn_##NAME##client_get_available_gateways_count(CC_MqttsnClientHa /// @brief Initialize the @ref CC_MqttsnGatewayInfo structure. /// @details Zeroes all the member fields. +/// @ingroup client void cc_mqttsn_##NAME##client_init_gateway_info(CC_MqttsnGatewayInfo* info); /// @brief Retrieve stored available gateway information @@ -156,10 +157,12 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_available_gateway_info(CC_Mqttsn /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[in] gwId Gateway ID. /// @return Result code of the call. +/// @ingroup client CC_MqttsnErrorCode cc_mqttsn_##NAME##client_discard_available_gateway_info(CC_MqttsnClientHandle client, unsigned char gwId); /// @brief Discard stored information on all available gateways. /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @ingroup client void cc_mqttsn_##NAME##client_discard_all_gateway_infos(CC_MqttsnClientHandle client); /// @brief Set default gateway advertise duration. @@ -689,6 +692,7 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_unsubscribe( /// @param[in] cb Callback function. /// @param[in] data Pointer to any user data structure. It will passed as one /// of the parameters in callback invocation. May be NULL. +/// @ingroup client void cc_mqttsn_##NAME##client_set_next_tick_program_callback( CC_MqttsnClientHandle client, CC_MqttsnNextTickProgramCb cb, @@ -704,6 +708,7 @@ void cc_mqttsn_##NAME##client_set_next_tick_program_callback( /// @param[in] cb Callback function. /// @param[in] data Pointer to any user data structure. It will passed as one /// of the parameters in callback invocation. May be NULL. +/// @ingroup client void cc_mqttsn_##NAME##client_set_cancel_next_tick_wait_callback( CC_MqttsnClientHandle client, CC_MqttsnCancelNextTickWaitCb cb, @@ -717,6 +722,7 @@ void cc_mqttsn_##NAME##client_set_cancel_next_tick_wait_callback( /// @param[in] cb Callback function. /// @param[in] data Pointer to any user data structure. It will passed as one /// of the parameters in callback invocation. May be NULL. +/// @ingroup client void cc_mqttsn_##NAME##client_set_send_output_data_callback( CC_MqttsnClientHandle client, CC_MqttsnSendOutputDataCb cb, @@ -728,6 +734,7 @@ void cc_mqttsn_##NAME##client_set_send_output_data_callback( /// @param[in] cb Callback function. /// @param[in] data Pointer to any user data structure. It will passed as one /// of the parameters in callback invocation. May be NULL. +/// @ingroup client void cc_mqttsn_##NAME##client_set_gw_status_report_callback( CC_MqttsnClientHandle client, CC_MqttsnGwStatusReportCb cb, @@ -740,6 +747,7 @@ void cc_mqttsn_##NAME##client_set_gw_status_report_callback( /// @param[in] cb Callback function. /// @param[in] data Pointer to any user data structure. It will passed as one /// of the parameters in callback invocation. May be NULL. +/// @ingroup client void cc_mqttsn_##NAME##client_set_gw_disconnect_report_callback( CC_MqttsnClientHandle client, CC_MqttsnGwDisconnectedReportCb cb, @@ -750,6 +758,7 @@ void cc_mqttsn_##NAME##client_set_gw_disconnect_report_callback( /// @param[in] cb Callback function. /// @param[in] data Pointer to any user data structure. It will passed as one /// of the parameters in callback invocation. May be NULL. +/// @ingroup client void cc_mqttsn_##NAME##client_set_message_report_callback( CC_MqttsnClientHandle client, CC_MqttsnMessageReportCb cb, @@ -760,6 +769,7 @@ void cc_mqttsn_##NAME##client_set_message_report_callback( /// @param[in] cb Callback function. /// @param[in] data Pointer to any user data structure. It will passed as one /// of the parameters in callback invocation. May be NULL. +/// @ingroup client void cc_mqttsn_##NAME##client_set_error_log_callback( CC_MqttsnClientHandle client, CC_MqttsnErrorLogCb cb, @@ -769,6 +779,7 @@ void cc_mqttsn_##NAME##client_set_error_log_callback( /// @details According to the MQTT-SN specification, the client can send @b GWINFO message on behalf of a gateway /// after some randrom amount of time. Use this function to allow the library to /// request such timeout. If not set, sending @b GWINFO on behalf of the gateway is @b disabled. +/// @ingroup client void cc_mqttsn_##NAME##client_set_gwinfo_delay_request_callback( CC_MqttsnClientHandle client, CC_MqttsnGwinfoDelayRequestCb cb, From 6dc4661efee1cf632c2599980fa87abb4a32d6a3 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 19 Jul 2024 08:52:34 +1000 Subject: [PATCH 069/106] Initial support for publish operation in client. --- client/lib/CMakeLists.txt | 1 + client/lib/include/cc_mqttsn_client/common.h | 6 +- client/lib/src/ClientImpl.cpp | 108 ++--- client/lib/src/ClientImpl.h | 15 +- client/lib/src/SessionState.h | 1 + client/lib/src/TopicFilterDefs.h | 2 +- client/lib/src/op/Op.h | 3 +- client/lib/src/op/SendOp.cpp | 424 +++++++++++++++++++ client/lib/src/op/SendOp.h | 86 ++++ client/lib/src/op/SubscribeOp.cpp | 5 + client/lib/src/op/SubscribeOp.h | 1 + client/lib/src/op/UnsubscribeOp.cpp | 5 + client/lib/src/op/UnsubscribeOp.h | 1 + client/lib/templ/client.cpp.templ | 103 +++++ client/lib/templ/client.h.templ | 87 ++++ 15 files changed, 784 insertions(+), 64 deletions(-) create mode 100644 client/lib/src/op/SendOp.cpp create mode 100644 client/lib/src/op/SendOp.h diff --git a/client/lib/CMakeLists.txt b/client/lib/CMakeLists.txt index cbe1c675..921e00f9 100644 --- a/client/lib/CMakeLists.txt +++ b/client/lib/CMakeLists.txt @@ -170,6 +170,7 @@ function (gen_lib_mqttsn_client config_file) src/op/KeepAliveOp.cpp src/op/Op.cpp src/op/SearchOp.cpp + src/op/SendOp.cpp src/op/SubscribeOp.cpp src/op/UnsubscribeOp.cpp src/ClientImpl.cpp diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index dee9560f..72476c44 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -404,13 +404,17 @@ typedef void (*CC_MqttsnUnsubscribeCompleteCb)(void* data, CC_MqttsnUnsubscribeH /// @brief Callback used to report completion of the publish operation. /// @param[in] data Pointer to user data object, passed as the last parameter to /// the request call. +/// @param[in] handle Handle returned by @b cc_mqttsn_client_publish_prepare() function. When the +/// callback is invoked the handle is already invalid and cannot be used in any relevant +/// function invocation, but it allows end application to identify the original "publish" operation +/// and use the same callback function in parallel requests. /// @param[in] status Status of the "publish" operation. /// @param[in] info Information about op completion. Not-NULL is reported onfly if /// the "status" is equal to @ref CC_MqttsnAsyncOpStatus_Complete. When QoS2 publish /// is successfully performed the "info" can still be NULL. /// @post The data members of the reported response can NOT be accessed after the function returns. /// @ingroup publish -typedef void (*CC_MqttsnPublishCompleteCb)(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnPublishInfo* info); +typedef void (*CC_MqttsnPublishCompleteCb)(void* data, CC_MqttsnPublishHandle handle, CC_MqttsnAsyncOpStatus status, const CC_MqttsnPublishInfo* info); #ifdef __cplusplus } diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 1d6b7384..0d40bb2d 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -376,56 +376,50 @@ op::UnsubscribeOp* ClientImpl::unsubscribePrepare(CC_MqttsnErrorCode* ec) return op; } -// op::SendOp* ClientImpl::publishPrepare(CC_MqttsnErrorCode* ec) -// { -// op::SendOp* sendOp = nullptr; -// do { -// if (!m_sessionState.m_connected) { -// errorLog("Client must be connected to allow publish."); -// updateEc(ec, CC_MqttsnErrorCode_NotConnected); -// break; -// } - -// if (m_sessionState.m_disconnecting) { -// errorLog("Session disconnection is in progress, cannot initiate publish."); -// updateEc(ec, CC_MqttsnErrorCode_Disconnecting); -// break; -// } +op::SendOp* ClientImpl::publishPrepare(CC_MqttsnErrorCode* ec) +{ + op::SendOp* op = nullptr; + do { + if (!m_sessionState.m_connected) { + errorLog("Client must be connected to allow publish."); + updateEc(ec, CC_MqttsnErrorCode_NotConnected); + break; + } -// if (m_clientState.m_networkDisconnected) { -// errorLog("Network is disconnected."); -// updateEc(ec, CC_MqttsnErrorCode_NetworkDisconnected); -// break; -// } + if (m_sessionState.m_disconnecting) { + errorLog("Session disconnection is in progress, cannot initiate publish."); + updateEc(ec, CC_MqttsnErrorCode_Disconnecting); + break; + } -// if (m_ops.max_size() <= m_ops.size()) { -// errorLog("Cannot start publish operation, retry in next event loop iteration."); -// updateEc(ec, CC_MqttsnErrorCode_RetryLater); -// break; -// } + if (m_ops.max_size() <= m_ops.size()) { + errorLog("Cannot start publish operation, retry in next event loop iteration."); + updateEc(ec, CC_MqttsnErrorCode_RetryLater); + break; + } -// auto ptr = m_sendOpsAlloc.alloc(*this); -// if (!ptr) { -// errorLog("Cannot allocate new publish operation."); -// updateEc(ec, CC_MqttsnErrorCode_OutOfMemory); -// break; -// } + auto ptr = m_sendOpsAlloc.alloc(*this); + if (!ptr) { + errorLog("Cannot allocate new publish operation."); + updateEc(ec, CC_MqttsnErrorCode_OutOfMemory); + break; + } -// if (m_preparationLocked) { -// errorLog("Another operation is being prepared, cannot prepare \"unsubscribe\" without \"send\" or \"cancel\" of the previous."); -// updateEc(ec, CC_MqttsnErrorCode_PreparationLocked); -// break; -// } + if (m_preparationLocked) { + errorLog("Another operation is being prepared, cannot prepare \"unsubscribe\" without \"send\" or \"cancel\" of the previous."); + updateEc(ec, CC_MqttsnErrorCode_PreparationLocked); + break; + } -// m_preparationLocked = true; -// m_ops.push_back(ptr.get()); -// m_sendOps.push_back(std::move(ptr)); -// sendOp = m_sendOps.back().get(); -// updateEc(ec, CC_MqttsnErrorCode_Success); -// } while (false); + m_preparationLocked = true; + m_ops.push_back(ptr.get()); + m_sendOps.push_back(std::move(ptr)); + op = m_sendOps.back().get(); + updateEc(ec, CC_MqttsnErrorCode_Success); + } while (false); -// return sendOp; -// } + return op; +} // CC_MqttsnErrorCode ClientImpl::setPublishOrdering(CC_MqttsnPublishOrdering ordering) // { @@ -857,7 +851,7 @@ void ClientImpl::opComplete(const op::Op* op) /* Type_Subscribe */ &ClientImpl::opComplete_Subscribe, /* Type_Unsubscribe */ &ClientImpl::opComplete_Unsubscribe, // /* Type_Recv */ &ClientImpl::opComplete_Recv, - // /* Type_Send */ &ClientImpl::opComplete_Send, + /* Type_Send */ &ClientImpl::opComplete_Send, }; static const std::size_t MapSize = std::extent::value; static_assert(MapSize == op::Op::Type_NumOfValues); @@ -949,6 +943,17 @@ void ClientImpl::allowNextPrepare() m_preparationLocked = false; } +void ClientImpl::allowNextRegister() +{ + for (auto& op : m_sendOps) { + COMMS_ASSERT(op); + if (op->isRegPending()) { + op->proceedWithReg(); + break; + } + } +} + void ClientImpl::doApiEnter() { ++m_apiEnterCount; @@ -1223,15 +1228,10 @@ void ClientImpl::opComplete_Unsubscribe(const op::Op* op) // eraseFromList(op, m_recvOps); // } -// void ClientImpl::opComplete_Send(const op::Op* op) -// { -// auto idx = eraseFromList(op, m_sendOps); -// if (m_sessionState.m_disconnecting) { -// return; -// } - -// resumeSendOpsSince(idx); -// } +void ClientImpl::opComplete_Send(const op::Op* op) +{ + eraseFromList(op, m_sendOps); +} void ClientImpl::finaliseSupUnsubOp() { diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index 0a845ae5..4b374379 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -23,7 +23,7 @@ #include "op/Op.h" // #include "op/RecvOp.h" #include "op/SearchOp.h" -// #include "op/SendOp.h" +#include "op/SendOp.h" #include "op/SubscribeOp.h" #include "op/UnsubscribeOp.h" @@ -73,7 +73,7 @@ class ClientImpl final : public ProtMsgHandler op::DisconnectOp* disconnectPrepare(CC_MqttsnErrorCode* ec); op::SubscribeOp* subscribePrepare(CC_MqttsnErrorCode* ec); op::UnsubscribeOp* unsubscribePrepare(CC_MqttsnErrorCode* ec); - // op::SendOp* publishPrepare(CC_MqttsnErrorCode* ec); + op::SendOp* publishPrepare(CC_MqttsnErrorCode* ec); // std::size_t sendsCount() const // { @@ -176,6 +176,7 @@ class ClientImpl final : public ProtMsgHandler // bool hasPausedSendsBefore(const op::SendOp* sendOp) const; // bool hasHigherQosSendsBefore(const op::SendOp* sendOp, op::Op::Qos qos) const; void allowNextPrepare(); + void allowNextRegister(); TimerMgr& timerMgr() { @@ -253,8 +254,8 @@ class ClientImpl final : public ProtMsgHandler // using RecvOpAlloc = ObjAllocator; // using RecvOpsList = ObjListType; - // using SendOpAlloc = ObjAllocator; - // using SendOpsList = ObjListType; + using SendOpAlloc = ObjAllocator; + using SendOpsList = ObjListType; using OpPtrsList = ObjListType; // using OpToDeletePtrsList = ObjListType; @@ -287,7 +288,7 @@ class ClientImpl final : public ProtMsgHandler void opComplete_Subscribe(const op::Op* op); void opComplete_Unsubscribe(const op::Op* op); // void opComplete_Recv(const op::Op* op); - // void opComplete_Send(const op::Op* op); + void opComplete_Send(const op::Op* op); void finaliseSupUnsubOp(); void monitorGatewayExpiry(); @@ -361,8 +362,8 @@ class ClientImpl final : public ProtMsgHandler // RecvOpAlloc m_recvOpsAlloc; // RecvOpsList m_recvOps; - // SendOpAlloc m_sendOpsAlloc; - // SendOpsList m_sendOps; + SendOpAlloc m_sendOpsAlloc; + SendOpsList m_sendOps; OpPtrsList m_ops; unsigned m_pendingGwinfoBroadcastRadius = 0U; diff --git a/client/lib/src/SessionState.h b/client/lib/src/SessionState.h index 8e6e0dfc..375df4a2 100644 --- a/client/lib/src/SessionState.h +++ b/client/lib/src/SessionState.h @@ -18,6 +18,7 @@ struct SessionState unsigned m_keepAliveMs = 0U; bool m_connected = false; bool m_disconnecting = false; + bool m_topicRegInProgress = false; }; } // namespace cc_mqttsn_client diff --git a/client/lib/src/TopicFilterDefs.h b/client/lib/src/TopicFilterDefs.h index 5b7b2f96..b1db7376 100644 --- a/client/lib/src/TopicFilterDefs.h +++ b/client/lib/src/TopicFilterDefs.h @@ -33,6 +33,6 @@ struct RegTopicInfo using SubFiltersMap = ObjListType; // key is m_topic using InRegTopicsMap = ObjListType; // key is m_topicId; -using OutRegTopicsMap = ObjListType; // key is m_topicId; +using OutRegTopicsMap = ObjListType; // key is m_topic; } // namespace cc_mqttsn_client diff --git a/client/lib/src/op/Op.h b/client/lib/src/op/Op.h index 4efed56e..47507037 100644 --- a/client/lib/src/op/Op.h +++ b/client/lib/src/op/Op.h @@ -36,7 +36,7 @@ class Op : public ProtMsgHandler Type_Subscribe, Type_Unsubscribe, // Type_Recv, - // Type_Send, + Type_Send, Type_NumOfValues // Must be last }; @@ -99,6 +99,7 @@ class Op : public ProtMsgHandler void decRetryCount(); void storeInRegTopic(const char* topic, CC_MqttsnTopicId topicId); bool removeInRegTopic(const char* topic, CC_MqttsnTopicId topicId); + static bool isValidTopicId(CC_MqttsnTopicId id); static bool isShortTopic(const char* topic); diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp new file mode 100644 index 00000000..de8f5610 --- /dev/null +++ b/client/lib/src/op/SendOp.cpp @@ -0,0 +1,424 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "op/SendOp.h" +#include "ClientImpl.h" + +#include "comms/util/assign.h" +#include "comms/util/ScopeGuard.h" +#include "comms/units.h" + +#include +#include + +namespace cc_mqttsn_client +{ + +namespace op +{ + +namespace +{ + +inline SendOp* asSendOp(void* data) +{ + return reinterpret_cast(data); +} + +inline CC_MqttsnPublishHandle asHandle(SendOp* op) +{ + return reinterpret_cast(op); +} + +} // namespace + + +SendOp::SendOp(ClientImpl& client) : + Base(client), + m_timer(client.timerMgr().allocTimer()) +{ +} + +SendOp::~SendOp() +{ + releasePacketId(m_registerMsg.field_msgId().value()); + releasePacketId(m_publishMsg.field_msgId().value()); + + if (m_registerInProgress) { + COMMS_ASSERT(client().sessionState().m_topicRegInProgress); + client().sessionState().m_topicRegInProgress = false; + client().allowNextRegister(); + } +} + +CC_MqttsnErrorCode SendOp::config(const CC_MqttsnPublishConfig* config) +{ + if (config == nullptr) { + errorLog("Publish configuration is not provided."); + return CC_MqttsnErrorCode_BadParam; + } + + bool emptyTopic = + (config->m_topic == nullptr) || + (config->m_topic[0] == '\0'); + + if (emptyTopic && (!isValidTopicId(config->m_topicId))) { + errorLog("Neither topic nor pre-defined topic ID are provided in PUBLISH configuration."); + return CC_MqttsnErrorCode_BadParam; + } + + if (static_castm_qos)>(Config::MaxQos) < config->m_qos) { + errorLog("Bad publish qos value."); + return CC_MqttsnErrorCode_BadParam; + } + + if ((0U < config->m_dataLen) && (config->m_data == nullptr)) { + errorLog("Bad publish message data."); + return CC_MqttsnErrorCode_BadParam; + } + + if ((!emptyTopic) && (!verifySubFilter(config->m_topic))) { + errorLog("Bad topic filter format in publish."); + return CC_MqttsnErrorCode_BadParam; + } + + m_publishMsg.field_flags().field_qos().setValue(config->m_qos); + m_publishMsg.field_flags().field_mid().setBitValue_Retain(config->m_retain); + + if (0U < config->m_dataLen) { + comms::util::assign(m_publishMsg.field_data().value(), config->m_data, config->m_data + config->m_dataLen); + } + else { + m_publishMsg.field_data().value().clear(); + } + + using TopicIdType = PublishMsg::Field_flags::Field_topicIdType::ValueType; + do { + if (emptyTopic) { + m_publishMsg.field_topicId().setValue(config->m_topicId); + m_publishMsg.field_flags().field_topicIdType().value() = TopicIdType::PredefinedTopicId; + m_stage = Stage_Publish; + break; + } + + if (isShortTopic(config->m_topic)) { + auto topicId = + (static_cast(config->m_topic[0]) << 8U) | + (static_cast(config->m_topic[1])); + m_publishMsg.field_topicId().setValue(topicId); + m_publishMsg.field_flags().field_topicIdType().value() = TopicIdType::ShortTopicName; + m_stage = Stage_Publish; + } + + auto& regMap = client().reuseState().m_outRegTopics; + auto iter = + std::lower_bound( + regMap.begin(), regMap.end(), config->m_topic, + [](auto& elem, const char* topicParam) + { + return elem.m_topic < topicParam; + }); + + if ((iter != regMap.end()) && (iter->m_topic == config->m_topic)) { + m_publishMsg.field_topicId().setValue(iter->m_topicId); + m_publishMsg.field_flags().field_topicIdType().value() = TopicIdType::Normal; + m_stage = Stage_Publish; + break; + } + + m_registerMsg.field_topicName().value() = config->m_topic; + m_stage = Stage_Register; + } while (false); + + return CC_MqttsnErrorCode_Success; +} + +CC_MqttsnErrorCode SendOp::send(CC_MqttsnPublishCompleteCb cb, void* cbData) +{ + client().allowNextPrepare(); + auto completeOnError = + comms::util::makeScopeGuard( + [this]() + { + opComplete(); + }); + + if (cb == nullptr) { + errorLog("Publish completion callback is not provided."); + return CC_MqttsnErrorCode_BadParam; + } + + if (!m_timer.isValid()) { + errorLog("The library cannot allocate required number of timers."); + return CC_MqttsnErrorCode_InternalError; + } + + auto guard = client().apiEnter(); + m_cb = cb; + m_cbData = cbData; + + if (m_stage == Stage_Register) { + m_registerMsg.field_msgId().setValue(allocPacketId()); + } + + using QoS = PublishMsg::Field_flags::Field_qos::ValueType; + if (QoS::AtMostOnceDelivery < m_publishMsg.field_flags().field_qos().value()) { + m_publishMsg.field_msgId().setValue(allocPacketId()); + } + + m_origRetryCount = getRetryCount(); + + auto ec = sendInternal(); + if (ec != CC_MqttsnErrorCode_Success) { + return ec; + } + + completeOnError.release(); + return CC_MqttsnErrorCode_Success; +} + +CC_MqttsnErrorCode SendOp::cancel() +{ + if (m_cb == nullptr) { + // hasn't been sent yet + client().allowNextPrepare(); + } + + opComplete(); + return CC_MqttsnErrorCode_Success; +} + +void SendOp::proceedWithReg() +{ + COMMS_ASSERT(isRegPending()); + COMMS_ASSERT(!client().sessionState().m_topicRegInProgress); + auto ec = sendInternal(); + if (ec != CC_MqttsnErrorCode_Success) { + completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); + return; + } +} + +void SendOp::handle(RegackMsg& msg) +{ + if ((!m_registerInProgress) || (msg.field_msgId().value() != m_registerMsg.field_msgId().value())) { + errorLog("Unexpected REGACK message, ignoring"); + return; + } + + auto topicId = msg.field_topicId().value(); + if (!isValidTopicId(topicId)) { + errorLog("Unexpected topic ID in REGACK message, ignoring"); + return; + } + + auto& regMap = client().reuseState().m_outRegTopics; + auto& topicStr = m_registerMsg.field_topicName().value(); + COMMS_ASSERT(!topicStr.empty()); + + auto iter = + std::lower_bound( + regMap.begin(), regMap.end(), topicStr, + [](auto& elem, auto& topicParam) + { + return elem.m_topic < topicParam; + }); + + do { + if ((iter == regMap.end()) || (iter->m_topic != topicStr)) { + regMap.emplace(iter, topicStr.c_str(), topicId); + break; + } + + iter->m_topicId = topicId; + } while (false); + + m_registerInProgress = false; + client().sessionState().m_topicRegInProgress = false; + client().allowNextRegister(); + m_publishMsg.field_topicId().setValue(topicId); + m_stage = Stage_Publish; + setRetryCount(m_origRetryCount); + auto ec = sendInternal(); + if (ec != CC_MqttsnErrorCode_Success) { + completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); + return; + } +} + +#if CC_MQTTSN_CLIENT_MAX_QOS > 0 +void SendOp::handle(PubackMsg& msg) +{ + if ((m_stage < Stage_Publish) || + (msg.field_msgId().value() != m_publishMsg.field_msgId().value()) || + (msg.field_topicId().value() != m_publishMsg.field_topicId().value())) { + return; + } + + auto info = CC_MqttsnPublishInfo(); + info.m_returnCode = static_cast(msg.field_returnCode().value()); + completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &info); +} + +#if CC_MQTTSN_CLIENT_MAX_QOS > 1 +void SendOp::handle(PubrecMsg& msg) +{ + if ((m_stage < Stage_Publish) || + (msg.field_msgId().value() != m_publishMsg.field_msgId().value())) { + return; + } + + m_stage = Stage_Acked; + setRetryCount(m_origRetryCount); + auto ec = sendInternal(); + if (ec != CC_MqttsnErrorCode_Success) { + completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); + return; + } +} + +void SendOp::handle(PubcompMsg& msg) +{ + if ((m_stage < Stage_Acked) || + (msg.field_msgId().value() != m_publishMsg.field_msgId().value())) { + return; + } + + completeOpInternal(CC_MqttsnAsyncOpStatus_Complete); +} + +#endif // #if CC_MQTTSN_CLIENT_MAX_QOS > 1 +#endif // #if CC_MQTTSN_CLIENT_MAX_QOS > 0 + +Op::Type SendOp::typeImpl() const +{ + return Type_Send; +} + +void SendOp::terminateOpImpl(CC_MqttsnAsyncOpStatus status) +{ + completeOpInternal(status); +} + +void SendOp::completeOpInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnPublishInfo* info) +{ + auto handle = asHandle(this); + auto cb = m_cb; + auto* cbData = m_cbData; + opComplete(); // mustn't access data members after destruction + if (cb != nullptr) { + cb(cbData, handle, status, info); + } +} + +void SendOp::restartTimer() +{ + m_timer.wait(getRetryPeriod(), &SendOp::opTimeoutCb, this); +} + +CC_MqttsnErrorCode SendOp::sendInternal() +{ + using SendFunc = CC_MqttsnErrorCode (SendOp::*)(); + static const SendFunc Map[] = { + /* Stage_Register */ &SendOp::sendInternal_Register, + /* Stage_Publish */ &SendOp::sendInternal_Publish, + /* Stage_Acked */ &SendOp::sendInternal_Pubrel, + }; + static const std::size_t MapSize = std::extent::value; + static_assert(MapSize == Stage_ValuesLimit); + + auto idx = static_cast(m_stage); + COMMS_ASSERT(idx < MapSize); + auto func = Map[idx]; + return (this->*func)(); +} + +CC_MqttsnErrorCode SendOp::sendInternal_Register() +{ + if ((client().sessionState().m_topicRegInProgress) && (!m_registerInProgress)) { + return CC_MqttsnErrorCode_Success; // Wait fore other registration is complete + } + + client().sessionState().m_topicRegInProgress = true; + m_registerInProgress = true; + + auto ec = sendMessage(m_registerMsg); + if (ec == CC_MqttsnErrorCode_Success) { + restartTimer(); + } + + return ec; +} + +CC_MqttsnErrorCode SendOp::sendInternal_Publish() +{ + COMMS_ASSERT(isValidTopicId(m_publishMsg.field_topicId().value())); + + if constexpr (0 < Config::MaxQos) { + if (getRetryCount() < m_origRetryCount) { + m_publishMsg.field_flags().field_high().setBitValue_Dup(true); + } + } + + auto ec = sendMessage(m_publishMsg); + if (ec != CC_MqttsnErrorCode_Success) { + return ec; + } + + using QoS = PublishMsg::Field_flags::Field_qos::ValueType; + if (QoS::AtMostOnceDelivery == m_publishMsg.field_flags().field_qos().value()) { + completeOpInternal(CC_MqttsnAsyncOpStatus_Complete); + return CC_MqttsnErrorCode_Success; + } + + COMMS_ASSERT(m_publishMsg.field_msgId().value() > 0U); + restartTimer(); + return CC_MqttsnErrorCode_Success; +} + +CC_MqttsnErrorCode SendOp::sendInternal_Pubrel() +{ + if constexpr (2 <= Config::MaxQos) { + PubrelMsg pubrelMsg; + pubrelMsg.field_msgId().setValue(m_publishMsg.field_msgId().value()); + auto ec = sendMessage(m_publishMsg); + if (ec == CC_MqttsnErrorCode_Success) { + restartTimer(); + } + + return ec; + } + else { + return CC_MqttsnErrorCode_NotSupported; + } +} + +void SendOp::timeoutInternal() +{ + if (getRetryCount() == 0U) { + errorLog("All retries of the publish operation have been exhausted."); + completeOpInternal(CC_MqttsnAsyncOpStatus_Timeout); + return; + } + + auto ec = sendInternal(); + if (ec != CC_MqttsnErrorCode_Success) { + completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); + return; + } + + decRetryCount(); +} + +void SendOp::opTimeoutCb(void* data) +{ + asSendOp(data)->timeoutInternal(); +} + +} // namespace op + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/op/SendOp.h b/client/lib/src/op/SendOp.h new file mode 100644 index 00000000..295f32bb --- /dev/null +++ b/client/lib/src/op/SendOp.h @@ -0,0 +1,86 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "op/Op.h" +#include "ExtConfig.h" +#include "ProtocolDefs.h" + +#include "TimerMgr.h" + +namespace cc_mqttsn_client +{ + +namespace op +{ + +class SendOp final : public Op +{ + using Base = Op; +public: + + explicit SendOp(ClientImpl& client); + virtual ~SendOp(); + + CC_MqttsnErrorCode config(const CC_MqttsnPublishConfig* config); + CC_MqttsnErrorCode send(CC_MqttsnPublishCompleteCb cb, void* cbData); + CC_MqttsnErrorCode cancel(); + void proceedWithReg(); + + bool isRegPending() const + { + return (m_stage == Stage_Register) && (!m_registerInProgress); + } + + using Base::handle; + void handle(RegackMsg& msg) override; +#if CC_MQTTSN_CLIENT_MAX_QOS > 0 + void handle(PubackMsg& msg) override; + void handle(PubrecMsg& msg) override; + void handle(PubcompMsg& msg) override; +#endif // #if CC_MQTTSN_CLIENT_MAX_QOS > 0 + +protected: + virtual Type typeImpl() const override; + virtual void terminateOpImpl(CC_MqttsnAsyncOpStatus status) override; + +private: + enum Stage + { + Stage_Register, + Stage_Publish, + Stage_Acked, + Stage_ValuesLimit + }; + + void completeOpInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnPublishInfo* info = nullptr); + void restartTimer(); + CC_MqttsnErrorCode sendInternal(); + CC_MqttsnErrorCode sendInternal_Register(); + CC_MqttsnErrorCode sendInternal_Publish(); + CC_MqttsnErrorCode sendInternal_Pubrel(); + void timeoutInternal(); + + static void opTimeoutCb(void* data); + + RegisterMsg m_registerMsg; + PublishMsg m_publishMsg; + TimerMgr::Timer m_timer; + CC_MqttsnPublishCompleteCb m_cb = nullptr; + void* m_cbData = nullptr; + Stage m_stage = Stage_Register; + unsigned m_origRetryCount = 0U; + bool m_registerInProgress = false; + + static_assert(ExtConfig::SendOpTimers == 1U); +}; + +} // namespace op + + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/op/SubscribeOp.cpp b/client/lib/src/op/SubscribeOp.cpp index 9399cb90..837cdfc0 100644 --- a/client/lib/src/op/SubscribeOp.cpp +++ b/client/lib/src/op/SubscribeOp.cpp @@ -43,6 +43,11 @@ SubscribeOp::SubscribeOp(ClientImpl& client) : { } +SubscribeOp::~SubscribeOp() +{ + releasePacketId(m_subscribeMsg.field_msgId().value()); +} + CC_MqttsnErrorCode SubscribeOp::config(const CC_MqttsnSubscribeConfig* config) { if (config == nullptr) { diff --git a/client/lib/src/op/SubscribeOp.h b/client/lib/src/op/SubscribeOp.h index a99f0a01..9945c871 100644 --- a/client/lib/src/op/SubscribeOp.h +++ b/client/lib/src/op/SubscribeOp.h @@ -24,6 +24,7 @@ class SubscribeOp final : public Op using Base = Op; public: explicit SubscribeOp(ClientImpl& client); + virtual ~SubscribeOp(); CC_MqttsnErrorCode config(const CC_MqttsnSubscribeConfig* config); CC_MqttsnErrorCode send(CC_MqttsnSubscribeCompleteCb cb, void* cbData); diff --git a/client/lib/src/op/UnsubscribeOp.cpp b/client/lib/src/op/UnsubscribeOp.cpp index a907b486..021cd52c 100644 --- a/client/lib/src/op/UnsubscribeOp.cpp +++ b/client/lib/src/op/UnsubscribeOp.cpp @@ -43,6 +43,11 @@ UnsubscribeOp::UnsubscribeOp(ClientImpl& client) : { } +UnsubscribeOp::~UnsubscribeOp() +{ + releasePacketId(m_unsubscribeMsg.field_msgId().value()); +} + CC_MqttsnErrorCode UnsubscribeOp::config(const CC_MqttsnUnsubscribeConfig* config) { if (config == nullptr) { diff --git a/client/lib/src/op/UnsubscribeOp.h b/client/lib/src/op/UnsubscribeOp.h index 2de880c8..c5da604b 100644 --- a/client/lib/src/op/UnsubscribeOp.h +++ b/client/lib/src/op/UnsubscribeOp.h @@ -24,6 +24,7 @@ class UnsubscribeOp final : public Op using Base = Op; public: explicit UnsubscribeOp(ClientImpl& client); + virtual ~UnsubscribeOp(); CC_MqttsnErrorCode config(const CC_MqttsnUnsubscribeConfig* config); CC_MqttsnErrorCode send(CC_MqttsnUnsubscribeCompleteCb cb, void* cbData); diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index 4c355a76..ae9055c1 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -20,6 +20,7 @@ struct CC_MqttsnConnect {}; struct CC_MqttsnDisconnect {}; struct CC_MqttsnSubscribe {}; struct CC_MqttsnUnsubscribe {}; +struct CC_MqttsnPublish {}; namespace { @@ -92,6 +93,16 @@ inline CC_MqttsnUnsubscribeHandle handleFromUnsubscribeOp(cc_mqttsn_client::op:: return reinterpret_cast(op); } +inline cc_mqttsn_client::op::SendOp* sendOpFromHandle(CC_MqttsnPublishHandle handle) +{ + return reinterpret_cast(handle); +} + +inline CC_MqttsnPublishHandle handleFromSendOp(cc_mqttsn_client::op::SendOp* op) +{ + return reinterpret_cast(op); +} + } // namespace CC_MqttsnClientHandle cc_mqttsn_##NAME##client_alloc() @@ -905,6 +916,98 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_unsubscribe( return cc_mqttsn_##NAME##client_unsubscribe_send(unsubscribe, cb, cbData); } +CC_MqttsnPublishHandle cc_mqttsn_##NAME##client_publish_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec) +{ + COMMS_ASSERT(client != nullptr); + return handleFromSendOp(clientFromHandle(client)->publishPrepare(ec)); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_publish_set_retry_period(CC_MqttsnPublishHandle handle, unsigned ms) +{ + COMMS_ASSERT(handle != nullptr); + if (ms == 0U) { + sendOpFromHandle(handle)->client().errorLog("The retry period must be greater than 0"); + return CC_MqttsnErrorCode_BadParam; + } + + COMMS_ASSERT(handle != nullptr); + sendOpFromHandle(handle)->setRetryPeriod(ms); + return CC_MqttsnErrorCode_Success; +} + +unsigned cc_mqttsn_##NAME##client_publish_get_retry_period(CC_MqttsnPublishHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return sendOpFromHandle(handle)->getRetryPeriod(); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_publish_set_retry_count(CC_MqttsnPublishHandle handle, unsigned count) +{ + COMMS_ASSERT(handle != nullptr); + sendOpFromHandle(handle)->setRetryCount(count); + return CC_MqttsnErrorCode_Success; +} + +unsigned cc_mqttsn_##NAME##client_publish_get_retry_count(CC_MqttsnPublishHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return sendOpFromHandle(handle)->getRetryCount(); +} + +void cc_mqttsn_##NAME##client_publish_init_config(CC_MqttsnPublishConfig* config) +{ + COMMS_ASSERT(config != nullptr); + *config = CC_MqttsnPublishConfig(); + config->m_qos = static_castm_qos)>(cc_mqttsn_client::Config::MaxQos); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_publish_config(CC_MqttsnPublishHandle handle, const CC_MqttsnPublishConfig* config) +{ + COMMS_ASSERT(handle != nullptr); + return sendOpFromHandle(handle)->config(config); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_publish_send(CC_MqttsnPublishHandle handle, CC_MqttsnPublishCompleteCb cb, void* cbData) +{ + COMMS_ASSERT(handle != nullptr); + return sendOpFromHandle(handle)->send(cb, cbData); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_publish_cancel(CC_MqttsnPublishHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return sendOpFromHandle(handle)->cancel(); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_publish( + CC_MqttsnClientHandle client, + const CC_MqttsnPublishConfig* config, + CC_MqttsnPublishCompleteCb cb, + void* cbData) +{ + auto ec = CC_MqttsnErrorCode_Success; + auto publish = cc_mqttsn_##NAME##client_publish_prepare(client, &ec); + if (publish == nullptr) { + return ec; + } + + auto cancelOnExitGuard = + comms::util::makeScopeGuard( + [publish]() + { + [[maybe_unused]] auto ecTmp = cc_mqttsn_##NAME##client_publish_cancel(publish); + }); + + if (config != nullptr) { + ec = cc_mqttsn_##NAME##client_publish_config(publish, config); + if (ec != CC_MqttsnErrorCode_Success) { + return ec; + } + } + + cancelOnExitGuard.release(); + return cc_mqttsn_##NAME##client_publish_send(publish, cb, cbData); +} // --------------------- Callbacks --------------------- diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index 6c73f87d..62b7ca1f 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -680,6 +680,93 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_unsubscribe( CC_MqttsnUnsubscribeCompleteCb cb, void* cbData); +/// @brief Prepare "publish" operation. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[out] ec Error code reporting result of the operation. Can be NULL. +/// @return Handle of the "publish" operation, will be NULL in case of failure. To analyze the reason failure use "ec" output parameter. +/// @post The "publish" operation is allocated, use either @ref cc_mqttsn_##NAME##client_publish_send() +/// or @ref cc_mqttsn_##NAME##client_publish_cancel() to prevent memory leaks. +/// @ingroup publish +CC_MqttsnPublishHandle cc_mqttsn_##NAME##client_publish_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec); + +/// @brief Configure the retry period for the "publish" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_publish_prepare() function. +/// @param[in] ms Retry period in @b milliseconds. +/// @return Result code of the call. +/// @ingroup publish +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_publish_set_retry_period(CC_MqttsnPublishHandle handle, unsigned ms); + +/// @brief Retrieve the configured retry period for the "publish" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_publish_prepare() function. +/// @return Retry period duration in @b milliseconds. +/// @ingroup publish +unsigned cc_mqttsn_##NAME##client_publish_get_retry_period(CC_MqttsnPublishHandle handle); + +/// @brief Configure the retry count for the "publish" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_publish_prepare() function. +/// @param[in] count Number of retries. +/// @return Result code of the call. +/// @ingroup publish +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_publish_set_retry_count(CC_MqttsnPublishHandle handle, unsigned count); + +/// @brief Retrieve the configured retry count for the "publish" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_publish_prepare() function. +/// @return Number of retries. +/// @ingroup publish +unsigned cc_mqttsn_##NAME##client_publish_get_retry_count(CC_MqttsnPublishHandle handle); + +/// @brief Intialize the @ref CC_MqttsnPublishConfig configuration structure. +/// @param[out] config Configuration structure. Must not be NULL. +/// @ingroup publish +void cc_mqttsn_##NAME##client_publish_init_config(CC_MqttsnPublishConfig* config); + +/// @brief Perform configuration of the "publish" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_publish_prepare() function. +/// @param[in] config Configuration structure. Must NOT be NULL. Does not need to be preserved after invocation. +/// @return Result code of the call. +/// @ingroup publish +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_publish_config(CC_MqttsnPublishHandle handle, const CC_MqttsnPublishConfig* config); + +/// @brief Send the "publish" operation +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_publish_prepare() function. +/// @param[in] cb Callback to be invoked when "publish" operation is complete. +/// @param[in] cbData Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +/// @return Result code of the call. +/// @post The handle of the "publish" operation can be discarded. +/// @post The provided callback will be invoked when the "publish" operation is complete if and only if +/// the function returns @ref CC_MqttsnErrorCode_Success. +/// @ingroup publish +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_publish_send(CC_MqttsnPublishHandle handle, CC_MqttsnPublishCompleteCb cb, void* cbData); + +/// @brief Cancel the allocated "publish" operation +/// @details In case the @ref cc_mqttsn_##NAME##client_publish_send() function was successfully called before, +/// the operation is cancelled @b without callback invocation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_publish_prepare() function. +/// @return Result code of the call. +/// @post The handle of the "publish" operation is no longer valid and must be discarded. +/// @ingroup publish +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_publish_cancel(CC_MqttsnPublishHandle handle); + +/// @brief Prepare and send "publish" request in one go +/// @details Abstracts away sequence of the following functions invocation: +/// @li @ref cc_mqttsn_##NAME##client_publish_prepare() +/// @li @ref cc_mqttsn_##NAME##client_publish_config() +/// @li @ref cc_mqttsn_##NAME##client_publish_send() +/// +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[in] config Subscription configuration. +/// @param[in] cb Callback to be invoked when "publish" operation is complete. +/// @param[in] cbData Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +/// @return Result code of the call. +/// @ingroup publish +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_publish( + CC_MqttsnClientHandle client, + const CC_MqttsnPublishConfig* config, + CC_MqttsnPublishCompleteCb cb, + void* cbData); + // --------------------- Callbacks --------------------- From ea4acf1d8ba7e1dae6dff29e382160cf0ecfcd17 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 19 Jul 2024 09:17:31 +1000 Subject: [PATCH 070/106] Preliminary unit testing infrastructure for publish operation in the client library. --- client/lib/templ/client.cpp.templ | 1 - client/lib/test/CMakeLists.txt | 2 +- client/lib/test/UnitTestCommonBase.cpp | 78 ++++++++++++++++++++++++ client/lib/test/UnitTestCommonBase.h | 48 ++++++++++++++- client/lib/test/UnitTestDefaultBase.cpp | 10 ++++ client/lib/test/UnitTestPublish.th | 79 +++++++++++++++++++++++++ client/lib/test/UnitTestSubscribe.th | 6 +- client/lib/test/UnitTestUnsubscribe.th | 6 +- 8 files changed, 221 insertions(+), 9 deletions(-) create mode 100644 client/lib/test/UnitTestPublish.th diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index ae9055c1..17e74e78 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -958,7 +958,6 @@ void cc_mqttsn_##NAME##client_publish_init_config(CC_MqttsnPublishConfig* config { COMMS_ASSERT(config != nullptr); *config = CC_MqttsnPublishConfig(); - config->m_qos = static_castm_qos)>(cc_mqttsn_client::Config::MaxQos); } CC_MqttsnErrorCode cc_mqttsn_##NAME##client_publish_config(CC_MqttsnPublishHandle handle, const CC_MqttsnPublishConfig* config) diff --git a/client/lib/test/CMakeLists.txt b/client/lib/test/CMakeLists.txt index 1d351ce5..c1f2fc93 100644 --- a/client/lib/test/CMakeLists.txt +++ b/client/lib/test/CMakeLists.txt @@ -42,7 +42,7 @@ if (TARGET cc::cc_mqttsn_client) cc_mqttsn_client_add_unit_test(UnitTestGwDiscover ${DEFAULT_BASE_LIB_NAME}) cc_mqttsn_client_add_unit_test(UnitTestConnect ${DEFAULT_BASE_LIB_NAME}) cc_mqttsn_client_add_unit_test(UnitTestDisconnect ${DEFAULT_BASE_LIB_NAME}) -# cc_mqttsn_client_add_unit_test(UnitTestPublish ${DEFAULT_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(UnitTestPublish ${DEFAULT_BASE_LIB_NAME}) # cc_mqttsn_client_add_unit_test(UnitTestReceive ${DEFAULT_BASE_LIB_NAME}) cc_mqttsn_client_add_unit_test(UnitTestSubscribe ${DEFAULT_BASE_LIB_NAME}) cc_mqttsn_client_add_unit_test(UnitTestUnsubscribe ${DEFAULT_BASE_LIB_NAME}) diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index 79d4ce51..183ccf58 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -101,6 +101,16 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_unsubscribe_send != nullptr); test_assert(m_funcs.m_unsubscribe_cancel != nullptr); test_assert(m_funcs.m_unsubscribe != nullptr); + test_assert(m_funcs.m_publish_prepare != nullptr); + test_assert(m_funcs.m_publish_set_retry_period != nullptr); + test_assert(m_funcs.m_publish_get_retry_period != nullptr); + test_assert(m_funcs.m_publish_set_retry_count != nullptr); + test_assert(m_funcs.m_publish_get_retry_count != nullptr); + test_assert(m_funcs.m_publish_init_config != nullptr); + test_assert(m_funcs.m_publish_config != nullptr); + test_assert(m_funcs.m_publish_send != nullptr); + test_assert(m_funcs.m_publish_cancel != nullptr); + test_assert(m_funcs.m_publish != nullptr); test_assert(m_funcs.m_set_next_tick_program_callback != nullptr); test_assert(m_funcs.m_set_cancel_next_tick_wait_callback != nullptr); @@ -188,6 +198,21 @@ UnitTestCommonBase::UnitTestUnsubscribeCompleteReport::UnitTestUnsubscribeComple { } +UnitTestCommonBase::UnitTestPublishInfo& UnitTestCommonBase::UnitTestPublishInfo::operator=(const CC_MqttsnPublishInfo& info) +{ + m_returnCode = info.m_returnCode; + return *this; +} + +UnitTestCommonBase::UnitTestPublishCompleteReport::UnitTestPublishCompleteReport(CC_MqttsnPublishHandle handle, CC_MqttsnAsyncOpStatus status, const CC_MqttsnPublishInfo* info) : + m_handle(handle), + m_status(status) +{ + if (info != nullptr) { + m_info = *info; + } +} + void UnitTestCommonBase::unitTestSetUp() { } @@ -643,6 +668,28 @@ CC_MqttsnErrorCode UnitTestCommonBase::unitTestUnsubscribeSend(CC_MqttsnUnsubscr return m_funcs.m_unsubscribe_send(unsubscribe, &UnitTestCommonBase::unitTestUnsubscribeCompleteCb, this); } +bool UnitTestCommonBase::unitTestHasPublishCompleteReport() const +{ + return !m_data.m_publishCompleteReports.empty(); +} + +UnitTestCommonBase::UnitTestPublishCompleteReportPtr UnitTestCommonBase::unitTestPublishCompleteReport(bool mustExist) +{ + if (!unitTestHasPublishCompleteReport()) { + test_assert(!mustExist); + return UnitTestPublishCompleteReportPtr(); + } + + auto ptr = std::move(m_data.m_publishCompleteReports.front()); + m_data.m_publishCompleteReports.pop_front(); + return ptr; +} + +CC_MqttsnErrorCode UnitTestCommonBase::unitTestPublishSend(CC_MqttsnPublishHandle publish) +{ + return m_funcs.m_publish_send(publish, &UnitTestCommonBase::unitTestPublishCompleteCb, this); +} + void UnitTestCommonBase::apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen) { m_funcs.m_process_data(client, buf, bufLen); @@ -826,6 +873,31 @@ CC_MqttsnErrorCode UnitTestCommonBase::apiUnsubscribeCancel(CC_MqttsnUnsubscribe return m_funcs.m_unsubscribe_cancel(unsubscribe); } +CC_MqttsnPublishHandle UnitTestCommonBase::apiPublishPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec) +{ + return m_funcs.m_publish_prepare(client, ec); +} + +CC_MqttsnErrorCode UnitTestCommonBase::apiPublishSetRetryCount(CC_MqttsnPublishHandle publish, unsigned count) +{ + return m_funcs.m_publish_set_retry_count(publish, count); +} + +void UnitTestCommonBase::apiPublishInitConfig(CC_MqttsnPublishConfig* config) +{ + m_funcs.m_publish_init_config(config); +} + +CC_MqttsnErrorCode UnitTestCommonBase::apiPublishConfig(CC_MqttsnPublishHandle publish, const CC_MqttsnPublishConfig* config) +{ + return m_funcs.m_publish_config(publish, config); +} + +CC_MqttsnErrorCode UnitTestCommonBase::apiPublishCancel(CC_MqttsnPublishHandle publish) +{ + return m_funcs.m_publish_cancel(publish); +} + void UnitTestCommonBase::unitTestMessageReportCb(void* data, const CC_MqttsnMessageInfo* msgInfo) { // TODO: @@ -898,4 +970,10 @@ void UnitTestCommonBase::unitTestUnsubscribeCompleteCb(void* data, CC_MqttsnUnsu { auto* thisPtr = asThis(data); thisPtr->m_data.m_unsubscribeCompleteReports.push_back(std::make_unique(handle, status)); +} + +void UnitTestCommonBase::unitTestPublishCompleteCb(void* data, CC_MqttsnPublishHandle handle, CC_MqttsnAsyncOpStatus status, const CC_MqttsnPublishInfo* info) +{ + auto* thisPtr = asThis(data); + thisPtr->m_data.m_publishCompleteReports.push_back(std::make_unique(handle, status, info)); } \ No newline at end of file diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index 86f43f6f..aaddd129 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -91,7 +91,16 @@ class UnitTestCommonBase CC_MqttsnErrorCode (*m_unsubscribe_send)(CC_MqttsnUnsubscribeHandle, CC_MqttsnUnsubscribeCompleteCb, void*) = nullptr; CC_MqttsnErrorCode (*m_unsubscribe_cancel)(CC_MqttsnUnsubscribeHandle) = nullptr; CC_MqttsnErrorCode (*m_unsubscribe)(CC_MqttsnClientHandle, const CC_MqttsnUnsubscribeConfig*, CC_MqttsnUnsubscribeCompleteCb, void* cbData) = nullptr; - + CC_MqttsnPublishHandle (*m_publish_prepare)(CC_MqttsnClientHandle, CC_MqttsnErrorCode*) = nullptr; + CC_MqttsnErrorCode (*m_publish_set_retry_period)(CC_MqttsnPublishHandle, unsigned) = nullptr; + unsigned (*m_publish_get_retry_period)(CC_MqttsnPublishHandle) = nullptr; + CC_MqttsnErrorCode (*m_publish_set_retry_count)(CC_MqttsnPublishHandle, unsigned) = nullptr; + unsigned (*m_publish_get_retry_count)(CC_MqttsnPublishHandle) = nullptr; + void (*m_publish_init_config)(CC_MqttsnPublishConfig*) = nullptr; + CC_MqttsnErrorCode (*m_publish_config)(CC_MqttsnPublishHandle, const CC_MqttsnPublishConfig*) = nullptr; + CC_MqttsnErrorCode (*m_publish_send)(CC_MqttsnPublishHandle, CC_MqttsnPublishCompleteCb, void*) = nullptr; + CC_MqttsnErrorCode (*m_publish_cancel)(CC_MqttsnPublishHandle) = nullptr; + CC_MqttsnErrorCode (*m_publish)(CC_MqttsnClientHandle, const CC_MqttsnPublishConfig*, CC_MqttsnPublishCompleteCb, void* cbData) = nullptr; void (*m_set_next_tick_program_callback)(CC_MqttsnClientHandle, CC_MqttsnNextTickProgramCb, void*) = nullptr; void (*m_set_cancel_next_tick_wait_callback)(CC_MqttsnClientHandle, CC_MqttsnCancelNextTickWaitCb, void*) = nullptr; @@ -259,6 +268,30 @@ class UnitTestCommonBase using UnitTestUnsubscribeCompleteReportPtr = std::unique_ptr; using UnitTestUnsubscribeCompleteReportList = std::list; + struct UnitTestPublishInfo + { + CC_MqttsnReturnCode m_returnCode = CC_MqttsnReturnCode_ValuesLimit; + + UnitTestPublishInfo() = default; + UnitTestPublishInfo(const UnitTestPublishInfo&) = default; + UnitTestPublishInfo& operator=(const UnitTestPublishInfo&) = default; + UnitTestPublishInfo& operator=(const CC_MqttsnPublishInfo& info); + }; + + struct UnitTestPublishCompleteReport + { + CC_MqttsnPublishHandle m_handle = nullptr; + CC_MqttsnAsyncOpStatus m_status = CC_MqttsnAsyncOpStatus_ValuesLimit; + UnitTestPublishInfo m_info; + + UnitTestPublishCompleteReport(CC_MqttsnPublishHandle handle, CC_MqttsnAsyncOpStatus status, const CC_MqttsnPublishInfo* info); + UnitTestPublishCompleteReport(UnitTestPublishCompleteReport&&) = default; + UnitTestPublishCompleteReport& operator=(const UnitTestPublishCompleteReport&) = default; + }; + + using UnitTestPublishCompleteReportPtr = std::unique_ptr; + using UnitTestPublishCompleteReportList = std::list; + using UnitTestClientPtr = std::unique_ptr; void unitTestSetUp(); @@ -317,6 +350,11 @@ class UnitTestCommonBase CC_MqttsnErrorCode unitTestUnsubscribeSend(CC_MqttsnUnsubscribeHandle unsubscribe); + bool unitTestHasPublishCompleteReport() const; + UnitTestPublishCompleteReportPtr unitTestPublishCompleteReport(bool mustExist = true); + + CC_MqttsnErrorCode unitTestPublishSend(CC_MqttsnPublishHandle publish); + void apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen); CC_MqttsnErrorCode apiSetDefaultRetryPeriod(CC_MqttsnClient* client, unsigned value); CC_MqttsnErrorCode apiSetDefaultRetryCount(CC_MqttsnClient* client, unsigned value); @@ -352,6 +390,12 @@ class UnitTestCommonBase CC_MqttsnErrorCode apiUnsubscribeConfig(CC_MqttsnUnsubscribeHandle unsubscribe, const CC_MqttsnUnsubscribeConfig* config); CC_MqttsnErrorCode apiUnsubscribeCancel(CC_MqttsnUnsubscribeHandle unsubscribe); + CC_MqttsnPublishHandle apiPublishPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec = nullptr); + CC_MqttsnErrorCode apiPublishSetRetryCount(CC_MqttsnPublishHandle publish, unsigned count); + void apiPublishInitConfig(CC_MqttsnPublishConfig* config); + CC_MqttsnErrorCode apiPublishConfig(CC_MqttsnPublishHandle publish, const CC_MqttsnPublishConfig* config); + CC_MqttsnErrorCode apiPublishCancel(CC_MqttsnPublishHandle publish); + protected: explicit UnitTestCommonBase(const LibFuncs& funcs); @@ -370,6 +414,7 @@ class UnitTestCommonBase UnitTestDisconnectCompleteReportList m_disconnectCompleteReports; UnitTestSubscribeCompleteReportList m_subscribeCompleteReports; UnitTestUnsubscribeCompleteReportList m_unsubscribeCompleteReports; + UnitTestPublishCompleteReportList m_publishCompleteReports; }; static void unitTestTickProgramCb(void* data, unsigned duration); @@ -385,6 +430,7 @@ class UnitTestCommonBase static void unitTestDisconnectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status); static void unitTestSubscribeCompleteCb(void* data, CC_MqttsnSubscribeHandle handle, CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info); static void unitTestUnsubscribeCompleteCb(void* data, CC_MqttsnUnsubscribeHandle handle, CC_MqttsnAsyncOpStatus status); + static void unitTestPublishCompleteCb(void* data, CC_MqttsnPublishHandle handle, CC_MqttsnAsyncOpStatus status, const CC_MqttsnPublishInfo* info); LibFuncs m_funcs; ClientData m_data; diff --git a/client/lib/test/UnitTestDefaultBase.cpp b/client/lib/test/UnitTestDefaultBase.cpp index 7ba6601e..eeb10718 100644 --- a/client/lib/test/UnitTestDefaultBase.cpp +++ b/client/lib/test/UnitTestDefaultBase.cpp @@ -82,6 +82,16 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_unsubscribe_send = &cc_mqttsn_client_unsubscribe_send; funcs.m_unsubscribe_cancel = &cc_mqttsn_client_unsubscribe_cancel; funcs.m_unsubscribe = &cc_mqttsn_client_unsubscribe; + funcs.m_publish_prepare = &cc_mqttsn_client_publish_prepare; + funcs.m_publish_set_retry_period = &cc_mqttsn_client_publish_set_retry_period; + funcs.m_publish_get_retry_period = &cc_mqttsn_client_publish_get_retry_period; + funcs.m_publish_set_retry_count = &cc_mqttsn_client_publish_set_retry_count; + funcs.m_publish_get_retry_count = &cc_mqttsn_client_publish_get_retry_count; + funcs.m_publish_init_config = &cc_mqttsn_client_publish_init_config; + funcs.m_publish_config = &cc_mqttsn_client_publish_config; + funcs.m_publish_send = &cc_mqttsn_client_publish_send; + funcs.m_publish_cancel = &cc_mqttsn_client_publish_cancel; + funcs.m_publish = &cc_mqttsn_client_publish; funcs.m_set_next_tick_program_callback = &cc_mqttsn_client_set_next_tick_program_callback; funcs.m_set_cancel_next_tick_wait_callback = &cc_mqttsn_client_set_cancel_next_tick_wait_callback; diff --git a/client/lib/test/UnitTestPublish.th b/client/lib/test/UnitTestPublish.th new file mode 100644 index 00000000..27e5bda5 --- /dev/null +++ b/client/lib/test/UnitTestPublish.th @@ -0,0 +1,79 @@ +#include "UnitTestDefaultBase.h" +#include "UnitTestProtocolDefs.h" + +#include "comms/units.h" + +#include + +class UnitTestPublish : public CxxTest::TestSuite, public UnitTestDefaultBase +{ +public: + void test1(); + +private: + virtual void setUp() override + { + unitTestSetUp(); + } + + virtual void tearDown() override + { + unitTestTearDown(); + } + + using TopicIdType = UnitTestPublishMsg::Field_flags::Field_topicIdType::ValueType; +}; + +void UnitTestPublish::test1() +{ + // Testing basic Qos0 publish with pre-defined topic ID + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const CC_MqttsnTopicId TopicId = 1U; + const UnitTestData Data = {1, 2, 3, 4, 5}; + const bool Retain = true; + + CC_MqttsnPublishConfig config; + apiPublishInitConfig(&config); + TS_ASSERT_EQUALS(config.m_topic, nullptr); + TS_ASSERT_EQUALS(config.m_qos, CC_MqttsnQoS_AtMostOnceDelivery); + + config.m_topicId = TopicId; + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + config.m_retain = Retain; + + auto publish = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish, nullptr); + + auto ec = apiPublishConfig(publish, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::PredefinedTopicId); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), CC_MqttsnQoS_AtMostOnceDelivery); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + TS_ASSERT(!unitTestHasOutputData()); + + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_handle, publish); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + + TS_ASSERT(unitTestHasTickReq()); +} diff --git a/client/lib/test/UnitTestSubscribe.th b/client/lib/test/UnitTestSubscribe.th index 8b833889..8f74c9d7 100644 --- a/client/lib/test/UnitTestSubscribe.th +++ b/client/lib/test/UnitTestSubscribe.th @@ -474,7 +474,7 @@ void UnitTestSubscribe::test6() TS_ASSERT(!unitTestHasOutputData()); // Will wait until previous subscribe is complete } - TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(!unitTestHasSubscribeCompleteReport()); TS_ASSERT(unitTestHasTickReq()); unitTestTick(client, 10); @@ -530,7 +530,7 @@ void UnitTestSubscribe::test6() TS_ASSERT_DIFFERS(subMsgId2, subMsgId1); } - TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(!unitTestHasSubscribeCompleteReport()); TS_ASSERT(unitTestHasTickReq()); unitTestTick(client, 10); @@ -568,7 +568,7 @@ void UnitTestSubscribe::test6() TS_ASSERT_DIFFERS(subMsgId3, subMsgId1); } - TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(!unitTestHasSubscribeCompleteReport()); TS_ASSERT(unitTestHasTickReq()); unitTestTick(client, 10); diff --git a/client/lib/test/UnitTestUnsubscribe.th b/client/lib/test/UnitTestUnsubscribe.th index 42dc8279..09db8c1e 100644 --- a/client/lib/test/UnitTestUnsubscribe.th +++ b/client/lib/test/UnitTestUnsubscribe.th @@ -377,7 +377,7 @@ void UnitTestUnsubscribe::test5() TS_ASSERT(!unitTestHasOutputData()); // Will wait until previous unsubscribe is complete } - TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(!unitTestHasUnsubscribeCompleteReport()); TS_ASSERT(unitTestHasTickReq()); unitTestTick(client, 10); @@ -425,7 +425,7 @@ void UnitTestUnsubscribe::test5() TS_ASSERT_DIFFERS(unsubMsgId2, unsubMsgId1); } - TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(!unitTestHasUnsubscribeCompleteReport()); TS_ASSERT(unitTestHasTickReq()); unitTestTick(client, 10); @@ -459,7 +459,7 @@ void UnitTestUnsubscribe::test5() TS_ASSERT_DIFFERS(unsubMsgId3, unsubMsgId1); } - TS_ASSERT(!unitTestHasDisconnectCompleteReport()); + TS_ASSERT(!unitTestHasUnsubscribeCompleteReport()); TS_ASSERT(unitTestHasTickReq()); unitTestTick(client, 10); From 5d3ab9e69750a829bedad459a4913fede1cbb6ef Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Mon, 22 Jul 2024 09:18:21 +1000 Subject: [PATCH 071/106] More client publish unit testing. --- client/lib/src/op/ConnectOp.cpp | 3 +- client/lib/src/op/DisconnectOp.cpp | 3 +- client/lib/src/op/SearchOp.cpp | 3 +- client/lib/src/op/SendOp.cpp | 42 +- client/lib/src/op/SubscribeOp.cpp | 3 +- client/lib/src/op/UnsubscribeOp.cpp | 3 +- client/lib/test/UnitTestCommonBase.cpp | 9 + client/lib/test/UnitTestCommonBase.h | 2 + client/lib/test/UnitTestProtocolDefs.h | 1 + client/lib/test/UnitTestPublish.th | 1339 ++++++++++++++++++++++++ 10 files changed, 1391 insertions(+), 17 deletions(-) diff --git a/client/lib/src/op/ConnectOp.cpp b/client/lib/src/op/ConnectOp.cpp index 188c8b2d..20a482c0 100644 --- a/client/lib/src/op/ConnectOp.cpp +++ b/client/lib/src/op/ConnectOp.cpp @@ -279,13 +279,12 @@ void ConnectOp::timeoutInternal() return; } + decRetryCount(); auto ec = sendInternal(); if (ec != CC_MqttsnErrorCode_Success) { completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); return; } - - decRetryCount(); } const ProtMessage& ConnectOp::getConnectMsg() const diff --git a/client/lib/src/op/DisconnectOp.cpp b/client/lib/src/op/DisconnectOp.cpp index 4c6766cb..c4501a68 100644 --- a/client/lib/src/op/DisconnectOp.cpp +++ b/client/lib/src/op/DisconnectOp.cpp @@ -140,13 +140,12 @@ void DisconnectOp::timeoutInternal() return; } + decRetryCount(); auto ec = sendInternal(); if (ec != CC_MqttsnErrorCode_Success) { completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); return; } - - decRetryCount(); } void DisconnectOp::opTimeoutCb(void* data) diff --git a/client/lib/src/op/SearchOp.cpp b/client/lib/src/op/SearchOp.cpp index 70fb29ac..c3a5226c 100644 --- a/client/lib/src/op/SearchOp.cpp +++ b/client/lib/src/op/SearchOp.cpp @@ -153,13 +153,12 @@ void SearchOp::timeoutInternal() return; } + decRetryCount(); auto ec = sendInternal(); if (ec != CC_MqttsnErrorCode_Success) { completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); return; } - - decRetryCount(); } void SearchOp::opTimeoutCb(void* data) diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index de8f5610..a6956d22 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -81,7 +81,7 @@ CC_MqttsnErrorCode SendOp::config(const CC_MqttsnPublishConfig* config) return CC_MqttsnErrorCode_BadParam; } - if ((!emptyTopic) && (!verifySubFilter(config->m_topic))) { + if ((!emptyTopic) && (!verifyPubTopic(config->m_topic, true))) { errorLog("Bad topic filter format in publish."); return CC_MqttsnErrorCode_BadParam; } @@ -112,6 +112,7 @@ CC_MqttsnErrorCode SendOp::config(const CC_MqttsnPublishConfig* config) m_publishMsg.field_topicId().setValue(topicId); m_publishMsg.field_flags().field_topicIdType().value() = TopicIdType::ShortTopicName; m_stage = Stage_Publish; + break; } auto& regMap = client().reuseState().m_outRegTopics; @@ -210,6 +211,13 @@ void SendOp::handle(RegackMsg& msg) return; } + if (msg.field_returnCode().value() != RegackMsg::Field_returnCode::ValueType::Accepted) { + auto info = CC_MqttsnPublishInfo(); + comms::cast_assign(info.m_returnCode) = msg.field_returnCode().value(); + completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &info); + return; + } + auto topicId = msg.field_topicId().value(); if (!isValidTopicId(topicId)) { errorLog("Unexpected topic ID in REGACK message, ignoring"); @@ -237,11 +245,17 @@ void SendOp::handle(RegackMsg& msg) iter->m_topicId = topicId; } while (false); + auto onExitAllowRegister = + comms::util::makeScopeGuard( + [&cl = client()]() + { + cl.allowNextRegister(); + }); + m_registerInProgress = false; + m_stage = Stage_Publish; client().sessionState().m_topicRegInProgress = false; - client().allowNextRegister(); m_publishMsg.field_topicId().setValue(topicId); - m_stage = Stage_Publish; setRetryCount(m_origRetryCount); auto ec = sendInternal(); if (ec != CC_MqttsnErrorCode_Success) { @@ -260,7 +274,15 @@ void SendOp::handle(PubackMsg& msg) } auto info = CC_MqttsnPublishInfo(); - info.m_returnCode = static_cast(msg.field_returnCode().value()); + info.m_returnCode = static_cast(msg.field_returnCode().value()); + + using Qos = PublishMsg::Field_flags::Field_qos::ValueType; + if ((info.m_returnCode == CC_MqttsnReturnCode_Accepted) && + (m_publishMsg.field_flags().field_qos().value() != Qos::AtLeastOnceDelivery)) { + errorLog("Received PUBACK instead of PUBREC, ignoring..."); + return; + } + completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &info); } @@ -272,6 +294,12 @@ void SendOp::handle(PubrecMsg& msg) return; } + using Qos = PublishMsg::Field_flags::Field_qos::ValueType; + if (m_publishMsg.field_flags().field_qos().value() != Qos::ExactlyOnceDelivery) { + errorLog("Received PUBREC instead of PUBACK, ignoring..."); + return; + } + m_stage = Stage_Acked; setRetryCount(m_origRetryCount); auto ec = sendInternal(); @@ -385,7 +413,7 @@ CC_MqttsnErrorCode SendOp::sendInternal_Pubrel() if constexpr (2 <= Config::MaxQos) { PubrelMsg pubrelMsg; pubrelMsg.field_msgId().setValue(m_publishMsg.field_msgId().value()); - auto ec = sendMessage(m_publishMsg); + auto ec = sendMessage(pubrelMsg); if (ec == CC_MqttsnErrorCode_Success) { restartTimer(); } @@ -405,13 +433,13 @@ void SendOp::timeoutInternal() return; } + decRetryCount(); + auto ec = sendInternal(); if (ec != CC_MqttsnErrorCode_Success) { completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); return; } - - decRetryCount(); } void SendOp::opTimeoutCb(void* data) diff --git a/client/lib/src/op/SubscribeOp.cpp b/client/lib/src/op/SubscribeOp.cpp index 837cdfc0..0220c405 100644 --- a/client/lib/src/op/SubscribeOp.cpp +++ b/client/lib/src/op/SubscribeOp.cpp @@ -276,13 +276,12 @@ void SubscribeOp::timeoutInternal() return; } + decRetryCount(); auto ec = sendInternal(); if (ec != CC_MqttsnErrorCode_Success) { completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); return; } - - decRetryCount(); } void SubscribeOp::opTimeoutCb(void* data) diff --git a/client/lib/src/op/UnsubscribeOp.cpp b/client/lib/src/op/UnsubscribeOp.cpp index 021cd52c..28683376 100644 --- a/client/lib/src/op/UnsubscribeOp.cpp +++ b/client/lib/src/op/UnsubscribeOp.cpp @@ -297,13 +297,12 @@ void UnsubscribeOp::timeoutInternal() return; } + decRetryCount(); auto ec = sendInternal(); if (ec != CC_MqttsnErrorCode_Success) { completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); return; } - - decRetryCount(); } void UnsubscribeOp::opTimeoutCb(void* data) diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index 183ccf58..cda12574 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -261,6 +261,15 @@ void UnitTestCommonBase::unitTestPushSearchgwResponseDelay(unsigned val) m_data.m_searchgwResponseDelays.push_back(val); } +CC_MqttsnTopicId UnitTestCommonBase::unitTestShortTopicNameToId(const std::string& topic) +{ + test_assert(topic.size() == 2U); + unsigned result = + (static_cast(topic[0]) << 8U) | + (static_cast(topic[1])); + return static_cast(result); +} + bool UnitTestCommonBase::unitTestHasTickReq() const { return !m_data.m_ticks.empty(); diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index aaddd129..f5598112 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -302,6 +302,8 @@ class UnitTestCommonBase void unitTestClientInputMessage(CC_MqttsnClient* client, const UnitTestMessage& msg); void unitTestPushSearchgwResponseDelay(unsigned val); + static CC_MqttsnTopicId unitTestShortTopicNameToId(const std::string& topic); + bool unitTestHasTickReq() const; const UnitTestTickInfo* unitTestTickInfo(bool mustExist = true) const; void unitTestTick(CC_MqttsnClient* client, unsigned ms = 0U); diff --git a/client/lib/test/UnitTestProtocolDefs.h b/client/lib/test/UnitTestProtocolDefs.h index c2f20e3a..aabc7405 100644 --- a/client/lib/test/UnitTestProtocolDefs.h +++ b/client/lib/test/UnitTestProtocolDefs.h @@ -22,6 +22,7 @@ using UnitTestMessage = cc_mqttsn::Message< comms::option::app::WriteIterator, comms::option::app::LengthInfoInterface, comms::option::app::IdInfoInterface, + comms::option::app::NameInterface, comms::option::app::Handler >; diff --git a/client/lib/test/UnitTestPublish.th b/client/lib/test/UnitTestPublish.th index 27e5bda5..bebca670 100644 --- a/client/lib/test/UnitTestPublish.th +++ b/client/lib/test/UnitTestPublish.th @@ -9,6 +9,21 @@ class UnitTestPublish : public CxxTest::TestSuite, public UnitTestDefaultBase { public: void test1(); + void test2(); + void test3(); + void test4(); + void test5(); + void test6(); + void test7(); + void test8(); + void test9(); + void test10(); + void test11(); + void test12(); + void test13(); + void test14(); + void test15(); + void test16(); private: virtual void setUp() override @@ -77,3 +92,1327 @@ void UnitTestPublish::test1() TS_ASSERT(unitTestHasTickReq()); } + +void UnitTestPublish::test2() +{ + // Testing Qos1 publish with pre-defined topic ID + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const CC_MqttsnTopicId TopicId = 1U; + CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; + const UnitTestData Data = {1, 2, 3, 4, 5}; + const bool Retain = true; + + CC_MqttsnPublishConfig config; + apiPublishInitConfig(&config); + + config.m_topicId = TopicId; + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + config.m_qos = Qos; + config.m_retain = Retain; + + auto publish = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish, nullptr); + + auto ec = apiPublishConfig(publish, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::PredefinedTopicId); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + TS_ASSERT(!unitTestHasOutputData()); + + TS_ASSERT(!unitTestHasPublishCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + UnitTestPubackMsg pubackMsg; + pubackMsg.field_topicId().setValue(TopicId); + pubackMsg.field_msgId().setValue(publishMsg->field_msgId().value()); + pubackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, pubackMsg); + + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_handle, publish); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(publishReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted); + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestPublish::test3() +{ + // Testing Qos2 publish with pre-defined topic ID + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const CC_MqttsnTopicId TopicId = 1U; + CC_MqttsnQoS Qos = CC_MqttsnQoS_ExactlyOnceDelivery; + const UnitTestData Data = {1, 2, 3, 4, 5}; + const bool Retain = true; + + CC_MqttsnPublishConfig config; + apiPublishInitConfig(&config); + + config.m_topicId = TopicId; + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + config.m_qos = Qos; + config.m_retain = Retain; + + auto publish = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish, nullptr); + + auto ec = apiPublishConfig(publish, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + unsigned pubMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::PredefinedTopicId); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + TS_ASSERT(!unitTestHasOutputData()); + pubMsgId = publishMsg->field_msgId().value(); + } + + TS_ASSERT(!unitTestHasPublishCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestPubrecMsg pubrecMsg; + pubrecMsg.field_msgId().setValue(pubMsgId); + unitTestClientInputMessage(client, pubrecMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pubrelMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrelMsg, nullptr); + TS_ASSERT_EQUALS(pubrelMsg->field_msgId().value(), pubMsgId); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(!unitTestHasPublishCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestPubcompMsg pubcompMsg; + pubcompMsg.field_msgId().setValue(pubMsgId); + unitTestClientInputMessage(client, pubcompMsg); + } + + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_handle, publish); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestPublish::test4() +{ + // Testing Qos0 publish with short topic name + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic("ab"); + const UnitTestData Data = {1, 2, 3, 4, 5}; + + CC_MqttsnPublishConfig config; + apiPublishInitConfig(&config); + TS_ASSERT_EQUALS(config.m_topic, nullptr); + TS_ASSERT_EQUALS(config.m_qos, CC_MqttsnQoS_AtMostOnceDelivery); + + config.m_topic = Topic.c_str(); + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + + auto publish = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish, nullptr); + + auto ec = apiPublishConfig(publish, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::ShortTopicName); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), unitTestShortTopicNameToId(Topic)); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + TS_ASSERT(!unitTestHasOutputData()); + + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_handle, publish); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestPublish::test5() +{ + // Testing Qos0 publish with topic registration + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic("abcd"); + const UnitTestData Data = {1, 2, 3, 4, 5}; + const CC_MqttsnTopicId TopicId = 123; + + CC_MqttsnPublishConfig config; + apiPublishInitConfig(&config); + TS_ASSERT_EQUALS(config.m_topic, nullptr); + TS_ASSERT_EQUALS(config.m_qos, CC_MqttsnQoS_AtMostOnceDelivery); + + config.m_topic = Topic.c_str(); + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + + auto publish = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish, nullptr); + + auto ec = apiPublishConfig(publish, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + unsigned regMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* registerMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(registerMsg, nullptr); + TS_ASSERT_EQUALS(registerMsg->field_topicId().value(), 0U); + TS_ASSERT_EQUALS(registerMsg->field_topicName().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + + regMsgId = registerMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + UnitTestRegackMsg regackMsg; + regackMsg.field_msgId().setValue(regMsgId); + regackMsg.field_topicId().setValue(TopicId); + regackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, regackMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + TS_ASSERT(!unitTestHasOutputData()); + } + + { + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_handle, publish); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + // Repeating publish with the same topic + auto publish2 = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish2, nullptr); + + ec = apiPublishConfig(publish2, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish2); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + TS_ASSERT(!unitTestHasOutputData()); + } + + { + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_handle, publish2); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + } +} + +void UnitTestPublish::test6() +{ + // Testing Qos0 publish with topic registration rejection + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic("abcd"); + const UnitTestData Data = {1, 2, 3, 4, 5}; + const CC_MqttsnReturnCode RejectCode = CC_MqttsnReturnCode_Conjestion; + + CC_MqttsnPublishConfig config; + apiPublishInitConfig(&config); + TS_ASSERT_EQUALS(config.m_topic, nullptr); + TS_ASSERT_EQUALS(config.m_qos, CC_MqttsnQoS_AtMostOnceDelivery); + + config.m_topic = Topic.c_str(); + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + + auto publish = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish, nullptr); + + auto ec = apiPublishConfig(publish, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + unsigned regMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* registerMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(registerMsg, nullptr); + TS_ASSERT_EQUALS(registerMsg->field_topicId().value(), 0U); + TS_ASSERT_EQUALS(registerMsg->field_topicName().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + + regMsgId = registerMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + UnitTestRegackMsg regackMsg; + regackMsg.field_msgId().setValue(regMsgId); + regackMsg.field_returnCode().setValue(RejectCode); + unitTestClientInputMessage(client, regackMsg); + } + + TS_ASSERT(!unitTestHasOutputData()); + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_handle, publish); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(publishReport->m_info.m_returnCode, RejectCode); + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestPublish::test7() +{ + // Testing Qos2 publish with topic registration and rejection + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic("abcd"); + const UnitTestData Data = {1, 2, 3, 4, 5}; + const CC_MqttsnQoS Qos = CC_MqttsnQoS_ExactlyOnceDelivery; + const CC_MqttsnTopicId TopicId = 123; + const CC_MqttsnReturnCode RejectCode = CC_MqttsnReturnCode_Conjestion; + + CC_MqttsnPublishConfig config; + apiPublishInitConfig(&config); + TS_ASSERT_EQUALS(config.m_topic, nullptr); + TS_ASSERT_EQUALS(config.m_qos, CC_MqttsnQoS_AtMostOnceDelivery); + + config.m_topic = Topic.c_str(); + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + config.m_qos = Qos; + + auto publish = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish, nullptr); + + auto ec = apiPublishConfig(publish, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + unsigned regMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* registerMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(registerMsg, nullptr); + TS_ASSERT_EQUALS(registerMsg->field_topicId().value(), 0U); + TS_ASSERT_EQUALS(registerMsg->field_topicName().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + + regMsgId = registerMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestRegackMsg regackMsg; + regackMsg.field_topicId().setValue(TopicId); + regackMsg.field_msgId().setValue(regMsgId); + regackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, regackMsg); + } + + unsigned pubMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + TS_ASSERT(!unitTestHasOutputData()); + pubMsgId = publishMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestPubackMsg pubackMsg; + pubackMsg.field_topicId().setValue(TopicId); + pubackMsg.field_msgId().setValue(pubMsgId); + pubackMsg.field_returnCode().setValue(RejectCode); + unitTestClientInputMessage(client, pubackMsg); + } + + TS_ASSERT(!unitTestHasOutputData()); + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_handle, publish); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(publishReport->m_info.m_returnCode, RejectCode); + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestPublish::test8() +{ + // Testing Qos1 publish with topic registration and timeouts + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic("abcd"); + const UnitTestData Data = {1, 2, 3, 4, 5}; + const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; + const CC_MqttsnTopicId TopicId = 123; + + CC_MqttsnPublishConfig config; + apiPublishInitConfig(&config); + TS_ASSERT_EQUALS(config.m_topic, nullptr); + TS_ASSERT_EQUALS(config.m_qos, CC_MqttsnQoS_AtMostOnceDelivery); + + config.m_topic = Topic.c_str(); + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + config.m_qos = Qos; + + auto publish = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish, nullptr); + + auto ec = apiPublishConfig(publish, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + unsigned regMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* registerMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(registerMsg, nullptr); + TS_ASSERT_EQUALS(registerMsg->field_topicId().value(), 0U); + TS_ASSERT_EQUALS(registerMsg->field_topicName().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + + regMsgId = registerMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* registerMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(registerMsg, nullptr); + TS_ASSERT_EQUALS(registerMsg->field_topicId().value(), 0U); + TS_ASSERT_EQUALS(registerMsg->field_topicName().value(), Topic); + TS_ASSERT_EQUALS(registerMsg->field_msgId().value(), regMsgId); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestRegackMsg regackMsg; + regackMsg.field_topicId().setValue(TopicId); + regackMsg.field_msgId().setValue(regMsgId); + regackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, regackMsg); + } + + unsigned pubMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + TS_ASSERT(!publishMsg->field_flags().field_high().getBitValue_Dup()); + TS_ASSERT(!unitTestHasOutputData()); + pubMsgId = publishMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + TS_ASSERT_EQUALS(publishMsg->field_msgId().value(), pubMsgId); + TS_ASSERT(publishMsg->field_flags().field_high().getBitValue_Dup()); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestPubackMsg pubackMsg; + pubackMsg.field_topicId().setValue(TopicId); + pubackMsg.field_msgId().setValue(pubMsgId); + pubackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, pubackMsg); + } + + TS_ASSERT(!unitTestHasOutputData()); + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_handle, publish); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(publishReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted); + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestPublish::test9() +{ + // Testing Qos2 publish with topic registration and timeouts + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic("abcd"); + const UnitTestData Data = {1, 2, 3, 4, 5}; + const CC_MqttsnQoS Qos = CC_MqttsnQoS_ExactlyOnceDelivery; + const CC_MqttsnTopicId TopicId = 123; + + CC_MqttsnPublishConfig config; + apiPublishInitConfig(&config); + + config.m_topic = Topic.c_str(); + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + config.m_qos = Qos; + + auto publish = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish, nullptr); + + auto ec = apiPublishConfig(publish, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + unsigned regMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* registerMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(registerMsg, nullptr); + TS_ASSERT_EQUALS(registerMsg->field_topicId().value(), 0U); + TS_ASSERT_EQUALS(registerMsg->field_topicName().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + + regMsgId = registerMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* registerMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(registerMsg, nullptr); + TS_ASSERT_EQUALS(registerMsg->field_topicId().value(), 0U); + TS_ASSERT_EQUALS(registerMsg->field_topicName().value(), Topic); + TS_ASSERT_EQUALS(registerMsg->field_msgId().value(), regMsgId); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestRegackMsg regackMsg; + regackMsg.field_topicId().setValue(TopicId); + regackMsg.field_msgId().setValue(regMsgId); + regackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, regackMsg); + } + + unsigned pubMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + TS_ASSERT(!publishMsg->field_flags().field_high().getBitValue_Dup()); + TS_ASSERT(!unitTestHasOutputData()); + pubMsgId = publishMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + TS_ASSERT_EQUALS(publishMsg->field_msgId().value(), pubMsgId); + TS_ASSERT(publishMsg->field_flags().field_high().getBitValue_Dup()); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestPubrecMsg pubrecMsg; + pubrecMsg.field_msgId().setValue(pubMsgId); + unitTestClientInputMessage(client, pubrecMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pubrelMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrelMsg, nullptr); + TS_ASSERT_EQUALS(pubrelMsg->field_msgId().value(), pubMsgId); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // Timeout + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pubrelMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrelMsg, nullptr); + TS_ASSERT_EQUALS(pubrelMsg->field_msgId().value(), pubMsgId); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestPubcompMsg pubrecMsg; + pubrecMsg.field_msgId().setValue(pubMsgId); + unitTestClientInputMessage(client, pubrecMsg); + } + + TS_ASSERT(!unitTestHasOutputData()); + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_handle, publish); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestPublish::test10() +{ + // Testing Qos0 publish with topic registration getting lost + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic("abcd"); + const UnitTestData Data = {1, 2, 3, 4, 5}; + const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtMostOnceDelivery; + + CC_MqttsnPublishConfig config; + apiPublishInitConfig(&config); + + config.m_topic = Topic.c_str(); + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + config.m_qos = Qos; + + auto publish = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish, nullptr); + auto ec = apiPublishSetRetryCount(publish, 1U); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = apiPublishConfig(publish, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + unsigned regMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* registerMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(registerMsg, nullptr); + TS_ASSERT_EQUALS(registerMsg->field_topicId().value(), 0U); + TS_ASSERT_EQUALS(registerMsg->field_topicName().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + + regMsgId = registerMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* registerMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(registerMsg, nullptr); + TS_ASSERT_EQUALS(registerMsg->field_topicId().value(), 0U); + TS_ASSERT_EQUALS(registerMsg->field_topicName().value(), Topic); + TS_ASSERT_EQUALS(registerMsg->field_msgId().value(), regMsgId); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + + TS_ASSERT(!unitTestHasOutputData()); + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_handle, publish); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Timeout); + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestPublish::test11() +{ + // Testing Qos1, PUBLISH is not acked + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic("abcd"); + const UnitTestData Data = {1, 2, 3, 4, 5}; + const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; + const CC_MqttsnTopicId TopicId = 123; + + CC_MqttsnPublishConfig config; + apiPublishInitConfig(&config); + + config.m_topic = Topic.c_str(); + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + config.m_qos = Qos; + + auto publish = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish, nullptr); + auto ec = apiPublishSetRetryCount(publish, 1U); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = apiPublishConfig(publish, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + unsigned regMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* registerMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(registerMsg, nullptr); + TS_ASSERT_EQUALS(registerMsg->field_topicId().value(), 0U); + TS_ASSERT_EQUALS(registerMsg->field_topicName().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + + regMsgId = registerMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestRegackMsg regackMsg; + regackMsg.field_msgId().setValue(regMsgId); + regackMsg.field_topicId().setValue(TopicId); + regackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, regackMsg); + } + + unsigned pubMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + TS_ASSERT(!publishMsg->field_flags().field_high().getBitValue_Dup()); + TS_ASSERT(!unitTestHasOutputData()); + pubMsgId = publishMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + TS_ASSERT_EQUALS(publishMsg->field_msgId().value(), pubMsgId); + TS_ASSERT(publishMsg->field_flags().field_high().getBitValue_Dup()); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + + TS_ASSERT(!unitTestHasOutputData()); + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_handle, publish); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Timeout); + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestPublish::test12() +{ + // Testing Qos2, PUBCOMP is not received + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic("abcd"); + const UnitTestData Data = {1, 2, 3, 4, 5}; + const CC_MqttsnQoS Qos = CC_MqttsnQoS_ExactlyOnceDelivery; + const CC_MqttsnTopicId TopicId = 123; + + CC_MqttsnPublishConfig config; + apiPublishInitConfig(&config); + + config.m_topic = Topic.c_str(); + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + config.m_qos = Qos; + + auto publish = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish, nullptr); + auto ec = apiPublishSetRetryCount(publish, 1U); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = apiPublishConfig(publish, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + unsigned regMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* registerMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(registerMsg, nullptr); + TS_ASSERT_EQUALS(registerMsg->field_topicId().value(), 0U); + TS_ASSERT_EQUALS(registerMsg->field_topicName().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + + regMsgId = registerMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestRegackMsg regackMsg; + regackMsg.field_msgId().setValue(regMsgId); + regackMsg.field_topicId().setValue(TopicId); + regackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, regackMsg); + } + + unsigned pubMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + TS_ASSERT(!publishMsg->field_flags().field_high().getBitValue_Dup()); + TS_ASSERT(!unitTestHasOutputData()); + pubMsgId = publishMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + TS_ASSERT_EQUALS(publishMsg->field_msgId().value(), pubMsgId); + TS_ASSERT(publishMsg->field_flags().field_high().getBitValue_Dup()); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestPubrecMsg pubrecMsg; + pubrecMsg.field_msgId().setValue(pubMsgId); + unitTestClientInputMessage(client, pubrecMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pubrelMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrelMsg, nullptr); + TS_ASSERT_EQUALS(pubrelMsg->field_msgId().value(), pubMsgId); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pubrelMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrelMsg, nullptr); + TS_ASSERT_EQUALS(pubrelMsg->field_msgId().value(), pubMsgId); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + + TS_ASSERT(!unitTestHasOutputData()); + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_handle, publish); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Timeout); + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestPublish::test13() +{ + // Testing PUBACK instead of PUBREC + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const UnitTestData Data = {1, 2, 3, 4, 5}; + const CC_MqttsnQoS Qos = CC_MqttsnQoS_ExactlyOnceDelivery; + const CC_MqttsnTopicId TopicId = 123; + + CC_MqttsnPublishConfig config; + apiPublishInitConfig(&config); + + config.m_topicId = TopicId; + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + config.m_qos = Qos; + + auto publish = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish, nullptr); + + auto ec = apiPublishConfig(publish, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + unsigned pubMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::PredefinedTopicId); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + TS_ASSERT(!unitTestHasOutputData()); + pubMsgId = publishMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestPubackMsg pubackMsg; + pubackMsg.field_topicId().setValue(TopicId); + pubackMsg.field_msgId().setValue(pubMsgId); + pubackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, pubackMsg); + } + + // PUBACK is expected to be ignored + TS_ASSERT(!unitTestHasOutputData()); + TS_ASSERT(!unitTestHasPublishCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestPublish::test14() +{ + // Testing PUBREC instead of PUBACK + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const UnitTestData Data = {1, 2, 3, 4, 5}; + const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; + const CC_MqttsnTopicId TopicId = 123; + + CC_MqttsnPublishConfig config; + apiPublishInitConfig(&config); + + config.m_topicId = TopicId; + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + config.m_qos = Qos; + + auto publish = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish, nullptr); + + auto ec = apiPublishConfig(publish, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + unsigned pubMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::PredefinedTopicId); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + TS_ASSERT(!unitTestHasOutputData()); + pubMsgId = publishMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestPubrecMsg pubrecMsg; + pubrecMsg.field_msgId().setValue(pubMsgId); + unitTestClientInputMessage(client, pubrecMsg); + } + + // PUBREC is expected to be ignored + TS_ASSERT(!unitTestHasOutputData()); + TS_ASSERT(!unitTestHasPublishCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestPublish::test15() +{ + // Testing PUBACK with invalid msg id + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const UnitTestData Data = {1, 2, 3, 4, 5}; + const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; + const CC_MqttsnTopicId TopicId = 123; + + CC_MqttsnPublishConfig config; + apiPublishInitConfig(&config); + + config.m_topicId = TopicId; + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + config.m_qos = Qos; + + auto publish = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish, nullptr); + + auto ec = apiPublishConfig(publish, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + unsigned pubMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::PredefinedTopicId); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + TS_ASSERT(!unitTestHasOutputData()); + pubMsgId = publishMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestPubackMsg pubackMsg; + pubackMsg.field_topicId().setValue(TopicId); + pubackMsg.field_msgId().setValue(pubMsgId + 1U); + pubackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, pubackMsg); + } + + // PUBACK is expected to be ignored + TS_ASSERT(!unitTestHasOutputData()); + TS_ASSERT(!unitTestHasPublishCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestPublish::test16() +{ + // Testing PUBREC with invalid msg id + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const UnitTestData Data = {1, 2, 3, 4, 5}; + const CC_MqttsnQoS Qos = CC_MqttsnQoS_ExactlyOnceDelivery; + const CC_MqttsnTopicId TopicId = 123; + + CC_MqttsnPublishConfig config; + apiPublishInitConfig(&config); + + config.m_topicId = TopicId; + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + config.m_qos = Qos; + + auto publish = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish, nullptr); + + auto ec = apiPublishConfig(publish, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + unsigned pubMsgId = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::PredefinedTopicId); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + TS_ASSERT(!unitTestHasOutputData()); + pubMsgId = publishMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestPubrecMsg pubrecMsg; + pubrecMsg.field_msgId().setValue(pubMsgId + 1U); + unitTestClientInputMessage(client, pubrecMsg); + } + + // PUBREC is expected to be ignored + TS_ASSERT(!unitTestHasOutputData()); + TS_ASSERT(!unitTestHasPublishCompleteReport()); + TS_ASSERT(unitTestHasTickReq()); +} + +// TODO: multiple parallel publishes \ No newline at end of file From 394f4f051b916bd2e1a9a1a4d2d57cf4be219ec1 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 23 Jul 2024 09:17:20 +1000 Subject: [PATCH 072/106] Limiting publish operations to one at a time. --- client/lib/src/ClientImpl.cpp | 24 +-- client/lib/src/ClientImpl.h | 1 - client/lib/src/SessionState.h | 1 - client/lib/src/op/SendOp.cpp | 48 +++--- client/lib/src/op/SendOp.h | 8 +- client/lib/test/UnitTestPublish.th | 225 ++++++++++++++++++++++++++++- 6 files changed, 261 insertions(+), 46 deletions(-) diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 0d40bb2d..eec69283 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -415,6 +415,12 @@ op::SendOp* ClientImpl::publishPrepare(CC_MqttsnErrorCode* ec) m_ops.push_back(ptr.get()); m_sendOps.push_back(std::move(ptr)); op = m_sendOps.back().get(); + + if (1U < m_sendOps.size()) { + // Only one PUBLISH transaction is allowed at a time by the specification + op->suspend(); + } + updateEc(ec, CC_MqttsnErrorCode_Success); } while (false); @@ -943,17 +949,6 @@ void ClientImpl::allowNextPrepare() m_preparationLocked = false; } -void ClientImpl::allowNextRegister() -{ - for (auto& op : m_sendOps) { - COMMS_ASSERT(op); - if (op->isRegPending()) { - op->proceedWithReg(); - break; - } - } -} - void ClientImpl::doApiEnter() { ++m_apiEnterCount; @@ -1231,6 +1226,13 @@ void ClientImpl::opComplete_Unsubscribe(const op::Op* op) void ClientImpl::opComplete_Send(const op::Op* op) { eraseFromList(op, m_sendOps); + + if (m_sendOps.empty()) { + return; + } + + COMMS_ASSERT(m_sendOps.front()); + m_sendOps.front()->resume(); } void ClientImpl::finaliseSupUnsubOp() diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index 4b374379..0296e436 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -176,7 +176,6 @@ class ClientImpl final : public ProtMsgHandler // bool hasPausedSendsBefore(const op::SendOp* sendOp) const; // bool hasHigherQosSendsBefore(const op::SendOp* sendOp, op::Op::Qos qos) const; void allowNextPrepare(); - void allowNextRegister(); TimerMgr& timerMgr() { diff --git a/client/lib/src/SessionState.h b/client/lib/src/SessionState.h index 375df4a2..8e6e0dfc 100644 --- a/client/lib/src/SessionState.h +++ b/client/lib/src/SessionState.h @@ -18,7 +18,6 @@ struct SessionState unsigned m_keepAliveMs = 0U; bool m_connected = false; bool m_disconnecting = false; - bool m_topicRegInProgress = false; }; } // namespace cc_mqttsn_client diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index a6956d22..c518311c 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -47,12 +47,6 @@ SendOp::~SendOp() { releasePacketId(m_registerMsg.field_msgId().value()); releasePacketId(m_publishMsg.field_msgId().value()); - - if (m_registerInProgress) { - COMMS_ASSERT(client().sessionState().m_topicRegInProgress); - client().sessionState().m_topicRegInProgress = false; - client().allowNextRegister(); - } } CC_MqttsnErrorCode SendOp::config(const CC_MqttsnPublishConfig* config) @@ -193,12 +187,13 @@ CC_MqttsnErrorCode SendOp::cancel() return CC_MqttsnErrorCode_Success; } -void SendOp::proceedWithReg() +void SendOp::resume() { - COMMS_ASSERT(isRegPending()); - COMMS_ASSERT(!client().sessionState().m_topicRegInProgress); + COMMS_ASSERT(m_suspended); + m_suspended = false; auto ec = sendInternal(); if (ec != CC_MqttsnErrorCode_Success) { + errorLog("Failed to send SUBSCRIBE, after prev SUBSCRIBE completion"); completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); return; } @@ -206,7 +201,11 @@ void SendOp::proceedWithReg() void SendOp::handle(RegackMsg& msg) { - if ((!m_registerInProgress) || (msg.field_msgId().value() != m_registerMsg.field_msgId().value())) { + if (m_suspended) { + return; + } + + if ((Stage_Register < m_stage) || (msg.field_msgId().value() != m_registerMsg.field_msgId().value())) { errorLog("Unexpected REGACK message, ignoring"); return; } @@ -245,16 +244,7 @@ void SendOp::handle(RegackMsg& msg) iter->m_topicId = topicId; } while (false); - auto onExitAllowRegister = - comms::util::makeScopeGuard( - [&cl = client()]() - { - cl.allowNextRegister(); - }); - - m_registerInProgress = false; m_stage = Stage_Publish; - client().sessionState().m_topicRegInProgress = false; m_publishMsg.field_topicId().setValue(topicId); setRetryCount(m_origRetryCount); auto ec = sendInternal(); @@ -267,7 +257,8 @@ void SendOp::handle(RegackMsg& msg) #if CC_MQTTSN_CLIENT_MAX_QOS > 0 void SendOp::handle(PubackMsg& msg) { - if ((m_stage < Stage_Publish) || + if ((m_suspended) || + (m_stage < Stage_Publish) || (msg.field_msgId().value() != m_publishMsg.field_msgId().value()) || (msg.field_topicId().value() != m_publishMsg.field_topicId().value())) { return; @@ -289,7 +280,8 @@ void SendOp::handle(PubackMsg& msg) #if CC_MQTTSN_CLIENT_MAX_QOS > 1 void SendOp::handle(PubrecMsg& msg) { - if ((m_stage < Stage_Publish) || + if ((m_suspended) || + (m_stage < Stage_Publish) || (msg.field_msgId().value() != m_publishMsg.field_msgId().value())) { return; } @@ -311,7 +303,8 @@ void SendOp::handle(PubrecMsg& msg) void SendOp::handle(PubcompMsg& msg) { - if ((m_stage < Stage_Acked) || + if ((m_suspended) || + (m_stage < Stage_Acked) || (msg.field_msgId().value() != m_publishMsg.field_msgId().value())) { return; } @@ -350,6 +343,10 @@ void SendOp::restartTimer() CC_MqttsnErrorCode SendOp::sendInternal() { + if (m_suspended) { + return CC_MqttsnErrorCode_Success; + } + using SendFunc = CC_MqttsnErrorCode (SendOp::*)(); static const SendFunc Map[] = { /* Stage_Register */ &SendOp::sendInternal_Register, @@ -367,13 +364,6 @@ CC_MqttsnErrorCode SendOp::sendInternal() CC_MqttsnErrorCode SendOp::sendInternal_Register() { - if ((client().sessionState().m_topicRegInProgress) && (!m_registerInProgress)) { - return CC_MqttsnErrorCode_Success; // Wait fore other registration is complete - } - - client().sessionState().m_topicRegInProgress = true; - m_registerInProgress = true; - auto ec = sendMessage(m_registerMsg); if (ec == CC_MqttsnErrorCode_Success) { restartTimer(); diff --git a/client/lib/src/op/SendOp.h b/client/lib/src/op/SendOp.h index 295f32bb..8ebd1e42 100644 --- a/client/lib/src/op/SendOp.h +++ b/client/lib/src/op/SendOp.h @@ -32,11 +32,13 @@ class SendOp final : public Op CC_MqttsnErrorCode cancel(); void proceedWithReg(); - bool isRegPending() const + void suspend() { - return (m_stage == Stage_Register) && (!m_registerInProgress); + m_suspended = true; } + void resume(); + using Base::handle; void handle(RegackMsg& msg) override; #if CC_MQTTSN_CLIENT_MAX_QOS > 0 @@ -75,7 +77,7 @@ class SendOp final : public Op void* m_cbData = nullptr; Stage m_stage = Stage_Register; unsigned m_origRetryCount = 0U; - bool m_registerInProgress = false; + bool m_suspended = false; static_assert(ExtConfig::SendOpTimers == 1U); }; diff --git a/client/lib/test/UnitTestPublish.th b/client/lib/test/UnitTestPublish.th index bebca670..3dc21dc7 100644 --- a/client/lib/test/UnitTestPublish.th +++ b/client/lib/test/UnitTestPublish.th @@ -24,6 +24,7 @@ public: void test14(); void test15(); void test16(); + void test17(); private: virtual void setUp() override @@ -1415,4 +1416,226 @@ void UnitTestPublish::test16() TS_ASSERT(unitTestHasTickReq()); } -// TODO: multiple parallel publishes \ No newline at end of file +void UnitTestPublish::test17() +{ + // Testing multiple publish ops issued one after another + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic1("abcd"); + const UnitTestData Data1 = {1, 2, 3, 4, 5}; + const CC_MqttsnQoS Qos1 = CC_MqttsnQoS_AtMostOnceDelivery; + const CC_MqttsnTopicId TopicId1 = 1; + + const std::string Topic2("ab"); + const UnitTestData Data2 = {1, 2, 3}; + const CC_MqttsnQoS Qos2 = CC_MqttsnQoS_AtLeastOnceDelivery; + + const UnitTestData Data3 = {4, 5, 6}; + const CC_MqttsnQoS Qos3 = CC_MqttsnQoS_ExactlyOnceDelivery; + const CC_MqttsnTopicId TopicId3 = 3; + + auto publish1 = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish1, nullptr); + + { + CC_MqttsnPublishConfig config1; + apiPublishInitConfig(&config1); + + config1.m_topic = Topic1.c_str(); + config1.m_qos = Qos1; + config1.m_data = Data1.data(); + config1.m_dataLen = static_cast(Data1.size()); + + auto ec = apiPublishConfig(publish1, &config1); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish1); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + } + + unsigned regMsgId1 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* registerMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(registerMsg, nullptr); + TS_ASSERT_EQUALS(registerMsg->field_topicId().value(), 0U); + TS_ASSERT_EQUALS(registerMsg->field_topicName().value(), Topic1); + TS_ASSERT(!unitTestHasOutputData()); + + regMsgId1 = registerMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1); + + auto publish2 = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish2, nullptr); + + { + CC_MqttsnPublishConfig config2; + apiPublishInitConfig(&config2); + + config2.m_topic = Topic2.c_str(); + config2.m_qos = Qos2; + config2.m_data = Data2.data(); + config2.m_dataLen = static_cast(Data2.size()); + + auto ec = apiPublishConfig(publish2, &config2); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish2); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1); + + auto publish3 = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish3, nullptr); + { + CC_MqttsnPublishConfig config3; + apiPublishInitConfig(&config3); + + config3.m_topicId = TopicId3; + config3.m_qos = Qos3; + config3.m_data = Data3.data(); + config3.m_dataLen = static_cast(Data3.size()); + + auto ec = apiPublishConfig(publish3, &config3); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish3); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestRegackMsg regackMsg; + regackMsg.field_msgId().setValue(regMsgId1); + regackMsg.field_topicId().setValue(TopicId1); + regackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, regackMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos1); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId1); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data1); + } + + { + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_handle, publish1); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT(!unitTestHasPublishCompleteReport()); + } + + // Second publish is sent right away + unsigned pubMsgId2 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::ShortTopicName); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos2); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), unitTestShortTopicNameToId(Topic2)); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data2); + TS_ASSERT(!unitTestHasOutputData()); + + pubMsgId2 = publishMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestPubackMsg pubackMsg; + pubackMsg.field_topicId().setValue(unitTestShortTopicNameToId(Topic2)); + pubackMsg.field_msgId().setValue(pubMsgId2); + pubackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, pubackMsg); + } + + { + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_handle, publish2); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT(!unitTestHasPublishCompleteReport()); + } + + // Third publish is sent right away + unsigned pubMsgId3 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::PredefinedTopicId); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos3); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId3); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data3); + TS_ASSERT(!unitTestHasOutputData()); + + pubMsgId3 = publishMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestPubrecMsg pubrecMsg; + pubrecMsg.field_msgId().setValue(pubMsgId3); + unitTestClientInputMessage(client, pubrecMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pubrelMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrelMsg, nullptr); + TS_ASSERT_EQUALS(pubrelMsg->field_msgId().value(), pubMsgId3); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestPubcompMsg pubcompMsg; + pubcompMsg.field_msgId().setValue(pubMsgId3); + unitTestClientInputMessage(client, pubcompMsg); + } + + { + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_handle, publish3); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT(!unitTestHasPublishCompleteReport()); + } +} + +// TODO: repeat registration for invalid topic id \ No newline at end of file From a95cccea8178e43c6aa435fc0af3b63ab2fbd4c8 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 23 Jul 2024 09:22:01 +1000 Subject: [PATCH 073/106] Fixing gcc build. --- client/lib/src/op/SendOp.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index c518311c..70984c25 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -160,8 +160,7 @@ CC_MqttsnErrorCode SendOp::send(CC_MqttsnPublishCompleteCb cb, void* cbData) m_registerMsg.field_msgId().setValue(allocPacketId()); } - using QoS = PublishMsg::Field_flags::Field_qos::ValueType; - if (QoS::AtMostOnceDelivery < m_publishMsg.field_flags().field_qos().value()) { + if (Qos::AtMostOnceDelivery < m_publishMsg.field_flags().field_qos().value()) { m_publishMsg.field_msgId().setValue(allocPacketId()); } @@ -267,7 +266,6 @@ void SendOp::handle(PubackMsg& msg) auto info = CC_MqttsnPublishInfo(); info.m_returnCode = static_cast(msg.field_returnCode().value()); - using Qos = PublishMsg::Field_flags::Field_qos::ValueType; if ((info.m_returnCode == CC_MqttsnReturnCode_Accepted) && (m_publishMsg.field_flags().field_qos().value() != Qos::AtLeastOnceDelivery)) { errorLog("Received PUBACK instead of PUBREC, ignoring..."); @@ -286,7 +284,6 @@ void SendOp::handle(PubrecMsg& msg) return; } - using Qos = PublishMsg::Field_flags::Field_qos::ValueType; if (m_publishMsg.field_flags().field_qos().value() != Qos::ExactlyOnceDelivery) { errorLog("Received PUBREC instead of PUBACK, ignoring..."); return; @@ -387,8 +384,7 @@ CC_MqttsnErrorCode SendOp::sendInternal_Publish() return ec; } - using QoS = PublishMsg::Field_flags::Field_qos::ValueType; - if (QoS::AtMostOnceDelivery == m_publishMsg.field_flags().field_qos().value()) { + if (Qos::AtMostOnceDelivery == m_publishMsg.field_flags().field_qos().value()) { completeOpInternal(CC_MqttsnAsyncOpStatus_Complete); return CC_MqttsnErrorCode_Success; } From 72b29cc7aed1611c48e135262df6e2d4ab096e73 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 24 Jul 2024 09:18:13 +1000 Subject: [PATCH 074/106] More publish unit-testing. --- client/lib/src/ClientImpl.cpp | 89 ++++--- client/lib/src/ClientImpl.h | 11 +- client/lib/src/ProtocolDefs.h | 2 - client/lib/src/op/SendOp.cpp | 71 +++++- client/lib/src/op/SendOp.h | 8 + client/lib/test/UnitTestPublish.th | 368 ++++++++++++++++++++++++++++- 6 files changed, 476 insertions(+), 73 deletions(-) diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index eec69283..7bf74493 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -698,58 +698,49 @@ void ClientImpl::handle(GwinfoMsg& msg) // } while (false); // } -// #if CC_MQTTSN_CLIENT_MAX_QOS >= 1 -// void ClientImpl::handle(PubackMsg& msg) -// { -// static_assert(Config::MaxQos >= 1); -// if (!processPublishAckMsg(msg, msg.field_packetId().value(), false)) { -// errorLog("PUBACK with unknown packet id"); -// } -// } -// #endif // #if CC_MQTTSN_CLIENT_MAX_QOS >= 1 - -// #if CC_MQTTSN_CLIENT_MAX_QOS >= 2 -// void ClientImpl::handle(PubrecMsg& msg) -// { -// static_assert(Config::MaxQos >= 2); -// if (!processPublishAckMsg(msg, msg.field_packetId().value(), false)) { -// errorLog("PUBREC with unknown packet id"); -// } -// } - -// void ClientImpl::handle(PubrelMsg& msg) -// { -// static_assert(Config::MaxQos >= 2); -// for (auto& opPtr : m_keepAliveOps) { -// msg.dispatch(*opPtr); -// } - -// auto iter = -// std::find_if( -// m_recvOps.begin(), m_recvOps.end(), -// [&msg](auto& opPtr) -// { -// COMMS_ASSERT(opPtr); -// return opPtr->packetId() == msg.field_packetId().value(); -// }); - -// if (iter == m_recvOps.end()) { -// errorLog("PUBREL with unknown packet id"); -// return; -// } +void ClientImpl::handle(PubackMsg& msg) +{ + for (auto& opPtr : m_keepAliveOps) { + msg.dispatch(*opPtr); + } + + auto iter = m_sendOps.end(); + if constexpr (Config::MaxQos >= 1) { + iter = + std::find_if( + m_sendOps.begin(), m_sendOps.end(), + [&msg](auto& opPtr) + { + COMMS_ASSERT(opPtr); + return opPtr->publishMsgId() == msg.field_msgId().value(); + }); + } -// msg.dispatch(**iter); -// } + auto retCode = static_cast(msg.field_returnCode().value()); + if (retCode == CC_MqttsnReturnCode_InvalidTopicId) { + auto& map = m_reuseState.m_outRegTopics; + auto topicId = msg.field_topicId().value(); + map.erase( + std::remove_if( + map.begin(), map.end(), + [topicId](auto& elem) + { + return topicId == elem.m_topicId; + }), + map.end()); + } -// void ClientImpl::handle(PubcompMsg& msg) -// { -// static_assert(Config::MaxQos >= 2); -// if (!processPublishAckMsg(msg, msg.field_packetId().value(), true)) { -// errorLog("PUBCOMP with unknown packet id"); -// } -// } + if ((iter == m_sendOps.end()) && + (msg.field_msgId().value() != 0U)) { + errorLog("PUBACK with uknown msg id"); + return; + } -// #endif // #if CC_MQTTSN_CLIENT_MAX_QOS >= 2 + if (iter != m_sendOps.end()) { + COMMS_ASSERT(*iter); + msg.dispatch(**iter); + } +} void ClientImpl::handle([[maybe_unused]] PingreqMsg& msg) { diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index 0296e436..4c9bf383 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -150,16 +150,7 @@ class ClientImpl final : public ProtMsgHandler #endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY // virtual void handle(PublishMsg& msg) override; -// #if CC_MQTTSN_CLIENT_MAX_QOS >= 1 -// virtual void handle(PubackMsg& msg) override; -// #endif // #if CC_MQTTSN_CLIENT_MAX_QOS >= 1 - -// #if CC_MQTTSN_CLIENT_MAX_QOS >= 2 -// virtual void handle(PubrecMsg& msg) override; -// virtual void handle(PubrelMsg& msg) override; -// virtual void handle(PubcompMsg& msg) override; -// #endif // #if CC_MQTTSN_CLIENT_MAX_QOS >= 2 - + virtual void handle(PubackMsg& msg) override; virtual void handle(PingreqMsg& msg) override; virtual void handle(DisconnectMsg& msg) override; virtual void handle(ProtMessage& msg) override; diff --git a/client/lib/src/ProtocolDefs.h b/client/lib/src/ProtocolDefs.h index d5199928..a378691d 100644 --- a/client/lib/src/ProtocolDefs.h +++ b/client/lib/src/ProtocolDefs.h @@ -53,14 +53,12 @@ using ProtInputMessages = cc_mqttsn::message::Register, cc_mqttsn::message::Regack, cc_mqttsn::message::Publish, -#if CC_MQTTSN_CLIENT_MAX_QOS > 0 cc_mqttsn::message::Puback, #if CC_MQTTSN_CLIENT_MAX_QOS > 1 cc_mqttsn::message::Pubcomp, cc_mqttsn::message::Pubrec, cc_mqttsn::message::Pubrel, #endif // #if CC_MQTTSN_CLIENT_MAX_QOS > 1 -#endif // #if CC_MQTTSN_CLIENT_MAX_QOS > 0 cc_mqttsn::message::Suback, cc_mqttsn::message::Unsuback, cc_mqttsn::message::Pingreq, diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index 70984c25..1427fdf0 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -45,8 +45,7 @@ SendOp::SendOp(ClientImpl& client) : SendOp::~SendOp() { - releasePacketId(m_registerMsg.field_msgId().value()); - releasePacketId(m_publishMsg.field_msgId().value()); + releasePacketIdsInternal(); } CC_MqttsnErrorCode SendOp::config(const CC_MqttsnPublishConfig* config) @@ -109,6 +108,7 @@ CC_MqttsnErrorCode SendOp::config(const CC_MqttsnPublishConfig* config) break; } + m_registerMsg.field_topicName().value() = config->m_topic; auto& regMap = client().reuseState().m_outRegTopics; auto iter = std::lower_bound( @@ -125,7 +125,6 @@ CC_MqttsnErrorCode SendOp::config(const CC_MqttsnPublishConfig* config) break; } - m_registerMsg.field_topicName().value() = config->m_topic; m_stage = Stage_Register; } while (false); @@ -156,15 +155,10 @@ CC_MqttsnErrorCode SendOp::send(CC_MqttsnPublishCompleteCb cb, void* cbData) m_cb = cb; m_cbData = cbData; - if (m_stage == Stage_Register) { - m_registerMsg.field_msgId().setValue(allocPacketId()); - } - - if (Qos::AtMostOnceDelivery < m_publishMsg.field_flags().field_qos().value()) { - m_publishMsg.field_msgId().setValue(allocPacketId()); - } + allocPacketIdsInternal(); m_origRetryCount = getRetryCount(); + m_fullRetryRemCount = m_origRetryCount; auto ec = sendInternal(); if (ec != CC_MqttsnErrorCode_Success) { @@ -272,7 +266,41 @@ void SendOp::handle(PubackMsg& msg) return; } - completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &info); + auto status = CC_MqttsnAsyncOpStatus_Complete; + do { + if (info.m_returnCode != CC_MqttsnReturnCode_InvalidTopicId) { + break; + } + + using TopicIdType = PublishMsg::Field_flags::Field_topicIdType::ValueType; + if (m_publishMsg.field_flags().field_topicIdType().value() != TopicIdType::Normal) { + errorLog("Unexpected return code for the publish"); + break; + } + + if (m_fullRetryRemCount == 0U) { + errorLog("Used topic ID was rejected, by the gateway"); + break; + } + + --m_fullRetryRemCount; + m_stage = Stage_Register; + + // Re-allocate new packet IDs + releasePacketIdsInternal(); + allocPacketIdsInternal(); + + setRetryCount(m_origRetryCount); + auto ec = sendInternal(); + if (ec != CC_MqttsnErrorCode_Success) { + status = translateErrorCodeToAsyncOpStatus(ec); + break; + } + + return; + } while (false); + + completeOpInternal(status, &info); } #if CC_MQTTSN_CLIENT_MAX_QOS > 1 @@ -327,6 +355,10 @@ void SendOp::completeOpInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnPu auto handle = asHandle(this); auto cb = m_cb; auto* cbData = m_cbData; + if (status != CC_MqttsnAsyncOpStatus_Complete) { + info = nullptr; + } + opComplete(); // mustn't access data members after destruction if (cb != nullptr) { cb(cbData, handle, status, info); @@ -433,6 +465,23 @@ void SendOp::opTimeoutCb(void* data) asSendOp(data)->timeoutInternal(); } +void SendOp::allocPacketIdsInternal() +{ + if (m_stage == Stage_Register) { + m_registerMsg.field_msgId().setValue(allocPacketId()); + } + + if (Qos::AtMostOnceDelivery < m_publishMsg.field_flags().field_qos().value()) { + m_publishMsg.field_msgId().setValue(allocPacketId()); + } +} + +void SendOp::releasePacketIdsInternal() +{ + releasePacketId(m_registerMsg.field_msgId().value()); + releasePacketId(m_publishMsg.field_msgId().value()); +} + } // namespace op } // namespace cc_mqttsn_client diff --git a/client/lib/src/op/SendOp.h b/client/lib/src/op/SendOp.h index 8ebd1e42..cbc378ff 100644 --- a/client/lib/src/op/SendOp.h +++ b/client/lib/src/op/SendOp.h @@ -39,6 +39,11 @@ class SendOp final : public Op void resume(); + std::uint16_t publishMsgId() const + { + return m_publishMsg.field_msgId().value(); + } + using Base::handle; void handle(RegackMsg& msg) override; #if CC_MQTTSN_CLIENT_MAX_QOS > 0 @@ -67,6 +72,8 @@ class SendOp final : public Op CC_MqttsnErrorCode sendInternal_Publish(); CC_MqttsnErrorCode sendInternal_Pubrel(); void timeoutInternal(); + void allocPacketIdsInternal(); + void releasePacketIdsInternal(); static void opTimeoutCb(void* data); @@ -77,6 +84,7 @@ class SendOp final : public Op void* m_cbData = nullptr; Stage m_stage = Stage_Register; unsigned m_origRetryCount = 0U; + unsigned m_fullRetryRemCount = 0U; bool m_suspended = false; static_assert(ExtConfig::SendOpTimers == 1U); diff --git a/client/lib/test/UnitTestPublish.th b/client/lib/test/UnitTestPublish.th index 3dc21dc7..103711f1 100644 --- a/client/lib/test/UnitTestPublish.th +++ b/client/lib/test/UnitTestPublish.th @@ -25,6 +25,8 @@ public: void test15(); void test16(); void test17(); + void test18(); + void test19(); private: virtual void setUp() override @@ -1638,4 +1640,368 @@ void UnitTestPublish::test17() } } -// TODO: repeat registration for invalid topic id \ No newline at end of file +void UnitTestPublish::test18() +{ + // Testing registration invalidation by the gateway with Qos0 messages + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic("abcd"); + const UnitTestData Data = {1, 2, 3, 4, 5}; + const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtMostOnceDelivery; + const CC_MqttsnTopicId TopicId = 1; + + CC_MqttsnPublishConfig config; + apiPublishInitConfig(&config); + + config.m_topic = Topic.c_str(); + config.m_qos = Qos; + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + + { + auto publish1 = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish1, nullptr); + + auto ec = apiPublishConfig(publish1, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish1); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + } + + unsigned regMsgId1 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* registerMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(registerMsg, nullptr); + TS_ASSERT_EQUALS(registerMsg->field_topicId().value(), 0U); + TS_ASSERT_EQUALS(registerMsg->field_topicName().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + + regMsgId1 = registerMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestRegackMsg regackMsg; + regackMsg.field_msgId().setValue(regMsgId1); + regackMsg.field_topicId().setValue(TopicId); + regackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, regackMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + } + + { + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT(!unitTestHasPublishCompleteReport()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + auto publish2 = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish2, nullptr); + + auto ec = apiPublishConfig(publish2, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish2); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + } + + { + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT(!unitTestHasPublishCompleteReport()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + // Invalidating topic ID from the gateway + { + UnitTestPubackMsg pubackMsg; + pubackMsg.field_topicId().setValue(TopicId); + pubackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_InvalidTopicId); + unitTestClientInputMessage(client, pubackMsg); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + auto publish3 = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish3, nullptr); + + auto ec = apiPublishConfig(publish3, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish3); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + } + + + unsigned regMsgId2 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* registerMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(registerMsg, nullptr); + TS_ASSERT_EQUALS(registerMsg->field_topicId().value(), 0U); + TS_ASSERT_EQUALS(registerMsg->field_topicName().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + + regMsgId2 = registerMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestRegackMsg regackMsg; + regackMsg.field_msgId().setValue(regMsgId2); + regackMsg.field_topicId().setValue(TopicId); + regackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, regackMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + } + + { + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT(!unitTestHasPublishCompleteReport()); + } + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestPublish::test19() +{ + // Testing registration invalidation by the gateway with Qos1 messages + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic("abcd"); + const UnitTestData Data = {1, 2, 3, 4, 5}; + const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; + const CC_MqttsnTopicId TopicId = 1; + + CC_MqttsnPublishConfig config; + apiPublishInitConfig(&config); + + config.m_topic = Topic.c_str(); + config.m_qos = Qos; + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + + { + auto publish1 = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish1, nullptr); + + auto ec = apiPublishConfig(publish1, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish1); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + } + + unsigned regMsgId1 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* registerMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(registerMsg, nullptr); + TS_ASSERT_EQUALS(registerMsg->field_topicId().value(), 0U); + TS_ASSERT_EQUALS(registerMsg->field_topicName().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + + regMsgId1 = registerMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestRegackMsg regackMsg; + regackMsg.field_msgId().setValue(regMsgId1); + regackMsg.field_topicId().setValue(TopicId); + regackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, regackMsg); + } + + unsigned pubMsgId1 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + + pubMsgId1 = publishMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestPubackMsg pubackMsg; + pubackMsg.field_msgId().setValue(pubMsgId1); + pubackMsg.field_topicId().setValue(TopicId); + pubackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, pubackMsg); + } + + { + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT(!unitTestHasPublishCompleteReport()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + auto publish2 = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish2, nullptr); + + auto ec = apiPublishConfig(publish2, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish2); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + } + + unsigned pubMsgId2 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + pubMsgId2 = publishMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(pubMsgId2, pubMsgId1); + } + + // Rejecting topic id + { + UnitTestPubackMsg pubackMsg; + pubackMsg.field_msgId().setValue(pubMsgId2); + pubackMsg.field_topicId().setValue(TopicId); + pubackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_InvalidTopicId); + unitTestClientInputMessage(client, pubackMsg); + TS_ASSERT(!unitTestHasPublishCompleteReport()); + } + + unsigned regMsgId2 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* registerMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(registerMsg, nullptr); + TS_ASSERT_EQUALS(registerMsg->field_topicId().value(), 0U); + TS_ASSERT_EQUALS(registerMsg->field_topicName().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + + regMsgId2 = registerMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(regMsgId1, regMsgId2); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestRegackMsg regackMsg; + regackMsg.field_msgId().setValue(regMsgId2); + regackMsg.field_topicId().setValue(TopicId); + regackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, regackMsg); + } + + unsigned pubMsgId3 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + pubMsgId3 = publishMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(pubMsgId3, pubMsgId2); + } + + // Invalidating topic ID from the gateway + { + UnitTestPubackMsg pubackMsg; + pubackMsg.field_topicId().setValue(TopicId); + pubackMsg.field_msgId().setValue(pubMsgId3); + pubackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, pubackMsg); + } + + TS_ASSERT(unitTestHasTickReq()); +} + +// TODO: loop of accepting registration, but rejecting topic id \ No newline at end of file From 05b86c6692809710b020303051fe5340654f763f Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 25 Jul 2024 08:12:01 +1000 Subject: [PATCH 075/106] More publish unit testing. --- client/lib/src/op/SendOp.cpp | 1 + client/lib/test/UnitTestPublish.th | 152 +++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index 1427fdf0..e7bbc3dd 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -285,6 +285,7 @@ void SendOp::handle(PubackMsg& msg) --m_fullRetryRemCount; m_stage = Stage_Register; + m_publishMsg.field_flags().field_high().setBitValue_Dup(false); // Make sure it's not reported as duplicate // Re-allocate new packet IDs releasePacketIdsInternal(); diff --git a/client/lib/test/UnitTestPublish.th b/client/lib/test/UnitTestPublish.th index 103711f1..34112b7f 100644 --- a/client/lib/test/UnitTestPublish.th +++ b/client/lib/test/UnitTestPublish.th @@ -27,6 +27,7 @@ public: void test17(); void test18(); void test19(); + void test20(); private: virtual void setUp() override @@ -2004,4 +2005,155 @@ void UnitTestPublish::test19() TS_ASSERT(unitTestHasTickReq()); } +void UnitTestPublish::test20() +{ + // Testing loop of gateway accepting registration, but rejecting topic id + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + auto ec = apiSetDefaultRetryCount(client, 1U); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic("abcd"); + const UnitTestData Data = {1, 2, 3, 4, 5}; + const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; + const CC_MqttsnTopicId TopicId = 1; + + CC_MqttsnPublishConfig config; + apiPublishInitConfig(&config); + + config.m_topic = Topic.c_str(); + config.m_qos = Qos; + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + + { + auto publish = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish, nullptr); + + ec = apiPublishConfig(publish, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + } + + unsigned regMsgId1 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* registerMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(registerMsg, nullptr); + TS_ASSERT_EQUALS(registerMsg->field_topicId().value(), 0U); + TS_ASSERT_EQUALS(registerMsg->field_topicName().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + + regMsgId1 = registerMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestRegackMsg regackMsg; + regackMsg.field_msgId().setValue(regMsgId1); + regackMsg.field_topicId().setValue(TopicId); + regackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, regackMsg); + } + + unsigned pubMsgId1 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + pubMsgId1 = publishMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestPubackMsg pubackMsg; + pubackMsg.field_msgId().setValue(pubMsgId1); + pubackMsg.field_topicId().setValue(TopicId); + pubackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_InvalidTopicId); + unitTestClientInputMessage(client, pubackMsg); + TS_ASSERT(!unitTestHasPublishCompleteReport()); + } + + unsigned regMsgId2 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* registerMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(registerMsg, nullptr); + TS_ASSERT_EQUALS(registerMsg->field_topicId().value(), 0U); + TS_ASSERT_EQUALS(registerMsg->field_topicName().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + + regMsgId2 = registerMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(regMsgId2, regMsgId1); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestRegackMsg regackMsg; + regackMsg.field_msgId().setValue(regMsgId2); + regackMsg.field_topicId().setValue(TopicId); + regackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, regackMsg); + } + + unsigned pubMsgId2 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(static_cast(publishMsg->field_flags().field_qos().value()), Qos); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + pubMsgId2 = publishMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(pubMsgId2, pubMsgId1); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestPubackMsg pubackMsg; + pubackMsg.field_msgId().setValue(pubMsgId2); + pubackMsg.field_topicId().setValue(TopicId); + pubackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_InvalidTopicId); + unitTestClientInputMessage(client, pubackMsg); + } + + { + TS_ASSERT(!unitTestHasOutputData()); + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(publishReport->m_info.m_returnCode, CC_MqttsnReturnCode_InvalidTopicId); + TS_ASSERT(!unitTestHasPublishCompleteReport()); + } + + TS_ASSERT(unitTestHasTickReq()); +} + // TODO: loop of accepting registration, but rejecting topic id \ No newline at end of file From 2b42a62a5d5fa38fe593da95938e5834f2a53117 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 25 Jul 2024 08:56:07 +1000 Subject: [PATCH 076/106] Added publish operation documentation. --- client/lib/doxygen/main.dox | 217 +++++++++++++++++++++++++++++++++++- 1 file changed, 211 insertions(+), 6 deletions(-) diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index e288d3c2..4dca6eb1 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -243,7 +243,7 @@ /// @code /// CC_MqttsnErrorCode ec = CC_MqttsnErrorCode_Success; /// CC_MqttsnConnectHandle connect = cc_mqttsn_client_search_prepare(...); -/// assert(connect != nullptr); +/// assert(connect != NULL); /// /// // The following attempt to prepare the "subscribe" operation will fail because /// // previously allocated "search" hasn't been sent or cancelled yet. @@ -809,7 +809,7 @@ /// ... /// } /// -/// ec = cc_mqttsn_client_disconnect_send(connect, &my_disconnect_complete_cb, data); +/// ec = cc_mqttsn_client_disconnect_send(disconnect, &my_disconnect_complete_cb, data); /// if (ec != CC_MqttsnErrorCode_Success) { /// printf("ERROR: Failed to send disconnect request with ec=%d\n", ec); /// ... @@ -887,7 +887,7 @@ /// configuration. It can be changed for the allocated operation using the /// @b cc_mqttsn_client_subscribe_set_retry_period() function. /// @code -/// ec = cc_mqttsn_client_subscribe_set_retry_period(connect, 1000); +/// ec = cc_mqttsn_client_subscribe_set_retry_period(subscribe, 1000); /// if (ec != CC_MqttsnErrorCode_Success) { /// ... /* Something went wrong */ /// } @@ -963,7 +963,7 @@ /// /// @subsection doc_cc_mqttsn_client_subscribe_send Sending Subscription Request /// When all the necessary configurations are performed for the allocated "subscribe" -/// operation it can actually be sent to the broker. To initiate sending +/// operation it can actually be sent to the gateway. To initiate sending /// use the @b cc_mqttsn_client_subscribe_send() function. /// @code /// void my_subscribe_complete_cb(void* data, CC_MqttsnSubscribeHandle handle, CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info) @@ -1073,7 +1073,7 @@ /// configuration. It can be changed for the allocated operation using the /// @b cc_mqttsn_client_unsubscribe_set_retry_period() function. /// @code -/// ec = cc_mqttsn_client_unsubscribe_set_retry_period(connect, 1000); +/// ec = cc_mqttsn_client_unsubscribe_set_retry_period(unsubscribe, 1000); /// if (ec != CC_MqttsnErrorCode_Success) { /// ... /* Something went wrong */ /// } @@ -1148,7 +1148,7 @@ /// /// @subsection doc_cc_mqttsn_client_unsubscribe_send Sending Unsubscription Request /// When all the necessary configurations are performed for the allocated "unsubscribe" -/// operation it can actually be sent to the broker. To initiate sending +/// operation it can actually be sent to the gateway. To initiate sending /// use the @b cc_mqttsn_client_unsubscribe_send() function. /// @code /// void my_unsubscribe_complete_cb(void* data, CC_MqttsnUnsubscribeHandle handle, CC_MqttsnAsyncOpStatus status) @@ -1232,6 +1232,211 @@ /// unsubscribe operation by the reported handle when the completion callback /// is invoked. /// +/// @section doc_cc_mqttsn_client_publish Publishing Messages +/// To publish messages to the gateway use @ref publish "publish" operation. +/// +/// @subsection doc_cc_mqttsn_client_publish_prepare Preparing "Publish" Operation. +/// @code +/// CC_MqttsnErrorCode ec = CC_MqttsnErrorCode_Success; +/// CC_MqttsnPublishHandle publish = cc_mqttsn_client_publish_prepare(client, &ec); +/// if (publish == NULL) { +/// printf("ERROR: Publish allocation failed with ec=%d\n", ec); +/// } +/// @endcode +/// +/// @subsection doc_cc_mqttsn_client_publish_retry_period Configuring "Publish" Retry Period +/// When created, the "publish" operation inherits the @ref doc_cc_mqttsn_client_retry_period +/// configuration. It can be changed for the allocated operation using the +/// @b cc_mqttsn_client_publish_set_retry_period() function. +/// @code +/// ec = cc_mqttsn_client_publish_set_retry_period(publish, 1000); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// ... /* Something went wrong */ +/// } +/// @endcode +/// To retrieve the configured response timeout use the @b cc_mqttsn_client_publish_get_retry_period() function. +/// +/// @subsection doc_cc_mqttsn_client_publish_retry_count Configuring "Publish" Retry Count +/// When created, the "publish" operation inherits the @ref doc_cc_mqttsn_client_retry_count +/// configuration. It can be changed for the allocated operation using the +/// @b cc_mqttsn_client_publish_set_retry_count() function. +/// @code +/// ec = cc_mqttsn_client_unsubscribe_set_retry_count(publish, 2); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// ... /* Something went wrong */ +/// } +/// @endcode +/// To retrieve the configured response timeout use the @b cc_mqttsn_client_publish_get_retry_count() function. +/// +/// The publish operation can exchange multiple messages in a single transaction. For example, when +/// publishing @b QoS2 messages which require registration, the client will send @b REGISTER, +/// @b PUBLISH, and @b PUBREL messages and will expect @b REGACK, @b PUBREC, and @b PUBCOMP +/// messages as their respective acknowledgements. The configuration of the retry count +/// is per such single message exchange. In other words when 2 retries are allowed, the client will +/// allow 2 retries for @b REGISTER <-> @b REGACK, 2 retries for @b PUBLISH <-> @b PUBREC +/// and 2 retries for @b PUBREL <-> @b PUBCOMP exchanges. +/// +/// @subsection doc_cc_mqttsn_client_publish_config Configuration of "Publish" Operation +/// @code +/// CC_MqttsnPublishConfig config; +/// +/// // Assign default values to the "config" +/// cc_mqttsn_client_publish_init_config(&config); +/// +/// // Update the required values +/// config.m_topic = "some/topic"; +/// config.m_data = &some_buf[0]; +/// config.m_dataLen = ...; +/// config.m_qos = ...; +/// +/// // Perform the configuration +/// ec = cc_mqttsn_client_publish_config(publish, &config); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Configuration failed with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// See also documentation of the @ref CC_MqttsnPublishConfig structure. +/// +/// Similar to the @ref doc_cc_mqttsn_client_subscribe "subscribe" operation it +/// is possible to use predefined topic id by setting @ref CC_MqttsnPublishConfig::m_topicId "topicId" +/// member instead of @ref CC_MqttsnPublishConfig::m_topic "m_topic". +/// @code +/// // Update the required values +/// config.m_topicId = 123; +/// @endcode +/// +/// By default the library will perform the analysis of the submitted topic format and +/// reject it if topic format is incorrect. However, for performance reasons +/// it is possible to disable such verification when client application +/// ensures that no invalid topics are used. +/// @code +/// ec = cc_mqttsn_client_set_verify_outgoing_topic_enabled(client, false); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// ... /* Something is wrong */ +/// } +/// @endcode +/// To retrieve the current configuration use the @b cc_mqttsn_client_get_verify_outgoing_topic_enabled() +/// function. +/// +/// @b NOTE that the configuration is global per client and not per "publish" +/// operation. +/// +/// Also @b note that the same function controls the verification of the +/// "subscribe", "unsubscribe", and "publish" filter / topic formats. +/// +/// @subsection doc_cc_mqttsn_client_publish_send Sending Publish Request +/// When all the necessary configurations are performed for the allocated "publish" +/// operation it can actually be sent to the gateway. To initiate sending +/// use the @b cc_mqttsn_client_publish_send() function. +/// @code +/// void my_publish_complete_cb(void* data, CC_MqttsnPublishHandle handle, CC_MqttsnAsyncOpStatus status, const CC_MqttsnPublishInfo* info) +/// { +/// if (status != CC_MqttsnAsyncOpStatus_Complete) { +/// printf("ERROR: The publish operation has failed with status=%d\n", status); +/// ... // handle error. +/// return; +/// } +/// +/// if (info != NULL) { +/// ... // Analyse return code +/// } +/// ... +/// } +/// +/// ec = cc_mqttsn_client_publish_send(publish, &my_publish_complete_cb, data); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to send publish request with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// The provided callback will be invoked when the "publish" operation is complete +/// if and only if the function returns @ref CC_MqttsnErrorCode_Success. +/// +/// When QoS value is @ref CC_MqttsnQoS_AtMostOnceDelivery and no registration stage +/// is required, there is no gateway response to wait for and the callback is invoked right away +/// after sending the serialized data. +/// +/// When the callback is invoked with @ref CC_MqttsnAsyncOpStatus_Complete status +/// the "info" parameter may or may not be @b NULL. If it's @b NULL then +/// the operation is successful. If its not @b NULL, then extra analysis of the +/// @ref CC_MqttsnPublishInfo::m_returnCode "m_returnCode" data member value is +/// expected to be performed by the application. +/// @li If the return code reports +/// @ref CC_MqttsnReturnCode_Conjestion, then it's up to the application to retry +/// the re-send later (specification requires waiting @b Twait time before +/// retrying). The retry is performed by issuing another @ref doc_cc_mqttsn_client_publish "publish" +/// operation. +/// @li If the return code reports @ref CC_MqttsnReturnCode_InvalidTopicId then +/// it's a case when the gateway rejected used topic ID (probably pre-defined one) +/// and the client library cannot silently perform re-registration because +/// the topic information was missing in the @ref doc_cc_mqttsn_client_publish_config "configuration". +/// +/// The handle returned by the @b cc_mqttsn_client_publish_prepare() function +/// can be discarded (there is no free / de-allocation) right after the +/// @b cc_mqttsn_client_publish_send() invocation +/// regardless of the returned error code. However, the handle remains valid until +/// the callback is called (in case the @ref CC_MqttsnErrorCode_Success was returned). +/// The valid handle can be used to @ref doc_cc_mqttsn_client_publish_cancel "cancel" +/// the operation before the completion callback is invoked. +/// +/// The MQTT-SN spec demands that the @b PUBLISH transactions be issued one at +/// a time. However, the library allows @ref doc_cc_mqttsn_client_publish_prepare "preparing" +/// and @ref doc_cc_mqttsn_client_publish_send "sending" multiple publish +/// requests in parallel before completion of the first one. The library will +/// retain the requested "publish" operation requests internally and will +/// issue them one after another to comply with the specification. +/// +/// Note that the callback function receives the "publish" operation handle as +/// its second parameter. Although the handle is already invalid and cannot be +/// used in any other function, it allows the application to identify the +/// original "publish" request if multiple have been issued in parallel +/// and use the same callback function for all of them. +/// +/// @subsection doc_cc_mqttsn_client_publish_cancel Cancel the "Publish" Operation. +/// While the handle returned by the @b cc_mqttsn_client_publish_prepare() is still +/// valid it is possible to cancel / discard the operation. +/// @code +/// ec = cc_mqttsn_client_publish_cancel(publish); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to cancel publish with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// In case the @b cc_mqttsn_client_publish_send() function was successfully +/// called before the @b cc_mqttsn_client_publish_cancel(), the operation is +/// cancelled @b without callback invocation. +/// +/// @subsection doc_cc_mqttsn_client_publish_simplify Simplifying the "Publish" Operation Preparation. +/// In many use cases the "publish" operation can be quite simple with a lot of defaults. +/// To simplify the sequence of the operation preparation and handling of errors, +/// the library provides wrapper function that can be used: +/// @li @b cc_mqttsn_client_publish() +/// +/// For example: +/// @code +/// CC_MqttsnPublishConfig config; +/// +/// // Assign default values to the configuration +/// cc_mqttsn_client_publish_init_config(&config); +/// +/// // Update values +/// config.m_topic = "some/topic"; +/// config.m_data = ...; +/// ... +/// +/// ec = cc_mqttsn_client_publish(client, &config, &my_publish_complete_cb, data); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to send publish request with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// Note that the wrapper function does NOT expose the handle returned by the +/// @b cc_mqttsn_client_publish_prepare(). It means that it's not possible to +/// cancel the "publish" operation before its completion or identify the +/// publish operation by the reported handle when the completion callback +/// is invoked. +/// /// @section doc_cc_mqttsn_client_receive Receiving Messages /// TODO /// From 9c20e34aeae5ba71e0f3b38d99b7f7b9111efc7f Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Mon, 29 Jul 2024 09:21:01 +1000 Subject: [PATCH 077/106] Saving work on will update. --- client/lib/CMakeLists.txt | 1 + client/lib/include/cc_mqttsn_client/common.h | 39 ++- client/lib/src/ClientImpl.cpp | 148 ++++++---- client/lib/src/ClientImpl.h | 21 +- client/lib/src/ExtConfig.h | 12 +- client/lib/src/ProtocolDefs.h | 8 +- client/lib/src/ReuseState.h | 20 ++ client/lib/src/SessionState.h | 1 - client/lib/src/op/ConnectOp.cpp | 54 +++- client/lib/src/op/ConnectOp.h | 14 +- client/lib/src/op/Op.h | 1 + client/lib/src/op/WillOp.cpp | 290 +++++++++++++++++++ client/lib/src/op/WillOp.h | 75 +++++ client/lib/test/UnitTestConnect.th | 4 +- 14 files changed, 589 insertions(+), 99 deletions(-) create mode 100644 client/lib/src/op/WillOp.cpp create mode 100644 client/lib/src/op/WillOp.h diff --git a/client/lib/CMakeLists.txt b/client/lib/CMakeLists.txt index 921e00f9..ec75960a 100644 --- a/client/lib/CMakeLists.txt +++ b/client/lib/CMakeLists.txt @@ -173,6 +173,7 @@ function (gen_lib_mqttsn_client config_file) src/op/SendOp.cpp src/op/SubscribeOp.cpp src/op/UnsubscribeOp.cpp + src/op/WillOp.cpp src/ClientImpl.cpp src/TimerMgr.cpp ) diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index 72476c44..b975d46b 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -176,19 +176,18 @@ struct CC_MqttsnPublish; /// @ingroup "publish". typedef struct CC_MqttsnPublish* CC_MqttsnPublishHandle; +/// @brief Declaration of the hidden structure used to define @ref CC_MqttsnWillHandle +/// @ingroup will +struct CC_MqttsnWill; + +/// @brief Handle for "will" operation. +/// @details Returned by @b cc_mqttsn_client_will_prepare() function. +/// @ingroup "will". +typedef struct CC_MqttsnWill* CC_MqttsnWillHandle; + /// @brief Type used to hold Topic ID value. typedef unsigned short CC_MqttsnTopicId; -/// @brief Will Information -typedef struct -{ - const char* topic; ///< Topic of the will, can be NULL (means empty topic) - const unsigned char* msg; ///< Pointer to the buffer containing will binary message. - unsigned msgLen; ///< Length of the buffer containing will binary message. - CC_MqttsnQoS qos; ///< QoS level of the will message. - bool retain; ///< Retain flag -} CC_MqttsnWillInfo; - /// @brief Incoming message information typedef struct { @@ -280,6 +279,14 @@ typedef struct CC_MqttsnReturnCode m_returnCode; ///< Return code reported by the @b PUBACK message } CC_MqttsnPublishInfo; +/// @brief Information on the "will" operation completion +/// @ingroup will +typedef struct +{ + CC_MqttsnReturnCode m_topicUpdReturnCode; ///< Return code reported by the @b WILLTOPICRESP message + CC_MqttsnReturnCode m_msgUpdReturnCode; ///< Return code reported by the @b WILLMSGRESP message +} CC_MqttsnWillInfo; + /// @brief Callback used to request time measurement. /// @details The callback is set using /// cc_mqttsn_client_set_next_tick_program_callback() function. @@ -409,13 +416,23 @@ typedef void (*CC_MqttsnUnsubscribeCompleteCb)(void* data, CC_MqttsnUnsubscribeH /// function invocation, but it allows end application to identify the original "publish" operation /// and use the same callback function in parallel requests. /// @param[in] status Status of the "publish" operation. -/// @param[in] info Information about op completion. Not-NULL is reported onfly if +/// @param[in] info Information about op completion. Not-NULL is reported only if /// the "status" is equal to @ref CC_MqttsnAsyncOpStatus_Complete. When QoS2 publish /// is successfully performed the "info" can still be NULL. /// @post The data members of the reported response can NOT be accessed after the function returns. /// @ingroup publish typedef void (*CC_MqttsnPublishCompleteCb)(void* data, CC_MqttsnPublishHandle handle, CC_MqttsnAsyncOpStatus status, const CC_MqttsnPublishInfo* info); +/// @brief Callback used to report completion of the publish operation. +/// @param[in] data Pointer to user data object, passed as the last parameter to +/// the request call. +/// @param[in] status Status of the "will" operation. +/// @param[in] info Information about op completion. Not-NULL is reported if and onfly if +/// the "status" is equal to @ref CC_MqttsnAsyncOpStatus_Complete. +/// @post The data members of the reported response can NOT be accessed after the function returns. +/// @ingroup will +typedef void (*CC_MqttsnWillCompleteCb)(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnWillInfo* info); + #ifdef __cplusplus } #endif diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 7bf74493..d19507d9 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -427,6 +427,80 @@ op::SendOp* ClientImpl::publishPrepare(CC_MqttsnErrorCode* ec) return op; } +#if CC_MQTTSN_CLIENT_HAS_WILL +op::WillOp* ClientImpl::willPrepare(CC_MqttsnErrorCode* ec) +{ + op::WillOp* op = nullptr; + do { + if (!m_sessionState.m_connected) { + errorLog("Client must be connected to allow will update."); + updateEc(ec, CC_MqttsnErrorCode_NotConnected); + break; + } + + if (m_sessionState.m_disconnecting) { + errorLog("Session disconnection is in progress, cannot initiate publish."); + updateEc(ec, CC_MqttsnErrorCode_Disconnecting); + break; + } + + if (!m_willOps.empty()) { + // Already allocated + errorLog("Another will operation is in progress."); + updateEc(ec, CC_MqttsnErrorCode_Busy); + break; + } + + if (!m_connectOps.empty()) { + // Already allocated + errorLog("Another connect operation is in progress."); + updateEc(ec, CC_MqttsnErrorCode_Busy); + break; + } + + if (!m_disconnectOps.empty()) { + // Already allocated + errorLog("Another disconnect operation is in progress."); + updateEc(ec, CC_MqttsnErrorCode_Busy); + break; + } + + if (m_sessionState.m_disconnecting) { + errorLog("Session disconnection is in progress, cannot initiate will update."); + updateEc(ec, CC_MqttsnErrorCode_Disconnecting); + break; + } + + if (m_ops.max_size() <= m_ops.size()) { + errorLog("Cannot start will operation, retry in next event loop iteration."); + updateEc(ec, CC_MqttsnErrorCode_RetryLater); + break; + } + + if (m_preparationLocked) { + errorLog("Another operation is being prepared, cannot prepare \"will\" without \"send\" or \"cancel\" of the previous."); + updateEc(ec, CC_MqttsnErrorCode_PreparationLocked); + break; + } + + auto ptr = m_willOpAlloc.alloc(*this); + if (!ptr) { + errorLog("Cannot allocate new will operation."); + updateEc(ec, CC_MqttsnErrorCode_OutOfMemory); + break; + } + + m_preparationLocked = true; + m_ops.push_back(ptr.get()); + m_willOps.push_back(std::move(ptr)); + op = m_willOps.back().get(); + updateEc(ec, CC_MqttsnErrorCode_Success); + } while (false); + + return op; +} +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL + // CC_MqttsnErrorCode ClientImpl::setPublishOrdering(CC_MqttsnPublishOrdering ordering) // { // if (CC_MqttsnPublishOrdering_ValuesLimit <= ordering) { @@ -849,6 +923,7 @@ void ClientImpl::opComplete(const op::Op* op) /* Type_Unsubscribe */ &ClientImpl::opComplete_Unsubscribe, // /* Type_Recv */ &ClientImpl::opComplete_Recv, /* Type_Send */ &ClientImpl::opComplete_Send, + /* Type_Will */ &ClientImpl::opComplete_Will, }; static const std::size_t MapSize = std::extent::value; static_assert(MapSize == op::Op::Type_NumOfValues); @@ -892,48 +967,6 @@ void ClientImpl::gatewayDisconnected( // m_messageReceivedReportCb(m_messageReceivedReportData, &info); // } -// bool ClientImpl::hasPausedSendsBefore(const op::SendOp* sendOp) const -// { -// auto riter = -// std::find_if( -// m_sendOps.rbegin(), m_sendOps.rend(), -// [sendOp](auto& opPtr) -// { -// return opPtr.get() == sendOp; -// }); - -// COMMS_ASSERT(riter != m_sendOps.rend()); -// if (riter == m_sendOps.rend()) { -// return false; -// } - -// auto iter = riter.base() - 1; -// auto idx = static_cast(std::distance(m_sendOps.begin(), iter)); -// COMMS_ASSERT(idx < m_sendOps.size()); -// if (idx == 0U) { -// return false; -// } - -// auto& prevSendOpPtr = m_sendOps[idx - 1U]; -// return prevSendOpPtr->isPaused(); -// } - -// bool ClientImpl::hasHigherQosSendsBefore(const op::SendOp* sendOp, op::Op::Qos qos) const -// { -// for (auto& sendOpPtr : m_sendOps) { -// if (sendOpPtr.get() == sendOp) { -// return false; -// } - -// if (sendOpPtr->qos() > qos) { -// return true; -// } -// } - -// COMMS_ASSERT(false); // Mustn't reach here -// return false; -// } - void ClientImpl::allowNextPrepare() { COMMS_ASSERT(m_preparationLocked); @@ -1008,20 +1041,20 @@ void ClientImpl::terminateOps(CC_MqttsnAsyncOpStatus status) void ClientImpl::cleanOps() { -// if (!m_opsDeleted) { -// return; -// } + if (!m_opsDeleted) { + return; + } -// m_ops.erase( -// std::remove_if( -// m_ops.begin(), m_ops.end(), -// [](auto* op) -// { -// return op == nullptr; -// }), -// m_ops.end()); + m_ops.erase( + std::remove_if( + m_ops.begin(), m_ops.end(), + [](auto* op) + { + return op == nullptr; + }), + m_ops.end()); -// m_opsDeleted = false; + m_opsDeleted = false; } void ClientImpl::errorLogInternal(const char* msg) @@ -1226,6 +1259,13 @@ void ClientImpl::opComplete_Send(const op::Op* op) m_sendOps.front()->resume(); } +void ClientImpl::opComplete_Will([[maybe_unused]] const op::Op* op) +{ +#if CC_MQTTSN_CLIENT_HAS_WILL + eraseFromList(op, m_willOps); +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL +} + void ClientImpl::finaliseSupUnsubOp() { if (m_subscribeOps.empty() && m_unsubscribeOps.empty()) { diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index 4c9bf383..ee1eb7a9 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -26,6 +26,7 @@ #include "op/SendOp.h" #include "op/SubscribeOp.h" #include "op/UnsubscribeOp.h" +#include "op/WillOp.h" #include "cc_mqttsn_client/common.h" @@ -74,6 +75,7 @@ class ClientImpl final : public ProtMsgHandler op::SubscribeOp* subscribePrepare(CC_MqttsnErrorCode* ec); op::UnsubscribeOp* unsubscribePrepare(CC_MqttsnErrorCode* ec); op::SendOp* publishPrepare(CC_MqttsnErrorCode* ec); + op::WillOp* willPrepare(CC_MqttsnErrorCode* ec); // std::size_t sendsCount() const // { @@ -247,17 +249,16 @@ class ClientImpl final : public ProtMsgHandler using SendOpAlloc = ObjAllocator; using SendOpsList = ObjListType; +#if CC_MQTTSN_CLIENT_HAS_WILL + using WillOpAlloc = ObjAllocator; + using WillOpsList = ObjListType; +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL + + using OpPtrsList = ObjListType; // using OpToDeletePtrsList = ObjListType; using OutputBuf = ObjListType; - // enum TerminateMode - // { - // TerminateMode_KeepSendRecvOps, - // TerminateMode_AbortSendRecvOps, - // TerminateMode_NumOfValues - // }; - void doApiEnter(); void doApiExit(); void createKeepAliveOpIfNeeded(); @@ -279,6 +280,7 @@ class ClientImpl final : public ProtMsgHandler void opComplete_Unsubscribe(const op::Op* op); // void opComplete_Recv(const op::Op* op); void opComplete_Send(const op::Op* op); + void opComplete_Will(const op::Op* op); void finaliseSupUnsubOp(); void monitorGatewayExpiry(); @@ -355,6 +357,11 @@ class ClientImpl final : public ProtMsgHandler SendOpAlloc m_sendOpsAlloc; SendOpsList m_sendOps; +#if CC_MQTTSN_CLIENT_HAS_WILL + WillOpAlloc m_willOpAlloc; + WillOpsList m_willOps; +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL + OpPtrsList m_ops; unsigned m_pendingGwinfoBroadcastRadius = 0U; bool m_opsDeleted = false; diff --git a/client/lib/src/ExtConfig.h b/client/lib/src/ExtConfig.h index e29bc9d8..26071259 100644 --- a/client/lib/src/ExtConfig.h +++ b/client/lib/src/ExtConfig.h @@ -30,6 +30,8 @@ struct ExtConfig : public Config static constexpr unsigned RecvOpsLimit = MaxQos < 2 ? 1U : (ReceiveMaxLimit == 0U ? 0U : ReceiveMaxLimit + 1U); static constexpr unsigned RecvOpTimers = 1U; static constexpr unsigned SendOpTimers = 1U; + static constexpr unsigned WillOpsLimit = HasDynMemAlloc ? 0 : 1U; + static constexpr unsigned WillOpTimers = 1U; static constexpr bool HasOpsLimit = (SearchOpsLimit > 0U) && (ConnectOpsLimit > 0U) && @@ -38,7 +40,8 @@ struct ExtConfig : public Config (SubscribeOpsLimit > 0U) && (UnsubscribeOpsLimit > 0U) && (RecvOpsLimit > 0U) && - (SendOpsLimit > 0U); + (SendOpsLimit > 0U) && + (HasWill && (WillOpsLimit > 0U)); static constexpr unsigned MaxTimersLimit = (DiscoveryTimers) + (SearchOpsLimit * SearchOpTimers) + @@ -49,7 +52,8 @@ struct ExtConfig : public Config (SubscribeOpsLimit * SubscribeOpTimers) + (UnsubscribeOpsLimit * UnsubscribeOpTimers) + (RecvOpsLimit * RecvOpTimers) + - (SendOpsLimit * SendOpTimers); + (SendOpsLimit * SendOpTimers) + + (WillOpsLimit * WillOpTimers); static constexpr unsigned TimersLimit = HasOpsLimit ? MaxTimersLimit : 0U; static const unsigned MaxOpsLimit = @@ -59,7 +63,8 @@ struct ExtConfig : public Config SubscribeOpsLimit + UnsubscribeOpsLimit + RecvOpsLimit + - SendOpsLimit; + SendOpsLimit + + WillOpsLimit; static const unsigned OpsLimit = HasOpsLimit ? MaxOpsLimit : 0U; @@ -76,6 +81,7 @@ struct ExtConfig : public Config static_assert(HasDynMemAlloc || (DisconnectOpsLimit > 0U)); static_assert(HasDynMemAlloc || (RecvOpsLimit > 0U)); static_assert(HasDynMemAlloc || (SendOpsLimit > 0U)); + static_assert(HasDynMemAlloc || (WillOpsLimit > 0U)); static_assert(HasDynMemAlloc || (OpsLimit > 0U)); static_assert(HasDynMemAlloc || (PacketIdsLimit > 0U)); }; diff --git a/client/lib/src/ProtocolDefs.h b/client/lib/src/ProtocolDefs.h index a378691d..179e5bf9 100644 --- a/client/lib/src/ProtocolDefs.h +++ b/client/lib/src/ProtocolDefs.h @@ -46,10 +46,10 @@ using ProtInputMessages = cc_mqttsn::message::Gwinfo, #endif // CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY cc_mqttsn::message::Connack, -#ifdef CC_MQTTSN_CLIENT_HAS_WILL +#if CC_MQTTSN_CLIENT_HAS_WILL cc_mqttsn::message::Willtopicreq, cc_mqttsn::message::Willmsgreq, -#endif // #ifdef CC_MQTTSN_CLIENT_HAS_WILL +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL cc_mqttsn::message::Register, cc_mqttsn::message::Regack, cc_mqttsn::message::Publish, @@ -64,11 +64,11 @@ using ProtInputMessages = cc_mqttsn::message::Pingreq, cc_mqttsn::message::Pingresp, cc_mqttsn::message::Disconnect -#ifdef CC_MQTTSN_CLIENT_HAS_WILL +#if CC_MQTTSN_CLIENT_HAS_WILL , cc_mqttsn::message::Willtopicresp, cc_mqttsn::message::Willmsgresp -#endif // #ifdef CC_MQTTSN_CLIENT_HAS_WILL +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL >; using ProtFrame = cc_mqttsn::frame::Frame; diff --git a/client/lib/src/ReuseState.h b/client/lib/src/ReuseState.h index adc57ad9..74b25915 100644 --- a/client/lib/src/ReuseState.h +++ b/client/lib/src/ReuseState.h @@ -7,8 +7,12 @@ #pragma once +#include "ExtConfig.h" +#include "ProtocolDefs.h" #include "TopicFilterDefs.h" +#include "cc_mqttsn_client/common.h" + namespace cc_mqttsn_client { @@ -17,6 +21,22 @@ struct ReuseState SubFiltersMap m_subFilters; InRegTopicsMap m_inRegTopics; OutRegTopicsMap m_outRegTopics; + +#if CC_MQTTSN_CLIENT_HAS_WILL + using WillTopicType = WilltopicMsg::Field_willTopic::ValueType; + using WillMsgType = WillmsgMsg::Field_willMsg::ValueType; + + struct WillInfo + { + WillTopicType m_topic; + WillMsgType m_msg; + CC_MqttsnQoS m_qos = CC_MqttsnQoS_AtMostOnceDelivery; + bool m_retain = false; + }; + + WillInfo m_prevWill; + +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL }; } // namespace cc_mqttsn_client diff --git a/client/lib/src/SessionState.h b/client/lib/src/SessionState.h index 8e6e0dfc..2c3f55bc 100644 --- a/client/lib/src/SessionState.h +++ b/client/lib/src/SessionState.h @@ -12,7 +12,6 @@ namespace cc_mqttsn_client struct SessionState { - static constexpr unsigned DefaultKeepAlive = 60; unsigned m_keepAliveMs = 0U; diff --git a/client/lib/src/op/ConnectOp.cpp b/client/lib/src/op/ConnectOp.cpp index 20a482c0..ac967fe2 100644 --- a/client/lib/src/op/ConnectOp.cpp +++ b/client/lib/src/op/ConnectOp.cpp @@ -66,7 +66,7 @@ CC_MqttsnErrorCode ConnectOp::config(const CC_MqttsnConnectConfig* config) CC_MqttsnErrorCode ConnectOp::willConfig([[maybe_unused]] const CC_MqttsnWillConfig* config) { -#ifdef CC_MQTTSN_CLIENT_HAS_WILL +#if CC_MQTTSN_CLIENT_HAS_WILL if (config == nullptr) { errorLog("Will configuration is not provided."); return CC_MqttsnErrorCode_BadParam; @@ -79,6 +79,8 @@ CC_MqttsnErrorCode ConnectOp::willConfig([[maybe_unused]] const CC_MqttsnWillCon return CC_MqttsnErrorCode_BadParam; } + m_willtopicMsg.field_flags().setMissing(); + m_willtopicMsg.field_willTopic().value().clear(); return CC_MqttsnErrorCode_Success; } @@ -101,10 +103,10 @@ CC_MqttsnErrorCode ConnectOp::willConfig([[maybe_unused]] const CC_MqttsnWillCon comms::util::assign(m_willmsgMsg.field_willMsg().value(), config->m_data, config->m_data + config->m_dataLen); } return CC_MqttsnErrorCode_Success; -#else // #ifdef CC_MQTTSN_CLIENT_HAS_WILL +#else // #if CC_MQTTSN_CLIENT_HAS_WILL errorLog("Will configuration is not supported"); return CC_MqttsnErrorCode_NotSupported; -#endif // #ifdef CC_MQTTSN_CLIENT_HAS_WILL +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL } CC_MqttsnErrorCode ConnectOp::send(CC_MqttsnConnectCompleteCb cb, void* cbData) @@ -137,6 +139,7 @@ CC_MqttsnErrorCode ConnectOp::send(CC_MqttsnConnectCompleteCb cb, void* cbData) m_cbData = cbData; m_origRetryCount = getRetryCount(); + auto ec = sendInternal(); if (ec != CC_MqttsnErrorCode_Success) { return ec; @@ -162,7 +165,7 @@ CC_MqttsnErrorCode ConnectOp::cancel() return CC_MqttsnErrorCode_Success; } -#ifdef CC_MQTTSN_CLIENT_HAS_WILL +#if CC_MQTTSN_CLIENT_HAS_WILL void ConnectOp::handle([[maybe_unused]] WilltopicreqMsg& msg) { if (!m_connectMsg.field_flags().field_mid().getBitValue_Will()) { @@ -170,6 +173,11 @@ void ConnectOp::handle([[maybe_unused]] WilltopicreqMsg& msg) return; } + if (m_willtopicMsg.field_willTopic().value().empty()) { + // The will message won't be requested + client().reuseState().m_prevWill = ReuseState::WillInfo(); + } + setRetryCount(m_origRetryCount); m_stage = Stage_willTopic; @@ -187,6 +195,17 @@ void ConnectOp::handle([[maybe_unused]] WillmsgreqMsg& msg) return; } + if (m_stage != Stage_willTopic) { + errorLog("WILLMSGREQ before WILLTOPICREQ, ignoring"); + return; + } + + auto& prevWill = client().reuseState().m_prevWill; + prevWill.m_topic = m_willtopicMsg.field_willTopic().value().c_str(); + prevWill.m_msg.clear(); + prevWill.m_qos = static_cast(m_willtopicMsg.field_flags().field().field_qos().value()); + prevWill.m_retain = m_willtopicMsg.field_flags().field().field_mid().getBitValue_Retain(); + setRetryCount(m_origRetryCount); m_stage = Stage_willMsg; @@ -196,20 +215,33 @@ void ConnectOp::handle([[maybe_unused]] WillmsgreqMsg& msg) return; } } -#endif // #ifdef CC_MQTTSN_CLIENT_HAS_WILL +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL void ConnectOp::handle(ConnackMsg& msg) { auto info = CC_MqttsnConnectInfo(); comms::cast_assign(info.m_returnCode) = msg.field_returnCode().value(); -#ifdef CC_MQTTSN_CLIENT_HAS_WILL +#if CC_MQTTSN_CLIENT_HAS_WILL + bool clearPrevWillInfo = (info.m_returnCode != CC_MqttsnReturnCode_Accepted); + if ((info.m_returnCode == CC_MqttsnReturnCode_Accepted) && (m_stage < Stage_willMsg) && (m_connectMsg.field_flags().field_mid().getBitValue_Will())) { + errorLog("Connection accepted without full will inquiry"); + clearPrevWillInfo = true; + } + + if (clearPrevWillInfo) { + client().reuseState().m_prevWill = ReuseState::WillInfo(); + } + else if (Stage_willMsg <= m_stage) { + auto& dataVec = m_willmsgMsg.field_willMsg().value(); + comms::util::assign(client().reuseState().m_prevWill.m_msg, dataVec.begin(), dataVec.end()); } -#endif // #ifdef CC_MQTTSN_CLIENT_HAS_WILL + +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL if (info.m_returnCode == CC_MqttsnReturnCode_Accepted) { client().sessionState().m_keepAliveMs = comms::units::getMilliseconds(m_connectMsg.field_duration()); @@ -249,10 +281,10 @@ CC_MqttsnErrorCode ConnectOp::sendInternal() using GetMsgFunc = const ProtMessage& (ConnectOp::*)() const; static const GetMsgFunc Map[] = { /* Stage_connect */ &ConnectOp::getConnectMsg, -#ifdef CC_MQTTSN_CLIENT_HAS_WILL +#if CC_MQTTSN_CLIENT_HAS_WILL /* Stage_willTopic */ &ConnectOp::getWilltopicMsg, /* Stage_willMsg */ &ConnectOp::getWillmsgMsg, -#endif // #ifdef CC_MQTTSN_CLIENT_HAS_WILL +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL }; static const std::size_t MapSize = std::extent::value; static_assert(MapSize == Stage_valuesLimit); @@ -292,7 +324,7 @@ const ProtMessage& ConnectOp::getConnectMsg() const return m_connectMsg; } -#ifdef CC_MQTTSN_CLIENT_HAS_WILL +#if CC_MQTTSN_CLIENT_HAS_WILL const ProtMessage& ConnectOp::getWilltopicMsg() const { return m_willtopicMsg; @@ -302,7 +334,7 @@ const ProtMessage& ConnectOp::getWillmsgMsg() const { return m_willmsgMsg; } -#endif // #ifdef CC_MQTTSN_CLIENT_HAS_WILL +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL void ConnectOp::opTimeoutCb(void* data) { diff --git a/client/lib/src/op/ConnectOp.h b/client/lib/src/op/ConnectOp.h index 4cd5ae93..e38f7ca5 100644 --- a/client/lib/src/op/ConnectOp.h +++ b/client/lib/src/op/ConnectOp.h @@ -31,7 +31,7 @@ class ConnectOp final : public Op CC_MqttsnErrorCode cancel(); using Base::handle; -#ifdef CC_MQTTSN_CLIENT_HAS_WILL +#if CC_MQTTSN_CLIENT_HAS_WILL void handle(WilltopicreqMsg& msg) override; void handle(WillmsgreqMsg& msg) override; #endif @@ -45,10 +45,10 @@ class ConnectOp final : public Op enum Stage : unsigned { Stage_connect, -#ifdef CC_MQTTSN_CLIENT_HAS_WILL +#if CC_MQTTSN_CLIENT_HAS_WILL Stage_willTopic, Stage_willMsg, -#endif // #ifdef CC_MQTTSN_CLIENT_HAS_WILL +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL Stage_valuesLimit }; @@ -58,10 +58,10 @@ class ConnectOp final : public Op void timeoutInternal(); const ProtMessage& getConnectMsg() const; -#ifdef CC_MQTTSN_CLIENT_HAS_WILL +#if CC_MQTTSN_CLIENT_HAS_WILL const ProtMessage& getWilltopicMsg() const; const ProtMessage& getWillmsgMsg() const; -#endif // #ifdef CC_MQTTSN_CLIENT_HAS_WILL +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL static void opTimeoutCb(void* data); @@ -72,10 +72,10 @@ class ConnectOp final : public Op CC_MqttsnConnectCompleteCb m_cb = nullptr; void* m_cbData = nullptr; -#ifdef CC_MQTTSN_CLIENT_HAS_WILL +#if CC_MQTTSN_CLIENT_HAS_WILL WilltopicMsg m_willtopicMsg; WillmsgMsg m_willmsgMsg; -#endif // #ifdef CC_MQTTSN_CLIENT_HAS_WILL +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL static_assert(ExtConfig::ConnectOpTimers == 1U); }; diff --git a/client/lib/src/op/Op.h b/client/lib/src/op/Op.h index 47507037..44c07969 100644 --- a/client/lib/src/op/Op.h +++ b/client/lib/src/op/Op.h @@ -37,6 +37,7 @@ class Op : public ProtMsgHandler Type_Unsubscribe, // Type_Recv, Type_Send, + Type_Will, Type_NumOfValues // Must be last }; diff --git a/client/lib/src/op/WillOp.cpp b/client/lib/src/op/WillOp.cpp new file mode 100644 index 00000000..fdb04d8f --- /dev/null +++ b/client/lib/src/op/WillOp.cpp @@ -0,0 +1,290 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +#include "op/WillOp.h" + +#if CC_MQTTSN_CLIENT_HAS_WILL + +#include "ClientImpl.h" + +#include "comms/util/assign.h" +#include "comms/util/ScopeGuard.h" +#include "comms/units.h" + +#include +#include + +namespace cc_mqttsn_client +{ + +namespace op +{ + +namespace +{ + +inline WillOp* asWillOp(void* data) +{ + return reinterpret_cast(data); +} + +CC_MqttsnWillInfo initWillInfo() +{ + auto info = CC_MqttsnWillInfo(); + info.m_topicUpdReturnCode = CC_MqttsnReturnCode_ValuesLimit; + info.m_msgUpdReturnCode = CC_MqttsnReturnCode_ValuesLimit; + return info; +} + +} // namespace + +WillOp::WillOp(ClientImpl& client) : + Base(client), + m_timer(client.timerMgr().allocTimer()), + m_info(initWillInfo()) +{ +} + +CC_MqttsnErrorCode WillOp::config(const CC_MqttsnWillConfig* config) +{ + if (config == nullptr) { + errorLog("Will configuration is not provided."); + return CC_MqttsnErrorCode_BadParam; + } + + if ((0U < config->m_dataLen) && (config->m_topic == nullptr)) { + errorLog("Will data is provided without will topic."); + return CC_MqttsnErrorCode_BadParam; + } + + if ((0U < config->m_dataLen) && (config->m_data == nullptr)) { + errorLog("Bad will message data."); + return CC_MqttsnErrorCode_BadParam; + } + + if (static_castm_qos)>(Config::MaxQos) < config->m_qos) { + errorLog("Bad will qos value."); + return CC_MqttsnErrorCode_BadParam; + } + + m_willtopicupdMsg.field_willTopic().value().clear(); + m_willtopicupdMsg.field_flags().setMissing(); + m_willmsgupdMsg.field_willMsg().value().clear(); + + if (config->m_topic != nullptr) { + m_willtopicupdMsg.field_willTopic().value() = config->m_topic; + m_willtopicupdMsg.field_flags().setExists(); + m_willtopicupdMsg.field_flags().field().field_qos().setValue(config->m_qos); + m_willtopicupdMsg.field_flags().field().field_mid().setBitValue_Retain(config->m_retain); + } + + if (0U < config->m_dataLen) { + comms::util::assign(m_willmsgupdMsg.field_willMsg().value(), config->m_data, config->m_data + config->m_dataLen); + } + + return CC_MqttsnErrorCode_Success; +} + +CC_MqttsnErrorCode WillOp::send(CC_MqttsnWillCompleteCb cb, void* cbData) +{ + client().allowNextPrepare(); + auto completeOnError = + comms::util::makeScopeGuard( + [this]() + { + opComplete(); + }); + + if (cb == nullptr) { + errorLog("Will completion callback is not provided."); + return CC_MqttsnErrorCode_BadParam; + } + + auto guard = client().apiEnter(); + m_cb = cb; + m_cbData = cbData; + + auto& prevWill = client().reuseState().m_prevWill; + m_stage = Stage_willTopic; + if ((!prevWill.m_topic.empty()) && + (m_willtopicupdMsg.field_willTopic().value() == prevWill.m_topic) && + (prevWill.m_qos == static_cast(m_willtopicupdMsg.field_flags().field().field_qos().value())) && + (prevWill.m_retain == m_willtopicupdMsg.field_flags().field().field_mid().getBitValue_Retain())) { + m_stage = Stage_willMsg; + } + + if ((m_stage == Stage_willMsg) && + (m_willmsgupdMsg.field_willMsg().value() == prevWill.m_msg)) { + completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &m_info); + return CC_MqttsnErrorCode_Success; + } + + if (m_stage == Stage_willTopic) { + prevWill.m_topic.clear(); + } + + if (m_willtopicupdMsg.field_willTopic().value().empty()) { + prevWill = ReuseState::WillInfo(); + } + + m_origRetryCount = getRetryCount(); + auto ec = sendInternal(); + if (ec != CC_MqttsnErrorCode_Success) { + return ec; + } + + completeOnError.release(); + return CC_MqttsnErrorCode_Success; +} + +CC_MqttsnErrorCode WillOp::cancel() +{ + if (m_cb == nullptr) { + // hasn't been sent yet + client().allowNextPrepare(); + } + + opComplete(); + return CC_MqttsnErrorCode_Success; +} + +void WillOp::handle(WilltopicrespMsg& msg) +{ + if (m_stage != Stage_willTopic) { + errorLog("Unexpected WILLTOPICRESP message, ignoring"); + return; + } + + auto rc = static_cast(msg.field_returnCode().value()); + if (rc != CC_MqttsnReturnCode_Accepted) { + m_info.m_topicUpdReturnCode = rc; + completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &m_info); + return; + } + + auto& prevWill = client().reuseState().m_prevWill; + prevWill.m_topic = m_willtopicupdMsg.field_willTopic().value(); + prevWill.m_qos = static_cast(m_willtopicupdMsg.field_flags().field().field_qos().value()); + prevWill.m_retain = m_willtopicupdMsg.field_flags().field().field_mid().getBitValue_Retain(); + + m_stage = Stage_willMsg; + if (m_willmsgupdMsg.field_willMsg().value() == prevWill.m_msg) { + completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &m_info); + return; + } + + prevWill.m_msg.clear(); + setRetryCount(m_origRetryCount); + auto ec = sendInternal(); + if (ec != CC_MqttsnErrorCode_Success) { + completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); + return; + } +} + +void WillOp::handle(WillmsgrespMsg& msg) +{ + if (m_stage != Stage_willMsg) { + errorLog("Unexpected WILLMSGRESP message, ignoring"); + return; + } + + auto rc = static_cast(msg.field_returnCode().value()); + if (rc == CC_MqttsnReturnCode_Accepted) { + client().reuseState().m_prevWill.m_msg = m_willmsgupdMsg.field_willMsg().value(); + } + + m_info.m_msgUpdReturnCode = rc; + completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &m_info); +} + +Op::Type WillOp::typeImpl() const +{ + return Type_Will; +} + +void WillOp::terminateOpImpl(CC_MqttsnAsyncOpStatus status) +{ + completeOpInternal(status); +} + +void WillOp::completeOpInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnWillInfo* info) +{ + auto cb = m_cb; + auto* cbData = m_cbData; + opComplete(); // mustn't access data members after destruction + if (cb != nullptr) { + cb(cbData, status, info); + } +} + +void WillOp::restartTimer() +{ + m_timer.wait(getRetryPeriod(), &WillOp::opTimeoutCb, this); +} + +CC_MqttsnErrorCode WillOp::sendInternal() +{ + using GetMsgFunc = const ProtMessage& (WillOp::*)() const; + static const GetMsgFunc Map[] = { + /* Stage_willTopic */ &WillOp::getWilltopicupdMsg, + /* Stage_willMsg */ &WillOp::getWillmsgupdMsg, + }; + static const std::size_t MapSize = std::extent::value; + static_assert(MapSize == Stage_valuesLimit); + + if (MapSize <= m_stage) { + COMMS_ASSERT(false); + return CC_MqttsnErrorCode_InternalError; + } + + auto func = Map[m_stage]; + auto ec = sendMessage((this->*func)()); + if (ec == CC_MqttsnErrorCode_Success) { + restartTimer(); + } + + return ec; +} + +void WillOp::timeoutInternal() +{ + if (getRetryCount() == 0U) { + errorLog("All retries of the will operation have been exhausted."); + completeOpInternal(CC_MqttsnAsyncOpStatus_Timeout); + return; + } + + decRetryCount(); + auto ec = sendInternal(); + if (ec != CC_MqttsnErrorCode_Success) { + completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); + return; + } +} + +void WillOp::opTimeoutCb(void* data) +{ + asWillOp(data)->timeoutInternal(); +} + +const ProtMessage& WillOp::getWilltopicupdMsg() const +{ + return m_willtopicupdMsg; +} + +const ProtMessage& WillOp::getWillmsgupdMsg() const +{ + return m_willmsgupdMsg; +} + +} // namespace op + +} // namespace cc_mqttsn_client + +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL \ No newline at end of file diff --git a/client/lib/src/op/WillOp.h b/client/lib/src/op/WillOp.h new file mode 100644 index 00000000..945c3c6a --- /dev/null +++ b/client/lib/src/op/WillOp.h @@ -0,0 +1,75 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "op/Op.h" +#include "ExtConfig.h" +#include "ProtocolDefs.h" + +#include "TimerMgr.h" + +#if CC_MQTTSN_CLIENT_HAS_WILL + +namespace cc_mqttsn_client +{ + +namespace op +{ + +class WillOp final : public Op +{ + using Base = Op; +public: + explicit WillOp(ClientImpl& client); + + CC_MqttsnErrorCode config(const CC_MqttsnWillConfig* config); + CC_MqttsnErrorCode send(CC_MqttsnWillCompleteCb cb, void* cbData); + CC_MqttsnErrorCode cancel(); + + using Base::handle; + void handle(WilltopicrespMsg& msg) override; + void handle(WillmsgrespMsg& msg) override; + +protected: + virtual Type typeImpl() const override; + virtual void terminateOpImpl(CC_MqttsnAsyncOpStatus status) override; + +private: + enum Stage : unsigned + { + Stage_willTopic, + Stage_willMsg, + Stage_valuesLimit + }; + + void completeOpInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnWillInfo* info = nullptr); + void restartTimer(); + CC_MqttsnErrorCode sendInternal(); + void timeoutInternal(); + const ProtMessage& getWilltopicupdMsg() const; + const ProtMessage& getWillmsgupdMsg() const; + + static void opTimeoutCb(void* data); + + WilltopicupdMsg m_willtopicupdMsg; + WillmsgupdMsg m_willmsgupdMsg; + TimerMgr::Timer m_timer; + unsigned m_stage = Stage_willTopic; + unsigned m_origRetryCount = 0U; + CC_MqttsnWillCompleteCb m_cb = nullptr; + void* m_cbData = nullptr; + CC_MqttsnWillInfo m_info; + + static_assert(ExtConfig::WillOpTimers == 1U); +}; + +} // namespace op + +} // namespace cc_mqttsn_client + +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL diff --git a/client/lib/test/UnitTestConnect.th b/client/lib/test/UnitTestConnect.th index ba7ae660..83e10cfc 100644 --- a/client/lib/test/UnitTestConnect.th +++ b/client/lib/test/UnitTestConnect.th @@ -506,4 +506,6 @@ void UnitTestConnect::test6() TS_ASSERT(!unitTestHasTickReq()); TS_ASSERT(!apiIsConnected(client)); -} \ No newline at end of file +} + +// TODO: test reconnect without disconnecting \ No newline at end of file From e43f391a3e4f709ec72076aef9ae8a7f0ed5b625 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 30 Jul 2024 09:22:59 +1000 Subject: [PATCH 078/106] Added will update unit-testing. --- client/lib/src/op/WillOp.cpp | 15 +- client/lib/templ/client.cpp.templ | 162 ++++++- client/lib/templ/client.h.templ | 86 ++++ client/lib/test/CMakeLists.txt | 1 + client/lib/test/UnitTestCommonBase.cpp | 105 +++++ client/lib/test/UnitTestCommonBase.h | 54 ++- client/lib/test/UnitTestDefaultBase.cpp | 10 + client/lib/test/UnitTestPublish.th | 2 - client/lib/test/UnitTestWill.th | 558 ++++++++++++++++++++++++ 9 files changed, 970 insertions(+), 23 deletions(-) create mode 100644 client/lib/test/UnitTestWill.th diff --git a/client/lib/src/op/WillOp.cpp b/client/lib/src/op/WillOp.cpp index fdb04d8f..98224ca6 100644 --- a/client/lib/src/op/WillOp.cpp +++ b/client/lib/src/op/WillOp.cpp @@ -120,7 +120,9 @@ CC_MqttsnErrorCode WillOp::send(CC_MqttsnWillCompleteCb cb, void* cbData) if ((m_stage == Stage_willMsg) && (m_willmsgupdMsg.field_willMsg().value() == prevWill.m_msg)) { - completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &m_info); + completeOnError.release(); + auto info = m_info; // copy + completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &info); return CC_MqttsnErrorCode_Success; } @@ -161,9 +163,10 @@ void WillOp::handle(WilltopicrespMsg& msg) } auto rc = static_cast(msg.field_returnCode().value()); + m_info.m_topicUpdReturnCode = rc; if (rc != CC_MqttsnReturnCode_Accepted) { - m_info.m_topicUpdReturnCode = rc; - completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &m_info); + auto info = m_info; // copy + completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &info); return; } @@ -174,7 +177,8 @@ void WillOp::handle(WilltopicrespMsg& msg) m_stage = Stage_willMsg; if (m_willmsgupdMsg.field_willMsg().value() == prevWill.m_msg) { - completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &m_info); + auto info = m_info; // copy + completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &info); return; } @@ -200,7 +204,8 @@ void WillOp::handle(WillmsgrespMsg& msg) } m_info.m_msgUpdReturnCode = rc; - completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &m_info); + auto info = m_info; // copy + completeOpInternal(CC_MqttsnAsyncOpStatus_Complete, &info); } Op::Type WillOp::typeImpl() const diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index 17e74e78..cbf4f69d 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -21,6 +21,7 @@ struct CC_MqttsnDisconnect {}; struct CC_MqttsnSubscribe {}; struct CC_MqttsnUnsubscribe {}; struct CC_MqttsnPublish {}; +struct CC_MqttsnWill {}; namespace { @@ -103,6 +104,16 @@ inline CC_MqttsnPublishHandle handleFromSendOp(cc_mqttsn_client::op::SendOp* op) return reinterpret_cast(op); } +inline cc_mqttsn_client::op::WillOp* willOpFromHandle(CC_MqttsnWillHandle handle) +{ + return reinterpret_cast(handle); +} + +inline CC_MqttsnWillHandle handleFromWillOp(cc_mqttsn_client::op::WillOp* op) +{ + return reinterpret_cast(op); +} + } // namespace CC_MqttsnClientHandle cc_mqttsn_##NAME##client_alloc() @@ -408,7 +419,7 @@ CC_MqttsnSearchHandle cc_mqttsn_##NAME##client_search_prepare( COMMS_ASSERT(client != nullptr); return handleFromSearchOp(clientFromHandle(client)->searchPrepare(ec)); #else // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY - clientFromHandle(client)->errorLog("Gateway discovery support for excluded from compilation"); + clientFromHandle(client)->errorLog("Gateway discovery support was excluded from compilation"); if (ec != nullptr) { *ec = CC_MqttsnErrorCode_NotSupported; } @@ -421,7 +432,6 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_set_retry_period( [[maybe_unused]] unsigned ms) { #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY - COMMS_ASSERT(handle != nullptr); if (ms == 0U) { searchOpFromHandle(handle)->client().errorLog("The retry period must be greater than 0"); @@ -431,17 +441,13 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_set_retry_period( COMMS_ASSERT(handle != nullptr); searchOpFromHandle(handle)->setRetryPeriod(ms); return CC_MqttsnErrorCode_Success; - #else // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY - return CC_MqttsnErrorCode_NotSupported; - #endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY } unsigned cc_mqttsn_##NAME##client_search_get_retry_period([[maybe_unused]] CC_MqttsnSearchHandle handle) { - #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY COMMS_ASSERT(handle != nullptr); return searchOpFromHandle(handle)->getRetryPeriod(); @@ -459,11 +465,8 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_set_retry_count( COMMS_ASSERT(handle != nullptr); searchOpFromHandle(handle)->setRetryCount(count); return CC_MqttsnErrorCode_Success; - #else // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY - return CC_MqttsnErrorCode_NotSupported; - #endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY } @@ -483,7 +486,6 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_set_broadcast_radius( [[maybe_unused]] unsigned broadcastRadius) { #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY - COMMS_ASSERT(handle != nullptr); if (broadcastRadius == 0U) { searchOpFromHandle(handle)->client().errorLog("The broadcast radius must be greater than 0"); @@ -492,11 +494,8 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_search_set_broadcast_radius( searchOpFromHandle(handle)->setBroadcastRadius(broadcastRadius); return CC_MqttsnErrorCode_Success; - #else // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY - return CC_MqttsnErrorCode_NotSupported; - #endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY } @@ -1008,6 +1007,143 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_publish( return cc_mqttsn_##NAME##client_publish_send(publish, cb, cbData); } +CC_MqttsnWillHandle cc_mqttsn_##NAME##client_will_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec) +{ +#if CC_MQTTSN_CLIENT_HAS_WILL + COMMS_ASSERT(client != nullptr); + return handleFromWillOp(clientFromHandle(client)->willPrepare(ec)); +#else // #if CC_MQTTSN_CLIENT_HAS_WILL + clientFromHandle(client)->errorLog("Will support was excluded from compilation"); + if (ec != nullptr) { + *ec = CC_MqttsnErrorCode_NotSupported; + } + return nullptr; +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_will_set_retry_period( + [[maybe_unused]] CC_MqttsnWillHandle handle, + [[maybe_unused]] unsigned ms) +{ +#if CC_MQTTSN_CLIENT_HAS_WILL + COMMS_ASSERT(handle != nullptr); + if (ms == 0U) { + willOpFromHandle(handle)->client().errorLog("The retry period must be greater than 0"); + return CC_MqttsnErrorCode_BadParam; + } + + COMMS_ASSERT(handle != nullptr); + willOpFromHandle(handle)->setRetryPeriod(ms); + return CC_MqttsnErrorCode_Success; +#else // #if CC_MQTTSN_CLIENT_HAS_WILL + return CC_MqttsnErrorCode_NotSupported; +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL +} + +unsigned cc_mqttsn_##NAME##client_will_get_retry_period([[maybe_unused]] CC_MqttsnWillHandle handle) +{ +#if CC_MQTTSN_CLIENT_HAS_WILL + COMMS_ASSERT(handle != nullptr); + return willOpFromHandle(handle)->getRetryPeriod(); +#else // #if CC_MQTTSN_CLIENT_HAS_WILL + return 0U; +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_will_set_retry_count( + [[maybe_unused]] CC_MqttsnWillHandle handle, + [[maybe_unused]] unsigned count) +{ +#if CC_MQTTSN_CLIENT_HAS_WILL + COMMS_ASSERT(handle != nullptr); + willOpFromHandle(handle)->setRetryCount(count); + return CC_MqttsnErrorCode_Success; +#else // #if CC_MQTTSN_CLIENT_HAS_WILL + return CC_MqttsnErrorCode_NotSupported; +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL +} + +unsigned cc_mqttsn_##NAME##client_will_get_retry_count([[maybe_unused]] CC_MqttsnWillHandle handle) +{ +#if CC_MQTTSN_CLIENT_HAS_WILL + COMMS_ASSERT(handle != nullptr); + return willOpFromHandle(handle)->getRetryCount(); +#else // #if CC_MQTTSN_CLIENT_HAS_WILL + return 0U; +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL +} + +void cc_mqttsn_##NAME##client_will_init_config(CC_MqttsnWillConfig* config) +{ + COMMS_ASSERT(config != nullptr); + *config = CC_MqttsnWillConfig(); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_will_config( + [[maybe_unused]] CC_MqttsnWillHandle handle, + [[maybe_unused]] const CC_MqttsnWillConfig* config) +{ +#if CC_MQTTSN_CLIENT_HAS_WILL + COMMS_ASSERT(handle != nullptr); + return willOpFromHandle(handle)->config(config); +#else // #if CC_MQTTSN_CLIENT_HAS_WILL + return CC_MqttsnErrorCode_NotSupported; +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_will_send( + [[maybe_unused]] CC_MqttsnWillHandle handle, + [[maybe_unused]] CC_MqttsnWillCompleteCb cb, + [[maybe_unused]] void* cbData) +{ +#if CC_MQTTSN_CLIENT_HAS_WILL + COMMS_ASSERT(handle != nullptr); + return willOpFromHandle(handle)->send(cb, cbData); +#else // #if CC_MQTTSN_CLIENT_HAS_WILL + return CC_MqttsnErrorCode_NotSupported; +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_will_cancel([[maybe_unused]] CC_MqttsnWillHandle handle) +{ +#if CC_MQTTSN_CLIENT_HAS_WILL + COMMS_ASSERT(handle != nullptr); + return willOpFromHandle(handle)->cancel(); +#else // #if CC_MQTTSN_CLIENT_HAS_WILL + return CC_MqttsnErrorCode_NotSupported; +#endif // #if CC_MQTTSN_CLIENT_HAS_WILL +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_will( + CC_MqttsnClientHandle client, + const CC_MqttsnWillConfig* config, + CC_MqttsnWillCompleteCb cb, + void* cbData) +{ + auto ec = CC_MqttsnErrorCode_Success; + auto will = cc_mqttsn_##NAME##client_will_prepare(client, &ec); + if (will == nullptr) { + return ec; + } + + auto cancelOnExitGuard = + comms::util::makeScopeGuard( + [will]() + { + [[maybe_unused]] auto ecTmp = cc_mqttsn_##NAME##client_will_cancel(will); + }); + + if (config != nullptr) { + ec = cc_mqttsn_##NAME##client_will_config(will, config); + if (ec != CC_MqttsnErrorCode_Success) { + return ec; + } + } + + cancelOnExitGuard.release(); + return cc_mqttsn_##NAME##client_will_send(will, cb, cbData); +} + // --------------------- Callbacks --------------------- void cc_mqttsn_##NAME##client_set_next_tick_program_callback( diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index 62b7ca1f..406ac291 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -767,6 +767,92 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_publish( CC_MqttsnPublishCompleteCb cb, void* cbData); +/// @brief Prepare "will" operation. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[out] ec Error code reporting result of the operation. Can be NULL. +/// @return Handle of the "will" operation, will be NULL in case of failure. To analyze the reason failure use "ec" output parameter. +/// @post The "will" operation is allocated, use either @ref cc_mqttsn_##NAME##client_will_send() +/// or @ref cc_mqttsn_##NAME##client_will_cancel() to prevent memory leaks. +/// @ingroup will +CC_MqttsnWillHandle cc_mqttsn_##NAME##client_will_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec); + +/// @brief Configure the retry period for the "will" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_will_prepare() function. +/// @param[in] ms Retry period in @b milliseconds. +/// @return Result code of the call. +/// @ingroup will +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_will_set_retry_period(CC_MqttsnWillHandle handle, unsigned ms); + +/// @brief Retrieve the configured retry period for the "will" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_will_prepare() function. +/// @return Retry period duration in @b milliseconds. +/// @ingroup will +unsigned cc_mqttsn_##NAME##client_will_get_retry_period(CC_MqttsnWillHandle handle); + +/// @brief Configure the retry count for the "will" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_will_prepare() function. +/// @param[in] count Number of retries. +/// @return Result code of the call. +/// @ingroup will +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_will_set_retry_count(CC_MqttsnWillHandle handle, unsigned count); + +/// @brief Retrieve the configured retry count for the "will" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_will_prepare() function. +/// @return Number of retries. +/// @ingroup will +unsigned cc_mqttsn_##NAME##client_will_get_retry_count(CC_MqttsnWillHandle handle); + +/// @brief Intialize the @ref CC_MqttsnWillConfig configuration structure. +/// @param[out] config Configuration structure. Must not be NULL. +/// @ingroup will +void cc_mqttsn_##NAME##client_will_init_config(CC_MqttsnWillConfig* config); + +/// @brief Perform configuration of the "will" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_will_prepare() function. +/// @param[in] config Configuration structure. Must NOT be NULL. Does not need to be preserved after invocation. +/// @return Result code of the call. +/// @ingroup will +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_will_config(CC_MqttsnWillHandle handle, const CC_MqttsnWillConfig* config); + +/// @brief Send the "will" operation +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_will_prepare() function. +/// @param[in] cb Callback to be invoked when "will" operation is complete. +/// @param[in] cbData Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +/// @return Result code of the call. +/// @post The handle of the "will" operation can be discarded. +/// @post The provided callback will be invoked when the "will" operation is complete if and only if +/// the function returns @ref CC_MqttsnErrorCode_Success. +/// @ingroup will +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_will_send(CC_MqttsnWillHandle handle, CC_MqttsnWillCompleteCb cb, void* cbData); + +/// @brief Cancel the allocated "will" operation +/// @details In case the @ref cc_mqttsn_##NAME##client_will_send() function was successfully called before, +/// the operation is cancelled @b without callback invocation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_will_prepare() function. +/// @return Result code of the call. +/// @post The handle of the "will" operation is no longer valid and must be discarded. +/// @ingroup will +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_will_cancel(CC_MqttsnWillHandle handle); + +/// @brief Prepare and send "will" request in one go +/// @details Abstracts away sequence of the following functions invocation: +/// @li @ref cc_mqttsn_##NAME##client_will_prepare() +/// @li @ref cc_mqttsn_##NAME##client_will_config() +/// @li @ref cc_mqttsn_##NAME##client_will_send() +/// +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[in] config Subscription configuration. +/// @param[in] cb Callback to be invoked when "will" operation is complete. +/// @param[in] cbData Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +/// @return Result code of the call. +/// @ingroup will +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_will( + CC_MqttsnClientHandle client, + const CC_MqttsnWillConfig* config, + CC_MqttsnWillCompleteCb cb, + void* cbData); // --------------------- Callbacks --------------------- diff --git a/client/lib/test/CMakeLists.txt b/client/lib/test/CMakeLists.txt index c1f2fc93..a097301b 100644 --- a/client/lib/test/CMakeLists.txt +++ b/client/lib/test/CMakeLists.txt @@ -46,6 +46,7 @@ if (TARGET cc::cc_mqttsn_client) # cc_mqttsn_client_add_unit_test(UnitTestReceive ${DEFAULT_BASE_LIB_NAME}) cc_mqttsn_client_add_unit_test(UnitTestSubscribe ${DEFAULT_BASE_LIB_NAME}) cc_mqttsn_client_add_unit_test(UnitTestUnsubscribe ${DEFAULT_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(UnitTestWill ${DEFAULT_BASE_LIB_NAME}) endif () # TODO diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index cda12574..e96b72e8 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -111,6 +111,16 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_publish_send != nullptr); test_assert(m_funcs.m_publish_cancel != nullptr); test_assert(m_funcs.m_publish != nullptr); + test_assert(m_funcs.m_will_prepare != nullptr); + test_assert(m_funcs.m_will_set_retry_period != nullptr); + test_assert(m_funcs.m_will_get_retry_period != nullptr); + test_assert(m_funcs.m_will_set_retry_count != nullptr); + test_assert(m_funcs.m_will_get_retry_count != nullptr); + test_assert(m_funcs.m_will_init_config != nullptr); + test_assert(m_funcs.m_will_config != nullptr); + test_assert(m_funcs.m_will_send != nullptr); + test_assert(m_funcs.m_will_cancel != nullptr); + test_assert(m_funcs.m_will != nullptr); test_assert(m_funcs.m_set_next_tick_program_callback != nullptr); test_assert(m_funcs.m_set_cancel_next_tick_wait_callback != nullptr); @@ -213,6 +223,21 @@ UnitTestCommonBase::UnitTestPublishCompleteReport::UnitTestPublishCompleteReport } } +UnitTestCommonBase::UnitTestWillInfo& UnitTestCommonBase::UnitTestWillInfo::operator=(const CC_MqttsnWillInfo& info) +{ + m_topicUpdReturnCode = info.m_topicUpdReturnCode; + m_msgUpdReturnCode = info.m_msgUpdReturnCode; + return *this; +} + +UnitTestCommonBase::UnitTestWillCompleteReport::UnitTestWillCompleteReport(CC_MqttsnAsyncOpStatus status, const CC_MqttsnWillInfo* info) : + m_status(status) +{ + if (info != nullptr) { + m_info = *info; + } +} + void UnitTestCommonBase::unitTestSetUp() { } @@ -579,6 +604,33 @@ UnitTestCommonBase::UnitTestDisconnectCompleteReportPtr UnitTestCommonBase::unit } +void UnitTestCommonBase::unitTestDoDisconnect(CC_MqttsnClient* client) +{ + auto ec = m_funcs.m_disconnect(client, &UnitTestCommonBase::unitTestDisconnectCompleteCb, this); + test_assert(ec == CC_MqttsnErrorCode_Success); + + { + test_assert(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* disconnectMsg = dynamic_cast(sentMsg.get()); + test_assert(disconnectMsg != nullptr); + test_assert(disconnectMsg->field_duration().isMissing()); + test_assert(!unitTestHasOutputData()); + } + + test_assert(unitTestHasTickReq()); + unitTestTick(client, 100); // timeout + + { + UnitTestDisconnectMsg disconnectMsg; + unitTestClientInputMessage(client, disconnectMsg); + } + + test_assert(unitTestHasDisconnectCompleteReport()); + auto disconnectReport = unitTestDisconnectCompleteReport(); + test_assert(disconnectReport->m_status == CC_MqttsnAsyncOpStatus_Complete) +} + CC_MqttsnErrorCode UnitTestCommonBase::unitTestDisconnectSend(CC_MqttsnDisconnectHandle disconnect) { return m_funcs.m_disconnect_send(disconnect, &UnitTestCommonBase::unitTestDisconnectCompleteCb, this); @@ -699,6 +751,28 @@ CC_MqttsnErrorCode UnitTestCommonBase::unitTestPublishSend(CC_MqttsnPublishHandl return m_funcs.m_publish_send(publish, &UnitTestCommonBase::unitTestPublishCompleteCb, this); } +bool UnitTestCommonBase::unitTestHasWillCompleteReport() const +{ + return !m_data.m_willCompleteReports.empty(); +} + +UnitTestCommonBase::UnitTestWillCompleteReportPtr UnitTestCommonBase::unitTestWillCompleteReport(bool mustExist) +{ + if (!unitTestHasWillCompleteReport()) { + test_assert(!mustExist); + return UnitTestWillCompleteReportPtr(); + } + + auto ptr = std::move(m_data.m_willCompleteReports.front()); + m_data.m_willCompleteReports.pop_front(); + return ptr; +} + +CC_MqttsnErrorCode UnitTestCommonBase::unitTestWillSend(CC_MqttsnWillHandle will) +{ + return m_funcs.m_will_send(will, &UnitTestCommonBase::unitTestWillCompleteCb, this); +} + void UnitTestCommonBase::apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen) { m_funcs.m_process_data(client, buf, bufLen); @@ -907,6 +981,31 @@ CC_MqttsnErrorCode UnitTestCommonBase::apiPublishCancel(CC_MqttsnPublishHandle p return m_funcs.m_publish_cancel(publish); } +CC_MqttsnWillHandle UnitTestCommonBase::apiWillPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec) +{ + return m_funcs.m_will_prepare(client, ec); +} + +CC_MqttsnErrorCode UnitTestCommonBase::apiWillSetRetryCount(CC_MqttsnWillHandle will, unsigned count) +{ + return m_funcs.m_will_set_retry_count(will, count); +} + +void UnitTestCommonBase::apiWillInitConfig(CC_MqttsnWillConfig* config) +{ + m_funcs.m_will_init_config(config); +} + +CC_MqttsnErrorCode UnitTestCommonBase::apiWillConfig(CC_MqttsnWillHandle will, const CC_MqttsnWillConfig* config) +{ + return m_funcs.m_will_config(will, config); +} + +CC_MqttsnErrorCode UnitTestCommonBase::apiWillCancel(CC_MqttsnWillHandle will) +{ + return m_funcs.m_will_cancel(will); +} + void UnitTestCommonBase::unitTestMessageReportCb(void* data, const CC_MqttsnMessageInfo* msgInfo) { // TODO: @@ -985,4 +1084,10 @@ void UnitTestCommonBase::unitTestPublishCompleteCb(void* data, CC_MqttsnPublishH { auto* thisPtr = asThis(data); thisPtr->m_data.m_publishCompleteReports.push_back(std::make_unique(handle, status, info)); +} + +void UnitTestCommonBase::unitTestWillCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnWillInfo* info) +{ + auto* thisPtr = asThis(data); + thisPtr->m_data.m_willCompleteReports.push_back(std::make_unique(status, info)); } \ No newline at end of file diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index f5598112..d35a6565 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -101,6 +101,16 @@ class UnitTestCommonBase CC_MqttsnErrorCode (*m_publish_send)(CC_MqttsnPublishHandle, CC_MqttsnPublishCompleteCb, void*) = nullptr; CC_MqttsnErrorCode (*m_publish_cancel)(CC_MqttsnPublishHandle) = nullptr; CC_MqttsnErrorCode (*m_publish)(CC_MqttsnClientHandle, const CC_MqttsnPublishConfig*, CC_MqttsnPublishCompleteCb, void* cbData) = nullptr; + CC_MqttsnWillHandle (*m_will_prepare)(CC_MqttsnClientHandle, CC_MqttsnErrorCode*) = nullptr; + CC_MqttsnErrorCode (*m_will_set_retry_period)(CC_MqttsnWillHandle, unsigned) = nullptr; + unsigned (*m_will_get_retry_period)(CC_MqttsnWillHandle) = nullptr; + CC_MqttsnErrorCode (*m_will_set_retry_count)(CC_MqttsnWillHandle, unsigned) = nullptr; + unsigned (*m_will_get_retry_count)(CC_MqttsnWillHandle) = nullptr; + void (*m_will_init_config)(CC_MqttsnWillConfig*) = nullptr; + CC_MqttsnErrorCode (*m_will_config)(CC_MqttsnWillHandle, const CC_MqttsnWillConfig*) = nullptr; + CC_MqttsnErrorCode (*m_will_send)(CC_MqttsnWillHandle, CC_MqttsnWillCompleteCb, void*) = nullptr; + CC_MqttsnErrorCode (*m_will_cancel)(CC_MqttsnWillHandle) = nullptr; + CC_MqttsnErrorCode (*m_will)(CC_MqttsnClientHandle, const CC_MqttsnWillConfig*, CC_MqttsnWillCompleteCb, void* cbData) = nullptr; void (*m_set_next_tick_program_callback)(CC_MqttsnClientHandle, CC_MqttsnNextTickProgramCb, void*) = nullptr; void (*m_set_cancel_next_tick_wait_callback)(CC_MqttsnClientHandle, CC_MqttsnCancelNextTickWaitCb, void*) = nullptr; @@ -110,7 +120,6 @@ class UnitTestCommonBase void (*m_set_message_report_callback)(CC_MqttsnClientHandle, CC_MqttsnMessageReportCb, void*) = nullptr; void (*m_set_error_log_callback)(CC_MqttsnClientHandle, CC_MqttsnErrorLogCb, void*) = nullptr; void (*m_set_gwinfo_delay_request_callback)(CC_MqttsnClientHandle, CC_MqttsnGwinfoDelayRequestCb, void*) = nullptr; - }; struct UnitTestDeleter @@ -290,7 +299,32 @@ class UnitTestCommonBase }; using UnitTestPublishCompleteReportPtr = std::unique_ptr; - using UnitTestPublishCompleteReportList = std::list; + using UnitTestPublishCompleteReportList = std::list; + + struct UnitTestWillInfo + { + CC_MqttsnReturnCode m_topicUpdReturnCode = CC_MqttsnReturnCode_ValuesLimit; + CC_MqttsnReturnCode m_msgUpdReturnCode = CC_MqttsnReturnCode_ValuesLimit; + + UnitTestWillInfo() = default; + UnitTestWillInfo(const UnitTestWillInfo&) = default; + UnitTestWillInfo& operator=(const UnitTestWillInfo&) = default; + UnitTestWillInfo& operator=(const CC_MqttsnWillInfo& info); + }; + + + struct UnitTestWillCompleteReport + { + CC_MqttsnAsyncOpStatus m_status = CC_MqttsnAsyncOpStatus_ValuesLimit; + UnitTestWillInfo m_info; + + UnitTestWillCompleteReport(CC_MqttsnAsyncOpStatus status, const CC_MqttsnWillInfo* info); + UnitTestWillCompleteReport(UnitTestWillCompleteReport&&) = default; + UnitTestWillCompleteReport& operator=(const UnitTestWillCompleteReport&) = default; + }; + + using UnitTestWillCompleteReportPtr = std::unique_ptr; + using UnitTestWillCompleteReportList = std::list; using UnitTestClientPtr = std::unique_ptr; @@ -337,6 +371,7 @@ class UnitTestCommonBase bool unitTestHasDisconnectCompleteReport() const; UnitTestDisconnectCompleteReportPtr unitTestDisconnectCompleteReport(bool mustExist = true); + void unitTestDoDisconnect(CC_MqttsnClient* client); CC_MqttsnErrorCode unitTestDisconnectSend(CC_MqttsnDisconnectHandle disconnect); bool unitTestHasSubscribeCompleteReport() const; @@ -357,6 +392,11 @@ class UnitTestCommonBase CC_MqttsnErrorCode unitTestPublishSend(CC_MqttsnPublishHandle publish); + bool unitTestHasWillCompleteReport() const; + UnitTestWillCompleteReportPtr unitTestWillCompleteReport(bool mustExist = true); + + CC_MqttsnErrorCode unitTestWillSend(CC_MqttsnWillHandle will); + void apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen); CC_MqttsnErrorCode apiSetDefaultRetryPeriod(CC_MqttsnClient* client, unsigned value); CC_MqttsnErrorCode apiSetDefaultRetryCount(CC_MqttsnClient* client, unsigned value); @@ -396,7 +436,13 @@ class UnitTestCommonBase CC_MqttsnErrorCode apiPublishSetRetryCount(CC_MqttsnPublishHandle publish, unsigned count); void apiPublishInitConfig(CC_MqttsnPublishConfig* config); CC_MqttsnErrorCode apiPublishConfig(CC_MqttsnPublishHandle publish, const CC_MqttsnPublishConfig* config); - CC_MqttsnErrorCode apiPublishCancel(CC_MqttsnPublishHandle publish); + CC_MqttsnErrorCode apiPublishCancel(CC_MqttsnPublishHandle publish); + + CC_MqttsnWillHandle apiWillPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec = nullptr); + CC_MqttsnErrorCode apiWillSetRetryCount(CC_MqttsnWillHandle will, unsigned count); + void apiWillInitConfig(CC_MqttsnWillConfig* config); + CC_MqttsnErrorCode apiWillConfig(CC_MqttsnWillHandle will, const CC_MqttsnWillConfig* config); + CC_MqttsnErrorCode apiWillCancel(CC_MqttsnWillHandle will); protected: @@ -417,6 +463,7 @@ class UnitTestCommonBase UnitTestSubscribeCompleteReportList m_subscribeCompleteReports; UnitTestUnsubscribeCompleteReportList m_unsubscribeCompleteReports; UnitTestPublishCompleteReportList m_publishCompleteReports; + UnitTestWillCompleteReportList m_willCompleteReports; }; static void unitTestTickProgramCb(void* data, unsigned duration); @@ -433,6 +480,7 @@ class UnitTestCommonBase static void unitTestSubscribeCompleteCb(void* data, CC_MqttsnSubscribeHandle handle, CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info); static void unitTestUnsubscribeCompleteCb(void* data, CC_MqttsnUnsubscribeHandle handle, CC_MqttsnAsyncOpStatus status); static void unitTestPublishCompleteCb(void* data, CC_MqttsnPublishHandle handle, CC_MqttsnAsyncOpStatus status, const CC_MqttsnPublishInfo* info); + static void unitTestWillCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnWillInfo* info); LibFuncs m_funcs; ClientData m_data; diff --git a/client/lib/test/UnitTestDefaultBase.cpp b/client/lib/test/UnitTestDefaultBase.cpp index eeb10718..f7443a68 100644 --- a/client/lib/test/UnitTestDefaultBase.cpp +++ b/client/lib/test/UnitTestDefaultBase.cpp @@ -92,6 +92,16 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_publish_send = &cc_mqttsn_client_publish_send; funcs.m_publish_cancel = &cc_mqttsn_client_publish_cancel; funcs.m_publish = &cc_mqttsn_client_publish; + funcs.m_will_prepare = &cc_mqttsn_client_will_prepare; + funcs.m_will_set_retry_period = &cc_mqttsn_client_will_set_retry_period; + funcs.m_will_get_retry_period = &cc_mqttsn_client_will_get_retry_period; + funcs.m_will_set_retry_count = &cc_mqttsn_client_will_set_retry_count; + funcs.m_will_get_retry_count = &cc_mqttsn_client_will_get_retry_count; + funcs.m_will_init_config = &cc_mqttsn_client_will_init_config; + funcs.m_will_config = &cc_mqttsn_client_will_config; + funcs.m_will_send = &cc_mqttsn_client_will_send; + funcs.m_will_cancel = &cc_mqttsn_client_will_cancel; + funcs.m_will = &cc_mqttsn_client_will; funcs.m_set_next_tick_program_callback = &cc_mqttsn_client_set_next_tick_program_callback; funcs.m_set_cancel_next_tick_wait_callback = &cc_mqttsn_client_set_cancel_next_tick_wait_callback; diff --git a/client/lib/test/UnitTestPublish.th b/client/lib/test/UnitTestPublish.th index 34112b7f..39d59bf2 100644 --- a/client/lib/test/UnitTestPublish.th +++ b/client/lib/test/UnitTestPublish.th @@ -2155,5 +2155,3 @@ void UnitTestPublish::test20() TS_ASSERT(unitTestHasTickReq()); } - -// TODO: loop of accepting registration, but rejecting topic id \ No newline at end of file diff --git a/client/lib/test/UnitTestWill.th b/client/lib/test/UnitTestWill.th new file mode 100644 index 00000000..12d26df2 --- /dev/null +++ b/client/lib/test/UnitTestWill.th @@ -0,0 +1,558 @@ +#include "UnitTestDefaultBase.h" +#include "UnitTestProtocolDefs.h" + +#include "comms/units.h" + +#include + +class UnitTestWill : public CxxTest::TestSuite, public UnitTestDefaultBase +{ +public: + void test1(); + void test2(); + void test3(); + void test4(); + void test5(); + void test6(); + void test7(); + +private: + virtual void setUp() override + { + unitTestSetUp(); + } + + virtual void tearDown() override + { + unitTestTearDown(); + } +}; + +void UnitTestWill::test1() +{ + // Testing basic will update after connection + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic("will/topic"); + const UnitTestData Data{1, 2, 3, 4}; + const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; + const bool Retain = true; + + CC_MqttsnWillConfig config; + apiWillInitConfig(&config); + config.m_topic = Topic.c_str(); + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + config.m_qos = Qos; + config.m_retain = Retain; + + auto will = apiWillPrepare(client); + TS_ASSERT_DIFFERS(will, nullptr); + + auto ec = apiWillConfig(will, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestWillSend(will); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willtopicupdMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willtopicupdMsg, nullptr); + TS_ASSERT(willtopicupdMsg->field_flags().doesExist()); + TS_ASSERT_EQUALS(static_cast(willtopicupdMsg->field_flags().field().field_qos().value()), Qos); + TS_ASSERT_EQUALS(willtopicupdMsg->field_flags().field().field_mid().getBitValue_Retain(), Retain); + TS_ASSERT_EQUALS(willtopicupdMsg->field_willTopic().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestWilltopicrespMsg respMsg; + respMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, respMsg); + } + + TS_ASSERT(!unitTestHasWillCompleteReport()); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willmsgupdMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willmsgupdMsg, nullptr); + TS_ASSERT_EQUALS(willmsgupdMsg->field_willMsg().value(), Data); + TS_ASSERT(!unitTestHasOutputData()); + } + + { + UnitTestWillmsgrespMsg respMsg; + respMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, respMsg); + } + + TS_ASSERT(!unitTestHasOutputData()); + + { + TS_ASSERT(unitTestHasWillCompleteReport()); + auto willReport = unitTestWillCompleteReport(); + TS_ASSERT_EQUALS(willReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(willReport->m_info.m_topicUpdReturnCode, CC_MqttsnReturnCode_Accepted); + TS_ASSERT_EQUALS(willReport->m_info.m_msgUpdReturnCode, CC_MqttsnReturnCode_Accepted); + TS_ASSERT(!unitTestHasWillCompleteReport()); + } +} + +void UnitTestWill::test2() +{ + // Testing full will update after connection with will + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + const std::string ConnectWillTopic("connect/will/topic"); + const UnitTestData ConnectWillData{1, 2, 3, 4, 5, 6}; + const CC_MqttsnQoS ConnectWillQos = CC_MqttsnQoS_AtLeastOnceDelivery; + const bool ConnectWillRetain = false; + + + CC_MqttsnConnectConfig connectConfig; + apiConnectInitConfig(&connectConfig); + connectConfig.m_clientId = ClientId.c_str(); + connectConfig.m_cleanSession = true; + + CC_MqttsnWillConfig connectWillConfig; + apiConnectInitConfigWill(&connectWillConfig); + connectWillConfig.m_topic = ConnectWillTopic.c_str(); + connectWillConfig.m_data = ConnectWillData.data(); + connectWillConfig.m_dataLen = static_cast(ConnectWillData.size()); + connectWillConfig.m_qos = ConnectWillQos; + connectWillConfig.m_retain = ConnectWillRetain; + + unitTestDoConnect(client, &connectConfig, &connectWillConfig); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic("will/topic"); + const UnitTestData Data{1, 2, 3, 4}; + const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; + const bool Retain = true; + + CC_MqttsnWillConfig config; + apiWillInitConfig(&config); + config.m_topic = Topic.c_str(); + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + config.m_qos = Qos; + config.m_retain = Retain; + + auto will = apiWillPrepare(client); + TS_ASSERT_DIFFERS(will, nullptr); + + auto ec = apiWillConfig(will, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestWillSend(will); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willtopicupdMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willtopicupdMsg, nullptr); + TS_ASSERT(willtopicupdMsg->field_flags().doesExist()); + TS_ASSERT_EQUALS(static_cast(willtopicupdMsg->field_flags().field().field_qos().value()), Qos); + TS_ASSERT_EQUALS(willtopicupdMsg->field_flags().field().field_mid().getBitValue_Retain(), Retain); + TS_ASSERT_EQUALS(willtopicupdMsg->field_willTopic().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestWilltopicrespMsg respMsg; + respMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, respMsg); + } + + TS_ASSERT(!unitTestHasWillCompleteReport()); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willmsgupdMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willmsgupdMsg, nullptr); + TS_ASSERT_EQUALS(willmsgupdMsg->field_willMsg().value(), Data); + TS_ASSERT(!unitTestHasOutputData()); + } + + { + UnitTestWillmsgrespMsg respMsg; + respMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, respMsg); + } + + TS_ASSERT(!unitTestHasOutputData()); + + { + TS_ASSERT(unitTestHasWillCompleteReport()); + auto willReport = unitTestWillCompleteReport(); + TS_ASSERT_EQUALS(willReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(willReport->m_info.m_topicUpdReturnCode, CC_MqttsnReturnCode_Accepted); + TS_ASSERT_EQUALS(willReport->m_info.m_msgUpdReturnCode, CC_MqttsnReturnCode_Accepted); + TS_ASSERT(!unitTestHasWillCompleteReport()); + } +} + +void UnitTestWill::test3() +{ + // Testing will message only update after connection with will + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + const std::string ConnectWillTopic("connect/will/topic"); + const UnitTestData ConnectWillData{1, 2, 3, 4, 5, 6}; + const CC_MqttsnQoS ConnectWillQos = CC_MqttsnQoS_AtLeastOnceDelivery; + const bool ConnectWillRetain = false; + + CC_MqttsnConnectConfig connectConfig; + apiConnectInitConfig(&connectConfig); + connectConfig.m_clientId = ClientId.c_str(); + connectConfig.m_cleanSession = true; + + CC_MqttsnWillConfig connectWillConfig; + apiConnectInitConfigWill(&connectWillConfig); + connectWillConfig.m_topic = ConnectWillTopic.c_str(); + connectWillConfig.m_data = ConnectWillData.data(); + connectWillConfig.m_dataLen = static_cast(ConnectWillData.size()); + connectWillConfig.m_qos = ConnectWillQos; + connectWillConfig.m_retain = ConnectWillRetain; + + unitTestDoConnect(client, &connectConfig, &connectWillConfig); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const UnitTestData Data{1, 2, 3, 4}; + + CC_MqttsnWillConfig config; + apiWillInitConfig(&config); + config = connectWillConfig; + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + + auto will = apiWillPrepare(client); + TS_ASSERT_DIFFERS(will, nullptr); + + auto ec = apiWillConfig(will, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestWillSend(will); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + TS_ASSERT(!unitTestHasWillCompleteReport()); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willmsgupdMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willmsgupdMsg, nullptr); + TS_ASSERT_EQUALS(willmsgupdMsg->field_willMsg().value(), Data); + TS_ASSERT(!unitTestHasOutputData()); + } + + { + UnitTestWillmsgrespMsg respMsg; + respMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, respMsg); + } + + TS_ASSERT(!unitTestHasOutputData()); + + { + TS_ASSERT(unitTestHasWillCompleteReport()); + auto willReport = unitTestWillCompleteReport(); + TS_ASSERT_EQUALS(willReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(willReport->m_info.m_topicUpdReturnCode, CC_MqttsnReturnCode_ValuesLimit); + TS_ASSERT_EQUALS(willReport->m_info.m_msgUpdReturnCode, CC_MqttsnReturnCode_Accepted); + TS_ASSERT(!unitTestHasWillCompleteReport()); + } +} + +void UnitTestWill::test4() +{ + // Testing will topic only update after connection with will + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + const std::string ConnectWillTopic("connect/will/topic"); + const UnitTestData ConnectWillData{1, 2, 3, 4, 5, 6}; + const CC_MqttsnQoS ConnectWillQos = CC_MqttsnQoS_AtLeastOnceDelivery; + const bool ConnectWillRetain = false; + + CC_MqttsnConnectConfig connectConfig; + apiConnectInitConfig(&connectConfig); + connectConfig.m_clientId = ClientId.c_str(); + connectConfig.m_cleanSession = true; + + CC_MqttsnWillConfig connectWillConfig; + apiConnectInitConfigWill(&connectWillConfig); + connectWillConfig.m_topic = ConnectWillTopic.c_str(); + connectWillConfig.m_data = ConnectWillData.data(); + connectWillConfig.m_dataLen = static_cast(ConnectWillData.size()); + connectWillConfig.m_qos = ConnectWillQos; + connectWillConfig.m_retain = ConnectWillRetain; + + unitTestDoConnect(client, &connectConfig, &connectWillConfig); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string WillTopic("will/topic"); + + auto config1 = connectWillConfig; + config1.m_topic = WillTopic.c_str(); + + { + auto will = apiWillPrepare(client); + TS_ASSERT_DIFFERS(will, nullptr); + + auto ec = apiWillConfig(will, &config1); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestWillSend(will); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + } + + TS_ASSERT(!unitTestHasWillCompleteReport()); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willtopicupdMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willtopicupdMsg, nullptr); + TS_ASSERT(willtopicupdMsg->field_flags().doesExist()); + TS_ASSERT_EQUALS(static_cast(willtopicupdMsg->field_flags().field().field_qos().value()), ConnectWillQos); + TS_ASSERT_EQUALS(willtopicupdMsg->field_flags().field().field_mid().getBitValue_Retain(), ConnectWillRetain); + TS_ASSERT_EQUALS(willtopicupdMsg->field_willTopic().value(), WillTopic); + TS_ASSERT(!unitTestHasOutputData()); + } + + { + UnitTestWilltopicrespMsg respMsg; + respMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, respMsg); + } + + TS_ASSERT(!unitTestHasOutputData()); + + { + TS_ASSERT(unitTestHasWillCompleteReport()); + auto willReport = unitTestWillCompleteReport(); + TS_ASSERT_EQUALS(willReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(willReport->m_info.m_topicUpdReturnCode, CC_MqttsnReturnCode_Accepted); + TS_ASSERT_EQUALS(willReport->m_info.m_msgUpdReturnCode, CC_MqttsnReturnCode_ValuesLimit); + TS_ASSERT(!unitTestHasWillCompleteReport()); + } + + const CC_MqttsnQoS WillQos = CC_MqttsnQoS_AtMostOnceDelivery; + auto config2 = config1; + config2.m_qos = WillQos; + + { + auto will = apiWillPrepare(client); + TS_ASSERT_DIFFERS(will, nullptr); + + auto ec = apiWillConfig(will, &config2); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestWillSend(will); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + } + + TS_ASSERT(!unitTestHasWillCompleteReport()); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willtopicupdMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willtopicupdMsg, nullptr); + TS_ASSERT(willtopicupdMsg->field_flags().doesExist()); + TS_ASSERT_EQUALS(static_cast(willtopicupdMsg->field_flags().field().field_qos().value()), WillQos); + TS_ASSERT_EQUALS(willtopicupdMsg->field_flags().field().field_mid().getBitValue_Retain(), ConnectWillRetain); + TS_ASSERT_EQUALS(willtopicupdMsg->field_willTopic().value(), WillTopic); + TS_ASSERT(!unitTestHasOutputData()); + } + + { + UnitTestWilltopicrespMsg respMsg; + respMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, respMsg); + } + + TS_ASSERT(!unitTestHasOutputData()); + + { + TS_ASSERT(unitTestHasWillCompleteReport()); + auto willReport = unitTestWillCompleteReport(); + TS_ASSERT_EQUALS(willReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(willReport->m_info.m_topicUpdReturnCode, CC_MqttsnReturnCode_Accepted); + TS_ASSERT_EQUALS(willReport->m_info.m_msgUpdReturnCode, CC_MqttsnReturnCode_ValuesLimit); + TS_ASSERT(!unitTestHasWillCompleteReport()); + } + + const bool WillRetain = !config2.m_retain; + auto config3 = config2; + config3.m_retain = WillRetain; + + { + auto will = apiWillPrepare(client); + TS_ASSERT_DIFFERS(will, nullptr); + + auto ec = apiWillConfig(will, &config3); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestWillSend(will); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + } + + TS_ASSERT(!unitTestHasWillCompleteReport()); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willtopicupdMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willtopicupdMsg, nullptr); + TS_ASSERT(willtopicupdMsg->field_flags().doesExist()); + TS_ASSERT_EQUALS(static_cast(willtopicupdMsg->field_flags().field().field_qos().value()), WillQos); + TS_ASSERT_EQUALS(willtopicupdMsg->field_flags().field().field_mid().getBitValue_Retain(), WillRetain); + TS_ASSERT_EQUALS(willtopicupdMsg->field_willTopic().value(), WillTopic); + TS_ASSERT(!unitTestHasOutputData()); + } + + { + UnitTestWilltopicrespMsg respMsg; + respMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, respMsg); + } + + TS_ASSERT(!unitTestHasOutputData()); + + { + TS_ASSERT(unitTestHasWillCompleteReport()); + auto willReport = unitTestWillCompleteReport(); + TS_ASSERT_EQUALS(willReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(willReport->m_info.m_topicUpdReturnCode, CC_MqttsnReturnCode_Accepted); + TS_ASSERT_EQUALS(willReport->m_info.m_msgUpdReturnCode, CC_MqttsnReturnCode_ValuesLimit); + TS_ASSERT(!unitTestHasWillCompleteReport()); + } +} + +void UnitTestWill::test5() +{ + // Testing will update when nothing changes, immediate callback invocation. + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + const std::string ConnectWillTopic("connect/will/topic"); + const UnitTestData ConnectWillData{1, 2, 3, 4, 5, 6}; + const CC_MqttsnQoS ConnectWillQos = CC_MqttsnQoS_AtLeastOnceDelivery; + const bool ConnectWillRetain = false; + + CC_MqttsnConnectConfig connectConfig; + apiConnectInitConfig(&connectConfig); + connectConfig.m_clientId = ClientId.c_str(); + connectConfig.m_cleanSession = true; + + CC_MqttsnWillConfig connectWillConfig; + apiConnectInitConfigWill(&connectWillConfig); + connectWillConfig.m_topic = ConnectWillTopic.c_str(); + connectWillConfig.m_data = ConnectWillData.data(); + connectWillConfig.m_dataLen = static_cast(ConnectWillData.size()); + connectWillConfig.m_qos = ConnectWillQos; + connectWillConfig.m_retain = ConnectWillRetain; + + unitTestDoConnect(client, &connectConfig, &connectWillConfig); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + auto will = apiWillPrepare(client); + TS_ASSERT_DIFFERS(will, nullptr); + + auto ec = apiWillConfig(will, &connectWillConfig); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestWillSend(will); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + } + + TS_ASSERT(!unitTestHasOutputData()); + + { + TS_ASSERT(unitTestHasWillCompleteReport()); + auto willReport = unitTestWillCompleteReport(); + TS_ASSERT_EQUALS(willReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(willReport->m_info.m_topicUpdReturnCode, CC_MqttsnReturnCode_ValuesLimit); + TS_ASSERT_EQUALS(willReport->m_info.m_msgUpdReturnCode, CC_MqttsnReturnCode_ValuesLimit); + TS_ASSERT(!unitTestHasWillCompleteReport()); + } +} + +void UnitTestWill::test6() +{ + // Attempting will update before connection + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + auto ec = CC_MqttsnErrorCode_ValuesLimit; + auto will = apiWillPrepare(client, &ec); + TS_ASSERT_EQUALS(will, nullptr); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_NotConnected); +} + +void UnitTestWill::test7() +{ + // Attempting will update after disconnection + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + unitTestDoDisconnect(client); + + TS_ASSERT(!unitTestHasTickReq()); + + auto ec = CC_MqttsnErrorCode_ValuesLimit; + auto will = apiWillPrepare(client, &ec); + TS_ASSERT_EQUALS(will, nullptr); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_NotConnected); +} + + From e68896d6c3df194742e71ee131004a62def6f40a Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 31 Jul 2024 08:35:47 +1000 Subject: [PATCH 079/106] Added documentation of the will update operation. --- client/lib/doxygen/main.dox | 182 ++++++++++++++++++- client/lib/include/cc_mqttsn_client/common.h | 23 ++- client/lib/test/UnitTestWill.th | 1 + 3 files changed, 198 insertions(+), 8 deletions(-) diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index 4dca6eb1..0882ca0d 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -611,7 +611,7 @@ /// // Perform the configuration /// ec = cc_mqttsn_client_connect_config(connect, &config); /// if (ec != CC_MqttsnErrorCode_Success) { -/// printf("ERROR: Basic configuration failed with ec=%d\n", ec); +/// printf("ERROR: Connect configuration failed with ec=%d\n", ec); /// ... /// } /// @endcode @@ -692,7 +692,7 @@ /// the operation before the completion callback is invoked. /// /// When the "connect" operation completion callback is invoked the reported -/// "response" information is present if and only if the "status" is +/// "info" is present if and only if the "status" is /// @ref CC_MqttsnAsyncOpStatus_Complete. /// /// @b NOTE that only single "connect" / "disconnect" operation is allowed at a time, any attempt to @@ -1437,8 +1437,186 @@ /// publish operation by the reported handle when the completion callback /// is invoked. /// +/// --- +/// @section doc_cc_mqttsn_client_will Updating Will +/// To update will after the connection established use @ref will "will" operation. +/// +/// @subsection doc_cc_mqttsn_client_will_prepare Preparing "Will" Operation. +/// @code +/// CC_MqttsnErrorCode ec = CC_MqttsnErrorCode_Success; +/// CC_MqttsnConnectHandle will = cc_mqttsn_client_will_prepare(client, &ec); +/// if (will == NULL) { +/// printf("ERROR: Will update allocation failed with ec=%d\n", ec); +/// } +/// @endcode +/// +/// @subsection doc_cc_mqttsn_client_will_retry_period Configuring "Will" Retry Period +/// When created, the "will" operation inherits the @ref doc_cc_mqttsn_client_retry_period +/// configuration. It can be changed for the allocated operation using the +/// @b cc_mqttsn_client_will_set_retry_period() function. +/// @code +/// ec = cc_mqttsn_client_will_set_retry_period(will, 1000); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// ... /* Something went wrong */ +/// } +/// @endcode +/// To retrieve the configured response timeout use the @b cc_mqttsn_client_will_get_retry_period() function. +/// +/// @subsection doc_cc_mqttsn_client_will_retry_count Configuring "Will" Retry Count +/// When created, the "will" operation inherits the @ref doc_cc_mqttsn_client_retry_count +/// configuration. It can be changed for the allocated operation using the +/// @b cc_mqttsn_client_will_set_retry_count() function. +/// @code +/// ec = cc_mqttsn_client_will_set_retry_count(will, 2); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// ... /* Something went wrong */ +/// } +/// @endcode +/// To retrieve the configured response timeout use the @b cc_mqttsn_client_will_get_retry_count() function. +/// +/// @subsection doc_cc_mqttsn_client_will_config Configuration of "Will" Operation +/// To configure "will" operation use @b cc_mqttsn_client_will_config() function. +/// @code +/// CC_MqttsnWillConfig config; +/// +/// // Assign default values to the "config" +/// cc_mqttsn_client_will_init_config(&config); +/// +/// // Update the values if needed: +/// config.m_topic = "some_topic"; +/// config.m_data = ...; +/// config.m_dataLen = ...; +/// ... +/// +/// // Perform the configuration +/// ec = cc_mqttsn_client_will_config(will, &config); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Will configuration failed with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// See also documentation of the @ref CC_MqttsnWillConfig structure. +/// +/// To remove the previously set will, just assign @b NULL (done by the +/// invocation of the @b cc_mqttsn_client_will_init_config()) to +/// @ref CC_MqttsnWillConfig::m_topic "m_topic" data member (also without assigning +/// anything to @ref CC_MqttsnWillConfig::m_data "m_data"). +/// +/// @subsection doc_cc_mqttsn_client_will_send Sending Will Update Request +/// When all the necessary configurations are performed for the allocated "will" +/// operation it can actually be sent to the gateway. To initiate sending +/// use the @b cc_mqttsn_client_will_send() function. +/// @code +/// void my_will_complete_cb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info) +/// { +/// if (status != CC_MqttsnAsyncOpStatus_Complete) { +/// printf("ERROR: The will operation has failed with status=%d\n", status); +/// ... // handle error. +/// return; +/// } +/// +/// // "info" is not NULL when status is CC_MqttsnAsyncOpStatus_Complete. +/// assert(info != NULL); +/// ... // Analyze info values. +/// } +/// +/// ec = cc_mqttsn_client_will_send(will, &my_will_complete_cb, data); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to send will update request with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// The provided callback will be invoked when the "will" operation is complete +/// if and only if the function returns @ref CC_MqttsnErrorCode_Success. +/// +/// The handle returned by the @b cc_mqttsn_client_will_prepare() function +/// can be discarded (there is no free / de-allocation) right after the +/// @b cc_mqttsn_client_will_send() invocation +/// regardless of the returned error code. However, the handle remains valid until +/// the callback is called (in case the @ref CC_MqttsnErrorCode_Success was returned). +/// The valid handle can be used to @ref doc_cc_mqttsn_client_will_cancel "cancel" +/// the operation before the completion callback is invoked. +/// +/// When the "will" operation completion callback is invoked the reported +/// "info" is present if and only if the "status" is +/// @ref CC_MqttsnAsyncOpStatus_Complete. +/// +/// @b NOTE that only single "will" operation is allowed at a time, any attempt to +/// prepare a new one via @b cc_mqttsn_client_will_prepare() will be rejected +/// until the "will" operation completion callback is invoked or +/// the operation is @ref doc_cc_mqttsn_client_will_cancel "cancelled". +/// +/// The will update operation can be split into up to 2 stages: +/// @li @b WILLTOPICUPD <-> @b WILLTOPICRESP messages exchange +/// @li @b WILLMSGUPD <-> @b WILLMSGRESP messages exchange +/// +/// The library monitors the currently set will configuration and may skip one or +/// more of the stages. When the will update operation is issued, but no +/// will update is actually needed, the callback passed to the +/// @b cc_mqttsn_client_will_send() will be invoked right away. +/// +/// When the callback reporting the will update status is invoked, it is responsibility +/// of the application to check the @ref CC_MqttsnWillInfo::m_topicUpdReturnCode and +/// @ref CC_MqttsnWillInfo::m_msgUpdReturnCode values. If the value of any of +/// them is @ref CC_MqttsnReturnCode_ValuesLimit, then the relevant operation didn't take +/// place. Otherwise, if the value is not @ref CC_MqttsnReturnCode_Accepted, then the +/// relevant operation stage has failed. +/// +/// For example if any of the return codes is reported to be @ref CC_MqttsnReturnCode_Conjestion, +/// it means that the gateway may still be processing the previous will update stage +/// and cannot process the next one. The application is responsible to re-issue +/// the the same will update request a bit later. If one of the stages was successful +/// (acknowledged by the gateway), then the appropriate stage will be skipped by the +/// library and the failing stage is going to be repeated. +/// +/// @subsection doc_cc_mqttsn_client_will_cancel Cancel the "Will" Operation. +/// While the handle returned by the @b cc_mqttsn_client_will_prepare() is still +/// valid it is possible to cancel / discard the operation. +/// @code +/// ec = cc_mqttsn_client_will_cancel(will); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to cancel will with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// In case the @b cc_mqttsn_client_will_send() function was successfully +/// called before the @b cc_mqttsn_client_will_cancel(), the operation is +/// cancelled @b without callback invocation. +/// +/// @subsection doc_cc_mqttsn_client_will_simplify Simplifying the "Will" Operation Preparation. +/// In many use cases the "will" operation can be quite simple with a lot of defaults. +/// To simplify the sequence of the operation preparation and handling of errors, +/// the library provides wrapper function(s) that can be used: +/// @li @b cc_mqttsn_client_will() +/// +/// For example: +/// @code +/// CC_MqttsnWillConfig config; +/// +/// // Assign default values to the "config" +/// cc_mqttsn_client_will_init_config(&config); +/// +/// // Update the values if needed: +/// config.m_topic = "some_topic"; +/// config.m_data = ...; +/// config.m_dataLen = ...; +/// +/// ec = cc_mqttsn_client_will(client, config, &my_will_complete_cb, data); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to send will request with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// Note that the wrapper function does NOT expose the handle returned by the +/// @b cc_mqttsn_client_will_prepare(). It means that it's not possible to +/// cancel the "will" operation before its completion. +/// +/// /// @section doc_cc_mqttsn_client_receive Receiving Messages /// TODO /// +/// @section doc_cc_mqttsn_client_sleep Sleeping +/// TODO +/// /// @section doc_cc_mqttsn_client_unsolicited_disconnect Unsolicited Gateway Disconnection /// TODO diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index b975d46b..1d2e1a29 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -38,7 +38,7 @@ extern "C" { #define CC_MQTTSN_CLIENT_VERSION CC_MQTTSN_CLIENT_MAKE_VERSION(CC_MQTTSN_CLIENT_MAJOR_VERSION, CC_MQTTSN_CLIENT_MINOR_VERSION, CC_MQTTSN_CLIENT_PATCH_VERSION) /// @brief Quality of Service -/// @ingroup client +/// @ingroup global typedef enum { CC_MqttsnQoS_AtMostOnceDelivery = 0, ///< QoS=0. At most once delivery. @@ -47,7 +47,7 @@ typedef enum } CC_MqttsnQoS; /// @brief Error code returned by various API functions. -/// @ingroup client +/// @ingroup global typedef enum { CC_MqttsnErrorCode_Success = 0, ///< The requested operation was successfully started. @@ -69,7 +69,7 @@ typedef enum } CC_MqttsnErrorCode; /// @brief Status of the gateway -/// @ingroup client +/// @ingroup global typedef enum { CC_MqttsnGwStatus_AddedByGateway = 0, ///< Added by the @b ADVERTISE or @b GWINFO sent by the gateway messages @@ -82,7 +82,7 @@ typedef enum } CC_MqttsnGwStatus; /// @brief Status of the asynchronous operation -/// @ingroup client +/// @ingroup global typedef enum { CC_MqttsnAsyncOpStatus_Complete = 0, ///< The requested operation has been completed, refer to reported extra details for information @@ -105,7 +105,7 @@ typedef enum } CC_MqttsnGatewayDisconnectReason; /// @brief Return code as per MQTT-SN specification -/// @ingroup client +/// @ingroup global typedef enum { CC_MqttsnReturnCode_Accepted = 0, ///< Accepted @@ -116,10 +116,12 @@ typedef enum } CC_MqttsnReturnCode; /// @brief Declaration of struct for the @ref CC_MqttsnClientHandle; +/// @ingroup client struct CC_MqttsnClient; /// @brief Handler used to access client specific data structures. /// @details Returned by cc_mqttsn_client_alloc() function. +/// @ingroup client typedef struct CC_MqttsnClient* CC_MqttsnClientHandle; /// @brief Declaration of the hidden structure used to define @ref CC_MqttsnSearchHandle @@ -186,9 +188,11 @@ struct CC_MqttsnWill; typedef struct CC_MqttsnWill* CC_MqttsnWillHandle; /// @brief Type used to hold Topic ID value. +/// @ingroup global typedef unsigned short CC_MqttsnTopicId; /// @brief Incoming message information +/// @ingroup global typedef struct { const char* topic; ///< Topic the message was published with. May be NULL if message is reported with predefined topic ID. @@ -200,7 +204,7 @@ typedef struct } CC_MqttsnMessageInfo; /// @brief Gateway information -/// @ingroup client +/// @ingroup search typedef struct { unsigned char m_gwId; ///< Gateway ID @@ -294,6 +298,7 @@ typedef struct /// cc_mqttsn_client_set_next_tick_program_callback() function. /// @param[in] duration Time duration in @b milliseconds. After the requested /// time expires, the cc_mqttsn_client_tick() function is expected to be invoked. +/// @ingroup client typedef void (*CC_MqttsnNextTickProgramCb)(void* data, unsigned duration); /// @brief Callback used to request termination of existing time measurement. @@ -302,6 +307,7 @@ typedef void (*CC_MqttsnNextTickProgramCb)(void* data, unsigned duration); /// @param[in] data Pointer to user data object, passed as last parameter to /// cc_mqttsn_client_set_cancel_next_tick_wait_callback() function. /// @return Number of elapsed milliseconds since last time measurement request. +/// @ingroup client typedef unsigned (*CC_MqttsnCancelNextTickWaitCb)(void* data); /// @brief Callback used to request to send data to the gateway. @@ -316,6 +322,7 @@ typedef unsigned (*CC_MqttsnCancelNextTickWaitCb)(void* data); /// @param[in] buf Pointer to the buffer containing data to send /// @param[in] bufLen Number of bytes to send /// @param[in] broadcastRadius Broadcast radius. When @b 0, means unicast to the connected gateway. +/// @ingroup client typedef void (*CC_MqttsnSendOutputDataCb)(void* data, const unsigned char* buf, unsigned bufLen, unsigned broadcastRadius); /// @brief Callback used to report gateway status. @@ -325,12 +332,14 @@ typedef void (*CC_MqttsnSendOutputDataCb)(void* data, const unsigned char* buf, /// cc_mqttsn_client_set_gw_status_report_callback() function. /// @param[in] status Current status of the gateway. /// @param[in] info Currently stored gateway information. +/// @ingroup client typedef void (*CC_MqttsnGwStatusReportCb)(void* data, CC_MqttsnGwStatus status, const CC_MqttsnGatewayInfo* info); /// @brief Callback used to report unsolicited disconnection of the gateway. /// @param[in] data Pointer to user data object, passed as the last parameter to /// the request call. /// @param[in] reason Reason of the disconnection. +/// @ingroup client typedef void (*CC_MqttsnGwDisconnectedReportCb)(void* data, CC_MqttsnGatewayDisconnectReason reason); /// @brief Callback used to report incoming messages. @@ -341,6 +350,7 @@ typedef void (*CC_MqttsnGwDisconnectedReportCb)(void* data, CC_MqttsnGatewayDisc /// @param[in] data Pointer to user data object, passed as last parameter to /// cc_mqttsn_client_set_message_report_callback() function. /// @param[in] msgInfo Information about incoming message. +/// @ingroup client typedef void (*CC_MqttsnMessageReportCb)(void* data, const CC_MqttsnMessageInfo* msgInfo); /// @brief Callback used to report discovered errors. @@ -354,6 +364,7 @@ typedef void (*CC_MqttsnErrorLogCb)(void* data, const char* msg); /// responding with @b GWINFO message on behalf of a gateway. /// @details In case function return 0U, the response on behalf of the gateway is disabled. /// @return Number of milliseconds to wait for another @b GWINFO to cancel the intended send of @b GWINFO on behalf of the gateway. +/// @ingroup client typedef unsigned (*CC_MqttsnGwinfoDelayRequestCb)(void* data); /// @brief Callback used to report completion of the asynchronous operation. diff --git a/client/lib/test/UnitTestWill.th b/client/lib/test/UnitTestWill.th index 12d26df2..4abcf99d 100644 --- a/client/lib/test/UnitTestWill.th +++ b/client/lib/test/UnitTestWill.th @@ -555,4 +555,5 @@ void UnitTestWill::test7() TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_NotConnected); } +// TODO: test clear will From 1ebfa6f4a69f224d3abc5e6f01851b3acf0cfc36 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 31 Jul 2024 08:50:45 +1000 Subject: [PATCH 080/106] More will update unit testing. --- client/lib/test/UnitTestWill.th | 379 +++++++++++++++++++++++++++++++- 1 file changed, 378 insertions(+), 1 deletion(-) diff --git a/client/lib/test/UnitTestWill.th b/client/lib/test/UnitTestWill.th index 4abcf99d..c3a4f20c 100644 --- a/client/lib/test/UnitTestWill.th +++ b/client/lib/test/UnitTestWill.th @@ -15,6 +15,10 @@ public: void test5(); void test6(); void test7(); + void test8(); + void test9(); + void test10(); + void test11(); private: virtual void setUp() override @@ -555,5 +559,378 @@ void UnitTestWill::test7() TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_NotConnected); } -// TODO: test clear will +void UnitTestWill::test8() +{ + // Testing clearing of the will + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + const std::string ConnectWillTopic("connect/will/topic"); + const UnitTestData ConnectWillData{1, 2, 3, 4, 5, 6}; + const CC_MqttsnQoS ConnectWillQos = CC_MqttsnQoS_AtLeastOnceDelivery; + const bool ConnectWillRetain = false; + + CC_MqttsnConnectConfig connectConfig; + apiConnectInitConfig(&connectConfig); + connectConfig.m_clientId = ClientId.c_str(); + connectConfig.m_cleanSession = true; + + CC_MqttsnWillConfig connectWillConfig; + apiConnectInitConfigWill(&connectWillConfig); + connectWillConfig.m_topic = ConnectWillTopic.c_str(); + connectWillConfig.m_data = ConnectWillData.data(); + connectWillConfig.m_dataLen = static_cast(ConnectWillData.size()); + connectWillConfig.m_qos = ConnectWillQos; + connectWillConfig.m_retain = ConnectWillRetain; + + unitTestDoConnect(client, &connectConfig, &connectWillConfig); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + CC_MqttsnWillConfig willConfig; + apiWillInitConfig(&willConfig); + + auto will = apiWillPrepare(client); + TS_ASSERT_DIFFERS(will, nullptr); + + auto ec = apiWillConfig(will, &willConfig); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestWillSend(will); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willtopicupdMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willtopicupdMsg, nullptr); + TS_ASSERT(willtopicupdMsg->field_flags().isMissing()); + TS_ASSERT(willtopicupdMsg->field_willTopic().value().empty()); + TS_ASSERT(!unitTestHasOutputData()); + } + + { + UnitTestWilltopicrespMsg respMsg; + respMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, respMsg); + } + + { + TS_ASSERT(unitTestHasWillCompleteReport()); + auto willReport = unitTestWillCompleteReport(); + TS_ASSERT_EQUALS(willReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(willReport->m_info.m_topicUpdReturnCode, CC_MqttsnReturnCode_Accepted); + TS_ASSERT_EQUALS(willReport->m_info.m_msgUpdReturnCode, CC_MqttsnReturnCode_ValuesLimit); + TS_ASSERT(!unitTestHasWillCompleteReport()); + } +} + +void UnitTestWill::test9() +{ + // Testing timeouts + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic("will/topic"); + const UnitTestData Data{1, 2, 3, 4}; + const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; + const bool Retain = true; + + CC_MqttsnWillConfig config; + apiWillInitConfig(&config); + config.m_topic = Topic.c_str(); + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + config.m_qos = Qos; + config.m_retain = Retain; + + auto will = apiWillPrepare(client); + TS_ASSERT_DIFFERS(will, nullptr); + + auto ec = apiWillConfig(will, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestWillSend(will); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willtopicupdMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willtopicupdMsg, nullptr); + TS_ASSERT(willtopicupdMsg->field_flags().doesExist()); + TS_ASSERT_EQUALS(static_cast(willtopicupdMsg->field_flags().field().field_qos().value()), Qos); + TS_ASSERT_EQUALS(willtopicupdMsg->field_flags().field().field_mid().getBitValue_Retain(), Retain); + TS_ASSERT_EQUALS(willtopicupdMsg->field_willTopic().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willtopicupdMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willtopicupdMsg, nullptr); + TS_ASSERT(willtopicupdMsg->field_flags().doesExist()); + TS_ASSERT_EQUALS(static_cast(willtopicupdMsg->field_flags().field().field_qos().value()), Qos); + TS_ASSERT_EQUALS(willtopicupdMsg->field_flags().field().field_mid().getBitValue_Retain(), Retain); + TS_ASSERT_EQUALS(willtopicupdMsg->field_willTopic().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestWilltopicrespMsg respMsg; + respMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, respMsg); + } + + TS_ASSERT(!unitTestHasWillCompleteReport()); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willmsgupdMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willmsgupdMsg, nullptr); + TS_ASSERT_EQUALS(willmsgupdMsg->field_willMsg().value(), Data); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willmsgupdMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willmsgupdMsg, nullptr); + TS_ASSERT_EQUALS(willmsgupdMsg->field_willMsg().value(), Data); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestWillmsgrespMsg respMsg; + respMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, respMsg); + } + + TS_ASSERT(!unitTestHasOutputData()); + + { + TS_ASSERT(unitTestHasWillCompleteReport()); + auto willReport = unitTestWillCompleteReport(); + TS_ASSERT_EQUALS(willReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + TS_ASSERT_EQUALS(willReport->m_info.m_topicUpdReturnCode, CC_MqttsnReturnCode_Accepted); + TS_ASSERT_EQUALS(willReport->m_info.m_msgUpdReturnCode, CC_MqttsnReturnCode_Accepted); + TS_ASSERT(!unitTestHasWillCompleteReport()); + } +} + +void UnitTestWill::test10() +{ + // Testing loss of WILLTOPICUPD / WILLTOPICRESP + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic("will/topic"); + const UnitTestData Data{1, 2, 3, 4}; + const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; + const bool Retain = true; + + CC_MqttsnWillConfig config; + apiWillInitConfig(&config); + config.m_topic = Topic.c_str(); + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + config.m_qos = Qos; + config.m_retain = Retain; + + auto will = apiWillPrepare(client); + TS_ASSERT_DIFFERS(will, nullptr); + + auto ec = apiWillSetRetryCount(will, 1U); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = apiWillConfig(will, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestWillSend(will); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willtopicupdMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willtopicupdMsg, nullptr); + TS_ASSERT(willtopicupdMsg->field_flags().doesExist()); + TS_ASSERT_EQUALS(static_cast(willtopicupdMsg->field_flags().field().field_qos().value()), Qos); + TS_ASSERT_EQUALS(willtopicupdMsg->field_flags().field().field_mid().getBitValue_Retain(), Retain); + TS_ASSERT_EQUALS(willtopicupdMsg->field_willTopic().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willtopicupdMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willtopicupdMsg, nullptr); + TS_ASSERT(willtopicupdMsg->field_flags().doesExist()); + TS_ASSERT_EQUALS(static_cast(willtopicupdMsg->field_flags().field().field_qos().value()), Qos); + TS_ASSERT_EQUALS(willtopicupdMsg->field_flags().field().field_mid().getBitValue_Retain(), Retain); + TS_ASSERT_EQUALS(willtopicupdMsg->field_willTopic().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + + TS_ASSERT(!unitTestHasOutputData()); + + { + TS_ASSERT(unitTestHasWillCompleteReport()); + auto willReport = unitTestWillCompleteReport(); + TS_ASSERT_EQUALS(willReport->m_status, CC_MqttsnAsyncOpStatus_Timeout); + TS_ASSERT(!unitTestHasWillCompleteReport()); + } +} + +void UnitTestWill::test11() +{ + // Testing loss of WILLMSGUPD / WILLMSGRESP + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic("will/topic"); + const UnitTestData Data{1, 2, 3, 4}; + const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; + const bool Retain = true; + + CC_MqttsnWillConfig config; + apiWillInitConfig(&config); + config.m_topic = Topic.c_str(); + config.m_data = Data.data(); + config.m_dataLen = static_cast(Data.size()); + config.m_qos = Qos; + config.m_retain = Retain; + + auto will = apiWillPrepare(client); + TS_ASSERT_DIFFERS(will, nullptr); + + auto ec = apiWillSetRetryCount(will, 1U); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = apiWillConfig(will, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestWillSend(will); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willtopicupdMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willtopicupdMsg, nullptr); + TS_ASSERT(willtopicupdMsg->field_flags().doesExist()); + TS_ASSERT_EQUALS(static_cast(willtopicupdMsg->field_flags().field().field_qos().value()), Qos); + TS_ASSERT_EQUALS(willtopicupdMsg->field_flags().field().field_mid().getBitValue_Retain(), Retain); + TS_ASSERT_EQUALS(willtopicupdMsg->field_willTopic().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willtopicupdMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willtopicupdMsg, nullptr); + TS_ASSERT(willtopicupdMsg->field_flags().doesExist()); + TS_ASSERT_EQUALS(static_cast(willtopicupdMsg->field_flags().field().field_qos().value()), Qos); + TS_ASSERT_EQUALS(willtopicupdMsg->field_flags().field().field_mid().getBitValue_Retain(), Retain); + TS_ASSERT_EQUALS(willtopicupdMsg->field_willTopic().value(), Topic); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestWilltopicrespMsg respMsg; + respMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, respMsg); + } + + TS_ASSERT(!unitTestHasWillCompleteReport()); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willmsgupdMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willmsgupdMsg, nullptr); + TS_ASSERT_EQUALS(willmsgupdMsg->field_willMsg().value(), Data); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willmsgupdMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(willmsgupdMsg, nullptr); + TS_ASSERT_EQUALS(willmsgupdMsg->field_willMsg().value(), Data); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + + TS_ASSERT(!unitTestHasOutputData()); + + { + TS_ASSERT(unitTestHasWillCompleteReport()); + auto willReport = unitTestWillCompleteReport(); + TS_ASSERT_EQUALS(willReport->m_status, CC_MqttsnAsyncOpStatus_Timeout); + TS_ASSERT(!unitTestHasWillCompleteReport()); + } +} + + + From a94e67f377b6f3c78b3dafb9bfe45012c07c039e Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 1 Aug 2024 08:57:56 +1000 Subject: [PATCH 081/106] Added limit of the stored outgoing topic ids. --- client/lib/doxygen/main.dox | 30 +++- client/lib/src/ClientImpl.cpp | 51 ++++++ client/lib/src/ClientImpl.h | 11 +- client/lib/src/ClientState.h | 3 + client/lib/src/TopicFilterDefs.h | 28 +++- client/lib/src/op/Op.cpp | 12 +- client/lib/src/op/SendOp.cpp | 46 ++++-- client/lib/templ/client.cpp.templ | 24 +++ client/lib/templ/client.h.templ | 39 ++++- client/lib/test/UnitTestCommonBase.cpp | 9 ++ client/lib/test/UnitTestCommonBase.h | 5 + client/lib/test/UnitTestDefaultBase.cpp | 4 + client/lib/test/UnitTestPublish.th | 206 ++++++++++++++++++++++++ 13 files changed, 441 insertions(+), 27 deletions(-) diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index 0882ca0d..46a8c0c6 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -1437,7 +1437,35 @@ /// publish operation by the reported handle when the completion callback /// is invoked. /// -/// --- +/// @subsection doc_cc_mqttsn_client_publish_reg_limit Limiting Stored Outgoing Topic IDs +/// When a client attempts to publish a message with non-short topic (length of which +/// is not equal to 2 characters), the topic needs to be registered against the +/// gateway, and receive a numeric topic ID in response. Such topic ID is preserved +/// for future use to avoid registration process before the required message is +/// actually PUBLISH-ed. Depending on the amount of various topic strings that +/// the end application generates there might be a need to limit the uncontrolled growth of +/// internal storage. To do so use @b cc_mqttsn_client_set_outgoing_topic_id_storage_limit() +/// function +/// @code +/// ec = cc_mqttsn_client_set_outgoing_topic_id_storage_limit(client, 10); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to limit the outgoing topic ids storage with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// To get the current limit use @b cc_mqttsn_client_get_outgoing_topic_id_storage_limit() +/// function. +/// +/// To remove the previously set limit, i.e. reset it to the default pass @b 0 as +/// the limit value to @b cc_mqttsn_client_set_outgoing_topic_id_storage_limit(). +/// @code +/// ec = cc_mqttsn_client_set_outgoing_topic_id_storage_limit(client, 0); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to reset the limit the outgoing topic ids storage with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// /// @section doc_cc_mqttsn_client_will Updating Will /// To update will after the connection established use @ref will "will" operation. /// diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index d19507d9..41f1013d 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -57,6 +57,9 @@ ClientImpl::ClientImpl() : m_gwDiscoveryTimer(m_timerMgr.allocTimer()), m_sendGwinfoTimer(m_timerMgr.allocTimer()) { + // Set the limits to maximum allowed + setOutgoingRegTopicsLimit(0); + setIncomingRegTopicsLimit(0); } ClientImpl::~ClientImpl() @@ -501,6 +504,48 @@ op::WillOp* ClientImpl::willPrepare(CC_MqttsnErrorCode* ec) } #endif // #if CC_MQTTSN_CLIENT_HAS_WILL +CC_MqttsnErrorCode ClientImpl::setOutgoingRegTopicsLimit(std::size_t limit) +{ + auto maxLimit = m_reuseState.m_outRegTopics.max_size(); + if (maxLimit < limit) { + errorLog("The specified limit for outgoing topic ids is too high"); + return CC_MqttsnErrorCode_BadParam; + } + + if (limit == 0U) { + limit = maxLimit; + } + + m_clientState.m_outRegTopicsLimit = limit; + return CC_MqttsnErrorCode_Success; +} + +std::size_t ClientImpl::getOutgoingRegTopicsLimit() const +{ + return m_clientState.m_outRegTopicsLimit; +} + +CC_MqttsnErrorCode ClientImpl::setIncomingRegTopicsLimit(std::size_t limit) +{ + auto maxLimit = m_reuseState.m_inRegTopics.max_size(); + if (maxLimit < limit) { + errorLog("The specified limit for incoming topic ids is too high"); + return CC_MqttsnErrorCode_BadParam; + } + + if (limit == 0U) { + limit = maxLimit; + } + + m_clientState.m_inRegTopicsLimit = limit; + return CC_MqttsnErrorCode_Success; +} + +std::size_t ClientImpl::getIncomingRegTopicsLimit() const +{ + return m_clientState.m_inRegTopicsLimit; +} + // CC_MqttsnErrorCode ClientImpl::setPublishOrdering(CC_MqttsnPublishOrdering ordering) // { // if (CC_MqttsnPublishOrdering_ValuesLimit <= ordering) { @@ -702,6 +747,12 @@ void ClientImpl::handle(GwinfoMsg& msg) } #endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY +void ClientImpl::handle(RegisterMsg& msg) +{ + static_cast(msg); + // TODO: +} + // void ClientImpl::handle(PublishMsg& msg) // { // if (m_sessionState.m_disconnecting) { diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index ee1eb7a9..0a72c348 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -66,8 +66,6 @@ class ClientImpl final : public ProtMsgHandler // -------------------- API Calls ----------------------------- void tick(unsigned ms); void processData(const std::uint8_t* iter, unsigned len); - // void notifyNetworkDisconnected(); - // bool isNetworkDisconnected() const; op::SearchOp* searchPrepare(CC_MqttsnErrorCode* ec); op::ConnectOp* connectPrepare(CC_MqttsnErrorCode* ec); @@ -77,10 +75,10 @@ class ClientImpl final : public ProtMsgHandler op::SendOp* publishPrepare(CC_MqttsnErrorCode* ec); op::WillOp* willPrepare(CC_MqttsnErrorCode* ec); - // std::size_t sendsCount() const - // { - // return m_sendOps.size(); - // } + CC_MqttsnErrorCode setOutgoingRegTopicsLimit(std::size_t limit); + std::size_t getOutgoingRegTopicsLimit() const; + CC_MqttsnErrorCode setIncomingRegTopicsLimit(std::size_t limit); + std::size_t getIncomingRegTopicsLimit() const; void setNextTickProgramCallback(CC_MqttsnNextTickProgramCb cb, void* data) { @@ -152,6 +150,7 @@ class ClientImpl final : public ProtMsgHandler #endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY // virtual void handle(PublishMsg& msg) override; + virtual void handle(RegisterMsg& msg) override; virtual void handle(PubackMsg& msg) override; virtual void handle(PingreqMsg& msg) override; virtual void handle(DisconnectMsg& msg) override; diff --git a/client/lib/src/ClientState.h b/client/lib/src/ClientState.h index 411e14e1..92618a30 100644 --- a/client/lib/src/ClientState.h +++ b/client/lib/src/ClientState.h @@ -14,6 +14,7 @@ #include "cc_mqttsn_client/common.h" #include +#include namespace cc_mqttsn_client { @@ -40,6 +41,8 @@ struct ClientState GwInfosList m_gwInfos; PacketIdsList m_allocatedPacketIds; Timestamp m_timestamp = 0U; + std::size_t m_outRegTopicsLimit = std::numeric_limits::max(); + std::size_t m_inRegTopicsLimit = std::numeric_limits::max(); std::uint16_t m_lastPacketId = 0U; bool m_initialized = false; bool m_firstConnect = true; diff --git a/client/lib/src/TopicFilterDefs.h b/client/lib/src/TopicFilterDefs.h index b1db7376..f2803c76 100644 --- a/client/lib/src/TopicFilterDefs.h +++ b/client/lib/src/TopicFilterDefs.h @@ -13,6 +13,8 @@ #include "ObjListType.h" #include "ProtocolDefs.h" +#include + namespace cc_mqttsn_client { @@ -31,8 +33,30 @@ struct RegTopicInfo RegTopicInfo(CC_MqttsnTopicId topicId) : m_topicId(topicId) {} }; +struct TimestampStorage +{ + using Timestamp = std::uint64_t; + Timestamp m_timestamp = 0U; + + TimestampStorage(Timestamp timestamp) : m_timestamp(timestamp) {} +}; + +struct FullRegTopicInfo : public TimestampStorage, public RegTopicInfo +{ + template + FullRegTopicInfo(Timestamp timestamp, T&& topic, CC_MqttsnTopicId topicId) : + TimestampStorage(timestamp), + RegTopicInfo(std::forward(topic), topicId) + { + } + + FullRegTopicInfo(Timestamp timestamp, const char* topic) : TimestampStorage(timestamp), RegTopicInfo(topic) {} + FullRegTopicInfo(Timestamp timestamp, CC_MqttsnTopicId topicId) : TimestampStorage(timestamp), RegTopicInfo(topicId) {} +}; + + using SubFiltersMap = ObjListType; // key is m_topic -using InRegTopicsMap = ObjListType; // key is m_topicId; -using OutRegTopicsMap = ObjListType; // key is m_topic; +using InRegTopicsMap = ObjListType; // key is m_topicId; +using OutRegTopicsMap = ObjListType; // key is m_topic; } // namespace cc_mqttsn_client diff --git a/client/lib/src/op/Op.cpp b/client/lib/src/op/Op.cpp index cbabf442..a98841b3 100644 --- a/client/lib/src/op/Op.cpp +++ b/client/lib/src/op/Op.cpp @@ -58,7 +58,7 @@ typename TMap::iterator findRegTopicInfo(const char* topic, TMap& map) } template -void storeRegTopic(const char* topic, CC_MqttsnTopicId topicId, TMap& map) +void storeFullRegTopic(ClientState::Timestamp timestamp, const char* topic, CC_MqttsnTopicId topicId, TMap& map) { auto iter = findRegTopicInfo(topicId, map); if ((iter != map.end()) && (iter->m_topicId == topicId)) { @@ -67,15 +67,15 @@ void storeRegTopic(const char* topic, CC_MqttsnTopicId topicId, TMap& map) } if (topic == nullptr) { - map.insert(iter, RegTopicInfo{TopicNameStr(), topicId}); + map.insert(iter, FullRegTopicInfo{timestamp, TopicNameStr(), topicId}); return; } - map.insert(iter, RegTopicInfo{topic, topicId}); + map.insert(iter, FullRegTopicInfo{timestamp, topic, topicId}); } template -bool removeRegTopic(const char* topic, CC_MqttsnTopicId topicId, TMap& map) +bool removeFullRegTopic(const char* topic, CC_MqttsnTopicId topicId, TMap& map) { if (isValidTopicIdInternal(topicId)) { auto iter = findRegTopicInfo(topicId, map); @@ -220,13 +220,13 @@ void Op::decRetryCount() void Op::storeInRegTopic(const char* topic, CC_MqttsnTopicId topicId) { auto& map = m_client.reuseState().m_inRegTopics; - storeRegTopic(topic, topicId, map); + storeFullRegTopic(m_client.clientState().m_timestamp, topic, topicId, map); } bool Op::removeInRegTopic(const char* topic, CC_MqttsnTopicId topicId) { auto& map = m_client.reuseState().m_inRegTopics; - return removeRegTopic(topic, topicId, map); + return removeFullRegTopic(topic, topicId, map); } bool Op::isValidTopicId(CC_MqttsnTopicId id) diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index e7bbc3dd..f2ef9b9e 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -122,6 +122,7 @@ CC_MqttsnErrorCode SendOp::config(const CC_MqttsnPublishConfig* config) m_publishMsg.field_topicId().setValue(iter->m_topicId); m_publishMsg.field_flags().field_topicIdType().value() = TopicIdType::Normal; m_stage = Stage_Publish; + iter->m_timestamp = client().clientState().m_timestamp; break; } @@ -220,21 +221,46 @@ void SendOp::handle(RegackMsg& msg) auto& topicStr = m_registerMsg.field_topicName().value(); COMMS_ASSERT(!topicStr.empty()); - auto iter = - std::lower_bound( - regMap.begin(), regMap.end(), topicStr, - [](auto& elem, auto& topicParam) - { - return elem.m_topic < topicParam; - }); + auto findElem = + [®Map, &topicStr]() + { + return + std::lower_bound( + regMap.begin(), regMap.end(), topicStr, + [](auto& elem, auto& topicParam) + { + return elem.m_topic < topicParam; + }); + }; + + auto iter = findElem(); do { - if ((iter == regMap.end()) || (iter->m_topic != topicStr)) { - regMap.emplace(iter, topicStr.c_str(), topicId); + if ((iter != regMap.end()) && (iter->m_topic == topicStr)) { + iter->m_timestamp = client().clientState().m_timestamp; + iter->m_topicId = topicId; break; } - iter->m_topicId = topicId; + auto outRegTopicsLimit = client().clientState().m_outRegTopicsLimit; + COMMS_ASSERT(outRegTopicsLimit <= regMap.max_size()); + if (outRegTopicsLimit <= regMap.size()) { + // Already full, need to drop least recently used + auto dropIter = + std::min_element( + regMap.begin(), regMap.end(), + [](auto& info1, auto& info2) + { + return info1.m_timestamp < info2.m_timestamp; + }); + + COMMS_ASSERT(dropIter != regMap.end()); + regMap.erase(dropIter); + + iter = findElem(); // the insert place may have changed + } + + regMap.emplace(iter, client().clientState().m_timestamp, topicStr.c_str(), topicId); } while (false); m_stage = Stage_Publish; diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index cbf4f69d..812a289a 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -411,6 +411,30 @@ bool cc_mqttsn_##NAME##client_get_verify_incoming_msg_subscribed(CC_MqttsnClient } } +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_outgoing_topic_id_storage_limit(CC_MqttsnClientHandle client, unsigned long long limit) +{ + COMMS_ASSERT(client != nullptr); + return clientFromHandle(client)->setOutgoingRegTopicsLimit(static_cast(limit)); +} + +unsigned long long cc_mqttsn_##NAME##client_get_outgoing_topic_id_storage_limit(CC_MqttsnClientHandle client) +{ + COMMS_ASSERT(client != nullptr); + return static_cast(clientFromHandle(client)->getOutgoingRegTopicsLimit()); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_incoming_topic_id_storage_limit(CC_MqttsnClientHandle client, unsigned long long limit) +{ + COMMS_ASSERT(client != nullptr); + return clientFromHandle(client)->setIncomingRegTopicsLimit(static_cast(limit)); +} + +unsigned long long cc_mqttsn_##NAME##client_get_incoming_topic_id_storage_limit(CC_MqttsnClientHandle client) +{ + COMMS_ASSERT(client != nullptr); + return static_cast(clientFromHandle(client)->getIncomingRegTopicsLimit()); +} + CC_MqttsnSearchHandle cc_mqttsn_##NAME##client_search_prepare( [[maybe_unused]] CC_MqttsnClientHandle client, [[maybe_unused]] CC_MqttsnErrorCode* ec) diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index 406ac291..de1a0b0b 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -237,10 +237,45 @@ bool cc_mqttsn_##NAME##client_get_verify_incoming_topic_enabled(CC_MqttsnClientH CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_verify_incoming_msg_subscribed(CC_MqttsnClientHandle client, bool enabled); /// @brief Retrieve current incoming message being correctly subscribed control. -/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @return @b true when enabled, @b false when disabled /// @ingroup client -bool cc_mqttsn_##NAME##client_get_verify_incoming_msg_subscribed(CC_MqttsnClientHandle handle); +bool cc_mqttsn_##NAME##client_get_verify_incoming_msg_subscribed(CC_MqttsnClientHandle client); + +/// @brief Specify limit of the topic IDs stored for the outgoing messages +/// @details When performing publish operations with the normal topic string there is a +/// need to be able to store the topic string <-> topic id mapping, which can be used +/// in the future publish operations as well. Use this function to set the runtime limit. +/// When the specified limit reached the least recently used mapping will be dropped to +/// accomodate the new one. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[in] limit Limit for the topic ids stored. @b 0 means previously set limit is removed. i.e reset to default +/// @return Error code of the operation +/// @ingroup client +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_outgoing_topic_id_storage_limit(CC_MqttsnClientHandle client, unsigned long long limit); + +/// @brief Retrieve currently configured limit of the topic IDs stored for the outgoing messages +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @see @ref cc_mqttsn_##NAME##client_set_outgoing_topic_id_storage_limit() +/// @ingroup client +unsigned long long cc_mqttsn_##NAME##client_get_outgoing_topic_id_storage_limit(CC_MqttsnClientHandle client); + +/// @brief Specify limit of the topic IDs stored for the incoming messages +/// @details When receiving messages from the gateway, the latter may register topic IDs. +/// Use this function to set the runtime limit of how many such registration are stored. +/// When the specified limit reached the least recently used mapping will be dropped to +/// accomodate the new one. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[in] limit Limit for the topic ids stored. @b 0 means previously set limit is removed. i.e reset to default +/// @return Error code of the operation +/// @ingroup client +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_incoming_topic_id_storage_limit(CC_MqttsnClientHandle client, unsigned long long limit); + +/// @brief Retrieve currently configured limit of the topic IDs stored for the incoming messages +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @see @ref cc_mqttsn_##NAME##client_set_incoming_topic_id_storage_limit() +/// @ingroup client +unsigned long long cc_mqttsn_##NAME##client_get_incoming_topic_id_storage_limit(CC_MqttsnClientHandle client); /// @brief Prepare "search" operation. /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index e96b72e8..d0fbea15 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -50,6 +50,10 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_get_verify_incoming_topic_enabled != nullptr); test_assert(m_funcs.m_set_verify_incoming_msg_subscribed != nullptr); test_assert(m_funcs.m_get_verify_incoming_msg_subscribed != nullptr); + test_assert(m_funcs.m_set_outgoing_topic_id_storage_limit != nullptr); + test_assert(m_funcs.m_get_outgoing_topic_id_storage_limit != nullptr); + test_assert(m_funcs.m_set_incoming_topic_id_storage_limit != nullptr); + test_assert(m_funcs.m_get_incoming_topic_id_storage_limit != nullptr); test_assert(m_funcs.m_search_prepare != nullptr); test_assert(m_funcs.m_search_set_retry_period != nullptr); test_assert(m_funcs.m_search_get_retry_period != nullptr); @@ -803,6 +807,11 @@ CC_MqttsnErrorCode UnitTestCommonBase::apiSetAvailableGatewayInfo(CC_MqttsnClien return m_funcs.m_set_available_gateway_info(client, info); } +CC_MqttsnErrorCode UnitTestCommonBase::apiSetOutgoingTopicIdStorageLimit(CC_MqttsnClient* client, unsigned long long limit) +{ + return m_funcs.m_set_outgoing_topic_id_storage_limit(client, limit); +} + CC_MqttsnSearchHandle UnitTestCommonBase::apiSearchPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec) { return m_funcs.m_search_prepare(client, ec); diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index d35a6565..63e954c8 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -40,6 +40,10 @@ class UnitTestCommonBase bool (*m_get_verify_incoming_topic_enabled)(CC_MqttsnClientHandle) = nullptr; CC_MqttsnErrorCode (*m_set_verify_incoming_msg_subscribed)(CC_MqttsnClientHandle, bool) = nullptr; bool (*m_get_verify_incoming_msg_subscribed)(CC_MqttsnClientHandle) = nullptr; + CC_MqttsnErrorCode (*m_set_outgoing_topic_id_storage_limit)(CC_MqttsnClientHandle, unsigned long long) = nullptr; + unsigned long long (*m_get_outgoing_topic_id_storage_limit)(CC_MqttsnClientHandle) = nullptr; + CC_MqttsnErrorCode (*m_set_incoming_topic_id_storage_limit)(CC_MqttsnClientHandle, unsigned long long) = nullptr; + unsigned long long (*m_get_incoming_topic_id_storage_limit)(CC_MqttsnClientHandle) = nullptr; CC_MqttsnSearchHandle (*m_search_prepare)(CC_MqttsnClientHandle, CC_MqttsnErrorCode*) = nullptr; CC_MqttsnErrorCode (*m_search_set_retry_period)(CC_MqttsnSearchHandle, unsigned) = nullptr; unsigned (*m_search_get_retry_period)(CC_MqttsnSearchHandle) = nullptr; @@ -403,6 +407,7 @@ class UnitTestCommonBase CC_MqttsnErrorCode apiSetVerifyIncomingMsgSubscribed(CC_MqttsnClient* client, bool enabled); void apiInitGatewayInfo(CC_MqttsnGatewayInfo* info); CC_MqttsnErrorCode apiSetAvailableGatewayInfo(CC_MqttsnClient* client, const CC_MqttsnGatewayInfo* info); + CC_MqttsnErrorCode apiSetOutgoingTopicIdStorageLimit(CC_MqttsnClient* client, unsigned long long limit); CC_MqttsnSearchHandle apiSearchPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec = nullptr); CC_MqttsnErrorCode apiSearchSetRetryPeriod(CC_MqttsnSearchHandle search, unsigned value); diff --git a/client/lib/test/UnitTestDefaultBase.cpp b/client/lib/test/UnitTestDefaultBase.cpp index f7443a68..3a5b58c1 100644 --- a/client/lib/test/UnitTestDefaultBase.cpp +++ b/client/lib/test/UnitTestDefaultBase.cpp @@ -31,6 +31,10 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_get_verify_incoming_topic_enabled = &cc_mqttsn_client_get_verify_incoming_topic_enabled; funcs.m_set_verify_incoming_msg_subscribed = &cc_mqttsn_client_set_verify_incoming_msg_subscribed; funcs.m_get_verify_incoming_msg_subscribed = &cc_mqttsn_client_get_verify_incoming_msg_subscribed; + funcs.m_set_outgoing_topic_id_storage_limit = &cc_mqttsn_client_set_outgoing_topic_id_storage_limit; + funcs.m_get_outgoing_topic_id_storage_limit = &cc_mqttsn_client_get_outgoing_topic_id_storage_limit; + funcs.m_set_incoming_topic_id_storage_limit = &cc_mqttsn_client_set_incoming_topic_id_storage_limit; + funcs.m_get_incoming_topic_id_storage_limit = &cc_mqttsn_client_get_incoming_topic_id_storage_limit; funcs.m_search_prepare = &cc_mqttsn_client_search_prepare; funcs.m_search_set_retry_period = &cc_mqttsn_client_search_set_retry_period; funcs.m_search_get_retry_period = &cc_mqttsn_client_search_get_retry_period; diff --git a/client/lib/test/UnitTestPublish.th b/client/lib/test/UnitTestPublish.th index 39d59bf2..cda97cef 100644 --- a/client/lib/test/UnitTestPublish.th +++ b/client/lib/test/UnitTestPublish.th @@ -28,6 +28,7 @@ public: void test18(); void test19(); void test20(); + void test21(); private: virtual void setUp() override @@ -2155,3 +2156,208 @@ void UnitTestPublish::test20() TS_ASSERT(unitTestHasTickReq()); } + +void UnitTestPublish::test21() +{ + // Test dropping the storage of previously registered topics + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + auto ec = apiSetOutgoingTopicIdStorageLimit(client, 1); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const std::string Topic1("dead"); + const std::string Topic2("beef"); + const UnitTestData Data = {1, 2, 3, 4, 5}; + const CC_MqttsnTopicId TopicId1 = 123; + const CC_MqttsnTopicId TopicId2 = 321; + + CC_MqttsnPublishConfig config1; + apiPublishInitConfig(&config1); + + config1.m_topic = Topic1.c_str(); + config1.m_data = Data.data(); + config1.m_dataLen = static_cast(Data.size()); + + CC_MqttsnPublishConfig config2; + apiPublishInitConfig(&config2); + + config2.m_topic = Topic2.c_str(); + config2.m_data = Data.data(); + config2.m_dataLen = static_cast(Data.size()); + + { + auto publish = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish, nullptr); + + ec = apiPublishConfig(publish, &config1); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + } + + unsigned regMsgId1 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* registerMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(registerMsg, nullptr); + TS_ASSERT_EQUALS(registerMsg->field_topicId().value(), 0U); + TS_ASSERT_EQUALS(registerMsg->field_topicName().value(), Topic1); + TS_ASSERT(!unitTestHasOutputData()); + + regMsgId1 = registerMsg->field_msgId().value(); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestRegackMsg regackMsg; + regackMsg.field_msgId().setValue(regMsgId1); + regackMsg.field_topicId().setValue(TopicId1); + regackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, regackMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId1); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + TS_ASSERT(!unitTestHasOutputData()); + } + + { + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + auto publish = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish, nullptr); + + ec = apiPublishConfig(publish, &config2); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + } + + unsigned regMsgId2 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* registerMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(registerMsg, nullptr); + TS_ASSERT_EQUALS(registerMsg->field_topicId().value(), 0U); + TS_ASSERT_EQUALS(registerMsg->field_topicName().value(), Topic2); + TS_ASSERT(!unitTestHasOutputData()); + + regMsgId2 = registerMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(regMsgId2, regMsgId1); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestRegackMsg regackMsg; + regackMsg.field_msgId().setValue(regMsgId2); + regackMsg.field_topicId().setValue(TopicId2); + regackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, regackMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId2); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + TS_ASSERT(!unitTestHasOutputData()); + } + + { + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + } + + + // Repeating Topic1, the previous registration is expected to be invalidated + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + auto publish = apiPublishPrepare(client); + TS_ASSERT_DIFFERS(publish, nullptr); + + ec = apiPublishConfig(publish, &config1); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + } + + unsigned regMsgId3 = 0U; + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* registerMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(registerMsg, nullptr); + TS_ASSERT_EQUALS(registerMsg->field_topicId().value(), 0U); + TS_ASSERT_EQUALS(registerMsg->field_topicName().value(), Topic1); + TS_ASSERT(!unitTestHasOutputData()); + + regMsgId3 = registerMsg->field_msgId().value(); + TS_ASSERT_DIFFERS(regMsgId3, regMsgId2); + TS_ASSERT_DIFFERS(regMsgId3, regMsgId1); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestRegackMsg regackMsg; + regackMsg.field_msgId().setValue(regMsgId3); + regackMsg.field_topicId().setValue(TopicId1); + regackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, regackMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT_EQUALS(publishMsg->field_flags().field_topicIdType().value(), TopicIdType::Normal); + TS_ASSERT_EQUALS(publishMsg->field_topicId().value(), TopicId1); + TS_ASSERT_EQUALS(publishMsg->field_data().value(), Data); + TS_ASSERT(!unitTestHasOutputData()); + } + + { + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto publishReport = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(publishReport->m_status, CC_MqttsnAsyncOpStatus_Complete); + } +} From 3ac519ead252f336542babefd24d068426f72531 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 1 Aug 2024 09:20:08 +1000 Subject: [PATCH 082/106] Incoming registration support in the new client. --- client/lib/src/ClientImpl.cpp | 87 ++++++++++++++++++++++++++- client/lib/src/ClientImpl.h | 2 + client/lib/src/op/Op.cpp | 91 +---------------------------- client/lib/src/op/Op.h | 5 +- client/lib/src/op/SubscribeOp.cpp | 2 +- client/lib/src/op/UnsubscribeOp.cpp | 2 +- 6 files changed, 94 insertions(+), 95 deletions(-) diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 41f1013d..6221dfc7 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -51,6 +51,27 @@ void updateEc(CC_MqttsnErrorCode* ec, CC_MqttsnErrorCode val) } } +InRegTopicsMap::iterator findInRegTopicInfo(CC_MqttsnTopicId topicId, InRegTopicsMap& map) +{ + return + std::lower_bound( + map.begin(), map.end(), topicId, + [](auto& info, CC_MqttsnTopicId topicIdParam) { + return info.m_topicId < topicIdParam; + }); +} + +InRegTopicsMap::iterator findInRegTopicInfo(const char* topic, InRegTopicsMap& map) +{ + return + std::find_if( + map.begin(), map.end(), + [topic](auto& info) + { + return info.m_topic == topic; + }); +} + } // namespace ClientImpl::ClientImpl() : @@ -749,8 +770,12 @@ void ClientImpl::handle(GwinfoMsg& msg) void ClientImpl::handle(RegisterMsg& msg) { - static_cast(msg); - // TODO: + storeInRegTopic(msg.field_topicName().value().c_str(), msg.field_topicId().value()); + RegackMsg resp; + resp.field_topicId().setValue(msg.field_topicId().value()); + resp.field_msgId().setValue(msg.field_msgId().value()); + resp.field_returnCode().value() = RegackMsg::Field_returnCode::ValueType::Accepted; + sendMessage(resp); } // void ClientImpl::handle(PublishMsg& msg) @@ -1024,6 +1049,64 @@ void ClientImpl::allowNextPrepare() m_preparationLocked = false; } +void ClientImpl::storeInRegTopic(const char* topic, CC_MqttsnTopicId topicId) +{ + auto& map = m_reuseState.m_inRegTopics; + auto iter = findInRegTopicInfo(topicId, map); + if ((iter != map.end()) && (iter->m_topicId == topicId)) { + iter->m_topic = topic; + iter->m_timestamp = m_clientState.m_timestamp; + return; + } + + if (m_clientState.m_inRegTopicsLimit <= map.size()) { + auto eraseIter = + std::min_element( + map.begin(), map.end(), + [](auto& info1, auto& info2) + { + return info1.m_timestamp < info2.m_timestamp; + }); + + COMMS_ASSERT(eraseIter != map.end()); + map.erase(eraseIter); + iter = findInRegTopicInfo(topicId, map); // The location can change after erase + } + + if (topic == nullptr) { + map.insert(iter, FullRegTopicInfo{m_clientState.m_timestamp, TopicNameStr(), topicId}); + return; + } + + map.insert(iter, FullRegTopicInfo{m_clientState.m_timestamp, topic, topicId}); +} + +bool ClientImpl::removeInRegTopic(const char* topic, CC_MqttsnTopicId topicId) +{ + auto& map = m_reuseState.m_inRegTopics; + if (op::Op::isValidTopicId(topicId)) { + auto iter = findInRegTopicInfo(topicId, map); + if ((iter != map.end()) && (iter->m_topicId == topicId)) { + map.erase(iter); + return true; + } + + return false; + } + + if (topic == nullptr) { + return false; + } + + auto iter = findInRegTopicInfo(topic, map); + if (iter == map.end()) { + return false; + } + + map.erase(iter); + return true; +} + void ClientImpl::doApiEnter() { ++m_apiEnterCount; diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index 0a72c348..88f468b0 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -168,6 +168,8 @@ class ClientImpl final : public ProtMsgHandler // bool hasPausedSendsBefore(const op::SendOp* sendOp) const; // bool hasHigherQosSendsBefore(const op::SendOp* sendOp, op::Op::Qos qos) const; void allowNextPrepare(); + void storeInRegTopic(const char* topic, CC_MqttsnTopicId topicId); + bool removeInRegTopic(const char* topic, CC_MqttsnTopicId topicId); TimerMgr& timerMgr() { diff --git a/client/lib/src/op/Op.cpp b/client/lib/src/op/Op.cpp index a98841b3..4fbafb15 100644 --- a/client/lib/src/op/Op.cpp +++ b/client/lib/src/op/Op.cpp @@ -29,81 +29,13 @@ static constexpr char TopicSep = '/'; static constexpr char MultLevelWildcard = '#'; static constexpr char SingleLevelWildcard = '+'; -bool isValidTopicIdInternal(CC_MqttsnTopicId id) -{ - return (id != 0U) && (id != 0xffff); -} - -template -typename TMap::iterator findRegTopicInfo(CC_MqttsnTopicId topicId, TMap& map) -{ - return - std::lower_bound( - map.begin(), map.end(), topicId, - [](auto& info, CC_MqttsnTopicId topicIdParam) { - return info.m_topicId < topicIdParam; - }); -} - -template -typename TMap::iterator findRegTopicInfo(const char* topic, TMap& map) -{ - return - std::find_if( - map.begin(), map.end(), - [topic](auto& info) - { - return info.m_topic == topic; - }); -} - -template -void storeFullRegTopic(ClientState::Timestamp timestamp, const char* topic, CC_MqttsnTopicId topicId, TMap& map) -{ - auto iter = findRegTopicInfo(topicId, map); - if ((iter != map.end()) && (iter->m_topicId == topicId)) { - iter->m_topic = topic; - return; - } - - if (topic == nullptr) { - map.insert(iter, FullRegTopicInfo{timestamp, TopicNameStr(), topicId}); - return; - } - - map.insert(iter, FullRegTopicInfo{timestamp, topic, topicId}); -} +} // namespace -template -bool removeFullRegTopic(const char* topic, CC_MqttsnTopicId topicId, TMap& map) +bool Op::isValidTopicId(CC_MqttsnTopicId id) { - if (isValidTopicIdInternal(topicId)) { - auto iter = findRegTopicInfo(topicId, map); - if ((iter != map.end()) && (iter->m_topicId == topicId)) { - map.erase(iter); - return true; - } - - return false; - } - - if (topic == nullptr) { - return false; - } - - auto iter = findRegTopicInfo(topic, map); - if (iter == map.end()) { - return false; - } - - map.erase(iter); - return true; + return (id != 0U) && (id != 0xffff); } -} // namespace - - - Op::Op(ClientImpl& client) : m_client(client), m_retryPeriod(client.configState().m_retryPeriod), @@ -217,23 +149,6 @@ void Op::decRetryCount() --m_retryCount; } -void Op::storeInRegTopic(const char* topic, CC_MqttsnTopicId topicId) -{ - auto& map = m_client.reuseState().m_inRegTopics; - storeFullRegTopic(m_client.clientState().m_timestamp, topic, topicId, map); -} - -bool Op::removeInRegTopic(const char* topic, CC_MqttsnTopicId topicId) -{ - auto& map = m_client.reuseState().m_inRegTopics; - return removeFullRegTopic(topic, topicId, map); -} - -bool Op::isValidTopicId(CC_MqttsnTopicId id) -{ - return isValidTopicIdInternal(id); -} - bool Op::isShortTopic(const char* topic) { COMMS_ASSERT(topic != nullptr); diff --git a/client/lib/src/op/Op.h b/client/lib/src/op/Op.h index 44c07969..07599e26 100644 --- a/client/lib/src/op/Op.h +++ b/client/lib/src/op/Op.h @@ -86,6 +86,8 @@ class Op : public ProtMsgHandler return m_client; } + static bool isValidTopicId(CC_MqttsnTopicId id); + protected: explicit Op(ClientImpl& client); @@ -98,10 +100,7 @@ class Op : public ProtMsgHandler std::uint16_t allocPacketId(); void releasePacketId(std::uint16_t id); void decRetryCount(); - void storeInRegTopic(const char* topic, CC_MqttsnTopicId topicId); - bool removeInRegTopic(const char* topic, CC_MqttsnTopicId topicId); - static bool isValidTopicId(CC_MqttsnTopicId id); static bool isShortTopic(const char* topic); const ClientImpl& client() const diff --git a/client/lib/src/op/SubscribeOp.cpp b/client/lib/src/op/SubscribeOp.cpp index 0220c405..2271c08d 100644 --- a/client/lib/src/op/SubscribeOp.cpp +++ b/client/lib/src/op/SubscribeOp.cpp @@ -178,7 +178,7 @@ void SubscribeOp::handle(SubackMsg& msg) } while (false); if (topicId != 0U) { - storeInRegTopic(topicPtr, topicId); + client().storeInRegTopic(topicPtr, topicId); } auto& filtersMap = client().reuseState().m_subFilters; diff --git a/client/lib/src/op/UnsubscribeOp.cpp b/client/lib/src/op/UnsubscribeOp.cpp index 28683376..88e617d3 100644 --- a/client/lib/src/op/UnsubscribeOp.cpp +++ b/client/lib/src/op/UnsubscribeOp.cpp @@ -176,7 +176,7 @@ CC_MqttsnErrorCode UnsubscribeOp::send(CC_MqttsnUnsubscribeCompleteCb cb, void* COMMS_ASSERT((topicPtr == nullptr) || (topicId == 0U)); COMMS_ASSERT((topicPtr != nullptr) || (topicId != 0U)); - removeInRegTopic(topicPtr, topicId); + client().removeInRegTopic(topicPtr, topicId); if constexpr (Config::HasSubTopicVerification) { do { From 99d73ecc9559d14ebf7508bd8f5bc649888c3366 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Mon, 5 Aug 2024 09:16:11 +1000 Subject: [PATCH 083/106] Preliminary support for client messages reception. --- client/lib/include/cc_mqttsn_client/common.h | 12 +- client/lib/src/ClientImpl.cpp | 354 +++++++++++++++---- client/lib/src/ClientImpl.h | 17 + client/lib/src/ProtocolDefs.h | 4 +- client/lib/src/SessionState.h | 1 + client/lib/src/op/ConnectOp.cpp | 6 + client/lib/src/op/Op.cpp | 48 --- client/lib/src/op/Op.h | 11 - client/lib/src/op/SendOp.cpp | 2 +- client/lib/test/CMakeLists.txt | 2 +- client/lib/test/UnitTestCommonBase.cpp | 39 +- client/lib/test/UnitTestCommonBase.h | 20 ++ client/lib/test/UnitTestReceive.th | 213 +++++++++++ 13 files changed, 584 insertions(+), 145 deletions(-) create mode 100644 client/lib/test/UnitTestReceive.th diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index 1d2e1a29..b7a57251 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -195,12 +195,12 @@ typedef unsigned short CC_MqttsnTopicId; /// @ingroup global typedef struct { - const char* topic; ///< Topic the message was published with. May be NULL if message is reported with predefined topic ID. - CC_MqttsnTopicId topicId; ///< Predefined topic ID. This data member is used only if topic field has value NULL. - const unsigned char* msg; ///< Pointer to reported message binary data. - unsigned msgLen; ///< Number of bytes in reported message binary data. - CC_MqttsnQoS qos; ///< QoS level the message was received with. - bool retain; ///< Retain flag of the message. + const char* m_topic; ///< Topic the message was published with. May be NULL if message is reported with predefined topic ID. + const unsigned char* m_data; ///< Pointer to reported message binary data. + unsigned m_dataLen; ///< Number of bytes in reported message binary data. + CC_MqttsnQoS m_qos; ///< QoS level the message was received with. + CC_MqttsnTopicId m_topicId; ///< Predefined topic ID. This data member is used only if topic field has value NULL. + bool m_retained; ///< Retain flag of the message. } CC_MqttsnMessageInfo; /// @brief Gateway information diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 6221dfc7..dbf21999 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -23,6 +23,9 @@ namespace cc_mqttsn_client namespace { +static constexpr char MultLevelWildcard = '#'; +static constexpr char SingleLevelWildcard = '+'; + template unsigned eraseFromList(const op::Op* op, TList& list) { @@ -770,83 +773,220 @@ void ClientImpl::handle(GwinfoMsg& msg) void ClientImpl::handle(RegisterMsg& msg) { - storeInRegTopic(msg.field_topicName().value().c_str(), msg.field_topicId().value()); - RegackMsg resp; - resp.field_topicId().setValue(msg.field_topicId().value()); - resp.field_msgId().setValue(msg.field_msgId().value()); - resp.field_returnCode().value() = RegackMsg::Field_returnCode::ValueType::Accepted; - sendMessage(resp); + if (m_sessionState.m_disconnecting) { + return; + } + + for (auto& opPtr : m_keepAliveOps) { + msg.dispatch(*opPtr); + } + + using RetCodeType = RegackMsg::Field_returnCode::ValueType; + auto retCode = RetCodeType::Accepted; + + auto sendRegackOnExit = + comms::util::makeScopeGuard( + [this, &msg, &retCode]() + { + RegackMsg resp; + resp.field_topicId().setValue(msg.field_topicId().value()); + resp.field_msgId().setValue(msg.field_msgId().value()); + resp.field_returnCode().value() = retCode; + sendMessage(resp); + }); + + auto& topic = msg.field_topicName().value(); + if ((topic.empty()) || (!verifyPubTopic(topic.c_str(), false))) { + errorLog("Received PUBLISH with invalid topic format."); + retCode = RetCodeType::NotSupported; + return; // Sends REGACK on exit + } + + storeInRegTopic(topic.c_str(), msg.field_topicId().value()); + return; // Sends REGACK on exit } -// void ClientImpl::handle(PublishMsg& msg) -// { -// if (m_sessionState.m_disconnecting) { -// return; -// } +void ClientImpl::handle(PublishMsg& msg) +{ + if (m_sessionState.m_disconnecting) { + return; + } -// for (auto& opPtr : m_keepAliveOps) { -// msg.dispatch(*opPtr); -// } + for (auto& opPtr : m_keepAliveOps) { + msg.dispatch(*opPtr); + } -// do { -// auto createRecvOp = -// [this, &msg]() -// { -// auto ptr = m_recvOpsAlloc.alloc(*this); -// if (!ptr) { -// errorLog("Failed to allocate handling op for the incoming PUBLISH message, ignoring."); -// return; -// } - -// m_ops.push_back(ptr.get()); -// m_recvOps.push_back(std::move(ptr)); -// msg.dispatch(*m_recvOps.back()); -// }; - -// using Qos = op::Op::Qos; -// auto qos = msg.transportField_flags().field_qos().value(); -// if ((qos == Qos::AtMostOnceDelivery) || -// (qos == Qos::AtLeastOnceDelivery)) { -// createRecvOp(); -// break; -// } + using ReturnCode = PubackMsg::Field_returnCode::ValueType; + auto sendPuback = + [this, &msg](ReturnCode retCode) + { + PubackMsg pubackMsg; + pubackMsg.field_topicId().value() = msg.field_topicId().value(); + pubackMsg.field_msgId().value() = msg.field_msgId().value(); + pubackMsg.field_returnCode().value() = retCode; + auto ec = sendMessage(pubackMsg); + + if (ec != CC_MqttsnErrorCode_Success) { + errorLog("Failed to send PUBACK in response to PUBLISH"); + return; + } + }; -// if constexpr (Config::MaxQos >= 2) { -// auto iter = -// std::find_if( -// m_recvOps.begin(), m_recvOps.end(), -// [&msg](auto& opPtr) -// { -// return opPtr->packetId() == msg.field_packetId().field().value(); -// }); - -// if (iter == m_recvOps.end()) { -// createRecvOp(); -// break; -// } - -// PubrecMsg pubrecMsg; -// pubrecMsg.field_packetId().setValue(msg.field_packetId().field().value()); - -// if (!msg.transportField_flags().field_dup().getBitValue_bit()) { -// errorLog("Non duplicate PUBLISH with packet ID in use"); -// gatewayDisconnected(CC_MqttsnGatewayDisconnectReason_ProtocolError); -// return; -// } -// else { -// // Duplicate detected, just re-confirming -// (*iter)->resetTimer(); -// } - -// sendMessage(pubrecMsg); -// return; -// } -// else { -// createRecvOp(); -// break; -// } -// } while (false); -// } + auto qos = msg.field_flags().field_qos().value(); + if (Config::MaxQos < static_cast(qos)) { + sendPuback(ReturnCode::NotSupported); + return; + } + + char shortTopicName[3] = {0}; + const char* topic = nullptr; + auto topicIdType = msg.field_flags().field_topicIdType().value(); + CC_MqttsnTopicId topicId = msg.field_topicId().value(); + using TopicIdType = std::decay_t; + + if (topicIdType == TopicIdType::Normal) { + auto& regMap = m_reuseState.m_inRegTopics; + auto iter = findInRegTopicInfo(topicId, regMap); + if (iter == regMap.end()) { + sendPuback(ReturnCode::InvalidTopicId); + return; + } + + COMMS_ASSERT(!iter->m_topic.empty()); + topic = iter->m_topic.c_str(); + } + + if (topicIdType == TopicIdType::ShortTopicName) { + shortTopicName[0] = static_cast((topicId >> 8U) & 0xff); + shortTopicName[1] = static_cast(topicId & 0xff); + topic = &shortTopicName[0]; + } + + if constexpr (Config::HasSubTopicVerification) { + do { + if (!m_configState.m_verifySubFilter) { + break; + } + + auto& subFilters = m_reuseState.m_subFilters; + if (topicIdType == TopicIdType::PredefinedTopicId) { + auto iter = + std::find_if( + subFilters.begin(), subFilters.end(), + [topicId](const auto& info) + { + return + (info.m_topicId == topicId) && + (info.m_topic.empty()); + }); + + if (iter == subFilters.end()) { + errorLog("Received PUBLISH on non-subscribed pre-defined topic ID"); + return; + } + + // Topic ID is subscribed + break; + } + + if (topic == nullptr) { + errorLog("Cannot determing PUBLISH topic"); + return; + } + + auto iter = + std::lower_bound( + subFilters.begin(), subFilters.end(), topic, + [](const auto& info, const char* topicParam) + { + return info.m_topic < topicParam; + }); + + if (iter == subFilters.end()) { + errorLog("Received PUBLISH on non-subscribed topic"); + return; + } + + } while (false); + } + + auto reportMsgOnExit = + comms::util::makeScopeGuard( + [this, &msg, topic, topicId]() + { + auto& dataVec = msg.field_data().value(); + + auto info = CC_MqttsnMessageInfo(); + info.m_topic = topic; + info.m_data = dataVec.data(); + comms::cast_assign(info.m_dataLen) = dataVec.size(); + comms::cast_assign(info.m_qos) = msg.field_flags().field_qos().value(); + info.m_retained = msg.field_flags().field_mid().getBitValue_Retain(); + + if (topic == nullptr) { + info.m_topicId = topicId; + } + + COMMS_ASSERT(m_messageReceivedReportCb != nullptr); + m_messageReceivedReportCb(m_messageReceivedReportData, &info); + }); + + if (qos == op::Op::Qos::AtMostOnceDelivery) { + return; + } + + if constexpr (1U <= Config::MaxQos) { + if (qos == op::Op::Qos::AtLeastOnceDelivery) { + m_sessionState.m_lastRecvMsgId = 0U; + sendPuback(ReturnCode::Accepted); + return; + } + } + + if constexpr (2U <= Config::MaxQos) { + + auto msgId = msg.field_msgId().value(); + auto sendPubrec = + [this, msgId]() + { + PubrecMsg pubrecMsg; + pubrecMsg.field_msgId().setValue(msgId); + auto ec = sendMessage(pubrecMsg); + if (ec != CC_MqttsnErrorCode_Success) { + errorLog("Failed to send PUBREC message"); + } + }; + + do { + if (m_sessionState.m_lastRecvMsgId == 0U) { + break; + } + + if (m_sessionState.m_lastRecvMsgId != msgId) { + errorLog("Previous Qos2 message reception wasn't completed properly."); + break; + } + + if (!msg.field_flags().field_high().getBitValue_Dup()) { + errorLog("Repeated PUBLISH without DUP flag, ignoring."); + reportMsgOnExit.release(); + return; + } + + sendPubrec(); + reportMsgOnExit.release(); + return; + } while (false); + + m_sessionState.m_lastRecvMsgId = msgId; + sendPubrec(); + return; + } + + // Not expected to reach this point + COMMS_ASSERT(false); + reportMsgOnExit.release(); +} void ClientImpl::handle(PubackMsg& msg) { @@ -892,6 +1032,28 @@ void ClientImpl::handle(PubackMsg& msg) } } +#if CC_MQTTSN_CLIENT_MAX_QOS >= 2 +void ClientImpl::handle(PubrelMsg& msg) +{ + auto msgId = msg.field_msgId().value(); + if (m_sessionState.m_lastRecvMsgId == msgId) { + // Expected completion + m_sessionState.m_lastRecvMsgId = 0U; + } + else if (m_sessionState.m_lastRecvMsgId != 0U) { + // Previous Qos2 reception is incomplete while unexpected PUBREL arrives + errorLog("Unexpected PUBREL message received"); + } + + PubcompMsg pubcompMsg; + pubcompMsg.field_msgId().value() = msgId; + auto ec = sendMessage(pubcompMsg); + if (ec != CC_MqttsnErrorCode_Success) { + errorLog("Failed to send PUBCOMP message"); + } +} +#endif // #if CC_MQTTSN_CLIENT_MAX_QOS >= 2 + void ClientImpl::handle([[maybe_unused]] PingreqMsg& msg) { if ((m_sessionState.m_disconnecting) || (!m_sessionState.m_connected)) { @@ -1239,6 +1401,54 @@ CC_MqttsnErrorCode ClientImpl::initInternal() return CC_MqttsnErrorCode_Success; } +bool ClientImpl::verifyPubTopicInternal(const char* topic, bool outgoing) +{ + if (Config::HasTopicFormatVerification) { + if (outgoing && (!m_configState.m_verifyOutgoingTopic)) { + return true; + } + + if ((!outgoing) && (!m_configState.m_verifyIncomingTopic)) { + return true; + } + + COMMS_ASSERT(topic != nullptr); + if (topic[0] == '\0') { + return false; + } + + // if (outgoing && (topic[0] == '$')) { + // errorLog("Cannot start topic with \'$\'."); + // return false; + // } + + auto pos = 0U; + while (topic[pos] != '\0') { + auto incPosGuard = + comms::util::makeScopeGuard( + [&pos]() + { + ++pos; + }); + + auto ch = topic[pos]; + + if ((ch == MultLevelWildcard) || + (ch == SingleLevelWildcard)) { + errorLog("Wildcards cannot be used in publish topic"); + return false; + } + } + + return true; + } + else { + [[maybe_unused]] static constexpr bool ShouldNotBeCalled = false; + COMMS_ASSERT(ShouldNotBeCalled); + return false; + } +} + // void ClientImpl::resumeSendOpsSince(unsigned idx) // { // while (idx < m_sendOps.size()) { diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index 88f468b0..ba082c88 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -151,7 +151,13 @@ class ClientImpl final : public ProtMsgHandler // virtual void handle(PublishMsg& msg) override; virtual void handle(RegisterMsg& msg) override; + virtual void handle(PublishMsg& msg) override; virtual void handle(PubackMsg& msg) override; + +#if CC_MQTTSN_CLIENT_MAX_QOS >= 2 + virtual void handle(PubrelMsg& msg) override; +#endif // #if CC_MQTTSN_CLIENT_MAX_QOS >= 2 + virtual void handle(PingreqMsg& msg) override; virtual void handle(DisconnectMsg& msg) override; virtual void handle(ProtMessage& msg) override; @@ -218,6 +224,16 @@ class ClientImpl final : public ProtMsgHandler } } + inline bool verifyPubTopic(const char* topic, bool outgoing) + { + if (Config::HasTopicFormatVerification) { + return verifyPubTopicInternal(topic, outgoing); + } + else { + return true; + } + } + // std::size_t recvsCount() const // { // return m_recvOps.size(); @@ -267,6 +283,7 @@ class ClientImpl final : public ProtMsgHandler void cleanOps(); void errorLogInternal(const char* msg); CC_MqttsnErrorCode initInternal(); + bool verifyPubTopicInternal(const char* topic, bool outgoing); // void resumeSendOpsSince(unsigned idx); // op::SendOp* findSendOp(std::uint16_t packetId); // bool isLegitSendAck(const op::SendOp* sendOp, bool pubcompAck = false) const; diff --git a/client/lib/src/ProtocolDefs.h b/client/lib/src/ProtocolDefs.h index 179e5bf9..7fbd4584 100644 --- a/client/lib/src/ProtocolDefs.h +++ b/client/lib/src/ProtocolDefs.h @@ -54,11 +54,11 @@ using ProtInputMessages = cc_mqttsn::message::Regack, cc_mqttsn::message::Publish, cc_mqttsn::message::Puback, -#if CC_MQTTSN_CLIENT_MAX_QOS > 1 +#if CC_MQTTSN_CLIENT_MAX_QOS >= 2 cc_mqttsn::message::Pubcomp, cc_mqttsn::message::Pubrec, cc_mqttsn::message::Pubrel, -#endif // #if CC_MQTTSN_CLIENT_MAX_QOS > 1 +#endif // #if CC_MQTTSN_CLIENT_MAX_QOS >= 2 cc_mqttsn::message::Suback, cc_mqttsn::message::Unsuback, cc_mqttsn::message::Pingreq, diff --git a/client/lib/src/SessionState.h b/client/lib/src/SessionState.h index 2c3f55bc..69efbc98 100644 --- a/client/lib/src/SessionState.h +++ b/client/lib/src/SessionState.h @@ -15,6 +15,7 @@ struct SessionState static constexpr unsigned DefaultKeepAlive = 60; unsigned m_keepAliveMs = 0U; + std::uint16_t m_lastRecvMsgId = 0U; bool m_connected = false; bool m_disconnecting = false; }; diff --git a/client/lib/src/op/ConnectOp.cpp b/client/lib/src/op/ConnectOp.cpp index ac967fe2..d1e23af0 100644 --- a/client/lib/src/op/ConnectOp.cpp +++ b/client/lib/src/op/ConnectOp.cpp @@ -129,6 +129,12 @@ CC_MqttsnErrorCode ConnectOp::send(CC_MqttsnConnectCompleteCb cb, void* cbData) return CC_MqttsnErrorCode_InsufficientConfig; } + if ((!m_connectMsg.field_flags().field_mid().getBitValue_CleanSession()) && + (client().clientState().m_firstConnect)) { + errorLog("Clean session flag needs to be set on the first connection attempt, perform configuration first."); + return CC_MqttsnErrorCode_InsufficientConfig; + } + if (!m_timer.isValid()) { errorLog("The library cannot allocate required number of timers."); return CC_MqttsnErrorCode_InternalError; diff --git a/client/lib/src/op/Op.cpp b/client/lib/src/op/Op.cpp index 4fbafb15..a288a17e 100644 --- a/client/lib/src/op/Op.cpp +++ b/client/lib/src/op/Op.cpp @@ -245,54 +245,6 @@ bool Op::verifySubFilterInternal(const char* filter) } } -bool Op::verifyPubTopicInternal(const char* topic, bool outgoing) -{ - if (Config::HasTopicFormatVerification) { - if (outgoing && (!m_client.configState().m_verifyOutgoingTopic)) { - return true; - } - - if ((!outgoing) && (!m_client.configState().m_verifyIncomingTopic)) { - return true; - } - - COMMS_ASSERT(topic != nullptr); - if (topic[0] == '\0') { - return false; - } - - if (outgoing && (topic[0] == '$')) { - errorLog("Cannot start topic with \'$\'."); - return false; - } - - auto pos = 0U; - while (topic[pos] != '\0') { - auto incPosGuard = - comms::util::makeScopeGuard( - [&pos]() - { - ++pos; - }); - - auto ch = topic[pos]; - - if ((ch == MultLevelWildcard) || - (ch == SingleLevelWildcard)) { - errorLog("Wildcards cannot be used in publish topic"); - return false; - } - } - - return true; - } - else { - [[maybe_unused]] static constexpr bool ShouldNotBeCalled = false; - COMMS_ASSERT(ShouldNotBeCalled); - return false; - } -} - } // namespace op } // namespace cc_mqttsn_client diff --git a/client/lib/src/op/Op.h b/client/lib/src/op/Op.h index 07599e26..d36b0c43 100644 --- a/client/lib/src/op/Op.h +++ b/client/lib/src/op/Op.h @@ -125,16 +125,6 @@ class Op : public ProtMsgHandler } } - inline bool verifyPubTopic(const char* topic, bool outgoing) - { - if (Config::HasTopicFormatVerification) { - return verifyPubTopicInternal(topic, outgoing); - } - else { - return true; - } - } - static constexpr std::size_t maxStringLen() { return std::numeric_limits::max(); @@ -143,7 +133,6 @@ class Op : public ProtMsgHandler private: void errorLogInternal(const char* msg); bool verifySubFilterInternal(const char* filter); - bool verifyPubTopicInternal(const char* topic, bool outgoing); ClientImpl& m_client; unsigned m_retryPeriod = 0U; diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index f2ef9b9e..b320769b 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -74,7 +74,7 @@ CC_MqttsnErrorCode SendOp::config(const CC_MqttsnPublishConfig* config) return CC_MqttsnErrorCode_BadParam; } - if ((!emptyTopic) && (!verifyPubTopic(config->m_topic, true))) { + if ((!emptyTopic) && (!client().verifyPubTopic(config->m_topic, true))) { errorLog("Bad topic filter format in publish."); return CC_MqttsnErrorCode_BadParam; } diff --git a/client/lib/test/CMakeLists.txt b/client/lib/test/CMakeLists.txt index a097301b..fc267a6b 100644 --- a/client/lib/test/CMakeLists.txt +++ b/client/lib/test/CMakeLists.txt @@ -43,7 +43,7 @@ if (TARGET cc::cc_mqttsn_client) cc_mqttsn_client_add_unit_test(UnitTestConnect ${DEFAULT_BASE_LIB_NAME}) cc_mqttsn_client_add_unit_test(UnitTestDisconnect ${DEFAULT_BASE_LIB_NAME}) cc_mqttsn_client_add_unit_test(UnitTestPublish ${DEFAULT_BASE_LIB_NAME}) -# cc_mqttsn_client_add_unit_test(UnitTestReceive ${DEFAULT_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(UnitTestReceive ${DEFAULT_BASE_LIB_NAME}) cc_mqttsn_client_add_unit_test(UnitTestSubscribe ${DEFAULT_BASE_LIB_NAME}) cc_mqttsn_client_add_unit_test(UnitTestUnsubscribe ${DEFAULT_BASE_LIB_NAME}) cc_mqttsn_client_add_unit_test(UnitTestWill ${DEFAULT_BASE_LIB_NAME}) diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index d0fbea15..fb449074 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -242,6 +242,21 @@ UnitTestCommonBase::UnitTestWillCompleteReport::UnitTestWillCompleteReport(CC_Mq } } +UnitTestCommonBase::UnitTestMessageInfo::UnitTestMessageInfo(const CC_MqttsnMessageInfo& info) : + m_qos(info.m_qos), + m_topicId(info.m_topicId), + m_retained(info.m_retained) +{ + if (info.m_topic != nullptr) { + m_topic = info.m_topic; + } + + if (info.m_dataLen > 0U) { + test_assert(info.m_data != nullptr); + m_data.assign(info.m_data, info.m_data + info.m_dataLen); + } +} + void UnitTestCommonBase::unitTestSetUp() { } @@ -777,6 +792,23 @@ CC_MqttsnErrorCode UnitTestCommonBase::unitTestWillSend(CC_MqttsnWillHandle will return m_funcs.m_will_send(will, &UnitTestCommonBase::unitTestWillCompleteCb, this); } +bool UnitTestCommonBase::unitTestHasReceivedMessage() const +{ + return !m_data.m_recvMsgs.empty(); +} + +UnitTestCommonBase::UnitTestMessageInfoPtr UnitTestCommonBase::unitTestReceivedMessage(bool mustExist) +{ + if (!unitTestHasReceivedMessage()) { + test_assert(!mustExist); + return UnitTestMessageInfoPtr(); + } + + auto ptr = std::move(m_data.m_recvMsgs.front()); + m_data.m_recvMsgs.pop_front(); + return ptr; +} + void UnitTestCommonBase::apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen) { m_funcs.m_process_data(client, buf, bufLen); @@ -1017,10 +1049,9 @@ CC_MqttsnErrorCode UnitTestCommonBase::apiWillCancel(CC_MqttsnWillHandle will) void UnitTestCommonBase::unitTestMessageReportCb(void* data, const CC_MqttsnMessageInfo* msgInfo) { - // TODO: - static_cast(data); - static_cast(msgInfo); - test_assert(false); + test_assert(msgInfo != nullptr); + auto* thisPtr = asThis(data); + thisPtr->m_data.m_recvMsgs.push_back(std::make_unique(*msgInfo)); } unsigned UnitTestCommonBase::unitTestGwinfoDelayRequestCb(void* data) diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index 63e954c8..645cac50 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -330,6 +330,22 @@ class UnitTestCommonBase using UnitTestWillCompleteReportPtr = std::unique_ptr; using UnitTestWillCompleteReportList = std::list; + + struct UnitTestMessageInfo + { + std::string m_topic; + UnitTestData m_data; + CC_MqttsnQoS m_qos = CC_MqttsnQoS_AtMostOnceDelivery; + CC_MqttsnTopicId m_topicId = 0U; + bool m_retained = false; ///< Retain flag of the message. + + UnitTestMessageInfo() = default; + explicit UnitTestMessageInfo(const CC_MqttsnMessageInfo& info); + }; + + using UnitTestMessageInfoPtr = std::unique_ptr; + using UnitTestMessageInfosList = std::list; + using UnitTestClientPtr = std::unique_ptr; void unitTestSetUp(); @@ -401,6 +417,9 @@ class UnitTestCommonBase CC_MqttsnErrorCode unitTestWillSend(CC_MqttsnWillHandle will); + bool unitTestHasReceivedMessage() const; + UnitTestMessageInfoPtr unitTestReceivedMessage(bool mustExist = true); + void apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen); CC_MqttsnErrorCode apiSetDefaultRetryPeriod(CC_MqttsnClient* client, unsigned value); CC_MqttsnErrorCode apiSetDefaultRetryCount(CC_MqttsnClient* client, unsigned value); @@ -469,6 +488,7 @@ class UnitTestCommonBase UnitTestUnsubscribeCompleteReportList m_unsubscribeCompleteReports; UnitTestPublishCompleteReportList m_publishCompleteReports; UnitTestWillCompleteReportList m_willCompleteReports; + UnitTestMessageInfosList m_recvMsgs; }; static void unitTestTickProgramCb(void* data, unsigned duration); diff --git a/client/lib/test/UnitTestReceive.th b/client/lib/test/UnitTestReceive.th new file mode 100644 index 00000000..d9e290cd --- /dev/null +++ b/client/lib/test/UnitTestReceive.th @@ -0,0 +1,213 @@ +#include "UnitTestDefaultBase.h" +#include "UnitTestProtocolDefs.h" + +#include "comms/units.h" + +#include + +class UnitTestReceive : public CxxTest::TestSuite, public UnitTestDefaultBase +{ +public: + void test1(); + void test2(); + void test3(); + +private: + virtual void setUp() override + { + unitTestSetUp(); + } + + virtual void tearDown() override + { + unitTestTearDown(); + } + + using Qos = UnitTestPublishMsg::Field_flags::Field_qos::ValueType; + using TopicIdType = UnitTestPublishMsg::Field_flags::Field_topicIdType::ValueType; + using RetCode = UnitTestPubackMsg::Field_returnCode::ValueType; +}; + +void UnitTestReceive::test1() +{ + // Testing Qos0 reception of predefined topicId + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtMostOnceDelivery; + const CC_MqttsnTopicId TopicId = 123; + const bool Retained = true; + const UnitTestData Data = {1, 2, 3, 4}; + + unitTestDoSubscribeTopicId(client, TopicId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + UnitTestPublishMsg publishMsg; + publishMsg.field_flags().field_qos().setValue(Qos); + publishMsg.field_flags().field_mid().setBitValue_Retain(Retained); + publishMsg.field_flags().field_topicIdType().value() = TopicIdType::PredefinedTopicId; + publishMsg.field_topicId().setValue(TopicId); + publishMsg.field_data().value() = Data; + + unitTestClientInputMessage(client, publishMsg); + } + + { + TS_ASSERT(unitTestHasReceivedMessage()); + auto msgInfo = unitTestReceivedMessage(); + TS_ASSERT(msgInfo->m_topic.empty()); + TS_ASSERT_EQUALS(msgInfo->m_topicId, TopicId); + TS_ASSERT_EQUALS(msgInfo->m_data, Data); + TS_ASSERT_EQUALS(msgInfo->m_qos, Qos); + TS_ASSERT_EQUALS(msgInfo->m_retained, Retained); + TS_ASSERT(!unitTestHasReceivedMessage()); + } + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestReceive::test2() +{ + // Testing Qos1 reception of predefined topicId + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; + const CC_MqttsnTopicId TopicId = 123; + const bool Retained = true; + const UnitTestData Data = {1, 2, 3, 4}; + const std::uint16_t MsgId = 1; + + unitTestDoSubscribeTopicId(client, TopicId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + UnitTestPublishMsg publishMsg; + publishMsg.field_flags().field_qos().setValue(Qos); + publishMsg.field_flags().field_mid().setBitValue_Retain(Retained); + publishMsg.field_flags().field_topicIdType().value() = TopicIdType::PredefinedTopicId; + publishMsg.field_topicId().setValue(TopicId); + publishMsg.field_msgId().setValue(MsgId); + publishMsg.field_data().value() = Data; + + unitTestClientInputMessage(client, publishMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pubackMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubackMsg, nullptr); + TS_ASSERT_EQUALS(pubackMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(pubackMsg->field_msgId().value(), MsgId); + TS_ASSERT_EQUALS(pubackMsg->field_returnCode().value(), RetCode::Accepted); + } + + { + TS_ASSERT(unitTestHasReceivedMessage()); + auto msgInfo = unitTestReceivedMessage(); + TS_ASSERT(msgInfo->m_topic.empty()); + TS_ASSERT_EQUALS(msgInfo->m_topicId, TopicId); + TS_ASSERT_EQUALS(msgInfo->m_data, Data); + TS_ASSERT_EQUALS(msgInfo->m_qos, Qos); + TS_ASSERT_EQUALS(msgInfo->m_retained, Retained); + TS_ASSERT(!unitTestHasReceivedMessage()); + } + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestReceive::test3() +{ + // Testing Qos2 reception of predefined topicId + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const CC_MqttsnQoS Qos = CC_MqttsnQoS_ExactlyOnceDelivery; + const CC_MqttsnTopicId TopicId = 123; + const bool Retained = true; + const UnitTestData Data = {1, 2, 3, 4}; + const std::uint16_t MsgId = 1; + + unitTestDoSubscribeTopicId(client, TopicId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + UnitTestPublishMsg publishMsg; + publishMsg.field_flags().field_qos().setValue(Qos); + publishMsg.field_flags().field_mid().setBitValue_Retain(Retained); + publishMsg.field_flags().field_topicIdType().value() = TopicIdType::PredefinedTopicId; + publishMsg.field_topicId().setValue(TopicId); + publishMsg.field_msgId().setValue(MsgId); + publishMsg.field_data().value() = Data; + + unitTestClientInputMessage(client, publishMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pubrecMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrecMsg, nullptr); + TS_ASSERT_EQUALS(pubrecMsg->field_msgId().value(), MsgId); + } + + { + TS_ASSERT(unitTestHasReceivedMessage()); + auto msgInfo = unitTestReceivedMessage(); + TS_ASSERT(msgInfo->m_topic.empty()); + TS_ASSERT_EQUALS(msgInfo->m_topicId, TopicId); + TS_ASSERT_EQUALS(msgInfo->m_data, Data); + TS_ASSERT_EQUALS(msgInfo->m_qos, Qos); + TS_ASSERT_EQUALS(msgInfo->m_retained, Retained); + TS_ASSERT(!unitTestHasReceivedMessage()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestPubrelMsg pubrelMsg; + pubrelMsg.field_msgId().setValue(MsgId); + unitTestClientInputMessage(client, pubrelMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pubcompMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubcompMsg, nullptr); + TS_ASSERT_EQUALS(pubcompMsg->field_msgId().value(), MsgId); + TS_ASSERT(!unitTestHasReceivedMessage()); + } + + TS_ASSERT(unitTestHasTickReq()); +} From 430a700517dec32e8f3cdb2ccc44a1cf96c1b716 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 6 Aug 2024 09:20:55 +1000 Subject: [PATCH 084/106] More message reception unit testing. --- client/lib/script/BareMetalTestConfig.cmake | 3 - .../lib/script/DefineDefaultConfigVars.cmake | 1 - client/lib/script/WriteConfigHeader.cmake | 1 - client/lib/src/ClientImpl.cpp | 101 ++- client/lib/src/ClientImpl.h | 13 - client/lib/src/ExtConfig.h | 6 - client/lib/src/op/Op.h | 1 - client/lib/templ/Config.h.templ | 2 - client/lib/test/UnitTestCommonBase.cpp | 15 +- client/lib/test/UnitTestCommonBase.h | 8 +- client/lib/test/UnitTestReceive.th | 829 ++++++++++++++++++ 11 files changed, 929 insertions(+), 51 deletions(-) diff --git a/client/lib/script/BareMetalTestConfig.cmake b/client/lib/script/BareMetalTestConfig.cmake index a53da946..5b4632fe 100644 --- a/client/lib/script/BareMetalTestConfig.cmake +++ b/client/lib/script/BareMetalTestConfig.cmake @@ -31,9 +31,6 @@ set(CC_MQTTSN_CLIENT_DATA_FIELD_FIXED_LEN 128) # Limit the length of the buffer required to store serialized message set(CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE 512) -# Limit the amount of incomplete QoS2 messages being received in parallel -set (CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT 4) - # Limit the amount of outstanding publish (send) operations set (CC_MQTTSN_CLIENT_ASYNC_SEND_LIMIT 6) diff --git a/client/lib/script/DefineDefaultConfigVars.cmake b/client/lib/script/DefineDefaultConfigVars.cmake index 5619d282..8b30757c 100644 --- a/client/lib/script/DefineDefaultConfigVars.cmake +++ b/client/lib/script/DefineDefaultConfigVars.cmake @@ -23,7 +23,6 @@ set_default_var_value(CC_MQTTSN_CLIENT_WILL_DATA_FIELD_FIXED_LEN 0) set_default_var_value(CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN 0) set_default_var_value(CC_MQTTSN_CLIENT_DATA_FIELD_FIXED_LEN 0) set_default_var_value(CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE 0) -set_default_var_value(CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT 0) set_default_var_value(CC_MQTTSN_CLIENT_ASYNC_SEND_LIMIT 0) set_default_var_value(CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT 0) set_default_var_value(CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT 0) diff --git a/client/lib/script/WriteConfigHeader.cmake b/client/lib/script/WriteConfigHeader.cmake index 6394a7cf..a37f05e5 100644 --- a/client/lib/script/WriteConfigHeader.cmake +++ b/client/lib/script/WriteConfigHeader.cmake @@ -49,7 +49,6 @@ replace_in_text (CC_MQTTSN_CLIENT_GATEWAY_INFOS_MAX_LIMIT) replace_in_text (CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN) replace_in_text (CC_MQTTSN_CLIENT_HAS_WILL_CPP) replace_in_text (CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE) -replace_in_text (CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT) replace_in_text (CC_MQTTSN_CLIENT_ASYNC_SEND_LIMIT) replace_in_text (CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT) replace_in_text (CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT) diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index dbf21999..b885dbed 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -15,6 +15,7 @@ #include "comms/util/assign.h" #include +#include #include namespace cc_mqttsn_client @@ -75,6 +76,59 @@ InRegTopicsMap::iterator findInRegTopicInfo(const char* topic, InRegTopicsMap& m }); } + +bool isTopicMatch(std::string_view filter, std::string_view topic) +{ + if ((filter.size() == 1U) && (filter[0] == '#')) { + return true; + } + + if (topic.empty()) { + return filter.empty(); + } + + auto filterSepPos = filter.find_first_of("/"); + auto topicSepPos = topic.find_first_of("/"); + + if ((filterSepPos == std::string_view::npos) && + (topicSepPos != std::string_view::npos)) { + return false; + } + + if (topicSepPos != std::string_view::npos) { + COMMS_ASSERT(filterSepPos != std::string_view::npos); + if (((filter[0] == '+') && (filterSepPos == 1U)) || + (filter.substr(0, filterSepPos) == topic.substr(0, topicSepPos))) { + return isTopicMatch(filter.substr(filterSepPos + 1U), topic.substr(topicSepPos + 1U)); + } + + return false; + } + + if (filterSepPos != std::string_view::npos) { + COMMS_ASSERT(topicSepPos == std::string_view::npos); + if (filter.size() <= (filterSepPos + 1U)) { + // trailing '/' on the filter without any character after that + return false; + } + + if (((filter[0] == '+') && (filterSepPos == 1U)) || + (filter.substr(0, filterSepPos) == topic)) { + return isTopicMatch(filter.substr(filterSepPos + 1U), std::string_view()); + } + + return false; + } + + COMMS_ASSERT(filterSepPos == std::string_view::npos); + COMMS_ASSERT(topicSepPos == std::string_view::npos); + + return + (((filter[0] == '+') && (filter.size() == 1U)) || + (filter == topic)); +} + + } // namespace ClientImpl::ClientImpl() : @@ -773,9 +827,9 @@ void ClientImpl::handle(GwinfoMsg& msg) void ClientImpl::handle(RegisterMsg& msg) { - if (m_sessionState.m_disconnecting) { + if ((m_sessionState.m_disconnecting) || (!m_sessionState.m_connected)) { return; - } + } for (auto& opPtr : m_keepAliveOps) { msg.dispatch(*opPtr); @@ -808,7 +862,7 @@ void ClientImpl::handle(RegisterMsg& msg) void ClientImpl::handle(PublishMsg& msg) { - if (m_sessionState.m_disconnecting) { + if ((m_sessionState.m_disconnecting) || (!m_sessionState.m_connected)) { return; } @@ -839,7 +893,7 @@ void ClientImpl::handle(PublishMsg& msg) } char shortTopicName[3] = {0}; - const char* topic = nullptr; + std::string_view topic; auto topicIdType = msg.field_flags().field_topicIdType().value(); CC_MqttsnTopicId topicId = msg.field_topicId().value(); using TopicIdType = std::decay_t; @@ -847,19 +901,19 @@ void ClientImpl::handle(PublishMsg& msg) if (topicIdType == TopicIdType::Normal) { auto& regMap = m_reuseState.m_inRegTopics; auto iter = findInRegTopicInfo(topicId, regMap); - if (iter == regMap.end()) { + if ((iter == regMap.end()) || (iter->m_topicId != topicId)) { sendPuback(ReturnCode::InvalidTopicId); return; } COMMS_ASSERT(!iter->m_topic.empty()); - topic = iter->m_topic.c_str(); + topic = std::string_view(iter->m_topic.c_str(), iter->m_topic.size()); } if (topicIdType == TopicIdType::ShortTopicName) { shortTopicName[0] = static_cast((topicId >> 8U) & 0xff); shortTopicName[1] = static_cast(topicId & 0xff); - topic = &shortTopicName[0]; + topic = std::string_view(&shortTopicName[0], 2U); } if constexpr (Config::HasSubTopicVerification) { @@ -869,6 +923,7 @@ void ClientImpl::handle(PublishMsg& msg) } auto& subFilters = m_reuseState.m_subFilters; + if (topicIdType == TopicIdType::PredefinedTopicId) { auto iter = std::find_if( @@ -889,18 +944,18 @@ void ClientImpl::handle(PublishMsg& msg) break; } - if (topic == nullptr) { - errorLog("Cannot determing PUBLISH topic"); + if (topic.empty()) { + errorLog("Cannot determine PUBLISH topic"); return; } auto iter = - std::lower_bound( - subFilters.begin(), subFilters.end(), topic, - [](const auto& info, const char* topicParam) + std::find_if( + subFilters.begin(), subFilters.end(), + [&topic](auto& info) { - return info.m_topic < topicParam; - }); + return isTopicMatch(std::string_view(info.m_topic.c_str(), info.m_topic.size()), topic); + }); if (iter == subFilters.end()) { errorLog("Received PUBLISH on non-subscribed topic"); @@ -917,13 +972,13 @@ void ClientImpl::handle(PublishMsg& msg) auto& dataVec = msg.field_data().value(); auto info = CC_MqttsnMessageInfo(); - info.m_topic = topic; + info.m_topic = topic.data(); info.m_data = dataVec.data(); comms::cast_assign(info.m_dataLen) = dataVec.size(); comms::cast_assign(info.m_qos) = msg.field_flags().field_qos().value(); info.m_retained = msg.field_flags().field_mid().getBitValue_Retain(); - if (topic == nullptr) { + if (topic.empty()) { info.m_topicId = topicId; } @@ -990,6 +1045,10 @@ void ClientImpl::handle(PublishMsg& msg) void ClientImpl::handle(PubackMsg& msg) { + if ((m_sessionState.m_disconnecting) || (!m_sessionState.m_connected)) { + return; + } + for (auto& opPtr : m_keepAliveOps) { msg.dispatch(*opPtr); } @@ -1035,6 +1094,10 @@ void ClientImpl::handle(PubackMsg& msg) #if CC_MQTTSN_CLIENT_MAX_QOS >= 2 void ClientImpl::handle(PubrelMsg& msg) { + if ((m_sessionState.m_disconnecting) || (!m_sessionState.m_connected)) { + return; + } + auto msgId = msg.field_msgId().value(); if (m_sessionState.m_lastRecvMsgId == msgId) { // Expected completion @@ -1159,7 +1222,6 @@ void ClientImpl::opComplete(const op::Op* op) /* Type_Disconnect */ &ClientImpl::opComplete_Disconnect, /* Type_Subscribe */ &ClientImpl::opComplete_Subscribe, /* Type_Unsubscribe */ &ClientImpl::opComplete_Unsubscribe, - // /* Type_Recv */ &ClientImpl::opComplete_Recv, /* Type_Send */ &ClientImpl::opComplete_Send, /* Type_Will */ &ClientImpl::opComplete_Will, }; @@ -1586,11 +1648,6 @@ void ClientImpl::opComplete_Unsubscribe(const op::Op* op) finaliseSupUnsubOp(); } -// void ClientImpl::opComplete_Recv(const op::Op* op) -// { -// eraseFromList(op, m_recvOps); -// } - void ClientImpl::opComplete_Send(const op::Op* op) { eraseFromList(op, m_sendOps); diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index ba082c88..b7586909 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -21,7 +21,6 @@ #include "op/DisconnectOp.h" #include "op/KeepAliveOp.h" #include "op/Op.h" -// #include "op/RecvOp.h" #include "op/SearchOp.h" #include "op/SendOp.h" #include "op/SubscribeOp.h" @@ -234,11 +233,6 @@ class ClientImpl final : public ProtMsgHandler } } - // std::size_t recvsCount() const - // { - // return m_recvOps.size(); - // } - private: #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY using SearchOpAlloc = ObjAllocator; @@ -260,9 +254,6 @@ class ClientImpl final : public ProtMsgHandler using UnsubscribeOpAlloc = ObjAllocator; using UnsubscribeOpsList = ObjListType; - // using RecvOpAlloc = ObjAllocator; - // using RecvOpsList = ObjListType; - using SendOpAlloc = ObjAllocator; using SendOpsList = ObjListType; @@ -296,7 +287,6 @@ class ClientImpl final : public ProtMsgHandler void opComplete_Disconnect(const op::Op* op); void opComplete_Subscribe(const op::Op* op); void opComplete_Unsubscribe(const op::Op* op); - // void opComplete_Recv(const op::Op* op); void opComplete_Send(const op::Op* op); void opComplete_Will(const op::Op* op); @@ -369,9 +359,6 @@ class ClientImpl final : public ProtMsgHandler UnsubscribeOpAlloc m_unsubscribeOpsAlloc; UnsubscribeOpsList m_unsubscribeOps; - // RecvOpAlloc m_recvOpsAlloc; - // RecvOpsList m_recvOps; - SendOpAlloc m_sendOpsAlloc; SendOpsList m_sendOps; diff --git a/client/lib/src/ExtConfig.h b/client/lib/src/ExtConfig.h index 26071259..973c628c 100644 --- a/client/lib/src/ExtConfig.h +++ b/client/lib/src/ExtConfig.h @@ -27,8 +27,6 @@ struct ExtConfig : public Config static constexpr unsigned DisconnectOpTimers = 1U; static constexpr unsigned SubscribeOpTimers = 1U; static constexpr unsigned UnsubscribeOpTimers = 1U; - static constexpr unsigned RecvOpsLimit = MaxQos < 2 ? 1U : (ReceiveMaxLimit == 0U ? 0U : ReceiveMaxLimit + 1U); - static constexpr unsigned RecvOpTimers = 1U; static constexpr unsigned SendOpTimers = 1U; static constexpr unsigned WillOpsLimit = HasDynMemAlloc ? 0 : 1U; static constexpr unsigned WillOpTimers = 1U; @@ -39,7 +37,6 @@ struct ExtConfig : public Config (DisconnectOpsLimit > 0U) && (SubscribeOpsLimit > 0U) && (UnsubscribeOpsLimit > 0U) && - (RecvOpsLimit > 0U) && (SendOpsLimit > 0U) && (HasWill && (WillOpsLimit > 0U)); static constexpr unsigned MaxTimersLimit = @@ -51,7 +48,6 @@ struct ExtConfig : public Config (DisconnectOpsLimit * DisconnectOpTimers) + (SubscribeOpsLimit * SubscribeOpTimers) + (UnsubscribeOpsLimit * UnsubscribeOpTimers) + - (RecvOpsLimit * RecvOpTimers) + (SendOpsLimit * SendOpTimers) + (WillOpsLimit * WillOpTimers); static constexpr unsigned TimersLimit = HasOpsLimit ? MaxTimersLimit : 0U; @@ -62,7 +58,6 @@ struct ExtConfig : public Config DisconnectOpsLimit + SubscribeOpsLimit + UnsubscribeOpsLimit + - RecvOpsLimit + SendOpsLimit + WillOpsLimit; @@ -79,7 +74,6 @@ struct ExtConfig : public Config static_assert(HasDynMemAlloc || (ConnectOpsLimit > 0U)); static_assert(HasDynMemAlloc || (KeepAliveOpsLimit > 0U)); static_assert(HasDynMemAlloc || (DisconnectOpsLimit > 0U)); - static_assert(HasDynMemAlloc || (RecvOpsLimit > 0U)); static_assert(HasDynMemAlloc || (SendOpsLimit > 0U)); static_assert(HasDynMemAlloc || (WillOpsLimit > 0U)); static_assert(HasDynMemAlloc || (OpsLimit > 0U)); diff --git a/client/lib/src/op/Op.h b/client/lib/src/op/Op.h index d36b0c43..0d73e004 100644 --- a/client/lib/src/op/Op.h +++ b/client/lib/src/op/Op.h @@ -35,7 +35,6 @@ class Op : public ProtMsgHandler Type_Disconnect, Type_Subscribe, Type_Unsubscribe, - // Type_Recv, Type_Send, Type_Will, Type_NumOfValues // Must be last diff --git a/client/lib/templ/Config.h.templ b/client/lib/templ/Config.h.templ index 75728134..92a41244 100644 --- a/client/lib/templ/Config.h.templ +++ b/client/lib/templ/Config.h.templ @@ -12,7 +12,6 @@ struct Config static constexpr unsigned GatewayAddrLen = ##CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN##; static constexpr unsigned MaxOutputPacketSize = ##CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE##; static constexpr bool HasWill = ##CC_MQTTSN_CLIENT_HAS_WILL_CPP##; - static constexpr unsigned ReceiveMaxLimit = ##CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT##; static constexpr unsigned SendOpsLimit = ##CC_MQTTSN_CLIENT_ASYNC_SEND_LIMIT##; static constexpr unsigned SubscribeOpsLimit = ##CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT##; static constexpr unsigned UnsubscribeOpsLimit = ##CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT##; @@ -28,7 +27,6 @@ struct Config static_assert(HasDynMemAlloc || (!HasGatewayDiscovery) || (GatewayAddrLen > 0U), "Must use CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN in configuration to limit length of the gateway addr"); static_assert(HasDynMemAlloc || (!HasGatewayDiscovery) || (GatewayInfoxMaxLimit > 0U), "Must use CC_MQTTSN_CLIENT_GATEWAY_INFOS_MAX_LIMIT in configuration to limit amount of gateways to store"); static_assert(HasDynMemAlloc || (MaxOutputPacketSize > 0U), "Must use CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE in configuration to limit packet size"); - static_assert(HasDynMemAlloc || (ReceiveMaxLimit > 0U) || (MaxQos < 2), "Must use CC_MQTTSN_CLIENT_RECEIVE_MAX_LIMIT in configuration to limit amount of messages to receive"); static_assert(HasDynMemAlloc || (SendOpsLimit > 0U), "Must use CC_MQTTSN_CLIENT_ASYNC_SEND_LIMIT in configuration to limit amount of messages to send"); static_assert(HasDynMemAlloc || (SubscribeOpsLimit > 0U), "Must use CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT in configuration to limit amount of unfinished subscribes."); static_assert(HasDynMemAlloc || (UnsubscribeOpsLimit > 0U), "Must use CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT in configuration to limit amount of unfinished unsubscribes."); diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index fb449074..f6ac3dac 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -677,7 +677,10 @@ CC_MqttsnErrorCode UnitTestCommonBase::unitTestSubscribeSend(CC_MqttsnSubscribeH return m_funcs.m_subscribe_send(subscribe, &UnitTestCommonBase::unitTestSubscribeCompleteCb, this); } -void UnitTestCommonBase::unitTestDoSubscribe(CC_MqttsnClient* client, const CC_MqttsnSubscribeConfig* config) +void UnitTestCommonBase::unitTestDoSubscribe( + CC_MqttsnClient* client, + const CC_MqttsnSubscribeConfig* config, + const UnitTestSubscribeResponseConfig* respConfig) { auto ec = m_funcs.m_subscribe(client, config, &UnitTestCommonBase::unitTestSubscribeCompleteCb, this); test_assert(ec == CC_MqttsnErrorCode_Success); @@ -699,6 +702,11 @@ void UnitTestCommonBase::unitTestDoSubscribe(CC_MqttsnClient* client, const CC_M subackMsg.field_flags().field_qos().setValue(config->m_qos); subackMsg.field_msgId().setValue(subMsgId); subackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + + if (respConfig != nullptr) { + subackMsg.field_topicId().setValue(respConfig->m_topicId); + } + unitTestClientInputMessage(client, subackMsg); test_assert(unitTestHasSubscribeCompleteReport()); @@ -844,6 +852,11 @@ CC_MqttsnErrorCode UnitTestCommonBase::apiSetOutgoingTopicIdStorageLimit(CC_Mqtt return m_funcs.m_set_outgoing_topic_id_storage_limit(client, limit); } +CC_MqttsnErrorCode UnitTestCommonBase::apiSetIncomingTopicIdStorageLimit(CC_MqttsnClient* client, unsigned long long limit) +{ + return m_funcs.m_set_incoming_topic_id_storage_limit(client, limit); +} + CC_MqttsnSearchHandle UnitTestCommonBase::apiSearchPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec) { return m_funcs.m_search_prepare(client, ec); diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index 645cac50..4b4b0e95 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -267,6 +267,11 @@ class UnitTestCommonBase using UnitTestSubscribeCompleteReportPtr = std::unique_ptr; using UnitTestSubscribeCompleteReportList = std::list; + struct UnitTestSubscribeResponseConfig + { + CC_MqttsnTopicId m_topicId = 0U; + }; + struct UnitTestUnsubscribeCompleteReport { @@ -398,7 +403,7 @@ class UnitTestCommonBase UnitTestSubscribeCompleteReportPtr unitTestSubscribeCompleteReport(bool mustExist = true); CC_MqttsnErrorCode unitTestSubscribeSend(CC_MqttsnSubscribeHandle subscribe); - void unitTestDoSubscribe(CC_MqttsnClient* client, const CC_MqttsnSubscribeConfig* config); + void unitTestDoSubscribe(CC_MqttsnClient* client, const CC_MqttsnSubscribeConfig* config, const UnitTestSubscribeResponseConfig* respConfig = nullptr); void unitTestDoSubscribeTopic(CC_MqttsnClient* client, const std::string& topic, CC_MqttsnQoS qos = CC_MqttsnQoS_ExactlyOnceDelivery); void unitTestDoSubscribeTopicId(CC_MqttsnClient* client, CC_MqttsnTopicId topicId, CC_MqttsnQoS qos = CC_MqttsnQoS_ExactlyOnceDelivery); @@ -427,6 +432,7 @@ class UnitTestCommonBase void apiInitGatewayInfo(CC_MqttsnGatewayInfo* info); CC_MqttsnErrorCode apiSetAvailableGatewayInfo(CC_MqttsnClient* client, const CC_MqttsnGatewayInfo* info); CC_MqttsnErrorCode apiSetOutgoingTopicIdStorageLimit(CC_MqttsnClient* client, unsigned long long limit); + CC_MqttsnErrorCode apiSetIncomingTopicIdStorageLimit(CC_MqttsnClient* client, unsigned long long limit); CC_MqttsnSearchHandle apiSearchPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec = nullptr); CC_MqttsnErrorCode apiSearchSetRetryPeriod(CC_MqttsnSearchHandle search, unsigned value); diff --git a/client/lib/test/UnitTestReceive.th b/client/lib/test/UnitTestReceive.th index d9e290cd..8d14ad3b 100644 --- a/client/lib/test/UnitTestReceive.th +++ b/client/lib/test/UnitTestReceive.th @@ -11,6 +11,16 @@ public: void test1(); void test2(); void test3(); + void test4(); + void test5(); + void test6(); + void test7(); + void test8(); + void test9(); + void test10(); + void test11(); + void test12(); + void test13(); private: virtual void setUp() override @@ -211,3 +221,822 @@ void UnitTestReceive::test3() TS_ASSERT(unitTestHasTickReq()); } + +void UnitTestReceive::test4() +{ + // Testing Qos0 reception of short topic name + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtMostOnceDelivery; + const std::string Topic = "ab"; + const bool Retained = true; + const UnitTestData Data = {1, 2, 3, 4}; + + unitTestDoSubscribeTopic(client, "#"); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + auto topicId = unitTestShortTopicNameToId(Topic); + { + UnitTestPublishMsg publishMsg; + publishMsg.field_flags().field_qos().setValue(Qos); + publishMsg.field_flags().field_mid().setBitValue_Retain(Retained); + publishMsg.field_flags().field_topicIdType().value() = TopicIdType::ShortTopicName; + publishMsg.field_topicId().setValue(topicId); + publishMsg.field_data().value() = Data; + + unitTestClientInputMessage(client, publishMsg); + } + + { + TS_ASSERT(unitTestHasReceivedMessage()); + auto msgInfo = unitTestReceivedMessage(); + TS_ASSERT_EQUALS(msgInfo->m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo->m_topicId, 0U); + TS_ASSERT_EQUALS(msgInfo->m_data, Data); + TS_ASSERT_EQUALS(msgInfo->m_qos, Qos); + TS_ASSERT_EQUALS(msgInfo->m_retained, Retained); + TS_ASSERT(!unitTestHasReceivedMessage()); + } + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestReceive::test5() +{ + // Testing Qos1 reception of short topic name + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; + const std::string Topic = "ab"; + const bool Retained = true; + const UnitTestData Data = {1, 2, 3, 4}; + const std::uint16_t MsgId = 1; + + unitTestDoSubscribeTopic(client, "#"); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + auto topicId = unitTestShortTopicNameToId(Topic); + { + UnitTestPublishMsg publishMsg; + publishMsg.field_flags().field_qos().setValue(Qos); + publishMsg.field_flags().field_mid().setBitValue_Retain(Retained); + publishMsg.field_flags().field_topicIdType().value() = TopicIdType::ShortTopicName; + publishMsg.field_topicId().setValue(topicId); + publishMsg.field_msgId().setValue(MsgId); + publishMsg.field_data().value() = Data; + + unitTestClientInputMessage(client, publishMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pubackMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubackMsg, nullptr); + TS_ASSERT_EQUALS(pubackMsg->field_topicId().value(), topicId); + TS_ASSERT_EQUALS(pubackMsg->field_msgId().value(), MsgId); + TS_ASSERT_EQUALS(pubackMsg->field_returnCode().value(), RetCode::Accepted); + } + + { + TS_ASSERT(unitTestHasReceivedMessage()); + auto msgInfo = unitTestReceivedMessage(); + TS_ASSERT_EQUALS(msgInfo->m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo->m_topicId, 0U); + TS_ASSERT_EQUALS(msgInfo->m_data, Data); + TS_ASSERT_EQUALS(msgInfo->m_qos, Qos); + TS_ASSERT_EQUALS(msgInfo->m_retained, Retained); + TS_ASSERT(!unitTestHasReceivedMessage()); + } + + TS_ASSERT(unitTestHasTickReq()); +} + + +void UnitTestReceive::test6() +{ + // Testing Qos2 reception of short topic name + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const CC_MqttsnQoS Qos = CC_MqttsnQoS_ExactlyOnceDelivery; + const std::string Topic = "ab"; + const bool Retained = true; + const UnitTestData Data = {1, 2, 3, 4}; + const std::uint16_t MsgId = 1; + auto topicId = unitTestShortTopicNameToId(Topic); + + unitTestDoSubscribeTopic(client, "+"); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + UnitTestPublishMsg publishMsg; + publishMsg.field_flags().field_qos().setValue(Qos); + publishMsg.field_flags().field_mid().setBitValue_Retain(Retained); + publishMsg.field_flags().field_topicIdType().value() = TopicIdType::ShortTopicName; + publishMsg.field_topicId().setValue(topicId); + publishMsg.field_msgId().setValue(MsgId); + publishMsg.field_data().value() = Data; + + unitTestClientInputMessage(client, publishMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pubrecMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrecMsg, nullptr); + TS_ASSERT_EQUALS(pubrecMsg->field_msgId().value(), MsgId); + } + + { + TS_ASSERT(unitTestHasReceivedMessage()); + auto msgInfo = unitTestReceivedMessage(); + TS_ASSERT_EQUALS(msgInfo->m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo->m_topicId, 0U); + TS_ASSERT_EQUALS(msgInfo->m_data, Data); + TS_ASSERT_EQUALS(msgInfo->m_qos, Qos); + TS_ASSERT_EQUALS(msgInfo->m_retained, Retained); + TS_ASSERT(!unitTestHasReceivedMessage()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestPubrelMsg pubrelMsg; + pubrelMsg.field_msgId().setValue(MsgId); + unitTestClientInputMessage(client, pubrelMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pubcompMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubcompMsg, nullptr); + TS_ASSERT_EQUALS(pubcompMsg->field_msgId().value(), MsgId); + TS_ASSERT(!unitTestHasReceivedMessage()); + } + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestReceive::test7() +{ + // Testing Qos0 reception with registration + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtMostOnceDelivery; + const std::string Topic = "abcd"; + const bool Retained = true; + const UnitTestData Data = {1, 2, 3, 4}; + const CC_MqttsnTopicId TopicId = 123; + const std::uint16_t RegMsgId = 1; + + unitTestDoSubscribeTopic(client, "#"); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + UnitTestRegisterMsg registerMsg; + registerMsg.field_topicId().setValue(TopicId); + registerMsg.field_msgId().setValue(RegMsgId); + registerMsg.field_topicName().setValue(Topic); + unitTestClientInputMessage(client, registerMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* regackMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(regackMsg, nullptr); + TS_ASSERT_EQUALS(regackMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(regackMsg->field_msgId().value(), RegMsgId); + TS_ASSERT_EQUALS(regackMsg->field_returnCode().value(), RetCode::Accepted); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + UnitTestPublishMsg publishMsg; + publishMsg.field_flags().field_qos().setValue(Qos); + publishMsg.field_flags().field_mid().setBitValue_Retain(Retained); + publishMsg.field_flags().field_topicIdType().value() = TopicIdType::Normal; + publishMsg.field_topicId().setValue(TopicId); + publishMsg.field_data().value() = Data; + + unitTestClientInputMessage(client, publishMsg); + } + + { + TS_ASSERT(unitTestHasReceivedMessage()); + auto msgInfo = unitTestReceivedMessage(); + TS_ASSERT_EQUALS(msgInfo->m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo->m_topicId, 0U); + TS_ASSERT_EQUALS(msgInfo->m_data, Data); + TS_ASSERT_EQUALS(msgInfo->m_qos, Qos); + TS_ASSERT_EQUALS(msgInfo->m_retained, Retained); + TS_ASSERT(!unitTestHasReceivedMessage()); + } + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestReceive::test8() +{ + // Testing Qos1 reception with registration + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; + const std::string Topic = "abcd"; + const bool Retained = true; + const UnitTestData Data = {1, 2, 3, 4}; + const CC_MqttsnTopicId TopicId = 123; + const std::uint16_t RegMsgId = 1; + const std::uint16_t PubMsgId = 2; + + unitTestDoSubscribeTopic(client, "#"); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + UnitTestRegisterMsg registerMsg; + registerMsg.field_topicId().setValue(TopicId); + registerMsg.field_msgId().setValue(RegMsgId); + registerMsg.field_topicName().setValue(Topic); + unitTestClientInputMessage(client, registerMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* regackMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(regackMsg, nullptr); + TS_ASSERT_EQUALS(regackMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(regackMsg->field_msgId().value(), RegMsgId); + TS_ASSERT_EQUALS(regackMsg->field_returnCode().value(), RetCode::Accepted); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + UnitTestPublishMsg publishMsg; + publishMsg.field_flags().field_qos().setValue(Qos); + publishMsg.field_flags().field_mid().setBitValue_Retain(Retained); + publishMsg.field_flags().field_topicIdType().value() = TopicIdType::Normal; + publishMsg.field_topicId().setValue(TopicId); + publishMsg.field_msgId().setValue(PubMsgId); + publishMsg.field_data().value() = Data; + + unitTestClientInputMessage(client, publishMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pubackMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubackMsg, nullptr); + TS_ASSERT_EQUALS(pubackMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(pubackMsg->field_msgId().value(), PubMsgId); + TS_ASSERT_EQUALS(pubackMsg->field_returnCode().value(), RetCode::Accepted); + } + + { + TS_ASSERT(unitTestHasReceivedMessage()); + auto msgInfo = unitTestReceivedMessage(); + TS_ASSERT_EQUALS(msgInfo->m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo->m_topicId, 0U); + TS_ASSERT_EQUALS(msgInfo->m_data, Data); + TS_ASSERT_EQUALS(msgInfo->m_qos, Qos); + TS_ASSERT_EQUALS(msgInfo->m_retained, Retained); + TS_ASSERT(!unitTestHasReceivedMessage()); + } + + TS_ASSERT(unitTestHasTickReq()); +} + + +void UnitTestReceive::test9() +{ + // Testing Qos2 reception with registration + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const CC_MqttsnQoS Qos = CC_MqttsnQoS_ExactlyOnceDelivery; + const std::string Topic = "abcd"; + const bool Retained = true; + const UnitTestData Data = {1, 2, 3, 4}; + const CC_MqttsnTopicId TopicId = 123; + const std::uint16_t RegMsgId = 1; + const std::uint16_t PubMsgId = 2; + + unitTestDoSubscribeTopic(client, "#"); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + UnitTestRegisterMsg registerMsg; + registerMsg.field_topicId().setValue(TopicId); + registerMsg.field_msgId().setValue(RegMsgId); + registerMsg.field_topicName().setValue(Topic); + unitTestClientInputMessage(client, registerMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* regackMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(regackMsg, nullptr); + TS_ASSERT_EQUALS(regackMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(regackMsg->field_msgId().value(), RegMsgId); + TS_ASSERT_EQUALS(regackMsg->field_returnCode().value(), RetCode::Accepted); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + UnitTestPublishMsg publishMsg; + publishMsg.field_flags().field_qos().setValue(Qos); + publishMsg.field_flags().field_mid().setBitValue_Retain(Retained); + publishMsg.field_flags().field_topicIdType().value() = TopicIdType::Normal; + publishMsg.field_topicId().setValue(TopicId); + publishMsg.field_msgId().setValue(PubMsgId); + publishMsg.field_data().value() = Data; + + unitTestClientInputMessage(client, publishMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pubrecMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrecMsg, nullptr); + TS_ASSERT_EQUALS(pubrecMsg->field_msgId().value(), PubMsgId); + } + + { + TS_ASSERT(unitTestHasReceivedMessage()); + auto msgInfo = unitTestReceivedMessage(); + TS_ASSERT_EQUALS(msgInfo->m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo->m_topicId, 0U); + TS_ASSERT_EQUALS(msgInfo->m_data, Data); + TS_ASSERT_EQUALS(msgInfo->m_qos, Qos); + TS_ASSERT_EQUALS(msgInfo->m_retained, Retained); + TS_ASSERT(!unitTestHasReceivedMessage()); + } + + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestPubrelMsg pubrelMsg; + pubrelMsg.field_msgId().setValue(PubMsgId); + unitTestClientInputMessage(client, pubrelMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pubcompMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubcompMsg, nullptr); + TS_ASSERT_EQUALS(pubcompMsg->field_msgId().value(), PubMsgId); + TS_ASSERT(!unitTestHasReceivedMessage()); + } + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestReceive::test10() +{ + // Testing reception with unknown topic id + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtMostOnceDelivery; + const std::string Topic = "abcd"; + const bool Retained = true; + const UnitTestData Data = {1, 2, 3, 4}; + const CC_MqttsnTopicId TopicId = 123; + + unitTestDoSubscribeTopic(client, "#"); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + UnitTestPublishMsg publishMsg; + publishMsg.field_flags().field_qos().setValue(Qos); + publishMsg.field_flags().field_mid().setBitValue_Retain(Retained); + publishMsg.field_flags().field_topicIdType().value() = TopicIdType::Normal; + publishMsg.field_topicId().setValue(TopicId); + publishMsg.field_data().value() = Data; + + unitTestClientInputMessage(client, publishMsg); + } + + TS_ASSERT(!unitTestHasReceivedMessage()); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pubackMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubackMsg, nullptr); + TS_ASSERT_EQUALS(pubackMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(pubackMsg->field_msgId().value(), 0U); + TS_ASSERT_EQUALS(pubackMsg->field_returnCode().value(), RetCode::InvalidTopicId); + } + + TS_ASSERT(unitTestHasTickReq()); +} + + +void UnitTestReceive::test11() +{ + // Testing duplicate Qos2 reception of predefined topicId + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const CC_MqttsnQoS Qos = CC_MqttsnQoS_ExactlyOnceDelivery; + const CC_MqttsnTopicId TopicId = 123; + const bool Retained = true; + const UnitTestData Data = {1, 2, 3, 4}; + const std::uint16_t MsgId = 1; + + unitTestDoSubscribeTopicId(client, TopicId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + UnitTestPublishMsg publishMsg; + publishMsg.field_flags().field_qos().setValue(Qos); + publishMsg.field_flags().field_mid().setBitValue_Retain(Retained); + publishMsg.field_flags().field_topicIdType().value() = TopicIdType::PredefinedTopicId; + publishMsg.field_topicId().setValue(TopicId); + publishMsg.field_msgId().setValue(MsgId); + publishMsg.field_data().value() = Data; + + unitTestClientInputMessage(client, publishMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pubrecMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrecMsg, nullptr); + TS_ASSERT_EQUALS(pubrecMsg->field_msgId().value(), MsgId); + } + + { + TS_ASSERT(unitTestHasReceivedMessage()); + auto msgInfo = unitTestReceivedMessage(); + TS_ASSERT(msgInfo->m_topic.empty()); + TS_ASSERT_EQUALS(msgInfo->m_topicId, TopicId); + TS_ASSERT_EQUALS(msgInfo->m_data, Data); + TS_ASSERT_EQUALS(msgInfo->m_qos, Qos); + TS_ASSERT_EQUALS(msgInfo->m_retained, Retained); + TS_ASSERT(!unitTestHasReceivedMessage()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 3000); + + { + UnitTestPublishMsg publishMsg; + publishMsg.field_flags().field_qos().setValue(Qos); + publishMsg.field_flags().field_mid().setBitValue_Retain(Retained); + publishMsg.field_flags().field_topicIdType().value() = TopicIdType::PredefinedTopicId; + publishMsg.field_flags().field_high().setBitValue_Dup(true); + publishMsg.field_topicId().setValue(TopicId); + publishMsg.field_msgId().setValue(MsgId); + publishMsg.field_data().value() = Data; + + unitTestClientInputMessage(client, publishMsg); + } + + TS_ASSERT(!unitTestHasReceivedMessage()); // Duplicate message should not be reported + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pubrecMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrecMsg, nullptr); + TS_ASSERT_EQUALS(pubrecMsg->field_msgId().value(), MsgId); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestPubrelMsg pubrelMsg; + pubrelMsg.field_msgId().setValue(MsgId); + unitTestClientInputMessage(client, pubrelMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pubcompMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubcompMsg, nullptr); + TS_ASSERT_EQUALS(pubcompMsg->field_msgId().value(), MsgId); + TS_ASSERT(!unitTestHasReceivedMessage()); + } + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestReceive::test12() +{ + // Testing topic id registration during subscribe + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtMostOnceDelivery; + const std::string Topic = "abcd"; + const bool Retained = true; + const UnitTestData Data = {1, 2, 3, 4}; + const CC_MqttsnTopicId TopicId = 123; + + CC_MqttsnSubscribeConfig config; + UnitTestSubscribeResponseConfig subRespConfig; + subRespConfig.m_topicId = TopicId; + + apiSubscribeInitConfig(&config); + config.m_topic = Topic.c_str(); + unitTestDoSubscribe(client, &config, &subRespConfig); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + UnitTestPublishMsg publishMsg; + publishMsg.field_flags().field_qos().setValue(Qos); + publishMsg.field_flags().field_mid().setBitValue_Retain(Retained); + publishMsg.field_flags().field_topicIdType().value() = TopicIdType::Normal; + publishMsg.field_topicId().setValue(TopicId); + publishMsg.field_data().value() = Data; + + unitTestClientInputMessage(client, publishMsg); + } + + { + TS_ASSERT(unitTestHasReceivedMessage()); + auto msgInfo = unitTestReceivedMessage(); + TS_ASSERT_EQUALS(msgInfo->m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo->m_topicId, 0U); + TS_ASSERT_EQUALS(msgInfo->m_data, Data); + TS_ASSERT_EQUALS(msgInfo->m_qos, Qos); + TS_ASSERT_EQUALS(msgInfo->m_retained, Retained); + TS_ASSERT(!unitTestHasReceivedMessage()); + } + + TS_ASSERT(unitTestHasTickReq()); +} + +void UnitTestReceive::test13() +{ + // Testing reception with expiring registration + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + auto ec = apiSetIncomingTopicIdStorageLimit(client, 1U); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtMostOnceDelivery; + const std::string Topic1 = "dead"; + const std::string Topic2 = "beef"; + const UnitTestData Data = {1, 2, 3, 4}; + const CC_MqttsnTopicId TopicId1 = 123; + const CC_MqttsnTopicId TopicId2 = 321; + const std::uint16_t RegMsgId1 = 1; + const std::uint16_t RegMsgId2 = 2; + + unitTestDoSubscribeTopic(client, "#"); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + UnitTestRegisterMsg registerMsg; + registerMsg.field_topicId().setValue(TopicId1); + registerMsg.field_msgId().setValue(RegMsgId1); + registerMsg.field_topicName().setValue(Topic1); + unitTestClientInputMessage(client, registerMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* regackMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(regackMsg, nullptr); + TS_ASSERT_EQUALS(regackMsg->field_topicId().value(), TopicId1); + TS_ASSERT_EQUALS(regackMsg->field_msgId().value(), RegMsgId1); + TS_ASSERT_EQUALS(regackMsg->field_returnCode().value(), RetCode::Accepted); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + UnitTestPublishMsg publishMsg1; + publishMsg1.field_flags().field_qos().setValue(Qos); + publishMsg1.field_flags().field_topicIdType().value() = TopicIdType::Normal; + publishMsg1.field_topicId().setValue(TopicId1); + publishMsg1.field_data().value() = Data; + unitTestClientInputMessage(client, publishMsg1); + + { + TS_ASSERT(unitTestHasReceivedMessage()); + auto msgInfo = unitTestReceivedMessage(); + TS_ASSERT_EQUALS(msgInfo->m_topic, Topic1); + TS_ASSERT_EQUALS(msgInfo->m_topicId, 0U); + TS_ASSERT_EQUALS(msgInfo->m_data, Data); + TS_ASSERT_EQUALS(msgInfo->m_qos, Qos); + TS_ASSERT_EQUALS(msgInfo->m_retained, false); + TS_ASSERT(!unitTestHasReceivedMessage()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + // Repeat Topic1 publish + unitTestClientInputMessage(client, publishMsg1); + + { + TS_ASSERT(unitTestHasReceivedMessage()); + auto msgInfo = unitTestReceivedMessage(); + TS_ASSERT_EQUALS(msgInfo->m_topic, Topic1); + TS_ASSERT_EQUALS(msgInfo->m_topicId, 0U); + TS_ASSERT_EQUALS(msgInfo->m_data, Data); + TS_ASSERT_EQUALS(msgInfo->m_qos, Qos); + TS_ASSERT_EQUALS(msgInfo->m_retained, false); + TS_ASSERT(!unitTestHasReceivedMessage()); + } + + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + // Register Topic2 + { + UnitTestRegisterMsg registerMsg; + registerMsg.field_topicId().setValue(TopicId2); + registerMsg.field_msgId().setValue(RegMsgId2); + registerMsg.field_topicName().setValue(Topic2); + unitTestClientInputMessage(client, registerMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* regackMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(regackMsg, nullptr); + TS_ASSERT_EQUALS(regackMsg->field_topicId().value(), TopicId2); + TS_ASSERT_EQUALS(regackMsg->field_msgId().value(), RegMsgId2); + TS_ASSERT_EQUALS(regackMsg->field_returnCode().value(), RetCode::Accepted); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + UnitTestPublishMsg publishMsg2; + publishMsg2.field_flags().field_qos().setValue(Qos); + publishMsg2.field_flags().field_topicIdType().value() = TopicIdType::Normal; + publishMsg2.field_topicId().setValue(TopicId2); + publishMsg2.field_data().value() = Data; + unitTestClientInputMessage(client, publishMsg2); + + { + TS_ASSERT(unitTestHasReceivedMessage()); + auto msgInfo = unitTestReceivedMessage(); + TS_ASSERT_EQUALS(msgInfo->m_topic, Topic2); + TS_ASSERT_EQUALS(msgInfo->m_topicId, 0U); + TS_ASSERT_EQUALS(msgInfo->m_data, Data); + TS_ASSERT_EQUALS(msgInfo->m_qos, Qos); + TS_ASSERT_EQUALS(msgInfo->m_retained, false); + TS_ASSERT(!unitTestHasReceivedMessage()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + // Repeat Topic2 message + unitTestClientInputMessage(client, publishMsg2); + + { + TS_ASSERT(unitTestHasReceivedMessage()); + auto msgInfo = unitTestReceivedMessage(); + TS_ASSERT_EQUALS(msgInfo->m_topic, Topic2); + TS_ASSERT_EQUALS(msgInfo->m_topicId, 0U); + TS_ASSERT_EQUALS(msgInfo->m_data, Data); + TS_ASSERT_EQUALS(msgInfo->m_qos, Qos); + TS_ASSERT_EQUALS(msgInfo->m_retained, false); + TS_ASSERT(!unitTestHasReceivedMessage()); + } + + // Repeat Topic1 message, expected to reject + + unitTestClientInputMessage(client, publishMsg1); + + { + TS_ASSERT(!unitTestHasReceivedMessage()); + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pubackMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubackMsg, nullptr); + TS_ASSERT_EQUALS(pubackMsg->field_topicId().value(), TopicId1); + TS_ASSERT_EQUALS(pubackMsg->field_msgId().value(), 0U); + TS_ASSERT_EQUALS(pubackMsg->field_returnCode().value(), RetCode::InvalidTopicId); + } + + TS_ASSERT(unitTestHasTickReq()); +} From 58a544fdc0b71e03da1e86629f323b79a71fe99d Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 7 Aug 2024 08:01:34 +1000 Subject: [PATCH 085/106] Improving Qos2 messages handling in the client library. --- client/lib/src/ClientImpl.cpp | 14 +++++++------- client/lib/src/ReuseState.h | 1 + client/lib/src/SessionState.h | 1 - 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index b885dbed..ef073500 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -992,7 +992,7 @@ void ClientImpl::handle(PublishMsg& msg) if constexpr (1U <= Config::MaxQos) { if (qos == op::Op::Qos::AtLeastOnceDelivery) { - m_sessionState.m_lastRecvMsgId = 0U; + m_reuseState.m_lastRecvMsgId = 0U; sendPuback(ReturnCode::Accepted); return; } @@ -1013,11 +1013,11 @@ void ClientImpl::handle(PublishMsg& msg) }; do { - if (m_sessionState.m_lastRecvMsgId == 0U) { + if (m_reuseState.m_lastRecvMsgId == 0U) { break; } - if (m_sessionState.m_lastRecvMsgId != msgId) { + if (m_reuseState.m_lastRecvMsgId != msgId) { errorLog("Previous Qos2 message reception wasn't completed properly."); break; } @@ -1033,7 +1033,7 @@ void ClientImpl::handle(PublishMsg& msg) return; } while (false); - m_sessionState.m_lastRecvMsgId = msgId; + m_reuseState.m_lastRecvMsgId = msgId; sendPubrec(); return; } @@ -1099,11 +1099,11 @@ void ClientImpl::handle(PubrelMsg& msg) } auto msgId = msg.field_msgId().value(); - if (m_sessionState.m_lastRecvMsgId == msgId) { + if (m_reuseState.m_lastRecvMsgId == msgId) { // Expected completion - m_sessionState.m_lastRecvMsgId = 0U; + m_reuseState.m_lastRecvMsgId = 0U; } - else if (m_sessionState.m_lastRecvMsgId != 0U) { + else if (m_reuseState.m_lastRecvMsgId != 0U) { // Previous Qos2 reception is incomplete while unexpected PUBREL arrives errorLog("Unexpected PUBREL message received"); } diff --git a/client/lib/src/ReuseState.h b/client/lib/src/ReuseState.h index 74b25915..608079d9 100644 --- a/client/lib/src/ReuseState.h +++ b/client/lib/src/ReuseState.h @@ -21,6 +21,7 @@ struct ReuseState SubFiltersMap m_subFilters; InRegTopicsMap m_inRegTopics; OutRegTopicsMap m_outRegTopics; + std::uint16_t m_lastRecvMsgId = 0U; #if CC_MQTTSN_CLIENT_HAS_WILL using WillTopicType = WilltopicMsg::Field_willTopic::ValueType; diff --git a/client/lib/src/SessionState.h b/client/lib/src/SessionState.h index 69efbc98..2c3f55bc 100644 --- a/client/lib/src/SessionState.h +++ b/client/lib/src/SessionState.h @@ -15,7 +15,6 @@ struct SessionState static constexpr unsigned DefaultKeepAlive = 60; unsigned m_keepAliveMs = 0U; - std::uint16_t m_lastRecvMsgId = 0U; bool m_connected = false; bool m_disconnecting = false; }; From 03dac91f181c819e3f17b227e7b56fdb275aa6db Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 7 Aug 2024 08:43:18 +1000 Subject: [PATCH 086/106] Documentation client message reception. --- client/lib/doxygen/main.dox | 118 +++++++++++++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 2 deletions(-) diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index 46a8c0c6..4313f2ec 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -1453,6 +1453,9 @@ /// ... /// } /// @endcode +/// When the set limit is reached the least recently used topic id record is dropped +/// to free space for the newly registered one. +/// /// To get the current limit use @b cc_mqttsn_client_get_outgoing_topic_id_storage_limit() /// function. /// @@ -1641,10 +1644,121 @@ /// /// /// @section doc_cc_mqttsn_client_receive Receiving Messages -/// TODO +/// Right after the successful "connect" operation, the library starts expecting +/// the arrival of the new messages and reports it via the @ref doc_cc_mqttsn_client_callbacks_message +/// "registered callback". By default the library monitors the topics the client +/// application @ref doc_cc_mqttsn_client_subscribe "subscribed" to and does not +/// report "rogue" messages from the gateway. +/// +/// @b Note that only topics themselves +/// are monitored, and not the requested maximal @b QoS values. It means that if +/// application requested maximal @b QoS1 for a particular subscription, but the +/// gateway sends message using the @b QoS2, the message is still going to be +/// reported to the application. +/// +/// However, if the gateway is trusted to do the right thing, i.e. fully comply to +/// the specification, it is possible to enable / disable the incoming topics check using the +/// @b cc_mqttsn_client_set_verify_incoming_msg_subscribed() for performance reasons. +/// @code +/// CC_MqttsnErrorCode ec = cc_mqttsn_client_set_verify_incoming_msg_subscribed(client, false); +/// @endcode +/// To retrieve the current configuration use the @b cc_mqttsn_client_get_verify_incoming_msg_subscribed() function. +/// +/// @b WARNING: When the incoming message subscription verification is disabled, the +/// library does @b NOT track any new @ref doc_cc_mqttsn_client_subscribe "subscription" +/// requests, which can result in dropping legit messages and not reporting them to the application +/// after the subscription verification is re-enabled later. +/// +/// Similarly, the library also by default verifies the incoming topic format, that it doesn't contain +/// wildcard characters. To enable / disable such verification use the @b cc_mqttsn_client_set_verify_incoming_topic_enabled() +/// function. +/// @code +/// CC_MqttsnErrorCode ec = cc_mqttsn_client_set_verify_incoming_topic_enabled(client, false); +/// @endcode +/// To retrieve the current configuration use the @b cc_mqttsn_client_get_verify_incoming_topic_enabled() function. +/// +/// The +/// @ref doc_cc_mqttsn_client_callbacks_message "message report callback" is invoked immediately on +/// reception of the @b PUBLISH message even for QoS2 message. Just like it is shown as "Method B" in the "Figure 4.3" of the +/// MQTT v3.1.1 specification. For QoS2 messages the packet ID is stored and the +/// rest of the relevant QoS2 messages are exchanged in the background. +/// +/// When a gateway attempts to publish a message with non-short topic (length of which +/// is not equal to 2 characters), the topic needs to be registered against the +/// client with allocated topic ID. Such topic ID is preserved +/// for future use to avoid redundant registration process in the future. +/// Depending on the amount of various topic strings that +/// the end application may receive there might be a need to limit the uncontrolled growth of +/// internal storage. To do so use @b cc_mqttsn_client_set_incoming_topic_id_storage_limit() +/// function +/// @code +/// ec = cc_mqttsn_client_set_incoming_topic_id_storage_limit(client, 10); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to limit the incoming topic ids storage with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// When the set limit is reached the least recently used topic id record is dropped +/// to free space for the newly registered one. +/// +/// To get the current limit use @b cc_mqttsn_client_get_incoming_topic_id_storage_limit() +/// function. +/// +/// To remove the previously set limit, i.e. reset it to the default pass @b 0 as +/// the limit value to @b cc_mqttsn_client_set_incoming_topic_id_storage_limit(). +/// @code +/// ec = cc_mqttsn_client_set_incoming_topic_id_storage_limit(client, 0); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to reset the limit the incoming topic ids storage with ec=%d\n", ec); +/// ... +/// } +/// @endcode /// /// @section doc_cc_mqttsn_client_sleep Sleeping /// TODO /// /// @section doc_cc_mqttsn_client_unsolicited_disconnect Unsolicited Gateway Disconnection -/// TODO +/// When broker disconnection is detected all the incomplete asynchronous operations +/// will be terminated with the an appropriate @ref CC_MqttsnAsyncOpStatus "status" report. +/// +/// The unsolicited broker disconnection can happen in one of the following scenarios: +/// +/// @subsection doc_cc_mqttsn_client_unsolicited_disconnect_keep_alive Keep Alive Timeout +/// When there was no message from the broker for the "keep alive" timeout (configured during the @ref connect "connect" +/// operation) and the broker doesn't respond to the @b PINGREQ message. +/// In such case the library responds in the following way: +/// @li Terminates and invokes the callbacks of previously initiated but incomplete operations +/// passing the @ref CC_MqttsnAsyncOpStatus_GatewayDisconnected as their status report. +/// @li Invokes the @ref doc_cc_mqttsn_client_callbacks_gateway_disconnect "disconnection report callback" +/// registered using the @b cc_mqttsn_client_set_gw_disconnect_report_callback() function +/// reporting @ref CC_MqttsnGatewayDisconnectReason_NoGatewayResponse as the disconnection reason. +/// +/// @subsection doc_cc_mqttsn_client_unsolicited_disconnect_message Receiving DISCONNECT message +/// When something goes wrong the gateway is allowed to send @b DISCONNECT message to +/// the client at any time. +/// In such case the library responds in the following way: +/// @li Terminates and invokes the callbacks of all other previously initiated but incomplete operations +/// passing the @ref CC_MqttsnAsyncOpStatus_GatewayDisconnected as their status report. +/// @li Invokes the @ref doc_cc_mqttsn_client_callbacks_gateway_disconnect "disconnection report callback" +/// registered using the @b cc_mqttsn_client_set_gw_disconnect_report_callback() function +/// reporting @ref CC_MqttsnGatewayDisconnectReason_DisconnectMsg as the disconnection reason. +/// +/// With all said above it means that if application receives +/// @ref CC_MqttsnAsyncOpStatus_GatewayDisconnected status in the operation callback, then +/// the application is expected to wait for the @ref doc_cc_mqttsn_client_callbacks_gateway_disconnect "disconnection report callback" +/// which will follow. +/// +/// @section doc_cc_mqttsn_client_thread_safety Thread Safety +/// In general the library is @b NOT thread safe. To support multi-threading the application +/// is expected to use appropriate locking mechanisms before calling relevant API functions. +/// However, if each thread operates on a separate allocated +/// @ref doc_cc_mqttsn_client_allocation "client", then the locking is required only +/// for the @ref doc_cc_mqttsn_client_allocation "client allocation logic". +/// +/// @section doc_cc_mqttsn_client_debug_build Debug Build +/// The client library uses COMMS_ASSERT() +/// macro to check some internal pre- and post-conditions. It is a bug if the assertion fails, please report if +/// encountered. By default it is like standard +/// assert() but allows overriding +/// the default failure behavour, which can be more suitable for bare-metal systems. Please +/// refer to the documentation for details. From 907df8b6c013a96bb2f0eac107cb3ca1bf5588ae Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 7 Aug 2024 09:12:49 +1000 Subject: [PATCH 087/106] Preparation for the "sleep" operation support. --- client/lib/doxygen/main.dox | 3 +- client/lib/include/cc_mqttsn_client/common.h | 10 ++++++ client/lib/src/ClientImpl.cpp | 37 +++++++++++--------- client/lib/src/SessionState.h | 2 +- client/lib/templ/client.cpp.templ | 4 +-- client/lib/templ/client.h.templ | 2 +- client/lib/test/UnitTestCommonBase.cpp | 10 +++--- client/lib/test/UnitTestCommonBase.h | 4 +-- client/lib/test/UnitTestConnect.th | 18 +++++----- client/lib/test/UnitTestDefaultBase.cpp | 2 +- client/lib/test/UnitTestDisconnect.th | 12 +++---- 11 files changed, 59 insertions(+), 45 deletions(-) diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index 4313f2ec..4fe046ee 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -752,10 +752,9 @@ /// At any time it is possible to check the internal state of the library of /// whether it's properly connected to the gateway. /// @code -/// bool isConnected = cc_mqttsn_client_is_connected(client); +/// CC_MqttsnConnectionState connState = cc_mqttsn_client_get_connection_state(client); /// @endcode /// -/// /// @section doc_cc_mqttsn_client_disconnect Disconnecting From Gateway /// To intentionally disconnect from gateway use @ref disconnect "disconnect" operation. The /// unsolicited disconnection from the gateway is described in ref diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index b7a57251..18331a3a 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -115,6 +115,16 @@ typedef enum CC_MqttsnReturnCode_ValuesLimit ///< Limit for the values } CC_MqttsnReturnCode; +/// @brief Connection state +/// @ingroup global +typedef enum +{ + CC_MqttsnConnectionState_Disconnected = 0, ///< Client disconnection from the gateway + CC_MqttsnConnectionState_Connected = 1, ///< Client connected to the gateway + CC_MqttsnConnectionState_Asleep = 2, ///< Client in the sleep mode + CC_MqttsnConnectionState_ValuesLimit ///< Limit for the values +} CC_MqttsnConnectionState; + /// @brief Declaration of struct for the @ref CC_MqttsnClientHandle; /// @ingroup client struct CC_MqttsnClient; diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index ef073500..9b8d7810 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -262,7 +262,7 @@ op::ConnectOp* ClientImpl::connectPrepare(CC_MqttsnErrorCode* ec) break; } - if (m_sessionState.m_connected) { + if (m_sessionState.m_connectionState == CC_MqttsnConnectionState_Connected) { errorLog("Client is already connected."); updateEc(ec, CC_MqttsnErrorCode_AlreadyConnected); break; @@ -301,8 +301,8 @@ op::DisconnectOp* ClientImpl::disconnectPrepare(CC_MqttsnErrorCode* ec) { op::DisconnectOp* op = nullptr; do { - if (!m_sessionState.m_connected) { - errorLog("Client must be connected to allow disconnect."); + if (m_sessionState.m_connectionState == CC_MqttsnConnectionState_Disconnected) { + errorLog("Client must be connected / asleep to allow disconnect."); updateEc(ec, CC_MqttsnErrorCode_NotConnected); break; } @@ -359,7 +359,7 @@ op::SubscribeOp* ClientImpl::subscribePrepare(CC_MqttsnErrorCode* ec) { op::SubscribeOp* op = nullptr; do { - if (!m_sessionState.m_connected) { + if (m_sessionState.m_connectionState != CC_MqttsnConnectionState_Connected) { errorLog("Client must be connected to allow subscription."); updateEc(ec, CC_MqttsnErrorCode_NotConnected); break; @@ -410,7 +410,7 @@ op::UnsubscribeOp* ClientImpl::unsubscribePrepare(CC_MqttsnErrorCode* ec) { op::UnsubscribeOp* op = nullptr; do { - if (!m_sessionState.m_connected) { + if (m_sessionState.m_connectionState != CC_MqttsnConnectionState_Connected) { errorLog("Client must be connected to allow subscription."); updateEc(ec, CC_MqttsnErrorCode_NotConnected); break; @@ -461,7 +461,7 @@ op::SendOp* ClientImpl::publishPrepare(CC_MqttsnErrorCode* ec) { op::SendOp* op = nullptr; do { - if (!m_sessionState.m_connected) { + if (m_sessionState.m_connectionState != CC_MqttsnConnectionState_Connected) { errorLog("Client must be connected to allow publish."); updateEc(ec, CC_MqttsnErrorCode_NotConnected); break; @@ -513,7 +513,7 @@ op::WillOp* ClientImpl::willPrepare(CC_MqttsnErrorCode* ec) { op::WillOp* op = nullptr; do { - if (!m_sessionState.m_connected) { + if (m_sessionState.m_connectionState != CC_MqttsnConnectionState_Connected) { errorLog("Client must be connected to allow will update."); updateEc(ec, CC_MqttsnErrorCode_NotConnected); break; @@ -827,7 +827,8 @@ void ClientImpl::handle(GwinfoMsg& msg) void ClientImpl::handle(RegisterMsg& msg) { - if ((m_sessionState.m_disconnecting) || (!m_sessionState.m_connected)) { + if ((m_sessionState.m_disconnecting) || + (m_sessionState.m_connectionState == CC_MqttsnConnectionState_Disconnected)) { return; } @@ -862,7 +863,8 @@ void ClientImpl::handle(RegisterMsg& msg) void ClientImpl::handle(PublishMsg& msg) { - if ((m_sessionState.m_disconnecting) || (!m_sessionState.m_connected)) { + if ((m_sessionState.m_disconnecting) || + (m_sessionState.m_connectionState == CC_MqttsnConnectionState_Disconnected)) { return; } @@ -1045,7 +1047,8 @@ void ClientImpl::handle(PublishMsg& msg) void ClientImpl::handle(PubackMsg& msg) { - if ((m_sessionState.m_disconnecting) || (!m_sessionState.m_connected)) { + if ((m_sessionState.m_disconnecting) || + (m_sessionState.m_connectionState == CC_MqttsnConnectionState_Disconnected)) { return; } @@ -1094,7 +1097,8 @@ void ClientImpl::handle(PubackMsg& msg) #if CC_MQTTSN_CLIENT_MAX_QOS >= 2 void ClientImpl::handle(PubrelMsg& msg) { - if ((m_sessionState.m_disconnecting) || (!m_sessionState.m_connected)) { + if ((m_sessionState.m_disconnecting) || + (m_sessionState.m_connectionState == CC_MqttsnConnectionState_Disconnected)) { return; } @@ -1119,7 +1123,8 @@ void ClientImpl::handle(PubrelMsg& msg) void ClientImpl::handle([[maybe_unused]] PingreqMsg& msg) { - if ((m_sessionState.m_disconnecting) || (!m_sessionState.m_connected)) { + if ((m_sessionState.m_disconnecting) || + (m_sessionState.m_connectionState != CC_MqttsnConnectionState_Connected)) { return; } @@ -1130,7 +1135,8 @@ void ClientImpl::handle([[maybe_unused]] PingreqMsg& msg) void ClientImpl::handle(DisconnectMsg& msg) { - if ((m_sessionState.m_disconnecting) || (!m_sessionState.m_connected)) { + if ((m_sessionState.m_disconnecting) || + (m_sessionState.m_connectionState == CC_MqttsnConnectionState_Disconnected)) { return; } @@ -1241,7 +1247,7 @@ void ClientImpl::opComplete(const op::Op* op) void ClientImpl::gatewayConnected() { m_clientState.m_firstConnect = false; - m_sessionState.m_connected = true; + m_sessionState.m_connectionState = CC_MqttsnConnectionState_Connected; createKeepAliveOpIfNeeded(); } @@ -1250,8 +1256,7 @@ void ClientImpl::gatewayDisconnected( CC_MqttsnAsyncOpStatus status) { m_clientState.m_initialized = false; // Require re-initialization - m_sessionState.m_connected = false; - + m_sessionState.m_connectionState = CC_MqttsnConnectionState_Disconnected; m_sessionState.m_disconnecting = true; terminateOps(status); diff --git a/client/lib/src/SessionState.h b/client/lib/src/SessionState.h index 2c3f55bc..107b65ff 100644 --- a/client/lib/src/SessionState.h +++ b/client/lib/src/SessionState.h @@ -15,7 +15,7 @@ struct SessionState static constexpr unsigned DefaultKeepAlive = 60; unsigned m_keepAliveMs = 0U; - bool m_connected = false; + CC_MqttsnConnectionState m_connectionState = CC_MqttsnConnectionState_Disconnected; bool m_disconnecting = false; }; diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index 812a289a..2b5c6bdd 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -684,10 +684,10 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect( return cc_mqttsn_##NAME##client_connect_send(connect, cb, cbData); } -bool cc_mqttsn_##NAME##client_is_connected(CC_MqttsnClientHandle client) +CC_MqttsnConnectionState cc_mqttsn_##NAME##client_get_connection_state(CC_MqttsnClientHandle client) { COMMS_ASSERT(client != nullptr); - return clientFromHandle(client)->sessionState().m_connected; + return clientFromHandle(client)->sessionState().m_connectionState; } CC_MqttsnDisconnectHandle cc_mqttsn_##NAME##client_disconnect_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec) diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index de1a0b0b..de053b5c 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -467,7 +467,7 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect( /// @brief Check the inner state of the library of whether it's connected to the gateway. /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @ingroup connect -bool cc_mqttsn_##NAME##client_is_connected(CC_MqttsnClientHandle client); +CC_MqttsnConnectionState cc_mqttsn_##NAME##client_get_connection_state(CC_MqttsnClientHandle client); /// @brief Prepare "disconnect" operation. /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index f6ac3dac..f641e6b3 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -76,7 +76,7 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_connect_send != nullptr); test_assert(m_funcs.m_connect_cancel != nullptr); test_assert(m_funcs.m_connect != nullptr); - test_assert(m_funcs.m_is_connected != nullptr); + test_assert(m_funcs.m_get_connection_state != nullptr); test_assert(m_funcs.m_disconnect_prepare != nullptr); test_assert(m_funcs.m_disconnect_set_retry_period != nullptr); test_assert(m_funcs.m_disconnect_get_retry_period != nullptr); @@ -580,7 +580,7 @@ void UnitTestCommonBase::unitTestDoConnect(CC_MqttsnClient* client, const CC_Mqt } test_assert(!unitTestHasConnectCompleteReport()); - test_assert(!apiIsConnected(client)); + test_assert(apiGetConnectionState(client) == CC_MqttsnConnectionState_Disconnected); test_assert(unitTestHasTickReq()); unitTestTick(client, 100); } @@ -593,7 +593,7 @@ void UnitTestCommonBase::unitTestDoConnect(CC_MqttsnClient* client, const CC_Mqt auto connectReport = unitTestConnectCompleteReport(); test_assert(connectReport->m_status== CC_MqttsnAsyncOpStatus_Complete) test_assert(connectReport->m_info.m_returnCode == CC_MqttsnReturnCode_Accepted) - test_assert(apiIsConnected(client)); + test_assert(apiGetConnectionState(client) == CC_MqttsnConnectionState_Connected); } void UnitTestCommonBase::unitTestDoConnectBasic(CC_MqttsnClient* client, const std::string& clientId, bool cleanSession) @@ -907,9 +907,9 @@ CC_MqttsnErrorCode UnitTestCommonBase::apiConnectConfigWill(CC_MqttsnConnectHand return m_funcs.m_connect_config_will(connect, config); } -bool UnitTestCommonBase::apiIsConnected(CC_MqttsnClient* client) +CC_MqttsnConnectionState UnitTestCommonBase::apiGetConnectionState(CC_MqttsnClient* client) { - return m_funcs.m_is_connected(client); + return m_funcs.m_get_connection_state(client); } CC_MqttsnDisconnectHandle UnitTestCommonBase::apiDisconnectPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec) diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index 4b4b0e95..f1c4f7d3 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -66,7 +66,7 @@ class UnitTestCommonBase CC_MqttsnErrorCode (*m_connect_send)(CC_MqttsnConnectHandle, CC_MqttsnConnectCompleteCb, void*) = nullptr; CC_MqttsnErrorCode (*m_connect_cancel)(CC_MqttsnConnectHandle) = nullptr; CC_MqttsnErrorCode (*m_connect)(CC_MqttsnClientHandle, const CC_MqttsnConnectConfig*, const CC_MqttsnWillConfig*, CC_MqttsnConnectCompleteCb, void*) = nullptr; - bool (*m_is_connected)(CC_MqttsnClientHandle) = nullptr; + CC_MqttsnConnectionState (*m_get_connection_state)(CC_MqttsnClientHandle) = nullptr; CC_MqttsnDisconnectHandle (*m_disconnect_prepare)(CC_MqttsnClientHandle, CC_MqttsnErrorCode*) = nullptr; CC_MqttsnErrorCode (*m_disconnect_set_retry_period)(CC_MqttsnDisconnectHandle, unsigned ms) = nullptr; unsigned (*m_disconnect_get_retry_period)(CC_MqttsnDisconnectHandle) = nullptr; @@ -445,7 +445,7 @@ class UnitTestCommonBase void apiConnectInitConfigWill(CC_MqttsnWillConfig* config); CC_MqttsnErrorCode apiConnectConfig(CC_MqttsnConnectHandle connect, const CC_MqttsnConnectConfig* config); CC_MqttsnErrorCode apiConnectConfigWill(CC_MqttsnConnectHandle connect, const CC_MqttsnWillConfig* config); - bool apiIsConnected(CC_MqttsnClient* client); + CC_MqttsnConnectionState apiGetConnectionState(CC_MqttsnClient* client); CC_MqttsnDisconnectHandle apiDisconnectPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec = nullptr); CC_MqttsnErrorCode apiDisconnectSetRetryCount(CC_MqttsnDisconnectHandle disconnect, unsigned count); diff --git a/client/lib/test/UnitTestConnect.th b/client/lib/test/UnitTestConnect.th index 83e10cfc..cf1554cd 100644 --- a/client/lib/test/UnitTestConnect.th +++ b/client/lib/test/UnitTestConnect.th @@ -75,7 +75,7 @@ void UnitTestConnect::test1() TS_ASSERT_EQUALS(connectReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted) TS_ASSERT(unitTestHasTickReq()); // For keep alive - TS_ASSERT(apiIsConnected(client)); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Connected); } void UnitTestConnect::test2() @@ -164,7 +164,7 @@ void UnitTestConnect::test2() } TS_ASSERT(!unitTestHasConnectCompleteReport()); - TS_ASSERT(!apiIsConnected(client)); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Disconnected); TS_ASSERT(unitTestHasTickReq()); unitTestTick(client, 100); @@ -180,7 +180,7 @@ void UnitTestConnect::test2() TS_ASSERT_EQUALS(connectReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted) TS_ASSERT(unitTestHasTickReq()); // For keep alive - TS_ASSERT(apiIsConnected(client)); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Connected); } void UnitTestConnect::test3() @@ -244,7 +244,7 @@ void UnitTestConnect::test3() TS_ASSERT_EQUALS(connectReport->m_status, CC_MqttsnAsyncOpStatus_Timeout) TS_ASSERT(!unitTestHasTickReq()); - TS_ASSERT(!apiIsConnected(client)); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Disconnected); } void UnitTestConnect::test4() @@ -342,7 +342,7 @@ void UnitTestConnect::test4() TS_ASSERT_EQUALS(connectReport->m_status, CC_MqttsnAsyncOpStatus_Timeout) TS_ASSERT(!unitTestHasTickReq()); - TS_ASSERT(!apiIsConnected(client)); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Disconnected); } @@ -435,7 +435,7 @@ void UnitTestConnect::test5() } TS_ASSERT(!unitTestHasConnectCompleteReport()); - TS_ASSERT(!apiIsConnected(client)); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Disconnected); TS_ASSERT(unitTestHasTickReq()); unitTestTick(client); @@ -448,7 +448,7 @@ void UnitTestConnect::test5() } TS_ASSERT(!unitTestHasConnectCompleteReport()); - TS_ASSERT(!apiIsConnected(client)); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Disconnected); TS_ASSERT(unitTestHasTickReq()); unitTestTick(client); @@ -457,7 +457,7 @@ void UnitTestConnect::test5() TS_ASSERT_EQUALS(connectReport->m_status, CC_MqttsnAsyncOpStatus_Timeout) TS_ASSERT(!unitTestHasTickReq()); - TS_ASSERT(!apiIsConnected(client)); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Disconnected); } void UnitTestConnect::test6() @@ -505,7 +505,7 @@ void UnitTestConnect::test6() TS_ASSERT_EQUALS(connectReport->m_status, CC_MqttsnAsyncOpStatus_Timeout) TS_ASSERT(!unitTestHasTickReq()); - TS_ASSERT(!apiIsConnected(client)); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Disconnected); } // TODO: test reconnect without disconnecting \ No newline at end of file diff --git a/client/lib/test/UnitTestDefaultBase.cpp b/client/lib/test/UnitTestDefaultBase.cpp index 3a5b58c1..c20458b3 100644 --- a/client/lib/test/UnitTestDefaultBase.cpp +++ b/client/lib/test/UnitTestDefaultBase.cpp @@ -57,7 +57,7 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_connect_send = &cc_mqttsn_client_connect_send; funcs.m_connect_cancel = &cc_mqttsn_client_connect_cancel; funcs.m_connect = &cc_mqttsn_client_connect; - funcs.m_is_connected = &cc_mqttsn_client_is_connected; + funcs.m_get_connection_state = &cc_mqttsn_client_get_connection_state; funcs.m_disconnect_prepare = &cc_mqttsn_client_disconnect_prepare; funcs.m_disconnect_set_retry_period = &cc_mqttsn_client_disconnect_set_retry_period; funcs.m_disconnect_get_retry_period = &cc_mqttsn_client_disconnect_get_retry_period; diff --git a/client/lib/test/UnitTestDisconnect.th b/client/lib/test/UnitTestDisconnect.th index 57ca0a18..e900f1c1 100644 --- a/client/lib/test/UnitTestDisconnect.th +++ b/client/lib/test/UnitTestDisconnect.th @@ -30,7 +30,7 @@ void UnitTestDisconnect::test1() { // Testing basic disconnect - auto clientPtr = unitTestAllocClient(); + auto clientPtr = unitTestAllocClient(true); auto* client = clientPtr.get(); const std::string ClientId("bla"); @@ -66,7 +66,7 @@ void UnitTestDisconnect::test1() TS_ASSERT_EQUALS(disconnectReport->m_status, CC_MqttsnAsyncOpStatus_Complete) TS_ASSERT(!unitTestHasTickReq()); // No more keep alive - TS_ASSERT(!apiIsConnected(client)); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Disconnected); } void UnitTestDisconnect::test2() @@ -120,7 +120,7 @@ void UnitTestDisconnect::test2() TS_ASSERT_EQUALS(disconnectReport->m_status, CC_MqttsnAsyncOpStatus_Timeout) TS_ASSERT(unitTestHasTickReq()); // Still hase keep alive - TS_ASSERT(apiIsConnected(client)); // Still connected + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Connected); // Still connected } void UnitTestDisconnect::test3() @@ -179,7 +179,7 @@ void UnitTestDisconnect::test3() TS_ASSERT_EQUALS(disconnectReport->m_status, CC_MqttsnAsyncOpStatus_Complete) TS_ASSERT(!unitTestHasTickReq()); // No more keep alive - TS_ASSERT(!apiIsConnected(client)); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Disconnected); } void UnitTestDisconnect::test4() @@ -205,7 +205,7 @@ void UnitTestDisconnect::test4() TS_ASSERT_EQUALS(disconnectReport->m_reason, CC_MqttsnGatewayDisconnectReason_DisconnectMsg); TS_ASSERT(!unitTestHasTickReq()); // No more keep alive - TS_ASSERT(!apiIsConnected(client)); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Disconnected); } void UnitTestDisconnect::test5() @@ -251,5 +251,5 @@ void UnitTestDisconnect::test5() TS_ASSERT_EQUALS(disconnectReport->m_reason, CC_MqttsnGatewayDisconnectReason_NoGatewayResponse); TS_ASSERT(!unitTestHasTickReq()); // No more keep alive - TS_ASSERT(!apiIsConnected(client)); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Disconnected); } From 344f6102e650ed570227eae3b380d726e0b7aee5 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 8 Aug 2024 09:26:29 +1000 Subject: [PATCH 088/106] Implemented asleep op support. --- client/lib/doxygen/main.dox | 2 +- client/lib/include/cc_mqttsn_client/common.h | 49 +++-- client/lib/src/ClientImpl.cpp | 177 +++++-------------- client/lib/src/ClientImpl.h | 11 +- client/lib/src/SessionState.h | 2 +- client/lib/src/op/DisconnectOp.cpp | 40 ++++- client/lib/src/op/DisconnectOp.h | 6 + client/lib/src/op/KeepAliveOp.cpp | 25 ++- client/lib/src/op/KeepAliveOp.h | 2 + client/lib/templ/client.cpp.templ | 124 ++++++++++++- client/lib/templ/client.h.templ | 97 +++++++++- client/lib/test/UnitTestCommonBase.cpp | 10 +- client/lib/test/UnitTestCommonBase.h | 4 +- client/lib/test/UnitTestConnect.th | 18 +- client/lib/test/UnitTestDefaultBase.cpp | 2 +- client/lib/test/UnitTestDisconnect.th | 10 +- 16 files changed, 394 insertions(+), 185 deletions(-) diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index 4fe046ee..18495318 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -752,7 +752,7 @@ /// At any time it is possible to check the internal state of the library of /// whether it's properly connected to the gateway. /// @code -/// CC_MqttsnConnectionState connState = cc_mqttsn_client_get_connection_state(client); +/// CC_MqttsnConnectionStatus connStatus = cc_mqttsn_client_get_connection_status(client); /// @endcode /// /// @section doc_cc_mqttsn_client_disconnect Disconnecting From Gateway diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index 18331a3a..3428da22 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -119,11 +119,11 @@ typedef enum /// @ingroup global typedef enum { - CC_MqttsnConnectionState_Disconnected = 0, ///< Client disconnection from the gateway - CC_MqttsnConnectionState_Connected = 1, ///< Client connected to the gateway - CC_MqttsnConnectionState_Asleep = 2, ///< Client in the sleep mode - CC_MqttsnConnectionState_ValuesLimit ///< Limit for the values -} CC_MqttsnConnectionState; + CC_MqttsnConnectionStatus_Disconnected = 0, ///< Client disconnection from the gateway + CC_MqttsnConnectionStatus_Connected = 1, ///< Client connected to the gateway + CC_MqttsnConnectionStatus_Asleep = 2, ///< Client in the sleep mode + CC_MqttsnConnectionStatus_ValuesLimit ///< Limit for the values +} CC_MqttsnConnectionStatus; /// @brief Declaration of struct for the @ref CC_MqttsnClientHandle; /// @ingroup client @@ -140,7 +140,7 @@ struct CC_MqttsnSearch; /// @brief Handle for "search" operation. /// @details Returned by @b cc_mqttsn_client_search_prepare() function. -/// @ingroup "search". +/// @ingroup search. typedef struct CC_MqttsnSearch* CC_MqttsnSearchHandle; /// @brief Declaration of the hidden structure used to define @ref CC_MqttsnConnectHandle @@ -149,7 +149,7 @@ struct CC_MqttsnConnect; /// @brief Handle for "connect" operation. /// @details Returned by @b cc_mqttsn_client_connect_prepare() function. -/// @ingroup "connect". +/// @ingroup connect. typedef struct CC_MqttsnConnect* CC_MqttsnConnectHandle; /// @brief Declaration of the hidden structure used to define @ref CC_MqttsnDisconnectHandle @@ -158,7 +158,7 @@ struct CC_MqttsnDisconnect; /// @brief Handle for "disconnect" operation. /// @details Returned by @b cc_mqttsn_client_disconnect_prepare() function. -/// @ingroup "disconnect". +/// @ingroup disconnect. typedef struct CC_MqttsnDisconnect* CC_MqttsnDisconnectHandle; /// @brief Declaration of the hidden structure used to define @ref CC_MqttsnSubscribeHandle @@ -167,7 +167,7 @@ struct CC_MqttsnSubscribe; /// @brief Handle for "subscribe" operation. /// @details Returned by @b cc_mqttsn_client_subscribe_prepare() function. -/// @ingroup "subscribe". +/// @ingroup subscribe. typedef struct CC_MqttsnSubscribe* CC_MqttsnSubscribeHandle; /// @brief Declaration of the hidden structure used to define @ref CC_MqttsnUnsubscribeHandle @@ -176,7 +176,7 @@ struct CC_MqttsnUnsubscribe; /// @brief Handle for "unsubscribe" operation. /// @details Returned by @b cc_mqttsn_client_unsubscribe_prepare() function. -/// @ingroup "subscribe". +/// @ingroup subscribe. typedef struct CC_MqttsnUnsubscribe* CC_MqttsnUnsubscribeHandle; /// @brief Declaration of the hidden structure used to define @ref CC_MqttsnPublishHandle @@ -185,7 +185,7 @@ struct CC_MqttsnPublish; /// @brief Handle for "publish" operation. /// @details Returned by @b cc_mqttsn_client_publish_prepare() function. -/// @ingroup "publish". +/// @ingroup publish. typedef struct CC_MqttsnPublish* CC_MqttsnPublishHandle; /// @brief Declaration of the hidden structure used to define @ref CC_MqttsnWillHandle @@ -194,9 +194,18 @@ struct CC_MqttsnWill; /// @brief Handle for "will" operation. /// @details Returned by @b cc_mqttsn_client_will_prepare() function. -/// @ingroup "will". +/// @ingroup will. typedef struct CC_MqttsnWill* CC_MqttsnWillHandle; +/// @brief Declaration of the hidden structure used to define @ref CC_MqttsnSleepHandle +/// @ingroup sleep +struct CC_MqttsnSleep; + +/// @brief Handle for "sleep" operation. +/// @details Returned by @b cc_mqttsn_client_sleep_prepare() function. +/// @ingroup sleep. +typedef struct CC_MqttsnSleep* CC_MqttsnSleepHandle; + /// @brief Type used to hold Topic ID value. /// @ingroup global typedef unsigned short CC_MqttsnTopicId; @@ -301,6 +310,13 @@ typedef struct CC_MqttsnReturnCode m_msgUpdReturnCode; ///< Return code reported by the @b WILLMSGRESP message } CC_MqttsnWillInfo; +/// @brief Configuration the "sleep" operation +/// @ingroup sleep +typedef struct +{ + unsigned m_duration; ///< Duration configuration in seconds. +} CC_MqttsnSleepConfig; + /// @brief Callback used to request time measurement. /// @details The callback is set using /// cc_mqttsn_client_set_next_tick_program_callback() function. @@ -454,6 +470,15 @@ typedef void (*CC_MqttsnPublishCompleteCb)(void* data, CC_MqttsnPublishHandle ha /// @ingroup will typedef void (*CC_MqttsnWillCompleteCb)(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnWillInfo* info); +/// @brief Callback used to report completion of the sleep operation. +/// @param[in] data Pointer to user data object, passed as the last parameter to +/// the request call. +/// @param[in] status Status of the "sleep" operation. +/// @post The data members of the reported response can NOT be accessed after the function returns. +/// @ingroup sleep +typedef void (*CC_MqttsnSleepCompleteCb)(void* data, CC_MqttsnAsyncOpStatus status); + + #ifdef __cplusplus } #endif diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 9b8d7810..12840548 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -262,7 +262,7 @@ op::ConnectOp* ClientImpl::connectPrepare(CC_MqttsnErrorCode* ec) break; } - if (m_sessionState.m_connectionState == CC_MqttsnConnectionState_Connected) { + if (m_sessionState.m_connectionStatus == CC_MqttsnConnectionStatus_Connected) { errorLog("Client is already connected."); updateEc(ec, CC_MqttsnErrorCode_AlreadyConnected); break; @@ -301,7 +301,7 @@ op::DisconnectOp* ClientImpl::disconnectPrepare(CC_MqttsnErrorCode* ec) { op::DisconnectOp* op = nullptr; do { - if (m_sessionState.m_connectionState == CC_MqttsnConnectionState_Disconnected) { + if (m_sessionState.m_connectionStatus == CC_MqttsnConnectionStatus_Disconnected) { errorLog("Client must be connected / asleep to allow disconnect."); updateEc(ec, CC_MqttsnErrorCode_NotConnected); break; @@ -359,7 +359,7 @@ op::SubscribeOp* ClientImpl::subscribePrepare(CC_MqttsnErrorCode* ec) { op::SubscribeOp* op = nullptr; do { - if (m_sessionState.m_connectionState != CC_MqttsnConnectionState_Connected) { + if (m_sessionState.m_connectionStatus != CC_MqttsnConnectionStatus_Connected) { errorLog("Client must be connected to allow subscription."); updateEc(ec, CC_MqttsnErrorCode_NotConnected); break; @@ -410,7 +410,7 @@ op::UnsubscribeOp* ClientImpl::unsubscribePrepare(CC_MqttsnErrorCode* ec) { op::UnsubscribeOp* op = nullptr; do { - if (m_sessionState.m_connectionState != CC_MqttsnConnectionState_Connected) { + if (m_sessionState.m_connectionStatus != CC_MqttsnConnectionStatus_Connected) { errorLog("Client must be connected to allow subscription."); updateEc(ec, CC_MqttsnErrorCode_NotConnected); break; @@ -461,7 +461,7 @@ op::SendOp* ClientImpl::publishPrepare(CC_MqttsnErrorCode* ec) { op::SendOp* op = nullptr; do { - if (m_sessionState.m_connectionState != CC_MqttsnConnectionState_Connected) { + if (m_sessionState.m_connectionStatus != CC_MqttsnConnectionStatus_Connected) { errorLog("Client must be connected to allow publish."); updateEc(ec, CC_MqttsnErrorCode_NotConnected); break; @@ -513,7 +513,7 @@ op::WillOp* ClientImpl::willPrepare(CC_MqttsnErrorCode* ec) { op::WillOp* op = nullptr; do { - if (m_sessionState.m_connectionState != CC_MqttsnConnectionState_Connected) { + if (m_sessionState.m_connectionStatus != CC_MqttsnConnectionStatus_Connected) { errorLog("Client must be connected to allow will update."); updateEc(ec, CC_MqttsnErrorCode_NotConnected); break; @@ -624,16 +624,25 @@ std::size_t ClientImpl::getIncomingRegTopicsLimit() const return m_clientState.m_inRegTopicsLimit; } -// CC_MqttsnErrorCode ClientImpl::setPublishOrdering(CC_MqttsnPublishOrdering ordering) -// { -// if (CC_MqttsnPublishOrdering_ValuesLimit <= ordering) { -// errorLog("Bad publish ordering value"); -// return CC_MqttsnErrorCode_BadParam; -// } +CC_MqttsnErrorCode ClientImpl::asleepCheckMessages() +{ + if (m_sessionState.m_connectionStatus != CC_MqttsnConnectionStatus_Asleep) { + errorLog("Not in ASLEEP state, cannot check for pending messages"); + return CC_MqttsnErrorCode_NotSleeping; + } + + if (m_keepAliveOps.empty()) { + errorLog("BUG: Cannot send PINGREQ"); + return CC_MqttsnErrorCode_InternalError; + } -// m_configState.m_publishOrdering = ordering; -// return CC_MqttsnErrorCode_Success; -// } + for (auto& op : m_keepAliveOps) { + COMMS_ASSERT(op); + op->forceSendPing(); + } + + return CC_MqttsnErrorCode_Success; +} #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY void ClientImpl::handle(AdvertiseMsg& msg) @@ -828,7 +837,7 @@ void ClientImpl::handle(GwinfoMsg& msg) void ClientImpl::handle(RegisterMsg& msg) { if ((m_sessionState.m_disconnecting) || - (m_sessionState.m_connectionState == CC_MqttsnConnectionState_Disconnected)) { + (m_sessionState.m_connectionStatus == CC_MqttsnConnectionStatus_Disconnected)) { return; } @@ -864,7 +873,7 @@ void ClientImpl::handle(RegisterMsg& msg) void ClientImpl::handle(PublishMsg& msg) { if ((m_sessionState.m_disconnecting) || - (m_sessionState.m_connectionState == CC_MqttsnConnectionState_Disconnected)) { + (m_sessionState.m_connectionStatus == CC_MqttsnConnectionStatus_Disconnected)) { return; } @@ -1048,7 +1057,7 @@ void ClientImpl::handle(PublishMsg& msg) void ClientImpl::handle(PubackMsg& msg) { if ((m_sessionState.m_disconnecting) || - (m_sessionState.m_connectionState == CC_MqttsnConnectionState_Disconnected)) { + (m_sessionState.m_connectionStatus == CC_MqttsnConnectionStatus_Disconnected)) { return; } @@ -1098,7 +1107,7 @@ void ClientImpl::handle(PubackMsg& msg) void ClientImpl::handle(PubrelMsg& msg) { if ((m_sessionState.m_disconnecting) || - (m_sessionState.m_connectionState == CC_MqttsnConnectionState_Disconnected)) { + (m_sessionState.m_connectionStatus == CC_MqttsnConnectionStatus_Disconnected)) { return; } @@ -1124,7 +1133,7 @@ void ClientImpl::handle(PubrelMsg& msg) void ClientImpl::handle([[maybe_unused]] PingreqMsg& msg) { if ((m_sessionState.m_disconnecting) || - (m_sessionState.m_connectionState != CC_MqttsnConnectionState_Connected)) { + (m_sessionState.m_connectionStatus != CC_MqttsnConnectionStatus_Connected)) { return; } @@ -1136,7 +1145,7 @@ void ClientImpl::handle([[maybe_unused]] PingreqMsg& msg) void ClientImpl::handle(DisconnectMsg& msg) { if ((m_sessionState.m_disconnecting) || - (m_sessionState.m_connectionState == CC_MqttsnConnectionState_Disconnected)) { + (m_sessionState.m_connectionStatus == CC_MqttsnConnectionStatus_Disconnected)) { return; } @@ -1247,7 +1256,7 @@ void ClientImpl::opComplete(const op::Op* op) void ClientImpl::gatewayConnected() { m_clientState.m_firstConnect = false; - m_sessionState.m_connectionState = CC_MqttsnConnectionState_Connected; + m_sessionState.m_connectionStatus = CC_MqttsnConnectionStatus_Connected; createKeepAliveOpIfNeeded(); } @@ -1256,7 +1265,7 @@ void ClientImpl::gatewayDisconnected( CC_MqttsnAsyncOpStatus status) { m_clientState.m_initialized = false; // Require re-initialization - m_sessionState.m_connectionState = CC_MqttsnConnectionState_Disconnected; + m_sessionState.m_connectionStatus = CC_MqttsnConnectionStatus_Disconnected; m_sessionState.m_disconnecting = true; terminateOps(status); @@ -1266,11 +1275,19 @@ void ClientImpl::gatewayDisconnected( } } -// void ClientImpl::reportMsgInfo(const CC_MqttsnMessageInfo& info) -// { -// COMMS_ASSERT(m_messageReceivedReportCb != nullptr); -// m_messageReceivedReportCb(m_messageReceivedReportData, &info); -// } +void ClientImpl::enterSleepMode(unsigned durationMs) +{ + COMMS_ASSERT(0U < durationMs); + COMMS_ASSERT(m_sessionState.m_connectionStatus == CC_MqttsnConnectionStatus_Connected); + m_sessionState.m_connectionStatus = CC_MqttsnConnectionStatus_Asleep; + terminateOps(CC_MqttsnAsyncOpStatus_Aborted); + COMMS_ASSERT(m_keepAliveOps.empty()); + + auto diff = m_configState.m_retryPeriod * (m_configState.m_retryCount + 1U); + auto maxDuration = std::max(durationMs, diff + 1U); + m_sessionState.m_keepAliveMs = maxDuration - diff; + createKeepAliveOpIfNeeded(); +} void ClientImpl::allowNextPrepare() { @@ -1387,6 +1404,7 @@ void ClientImpl::createKeepAliveOpIfNeeded() return; } + COMMS_ASSERT(m_ops.size() < m_ops.max_size()); m_ops.push_back(ptr.get()); m_keepAliveOps.push_back(std::move(ptr)); } @@ -1516,109 +1534,6 @@ bool ClientImpl::verifyPubTopicInternal(const char* topic, bool outgoing) } } -// void ClientImpl::resumeSendOpsSince(unsigned idx) -// { -// while (idx < m_sendOps.size()) { -// auto& opToResumePtr = m_sendOps[idx]; -// if (!opToResumePtr->isPaused()) { -// ++idx; -// continue; -// } - -// if (!opToResumePtr->resume()) { -// break; -// } - -// // After resuming some (QoS0) ops can complete right away, increment idx next iteration -// } -// } - -// op::SendOp* ClientImpl::findSendOp(std::uint16_t packetId) -// { -// auto iter = -// std::find_if( -// m_sendOps.begin(), m_sendOps.end(), -// [packetId](auto& opPtr) -// { -// COMMS_ASSERT(opPtr); -// return opPtr->packetId() == packetId; -// }); - -// if (iter == m_sendOps.end()) { -// return nullptr; -// } - -// return iter->get(); -// } - -// bool ClientImpl::isLegitSendAck(const op::SendOp* sendOp, bool pubcompAck) const -// { -// if (!sendOp->isPublished()) { -// return false; -// } - -// for (auto& sendOpPtr : m_sendOps) { -// if (sendOpPtr.get() == sendOp) { -// return true; -// } - -// if (!sendOpPtr->isAcked()) { -// return false; -// } - -// if (pubcompAck && (sendOp != m_sendOps.front().get())) { -// return false; -// } -// } - -// COMMS_ASSERT(false); // Should not be reached; -// return false; -// } - -// void ClientImpl::resendAllUntil(op::SendOp* sendOp) -// { -// // Do index controlled iteration because forcing dup resend can -// // cause early message destruction. -// for (auto idx = 0U; idx < m_sendOps.size();) { -// auto& sendOpPtr = m_sendOps[idx]; -// COMMS_ASSERT(sendOpPtr); -// auto* opBeforeResend = sendOpPtr.get(); -// sendOpPtr->forceDupResend(); // can destruct object -// if (opBeforeResend == sendOp) { -// break; -// } - -// auto* opAfterResend = sendOpPtr.get(); -// if (opBeforeResend != opAfterResend) { -// // The op object was destructed and erased, -// // do not increment index; -// continue; -// } - -// ++idx; -// } -// } - -// bool ClientImpl::processPublishAckMsg(ProtMessage& msg, std::uint16_t packetId, bool pubcompAck) -// { -// for (auto& opPtr : m_keepAliveOps) { -// msg.dispatch(*opPtr); -// } - -// auto* sendOp = findSendOp(packetId); -// if (sendOp == nullptr) { -// return false; -// } - -// if (isLegitSendAck(sendOp, pubcompAck)) { -// msg.dispatch(*sendOp); -// return true; -// } - -// resendAllUntil(sendOp); -// return true; -// } - void ClientImpl::opComplete_Search([[maybe_unused]] const op::Op* op) { #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index b7586909..9268d302 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -78,6 +78,7 @@ class ClientImpl final : public ProtMsgHandler std::size_t getOutgoingRegTopicsLimit() const; CC_MqttsnErrorCode setIncomingRegTopicsLimit(std::size_t limit); std::size_t getIncomingRegTopicsLimit() const; + CC_MqttsnErrorCode asleepCheckMessages(); void setNextTickProgramCallback(CC_MqttsnNextTickProgramCb cb, void* data) { @@ -147,7 +148,6 @@ class ClientImpl final : public ProtMsgHandler virtual void handle(SearchgwMsg& msg) override; virtual void handle(GwinfoMsg& msg) override; #endif // #if CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY -// virtual void handle(PublishMsg& msg) override; virtual void handle(RegisterMsg& msg) override; virtual void handle(PublishMsg& msg) override; @@ -169,9 +169,7 @@ class ClientImpl final : public ProtMsgHandler void gatewayDisconnected( CC_MqttsnGatewayDisconnectReason reason = CC_MqttsnGatewayDisconnectReason_ValuesLimit, CC_MqttsnAsyncOpStatus status = CC_MqttsnAsyncOpStatus_GatewayDisconnected); - // void reportMsgInfo(const CC_MqttsnMessageInfo& info); - // bool hasPausedSendsBefore(const op::SendOp* sendOp) const; - // bool hasHigherQosSendsBefore(const op::SendOp* sendOp, op::Op::Qos qos) const; + void enterSleepMode(unsigned durationMs); void allowNextPrepare(); void storeInRegTopic(const char* topic, CC_MqttsnTopicId topicId); bool removeInRegTopic(const char* topic, CC_MqttsnTopicId topicId); @@ -275,11 +273,6 @@ class ClientImpl final : public ProtMsgHandler void errorLogInternal(const char* msg); CC_MqttsnErrorCode initInternal(); bool verifyPubTopicInternal(const char* topic, bool outgoing); - // void resumeSendOpsSince(unsigned idx); - // op::SendOp* findSendOp(std::uint16_t packetId); - // bool isLegitSendAck(const op::SendOp* sendOp, bool pubcompAck = false) const; - // void resendAllUntil(op::SendOp* sendOp); - // bool processPublishAckMsg(ProtMessage& msg, std::uint16_t packetId, bool pubcompAck = false); void opComplete_Search(const op::Op* op); void opComplete_Connect(const op::Op* op); diff --git a/client/lib/src/SessionState.h b/client/lib/src/SessionState.h index 107b65ff..06f2595a 100644 --- a/client/lib/src/SessionState.h +++ b/client/lib/src/SessionState.h @@ -15,7 +15,7 @@ struct SessionState static constexpr unsigned DefaultKeepAlive = 60; unsigned m_keepAliveMs = 0U; - CC_MqttsnConnectionState m_connectionState = CC_MqttsnConnectionState_Disconnected; + CC_MqttsnConnectionStatus m_connectionStatus = CC_MqttsnConnectionStatus_Disconnected; bool m_disconnecting = false; }; diff --git a/client/lib/src/op/DisconnectOp.cpp b/client/lib/src/op/DisconnectOp.cpp index c4501a68..df47319a 100644 --- a/client/lib/src/op/DisconnectOp.cpp +++ b/client/lib/src/op/DisconnectOp.cpp @@ -12,7 +12,6 @@ #include "comms/util/ScopeGuard.h" #include "comms/units.h" -#include #include namespace cc_mqttsn_client @@ -38,6 +37,31 @@ DisconnectOp::DisconnectOp(ClientImpl& client) : { } +CC_MqttsnErrorCode DisconnectOp::config(const CC_MqttsnSleepConfig* config) +{ + if (config == nullptr) { + errorLog("Sleep configuration is not provided"); + return CC_MqttsnErrorCode_BadParam; + } + + auto& st = client().configState(); + auto minDurationMs = (st.m_retryPeriod * (st.m_retryCount + 1U)); + if ((config->m_duration * 1000) <= minDurationMs) { + errorLog("Sleep duration is too low, must allow multiple attempts to send PINGREQ"); + return CC_MqttsnErrorCode_BadParam; + } + + using DurationValueType = DisconnectMsg::Field_duration::Field::ValueType; + if (std::numeric_limits::max() < config->m_duration) { + errorLog("Sleep duration value is too high, doesn't fit into protocol"); + return CC_MqttsnErrorCode_BadParam; + } + + comms::units::setSeconds(m_disconnectMsg.field_duration().field(), config->m_duration); + m_disconnectMsg.field_duration().setExists(); + return CC_MqttsnErrorCode_Success; +} + CC_MqttsnErrorCode DisconnectOp::send(CC_MqttsnDisconnectCompleteCb cb, void* cbData) { client().allowNextPrepare(); @@ -87,11 +111,21 @@ void DisconnectOp::handle([[maybe_unused]] DisconnectMsg& msg) m_timer.cancel(); auto& cl = client(); + unsigned sleepDurationMs = 0U; + if (isSleepConfigured()) { + sleepDurationMs = comms::units::getMilliseconds(m_disconnectMsg.field_duration().field()); + } + auto onExit = comms::util::makeScopeGuard( - [&cl]() + [&cl, sleepDurationMs]() { - cl.gatewayDisconnected(); + if (0U < sleepDurationMs) { + cl.enterSleepMode(sleepDurationMs); + return; + } + + cl.gatewayDisconnected(CC_MqttsnGatewayDisconnectReason_ValuesLimit, CC_MqttsnAsyncOpStatus_Aborted); }); completeOpInternal(CC_MqttsnAsyncOpStatus_Complete); diff --git a/client/lib/src/op/DisconnectOp.h b/client/lib/src/op/DisconnectOp.h index 9b8b4972..991786f6 100644 --- a/client/lib/src/op/DisconnectOp.h +++ b/client/lib/src/op/DisconnectOp.h @@ -26,11 +26,17 @@ class DisconnectOp final : public Op explicit DisconnectOp(ClientImpl& client); CC_MqttsnErrorCode send(CC_MqttsnDisconnectCompleteCb cb, void* cbData); + CC_MqttsnErrorCode config(const CC_MqttsnSleepConfig* config); CC_MqttsnErrorCode cancel(); using Base::handle; void handle(DisconnectMsg& msg) override; + bool isSleepConfigured() const + { + return m_disconnectMsg.field_duration().doesExist(); + } + protected: virtual Type typeImpl() const override; virtual void terminateOpImpl(CC_MqttsnAsyncOpStatus status) override; diff --git a/client/lib/src/op/KeepAliveOp.cpp b/client/lib/src/op/KeepAliveOp.cpp index 3615efad..04c0b1c5 100644 --- a/client/lib/src/op/KeepAliveOp.cpp +++ b/client/lib/src/op/KeepAliveOp.cpp @@ -37,6 +37,11 @@ KeepAliveOp::KeepAliveOp(ClientImpl& client) : restartPingTimer(); } +void KeepAliveOp::forceSendPing() +{ + sendPing(); +} + void KeepAliveOp::messageSent() { restartPingTimer(); @@ -44,6 +49,10 @@ void KeepAliveOp::messageSent() void KeepAliveOp::handle([[maybe_unused]] PingreqMsg& msg) { + if (client().sessionState().m_connectionStatus == CC_MqttsnConnectionStatus_Disconnected) { + return; + } + PingrespMsg respMsg; sendMessage(respMsg); restartRecvTimer(); @@ -59,6 +68,13 @@ void KeepAliveOp::handle([[maybe_unused]] PingrespMsg& msg) void KeepAliveOp::handle([[maybe_unused]] ProtMessage& msg) { + if ((client().sessionState().m_connectionStatus == CC_MqttsnConnectionStatus_Asleep) && + (m_respTimer.isActive())) { + + // In ASLEEP mode, gime some more time for PINGRESP after the last message + restartRespTimer(); + } + restartRecvTimer(); } @@ -87,6 +103,12 @@ void KeepAliveOp::restartRecvTimer() m_recvTimer.wait(state.m_keepAliveMs, &KeepAliveOp::recvTimeoutCb, this); } +void KeepAliveOp::restartRespTimer() +{ + auto& state = client().configState(); + m_respTimer.wait(state.m_retryPeriod, &KeepAliveOp::pingTimeoutCb, this); +} + void KeepAliveOp::sendPing() { if (m_respTimer.isActive()) { @@ -95,8 +117,7 @@ void KeepAliveOp::sendPing() PingreqMsg msg; client().sendMessage(msg); - auto& state = client().configState(); - m_respTimer.wait(state.m_retryPeriod, &KeepAliveOp::pingTimeoutCb, this); + restartRespTimer(); } void KeepAliveOp::pingTimeoutInternal() diff --git a/client/lib/src/op/KeepAliveOp.h b/client/lib/src/op/KeepAliveOp.h index db3cd63d..7b0054a8 100644 --- a/client/lib/src/op/KeepAliveOp.h +++ b/client/lib/src/op/KeepAliveOp.h @@ -25,6 +25,7 @@ class KeepAliveOp final : public Op public: explicit KeepAliveOp(ClientImpl& client); + void forceSendPing(); void messageSent(); using Base::handle; @@ -38,6 +39,7 @@ class KeepAliveOp final : public Op private: void restartPingTimer(); void restartRecvTimer(); + void restartRespTimer(); void sendPing(); void pingTimeoutInternal(); diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index 2b5c6bdd..aa692d70 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -22,6 +22,7 @@ struct CC_MqttsnSubscribe {}; struct CC_MqttsnUnsubscribe {}; struct CC_MqttsnPublish {}; struct CC_MqttsnWill {}; +struct CC_MqttsnSleep {}; namespace { @@ -69,11 +70,21 @@ inline cc_mqttsn_client::op::DisconnectOp* disconnectOpFromHandle(CC_MqttsnDisco return reinterpret_cast(handle); } +inline cc_mqttsn_client::op::DisconnectOp* disconnectOpFromHandle(CC_MqttsnSleepHandle handle) +{ + return reinterpret_cast(handle); +} + inline CC_MqttsnDisconnectHandle handleFromDisconnectOp(cc_mqttsn_client::op::DisconnectOp* op) { return reinterpret_cast(op); } +inline CC_MqttsnSleepHandle sleepHandleFromDisconnectOp(cc_mqttsn_client::op::DisconnectOp* op) +{ + return reinterpret_cast(op); +} + inline cc_mqttsn_client::op::SubscribeOp* subscribeOpFromHandle(CC_MqttsnSubscribeHandle handle) { return reinterpret_cast(handle); @@ -435,6 +446,12 @@ unsigned long long cc_mqttsn_##NAME##client_get_incoming_topic_id_storage_limit( return static_cast(clientFromHandle(client)->getIncomingRegTopicsLimit()); } +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_asleep_check_messages(CC_MqttsnClientHandle client) +{ + COMMS_ASSERT(client != nullptr); + return clientFromHandle(client)->asleepCheckMessages(); +} + CC_MqttsnSearchHandle cc_mqttsn_##NAME##client_search_prepare( [[maybe_unused]] CC_MqttsnClientHandle client, [[maybe_unused]] CC_MqttsnErrorCode* ec) @@ -684,10 +701,10 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect( return cc_mqttsn_##NAME##client_connect_send(connect, cb, cbData); } -CC_MqttsnConnectionState cc_mqttsn_##NAME##client_get_connection_state(CC_MqttsnClientHandle client) +CC_MqttsnConnectionStatus cc_mqttsn_##NAME##client_get_connection_status(CC_MqttsnClientHandle client) { COMMS_ASSERT(client != nullptr); - return clientFromHandle(client)->sessionState().m_connectionState; + return clientFromHandle(client)->sessionState().m_connectionStatus; } CC_MqttsnDisconnectHandle cc_mqttsn_##NAME##client_disconnect_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec) @@ -1168,6 +1185,109 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_will( return cc_mqttsn_##NAME##client_will_send(will, cb, cbData); } +CC_MqttsnSleepHandle cc_mqttsn_##NAME##client_sleep_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec) +{ + COMMS_ASSERT(client != nullptr); + return sleepHandleFromDisconnectOp(clientFromHandle(client)->disconnectPrepare(ec)); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_sleep_set_retry_period(CC_MqttsnSleepHandle handle, unsigned ms) +{ + COMMS_ASSERT(handle != nullptr); + if (ms == 0U) { + disconnectOpFromHandle(handle)->client().errorLog("The retry period must be greater than 0"); + return CC_MqttsnErrorCode_BadParam; + } + + COMMS_ASSERT(handle != nullptr); + disconnectOpFromHandle(handle)->setRetryPeriod(ms); + return CC_MqttsnErrorCode_Success; +} + +unsigned cc_mqttsn_##NAME##client_sleep_get_retry_period(CC_MqttsnSleepHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return disconnectOpFromHandle(handle)->getRetryPeriod(); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_sleep_set_retry_count(CC_MqttsnSleepHandle handle, unsigned count) +{ + COMMS_ASSERT(handle != nullptr); + disconnectOpFromHandle(handle)->setRetryCount(count); + return CC_MqttsnErrorCode_Success; +} + +unsigned cc_mqttsn_##NAME##client_sleep_get_retry_count(CC_MqttsnSleepHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return disconnectOpFromHandle(handle)->getRetryCount(); +} + +void cc_mqttsn_##NAME##client_sleep_init_config(CC_MqttsnSleepConfig* config) +{ + COMMS_ASSERT(config != nullptr); + *config = CC_MqttsnSleepConfig(); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_sleep_config(CC_MqttsnSleepHandle handle, const CC_MqttsnSleepConfig* config) +{ + COMMS_ASSERT(handle != nullptr); + return disconnectOpFromHandle(handle)->config(config); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_sleep_send(CC_MqttsnSleepHandle handle, CC_MqttsnSleepCompleteCb cb, void* cbData) +{ + static_assert( + std::is_same::value, + "Sleep op uses the disconnect functionality, callbacks must be of the same type"); + + COMMS_ASSERT(handle != nullptr); + auto* op = disconnectOpFromHandle(handle); + if (!op->isSleepConfigured()) { + op->client().errorLog("Sleep duration is not configured"); + cc_mqttsn_##NAME##client_sleep_cancel(handle); + return CC_MqttsnErrorCode_InsufficientConfig; + } + + return op->send(cb, cbData); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_sleep_cancel(CC_MqttsnSleepHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return disconnectOpFromHandle(handle)->cancel(); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_sleep( + CC_MqttsnClientHandle client, + const CC_MqttsnSleepConfig* config, + CC_MqttsnSleepCompleteCb cb, + void* cbData) +{ + auto ec = CC_MqttsnErrorCode_Success; + auto sleep = cc_mqttsn_##NAME##client_sleep_prepare(client, &ec); + if (sleep == nullptr) { + return ec; + } + + auto cancelOnExitGuard = + comms::util::makeScopeGuard( + [sleep]() + { + [[maybe_unused]] auto ecTmp = cc_mqttsn_##NAME##client_sleep_cancel(sleep); + }); + + if (config != nullptr) { + ec = cc_mqttsn_##NAME##client_sleep_config(sleep, config); + if (ec != CC_MqttsnErrorCode_Success) { + return ec; + } + } + + cancelOnExitGuard.release(); + return cc_mqttsn_##NAME##client_sleep_send(sleep, cb, cbData); +} + // --------------------- Callbacks --------------------- void cc_mqttsn_##NAME##client_set_next_tick_program_callback( diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index de053b5c..3e7a35d8 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -25,6 +25,7 @@ extern "C" { /// @defgroup unsubscribe "Unsubscribe Operation Data Types and Functions" /// @defgroup publish "Publish Operation Data Types and Functions" /// @defgroup will "Will Update Operation Data Types and Functions" +/// @defgroup sleep "Enter Sleep State Operation Data Types and Functions" /// @brief Allocate new client. /// @details When work with the client is complete, @ref cc_mqttsn_##NAME##client_free() @@ -277,6 +278,11 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_incoming_topic_id_storage_limit( /// @ingroup client unsigned long long cc_mqttsn_##NAME##client_get_incoming_topic_id_storage_limit(CC_MqttsnClientHandle client); +/// @brief Check messages when in "asleep" state. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @ingroup client +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_asleep_check_messages(CC_MqttsnClientHandle client); + /// @brief Prepare "search" operation. /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[out] ec Error code reporting result of the operation. Can be NULL. @@ -467,7 +473,7 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect( /// @brief Check the inner state of the library of whether it's connected to the gateway. /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @ingroup connect -CC_MqttsnConnectionState cc_mqttsn_##NAME##client_get_connection_state(CC_MqttsnClientHandle client); +CC_MqttsnConnectionStatus cc_mqttsn_##NAME##client_get_connection_status(CC_MqttsnClientHandle client); /// @brief Prepare "disconnect" operation. /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. @@ -887,7 +893,94 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_will( CC_MqttsnClientHandle client, const CC_MqttsnWillConfig* config, CC_MqttsnWillCompleteCb cb, - void* cbData); + void* cbData); + +/// @brief Prepare "sleep" operation. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[out] ec Error code reporting result of the operation. Can be NULL. +/// @return Handle of the "sleep" operation, will be NULL in case of failure. To analyze the reason failure use "ec" output parameter. +/// @post The "sleep" operation is allocated, use either @ref cc_mqttsn_##NAME##client_sleep_send() +/// or @ref cc_mqttsn_##NAME##client_sleep_cancel() to prevent memory leaks. +/// @ingroup sleep +CC_MqttsnSleepHandle cc_mqttsn_##NAME##client_sleep_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec); + +/// @brief Configure the retry period for the "sleep" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_sleep_prepare() function. +/// @param[in] ms Retry period in @b milliseconds. +/// @return Result code of the call. +/// @ingroup sleep +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_sleep_set_retry_period(CC_MqttsnSleepHandle handle, unsigned ms); + +/// @brief Retrieve the configured retry period for the "sleep" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_sleep_prepare() function. +/// @return Retry period duration in @b milliseconds. +/// @ingroup sleep +unsigned cc_mqttsn_##NAME##client_sleep_get_retry_period(CC_MqttsnSleepHandle handle); + +/// @brief Configure the retry count for the "sleep" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_sleep_prepare() function. +/// @param[in] count Number of retries. +/// @return Result code of the call. +/// @ingroup sleep +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_sleep_set_retry_count(CC_MqttsnSleepHandle handle, unsigned count); + +/// @brief Retrieve the configured retry count for the "sleep" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_sleep_prepare() function. +/// @return Number of retries. +/// @ingroup sleep +unsigned cc_mqttsn_##NAME##client_sleep_get_retry_count(CC_MqttsnSleepHandle handle); + +/// @brief Intialize the @ref CC_MqttsnSleepConfig configuration structure. +/// @param[out] config Configuration structure. Must not be NULL. +/// @ingroup sleep +void cc_mqttsn_##NAME##client_sleep_init_config(CC_MqttsnSleepConfig* config); + +/// @brief Perform configuration of the "sleep" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_sleep_prepare() function. +/// @param[in] config Configuration structure. Must NOT be NULL. Does not need to be preserved after invocation. +/// @return Result code of the call. +/// @ingroup sleep +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_sleep_config(CC_MqttsnSleepHandle handle, const CC_MqttsnSleepConfig* config); + +/// @brief Send the "sleep" operation +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_sleep_prepare() function. +/// @param[in] cb Callback to be invoked when "sleep" operation is complete. +/// @param[in] cbData Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +/// @return Result code of the call. +/// @post The handle of the "sleep" operation can be discarded. +/// @post The provided callback will be invoked when the "sleep" operation is complete if and only if +/// the function returns @ref CC_MqttsnErrorCode_Success. +/// @ingroup sleep +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_sleep_send(CC_MqttsnSleepHandle handle, CC_MqttsnSleepCompleteCb cb, void* cbData); + +/// @brief Cancel the allocated "sleep" operation +/// @details In case the @ref cc_mqttsn_##NAME##client_sleep_send() function was successfully called before, +/// the operation is cancelled @b without callback invocation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_sleep_prepare() function. +/// @return Result code of the call. +/// @post The handle of the "sleep" operation is no longer valid and must be discarded. +/// @ingroup sleep +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_sleep_cancel(CC_MqttsnSleepHandle handle); + +/// @brief Prepare and send "sleep" request in one go +/// @details Abstracts away sequence of the following functions invocation: +/// @li @ref cc_mqttsn_##NAME##client_sleep_prepare() +/// @li @ref cc_mqttsn_##NAME##client_sleep_config() +/// @li @ref cc_mqttsn_##NAME##client_sleep_send() +/// +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[in] config Subscription configuration. +/// @param[in] cb Callback to be invoked when "sleep" operation is complete. +/// @param[in] cbData Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +/// @return Result code of the call. +/// @ingroup sleep +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_sleep( + CC_MqttsnClientHandle client, + const CC_MqttsnSleepConfig* config, + CC_MqttsnSleepCompleteCb cb, + void* cbData); // --------------------- Callbacks --------------------- diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index f641e6b3..7dfe67e6 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -76,7 +76,7 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_connect_send != nullptr); test_assert(m_funcs.m_connect_cancel != nullptr); test_assert(m_funcs.m_connect != nullptr); - test_assert(m_funcs.m_get_connection_state != nullptr); + test_assert(m_funcs.m_get_connection_status != nullptr); test_assert(m_funcs.m_disconnect_prepare != nullptr); test_assert(m_funcs.m_disconnect_set_retry_period != nullptr); test_assert(m_funcs.m_disconnect_get_retry_period != nullptr); @@ -580,7 +580,7 @@ void UnitTestCommonBase::unitTestDoConnect(CC_MqttsnClient* client, const CC_Mqt } test_assert(!unitTestHasConnectCompleteReport()); - test_assert(apiGetConnectionState(client) == CC_MqttsnConnectionState_Disconnected); + test_assert(apiGetConnectionState(client) == CC_MqttsnConnectionStatus_Disconnected); test_assert(unitTestHasTickReq()); unitTestTick(client, 100); } @@ -593,7 +593,7 @@ void UnitTestCommonBase::unitTestDoConnect(CC_MqttsnClient* client, const CC_Mqt auto connectReport = unitTestConnectCompleteReport(); test_assert(connectReport->m_status== CC_MqttsnAsyncOpStatus_Complete) test_assert(connectReport->m_info.m_returnCode == CC_MqttsnReturnCode_Accepted) - test_assert(apiGetConnectionState(client) == CC_MqttsnConnectionState_Connected); + test_assert(apiGetConnectionState(client) == CC_MqttsnConnectionStatus_Connected); } void UnitTestCommonBase::unitTestDoConnectBasic(CC_MqttsnClient* client, const std::string& clientId, bool cleanSession) @@ -907,9 +907,9 @@ CC_MqttsnErrorCode UnitTestCommonBase::apiConnectConfigWill(CC_MqttsnConnectHand return m_funcs.m_connect_config_will(connect, config); } -CC_MqttsnConnectionState UnitTestCommonBase::apiGetConnectionState(CC_MqttsnClient* client) +CC_MqttsnConnectionStatus UnitTestCommonBase::apiGetConnectionState(CC_MqttsnClient* client) { - return m_funcs.m_get_connection_state(client); + return m_funcs.m_get_connection_status(client); } CC_MqttsnDisconnectHandle UnitTestCommonBase::apiDisconnectPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec) diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index f1c4f7d3..4a5b9990 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -66,7 +66,7 @@ class UnitTestCommonBase CC_MqttsnErrorCode (*m_connect_send)(CC_MqttsnConnectHandle, CC_MqttsnConnectCompleteCb, void*) = nullptr; CC_MqttsnErrorCode (*m_connect_cancel)(CC_MqttsnConnectHandle) = nullptr; CC_MqttsnErrorCode (*m_connect)(CC_MqttsnClientHandle, const CC_MqttsnConnectConfig*, const CC_MqttsnWillConfig*, CC_MqttsnConnectCompleteCb, void*) = nullptr; - CC_MqttsnConnectionState (*m_get_connection_state)(CC_MqttsnClientHandle) = nullptr; + CC_MqttsnConnectionStatus (*m_get_connection_status)(CC_MqttsnClientHandle) = nullptr; CC_MqttsnDisconnectHandle (*m_disconnect_prepare)(CC_MqttsnClientHandle, CC_MqttsnErrorCode*) = nullptr; CC_MqttsnErrorCode (*m_disconnect_set_retry_period)(CC_MqttsnDisconnectHandle, unsigned ms) = nullptr; unsigned (*m_disconnect_get_retry_period)(CC_MqttsnDisconnectHandle) = nullptr; @@ -445,7 +445,7 @@ class UnitTestCommonBase void apiConnectInitConfigWill(CC_MqttsnWillConfig* config); CC_MqttsnErrorCode apiConnectConfig(CC_MqttsnConnectHandle connect, const CC_MqttsnConnectConfig* config); CC_MqttsnErrorCode apiConnectConfigWill(CC_MqttsnConnectHandle connect, const CC_MqttsnWillConfig* config); - CC_MqttsnConnectionState apiGetConnectionState(CC_MqttsnClient* client); + CC_MqttsnConnectionStatus apiGetConnectionState(CC_MqttsnClient* client); CC_MqttsnDisconnectHandle apiDisconnectPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec = nullptr); CC_MqttsnErrorCode apiDisconnectSetRetryCount(CC_MqttsnDisconnectHandle disconnect, unsigned count); diff --git a/client/lib/test/UnitTestConnect.th b/client/lib/test/UnitTestConnect.th index cf1554cd..65914abf 100644 --- a/client/lib/test/UnitTestConnect.th +++ b/client/lib/test/UnitTestConnect.th @@ -75,7 +75,7 @@ void UnitTestConnect::test1() TS_ASSERT_EQUALS(connectReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted) TS_ASSERT(unitTestHasTickReq()); // For keep alive - TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Connected); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionStatus_Connected); } void UnitTestConnect::test2() @@ -164,7 +164,7 @@ void UnitTestConnect::test2() } TS_ASSERT(!unitTestHasConnectCompleteReport()); - TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Disconnected); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionStatus_Disconnected); TS_ASSERT(unitTestHasTickReq()); unitTestTick(client, 100); @@ -180,7 +180,7 @@ void UnitTestConnect::test2() TS_ASSERT_EQUALS(connectReport->m_info.m_returnCode, CC_MqttsnReturnCode_Accepted) TS_ASSERT(unitTestHasTickReq()); // For keep alive - TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Connected); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionStatus_Connected); } void UnitTestConnect::test3() @@ -244,7 +244,7 @@ void UnitTestConnect::test3() TS_ASSERT_EQUALS(connectReport->m_status, CC_MqttsnAsyncOpStatus_Timeout) TS_ASSERT(!unitTestHasTickReq()); - TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Disconnected); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionStatus_Disconnected); } void UnitTestConnect::test4() @@ -342,7 +342,7 @@ void UnitTestConnect::test4() TS_ASSERT_EQUALS(connectReport->m_status, CC_MqttsnAsyncOpStatus_Timeout) TS_ASSERT(!unitTestHasTickReq()); - TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Disconnected); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionStatus_Disconnected); } @@ -435,7 +435,7 @@ void UnitTestConnect::test5() } TS_ASSERT(!unitTestHasConnectCompleteReport()); - TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Disconnected); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionStatus_Disconnected); TS_ASSERT(unitTestHasTickReq()); unitTestTick(client); @@ -448,7 +448,7 @@ void UnitTestConnect::test5() } TS_ASSERT(!unitTestHasConnectCompleteReport()); - TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Disconnected); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionStatus_Disconnected); TS_ASSERT(unitTestHasTickReq()); unitTestTick(client); @@ -457,7 +457,7 @@ void UnitTestConnect::test5() TS_ASSERT_EQUALS(connectReport->m_status, CC_MqttsnAsyncOpStatus_Timeout) TS_ASSERT(!unitTestHasTickReq()); - TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Disconnected); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionStatus_Disconnected); } void UnitTestConnect::test6() @@ -505,7 +505,7 @@ void UnitTestConnect::test6() TS_ASSERT_EQUALS(connectReport->m_status, CC_MqttsnAsyncOpStatus_Timeout) TS_ASSERT(!unitTestHasTickReq()); - TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Disconnected); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionStatus_Disconnected); } // TODO: test reconnect without disconnecting \ No newline at end of file diff --git a/client/lib/test/UnitTestDefaultBase.cpp b/client/lib/test/UnitTestDefaultBase.cpp index c20458b3..536dfd52 100644 --- a/client/lib/test/UnitTestDefaultBase.cpp +++ b/client/lib/test/UnitTestDefaultBase.cpp @@ -57,7 +57,7 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_connect_send = &cc_mqttsn_client_connect_send; funcs.m_connect_cancel = &cc_mqttsn_client_connect_cancel; funcs.m_connect = &cc_mqttsn_client_connect; - funcs.m_get_connection_state = &cc_mqttsn_client_get_connection_state; + funcs.m_get_connection_status = &cc_mqttsn_client_get_connection_status; funcs.m_disconnect_prepare = &cc_mqttsn_client_disconnect_prepare; funcs.m_disconnect_set_retry_period = &cc_mqttsn_client_disconnect_set_retry_period; funcs.m_disconnect_get_retry_period = &cc_mqttsn_client_disconnect_get_retry_period; diff --git a/client/lib/test/UnitTestDisconnect.th b/client/lib/test/UnitTestDisconnect.th index e900f1c1..6700dfed 100644 --- a/client/lib/test/UnitTestDisconnect.th +++ b/client/lib/test/UnitTestDisconnect.th @@ -66,7 +66,7 @@ void UnitTestDisconnect::test1() TS_ASSERT_EQUALS(disconnectReport->m_status, CC_MqttsnAsyncOpStatus_Complete) TS_ASSERT(!unitTestHasTickReq()); // No more keep alive - TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Disconnected); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionStatus_Disconnected); } void UnitTestDisconnect::test2() @@ -120,7 +120,7 @@ void UnitTestDisconnect::test2() TS_ASSERT_EQUALS(disconnectReport->m_status, CC_MqttsnAsyncOpStatus_Timeout) TS_ASSERT(unitTestHasTickReq()); // Still hase keep alive - TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Connected); // Still connected + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionStatus_Connected); // Still connected } void UnitTestDisconnect::test3() @@ -179,7 +179,7 @@ void UnitTestDisconnect::test3() TS_ASSERT_EQUALS(disconnectReport->m_status, CC_MqttsnAsyncOpStatus_Complete) TS_ASSERT(!unitTestHasTickReq()); // No more keep alive - TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Disconnected); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionStatus_Disconnected); } void UnitTestDisconnect::test4() @@ -205,7 +205,7 @@ void UnitTestDisconnect::test4() TS_ASSERT_EQUALS(disconnectReport->m_reason, CC_MqttsnGatewayDisconnectReason_DisconnectMsg); TS_ASSERT(!unitTestHasTickReq()); // No more keep alive - TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Disconnected); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionStatus_Disconnected); } void UnitTestDisconnect::test5() @@ -251,5 +251,5 @@ void UnitTestDisconnect::test5() TS_ASSERT_EQUALS(disconnectReport->m_reason, CC_MqttsnGatewayDisconnectReason_NoGatewayResponse); TS_ASSERT(!unitTestHasTickReq()); // No more keep alive - TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionState_Disconnected); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionStatus_Disconnected); } From 23b934a0b9b5b4f36973036dd79b7c8d678c7d74 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 9 Aug 2024 08:40:17 +1000 Subject: [PATCH 089/106] Client sleep unittesting. --- client/lib/src/ClientImpl.cpp | 4 +- client/lib/src/SessionState.h | 4 + client/lib/src/op/ConnectOp.cpp | 2 + client/lib/src/op/DisconnectOp.cpp | 2 +- client/lib/src/op/KeepAliveOp.cpp | 9 +- client/lib/test/CMakeLists.txt | 1 + client/lib/test/UnitTestCommonBase.cpp | 77 ++++++++- client/lib/test/UnitTestCommonBase.h | 39 ++++- client/lib/test/UnitTestDefaultBase.cpp | 14 +- client/lib/test/UnitTestSleep.th | 206 ++++++++++++++++++++++++ 10 files changed, 347 insertions(+), 11 deletions(-) create mode 100644 client/lib/test/UnitTestSleep.th diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 12840548..dba86e3c 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -251,7 +251,7 @@ op::ConnectOp* ClientImpl::connectPrepare(CC_MqttsnErrorCode* ec) if (!m_disconnectOps.empty()) { // Already allocated - errorLog("Another disconnect operation is in progress."); + errorLog("Another disconnect or sleep operation is in progress."); updateEc(ec, CC_MqttsnErrorCode_Busy); break; } @@ -308,7 +308,7 @@ op::DisconnectOp* ClientImpl::disconnectPrepare(CC_MqttsnErrorCode* ec) } if (!m_disconnectOps.empty()) { - errorLog("Another disconnect operation is in progress."); + errorLog("Another disconnect or sleep operation is in progress."); updateEc(ec, CC_MqttsnErrorCode_Busy); break; } diff --git a/client/lib/src/SessionState.h b/client/lib/src/SessionState.h index 06f2595a..a76f8715 100644 --- a/client/lib/src/SessionState.h +++ b/client/lib/src/SessionState.h @@ -7,13 +7,17 @@ #pragma once +#include "ProtocolDefs.h" + namespace cc_mqttsn_client { struct SessionState { + using ClientIdStr = ConnectMsg::Field_clientId::ValueType; static constexpr unsigned DefaultKeepAlive = 60; + ClientIdStr m_clientId; unsigned m_keepAliveMs = 0U; CC_MqttsnConnectionStatus m_connectionStatus = CC_MqttsnConnectionStatus_Disconnected; bool m_disconnecting = false; diff --git a/client/lib/src/op/ConnectOp.cpp b/client/lib/src/op/ConnectOp.cpp index d1e23af0..85b50439 100644 --- a/client/lib/src/op/ConnectOp.cpp +++ b/client/lib/src/op/ConnectOp.cpp @@ -151,6 +151,7 @@ CC_MqttsnErrorCode ConnectOp::send(CC_MqttsnConnectCompleteCb cb, void* cbData) return ec; } + client().sessionState().m_clientId.clear(); if (m_connectMsg.field_flags().field_mid().getBitValue_CleanSession()) { // Don't wait for acknowledgement, assume state cleared upon send client().reuseState() = ReuseState(); @@ -250,6 +251,7 @@ void ConnectOp::handle(ConnackMsg& msg) #endif // #if CC_MQTTSN_CLIENT_HAS_WILL if (info.m_returnCode == CC_MqttsnReturnCode_Accepted) { + client().sessionState().m_clientId = m_connectMsg.field_clientId().value(); client().sessionState().m_keepAliveMs = comms::units::getMilliseconds(m_connectMsg.field_duration()); client().gatewayConnected(); } diff --git a/client/lib/src/op/DisconnectOp.cpp b/client/lib/src/op/DisconnectOp.cpp index df47319a..b5b623f7 100644 --- a/client/lib/src/op/DisconnectOp.cpp +++ b/client/lib/src/op/DisconnectOp.cpp @@ -169,7 +169,7 @@ CC_MqttsnErrorCode DisconnectOp::sendInternal() void DisconnectOp::timeoutInternal() { if (getRetryCount() == 0U) { - errorLog("All retries of the disconnect operation have been exhausted."); + errorLog("All retries of the disconnect or sleep operation have been exhausted."); completeOpInternal(CC_MqttsnAsyncOpStatus_Timeout); return; } diff --git a/client/lib/src/op/KeepAliveOp.cpp b/client/lib/src/op/KeepAliveOp.cpp index 04c0b1c5..717ddfc7 100644 --- a/client/lib/src/op/KeepAliveOp.cpp +++ b/client/lib/src/op/KeepAliveOp.cpp @@ -115,8 +115,15 @@ void KeepAliveOp::sendPing() return; // Ping has already been sent, waiting for response } + auto& cl = client(); + auto& st = cl.sessionState(); + PingreqMsg msg; - client().sendMessage(msg); + if (st.m_connectionStatus == CC_MqttsnConnectionStatus_Asleep) { + msg.field_clientId().setValue(st.m_clientId); + } + + cl.sendMessage(msg); restartRespTimer(); } diff --git a/client/lib/test/CMakeLists.txt b/client/lib/test/CMakeLists.txt index fc267a6b..92ad7117 100644 --- a/client/lib/test/CMakeLists.txt +++ b/client/lib/test/CMakeLists.txt @@ -47,6 +47,7 @@ if (TARGET cc::cc_mqttsn_client) cc_mqttsn_client_add_unit_test(UnitTestSubscribe ${DEFAULT_BASE_LIB_NAME}) cc_mqttsn_client_add_unit_test(UnitTestUnsubscribe ${DEFAULT_BASE_LIB_NAME}) cc_mqttsn_client_add_unit_test(UnitTestWill ${DEFAULT_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(UnitTestSleep ${DEFAULT_BASE_LIB_NAME}) endif () # TODO diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index 7dfe67e6..bcbdf56e 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -54,6 +54,7 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_get_outgoing_topic_id_storage_limit != nullptr); test_assert(m_funcs.m_set_incoming_topic_id_storage_limit != nullptr); test_assert(m_funcs.m_get_incoming_topic_id_storage_limit != nullptr); + test_assert(m_funcs.m_asleep_check_messages != nullptr); test_assert(m_funcs.m_search_prepare != nullptr); test_assert(m_funcs.m_search_set_retry_period != nullptr); test_assert(m_funcs.m_search_get_retry_period != nullptr); @@ -124,8 +125,17 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_will_config != nullptr); test_assert(m_funcs.m_will_send != nullptr); test_assert(m_funcs.m_will_cancel != nullptr); - test_assert(m_funcs.m_will != nullptr); - + test_assert(m_funcs.m_will != nullptr); + test_assert(m_funcs.m_sleep_prepare != nullptr); + test_assert(m_funcs.m_sleep_set_retry_period != nullptr); + test_assert(m_funcs.m_sleep_get_retry_period != nullptr); + test_assert(m_funcs.m_sleep_set_retry_count != nullptr); + test_assert(m_funcs.m_sleep_get_retry_count != nullptr); + test_assert(m_funcs.m_sleep_init_config != nullptr); + test_assert(m_funcs.m_sleep_config != nullptr); + test_assert(m_funcs.m_sleep_send != nullptr); + test_assert(m_funcs.m_sleep_cancel != nullptr); + test_assert(m_funcs.m_sleep != nullptr); test_assert(m_funcs.m_set_next_tick_program_callback != nullptr); test_assert(m_funcs.m_set_cancel_next_tick_wait_callback != nullptr); test_assert(m_funcs.m_set_send_output_data_callback != nullptr); @@ -800,6 +810,28 @@ CC_MqttsnErrorCode UnitTestCommonBase::unitTestWillSend(CC_MqttsnWillHandle will return m_funcs.m_will_send(will, &UnitTestCommonBase::unitTestWillCompleteCb, this); } +bool UnitTestCommonBase::unitTestHasSleepCompleteReport() const +{ + return !m_data.m_sleepCompleteReports.empty(); +} + +UnitTestCommonBase::UnitTestSleepCompleteReportPtr UnitTestCommonBase::unitTestSleepCompleteReport(bool mustExist) +{ + if (!unitTestHasSleepCompleteReport()) { + test_assert(!mustExist); + return UnitTestSleepCompleteReportPtr(); + } + + auto ptr = std::move(m_data.m_sleepCompleteReports.front()); + m_data.m_sleepCompleteReports.pop_front(); + return ptr; +} + +CC_MqttsnErrorCode UnitTestCommonBase::unitTestSleepSend(CC_MqttsnSleepHandle sleep) +{ + return m_funcs.m_sleep_send(sleep, &UnitTestCommonBase::unitTestSleepCompleteCb, this); +} + bool UnitTestCommonBase::unitTestHasReceivedMessage() const { return !m_data.m_recvMsgs.empty(); @@ -827,11 +859,21 @@ CC_MqttsnErrorCode UnitTestCommonBase::apiSetDefaultRetryPeriod(CC_MqttsnClient* return m_funcs.m_set_default_retry_period(client, value); } +unsigned UnitTestCommonBase::apiGetDefaultRetryPeriod(CC_MqttsnClientHandle client) +{ + return m_funcs.m_get_default_retry_period(client); +} + CC_MqttsnErrorCode UnitTestCommonBase::apiSetDefaultRetryCount(CC_MqttsnClient* client, unsigned value) { return m_funcs.m_set_default_retry_count(client, value); } +unsigned UnitTestCommonBase::apiGetDefaultRetryCount(CC_MqttsnClientHandle client) +{ + return m_funcs.m_get_default_retry_count(client); +} + CC_MqttsnErrorCode UnitTestCommonBase::apiSetVerifyIncomingMsgSubscribed(CC_MqttsnClient* client, bool enabled) { return m_funcs.m_set_verify_incoming_msg_subscribed(client, enabled); @@ -1060,6 +1102,31 @@ CC_MqttsnErrorCode UnitTestCommonBase::apiWillCancel(CC_MqttsnWillHandle will) return m_funcs.m_will_cancel(will); } +CC_MqttsnSleepHandle UnitTestCommonBase::apiSleepPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec) +{ + return m_funcs.m_sleep_prepare(client, ec); +} + +CC_MqttsnErrorCode UnitTestCommonBase::apiSleepSetRetryCount(CC_MqttsnSleepHandle sleep, unsigned count) +{ + return m_funcs.m_sleep_set_retry_count(sleep, count); +} + +void UnitTestCommonBase::apiSleepInitConfig(CC_MqttsnSleepConfig* config) +{ + m_funcs.m_sleep_init_config(config); +} + +CC_MqttsnErrorCode UnitTestCommonBase::apiSleepConfig(CC_MqttsnSleepHandle sleep, const CC_MqttsnSleepConfig* config) +{ + return m_funcs.m_sleep_config(sleep, config); +} + +CC_MqttsnErrorCode UnitTestCommonBase::apiSleepCancel(CC_MqttsnSleepHandle sleep) +{ + return m_funcs.m_sleep_cancel(sleep); +} + void UnitTestCommonBase::unitTestMessageReportCb(void* data, const CC_MqttsnMessageInfo* msgInfo) { test_assert(msgInfo != nullptr); @@ -1143,4 +1210,10 @@ void UnitTestCommonBase::unitTestWillCompleteCb(void* data, CC_MqttsnAsyncOpStat { auto* thisPtr = asThis(data); thisPtr->m_data.m_willCompleteReports.push_back(std::make_unique(status, info)); +} + +void UnitTestCommonBase::unitTestSleepCompleteCb(void* data, CC_MqttsnAsyncOpStatus status) +{ + auto* thisPtr = asThis(data); + thisPtr->m_data.m_sleepCompleteReports.push_back(std::make_unique(status)); } \ No newline at end of file diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index 4a5b9990..9d39797f 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -44,6 +44,7 @@ class UnitTestCommonBase unsigned long long (*m_get_outgoing_topic_id_storage_limit)(CC_MqttsnClientHandle) = nullptr; CC_MqttsnErrorCode (*m_set_incoming_topic_id_storage_limit)(CC_MqttsnClientHandle, unsigned long long) = nullptr; unsigned long long (*m_get_incoming_topic_id_storage_limit)(CC_MqttsnClientHandle) = nullptr; + CC_MqttsnErrorCode (*m_asleep_check_messages)(CC_MqttsnClientHandle client) = nullptr; CC_MqttsnSearchHandle (*m_search_prepare)(CC_MqttsnClientHandle, CC_MqttsnErrorCode*) = nullptr; CC_MqttsnErrorCode (*m_search_set_retry_period)(CC_MqttsnSearchHandle, unsigned) = nullptr; unsigned (*m_search_get_retry_period)(CC_MqttsnSearchHandle) = nullptr; @@ -115,7 +116,16 @@ class UnitTestCommonBase CC_MqttsnErrorCode (*m_will_send)(CC_MqttsnWillHandle, CC_MqttsnWillCompleteCb, void*) = nullptr; CC_MqttsnErrorCode (*m_will_cancel)(CC_MqttsnWillHandle) = nullptr; CC_MqttsnErrorCode (*m_will)(CC_MqttsnClientHandle, const CC_MqttsnWillConfig*, CC_MqttsnWillCompleteCb, void* cbData) = nullptr; - + CC_MqttsnSleepHandle (*m_sleep_prepare)(CC_MqttsnClientHandle, CC_MqttsnErrorCode*) = nullptr; + CC_MqttsnErrorCode (*m_sleep_set_retry_period)(CC_MqttsnSleepHandle, unsigned) = nullptr; + unsigned (*m_sleep_get_retry_period)(CC_MqttsnSleepHandle) = nullptr; + CC_MqttsnErrorCode (*m_sleep_set_retry_count)(CC_MqttsnSleepHandle, unsigned) = nullptr; + unsigned (*m_sleep_get_retry_count)(CC_MqttsnSleepHandle) = nullptr; + void (*m_sleep_init_config)(CC_MqttsnSleepConfig*) = nullptr; + CC_MqttsnErrorCode (*m_sleep_config)(CC_MqttsnSleepHandle, const CC_MqttsnSleepConfig*) = nullptr; + CC_MqttsnErrorCode (*m_sleep_send)(CC_MqttsnSleepHandle, CC_MqttsnSleepCompleteCb, void*) = nullptr; + CC_MqttsnErrorCode (*m_sleep_cancel)(CC_MqttsnSleepHandle) = nullptr; + CC_MqttsnErrorCode (*m_sleep)(CC_MqttsnClientHandle, const CC_MqttsnSleepConfig*, CC_MqttsnSleepCompleteCb, void* cbData) = nullptr; void (*m_set_next_tick_program_callback)(CC_MqttsnClientHandle, CC_MqttsnNextTickProgramCb, void*) = nullptr; void (*m_set_cancel_next_tick_wait_callback)(CC_MqttsnClientHandle, CC_MqttsnCancelNextTickWaitCb, void*) = nullptr; void (*m_set_send_output_data_callback)(CC_MqttsnClientHandle, CC_MqttsnSendOutputDataCb, void*) = nullptr; @@ -272,7 +282,6 @@ class UnitTestCommonBase CC_MqttsnTopicId m_topicId = 0U; }; - struct UnitTestUnsubscribeCompleteReport { CC_MqttsnUnsubscribeHandle m_handle = nullptr; @@ -335,6 +344,15 @@ class UnitTestCommonBase using UnitTestWillCompleteReportPtr = std::unique_ptr; using UnitTestWillCompleteReportList = std::list; + struct UnitTestSleepCompleteReport + { + CC_MqttsnAsyncOpStatus m_status = CC_MqttsnAsyncOpStatus_ValuesLimit; + + UnitTestSleepCompleteReport(CC_MqttsnAsyncOpStatus status) : m_status(status) {}; + }; + + using UnitTestSleepCompleteReportPtr = std::unique_ptr; + using UnitTestSleepCompleteReportList = std::list; struct UnitTestMessageInfo { @@ -422,12 +440,19 @@ class UnitTestCommonBase CC_MqttsnErrorCode unitTestWillSend(CC_MqttsnWillHandle will); + bool unitTestHasSleepCompleteReport() const; + UnitTestSleepCompleteReportPtr unitTestSleepCompleteReport(bool mustExist = true); + + CC_MqttsnErrorCode unitTestSleepSend(CC_MqttsnSleepHandle sleep); + bool unitTestHasReceivedMessage() const; UnitTestMessageInfoPtr unitTestReceivedMessage(bool mustExist = true); void apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen); CC_MqttsnErrorCode apiSetDefaultRetryPeriod(CC_MqttsnClient* client, unsigned value); + unsigned apiGetDefaultRetryPeriod(CC_MqttsnClientHandle client); CC_MqttsnErrorCode apiSetDefaultRetryCount(CC_MqttsnClient* client, unsigned value); + unsigned apiGetDefaultRetryCount(CC_MqttsnClientHandle client); CC_MqttsnErrorCode apiSetVerifyIncomingMsgSubscribed(CC_MqttsnClient* client, bool enabled); void apiInitGatewayInfo(CC_MqttsnGatewayInfo* info); CC_MqttsnErrorCode apiSetAvailableGatewayInfo(CC_MqttsnClient* client, const CC_MqttsnGatewayInfo* info); @@ -472,7 +497,13 @@ class UnitTestCommonBase CC_MqttsnErrorCode apiWillSetRetryCount(CC_MqttsnWillHandle will, unsigned count); void apiWillInitConfig(CC_MqttsnWillConfig* config); CC_MqttsnErrorCode apiWillConfig(CC_MqttsnWillHandle will, const CC_MqttsnWillConfig* config); - CC_MqttsnErrorCode apiWillCancel(CC_MqttsnWillHandle will); + CC_MqttsnErrorCode apiWillCancel(CC_MqttsnWillHandle will); + + CC_MqttsnSleepHandle apiSleepPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec = nullptr); + CC_MqttsnErrorCode apiSleepSetRetryCount(CC_MqttsnSleepHandle sleep, unsigned count); + void apiSleepInitConfig(CC_MqttsnSleepConfig* config); + CC_MqttsnErrorCode apiSleepConfig(CC_MqttsnSleepHandle sleep, const CC_MqttsnSleepConfig* config); + CC_MqttsnErrorCode apiSleepCancel(CC_MqttsnSleepHandle sleep); protected: @@ -494,6 +525,7 @@ class UnitTestCommonBase UnitTestUnsubscribeCompleteReportList m_unsubscribeCompleteReports; UnitTestPublishCompleteReportList m_publishCompleteReports; UnitTestWillCompleteReportList m_willCompleteReports; + UnitTestSleepCompleteReportList m_sleepCompleteReports; UnitTestMessageInfosList m_recvMsgs; }; @@ -512,6 +544,7 @@ class UnitTestCommonBase static void unitTestUnsubscribeCompleteCb(void* data, CC_MqttsnUnsubscribeHandle handle, CC_MqttsnAsyncOpStatus status); static void unitTestPublishCompleteCb(void* data, CC_MqttsnPublishHandle handle, CC_MqttsnAsyncOpStatus status, const CC_MqttsnPublishInfo* info); static void unitTestWillCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnWillInfo* info); + static void unitTestSleepCompleteCb(void* data, CC_MqttsnAsyncOpStatus status); LibFuncs m_funcs; ClientData m_data; diff --git a/client/lib/test/UnitTestDefaultBase.cpp b/client/lib/test/UnitTestDefaultBase.cpp index 536dfd52..f04f4a4a 100644 --- a/client/lib/test/UnitTestDefaultBase.cpp +++ b/client/lib/test/UnitTestDefaultBase.cpp @@ -35,6 +35,7 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_get_outgoing_topic_id_storage_limit = &cc_mqttsn_client_get_outgoing_topic_id_storage_limit; funcs.m_set_incoming_topic_id_storage_limit = &cc_mqttsn_client_set_incoming_topic_id_storage_limit; funcs.m_get_incoming_topic_id_storage_limit = &cc_mqttsn_client_get_incoming_topic_id_storage_limit; + funcs.m_asleep_check_messages = &cc_mqttsn_client_asleep_check_messages; funcs.m_search_prepare = &cc_mqttsn_client_search_prepare; funcs.m_search_set_retry_period = &cc_mqttsn_client_search_set_retry_period; funcs.m_search_get_retry_period = &cc_mqttsn_client_search_get_retry_period; @@ -105,8 +106,17 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_will_config = &cc_mqttsn_client_will_config; funcs.m_will_send = &cc_mqttsn_client_will_send; funcs.m_will_cancel = &cc_mqttsn_client_will_cancel; - funcs.m_will = &cc_mqttsn_client_will; - + funcs.m_will = &cc_mqttsn_client_will; + funcs.m_sleep_prepare = &cc_mqttsn_client_sleep_prepare; + funcs.m_sleep_set_retry_period = &cc_mqttsn_client_sleep_set_retry_period; + funcs.m_sleep_get_retry_period = &cc_mqttsn_client_sleep_get_retry_period; + funcs.m_sleep_set_retry_count = &cc_mqttsn_client_sleep_set_retry_count; + funcs.m_sleep_get_retry_count = &cc_mqttsn_client_sleep_get_retry_count; + funcs.m_sleep_init_config = &cc_mqttsn_client_sleep_init_config; + funcs.m_sleep_config = &cc_mqttsn_client_sleep_config; + funcs.m_sleep_send = &cc_mqttsn_client_sleep_send; + funcs.m_sleep_cancel = &cc_mqttsn_client_sleep_cancel; + funcs.m_sleep = &cc_mqttsn_client_sleep; funcs.m_set_next_tick_program_callback = &cc_mqttsn_client_set_next_tick_program_callback; funcs.m_set_cancel_next_tick_wait_callback = &cc_mqttsn_client_set_cancel_next_tick_wait_callback; funcs.m_set_send_output_data_callback = &cc_mqttsn_client_set_send_output_data_callback; diff --git a/client/lib/test/UnitTestSleep.th b/client/lib/test/UnitTestSleep.th new file mode 100644 index 00000000..960964bc --- /dev/null +++ b/client/lib/test/UnitTestSleep.th @@ -0,0 +1,206 @@ +#include "UnitTestDefaultBase.h" +#include "UnitTestProtocolDefs.h" + +#include "comms/units.h" + +#include + +class UnitTestSleep : public CxxTest::TestSuite, public UnitTestDefaultBase +{ +public: + void test1(); + void test2(); + +private: + virtual void setUp() override + { + unitTestSetUp(); + } + + virtual void tearDown() override + { + unitTestTearDown(); + } + + using TopicIdType = UnitTestPublishMsg::Field_flags::Field_topicIdType::ValueType; +}; + +void UnitTestSleep::test1() +{ + // Testing basic sleep and reconnect + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const CC_MqttsnTopicId TopicId = 123; + unitTestDoSubscribeTopicId(client, TopicId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const unsigned SleepDurationSec = 10 * 60; + + CC_MqttsnSleepConfig config; + apiSleepInitConfig(&config); + + config.m_duration = SleepDurationSec; + + auto sleep = apiSleepPrepare(client); + TS_ASSERT_DIFFERS(sleep, nullptr); + + auto ec = apiSleepConfig(sleep, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + unitTestSleepSend(sleep); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* disconnectMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(disconnectMsg, nullptr); + TS_ASSERT(disconnectMsg->field_duration().doesExist()); + TS_ASSERT_EQUALS(disconnectMsg->field_duration().field().value(), SleepDurationSec); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestDisconnectMsg disconnectMsg; + unitTestClientInputMessage(client, disconnectMsg); + } + + TS_ASSERT(unitTestHasSleepCompleteReport()); + auto sleepReport = unitTestSleepCompleteReport(); + TS_ASSERT_EQUALS(sleepReport->m_status, CC_MqttsnAsyncOpStatus_Complete) + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionStatus_Asleep); + + + auto period = apiGetDefaultRetryPeriod(client); + auto count = apiGetDefaultRetryCount(client) + 1U; + auto diff = (period * count); + auto expTick = (SleepDurationSec * 1000U) - diff; + + TS_ASSERT(unitTestHasTickReq()); // Timer for ping + { + auto* tickInfo = unitTestTickInfo(); + TS_ASSERT_EQUALS(tickInfo->m_req, expTick); + } + unitTestTick(client); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pingreqMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pingreqMsg, nullptr); + TS_ASSERT_EQUALS(pingreqMsg->field_clientId().value(), ClientId); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + //unitTestTick(client, 100); + + { + const UnitTestData Data = {1, 2, 3, 4}; + UnitTestPublishMsg publishMsg; + publishMsg.field_flags().field_topicIdType().value() = TopicIdType::PredefinedTopicId; + publishMsg.field_topicId().setValue(TopicId); + publishMsg.field_data().value() = Data; + unitTestClientInputMessage(client, publishMsg); + + TS_ASSERT(unitTestHasReceivedMessage()) + auto msgInfo = unitTestReceivedMessage(); + TS_ASSERT_EQUALS(msgInfo->m_topicId, TopicId); + TS_ASSERT_EQUALS(msgInfo->m_data, Data); + TS_ASSERT(!unitTestHasReceivedMessage()) + } + + { + UnitTestPingrespMsg pingrespMsg; + unitTestClientInputMessage(client, pingrespMsg); + } + + TS_ASSERT(!unitTestHasOutputData()); + TS_ASSERT(unitTestHasTickReq()); // Timer for next ping + { + auto* tickInfo = unitTestTickInfo(); + TS_ASSERT_EQUALS(tickInfo->m_req, expTick); + } + + unitTestTick(client, 1000); + + unitTestDoConnectBasic(client, ClientId, false); + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionStatus_Connected); +} + +void UnitTestSleep::test2() +{ + // Testing attempts to enter sleep mode + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const unsigned SleepDurationSec = 10 * 60; + + CC_MqttsnSleepConfig config; + apiSleepInitConfig(&config); + + config.m_duration = SleepDurationSec; + + auto sleep = apiSleepPrepare(client); + TS_ASSERT_DIFFERS(sleep, nullptr); + + auto ec = apiSleepSetRetryCount(sleep, 1U); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = apiSleepConfig(sleep, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + unitTestSleepSend(sleep); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* disconnectMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(disconnectMsg, nullptr); + TS_ASSERT(disconnectMsg->field_duration().doesExist()); + TS_ASSERT_EQUALS(disconnectMsg->field_duration().field().value(), SleepDurationSec); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // Timeout + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* disconnectMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(disconnectMsg, nullptr); + TS_ASSERT(disconnectMsg->field_duration().doesExist()); + TS_ASSERT_EQUALS(disconnectMsg->field_duration().field().value(), SleepDurationSec); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // Timeout + + TS_ASSERT(unitTestHasSleepCompleteReport()); + auto sleepReport = unitTestSleepCompleteReport(); + TS_ASSERT_EQUALS(sleepReport->m_status, CC_MqttsnAsyncOpStatus_Timeout) + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionStatus_Connected); +} + +// TODO: test gateway disconnection report when there is no response to PINGREQ From 5310c34be94e390ebcbfe9b29deac101a8346c04 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sat, 10 Aug 2024 13:14:58 +1000 Subject: [PATCH 090/106] Fixing gcc build. --- client/lib/test/UnitTestReceive.th | 1 - 1 file changed, 1 deletion(-) diff --git a/client/lib/test/UnitTestReceive.th b/client/lib/test/UnitTestReceive.th index 8d14ad3b..4febcf9e 100644 --- a/client/lib/test/UnitTestReceive.th +++ b/client/lib/test/UnitTestReceive.th @@ -33,7 +33,6 @@ private: unitTestTearDown(); } - using Qos = UnitTestPublishMsg::Field_flags::Field_qos::ValueType; using TopicIdType = UnitTestPublishMsg::Field_flags::Field_topicIdType::ValueType; using RetCode = UnitTestPubackMsg::Field_returnCode::ValueType; }; From 8931148e917641ed23ae42ef5ea63ff871aff850 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sat, 10 Aug 2024 13:47:46 +1000 Subject: [PATCH 091/106] Fixing build with gcc and C++20 enabled. --- client/lib/src/TopicFilterDefs.h | 2 +- client/lib/test/UnitTestCommonBase.h | 2 +- script/full_debug_build_gcc_cpp20.sh | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100755 script/full_debug_build_gcc_cpp20.sh diff --git a/client/lib/src/TopicFilterDefs.h b/client/lib/src/TopicFilterDefs.h index f2803c76..69c10fef 100644 --- a/client/lib/src/TopicFilterDefs.h +++ b/client/lib/src/TopicFilterDefs.h @@ -30,7 +30,7 @@ struct RegTopicInfo RegTopicInfo(T&& topic, CC_MqttsnTopicId topicId) : m_topic(std::forward(topic)), m_topicId(topicId) {} RegTopicInfo(const char* topic) : m_topic(topic) {} - RegTopicInfo(CC_MqttsnTopicId topicId) : m_topicId(topicId) {} + RegTopicInfo(CC_MqttsnTopicId topicId) noexcept : m_topicId(topicId) {} }; struct TimestampStorage diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index 9d39797f..338547fa 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -159,7 +159,7 @@ class UnitTestCommonBase unsigned m_elapsed = 0U; UnitTestTickInfo() = default; - explicit UnitTestTickInfo(unsigned req) : m_req(req) {} + explicit UnitTestTickInfo(unsigned req) noexcept : m_req(req) {} }; using UnitTestTickInfosList = std::list; diff --git a/script/full_debug_build_gcc_cpp20.sh b/script/full_debug_build_gcc_cpp20.sh new file mode 100755 index 00000000..cb8e4059 --- /dev/null +++ b/script/full_debug_build_gcc_cpp20.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +${SCRIPT_DIR}/full_debug_build_gcc.sh -DCMAKE_CXX_STANDARD=20 "$@" + From b1da6d91b07b0cd26d6f59a55ad834f0f7dd385b Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Mon, 12 Aug 2024 08:06:20 +1000 Subject: [PATCH 092/106] Extra unit tests. --- client/app/common/AppClient.cpp | 1 - client/lib/include/cc_mqttsn_client/common.h | 19 ++-- client/lib/src/ClientImpl.cpp | 6 -- client/lib/src/op/ConnectOp.cpp | 4 +- client/lib/src/op/Op.cpp | 1 - client/lib/test/UnitTestConnect.th | 19 +++- client/lib/test/UnitTestSleep.th | 96 +++++++++++++++++++- 7 files changed, 125 insertions(+), 21 deletions(-) diff --git a/client/app/common/AppClient.cpp b/client/app/common/AppClient.cpp index 9cef20a2..e106f174 100644 --- a/client/app/common/AppClient.cpp +++ b/client/app/common/AppClient.cpp @@ -98,7 +98,6 @@ std::string AppClient::toString(CC_MqttsnErrorCode val) /* CC_MqttsnErrorCode_NotIntitialized */ "Not Intitialized", /* CC_MqttsnErrorCode_Busy*/ "Busy", /* CC_MqttsnErrorCode_NotConnected*/ "Not Connected", - /* CC_MqttsnErrorCode_AlreadyConnected */ "Already Connected", /* CC_MqttsnErrorCode_BadParam*/ "Bad Param", /* CC_MqttsnErrorCode_InsufficientConfig*/ "Insufficient Config", /* CC_MqttsnErrorCode_OutOfMemory*/ "Out Of Memory", diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index 3428da22..bc34c942 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -55,16 +55,15 @@ typedef enum CC_MqttsnErrorCode_NotIntitialized = 2, ///< The allocated client hasn't been initialized. CC_MqttsnErrorCode_Busy = 3, ///< The client library is in the middle of previous operation(s), cannot start a new one. CC_MqttsnErrorCode_NotConnected = 4, ///< The client library is not connected to the gateway. Returned by operations that require connection to the gateway. - CC_MqttsnErrorCode_AlreadyConnected = 5, ///< The client library is already connected to the gateway. Returned when cc_mqttsn_client_connect() invoked second time. - CC_MqttsnErrorCode_BadParam = 6, ///< Bad parameter is passed to the function. - CC_MqttsnErrorCode_InsufficientConfig = 7, ///< The required configuration hasn't been performed. - CC_MqttsnErrorCode_OutOfMemory = 8, ///< Memory allocation failed. - CC_MqttsnErrorCode_BufferOverflow = 9, ///< Output buffer is too short - CC_MqttsnErrorCode_NotSupported = 10, ///< Feature is not supported - CC_MqttsnErrorCode_RetryLater = 11, ///< Retry in next event loop iteration. - CC_MqttsnErrorCode_Disconnecting = 12, ///< The client is in "disconnecting" state, (re)connect is required in the next iteration loop. - CC_MqttsnErrorCode_NotSleeping = 13, ///< The client is not in ASLEEP mode. - CC_MqttsnErrorCode_PreparationLocked = 14, ///< Another operation is being prepared, cannot create a new one without performing "send" or "cancel". + CC_MqttsnErrorCode_BadParam = 5, ///< Bad parameter is passed to the function. + CC_MqttsnErrorCode_InsufficientConfig = 6, ///< The required configuration hasn't been performed. + CC_MqttsnErrorCode_OutOfMemory = 7, ///< Memory allocation failed. + CC_MqttsnErrorCode_BufferOverflow = 8, ///< Output buffer is too short + CC_MqttsnErrorCode_NotSupported = 9, ///< Feature is not supported + CC_MqttsnErrorCode_RetryLater = 10, ///< Retry in next event loop iteration. + CC_MqttsnErrorCode_Disconnecting = 11, ///< The client is in "disconnecting" state, (re)connect is required in the next iteration loop. + CC_MqttsnErrorCode_NotSleeping = 12, ///< The client is not in ASLEEP mode. + CC_MqttsnErrorCode_PreparationLocked = 13, ///< Another operation is being prepared, cannot create a new one without performing "send" or "cancel". CC_MqttsnErrorCode_ValuesLimit ///< Upper limit of the values } CC_MqttsnErrorCode; diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index dba86e3c..21b926e4 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -262,12 +262,6 @@ op::ConnectOp* ClientImpl::connectPrepare(CC_MqttsnErrorCode* ec) break; } - if (m_sessionState.m_connectionStatus == CC_MqttsnConnectionStatus_Connected) { - errorLog("Client is already connected."); - updateEc(ec, CC_MqttsnErrorCode_AlreadyConnected); - break; - } - if (m_ops.max_size() <= m_ops.size()) { errorLog("Cannot start connect operation, retry in next event loop iteration."); updateEc(ec, CC_MqttsnErrorCode_RetryLater); diff --git a/client/lib/src/op/ConnectOp.cpp b/client/lib/src/op/ConnectOp.cpp index 85b50439..7c1b2583 100644 --- a/client/lib/src/op/ConnectOp.cpp +++ b/client/lib/src/op/ConnectOp.cpp @@ -151,7 +151,9 @@ CC_MqttsnErrorCode ConnectOp::send(CC_MqttsnConnectCompleteCb cb, void* cbData) return ec; } - client().sessionState().m_clientId.clear(); + auto& sessionState = client().sessionState(); + sessionState.m_clientId.clear(); + sessionState.m_connectionStatus = CC_MqttsnConnectionStatus_Disconnected; if (m_connectMsg.field_flags().field_mid().getBitValue_CleanSession()) { // Don't wait for acknowledgement, assume state cleared upon send client().reuseState() = ReuseState(); diff --git a/client/lib/src/op/Op.cpp b/client/lib/src/op/Op.cpp index a288a17e..3b301fd9 100644 --- a/client/lib/src/op/Op.cpp +++ b/client/lib/src/op/Op.cpp @@ -56,7 +56,6 @@ CC_MqttsnAsyncOpStatus Op::translateErrorCodeToAsyncOpStatus(CC_MqttsnErrorCode /* CC_MqttsnErrorCode_NotIntitialized */ CC_MqttsnAsyncOpStatus_InternalError, /* CC_MqttsnErrorCode_Busy */ CC_MqttsnAsyncOpStatus_InternalError, /* CC_MqttsnErrorCode_NotConnected */ CC_MqttsnAsyncOpStatus_InternalError, - /* CC_MqttsnErrorCode_AlreadyConnected */ CC_MqttsnAsyncOpStatus_InternalError, /* CC_MqttsnErrorCode_BadParam */ CC_MqttsnAsyncOpStatus_BadParam, /* CC_MqttsnErrorCode_InsufficientConfig */ CC_MqttsnAsyncOpStatus_InternalError, /* CC_MqttsnErrorCode_OutOfMemory */ CC_MqttsnAsyncOpStatus_OutOfMemory, diff --git a/client/lib/test/UnitTestConnect.th b/client/lib/test/UnitTestConnect.th index 65914abf..5d37c759 100644 --- a/client/lib/test/UnitTestConnect.th +++ b/client/lib/test/UnitTestConnect.th @@ -14,6 +14,7 @@ public: void test4(); void test5(); void test6(); + void test7(); private: virtual void setUp() override @@ -508,4 +509,20 @@ void UnitTestConnect::test6() TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionStatus_Disconnected); } -// TODO: test reconnect without disconnecting \ No newline at end of file +void UnitTestConnect::test7() +{ + // Testing reconnection without disconnect + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); +} diff --git a/client/lib/test/UnitTestSleep.th b/client/lib/test/UnitTestSleep.th index 960964bc..f69b891c 100644 --- a/client/lib/test/UnitTestSleep.th +++ b/client/lib/test/UnitTestSleep.th @@ -10,6 +10,7 @@ class UnitTestSleep : public CxxTest::TestSuite, public UnitTestDefaultBase public: void test1(); void test2(); + void test3(); private: virtual void setUp() override @@ -203,4 +204,97 @@ void UnitTestSleep::test2() TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionStatus_Connected); } -// TODO: test gateway disconnection report when there is no response to PINGREQ +void UnitTestSleep::test3() +{ + // Testing no response to PINGREQ + + auto clientPtr = unitTestAllocClient(true); + auto* client = clientPtr.get(); + + auto ec = apiSetDefaultRetryCount(client, 1U); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const CC_MqttsnTopicId TopicId = 123; + unitTestDoSubscribeTopicId(client, TopicId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const unsigned SleepDurationSec = 10 * 60; + + CC_MqttsnSleepConfig config; + apiSleepInitConfig(&config); + + config.m_duration = SleepDurationSec; + + auto sleep = apiSleepPrepare(client); + TS_ASSERT_DIFFERS(sleep, nullptr); + + ec = apiSleepConfig(sleep, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + unitTestSleepSend(sleep); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* disconnectMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(disconnectMsg, nullptr); + TS_ASSERT(disconnectMsg->field_duration().doesExist()); + TS_ASSERT_EQUALS(disconnectMsg->field_duration().field().value(), SleepDurationSec); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestDisconnectMsg disconnectMsg; + unitTestClientInputMessage(client, disconnectMsg); + } + + TS_ASSERT(unitTestHasSleepCompleteReport()); + auto sleepReport = unitTestSleepCompleteReport(); + TS_ASSERT_EQUALS(sleepReport->m_status, CC_MqttsnAsyncOpStatus_Complete) + TS_ASSERT_EQUALS(apiGetConnectionState(client), CC_MqttsnConnectionStatus_Asleep); + + TS_ASSERT(unitTestHasTickReq()); // Timer for ping + unitTestTick(client); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pingreqMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pingreqMsg, nullptr); + TS_ASSERT_EQUALS(pingreqMsg->field_clientId().value(), ClientId); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); // Timeout + unitTestTick(client); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pingreqMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pingreqMsg, nullptr); + TS_ASSERT_EQUALS(pingreqMsg->field_clientId().value(), ClientId); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); // Timeout + unitTestTick(client); + + { + TS_ASSERT(unitTestHasGwDisconnectReport()); + auto disconnectReport = unitTestGetGwDisconnectReport(); + TS_ASSERT_EQUALS(disconnectReport->m_reason, CC_MqttsnGatewayDisconnectReason_NoGatewayResponse); + } +} + From 8c5330f9f90db2761139ae99fbbf78b7145a1fee Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Mon, 12 Aug 2024 08:57:09 +1000 Subject: [PATCH 093/106] Documenting "sleep" operation. --- client/lib/doxygen/main.dox | 166 ++++++++++++++++++- client/lib/include/cc_mqttsn_client/common.h | 16 +- 2 files changed, 168 insertions(+), 14 deletions(-) diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index 18495318..f3a5088e 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -695,9 +695,9 @@ /// "info" is present if and only if the "status" is /// @ref CC_MqttsnAsyncOpStatus_Complete. /// -/// @b NOTE that only single "connect" / "disconnect" operation is allowed at a time, any attempt to +/// @b NOTE that only single "connect" / "disconnect" / "sleep" operation is allowed at a time, any attempt to /// prepare a new one via @b cc_mqttsn_client_connect_prepare() will be rejected -/// until the "connect" operation completion callback is invoked or +/// until the blocking operation completion callback is invoked or /// the operation is @ref doc_cc_mqttsn_client_connect_cancel "cancelled". /// /// When the callback reporting the connection status is invoked, it is responsibility @@ -707,6 +707,11 @@ /// done when the "connect" operation is not properly completed, i.e. the /// reported status is @b NOT @ref CC_MqttsnAsyncOpStatus_Complete. /// +/// The "connect" operation can be @ref doc_cc_mqttsn_client_connect_send "sent" at +/// any time, even if it's already @ref doc_cc_mqttsn_client_connect_check "connected". +/// However, upon failing attempt to re-connect the assumed inner state becomes +/// @ref CC_MqttsnConnectionStatus_Disconnected. +/// /// @subsection doc_cc_mqttsn_client_connect_cancel Cancel the "Connect" Operation. /// While the handle returned by the @b cc_mqttsn_client_connect_prepare() is still /// valid it is possible to cancel / discard the operation. @@ -825,9 +830,9 @@ /// The valid handle can be used to @ref doc_cc_mqttsn_client_disconnect_cancel "cancel" /// the operation before the completion callback is invoked. /// -/// @b NOTE that only single "connect" / "disconnect" operation is allowed at a time, any attempt to +/// @b NOTE that only single "connect" / "disconnect" / "sleep" operation is allowed at a time, any attempt to /// prepare a new one via @b cc_mqttsn_client_disconnect_prepare() will be rejected -/// until the "connect" operation completion callback is invoked or +/// until the blocking operation completion callback is invoked or /// the operation is @ref doc_cc_mqttsn_client_disconnect_cancel "cancelled". /// /// In case there are other asynchronous operations that hasn't been completed yet, @@ -1714,7 +1719,156 @@ /// @endcode /// /// @section doc_cc_mqttsn_client_sleep Sleeping -/// TODO +/// To enter the "sleep" mode use @ref sleep "sleep" operation. +/// +/// @subsection doc_cc_mqttsn_client_sleep_prepare Preparing "Sleep" Operation. +/// @code +/// CC_MqttsnErrorCode ec = CC_MqttsnErrorCode_Success; +/// CC_MqttsnSleepHandle sleep = cc_mqttsn_client_sleep_prepare(client, &ec); +/// if (sleep == NULL) { +/// printf("ERROR: Sleep allocation failed with ec=%d\n", ec); +/// } +/// @endcode +/// +/// @subsection doc_cc_mqttsn_client_sleep_retry_period Configuring "Sleep" Retry Period +/// When created, the "sleep" operation inherits the @ref doc_cc_mqttsn_client_retry_period +/// configuration. It can be changed for the allocated operation using the +/// @b cc_mqttsn_client_sleep_set_retry_period() function. +/// @code +/// ec = cc_mqttsn_client_sleep_set_retry_period(sleep, 1000); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// ... /* Something went wrong */ +/// } +/// @endcode +/// To retrieve the configured response timeout use the @b cc_mqttsn_client_sleep_get_retry_period() function. +/// +/// @subsection doc_cc_mqttsn_client_sleep_retry_count Configuring "Sleep" Retry Count +/// When created, the "sleep" operation inherits the @ref doc_cc_mqttsn_client_retry_count +/// configuration. It can be changed for the allocated operation using the +/// @b cc_mqttsn_client_sleep_set_retry_count() function. +/// @code +/// ec = cc_mqttsn_client_sleep_set_retry_count(sleep, 2); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// ... /* Something went wrong */ +/// } +/// @endcode +/// To retrieve the configured response timeout use the @b cc_mqttsn_client_sleep_get_retry_count() function. +/// +/// @subsection doc_cc_mqttsn_client_sleep_config Configuration of "Sleep" Operation +/// To configure "sleep" operation use @b cc_mqttsn_client_sleep_config() function. +/// @code +/// CC_MqttsnSleepConfig config; +/// +/// // Assign default values to the "config" +/// cc_mqttsn_client_sleep_init_config(&config); +/// +/// // Update the values: +/// config.m_duration = ...; +/// +/// // Perform the configuration +/// ec = cc_mqttsn_client_sleep_config(sleep, &config); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Sleep configuration failed with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// See also documentation of the @ref CC_MqttsnSleepConfig structure. +/// +/// Note that configuration of the "sleep" operation specifies duration within +/// which the client is expected to send @b PINGREQ message to check for the +/// accumulated messages. The library will try to perform several attempts to +/// send @b PINGREQ and receive @b PINGRESP in return. The configured +/// @ref CC_MqttsnSleepConfig::m_duration "duration" is expected to be longer (when translated to milliseconds) +/// than the @ref doc_cc_mqttsn_client_retry_period multiplied by the +/// (@ref doc_cc_mqttsn_client_retry_count + 1). +/// +/// @subsection doc_cc_mqttsn_client_sleep_send Sending Sleep Request +/// When the necessary configuration is performed for the allocated "sleep" +/// operation it can be sent to the gateway. To initiate sending +/// use the @b cc_mqttsn_client_sleep_send() function. +/// @code +/// void my_sleep_complete_cb(void* data, CC_MqttsnAsyncOpStatus status) +/// { +/// if (status != CC_MqttsnAsyncOpStatus_Complete) { +/// printf("ERROR: The sleep operation has failed with status=%d\n", status); +/// ... // handle error. +/// return; +/// } +/// ... +/// } +/// +/// ec = cc_mqttsn_client_sleep_send(sleep, &my_sleep_complete_cb, data); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to send sleep request with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// The provided callback will be invoked when the "sleep" operation is complete +/// if and only if the function returns @ref CC_MqttsnErrorCode_Success. +/// +/// The handle returned by the @b cc_mqttsn_client_sleep_prepare() function +/// can be discarded (there is no free / de-allocation) right after the +/// @b cc_mqttsn_client_sleep_send() invocation +/// regardless of the returned error code. However, the handle remains valid until +/// the callback is called (in case the @ref CC_MqttsnErrorCode_Success was returned). +/// The valid handle can be used to @ref doc_cc_mqttsn_client_sleep_cancel "cancel" +/// the operation before the completion callback is invoked. +/// +/// @b NOTE that only single "connect" / "disconnect" / "sleep" operation is allowed at a time, any attempt to +/// prepare a new one via @b cc_mqttsn_client_disconnect_prepare() will be rejected +/// until the blocking operation completion callback is invoked or +/// the operation is @ref doc_cc_mqttsn_client_sleep_cancel "cancelled". +/// +/// In case there are other asynchronous operations that hasn't been completed yet, +/// their completion callback is automatically invoked with @ref CC_MqttsnAsyncOpStatus_Aborted +/// status. +/// +/// After the intended sleep is over the application can re-establish network connection +/// to the gateway (if needed) and perform the @ref doc_cc_mqttsn_client_connect "connect" operation +/// again. +/// +/// @subsection doc_cc_mqttsn_client_sleep_cancel Cancel the "Sleep" Operation. +/// While the handle returned by the @b cc_mqttsn_client_sleep_prepare() is still +/// valid it is possible to cancel / discard the operation. +/// @code +/// ec = cc_mqttsn_client_sleep_cancel(sleep); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to cancel sleep with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// +/// @subsection doc_cc_mqttsn_client_sleep_simplify Simplifying the "Sleep" Operation Preparation. +/// In many use cases the "sleep" operation can be quite simple. +/// To simplify the sequence of the operation preparation and handling of errors, +/// the library provides wrapper function(s) that can be used: +/// @li @b cc_mqttsn_client_sleep() +/// +/// For example: +/// @code +/// ec = cc_mqttsn_client_sleep(client, &config, &my_sleep_complete_cb, data); +/// if (ec != CC_MqttsnErrorCode_Success) { +/// printf("ERROR: Failed to send sleep request with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// +/// @subsection doc_cc_mqttsn_client_sleep_recv Receiving Messages During "Sleep" State. +/// When in "asleep" mode the client is expected to periodically send @b PINGREQ +/// messages to inquire whether the gateway has accumulated pending messages for +/// the client. The library keeps track of time and expects the application to +/// still manage timers and notify the library about the elapsed ticks +/// (see @ref doc_cc_mqttsn_client_time). The library will perform up to +/// @ref doc_cc_mqttsn_client_retry_count + 1 attempts to get the @b PINGRESP +/// back within the @ref doc_cc_mqttsn_client_retry_period and will report all the +/// @ref doc_cc_mqttsn_client_receive "received messages" in between. +/// +/// The application can also independently and explicitly to initiate check +/// of the pending messages using @b cc_mqttsn_client_asleep_check_messages() +/// function. +/// +/// In case the gateway doesn't respond with @b PINGRESP to the sent @b PINGREQ +/// the library will report @ref doc_cc_mqttsn_client_unsolicited_disconnect. /// /// @section doc_cc_mqttsn_client_unsolicited_disconnect Unsolicited Gateway Disconnection /// When broker disconnection is detected all the incomplete asynchronous operations @@ -1724,7 +1878,7 @@ /// /// @subsection doc_cc_mqttsn_client_unsolicited_disconnect_keep_alive Keep Alive Timeout /// When there was no message from the broker for the "keep alive" timeout (configured during the @ref connect "connect" -/// operation) and the broker doesn't respond to the @b PINGREQ message. +/// or the @ref sleep "sleep" operations) and the broker doesn't respond to the @b PINGREQ message. /// In such case the library responds in the following way: /// @li Terminates and invokes the callbacks of previously initiated but incomplete operations /// passing the @ref CC_MqttsnAsyncOpStatus_GatewayDisconnected as their status report. diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index bc34c942..6f449ca8 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -139,7 +139,7 @@ struct CC_MqttsnSearch; /// @brief Handle for "search" operation. /// @details Returned by @b cc_mqttsn_client_search_prepare() function. -/// @ingroup search. +/// @ingroup search typedef struct CC_MqttsnSearch* CC_MqttsnSearchHandle; /// @brief Declaration of the hidden structure used to define @ref CC_MqttsnConnectHandle @@ -148,7 +148,7 @@ struct CC_MqttsnConnect; /// @brief Handle for "connect" operation. /// @details Returned by @b cc_mqttsn_client_connect_prepare() function. -/// @ingroup connect. +/// @ingroup connect typedef struct CC_MqttsnConnect* CC_MqttsnConnectHandle; /// @brief Declaration of the hidden structure used to define @ref CC_MqttsnDisconnectHandle @@ -157,7 +157,7 @@ struct CC_MqttsnDisconnect; /// @brief Handle for "disconnect" operation. /// @details Returned by @b cc_mqttsn_client_disconnect_prepare() function. -/// @ingroup disconnect. +/// @ingroup disconnect typedef struct CC_MqttsnDisconnect* CC_MqttsnDisconnectHandle; /// @brief Declaration of the hidden structure used to define @ref CC_MqttsnSubscribeHandle @@ -166,7 +166,7 @@ struct CC_MqttsnSubscribe; /// @brief Handle for "subscribe" operation. /// @details Returned by @b cc_mqttsn_client_subscribe_prepare() function. -/// @ingroup subscribe. +/// @ingroup subscribe typedef struct CC_MqttsnSubscribe* CC_MqttsnSubscribeHandle; /// @brief Declaration of the hidden structure used to define @ref CC_MqttsnUnsubscribeHandle @@ -175,7 +175,7 @@ struct CC_MqttsnUnsubscribe; /// @brief Handle for "unsubscribe" operation. /// @details Returned by @b cc_mqttsn_client_unsubscribe_prepare() function. -/// @ingroup subscribe. +/// @ingroup subscribe typedef struct CC_MqttsnUnsubscribe* CC_MqttsnUnsubscribeHandle; /// @brief Declaration of the hidden structure used to define @ref CC_MqttsnPublishHandle @@ -184,7 +184,7 @@ struct CC_MqttsnPublish; /// @brief Handle for "publish" operation. /// @details Returned by @b cc_mqttsn_client_publish_prepare() function. -/// @ingroup publish. +/// @ingroup publish typedef struct CC_MqttsnPublish* CC_MqttsnPublishHandle; /// @brief Declaration of the hidden structure used to define @ref CC_MqttsnWillHandle @@ -193,7 +193,7 @@ struct CC_MqttsnWill; /// @brief Handle for "will" operation. /// @details Returned by @b cc_mqttsn_client_will_prepare() function. -/// @ingroup will. +/// @ingroup will typedef struct CC_MqttsnWill* CC_MqttsnWillHandle; /// @brief Declaration of the hidden structure used to define @ref CC_MqttsnSleepHandle @@ -202,7 +202,7 @@ struct CC_MqttsnSleep; /// @brief Handle for "sleep" operation. /// @details Returned by @b cc_mqttsn_client_sleep_prepare() function. -/// @ingroup sleep. +/// @ingroup sleep typedef struct CC_MqttsnSleep* CC_MqttsnSleepHandle; /// @brief Type used to hold Topic ID value. From d6ad304501a795e8d4e410fb51a97e69a1ff1b5e Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 13 Aug 2024 08:06:04 +1000 Subject: [PATCH 094/106] Supporting info on data origin for the client. --- client/app/common/AppClient.cpp | 7 +++- client/app/common/AppClient.h | 6 ++++ client/lib/doxygen/main.dox | 10 ++++-- client/lib/include/cc_mqttsn_client/common.h | 9 +++++ client/lib/src/ClientImpl.cpp | 36 +++++++++++++++++++- client/lib/src/ClientImpl.h | 2 +- client/lib/src/SessionState.h | 1 + client/lib/templ/client.cpp.templ | 4 +-- client/lib/templ/client.h.templ | 3 +- client/lib/test/UnitTestCommonBase.cpp | 12 +++---- client/lib/test/UnitTestCommonBase.h | 8 ++--- client/lib/test/UnitTestGwDiscover.th | 14 ++++---- 12 files changed, 87 insertions(+), 25 deletions(-) diff --git a/client/app/common/AppClient.cpp b/client/app/common/AppClient.cpp index e106f174..68a1c885 100644 --- a/client/app/common/AppClient.cpp +++ b/client/app/common/AppClient.cpp @@ -358,7 +358,12 @@ bool AppClient::createSession() { assert(m_client); m_lastAddr = addr; - ::cc_mqttsn_client_process_data(m_client.get(), buf, static_cast(bufLen)); + auto origin = CC_MqttsnDataOrigin_Any; + if (addr == m_gwAddr) { + origin = CC_MqttsnDataOrigin_ConnectedGw; + } + + ::cc_mqttsn_client_process_data(m_client.get(), buf, static_cast(bufLen), origin); }); m_session->setNetworkErrorReportCb( diff --git a/client/app/common/AppClient.h b/client/app/common/AppClient.h index 2285162a..2a27ffb1 100644 --- a/client/app/common/AppClient.h +++ b/client/app/common/AppClient.h @@ -74,6 +74,11 @@ class AppClient return m_lastAddr; } + void setGwAddr(const Addr& addr) + { + m_gwAddr = addr; + } + static std::string toString(CC_MqttsnAsyncOpStatus val); private: @@ -100,6 +105,7 @@ class AppClient ClientPtr m_client; SessionPtr m_session; Addr m_lastAddr; + Addr m_gwAddr; }; } // namespace cc_mqttsn_client_app diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index f3a5088e..b5526d0a 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -187,14 +187,15 @@ /// See also the documentation of the @ref CC_MqttsnErrorLogCb callback function definition. /// /// @section doc_cc_mqttsn_client_data Reporting Incoming Data -/// It is the responsibility of the application to receive data from the gateway +/// It is the responsibility of the application to receive data from the other nodes on the network /// and report it to the library. The report is performed using the /// @b cc_mqttsn_client_process_data() function. /// @code /// uint8_t buf[MAX_BUF_SIZE]; /// ... // Receive data into buffer /// unsigned bufLen = ...; // Amount of received bytes in the buffer. -/// cc_mqttsn_client_process_data(client, buf, bufLen); +/// CC_MqttsnDataOrigin origin = ...; +/// cc_mqttsn_client_process_data(client, buf, bufLen, origin); /// @endcode /// The application is responsible to maintain the input buffer. /// The MQTT-SN protocol assumes datagram packets, where each packets @@ -202,6 +203,11 @@ /// and processes the message at the beginning of the reported buffer and /// discards any extra bytes that might follow. /// +/// The application is also responsible to allow input from multiple sources and +/// determine the @ref CC_MqttsnDataOrigin "origin" of the input data. It allows +/// the library to differentiate between the messages from the legit gateway to +/// which the application is connected and other nodes on the network. +/// /// When new data chunk is reported the library may invoke several callbacks, /// such as reporting received message, sending new data out, as well as canceling /// the old and programming new tick timeout. diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index 6f449ca8..c593a487 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -124,6 +124,15 @@ typedef enum CC_MqttsnConnectionStatus_ValuesLimit ///< Limit for the values } CC_MqttsnConnectionStatus; +/// @brief Data origin +/// @ingroup global +typedef enum +{ + CC_MqttsnDataOrigin_Any = 0, ///< Data comes from any node on the network + CC_MqttsnDataOrigin_ConnectedGw = 1, ///< Data comes from the connected gateway + CC_MqttsnDataOrigin_ValuesLimit ///< Limit for the values +} CC_MqttsnDataOrigin; + /// @brief Declaration of struct for the @ref CC_MqttsnClientHandle; /// @ingroup client struct CC_MqttsnClient; diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 21b926e4..dee846ed 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -156,10 +156,11 @@ void ClientImpl::tick(unsigned ms) doApiExit(); } -void ClientImpl::processData(const std::uint8_t* iter, unsigned len) +void ClientImpl::processData(const std::uint8_t* iter, unsigned len, CC_MqttsnDataOrigin origin) { auto guard = apiEnter(); + m_sessionState.m_lastOrigin = origin; ProtFrame::MsgPtr msgPtr; auto es = comms::processSingleWithDispatch(iter, len, m_frame, msgPtr, *this); if (es != comms::ErrorStatus::Success) { @@ -709,6 +710,11 @@ void ClientImpl::handle(AdvertiseMsg& msg) void ClientImpl::handle(SearchgwMsg& msg) { + if (m_sessionState.m_lastOrigin != CC_MqttsnDataOrigin_Any) { + errorLog("SEARCHGW message from connected gateway, ignoring..."); + return; + } + static_assert(Config::HasGatewayDiscovery); if (m_gwinfoDelayReqCb == nullptr) { // The application didn't provide a callback to inquire about the delay for resonditing to SEARCHGW @@ -830,6 +836,10 @@ void ClientImpl::handle(GwinfoMsg& msg) void ClientImpl::handle(RegisterMsg& msg) { + if (m_sessionState.m_lastOrigin != CC_MqttsnDataOrigin_ConnectedGw) { + return; + } + if ((m_sessionState.m_disconnecting) || (m_sessionState.m_connectionStatus == CC_MqttsnConnectionStatus_Disconnected)) { return; @@ -866,6 +876,10 @@ void ClientImpl::handle(RegisterMsg& msg) void ClientImpl::handle(PublishMsg& msg) { + if (m_sessionState.m_lastOrigin != CC_MqttsnDataOrigin_ConnectedGw) { + return; + } + if ((m_sessionState.m_disconnecting) || (m_sessionState.m_connectionStatus == CC_MqttsnConnectionStatus_Disconnected)) { return; @@ -1050,6 +1064,10 @@ void ClientImpl::handle(PublishMsg& msg) void ClientImpl::handle(PubackMsg& msg) { + if (m_sessionState.m_lastOrigin != CC_MqttsnDataOrigin_ConnectedGw) { + return; + } + if ((m_sessionState.m_disconnecting) || (m_sessionState.m_connectionStatus == CC_MqttsnConnectionStatus_Disconnected)) { return; @@ -1100,6 +1118,10 @@ void ClientImpl::handle(PubackMsg& msg) #if CC_MQTTSN_CLIENT_MAX_QOS >= 2 void ClientImpl::handle(PubrelMsg& msg) { + if (m_sessionState.m_lastOrigin != CC_MqttsnDataOrigin_ConnectedGw) { + return; + } + if ((m_sessionState.m_disconnecting) || (m_sessionState.m_connectionStatus == CC_MqttsnConnectionStatus_Disconnected)) { return; @@ -1126,6 +1148,10 @@ void ClientImpl::handle(PubrelMsg& msg) void ClientImpl::handle([[maybe_unused]] PingreqMsg& msg) { + if (m_sessionState.m_lastOrigin != CC_MqttsnDataOrigin_ConnectedGw) { + return; + } + if ((m_sessionState.m_disconnecting) || (m_sessionState.m_connectionStatus != CC_MqttsnConnectionStatus_Connected)) { return; @@ -1138,6 +1164,10 @@ void ClientImpl::handle([[maybe_unused]] PingreqMsg& msg) void ClientImpl::handle(DisconnectMsg& msg) { + if (m_sessionState.m_lastOrigin != CC_MqttsnDataOrigin_ConnectedGw) { + return; + } + if ((m_sessionState.m_disconnecting) || (m_sessionState.m_connectionStatus == CC_MqttsnConnectionStatus_Disconnected)) { return; @@ -1155,6 +1185,10 @@ void ClientImpl::handle(DisconnectMsg& msg) void ClientImpl::handle([[maybe_unused]] ProtMessage& msg) { + if (m_sessionState.m_lastOrigin != CC_MqttsnDataOrigin_ConnectedGw) { + return; + } + if (m_sessionState.m_disconnecting) { return; } diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index 9268d302..95d9c91a 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -64,7 +64,7 @@ class ClientImpl final : public ProtMsgHandler // -------------------- API Calls ----------------------------- void tick(unsigned ms); - void processData(const std::uint8_t* iter, unsigned len); + void processData(const std::uint8_t* iter, unsigned len, CC_MqttsnDataOrigin origin); op::SearchOp* searchPrepare(CC_MqttsnErrorCode* ec); op::ConnectOp* connectPrepare(CC_MqttsnErrorCode* ec); diff --git a/client/lib/src/SessionState.h b/client/lib/src/SessionState.h index a76f8715..000ffe19 100644 --- a/client/lib/src/SessionState.h +++ b/client/lib/src/SessionState.h @@ -20,6 +20,7 @@ struct SessionState ClientIdStr m_clientId; unsigned m_keepAliveMs = 0U; CC_MqttsnConnectionStatus m_connectionStatus = CC_MqttsnConnectionStatus_Disconnected; + CC_MqttsnDataOrigin m_lastOrigin = CC_MqttsnDataOrigin_Any; bool m_disconnecting = false; }; diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index aa692d70..f7bf543e 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -144,10 +144,10 @@ void cc_mqttsn_##NAME##client_tick(CC_MqttsnClientHandle client, unsigned ms) clientFromHandle(client)->tick(ms); } -void cc_mqttsn_##NAME##client_process_data(CC_MqttsnClientHandle client, const unsigned char* buf, unsigned bufLen) +void cc_mqttsn_##NAME##client_process_data(CC_MqttsnClientHandle client, const unsigned char* buf, unsigned bufLen, CC_MqttsnDataOrigin origin) { COMMS_ASSERT(client != nullptr); - clientFromHandle(client)->processData(buf, bufLen); + clientFromHandle(client)->processData(buf, bufLen, origin); } CC_MqttsnErrorCode cc_mqttsn_##NAME##client_set_default_retry_period(CC_MqttsnClientHandle client, unsigned value) diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index 3e7a35d8..af6501db 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -66,11 +66,12 @@ void cc_mqttsn_##NAME##client_tick(CC_MqttsnClientHandle client, unsigned ms); /// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. /// @param[in] buf Pointer to the buffer of data to process. /// @param[in] bufLen Number of bytes in the data buffer. +/// @param[in] origin Origin of the data, helps to ignore unsolicited messages on the network. /// @note According to the MQTT-SN specification every message should be sent in a separate /// single datagram packet. This function assumes such behaviour and "consumes" /// all the provided data discarding any additional bytes after the message (if exist). /// @ingroup client -void cc_mqttsn_##NAME##client_process_data(CC_MqttsnClientHandle client, const unsigned char* buf, unsigned bufLen); +void cc_mqttsn_##NAME##client_process_data(CC_MqttsnClientHandle client, const unsigned char* buf, unsigned bufLen, CC_MqttsnDataOrigin origin); /// @brief Set retry period to wait between resending unacknowledged message to the gateway (@b Tretry from spec). /// @details Some messages, sent to the gateway, may require acknowledgement by diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index bcbdf56e..2cc19672 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -294,12 +294,12 @@ UnitTestCommonBase::UnitTestClientPtr UnitTestCommonBase::unitTestAllocClient(bo return client; } -void UnitTestCommonBase::unitTestClientInputData(CC_MqttsnClient* client, const UnitTestData& data) +void UnitTestCommonBase::unitTestClientInputData(CC_MqttsnClient* client, const UnitTestData& data, CC_MqttsnDataOrigin origin) { - apiProcessData(client, data.data(), static_cast(data.size())); + apiProcessData(client, data.data(), static_cast(data.size()), origin); } -void UnitTestCommonBase::unitTestClientInputMessage(CC_MqttsnClient* client, const UnitTestMessage& msg) +void UnitTestCommonBase::unitTestClientInputMessage(CC_MqttsnClient* client, const UnitTestMessage& msg, CC_MqttsnDataOrigin origin) { UnitTestData data; UnitTestsFrame frame; @@ -307,7 +307,7 @@ void UnitTestCommonBase::unitTestClientInputMessage(CC_MqttsnClient* client, con auto writeIter = comms::writeIteratorFor(data.data()); auto ec = frame.write(msg, writeIter, data.size()); test_assert(ec == comms::ErrorStatus::Success); - unitTestClientInputData(client, data); + unitTestClientInputData(client, data, origin); } void UnitTestCommonBase::unitTestPushSearchgwResponseDelay(unsigned val) @@ -849,9 +849,9 @@ UnitTestCommonBase::UnitTestMessageInfoPtr UnitTestCommonBase::unitTestReceivedM return ptr; } -void UnitTestCommonBase::apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen) +void UnitTestCommonBase::apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen, CC_MqttsnDataOrigin origin) { - m_funcs.m_process_data(client, buf, bufLen); + m_funcs.m_process_data(client, buf, bufLen, origin); } CC_MqttsnErrorCode UnitTestCommonBase::apiSetDefaultRetryPeriod(CC_MqttsnClient* client, unsigned value) diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index 338547fa..1adbb055 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -17,7 +17,7 @@ class UnitTestCommonBase CC_MqttsnClientHandle (*m_alloc)() = nullptr; void (*m_free)(CC_MqttsnClientHandle) = nullptr; void (*m_tick)(CC_MqttsnClientHandle, unsigned) = nullptr; - void (*m_process_data)(CC_MqttsnClientHandle, const unsigned char*, unsigned) = nullptr; + void (*m_process_data)(CC_MqttsnClientHandle, const unsigned char*, unsigned, CC_MqttsnDataOrigin) = nullptr; CC_MqttsnErrorCode (*m_set_default_retry_period)(CC_MqttsnClientHandle, unsigned) = nullptr; unsigned (*m_get_default_retry_period)(CC_MqttsnClientHandle) = nullptr; CC_MqttsnErrorCode (*m_set_default_retry_count)(CC_MqttsnClientHandle, unsigned) = nullptr; @@ -375,8 +375,8 @@ class UnitTestCommonBase void unitTestTearDown(); UnitTestClientPtr unitTestAllocClient(bool enableLog = false); - void unitTestClientInputData(CC_MqttsnClient* client, const UnitTestData& data); - void unitTestClientInputMessage(CC_MqttsnClient* client, const UnitTestMessage& msg); + void unitTestClientInputData(CC_MqttsnClient* client, const UnitTestData& data, CC_MqttsnDataOrigin origin); + void unitTestClientInputMessage(CC_MqttsnClient* client, const UnitTestMessage& msg, CC_MqttsnDataOrigin origin = CC_MqttsnDataOrigin_ConnectedGw); void unitTestPushSearchgwResponseDelay(unsigned val); static CC_MqttsnTopicId unitTestShortTopicNameToId(const std::string& topic); @@ -448,7 +448,7 @@ class UnitTestCommonBase bool unitTestHasReceivedMessage() const; UnitTestMessageInfoPtr unitTestReceivedMessage(bool mustExist = true); - void apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen); + void apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen, CC_MqttsnDataOrigin origin); CC_MqttsnErrorCode apiSetDefaultRetryPeriod(CC_MqttsnClient* client, unsigned value); unsigned apiGetDefaultRetryPeriod(CC_MqttsnClientHandle client); CC_MqttsnErrorCode apiSetDefaultRetryCount(CC_MqttsnClient* client, unsigned value); diff --git a/client/lib/test/UnitTestGwDiscover.th b/client/lib/test/UnitTestGwDiscover.th index c391d338..cccfb8dd 100644 --- a/client/lib/test/UnitTestGwDiscover.th +++ b/client/lib/test/UnitTestGwDiscover.th @@ -41,7 +41,7 @@ void UnitTestGwDiscover::test1() UnitTestAdvertiseMsg advertiseMsg; advertiseMsg.field_gwId().setValue(GwId); comms::units::setMinutes(advertiseMsg.field_duration(), AdvDurationMin); - unitTestClientInputMessage(client, advertiseMsg); + unitTestClientInputMessage(client, advertiseMsg, CC_MqttsnDataOrigin_Any); { auto gwInfoReport = unitTestGetGwInfoReport(); @@ -96,7 +96,7 @@ void UnitTestGwDiscover::test2() const std::uint8_t GwId = 1U; UnitTestGwinfoMsg gwinfoMsg; gwinfoMsg.field_gwId().setValue(GwId); - unitTestClientInputMessage(client, gwinfoMsg); + unitTestClientInputMessage(client, gwinfoMsg, CC_MqttsnDataOrigin_Any); TS_ASSERT(unitTestHasSearchCompleteReport()); auto searchCompleteReport = unitTestSearchCompleteReport(); @@ -136,7 +136,7 @@ void UnitTestGwDiscover::test3() UnitTestGwinfoMsg gwinfoMsg; gwinfoMsg.field_gwId().setValue(GwId); comms::util::assign(gwinfoMsg.field_gwAdd().value(), Addr.begin(), Addr.end()); - unitTestClientInputMessage(client, gwinfoMsg); + unitTestClientInputMessage(client, gwinfoMsg, CC_MqttsnDataOrigin_Any); TS_ASSERT(unitTestHasSearchCompleteReport()); auto searchCompleteReport = unitTestSearchCompleteReport(); @@ -179,7 +179,7 @@ void UnitTestGwDiscover::test4() UnitTestAdvertiseMsg advertiseMsg; advertiseMsg.field_gwId().setValue(GwId); comms::units::setMinutes(advertiseMsg.field_duration(), AdvDurationMin); - unitTestClientInputMessage(client, advertiseMsg); + unitTestClientInputMessage(client, advertiseMsg, CC_MqttsnDataOrigin_Any); TS_ASSERT(unitTestHasSearchCompleteReport()); auto searchCompleteReport = unitTestSearchCompleteReport(); @@ -306,7 +306,7 @@ void UnitTestGwDiscover::test6() const std::uint8_t GwId = 1U; UnitTestGwinfoMsg gwinfoMsg; gwinfoMsg.field_gwId().setValue(GwId); - unitTestClientInputMessage(client, gwinfoMsg); + unitTestClientInputMessage(client, gwinfoMsg, CC_MqttsnDataOrigin_Any); TS_ASSERT(unitTestHasSearchCompleteReport()); auto searchCompleteReport = unitTestSearchCompleteReport(); @@ -336,7 +336,7 @@ void UnitTestGwDiscover::test7() UnitTestAdvertiseMsg advertiseMsg; advertiseMsg.field_gwId().setValue(GwId); comms::units::setMinutes(advertiseMsg.field_duration(), AdvDurationMin); - unitTestClientInputMessage(client, advertiseMsg); + unitTestClientInputMessage(client, advertiseMsg, CC_MqttsnDataOrigin_Any); } { @@ -364,7 +364,7 @@ void UnitTestGwDiscover::test7() { UnitTestSearchgwMsg searchgwMsg; searchgwMsg.field_radius().value() = BroadcastRadius; - unitTestClientInputMessage(client, searchgwMsg); + unitTestClientInputMessage(client, searchgwMsg, CC_MqttsnDataOrigin_Any); } unitTestTick(client); // Tiemout to send GWINFO From 4dadb268d6b195a94e829d7ef547bee817d7aa52 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 13 Aug 2024 08:09:23 +1000 Subject: [PATCH 095/106] Removed old client. --- client.old/CMakeLists.txt | 196 - client.old/doc/main.dox | 689 --- client.old/script/ProcessTemplate.cmake | 15 - client.old/src/CMakeLists.txt | 2 - client.old/src/app/CMakeLists.txt | 6 - client.old/src/app/pub/CMakeLists.txt | 1 - client.old/src/app/pub/udp/CMakeLists.txt | 44 - client.old/src/app/pub/udp/Pub.cpp | 501 --- client.old/src/app/pub/udp/Pub.h | 177 - client.old/src/app/pub/udp/main.cpp | 395 -- client.old/src/app/sub/CMakeLists.txt | 1 - client.old/src/app/sub/udp/CMakeLists.txt | 44 - client.old/src/app/sub/udp/Sub.cpp | 487 -- client.old/src/app/sub/udp/Sub.h | 176 - client.old/src/app/sub/udp/main.cpp | 384 -- client.old/src/basic/BasicClient.h | 2983 ------------- client.old/src/basic/CMakeLists.txt | 98 - client.old/src/basic/ClientMgr.h | 65 - client.old/src/basic/ParsedOptions.h | 20 - client.old/src/basic/details/OptionsParser.h | 123 - .../src/basic/details/WriteBufStorageType.h | 72 - client.old/src/basic/option.h | 65 - client.old/templ/client.cpp.templ | 348 -- client.old/templ/client.h.templ | 556 --- client.old/test/CMakeLists.txt | 54 - client.old/test/ClientBasic.th | 3957 ----------------- client.old/test/CommonTestClient.cpp | 737 --- client.old/test/CommonTestClient.h | 249 -- client.old/test/DataProcessor.cpp | 502 --- client.old/test/DataProcessor.h | 189 - client.old/test/bare_metal_app/CMakeLists.txt | 37 - client.old/test/bare_metal_app/raspberrypi.ld | 61 - .../bare_metal_app/startup/CMakeLists.txt | 20 - .../test/bare_metal_app/startup/startup.s | 110 - .../test_client_build/CMakeLists.txt | 39 - .../bare_metal_app/test_client_build/main.c | 171 - .../bare_metal_app/test_client_build/stub.cpp | 86 - 37 files changed, 13660 deletions(-) delete mode 100644 client.old/CMakeLists.txt delete mode 100644 client.old/doc/main.dox delete mode 100644 client.old/script/ProcessTemplate.cmake delete mode 100644 client.old/src/CMakeLists.txt delete mode 100644 client.old/src/app/CMakeLists.txt delete mode 100644 client.old/src/app/pub/CMakeLists.txt delete mode 100644 client.old/src/app/pub/udp/CMakeLists.txt delete mode 100644 client.old/src/app/pub/udp/Pub.cpp delete mode 100644 client.old/src/app/pub/udp/Pub.h delete mode 100644 client.old/src/app/pub/udp/main.cpp delete mode 100644 client.old/src/app/sub/CMakeLists.txt delete mode 100644 client.old/src/app/sub/udp/CMakeLists.txt delete mode 100644 client.old/src/app/sub/udp/Sub.cpp delete mode 100644 client.old/src/app/sub/udp/Sub.h delete mode 100644 client.old/src/app/sub/udp/main.cpp delete mode 100644 client.old/src/basic/BasicClient.h delete mode 100644 client.old/src/basic/CMakeLists.txt delete mode 100644 client.old/src/basic/ClientMgr.h delete mode 100644 client.old/src/basic/ParsedOptions.h delete mode 100644 client.old/src/basic/details/OptionsParser.h delete mode 100644 client.old/src/basic/details/WriteBufStorageType.h delete mode 100644 client.old/src/basic/option.h delete mode 100644 client.old/templ/client.cpp.templ delete mode 100644 client.old/templ/client.h.templ delete mode 100644 client.old/test/CMakeLists.txt delete mode 100644 client.old/test/ClientBasic.th delete mode 100644 client.old/test/CommonTestClient.cpp delete mode 100644 client.old/test/CommonTestClient.h delete mode 100644 client.old/test/DataProcessor.cpp delete mode 100644 client.old/test/DataProcessor.h delete mode 100644 client.old/test/bare_metal_app/CMakeLists.txt delete mode 100644 client.old/test/bare_metal_app/raspberrypi.ld delete mode 100644 client.old/test/bare_metal_app/startup/CMakeLists.txt delete mode 100644 client.old/test/bare_metal_app/startup/startup.s delete mode 100644 client.old/test/bare_metal_app/test_client_build/CMakeLists.txt delete mode 100644 client.old/test/bare_metal_app/test_client_build/main.c delete mode 100644 client.old/test/bare_metal_app/test_client_build/stub.cpp diff --git a/client.old/CMakeLists.txt b/client.old/CMakeLists.txt deleted file mode 100644 index 06bf433c..00000000 --- a/client.old/CMakeLists.txt +++ /dev/null @@ -1,196 +0,0 @@ -option (CC_MQTTSN_BARE_METAL_IMAGE_TEST "Include build of bare metal image" OFF) - -if ((NOT CC_MQTTSN_CLIENT_DEFAULT_LIB) AND - (NOT CC_MQTTSN_BARE_METAL_IMAGE_TEST) AND - ("${CC_MQTTSN_CUSTOM_CLIENT_CONFIG_FILES}" STREQUAL "")) - return () -endif () - -###################################################################### - -set (HEADER_TEMPL ${CMAKE_CURRENT_SOURCE_DIR}/templ/client.h.templ) -set (SRC_TEMPL ${CMAKE_CURRENT_SOURCE_DIR}/templ/client.cpp.templ) -set (TEMPL_PROCESS_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/script/ProcessTemplate.cmake) -set (DEFAULT_CLIENT_NAME "") -set (MQTTSN_CLIENT_LIB_NAME "cc_mqttsn_client") -set (MQTTSN_CLIENT_HEADER_TGT "client.h.tgt") -set (COMMON_INC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) - -###################################################################### - -include(CMakePackageConfigHelpers) -file (READ "${CMAKE_CURRENT_SOURCE_DIR}/include/cc_mqttsn_client/common.h" version_file) -string (REGEX MATCH "CC_MQTTSN_CLIENT_MAJOR_VERSION ([0-9]*)U*" _ ${version_file}) -set (major_ver ${CMAKE_MATCH_1}) -string (REGEX MATCH "CC_MQTTSN_CLIENT_MINOR_VERSION ([0-9]*)U*" _ ${version_file}) -set (minor_ver ${CMAKE_MATCH_1}) -string (REGEX MATCH "CC_MQTTSN_CLIENT_PATCH_VERSION ([0-9]*)U*" _ ${version_file}) -set (patch_ver ${CMAKE_MATCH_1}) -set (CC_MQTTSN_CLIENT_VERSION "${major_ver}.${minor_ver}.${patch_ver}") -write_basic_package_version_file( - ${PROJECT_BINARY_DIR}/ClientLibConfigVersion.cmake - VERSION ${CC_MQTTSN_CLIENT_VERSION} - COMPATIBILITY AnyNewerVersion) - -function (gen_lib_mqttsn_client name client_opts inst extra_flags) - if (NOT ${name} STREQUAL "") - set (name "${name}_") - endif () - - set (lib_name "cc_mqttsn_${name}client") - - set (header_output ${CMAKE_CURRENT_BINARY_DIR}/${name}client.h) - set (src_output ${CMAKE_CURRENT_BINARY_DIR}/${name}client.cpp) - - add_custom_command( - OUTPUT "${header_output}" - COMMAND ${CMAKE_COMMAND} - -DIN_FILE="${HEADER_TEMPL}" - -DOUT_FILE="${header_output}" - -DNAME="${name}" - -DCLIENT_OPTS="${client_opts}" - -P ${TEMPL_PROCESS_SCRIPT} - DEPENDS ${HEADER_TEMPL} ${TEMPL_PROCESS_SCRIPT} - ) - - set_source_files_properties( - ${header_output} - PROPERTIES GENERATED TRUE - ) - - set (header_tgt_name "${name}client.h.tgt") - add_custom_target( - ${header_tgt_name} - DEPENDS "${header_output}" ${HEADER_TEMPL} ${TEMPL_PROCESS_SCRIPT} - ) - - add_custom_command( - OUTPUT "${src_output}" - COMMAND ${CMAKE_COMMAND} - -DIN_FILE="${SRC_TEMPL}" - -DOUT_FILE="${src_output}" - -DNAME="${name}" - -DCLIENT_OPTS="${client_opts}" - -P ${TEMPL_PROCESS_SCRIPT} - DEPENDS ${SRC_TEMPL} ${TEMPL_PROCESS_SCRIPT} - ) - - set_source_files_properties( - ${src_output} - PROPERTIES GENERATED TRUE - ) - - set (src_tgt_name "${name}client.cpp.tgt") - add_custom_target( - ${src_tgt_name} - DEPENDS "${src_output}" ${SRC_TEMPL} ${TEMPL_PROCESS_SCRIPT} - ) - - message (STATUS "Defining library ${lib_name}") - add_library (${lib_name} STATIC ${src_output}) - add_library (cc::${lib_name} ALIAS ${lib_name}) - target_link_libraries(${lib_name} PRIVATE cc::cc_mqttsn cc::comms) - target_include_directories( - ${lib_name} BEFORE - PUBLIC - $ - $ - PRIVATE - "${CMAKE_CURRENT_BINARY_DIR}") - - set_target_properties( - ${lib_name} PROPERTIES - INTERFACE_LINK_LIBRARIES "" - ) - add_dependencies(${lib_name} ${header_tgt_name} ${src_tgt_name}) - - if (CMAKE_COMPILER_IS_GNUCC) - list (APPEND extra_flags - "-ftemplate-backtrace-limit=0" - "-fno-rtti" - "-fno-exceptions" - "-fno-unwind-tables" - "-fno-threadsafe-statics" - ) - - endif () - - if (NOT "${extra_flags}" STREQUAL "") - string(REPLACE ";" " " extra_flags "${extra_flags}") - - set_target_properties( - ${lib_name} PROPERTIES - COMPILE_FLAGS ${extra_flags} - ) - endif () - - if (inst) - install ( - FILES ${header_output} - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cc_mqttsn_client - ) - - install ( - TARGETS ${lib_name} - DESTINATION ${CMAKE_INSTALL_LIBDIR} - EXPORT ${lib_name}Config - ) - - install(EXPORT ${lib_name}Config NAMESPACE cc:: - DESTINATION ${CMAKE_INSTALL_LIBDIR}/${lib_name}/cmake - ) - - install( - FILES ${PROJECT_BINARY_DIR}/ClientLibConfigVersion.cmake - DESTINATION ${CMAKE_INSTALL_LIBDIR}/${lib_name}/cmake - RENAME ${lib_name}ConfigVersion.cmake - ) - endif () -endfunction() - -###################################################################### - -find_package(LibComms REQUIRED) -find_package(cc_mqttsn REQUIRED) - -install ( - DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/cc_mqttsn_client - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} -) - -add_subdirectory (src) -add_subdirectory (test) - -###################################################################### - -find_package (Doxygen) -if (DOXYGEN_FOUND AND CC_MQTTSN_CLIENT_DEFAULT_LIB) - set (doc_output_dir "${CMAKE_INSTALL_FULL_DATAROOTDIR}/doc/cc_mqttsn_client") - make_directory (${doc_output_dir}) - - set (match_str "OUTPUT_DIRECTORY[^\n]*") - set (replacement_str "OUTPUT_DIRECTORY = ${doc_output_dir}") - set (output_file "${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf") - - set (config_file "${CMAKE_CURRENT_SOURCE_DIR}/doc/doxygen.conf") - file (READ ${config_file} config_text) - string (REGEX REPLACE "${match_str}" "${replacement_str}" modified_config_text "${config_text}") - file (WRITE "${output_file}" "${modified_config_text}") - - set (interface_doc_dir "${CMAKE_CURRENT_BINARY_DIR}/interface_doc") - execute_process( - COMMAND ${CMAKE_COMMAND} -E make_directory ${interface_doc_dir}) - execute_process( - COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_CURRENT_SOURCE_DIR}/doc/main.dox - ${CMAKE_CURRENT_SOURCE_DIR}/include/cc_mqttsn_client/common.h - ${interface_doc_dir}/) - - add_custom_target ("doc_mqttsn_client" - COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_BINARY_DIR}/src/basic/client.h" ${interface_doc_dir}/ - COMMAND ${DOXYGEN_EXECUTABLE} ${output_file} - WORKING_DIRECTORY ${interface_doc_dir}) - - add_dependencies("doc_mqttsn_client" ${MQTTSN_CLIENT_HEADER_TGT}) -endif () - diff --git a/client.old/doc/main.dox b/client.old/doc/main.dox deleted file mode 100644 index db26efbd..00000000 --- a/client.old/doc/main.dox +++ /dev/null @@ -1,689 +0,0 @@ -/// @mainpage MQTT-SN Client Library -/// @tableofcontents -/// @section cc_mqttsn_client_overview Overview -/// The MQTT-SN Client Library provides simple, asynchronous, non-blocking, -/// and easy to use interface to operate MQTT-SN client. The library doesn't -/// make any assumption on the system it is running on, as well as on the type -/// of I/O link being used to communicate its data to the MQTT-SN gateway. -/// -/// The main logic of operation can be described as following: -/// -/// @li Sends and receives over the I/O link are managed outside this -/// library. The data received from the gateway is provided to the library for processing -/// (see cc_mqttsn_client_process_data()) and the data to be sent to the gateway is -/// provided by the library using callback (see -/// cc_mqttsn_client_set_send_output_data_callback()). -/// @li The library requires measurement of timeouts, it will request to -/// do so using callback (see -/// cc_mqttsn_client_set_next_tick_program_callback()). -/// The driving code will have to use available system timer -/// and notify the library about the requested time expiry (see cc_mqttsn_client_tick()). -/// @li Sometimes the library will terminate the previously requested time -/// measurement request and inquire the amount of elapsed milliseconds -/// since last request. Such inquiry is performed using callback (see -/// cc_mqttsn_client_set_cancel_next_tick_wait_callback()). -/// @li The library supports only @b one asynchronous operation at a time. The -/// next one can @b NOT be started until previous one complete or cancelled. -/// @li All the asynchronous operations receive pointer to callback function, -/// which will be invoked when the operation is complete. -/// @li When asynchronous operation request receives pointer to any buffer -/// (string or raw data), this buffer must be preserved intact by the caller -/// until the asynchronous operation is complete (provided callback is called). -/// -/// @section cc_mqttsn_client_io_links Various I/O Links -/// MQTT-SN is designed to be a @b datagram protocol, i.e. if sent message is -/// received by the other side, it is received in full and -/// without errors. If sent over UDP link, it can be used as is. The -/// UDP transport cares about all the rest. However, when sent over other -/// I/O link, such as RS-232 serial connection, the protocol may require additional -/// transport wrapping to insure correct delivery and differentiate between packets. -/// -/// The interface, this MQTT-SN Client Library provides, allows any -/// sent or received data to be wrapped with or unwrapped from additional -/// independent transport data, that insures safe and correct delivery. -/// -/// @section cc_mqttsn_client_header Header -/// To use this MQTT-SN Client Library use the following include statement: -/// @code -/// #include "cc_mqttsn_client/client.h" -/// @endcode -/// -/// @section cc_mqttsn_client_allocation Client Allocation -/// The library supports may support multiple independent MQTT-SN clients. The -/// allocation of data structures relevant to a single client is performed -/// using cc_mqttsn_client_new() function. -/// @code -/// CC_MqttsnClientHandle client = cc_mqttsn_client_new(); -/// @endcode -/// All other functions are client specific, the receive the returned handle -/// as their first parameter. -/// -/// When work with allocated client is complete, it must be freed using -/// cc_mqttsn_client_free() function. -/// @code -/// cc_mqttsn_client_free(client); -/// @endcode -/// -/// All the allocated clients are completely independent. It is safe to use -/// multiple threads working with multiple clients as long as there are no -/// concurrent accesses to the same one. -/// -/// @section cc_mqttsn_client_setup Client Setup -/// As was mentioned earlier, there is a need for multiple callbacks to be -/// set. Four of them are mandatory. -/// -# Callback to be invoked when new message needs to be sent to the gateway -/// (see cc_mqttsn_client_set_send_output_data_callback()). -/// @code -/// void my_send(void* userData, const unsigned char* buf, unsigned bufLen, bool broadcast) -/// { -/// if (broadcast) { -/// ... /* broadcast */ -/// } -/// else { -/// ... /* send data to connected GW*/ -/// } -/// } -/// -/// void* someUserData = ...; -/// cc_mqttsn_client_set_send_output_data_callback(client, &my_send, someUserData); -/// @endcode -/// -# Callback to be invoked when timer needs to be programmed -/// (see cc_mqttsn_client_set_next_tick_program_callback()). -/// @code -/// void my_timer(void* userData, unsigned ms) -/// { -/// ... /* Program asynchronous timer to tick after requested number of milliseconds. When expires invoke cc_mqttsn_client_tick() */ -/// } -/// -/// cc_mqttsn_client_set_next_tick_program_callback(client, &my_timer, someUserData); -/// @endcode -/// -# Callback to be invoked when previous timer program needs to be cancelled -/// and number of elapsed seconds since last timer programming request known -/// (see cc_mqttsn_client_set_cancel_next_tick_wait_callback()). -/// @code -/// unsigned my_cancel_timer(void* userData) -/// { -/// ... /* cancel timer */ -/// return ...; /* return number of elapsed milliseconds */ -/// } -/// -/// cc_mqttsn_client_set_cancel_next_tick_wait_callback(client, &my_cancel_timer, someUserData); -/// @endcode -/// -# Callback to be invoked when new application message arrives -/// (see cc_mqttsn_client_set_message_report_callback()). -/// @code -/// void my_message_handler(void* userData, const CC_MqttsnMessageInfo* msgInfo) -/// { -/// ... /* handle application message */ -/// } -/// -/// cc_mqttsn_client_set_message_report_callback(client, &my_message_handler, someUserData); -/// @endcode -/// -/// All other callbacks are optional and described in later sections. -/// -/// MQTT-SN protocol defines some messages that need to be acknowledged by the -/// gateway. If the expected response is not received after some time, the -/// message needs to be resent. After number of such retry attempts if there is -/// still no response, the requested operation needs to terminate with relevant -/// error code. The retry period in seconds can be configured using -/// cc_mqttsn_client_set_retry_period() function and number of retry attempts -/// can be configured using cc_mqttsn_client_set_retry_count() function. The -/// default values are @b 15 seconds and @b 3 attempts respectively. -/// @code -/// cc_mqttsn_client_set_retry_period(client, 5); // retry after 5 seconds -/// cc_mqttsn_client_set_retry_count(client, 4); // perform 4 retry attempts -/// @endcode -/// -/// After client has been successfully configured, it needs to be started. -/// @code -/// CC_MqttsnErrorCode result = cc_mqttsn_client_start(client); -/// if (result != CC_MqttsnErrorCode_Success) { -/// ... /* start has failed, probably some mandatory callback hasn't been set */ -/// } -/// @endcode -/// If a gateway needs to be discovered (see @ref cc_mqttsn_client_gateways), the -/// client will immediately request to send generated @b SEARCHGW message. -/// -/// If client's work needs to be paused for a while, it can be stopped using -/// cc_mqttsn_client_stop() and restarted again using cc_mqttsn_client_start(). -/// -/// @section cc_mqttsn_client_data_recv Receiving Data -/// When new data datagram is successfully received over I/O link, it needs -/// to be passed to the library for processing using cc_mqttsn_client_process_data(). -/// @code -/// unsigned char buf[1024] = {0}; -/// ... /* Read data into buffer */ -/// -/// unsigned consumed = cc_mqttsn_client_process_data(client, &buf[0], bytesCount); -/// if (consumed < bytesCount) { -/// ... /* Unexpected protocol error, too few bytes consumed. */ -/// } -/// @endcode -/// The cc_mqttsn_client_process_data() function returns number of bytes that -/// were actually processed. If number of processed bytes is less that number -/// of bytes residing in the buffer, then probably some unexpected protocol error has -/// occurred. -/// -/// The invocation of cc_mqttsn_client_process_data() function may cause invocation -/// of multiple callbacks, such as cancellation of the current time measurement, -/// report of incoming new application message, report information about new -/// gateway, asynchronous operation completion, request to send data back to -/// the gateway, and reprogram timer with new delay value. -/// -/// @section cc_mqttsn_client_data_send Sending Data -/// As was mentioned earlier, the request to send data is performed using callback -/// function. It is set using cc_mqttsn_client_set_send_output_data_callback(). -/// @code -/// void my_send(void* userData, const unsigned char* buf, unsigned bufLen, bool broadcast) -/// { -/// ... -/// } -/// -/// cc_mqttsn_client_set_send_output_data_callback(client, &my_send, someUserData); -/// @endcode -/// @b NOTE, that the callback function receives pointer to the buffer of data. -/// This buffer will be destroyed or updated right after the function returns. -/// The callback code may require to copy the buffer contents to some other -/// private data structure and preserve it until data has been properly sent -/// over the I/O link in full. -/// -/// Also @b NOTE the @b broadcast boolean parameter. It specifies whether the -/// data needs to be broadcasted on the network or sent directly to the -/// gateway that the client is connected to. -/// -/// @section cc_mqttsn_client_time_measurement Time Measurement -/// The MQTT-SN client must be aware of time to be able to measure delays -/// between messages. The driving code has to provide a callback using -/// which the library may request for such time measurement. The second -/// parameter to the callback will specify number of @b milliseconds it -/// needs to measure. -/// @code -/// void my_timer(void* userData, unsigned ms) -/// { -/// ... /* Program asynchronous timer to tick after requested number of milliseconds. When expires invoke cc_mqttsn_client_tick() */ -/// } -/// -/// cc_mqttsn_client_set_next_tick_program_callback(client, &my_timer, someUserData); -/// @endcode -/// -/// The driving code will have to use its system timer(s) to measure the -/// required time, and notify the library when it expires using -/// cc_mqttsn_client_tick() function. -/// @code -/// cc_mqttsn_client_tick(client); -/// @endcode -/// -/// Based on some events, the library may require knowledge of elapsed time -/// since last tick programming request. For this purpose the driving code -/// must set a callback to cancel the existing time measurement and return -/// number of elapsed @b milliseconds. -/// @code -/// unsigned my_cancel_timer(void* userData) -/// { -/// ... /* cancel timer */ -/// return ...; /* return number of elapsed milliseconds */ -/// } -/// -/// cc_mqttsn_client_set_cancel_next_tick_wait_callback(client, &my_cancel_timer, someUserData); -/// @endcode -/// When the library issues the existing time measurement cancellation request, -/// the driving code must cancel its programmed system timer and -/// must @b NOT call cc_mqttsn_client_tick() function later on. -/// -/// @section cc_mqttsn_client_gateways Gateway Discovery -/// The MQTT-SN protocol built to be suitable for wireless sensor networks, where -/// the exact address of the gateway may be unknown upfront or there may be -/// multiple gateways available. As the result it includes messages for -/// gateway tracking and discovery. However, when the address of the gateway to use is known, -/// the gateway discovery may become unnecessary. The MQTT-SN Client Library -/// provides an ability to enable or disable broadcast of @b SEARCHGW message, -/// which causes available gateways to respond and be identified. -/// @code -/// cc_mqttsn_client_set_searchgw_enabled(client, false); /* Disable sending of SEARCHGW messages to discover the gateway */ -/// @endcode -/// By default, the broadcast of @b SEARCHGW message is -/// @b enabled, and the library will immediately request to do so when -/// call to cc_mqttsn_client_start() is performed. -/// -/// The library monitors the @b GWINFO responses of the gateways as well as -/// their own independent advertising of their presence (using @b ADVERTISE -/// message) and reports the discovered gateways via callback. -/// @code -/// void my_gw_status_report(void* userData, unsigned char gwId, CC_MqttsnGwStatus status) -/// { -/// if (status == CC_MqttsnGwStatus_Available) { -/// ... /* New gateway has been discovered */ -/// } -/// else if (status == CC_MqttsnGwStatus_TimedOut) { -/// ... /* The gateway hasn't advertise its presence for a while, probably is not available any more */ -/// } -/// ... -/// } -/// -/// cc_mqttsn_client_set_gw_status_report_callback(client, &my_gw_status_report, someUserData); -/// @endcode -/// The gateways report their numeric ID (only one byte long), -/// which is expected be unique on the available network. -/// This ID is passed as a parameter to the provided callback. However, -/// in order to connect to the chosen available gateway, there may be a need to know -/// address of the latter. In this case, the driving code should store the origin address -/// of every message in its private data structures prior to forwarding the -/// incoming data to the library for processing (see @ref cc_mqttsn_client_data_recv). -/// If the gateway status report callback is called immediately, indicating -/// available gateway, the reported ID as well as recorded address may be -/// stored as gateway identification information and to be used for connection -/// in the future. -/// -/// @b NOTE, that the library sends @b SEARCHGW messages only when there is no -/// known available gateway. It will continue to send them periodically -/// until first available gateway responds. After that, the library will count -/// on any new gateways to independently advertise their presence using -/// @b ADVERTISE message, when they become available. When the last known -/// gateway fails to re-advertise its presence after some time, the library will -/// resume sending @b SEARCHGW messages again until first gateway responds. -/// -/// Also @b NOTE, that the reported list of available gateways is indicative -/// only. There may be the case when some gateway gone offline, but the library -/// hasn't reported that it's missing yet. The opposite case may also take place, -/// when some gateway failed to advertise its presence in time, and hence was -/// reported as "timed out", but may still be online and respond to messages. -/// The only way to identify the real availability of the gateway is to try -/// to connect to it (see @ref cc_mqttsn_client_connect_disconnect section below). -/// -/// The library provides an ability to discard information about currently being -/// tracked gateway. It may be useful when attempt to connect or send any messages -/// fails with timeout, which may indicate that gateway has gone offline. When -/// the gateway appears online again and advertises its presence, the gateway -/// status report callback will be invoked again reporting new availability. -/// @code -/// cc_mqttsn_client_discard_gw(client, gwId); -/// @endcode -/// -/// The library also provides an ability to discard information about all -/// gateways at once. -/// @code -/// cc_mqttsn_client_discard_all_gw(client); -/// @endcode -/// -/// @section cc_mqttsn_client_connect_disconnect Connect / Disconnect -/// In order to establish connection to MQTT-SN gateway, the client must send -/// to it special @b CONNECT message. It must be done prior to issuing any -/// publish or subscribe request. The connection can be performed using -/// cc_mqttsn_client_connect() call. -/// @code -/// const char* clientId = "my_client_id"; -/// void my_connect_complete(void* userData, CC_MqttsnAsyncOpStatus status) -/// { -/// if (status == CC_MqttsnAsyncOpStatus_Successful) { -/// ... /* Connection is successful, do something */ -/// } -/// else { -/// ... /* process error statuses */ -/// } -/// ... -/// } -/// -/// void someFunc() -/// { -/// CC_MqttsnErrorCode result = cc_mqttsn_client_connect(client, clientId, 60, true, NULL, &my_client_connect, someUserData); -/// ... -/// } -/// @endcode -/// @b NOTE, that client ID (second parameter) can be NULL or empty string. In -/// this case, the @b CONNECT message is sent to the gateway with empty client ID -/// information. The gateway will either replace it with some other default ID (based -/// on its configuration), or forward the connection request to broker with -/// empty client ID to make it an anonymous client. -/// -/// Also @b NOTE, that @b will information (fifth parameter) may also be NULL to -/// indicate that will doesn't exist. NULL or empty string as topic in the -/// passed @ref CC_MqttsnWillInfo structure will have the same effect. -/// @code -/// const char* willTopic = "this/is/will/topic" -/// const unsigned char willMsg[] = {...} -/// const unsigned willMsgSize = sizeof(willMsg)/sizeof(willMsg[0]); -/// -/// void someFunc() -/// { -/// CC_MqttsnWillInfo willInfo; -/// willInfo.topic = willTopic; -/// willInfo.msg = willMsg; -/// willInfo.msgLen = willMsgSize; -/// willInfo.qos = CC_MqttsnQoS_AtMostOnceDelivery; -/// willInfo.retain = false; -/// -/// CC_MqttsnErrorCode result = cc_mqttsn_client_connect(client, clientId, 60, true, &willInfo, &my_client_connect, someUserData); -/// ... -/// } -/// @endcode -/// Plaese pay attention to the fact that all the buffers containing -/// client ID, will topic as will as will message body must be preserved intact -/// until the completion callback is called. However, the @b willInfo structure -/// may reside on the stack and be destroyed right after invocation of -/// cc_mqttsn_client_connect() function. -/// -/// When client needs to gracefully terminate its connection to the gateway it -/// needs to send @b DISCONNECT message. It can be achieved by calling to -/// cc_mqttsn_client_disconnect() function. -/// @code -/// void my_disconnect_complete(void* userData, CC_MqttsnAsyncOpStatus status) -/// { -/// ... /* do something */ -/// } -/// -/// CC_MqttsnErrorCode result = cc_mqttsn_client_disconnect(client, &my_disconnect_complete, someUserData); -/// ... -/// @endcode -/// If client just abandons its connection to the gateway without proper -/// disconnection, sooner or later, the broker will terminate its connection -/// to the client through gateway and will publish the client's will (if such -/// existed). -/// -/// Regardless of success/failure status reported to the callback, the library -/// assumes disconnected state and won't be able to execute operations that -/// require valid connection to the gateway. -/// -/// The gateway itself may issue unsolicited disconnection request. To get -/// notified of such requests, the client needs to set a special callback. -/// @code -/// void my_gw_disconnect_report(void* userData) -/// { -/// ... /* try to connect to the gateway again */ -/// } -/// -/// cc_mqttsn_client_set_gw_disconnect_report_callback(client, &my_gw_disconnect_report, someUserData); -/// @endcode -/// If this callback is invoked, the client may try to connect to the gateway again. -/// -/// When establishing first connection to the broker (using cc_mqttsn_client_connect()), -/// the client ID and "keep alive" -/// period information is provided. This information is stored in internal -/// data structures of the library. When there is a need to refresh the same -/// connection to the client, cc_mqttsn_client_reconnect() function may be used. -/// Such need may arise when any asynchronous operation reports -/// @ref CC_MqttsnAsyncOpStatus_NoResponse. It may happen for multiple reasons, -/// for example, when gateway issued unsolicited disconnect request, but its -/// @b DISCONNECT message wasn't received by the client. The gateway will -/// drop or reject any incoming messages until the new connection request is -/// sent. -/// @code -/// void my_reconnect_complete(void* userData, CC_MqttsnAsyncOpStatus status) -/// { -/// if (status == CC_MqttsnAsyncOpStatus_Successful) { -/// ... /* Connection is successful, do something */ -/// } -/// else { -/// ... /* process error statuses */ -/// } -/// ... -/// } -/// -/// CC_MqttsnErrorCode result = cc_mqttsn_client_reconnect(client, &my_reconnect_complete, someUserData); -/// ... -/// @endcode -/// -/// @section cc_mqttsn_client_publish Publishing -/// The publish operation is performed using cc_mqttsn_client_publish() function call. -/// @code -/// void my_publish_complete(void* userData, CC_MqttsnAsyncOpStatus status) -/// { -/// ... -/// } -/// -/// const char* pubTopic = "some/topic" -/// const unsigned char pubData[] = {...}; -/// const unsigned pubDataSize = sizeof(pubData)/sizeof(pubData[0]); -/// -/// void someFunc() { -/// CC_MqttsnErrorCode result = -/// cc_mqttsn_client_publish( -/// client, -/// pubTopic, -/// pubData, -/// pubDataSize, -/// CC_MqttsnQoS_AtLeastOnceDelivery, -/// false, -/// &my_publish_complete, -/// someUserData); -/// ... -/// } -/// @endcode -/// @b NOTE, that buffers of publish topic and publish message data must be -/// preserved intact by the caller until the completion callback is invoked. -/// -/// The MQTT-SN protocol also supports predefined numeric topic IDs instead of -/// topic strings. To publish such message use cc_mqttsn_client_publish_id(). -/// @code -/// void someFunc() { -/// CC_MqttsnErrorCode result = -/// cc_mqttsn_client_publish_id( -/// client, -/// pubTopicId, /* numeric topic ID instead of string */ -/// pubData, -/// pubDataSize, -/// CC_MqttsnQoS_AtLeastOnceDelivery, -/// false, -/// &my_publish_complete, -/// someUserData); -/// } -/// @endcode -/// -/// @section cc_mqttsn_client_subscribe Subscribing -/// The subscribe operation is performed using cc_mqttsn_client_subscribe() function -/// call. -/// @code -/// void my_subscribe_complete(void* userData, CC_MqttsnAsyncOpStatus status, CC_MqttsnQoS qos) -/// { -/// ... -/// } -/// -/// const char* subTopic = "subtopic1/+/subtopic2/#" -/// -/// void someFunc() { -/// CC_MqttsnErrorCode result = -/// cc_mqttsn_client_subscribe( -/// client, -/// subTopic, -/// CC_MqttsnQoS_ExactlyOnceDelivery, /* max QoS */ -/// &my_subscribe_complete, -/// someUserData); -/// ... -/// } -/// @endcode -/// Please pay closer attention to the following facts: -/// @li The buffer containing subscription topic must be preserved intact by the caller -/// until the end of the operation when callback is called. -/// @li The @b qos parameter to the function call specifies @b maximal quality -/// of service level, with which the gateway/broker is allowed to publish messages -/// to the client. -/// @li The completion callback also has @b qos parameter. It specifies @b -/// actual maximal QoS level the broker/gateway is going to use to -/// publish messages to the client. -/// @li Just like with MQTT protocol, the subscription topic may have the same -/// @b + and @b # wildcards. -/// -/// The MQTT-SN protocol also supports subscription to predefined topic IDs -/// instead of topic strings. To do so use cc_mqttsn_client_subscribe_id(). -/// @code -/// void someFunc() { -/// CC_MqttsnErrorCode result = -/// cc_mqttsn_client_subscribe_id( -/// client, -/// subTopicId, /* numeric topic ID instead of string */ -/// CC_MqttsnQoS_ExactlyOnceDelivery, -/// &my_subscribe_complete, -/// someUserData); -/// ... -/// } -/// @endcode -/// -/// @section cc_mqttsn_client_unsubscribe Unsubscribing -/// To unsubscribe use either cc_mqttsn_client_unsubscribe() for string topics: -/// @code -/// void my_unsubscribe_complete(void* userData, CC_MqttsnAsyncOpStatus status) -/// { -/// ... -/// } -/// -/// const char* unsubTopic = "some/topic" -/// -/// void someFunc() { -/// CC_MqttsnErrorCode result = -/// cc_mqttsn_client_unsubscribe( -/// client, -/// unsubTopic, -/// &my_unsubscribe_complete, -/// someUserData); -/// ... -/// } -/// @endcode -/// and cc_mqttsn_client_unsubscribe_id() for predefined numeric topic IDs: -/// @code -/// void someFunc() { -/// CC_MqttsnErrorCode result = -/// cc_mqttsn_client_unsubscribe_id( -/// client, -/// unsubTopicId, // numeric ID -/// &my_unsubscribe_complete, -/// someUserData); -/// ... -/// } -/// @endcode -/// -/// @section cc_mqttsn_client_will Updating the Will -/// There are several ways to update the will information that was initially -/// setup during connection process (see @ref cc_mqttsn_client_connect_disconnect). -/// -/// One way is to update both topic and message body in one go: -/// @code -/// void my_will_update_complete(void* userData, CC_MqttsnAsyncOpStatus status) -/// { -/// ... -/// } -/// const char* willTopic = "updated/will/topic" -/// const unsigned char willMsg[] = {...} -/// const unsigned willMsgSize = sizeof(willMsg)/sizeof(willMsg[0]); -/// -/// void someFunc() -/// { -/// CC_MqttsnWillInfo willInfo; -/// willInfo.topic = willTopic; -/// willInfo.msg = willMsg; -/// willInfo.msgLen = willMsgSize; -/// willInfo.qos = CC_MqttsnQoS_AtMostOnceDelivery; -/// willInfo.retain = false; -/// -/// CC_MqttsnErrorCode result = cc_mqttsn_client_will_update(client, &willInfo, &my_will_update_complete, someUserData); -/// ... -/// } -/// @endcode -/// Just like with connection request, all the buffers must be preserved by the -/// caller until callback is called. Passing NULL as second parameter -/// (pointer to CC_MqttsnWillInfo structure) as well as assigning NULL or empty -/// string to @b topic data member of the struct means clearing the will -/// information recorded with the broker. -/// -/// Another possible way is to update both will topic and will message body -/// separately. -/// @code -/// void my_will_topic_update_complete(void* userData, CC_MqttsnAsyncOpStatus status) -/// { -/// ... -/// } -/// const char* willTopic = "updated/will/topic" -/// -/// void someFunc() -/// { -/// CC_MqttsnErrorCode result = -/// cc_mqttsn_client_will_topic_update( -/// client, -/// willTopic, -/// CC_MqttsnQoS_AtMostOnceDelivery, -/// false, -/// &my_will_topic_update_complete, -/// someUserData); -/// ... -/// } -/// @endcode -/// Passing NULL or empty string as will topic parameter will clear the will -/// information registration recorded with the broker. -/// -/// Updating will message body: -/// @code -/// void my_will_msg_update_complete(void* userData, CC_MqttsnAsyncOpStatus status) -/// { -/// ... -/// } -/// const unsigned char willMsg[] = {...} -/// const unsigned willMsgSize = sizeof(willMsg)/sizeof(willMsg[0]); -/// -/// void someFunc() -/// { -/// CC_MqttsnErrorCode result = -/// cc_mqttsn_client_will_msg_update( -/// client, -/// willMsg, -/// willMsgSize, -/// &my_will_msg_update_complete, -/// someUserData); -/// ... -/// } -/// @endcode -/// -/// @section cc_mqttsn_client_sleep Sleeping -/// The MQTT-SN protocol supports sleeping clients. It requires to let gateway -/// know about entering the low power mode to cause accumulating the -/// incoming messages with the gateway until clients wakes up. -/// -/// To notify the gateway use cc_mqttsn_client_sleep() function. -/// @code -/// void my_sleep_complete(void* userData, CC_MqttsnAsyncOpStatus status) -/// { -/// if (status == CC_MqttsnAsyncOpStatus_Successful) { -/// ... /* Enter low power mode */ -/// } -/// ... -/// } -/// -/// // Next communication attempt in 10 minutes (600 seconds) -/// CC_MqttsnErrorCode result = cc_mqttsn_client_sleep(client, 600, &my_sleep_complete, someUserData); -/// ... -/// @endcode -/// The second parameter specifies time duration in seconds within which the -/// client is going to check for accumulated messages or issue connection/disconnection -/// request. -/// -/// In order to notify the gateway about return to normal running mode, use -/// cc_mqttsn_client_reconnect() message, described in -/// @ref cc_mqttsn_client_connect_disconnect section. -/// -/// Check for pending messages to be delivered can be performed using -/// cc_mqttsn_client_check_messages() function. -/// @code -/// void my_check_messages_complete(void* userData, CC_MqttsnAsyncOpStatus status) -/// { -/// ... -/// } -/// -/// CC_MqttsnErrorCode result = cc_mqttsn_client_check_messages(client, &my_check_messages_complete, someUserData); -/// @endcode -/// Between issuing the check request and completion callback invocation, there -/// can be multiple exchange of messages between the client and gateway with -/// multiple reports about incoming application messages. After callback is -/// called, the client may return to low power mode again for up to number of -/// seconds provided to initial cc_mqttsn_client_sleep() call. When this period -/// is over again, the client needs to either check for accumulated messages -/// again or reconnect to the gateway (using cc_mqttsn_client_reconnect() function). -/// -/// @section cc_mqttsn_client_cancel Cancel Existing Operation -/// This MQTT-SN client library supports only one asynchronous operation at a -/// time. If current operation takes too much time or there is other more -/// important one to perform, the current operation can be cancelled. -/// @code -/// bool cancelled = cc_mqttsn_client_cancel(client); -/// @endcode -/// The return value shows whether the operation was really cancelled. If no -/// operation was in progress, false is returned. If the operation is cancelled, -/// the relevant callback will be invoked with @ref CC_MqttsnAsyncOpStatus_Aborted -/// status. diff --git a/client.old/script/ProcessTemplate.cmake b/client.old/script/ProcessTemplate.cmake deleted file mode 100644 index 5ff945d6..00000000 --- a/client.old/script/ProcessTemplate.cmake +++ /dev/null @@ -1,15 +0,0 @@ -# IN_FILE - input file -# OUT_FILE - output file -# NAME - name to replace -# CLIENT_OPTS -client options - -if (NOT EXISTS "${IN_FILE}") - message (FATAL_ERROR "Input file \"${IN_FILE}\" doesn't exist!") -endif () - -file (READ ${IN_FILE} text_1) -string (REPLACE "##NAME##" "${NAME}" text_2 "${text_1}") -string (REPLACE "##CLIENT_OPTS##" "${CLIENT_OPTS}" text_3 "${text_2}") - -file (WRITE "${OUT_FILE}" "${text_3}") - diff --git a/client.old/src/CMakeLists.txt b/client.old/src/CMakeLists.txt deleted file mode 100644 index 886bc006..00000000 --- a/client.old/src/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -add_subdirectory (basic) -add_subdirectory (app) \ No newline at end of file diff --git a/client.old/src/app/CMakeLists.txt b/client.old/src/app/CMakeLists.txt deleted file mode 100644 index 60e72cbd..00000000 --- a/client.old/src/app/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -if ((NOT CC_MQTTSN_CLIENT_DEFAULT_LIB) OR (NOT CC_MQTTSN_CLIENT_APPS)) - return () -endif () - -add_subdirectory (sub) -add_subdirectory (pub) \ No newline at end of file diff --git a/client.old/src/app/pub/CMakeLists.txt b/client.old/src/app/pub/CMakeLists.txt deleted file mode 100644 index 963576e4..00000000 --- a/client.old/src/app/pub/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_subdirectory (udp) \ No newline at end of file diff --git a/client.old/src/app/pub/udp/CMakeLists.txt b/client.old/src/app/pub/udp/CMakeLists.txt deleted file mode 100644 index bd69bf1c..00000000 --- a/client.old/src/app/pub/udp/CMakeLists.txt +++ /dev/null @@ -1,44 +0,0 @@ -function (bin_pub_udp) - set (name "cc_mqttsn_pub_udp") - - if (NOT Qt5Core_FOUND) - message(WARNING "Can NOT build ${name} due to missing Qt5Core library") - return() - endif () - - if (NOT Qt5Network_FOUND) - message(WARNING "Can NOT build ${name} due to missing Qt5Network library") - return() - endif () - - - set (src - main.cpp - Pub.cpp - ) - - qt5_wrap_cpp( - moc - Pub.h - ) - - add_executable(${name} ${src} ${moc}) - target_link_libraries(${name} PRIVATE cc::${MQTTSN_CLIENT_LIB_NAME} Qt5::Network Qt5::Core) - - install ( - TARGETS ${name} - DESTINATION ${CMAKE_INSTALL_BINDIR}) - -endfunction () - -########################################################### - -find_package(Qt5Core) -find_package(Qt5Network) - -include_directories ( - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_BINARY_DIR}/client/src/basic -) - -bin_pub_udp() diff --git a/client.old/src/app/pub/udp/Pub.cpp b/client.old/src/app/pub/udp/Pub.cpp deleted file mode 100644 index b8631e33..00000000 --- a/client.old/src/app/pub/udp/Pub.cpp +++ /dev/null @@ -1,501 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include "Pub.h" - -#include -#include -#include -#include -#include -#include - -#include - -namespace cc_mqttsn_client -{ - -namespace app -{ - -namespace sub -{ - -namespace udp -{ - -namespace -{ - -typedef std::vector DataBuf; - -} // namespace - -Pub::Pub() - : m_client(cc_mqttsn_client_new()) -{ - cc_mqttsn_client_set_next_tick_program_callback( - m_client, &Pub::nextTickProgramCb, this); - - cc_mqttsn_client_set_cancel_next_tick_wait_callback( - m_client, &Pub::caneclTickCb, this); - - cc_mqttsn_client_set_send_output_data_callback( - m_client, &Pub::sendDataCb, this); - - cc_mqttsn_client_set_gw_status_report_callback( - m_client, &Pub::gwStatusReportCb, this); - - cc_mqttsn_client_set_gw_disconnect_report_callback( - m_client, &Pub::gwDisconnectReportCb, this); - - cc_mqttsn_client_set_message_report_callback( - m_client, &Pub::messageReportCb, this); - - - connect( - &m_timer, SIGNAL(timeout()), - this, SLOT(tick())); - - connect( - &m_socket, SIGNAL(readyRead()), - this, SLOT(readFromSocket())); - - connect( - &m_socket, SIGNAL(error(QAbstractSocket::SocketError)), - this, SLOT(socketErrorOccurred(QAbstractSocket::SocketError))); - -} - -Pub::~Pub() -{ - cc_mqttsn_client_free(m_client); -} - -bool Pub::start() -{ - bool result = - bindLocalPort() && - openSocket() && - connectToGw() && - cc_mqttsn_client_start(m_client) == CC_MqttsnErrorCode_Success; - - if (!result) { - return false; - } - - if (m_qos == CC_MqttsnQoS_NoGwPublish) { - doPublish(); - return true; - } - - if (m_socket.state() == QUdpSocket::ConnectedState) { - doConnect(); - } - - return true; -} - -void Pub::tick() -{ - m_reqTimeout = 0; - cc_mqttsn_client_tick(m_client); -} - -void Pub::readFromSocket() -{ - DataBuf data; - - while (m_socket.hasPendingDatagrams()) { - data.resize(m_socket.pendingDatagramSize()); - auto readBytes = m_socket.readDatagram( - reinterpret_cast(&data[0]), - data.size(), - &m_lastSenderAddress, - &m_lastSenderPort); - assert(readBytes == static_cast(data.size())); - static_cast(readBytes); - - if (m_debug) { - std::cout << "[DEBUG]: --> " << std::hex; - for (auto byte : data) { - std::cout << std::setw(2) << std::setfill('0') << static_cast(byte) << ' '; - } - std::cout << std::dec << std::endl; - } - - cc_mqttsn_client_process_data(m_client, &data[0], static_cast(data.size())); - } -} - -void Pub::socketErrorOccurred(QAbstractSocket::SocketError err) -{ - static_cast(err); - std::cerr << "ERROR: UDP Socket: " << m_socket.errorString().toStdString() << std::endl; -} - - -void Pub::nextTickProgram(unsigned ms) -{ -// std::cout << "Tick req: " << ms << std::endl; - m_reqTimeout = ms; - m_timer.setSingleShot(true); - m_timer.start(ms); -} - -void Pub::nextTickProgramCb(void* obj, unsigned ms) -{ - assert(obj != nullptr); - reinterpret_cast(obj)->nextTickProgram(ms); -} - -unsigned Pub::cancelTick() -{ - auto rem = m_timer.remainingTime(); - m_timer.stop(); - - if (m_reqTimeout < static_cast(rem)) { - rem = static_cast(m_reqTimeout); - } - - return m_reqTimeout - static_cast(rem); -} - -unsigned Pub::caneclTickCb(void* obj) -{ - assert(obj != nullptr); - return reinterpret_cast(obj)->cancelTick(); -} - -void Pub::sendData(const unsigned char* buf, unsigned bufLen, bool broadcast) -{ - if (m_debug) { - std::cout << "[DEBUG]: <-- " << std::hex; - for (auto idx = 0U; idx < bufLen; ++idx) { - std::cout << std::setw(2) << std::setfill('0') << static_cast(buf[idx]) << ' '; - } - std::cout << std::dec << std::endl; - } - - if (broadcast) { - broadcastData(buf, bufLen); - return; - } - - if (m_socket.state() != QUdpSocket::ConnectedState) { - return; - } - - sendDataConnected(buf, bufLen); -} - -void Pub::sendDataCb(void* obj, const unsigned char* buf, unsigned bufLen, bool broadcast) -{ - assert(obj != nullptr); - reinterpret_cast(obj)->sendData(buf, bufLen, broadcast); -} - -void Pub::gwStatusReport(unsigned short gwId, CC_MqttsnGwStatus status) -{ - if (status != CC_MqttsnGwStatus_Available) { - return; - } - - if ((0 <= m_gwId) && (gwId != m_gwId)) { - return; - } - - if (m_socket.state() != QUdpSocket::ConnectedState) { - m_socket.connectToHost(m_lastSenderAddress, m_lastSenderPort); - if (!m_socket.waitForConnected(2000)) { - std::cerr << "ERROR: Failed to connect UDP socket" << std::endl; - return; - } - - assert(m_socket.isOpen()); - assert(m_socket.state() == QUdpSocket::ConnectedState); - } - - doConnect(); -} - -void Pub::gwStatusReportCb(void* obj, unsigned char gwId, CC_MqttsnGwStatus status) -{ - assert(obj != nullptr); - reinterpret_cast(obj)->gwStatusReport(gwId, status); -} - -void Pub::gwDisconnectReport() -{ - if (m_disconnecting) { - quitApp(); - return; - } - - std::cerr << "WARNING: Disconnected from GW, reconnecting..." << std::endl; - doConnect(); -} - -void Pub::gwDisconnectReportCb(void* obj) -{ - assert(obj != nullptr); - reinterpret_cast(obj)->gwDisconnectReport(); -} - -void Pub::messageReportCb(void* obj, const CC_MqttsnMessageInfo* msgInfo) -{ - static_cast(obj); - static_cast(msgInfo); -} - -void Pub::doConnect(bool reconnecting) -{ - bool cleanSession = m_cleanSession; - if (reconnecting) { - cleanSession = false; - } - - auto result = - cc_mqttsn_client_connect( - m_client, - m_clientId.c_str(), - m_keepAlive, - cleanSession, - nullptr, - &Pub::connectCompleteCb, - this); - if (result != CC_MqttsnErrorCode_Success) { - std::cerr << "ERROR: Failed to connect to the gateway" << std::endl; - } -} - -void Pub::doPublish() -{ - if (!m_topic.empty()) { - auto result = - cc_mqttsn_client_publish( - m_client, - m_topic.c_str(), - m_msg.empty() ? nullptr : &m_msg[0], - static_cast(m_msg.size()), - m_qos, - m_retain, - &Pub::publishCompleteCb, - this); - if (result != CC_MqttsnErrorCode_Success) { - std::cerr << "ERROR: Failed to initiate publish of topic " << m_topic << std::endl; - quitApp(); - } - return; - } - - if (m_topicId != 0) { - auto result = - cc_mqttsn_client_publish_id( - m_client, - m_topicId, - m_msg.empty() ? nullptr : &m_msg[0], - static_cast(m_msg.size()), - m_qos, - m_retain, - &Pub::publishCompleteCb, - this); - - if (result != CC_MqttsnErrorCode_Success) { - std::cerr << "ERROR: Failed to initiate subscribe for topic ID " << m_topicId << std::endl; - quitApp(); - } - return; - } - - if (m_qos == CC_MqttsnQoS_NoGwPublish) { - quitApp(); - return; - } - - m_disconnecting = true; - cc_mqttsn_client_disconnect(m_client, &Pub::disconnectCompleteCb, this); -} - -void Pub::connectComplete(CC_MqttsnAsyncOpStatus status) -{ - if (m_qos == CC_MqttsnQoS_NoGwPublish) { - return; - } - - if (status == CC_MqttsnAsyncOpStatus_Successful) { - doPublish(); - return; - } - - if (status == CC_MqttsnAsyncOpStatus_Congestion) { - std::cerr << "WARNING: Congestion reported, reconnecting..." << std::endl; - doConnect(); - return; - } - - std::cerr << "ERROR: Failed to connect..." << std::endl; - quitApp(); -} - -void Pub::connectCompleteCb(void* obj, CC_MqttsnAsyncOpStatus status) -{ - assert(obj != nullptr); - reinterpret_cast(obj)->connectComplete(status); -} - -void Pub::disconnectComplete(CC_MqttsnAsyncOpStatus status) -{ - static_cast(status); - quitApp(); -} - -void Pub::disconnectCompleteCb(void* obj, CC_MqttsnAsyncOpStatus status) -{ - assert(obj != nullptr); - reinterpret_cast(obj)->disconnectComplete(status); -} - -void Pub::publishComplete(CC_MqttsnAsyncOpStatus status) -{ - if (status == CC_MqttsnAsyncOpStatus_Congestion) { - std::cerr << "WARNING: Failed to publish due to congestion, retrying..." << std::endl; - doPublish(); - return; - } - - if (status != CC_MqttsnAsyncOpStatus_Successful) { - std::cerr << "WARNING: Failed to publish topic "; - if (!m_topic.empty()) { - std::cerr << m_topic; - } - else if (m_topicId != 0) { - std::cerr << "ID " << m_topicId; - } - std::cerr << std::endl; - } - - if (m_qos == CC_MqttsnQoS_NoGwPublish) { - m_socket.flush(); - quitApp(); - return; - } - - m_disconnecting = true; - cc_mqttsn_client_disconnect(m_client, &Pub::disconnectCompleteCb, this); -} - -void Pub::publishCompleteCb(void* obj, CC_MqttsnAsyncOpStatus status) -{ - assert(obj != nullptr); - reinterpret_cast(obj)->publishComplete(status); -} - -bool Pub::bindLocalPort() -{ - if (m_localPort == 0) { - return true; - } - - bool result = - m_socket.bind(QHostAddress::AnyIPv4, m_localPort, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint); - if (!result) { - std::cerr << "ERROR: Failed to bind UDP socket to local port " << m_localPort << std::endl; - } - return result; -} - -bool Pub::openSocket() -{ - if (m_socket.isOpen()) { - return true; - } - - bool result = m_socket.open(QUdpSocket::ReadWrite); - if (!result) { - std::cerr << "ERROR: Failed to open UDP socket" << std::endl; - } - return result; -} - -bool Pub::connectToGw() -{ - if ((m_gwAddr.isEmpty()) || (m_gwPort == 0)) { - return true; - } - - m_socket.connectToHost(m_gwAddr, m_gwPort); - if (!m_socket.waitForConnected(2000)) { - std::cerr << "ERROR: Failed to connect UDP socket" << std::endl; - return false; - } - - assert(m_socket.isOpen()); - assert(m_socket.state() == QUdpSocket::ConnectedState); - - cc_mqttsn_client_set_searchgw_enabled(m_client, false); - return true; -} - -void Pub::broadcastData(const unsigned char* buf, unsigned bufLen) -{ - if (m_gwPort == 0) { - return; - } - - std::size_t writtenCount = 0; - while (writtenCount < bufLen) { - auto remSize = bufLen - writtenCount; - auto count = - m_socket.writeDatagram( - reinterpret_cast(&buf[writtenCount]), - remSize, - QHostAddress::Broadcast, - m_gwPort); - - if (count < 0) { - std::cerr << "ERROR: Failed to broadcast data" << std::endl; - return; - } - - writtenCount += count; - } - -} - -void Pub::sendDataConnected(const unsigned char* buf, unsigned bufLen) -{ - std::size_t writtenCount = 0; - while (writtenCount < bufLen) { - auto remSize = bufLen - writtenCount; - auto count = - m_socket.write( - reinterpret_cast(&buf[writtenCount]), - remSize); - if (count < 0) { - std::cerr << "ERROR: Failed to write to UDP socket" << std::endl; - return; - } - - writtenCount += count; - } -} - -void Pub::quitApp() -{ - QTimer::singleShot(10, qApp, SLOT(quit())); -} - -} // namespace udp - -} // namespace sub - -} // namespace app - -} // namespace cc_mqttsn_client - - diff --git a/client.old/src/app/pub/udp/Pub.h b/client.old/src/app/pub/udp/Pub.h deleted file mode 100644 index cf59fcd0..00000000 --- a/client.old/src/app/pub/udp/Pub.h +++ /dev/null @@ -1,177 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#pragma once - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "client.h" - -namespace cc_mqttsn_client -{ - -namespace app -{ - -namespace sub -{ - -namespace udp -{ - -class Pub : public QObject -{ - Q_OBJECT -public: - Pub(); - ~Pub(); - - void setGwAddr(const QString& value) - { - m_gwAddr = value; - } - - void setGwPort(unsigned short value) - { - m_gwPort = value; - } - - void setGwId(int value) - { - m_gwId = value; - } - - void setLocalPort(unsigned short value) - { - m_localPort = value; - } - - void setClientId(const std::string& value) - { - m_clientId = value; - } - - void setKeepAlive(unsigned short value) - { - m_keepAlive = value; - } - - void setCleanSession(bool value) - { - m_cleanSession = value; - } - - void setTopic(const std::string& topic) - { - m_topic = topic; - } - - void setTopicId(std::uint16_t topicId) - { - m_topicId = topicId; - } - - void setQos(CC_MqttsnQoS value) - { - m_qos = value; - } - - void setRetain(bool value) - { - m_retain = value; - } - - typedef std::vector DataBuf; - void setMessage(const DataBuf& value) - { - m_msg = value; - } - - void setDebug(bool value) - { - m_debug = value; - } - - bool start(); - -private slots: - void tick(); - void readFromSocket(); - void socketErrorOccurred(QAbstractSocket::SocketError err); - -private: - void nextTickProgram(unsigned ms); - static void nextTickProgramCb(void* obj, unsigned ms); - - unsigned cancelTick(); - static unsigned caneclTickCb(void* obj); - - void sendData(const unsigned char* buf, unsigned bufLen, bool broadcast); - static void sendDataCb(void* obj, const unsigned char* buf, unsigned bufLen, bool broadcast); - - void gwStatusReport(unsigned short gwId, CC_MqttsnGwStatus status); - static void gwStatusReportCb(void* obj, unsigned char gwId, CC_MqttsnGwStatus status); - - void gwDisconnectReport(); - static void gwDisconnectReportCb(void* obj); - - static void messageReportCb(void* obj, const CC_MqttsnMessageInfo* msgInfo); - - void doConnect(bool reconnecting = false); - void doPublish(); - void connectComplete(CC_MqttsnAsyncOpStatus status); - static void connectCompleteCb(void* obj, CC_MqttsnAsyncOpStatus status); - void disconnectComplete(CC_MqttsnAsyncOpStatus status); - static void disconnectCompleteCb(void* obj, CC_MqttsnAsyncOpStatus status); - void publishComplete(CC_MqttsnAsyncOpStatus status); - static void publishCompleteCb(void* obj, CC_MqttsnAsyncOpStatus status); - bool bindLocalPort(); - bool openSocket(); - bool connectToGw(); - void broadcastData(const unsigned char* buf, unsigned bufLen); - void sendDataConnected(const unsigned char* buf, unsigned bufLen); - static void quitApp(); - - CC_MqttsnClientHandle m_client; - QTimer m_timer; - unsigned m_reqTimeout = 0; - QString m_gwAddr; - unsigned short m_gwPort = 0; - int m_gwId = -1; - unsigned short m_localPort = 0; - QUdpSocket m_socket; - QHostAddress m_lastSenderAddress; - quint16 m_lastSenderPort; - std::string m_clientId; - unsigned short m_keepAlive = 0; - bool m_cleanSession = true; - bool m_retain = false; - std::string m_topic; - std::uint16_t m_topicId; - CC_MqttsnQoS m_qos = CC_MqttsnQoS_AtMostOnceDelivery; - DataBuf m_msg; - bool m_debug = false; - bool m_disconnecting = false; -}; - -} // namespace udp - -} // namespace sub - -} // namespace app - -} // namespace cc_mqttsn_client - - diff --git a/client.old/src/app/pub/udp/main.cpp b/client.old/src/app/pub/udp/main.cpp deleted file mode 100644 index 7599a05f..00000000 --- a/client.old/src/app/pub/udp/main.cpp +++ /dev/null @@ -1,395 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "Pub.h" - -namespace -{ - -const QString GwOpt("gateway"); -const QString GwShortOpt("g"); -const QString GwIdOpt("gateway-id"); -const QString GwIdShortOpt("G"); -const QString PortOpt("port"); -const QString PortShortOpt("p"); -const unsigned short DefaultPort = 1883; -const QString DefaultPortStr = QString("%1").arg(DefaultPort); -const QString ClientIdOpt("client-id"); -const QString ClientIdShortOpt("i"); -const QString EmptyClientIdOpt("empty-client-id"); -const QString EmptyClientIdShortOpt("I"); -const QString KeepAliveOpt("keep-alive"); -const QString KeepAliveShortOpt("k"); -const unsigned short DefaultKeepAlivePeriod = 60; -const QString DefaultKeepAlivePeriodStr = QString("%1").arg(DefaultKeepAlivePeriod); -const QString NoCleanOpt("no-clean"); -const QString NoCleanShortOpt("c"); -const QString TopicOpt("topic"); -const QString TopicShortOpt("t"); -const QString TopicIdOpt("topic-id"); -const QString TopicIdShortOpt("T"); -const QString QosOpt("qos"); -const QString QosShortOpt("q"); -const QString RetainOpt("retain"); -const QString RetainShortOpt("r"); -const QString MessageOpt("message"); -const QString MessageShortOpt("m"); -const int DefaultQos = 0; -const QString DefaultQosStr = QString("%1").arg(DefaultQos); -const QString DebugOpt("debug"); -const QString DebugShortOpt("d"); - -void prepareCommandLineOptions(QCommandLineParser& parser) -{ - parser.addHelpOption(); - - QCommandLineOption gwOpt( - QStringList() << GwShortOpt << GwOpt, - "Gateway address. Expected to be in
: format. " - "Port is optional, if not provided defaults to " + DefaultPortStr + - ". If this option is used, no SEARCHGW messages are sent to the gateway, and -" + - GwIdShortOpt + " option is ignored." , - QCoreApplication::translate("main", "value") - ); - parser.addOption(gwOpt); - - QCommandLineOption gwIdOpt( - QStringList() << GwIdShortOpt << GwIdOpt, - QCoreApplication::translate("main", - "Gateway ID to connect to when discovered. " - "If not provided, the first gateway, that responds to SEARCHGW message, " - "will be chosen."), - QCoreApplication::translate("main", "value") - ); - parser.addOption(gwIdOpt); - - QCommandLineOption portOpt( - QStringList() << PortShortOpt << PortOpt, - "Local network port. Defaults to " + DefaultPortStr + " in case -" + GwShortOpt + " option is NOT used.", - QCoreApplication::translate("main", "value") - ); - parser.addOption(portOpt); - - QCommandLineOption idOpt( - QStringList() << ClientIdShortOpt << ClientIdOpt, - "Client ID. If not provided, the client ID is randomised. If provided suppresses usage of -" + EmptyClientIdShortOpt + " option.", - QCoreApplication::translate("main", "value") - ); - parser.addOption(idOpt); - - QCommandLineOption emptyIdOpt( - QStringList() << EmptyClientIdShortOpt << EmptyClientIdOpt, - "Use empty client ID." - ); - parser.addOption(emptyIdOpt); - - QCommandLineOption keepAliveOpt( - QStringList() << KeepAliveShortOpt << KeepAliveOpt, - "Keep alive period in seconds. Defaults to " + DefaultKeepAlivePeriodStr + '.', - QCoreApplication::translate("main", "value") - ); - parser.addOption(keepAliveOpt); - - QCommandLineOption noCleanOpt( - QStringList() << NoCleanShortOpt << NoCleanOpt, - "Connect to GW with 'clean session' bit cleared, i.e. preserve previous subscriptions." - ); - parser.addOption(noCleanOpt); - - QCommandLineOption topicOpt( - QStringList() << TopicShortOpt << TopicOpt, - "Topic to publish.", - QCoreApplication::translate("main", "value") - ); - parser.addOption(topicOpt); - - QCommandLineOption topicIdOpt( - QStringList() << TopicIdShortOpt << TopicIdOpt, - "Predefined topic ID to publish.", - QCoreApplication::translate("main", "value") - ); - parser.addOption(topicIdOpt); - - QCommandLineOption qosOpt( - QStringList() << QosShortOpt << QosOpt, - "Quality of service to publish with. Defaults to " + DefaultQosStr + '.', - QCoreApplication::translate("main", "value") - ); - parser.addOption(qosOpt); - - QCommandLineOption retainOpt( - QStringList() << RetainShortOpt << RetainOpt, - "Published message must be retained." - ); - parser.addOption(retainOpt); - - QCommandLineOption msgOpt( - QStringList() << MessageShortOpt << MessageOpt, - "Message to publish. Treated as ascii string, binary data can be provided using \\x prefix prior to each byte.", - QCoreApplication::translate("main", "value") - ); - parser.addOption(msgOpt); - - QCommandLineOption debugOpt( - QStringList() << DebugShortOpt << DebugOpt, - "Enable debug output." - ); - parser.addOption(debugOpt); -} - -std::tuple splitGwAddr(const QCommandLineParser& parser) -{ - auto gwUrl = parser.value(GwOpt); - auto gwAddr = gwUrl; - auto gwPort = DefaultPort; - auto colonIdx = gwUrl.indexOf(QChar(':')); - if (0 < colonIdx) { - gwAddr = gwUrl.left(colonIdx); - auto gwPortStr = gwUrl.right(gwUrl.size() - (colonIdx + 1)); - bool ok = false; - auto gwPortTmp = gwPortStr.toUInt(&ok); - if (ok && (0 < gwPortTmp) && (gwPortTmp <= std::numeric_limits::max())) { - gwPort = static_cast(gwPortTmp); - } - } - - return std::make_tuple(gwAddr, gwPort); -} - -int getGwId(const QCommandLineParser& parser, const QString& gwAddr) -{ - int gwId = -1; - do { - if (!gwAddr.isEmpty()) { - break; - } - - auto gwIdStr = parser.value(GwIdOpt); - if (gwIdStr.isEmpty()) { - break; - } - - bool ok = false; - auto gwIdTmp = gwIdStr.toInt(&ok); - if ((!ok) || (gwIdTmp < 0) || (0xffff < gwIdTmp)) { - break; - } - - gwId = gwIdTmp; - } while (false); - return gwId; -} - -QString getClientId(const QCommandLineParser& parser) -{ - QString clientId = parser.value(ClientIdOpt); - if (!clientId.isEmpty()) { - return clientId; - } - - if (parser.isSet(EmptyClientIdOpt)) { - return clientId; - } - - clientId = "mqttsn_pub_"; - std::random_device rd; - std::mt19937 gen(rd()); - static const QString Map = - "abcdefghijklmnopqrstuvwxyz" - "0123456789" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - std::uniform_int_distribution<> dis(0, Map.size() - 1); - - static const unsigned GenCount = 10; - for (auto c = 0U; c < GenCount; ++c) { - clientId.append(Map[dis(gen)]); - } - return clientId; -} - -unsigned short getLocalPort(const QCommandLineParser& parser, const QString& gwAddr) -{ - unsigned short port = DefaultPort; - do { - if (!gwAddr.isEmpty()) { - port = 0; - break; - } - - auto portStr = parser.value(PortOpt); - if (portStr.isEmpty()) { - break; - } - - bool ok = false; - auto portTmp = portStr.toUInt(&ok); - if ((!ok) || (0xffff < portTmp)) { - break; - } - - port = static_cast(portTmp); - } while (false); - return port; -} - -unsigned short getKeepAlive(const QCommandLineParser& parser) -{ - auto keepAlive = DefaultKeepAlivePeriod; - do { - auto keepAliveStr = parser.value(KeepAliveOpt); - if (keepAliveStr.isEmpty()) { - break; - } - - bool ok = false; - auto keepAliveTmp = keepAliveStr.toUInt(&ok); - if ((!ok) || (keepAliveTmp == 0) || (0xffff < keepAliveTmp)) { - break; - } - - keepAlive = static_cast(keepAliveTmp); - } while (false); - return keepAlive; -} - -std::uint16_t getTopicId(const QCommandLineParser& parser) -{ - std::uint16_t topicId = 0; - do { - auto topicIdStr = parser.value(TopicIdOpt); - if (topicIdStr.isEmpty()) { - break; - } - - bool ok = false; - auto topicIdTmp = topicIdStr.toUInt(&ok); - if ((!ok) || (topicIdTmp == 0) || (0xffff <= topicIdTmp)) { - break; - } - - topicId = static_cast(topicIdTmp); - } while (false); - return topicId; -} - -CC_MqttsnQoS getQos(const QCommandLineParser& parser) -{ - auto value = DefaultQos; - do { - auto qosStr = parser.value(QosOpt); - if (qosStr.isEmpty()) { - break; - } - - bool ok = false; - auto valueTmp = qosStr.toInt(&ok); - if (!ok) { - break; - } - - value = std::max(-1, std::min(2, valueTmp)); - } while (false); - return static_cast(value); -} - -std::vector getMessage(const QCommandLineParser& parser) -{ - auto msgStr = parser.value(MessageOpt); - std::vector result; - result.reserve(msgStr.size()); - int idx = 0; - while (idx < msgStr.size()) { - int remSize = msgStr.size() - idx; - if ((2 <= remSize) && - (msgStr[idx] == '\\') && - (msgStr[idx + 1] == '\\')) { - result.push_back(static_cast('\\')); - idx += 2; - continue; - } - - if ((4 <= remSize) && - (msgStr[idx] == '\\') && - (msgStr[idx + 1] == 'x')) { - auto hexNumStr = msgStr.mid(idx + 2, 2); - bool ok = false; - auto byteVal = msgStr.toUInt(&ok, 16); - if (!ok) { - result.push_back(static_cast(msgStr[idx].toLatin1())); - ++idx; - continue; - } - - result.push_back(static_cast(byteVal)); - idx += 4; - continue; - } - - result.push_back(static_cast(msgStr[idx].toLatin1())); - ++idx; - } - - return result; -} - -} // namespace - -int main(int argc, char *argv[]) -{ - QCoreApplication app(argc, argv); - QCommandLineParser parser; - prepareCommandLineOptions(parser); - parser.process(app); - - QString gwAddr; - unsigned short gwPort = 0; - std::tie(gwAddr, gwPort) = splitGwAddr(parser); - int gwId = getGwId(parser, gwAddr); - unsigned short port = getLocalPort(parser, gwAddr); - auto keepAlive = getKeepAlive(parser); - - cc_mqttsn_client::app::sub::udp::Pub pub; - - pub.setGwAddr(gwAddr); - pub.setGwPort(gwPort); - pub.setGwId(gwId); - pub.setLocalPort(port); - pub.setClientId(getClientId(parser).toStdString()); - pub.setKeepAlive(keepAlive); - pub.setCleanSession(!parser.isSet(NoCleanOpt)); - pub.setTopic(parser.value(TopicOpt).toStdString()); - pub.setTopicId(getTopicId(parser)); - pub.setQos(getQos(parser)); - pub.setRetain(parser.isSet(RetainOpt)); - pub.setMessage(getMessage(parser)); - pub.setDebug(parser.isSet(DebugOpt)); - - if (!pub.start()) { - std::cerr << "ERROR: Failed to start" << std::endl; - return -1; - } - - return app.exec(); -} - - - diff --git a/client.old/src/app/sub/CMakeLists.txt b/client.old/src/app/sub/CMakeLists.txt deleted file mode 100644 index 963576e4..00000000 --- a/client.old/src/app/sub/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_subdirectory (udp) \ No newline at end of file diff --git a/client.old/src/app/sub/udp/CMakeLists.txt b/client.old/src/app/sub/udp/CMakeLists.txt deleted file mode 100644 index d9ee6fd9..00000000 --- a/client.old/src/app/sub/udp/CMakeLists.txt +++ /dev/null @@ -1,44 +0,0 @@ -function (bin_sub_udp) - set (name "cc_mqttsn_sub_udp") - - if (NOT Qt5Core_FOUND) - message(WARNING "Can NOT build ${name} due to missing Qt5Core library") - return() - endif () - - if (NOT Qt5Network_FOUND) - message(WARNING "Can NOT build ${name} due to missing Qt5Network library") - return() - endif () - - - set (src - main.cpp - Sub.cpp - ) - - qt5_wrap_cpp( - moc - Sub.h - ) - - add_executable(${name} ${src} ${moc}) - target_link_libraries(${name} PRIVATE cc::${MQTTSN_CLIENT_LIB_NAME} Qt5::Network Qt5::Core) - - install ( - TARGETS ${name} - DESTINATION ${CMAKE_INSTALL_BINDIR}) - -endfunction () - -########################################################### - -find_package(Qt5Core) -find_package(Qt5Network) - -include_directories ( - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_BINARY_DIR}/client/src/basic -) - -bin_sub_udp() diff --git a/client.old/src/app/sub/udp/Sub.cpp b/client.old/src/app/sub/udp/Sub.cpp deleted file mode 100644 index 394e99e7..00000000 --- a/client.old/src/app/sub/udp/Sub.cpp +++ /dev/null @@ -1,487 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include "Sub.h" - -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace cc_mqttsn_client -{ - -namespace app -{ - -namespace sub -{ - -namespace udp -{ - -namespace -{ - -typedef std::vector DataBuf; - -} // namespace - -Sub::Sub() - : m_client(cc_mqttsn_client_new()) -{ - cc_mqttsn_client_set_next_tick_program_callback( - m_client, &Sub::nextTickProgramCb, this); - - cc_mqttsn_client_set_cancel_next_tick_wait_callback( - m_client, &Sub::caneclTickCb, this); - - cc_mqttsn_client_set_send_output_data_callback( - m_client, &Sub::sendDataCb, this); - - cc_mqttsn_client_set_gw_status_report_callback( - m_client, &Sub::gwStatusReportCb, this); - - cc_mqttsn_client_set_gw_disconnect_report_callback( - m_client, &Sub::gwDisconnectReportCb, this); - - cc_mqttsn_client_set_message_report_callback( - m_client, &Sub::messageReportCb, this); - - - connect( - &m_timer, SIGNAL(timeout()), - this, SLOT(tick())); - - connect( - &m_socket, SIGNAL(readyRead()), - this, SLOT(readFromSocket())); - - connect( - &m_socket, SIGNAL(error(QAbstractSocket::SocketError)), - this, SLOT(socketErrorOccurred(QAbstractSocket::SocketError))); - -} - -Sub::~Sub() -{ - cc_mqttsn_client_free(m_client); -} - -bool Sub::start() -{ - bool result = - bindLocalPort() && - openSocket() && - connectToGw() && - cc_mqttsn_client_start(m_client) == CC_MqttsnErrorCode_Success; - - - if (result && (m_socket.state() == QUdpSocket::ConnectedState)) { - doConnect(); - } - return result; -} - -void Sub::tick() -{ - m_reqTimeout = 0; - cc_mqttsn_client_tick(m_client); -} - -void Sub::readFromSocket() -{ - DataBuf data; - - while (m_socket.hasPendingDatagrams()) { - data.resize(m_socket.pendingDatagramSize()); - auto readBytes = m_socket.readDatagram( - reinterpret_cast(&data[0]), - data.size(), - &m_lastSenderAddress, - &m_lastSenderPort); - assert(readBytes == static_cast(data.size())); - static_cast(readBytes); - -// std::cout << "--> " << std::hex; -// std::copy_n(&data[0], data.size(), std::ostream_iterator(std::cout, " ")); -// std::cout << std::dec << std::endl; - - cc_mqttsn_client_process_data(m_client, &data[0], static_cast(data.size())); - } -} - -void Sub::socketErrorOccurred(QAbstractSocket::SocketError err) -{ - static_cast(err); - std::cerr << "ERROR: UDP Socket: " << m_socket.errorString().toStdString() << std::endl; -} - - -void Sub::nextTickProgram(unsigned ms) -{ -// std::cout << "Tick req: " << ms << std::endl; - m_reqTimeout = ms; - m_timer.setSingleShot(true); - m_timer.start(ms); -} - -void Sub::nextTickProgramCb(void* obj, unsigned ms) -{ - assert(obj != nullptr); - reinterpret_cast(obj)->nextTickProgram(ms); -} - -unsigned Sub::cancelTick() -{ - auto rem = m_timer.remainingTime(); - m_timer.stop(); - - if (m_reqTimeout < static_cast(rem)) { - rem = static_cast(m_reqTimeout); - } - - return m_reqTimeout - static_cast(rem); -} - -unsigned Sub::caneclTickCb(void* obj) -{ - assert(obj != nullptr); - return reinterpret_cast(obj)->cancelTick(); -} - -void Sub::sendData(const unsigned char* buf, unsigned bufLen, bool broadcast) -{ -// std::cout << "<-- " << std::hex; -// std::copy_n(buf, bufLen, std::ostream_iterator(std::cout, " ")); -// std::cout << std::dec << std::endl; - - if (broadcast) { - broadcastData(buf, bufLen); - return; - } - - if (m_socket.state() != QUdpSocket::ConnectedState) { - return; - } - - sendDataConnected(buf, bufLen); -} - -void Sub::sendDataCb(void* obj, const unsigned char* buf, unsigned bufLen, bool broadcast) -{ - assert(obj != nullptr); - reinterpret_cast(obj)->sendData(buf, bufLen, broadcast); -} - -void Sub::gwStatusReport(unsigned short gwId, CC_MqttsnGwStatus status) -{ - if (status != CC_MqttsnGwStatus_Available) { - return; - } - - if ((0 <= m_gwId) && (gwId != m_gwId)) { - return; - } - - if (m_socket.state() != QUdpSocket::ConnectedState) { - m_socket.connectToHost(m_lastSenderAddress, m_lastSenderPort); - if (!m_socket.waitForConnected(2000)) { - std::cerr << "ERROR: Failed to connect UDP socket" << std::endl; - return; - } - - assert(m_socket.isOpen()); - assert(m_socket.state() == QUdpSocket::ConnectedState); - } - - doConnect(); -} - -void Sub::gwStatusReportCb(void* obj, unsigned char gwId, CC_MqttsnGwStatus status) -{ - assert(obj != nullptr); - reinterpret_cast(obj)->gwStatusReport(gwId, status); -} - -void Sub::gwDisconnectReport() -{ - std::cerr << "WARNING: Disconnected from GW, reconnecting..." << std::endl; - doConnect(true); -} - -void Sub::gwDisconnectReportCb(void* obj) -{ - assert(obj != nullptr); - reinterpret_cast(obj)->gwDisconnectReport(); -} - -void Sub::messageReport(const CC_MqttsnMessageInfo* msgInfo) -{ - assert(msgInfo != nullptr); - if (msgInfo->retain && m_noRetain) { - return; - } - - if (m_verbose) { - if (msgInfo->topic != nullptr) { - std::cout << msgInfo->topic; - } - else { - std::cout << msgInfo->topicId; - } - std::cout << " (qos=" << static_cast(msgInfo->qos); - if (msgInfo->retain) { - std::cout << " retained"; - } - std::cout << ") : "; - } - - if (!m_hexOutput) { - std::copy_n(msgInfo->msg, msgInfo->msgLen, std::ostream_iterator(std::cout)); - std::cout << std::endl; - return; - } - - std::cout << std::hex; - for (auto idx = 0U; idx < msgInfo->msgLen; ++idx) { - if (0U < idx) { - std::cout << ' '; - } - std::cout << std::setw(2) << std::setfill('0') << static_cast(msgInfo->msg[idx]); - } - std::cout << std::dec << std::endl; -} - -void Sub::messageReportCb(void* obj, const CC_MqttsnMessageInfo* msgInfo) -{ - assert(obj != nullptr); - reinterpret_cast(obj)->messageReport(msgInfo); -} - -void Sub::doConnect(bool reconnecting) -{ - bool cleanSession = m_cleanSession; - if (reconnecting) { - cleanSession = false; - } - - auto result = - cc_mqttsn_client_connect( - m_client, - m_clientId.c_str(), - m_keepAlive, - cleanSession, - nullptr, - &Sub::connectCompleteCb, - this); - if (result != CC_MqttsnErrorCode_Success) { - std::cerr << "ERROR: Failed to connect to the gateway" << std::endl; - } -} - -void Sub::doSubscribe() -{ - if (!m_topics.empty()) { - auto result = - cc_mqttsn_client_subscribe( - m_client, - m_topics.front().c_str(), - m_qos, - &Sub::subscribeCompleteCb, - this); - if (result != CC_MqttsnErrorCode_Success) { - std::cerr << "ERROR: Failed to initiate subscribe for topic " << m_topics.front() << std::endl; - m_topics.pop_front(); - doSubscribe(); - } - return; - } - - if (!m_topicIds.empty()) { - auto result = - cc_mqttsn_client_subscribe_id( - m_client, - m_topicIds.front(), - m_qos, - &Sub::subscribeCompleteCb, - this); - - if (result != CC_MqttsnErrorCode_Success) { - std::cerr << "ERROR: Failed to initiate subscribe for topic ID " << m_topicIds.front() << std::endl; - m_topicIds.pop_front(); - doSubscribe(); - } - return; - } -} - -void Sub::connectComplete(CC_MqttsnAsyncOpStatus status) -{ - if (status == CC_MqttsnAsyncOpStatus_Successful) { - doSubscribe(); - return; - } - - if (status == CC_MqttsnAsyncOpStatus_Congestion) { - std::cerr << "WARNING: Congestion reported, reconnecting..." << std::endl; - doConnect(); - return; - } - - std::cerr << "ERROR: Failed to connect..." << std::endl; - QTimer::singleShot(10, qApp, SLOT(quit())); -} - -void Sub::connectCompleteCb(void* obj, CC_MqttsnAsyncOpStatus status) -{ - assert(obj != nullptr); - reinterpret_cast(obj)->connectComplete(status); -} - - -void Sub::subscribeComplete(CC_MqttsnAsyncOpStatus status) -{ - if (status == CC_MqttsnAsyncOpStatus_Congestion) { - std::cerr << "WARNING: Failed to subscribe due to congestion, retrying..." << std::endl; - doSubscribe(); - return; - } - - if (status != CC_MqttsnAsyncOpStatus_Successful) { - std::cerr << "WARNING: Failed to subscribe to topic "; - if (!m_topics.empty()) { - std::cerr << m_topics.front(); - } - else if (!m_topicIds.empty()) { - std::cerr << "ID " << m_topicIds.front(); - } - std::cerr << std::endl; - } - - if (!m_topics.empty()) { - m_topics.pop_front(); - } - else if (!m_topicIds.empty()) { - m_topicIds.pop_front(); - } - - doSubscribe(); -} - -void Sub::subscribeCompleteCb(void* obj, CC_MqttsnAsyncOpStatus status, CC_MqttsnQoS qos) -{ - static_cast(qos); - assert(obj != nullptr); - reinterpret_cast(obj)->subscribeComplete(status); -} - -bool Sub::bindLocalPort() -{ - if (m_localPort == 0) { - return true; - } - - bool result = - m_socket.bind(QHostAddress::AnyIPv4, m_localPort, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint); - if (!result) { - std::cerr << "ERROR: Failed to bind UDP socket to local port " << m_localPort << std::endl; - } - return result; -} - -bool Sub::openSocket() -{ - if (m_socket.isOpen()) { - return true; - } - - bool result = m_socket.open(QUdpSocket::ReadWrite); - if (!result) { - std::cerr << "ERROR: Failed to open UDP socket" << std::endl; - } - return result; -} - -bool Sub::connectToGw() -{ - if ((m_gwAddr.isEmpty()) || (m_gwPort == 0)) { - return true; - } - - m_socket.connectToHost(m_gwAddr, m_gwPort); - if (!m_socket.waitForConnected(2000)) { - std::cerr << "ERROR: Failed to connect UDP socket" << std::endl; - return false; - } - - assert(m_socket.isOpen()); - assert(m_socket.state() == QUdpSocket::ConnectedState); - - cc_mqttsn_client_set_searchgw_enabled(m_client, false); - return true; -} - -void Sub::broadcastData(const unsigned char* buf, unsigned bufLen) -{ - if (m_gwPort == 0) { - return; - } - - std::size_t writtenCount = 0; - while (writtenCount < bufLen) { - auto remSize = bufLen - writtenCount; - auto count = - m_socket.writeDatagram( - reinterpret_cast(&buf[writtenCount]), - remSize, - QHostAddress::Broadcast, - m_gwPort); - - if (count < 0) { - std::cerr << "ERROR: Failed to broadcast data" << std::endl; - return; - } - - writtenCount += count; - } - -} - -void Sub::sendDataConnected(const unsigned char* buf, unsigned bufLen) -{ - std::size_t writtenCount = 0; - while (writtenCount < bufLen) { - auto remSize = bufLen - writtenCount; - auto count = - m_socket.write( - reinterpret_cast(&buf[writtenCount]), - remSize); - if (count < 0) { - std::cerr << "ERROR: Failed to write to UDP socket" << std::endl; - return; - } - - writtenCount += count; - } -} - - -} // namespace udp - -} // namespace sub - -} // namespace app - -} // namespace cc_mqttsn_client - - diff --git a/client.old/src/app/sub/udp/Sub.h b/client.old/src/app/sub/udp/Sub.h deleted file mode 100644 index 5c6df64a..00000000 --- a/client.old/src/app/sub/udp/Sub.h +++ /dev/null @@ -1,176 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - - -#pragma once - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "client.h" - -namespace cc_mqttsn_client -{ - -namespace app -{ - -namespace sub -{ - -namespace udp -{ - -class Sub : public QObject -{ - Q_OBJECT -public: - Sub(); - ~Sub(); - - void setGwAddr(const QString& value) - { - m_gwAddr = value; - } - - void setGwPort(unsigned short value) - { - m_gwPort = value; - } - - void setGwId(int value) - { - m_gwId = value; - } - - void setLocalPort(unsigned short value) - { - m_localPort = value; - } - - void setClientId(const std::string& value) - { - m_clientId = value; - } - - void setKeepAlive(unsigned short value) - { - m_keepAlive = value; - } - - void setCleanSession(bool value) - { - m_cleanSession = value; - } - - typedef std::list TopicsList; - void setTopics(const TopicsList& topics) - { - m_topics = topics; - } - - typedef std::list TopicIdsList; - void setTopicIds(const TopicIdsList& topicIds) - { - m_topicIds = topicIds; - } - - void setQos(CC_MqttsnQoS value) - { - m_qos = value; - } - - void setVerbose(bool value) - { - m_verbose = value; - } - - void setNoRetain(bool value) - { - m_noRetain = value; - } - - void setHexOutput(bool value) - { - m_hexOutput = value; - } - - bool start(); - -private slots: - void tick(); - void readFromSocket(); - void socketErrorOccurred(QAbstractSocket::SocketError err); - -private: - void nextTickProgram(unsigned ms); - static void nextTickProgramCb(void* obj, unsigned ms); - - unsigned cancelTick(); - static unsigned caneclTickCb(void* obj); - - void sendData(const unsigned char* buf, unsigned bufLen, bool broadcast); - static void sendDataCb(void* obj, const unsigned char* buf, unsigned bufLen, bool broadcast); - - void gwStatusReport(unsigned short gwId, CC_MqttsnGwStatus status); - static void gwStatusReportCb(void* obj, unsigned char gwId, CC_MqttsnGwStatus status); - - void gwDisconnectReport(); - static void gwDisconnectReportCb(void* obj); - - void messageReport(const CC_MqttsnMessageInfo* msgInfo); - static void messageReportCb(void* obj, const CC_MqttsnMessageInfo* msgInfo); - - void doConnect(bool reconnecting = false); - void doSubscribe(); - void connectComplete(CC_MqttsnAsyncOpStatus status); - static void connectCompleteCb(void* obj, CC_MqttsnAsyncOpStatus status); - void subscribeComplete(CC_MqttsnAsyncOpStatus status); - static void subscribeCompleteCb(void* obj, CC_MqttsnAsyncOpStatus status, CC_MqttsnQoS qos); - bool bindLocalPort(); - bool openSocket(); - bool connectToGw(); - void broadcastData(const unsigned char* buf, unsigned bufLen); - void sendDataConnected(const unsigned char* buf, unsigned bufLen); - - CC_MqttsnClientHandle m_client; - QTimer m_timer; - unsigned m_reqTimeout = 0; - QString m_gwAddr; - unsigned short m_gwPort = 0; - int m_gwId = -1; - unsigned short m_localPort = 0; - QUdpSocket m_socket; - QHostAddress m_lastSenderAddress; - quint16 m_lastSenderPort; - std::string m_clientId; - unsigned short m_keepAlive = 0; - bool m_cleanSession = true; - bool m_verbose = false; - bool m_noRetain = false; - bool m_hexOutput = false; - TopicsList m_topics; - TopicIdsList m_topicIds; - CC_MqttsnQoS m_qos = CC_MqttsnQoS_ExactlyOnceDelivery; -}; - -} // namespace udp - -} // namespace sub - -} // namespace app - -} // namespace cc_mqttsn_client - - diff --git a/client.old/src/app/sub/udp/main.cpp b/client.old/src/app/sub/udp/main.cpp deleted file mode 100644 index 7b72f7f7..00000000 --- a/client.old/src/app/sub/udp/main.cpp +++ /dev/null @@ -1,384 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "Sub.h" - -namespace -{ - -const QString GwOpt("gateway"); -const QString GwShortOpt("g"); -const QString GwIdOpt("gateway-id"); -const QString GwIdShortOpt("G"); -const QString PortOpt("port"); -const QString PortShortOpt("p"); -const unsigned short DefaultPort = 1883; -const QString DefaultPortStr = QString("%1").arg(DefaultPort); -const QString ClientIdOpt("client-id"); -const QString ClientIdShortOpt("i"); -const QString EmptyClientIdOpt("empty-client-id"); -const QString EmptyClientIdShortOpt("I"); -const QString KeepAliveOpt("keep-alive"); -const QString KeepAliveShortOpt("k"); -const unsigned short DefaultKeepAlivePeriod = 60; -const QString DefaultKeepAlivePeriodStr = QString("%1").arg(DefaultKeepAlivePeriod); -const QString NoCleanOpt("no-clean"); -const QString NoCleanShortOpt("c"); -const QString TopicOpt("topic"); -const QString TopicShortOpt("t"); -const QString TopicIdOpt("topic-id"); -const QString TopicIdShortOpt("T"); -const QString QosOpt("qos"); -const QString QosShortOpt("q"); -const QString VerboseOpt("verbose"); -const QString VerboseShortOpt("v"); -const QString NoRetainOpt("no-retained"); -const QString NoRetainShortOpt("R"); -const QString HexOpt("hex-output"); -const QString HexShortOpt("x"); - - -const int DefaultQos = 2; -const QString DefaultQosStr = QString("%1").arg(DefaultQos); - -void prepareCommandLineOptions(QCommandLineParser& parser) -{ - parser.addHelpOption(); - - QCommandLineOption gwOpt( - QStringList() << GwShortOpt << GwOpt, - "Gateway address. Expected to be in
: format. " - "Port is optional, if not provided defaults to " + DefaultPortStr + - ". If this option is used, no SEARCHGW messages are sent to the gateway, and -" + - GwIdShortOpt + " option is ignored." , - QCoreApplication::translate("main", "value") - ); - parser.addOption(gwOpt); - - QCommandLineOption gwIdOpt( - QStringList() << GwIdShortOpt << GwIdOpt, - QCoreApplication::translate("main", - "Gateway ID to connect to when discovered. " - "If not provided, the first gateway, that responds to SEARCHGW message, " - "will be chosen."), - QCoreApplication::translate("main", "value") - ); - parser.addOption(gwIdOpt); - - QCommandLineOption portOpt( - QStringList() << PortShortOpt << PortOpt, - "Local network port. Defaults to " + DefaultPortStr + " in case -" + GwShortOpt + " option is NOT used.", - QCoreApplication::translate("main", "value") - ); - parser.addOption(portOpt); - - QCommandLineOption idOpt( - QStringList() << ClientIdShortOpt << ClientIdOpt, - "Client ID. If not provided, the client ID is randomised. If provided suppresses usage of -" + EmptyClientIdShortOpt + " option.", - QCoreApplication::translate("main", "value") - ); - parser.addOption(idOpt); - - QCommandLineOption emptyIdOpt( - QStringList() << EmptyClientIdShortOpt << EmptyClientIdOpt, - "Use empty client ID." - ); - parser.addOption(emptyIdOpt); - - QCommandLineOption keepAliveOpt( - QStringList() << KeepAliveShortOpt << KeepAliveOpt, - "Keep alive period in seconds. Defaults to " + DefaultKeepAlivePeriodStr + '.', - QCoreApplication::translate("main", "value") - ); - parser.addOption(keepAliveOpt); - - QCommandLineOption noCleanOpt( - QStringList() << NoCleanShortOpt << NoCleanOpt, - "Connect to GW with 'clean session' bit cleared, i.e. preserve previous subscriptions." - ); - parser.addOption(noCleanOpt); - - QCommandLineOption topicOpt( - QStringList() << TopicShortOpt << TopicOpt, - "Topic to subscribe to, may be repeated multiple times", - QCoreApplication::translate("main", "value") - ); - parser.addOption(topicOpt); - - QCommandLineOption topicIdOpt( - QStringList() << TopicIdShortOpt << TopicIdOpt, - "Predefined topic ID to subscribe to, may be repeated multiple times", - QCoreApplication::translate("main", "value") - ); - parser.addOption(topicIdOpt); - - QCommandLineOption qosOpt( - QStringList() << QosShortOpt << QosOpt, - "Max quality of service to use for subscription. Defaults to " + DefaultQosStr + '.', - QCoreApplication::translate("main", "value") - ); - parser.addOption(qosOpt); - - QCommandLineOption verboseOpt( - QStringList() << VerboseShortOpt << VerboseOpt, - "Verbose print of published messages." - ); - parser.addOption(verboseOpt); - - QCommandLineOption noRetainOpt( - QStringList() << NoRetainShortOpt << NoRetainOpt, - "Do NOT print retained messages." - ); - parser.addOption(noRetainOpt); - - QCommandLineOption hexOpt( - QStringList() << HexShortOpt << HexOpt, - "Print message body in hexadecimal byte values." - ); - parser.addOption(hexOpt); -} - -std::tuple splitGwAddr(const QCommandLineParser& parser) -{ - auto gwUrl = parser.value(GwOpt); - auto gwAddr = gwUrl; - auto gwPort = DefaultPort; - auto colonIdx = gwUrl.indexOf(QChar(':')); - if (0 < colonIdx) { - gwAddr = gwUrl.left(colonIdx); - auto gwPortStr = gwUrl.right(gwUrl.size() - (colonIdx + 1)); - bool ok = false; - auto gwPortTmp = gwPortStr.toUInt(&ok); - if (ok && (0 < gwPortTmp) && (gwPortTmp <= std::numeric_limits::max())) { - gwPort = static_cast(gwPortTmp); - } - } - - return std::make_tuple(gwAddr, gwPort); -} - -int getGwId(const QCommandLineParser& parser, const QString& gwAddr) -{ - int gwId = -1; - do { - if (!gwAddr.isEmpty()) { - break; - } - - auto gwIdStr = parser.value(GwIdOpt); - if (gwIdStr.isEmpty()) { - break; - } - - bool ok = false; - auto gwIdTmp = gwIdStr.toInt(&ok); - if ((!ok) || (gwIdTmp < 0) || (0xffff < gwIdTmp)) { - break; - } - - gwId = gwIdTmp; - } while (false); - return gwId; -} - -QString getClientId(const QCommandLineParser& parser) -{ - QString clientId = parser.value(ClientIdOpt); - if (!clientId.isEmpty()) { - return clientId; - } - - if (parser.isSet(EmptyClientIdOpt)) { - return clientId; - } - - clientId = "mqttsn_sub_"; - std::random_device rd; - std::mt19937 gen(rd()); - static const QString Map = - "abcdefghijklmnopqrstuvwxyz" - "0123456789" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - std::uniform_int_distribution<> dis(0, Map.size() - 1); - - static const unsigned GenCount = 10; - for (auto c = 0U; c < GenCount; ++c) { - clientId.append(Map[dis(gen)]); - } - return clientId; -} - -unsigned short getLocalPort(const QCommandLineParser& parser, const QString& gwAddr) -{ - unsigned short port = DefaultPort; - do { - if (!gwAddr.isEmpty()) { - port = 0; - break; - } - - auto portStr = parser.value(PortOpt); - if (portStr.isEmpty()) { - break; - } - - bool ok = false; - auto portTmp = portStr.toUInt(&ok); - if ((!ok) || (0xffff < portTmp)) { - break; - } - - port = static_cast(portTmp); - } while (false); - return port; -} - -unsigned short getKeepAlive(const QCommandLineParser& parser) -{ - auto keepAlive = DefaultKeepAlivePeriod; - do { - auto keepAliveStr = parser.value(KeepAliveOpt); - if (keepAliveStr.isEmpty()) { - break; - } - - bool ok = false; - auto keepAliveTmp = keepAliveStr.toUInt(&ok); - if ((!ok) || (keepAliveTmp == 0) || (0xffff < keepAliveTmp)) { - break; - } - - keepAlive = static_cast(keepAliveTmp); - } while (false); - return keepAlive; -} - -cc_mqttsn_client::app::sub::udp::Sub::TopicsList getTopics(const QCommandLineParser& parser) -{ - auto topicStrings = parser.values(TopicOpt); - std::vector topics; - topics.reserve(topicStrings.size()); - std::transform( - topicStrings.begin(), topicStrings.end(), std::back_inserter(topics), - [](const QString& s) -> std::string - { - return s.toStdString(); - }); - - std::sort(topics.begin(), topics.end()); - topics.erase( - std::unique(topics.begin(), topics.end()), - topics.end()); - return cc_mqttsn_client::app::sub::udp::Sub::TopicsList(topics.begin(), topics.end()); -} - -cc_mqttsn_client::app::sub::udp::Sub::TopicIdsList getTopicIds(const QCommandLineParser& parser) -{ - auto topicStrings = parser.values(TopicIdOpt); - std::vector topics; - topics.reserve(topicStrings.size()); - std::transform( - topicStrings.begin(), topicStrings.end(), std::back_inserter(topics), - [](const QString& s) -> std::uint16_t - { - return static_cast(s.toUInt()); - }); - - topics.erase( - std::remove_if( - topics.begin(), topics.end(), - [](std::uint16_t value) -> bool - { - return value == 0; - }), - topics.end()); - - std::sort(topics.begin(), topics.end()); - topics.erase( - std::unique(topics.begin(), topics.end()), - topics.end()); - - return cc_mqttsn_client::app::sub::udp::Sub::TopicIdsList(topics.begin(), topics.end()); -} - -CC_MqttsnQoS getQos(const QCommandLineParser& parser) -{ - auto value = DefaultQos; - do { - auto qosStr = parser.value(QosOpt); - if (qosStr.isEmpty()) { - break; - } - - bool ok = false; - auto valueTmp = qosStr.toInt(&ok); - if ((!ok) || (valueTmp < 0)) { - break; - } - - value = std::min(value, valueTmp); - } while (false); - return static_cast(value); -} - - -} // namespace - -int main(int argc, char *argv[]) -{ - QCoreApplication app(argc, argv); - QCommandLineParser parser; - prepareCommandLineOptions(parser); - parser.process(app); - - QString gwAddr; - unsigned short gwPort = 0; - std::tie(gwAddr, gwPort) = splitGwAddr(parser); - int gwId = getGwId(parser, gwAddr); - unsigned short port = getLocalPort(parser, gwAddr); - auto keepAlive = getKeepAlive(parser); - - cc_mqttsn_client::app::sub::udp::Sub sub; - - sub.setGwAddr(gwAddr); - sub.setGwPort(gwPort); - sub.setGwId(gwId); - sub.setLocalPort(port); - sub.setClientId(getClientId(parser).toStdString()); - sub.setKeepAlive(keepAlive); - sub.setCleanSession(!parser.isSet(NoCleanOpt)); - sub.setTopics(getTopics(parser)); - sub.setTopicIds(getTopicIds(parser)); - sub.setQos(getQos(parser)); - sub.setVerbose(parser.isSet(VerboseOpt)); - sub.setNoRetain(parser.isSet(NoRetainOpt)); - sub.setHexOutput(parser.isSet(HexOpt)); - - if (!sub.start()) { - std::cerr << "ERROR: Failed to start" << std::endl; - return -1; - } - - return app.exec(); -} - - - diff --git a/client.old/src/basic/BasicClient.h b/client.old/src/basic/BasicClient.h deleted file mode 100644 index b471b4bf..00000000 --- a/client.old/src/basic/BasicClient.h +++ /dev/null @@ -1,2983 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "comms/comms.h" -#include "comms/util/ScopeGuard.h" -#include "cc_mqttsn/Version.h" -#include "cc_mqttsn/Message.h" -#include "cc_mqttsn/frame/Frame.h" -#include "cc_mqttsn/input/ProtClientInputMessages.h" -#include "cc_mqttsn/options/ClientDefaultOptions.h" -#include "cc_mqttsn_client/common.h" -#include "details/WriteBufStorageType.h" - -static_assert(COMMS_MAKE_VERSION(2, 7, 1) <= cc_mqttsn::version(), - "The version of cc.mqttsn.generated library is too old"); - -namespace cc_mqttsn_client -{ - -namespace details -{ - -template -struct GwInfoStorageType; - -template -struct GwInfoStorageType -{ - typedef comms::util::StaticVector Type; -}; - -template -struct GwInfoStorageType -{ - typedef std::vector Type; -}; - -template -using GwInfoStorageTypeT = - typename GwInfoStorageType::Type; - -//----------------------------------------------------------- - -template -struct RegInfoStorageType; - -template -struct RegInfoStorageType -{ - typedef comms::util::StaticVector Type; -}; - -template -struct RegInfoStorageType -{ - typedef std::vector Type; -}; - -template -using RegInfoStorageTypeT = - typename RegInfoStorageType::Type; - -//----------------------------------------------------------- - -template -struct TopicNameStorageOpt; - -template -struct TopicNameStorageOpt -{ - typedef comms::option::FixedSizeStorage Type; -}; - -template -struct TopicNameStorageOpt -{ - typedef comms::option::EmptyOption Type; -}; - -template -using TopicNameStorageOptT = - typename TopicNameStorageOpt::Type; - -//----------------------------------------------------------- - -template -struct DataStorageOpt; - -template -struct DataStorageOpt -{ - typedef comms::option::FixedSizeStorage Type; -}; - -template -struct DataStorageOpt -{ - typedef comms::option::EmptyOption Type; -}; - -template -using DataStorageOptT = - typename DataStorageOpt::Type; - -//----------------------------------------------------------- - -template -struct ClientIdStorageOpt; - -template -struct ClientIdStorageOpt -{ - typedef comms::option::FixedSizeStorage Type; -}; - -template -struct ClientIdStorageOpt -{ - typedef comms::option::EmptyOption Type; -}; - -template -using ClientIdStorageOptT = - typename ClientIdStorageOpt::Type; - -//----------------------------------------------------------- - -cc_mqttsn::field::QosVal translateQosValue(CC_MqttsnQoS val) -{ - static_assert( - static_cast(cc_mqttsn::field::QosVal::AtMostOnceDelivery) == CC_MqttsnQoS_AtMostOnceDelivery, - "Invalid mapping"); - - static_assert( - static_cast(cc_mqttsn::field::QosVal::AtLeastOnceDelivery) == CC_MqttsnQoS_AtLeastOnceDelivery, - "Invalid mapping"); - - static_assert( - static_cast(cc_mqttsn::field::QosVal::ExactlyOnceDelivery) == CC_MqttsnQoS_ExactlyOnceDelivery, - "Invalid mapping"); - - if (val == CC_MqttsnQoS_NoGwPublish) { - return cc_mqttsn::field::QosVal::NoGwPublish; - } - - return static_cast(val); -} - -CC_MqttsnQoS translateQosValue(cc_mqttsn::field::QosVal val) -{ - - if (val == cc_mqttsn::field::QosVal::NoGwPublish) { - return CC_MqttsnQoS_NoGwPublish; - } - - return static_cast(val); -} - -} // namespace details - -template -class BasicClient -{ - typedef details::WriteBufStorageTypeT WriteBufStorage; - - typedef cc_mqttsn::Message< - comms::option::IdInfoInterface, - comms::option::ReadIterator, - comms::option::WriteIterator, - comms::option::Handler >, - comms::option::LengthInfoInterface - > Message; - - typedef unsigned long long Timestamp; - - enum class Op - { - None, - Connect, - Disconnect, - PublishId, - Publish, - SubscribeId, - Subscribe, - UnsubscribeId, - Unsubscribe, - WillTopicUpdate, - WillMsgUpdate, - Sleep, - CheckMessages, - NumOfValues // must be last - }; - - struct OpBase - { - Timestamp m_lastMsgTimestamp = 0; - unsigned m_attempt = 0; - }; - - struct AsyncOpBase : public OpBase - { - CC_MqttsnAsyncOpCompleteReportFn m_cb = nullptr; - void* m_cbData = nullptr; - }; - - - struct ConnectOp : public AsyncOpBase - { - CC_MqttsnWillInfo m_willInfo = CC_MqttsnWillInfo(); - const char* m_clientId = nullptr; - std::uint16_t m_keepAlivePeriod = 0; - bool m_hasWill = false; - bool m_cleanSession = false; - bool m_willTopicSent = false; - bool m_willMsgSent = false; - }; - - typedef AsyncOpBase DisconnectOp; - - struct PublishOpBase : public AsyncOpBase - { - CC_MqttsnTopicId m_topicId = 0U; - std::uint16_t m_msgId = 0U; - const std::uint8_t* m_msg = nullptr; - std::size_t m_msgLen = 0; - CC_MqttsnQoS m_qos = CC_MqttsnQoS_AtMostOnceDelivery; - bool m_retain = false; - bool m_ackReceived = false; - }; - - struct PublishIdOp : public PublishOpBase - { - }; - - struct PublishOp : public PublishOpBase - { - const char* m_topic = nullptr; - bool m_registered = false; - bool m_didRegistration = false; - bool m_shortName = false; - }; - - struct SubscribeOpBase : public OpBase - { - CC_MqttsnQoS m_qos = CC_MqttsnQoS_AtMostOnceDelivery; - CC_MqttsnSubscribeCompleteReportFn m_cb = nullptr; - void* m_cbData = nullptr; - std::uint16_t m_msgId = 0U; - CC_MqttsnTopicId m_topicId = 0U; - }; - - struct SubscribeIdOp : public SubscribeOpBase - { - }; - - struct SubscribeOp : public SubscribeOpBase - { - const char* m_topic = nullptr; - bool m_usingShortTopicName = false; - }; - - struct UnsubscribeOpBase : public AsyncOpBase - { - std::uint16_t m_msgId = 0U; - CC_MqttsnTopicId m_topicId = 0U; - }; - - struct UnsubscribeIdOp : public UnsubscribeOpBase - { - }; - - struct UnsubscribeOp : public UnsubscribeOpBase - { - const char* m_topic = nullptr; - bool m_usingShortTopicName = false; - }; - - struct WillTopicUpdateOp : public AsyncOpBase - { - const char* m_topic = nullptr; - CC_MqttsnQoS m_qos; - bool m_retain; - }; - - struct WillMsgUpdateOp : public AsyncOpBase - { - const unsigned char* m_msg = nullptr; - unsigned m_msgLen = 0; - }; - - struct SleepOp : public AsyncOpBase - { - std::uint16_t m_duration = 0; - }; - - typedef AsyncOpBase CheckMessagesOp; - - enum class ConnectionStatus - { - Disconnected, - Connected, - Asleep - }; - - typedef void (BasicClient::*FinaliseFunc)(CC_MqttsnAsyncOpStatus); - - using TopicIdTypeVal = cc_mqttsn::field::TopicIdTypeVal; - using ReturnCodeVal = cc_mqttsn::field::ReturnCodeVal; - -// struct NoOrigDataViewProtOpts : TProtOpts -// { -// static const bool HasOrigDataView = false; -// }; - - class ProtOpts : public cc_mqttsn::options::ClientDefaultOptions - { - using Base = cc_mqttsn::options::ClientDefaultOptions; - - public: - - struct field : public Base::field - { - using ClientId = comms::option::app::OrigDataView; - using Data = comms::option::app::OrigDataView; - using GwAdd = comms::option::app::OrigDataView; - using TopicName = comms::option::app::OrigDataView; - using WillMsg = comms::option::app::OrigDataView; - using WillTopic = comms::option::app::OrigDataView; - }; - - struct frame : public Base::frame - { - struct FrameLayers : public Base::frame::FrameLayers - { - using Id = comms::option::app::InPlaceAllocation; - }; // struct FrameLayers - - }; // struct frame - }; - - class StorageOptions : public cc_mqttsn::options::ClientDefaultOptions - { - using Base = cc_mqttsn::options::ClientDefaultOptions; - public: - struct field : public Base::field - { - using ClientId = details::ClientIdStorageOptT; - using Data = details::DataStorageOptT; - using TopicName = details::TopicNameStorageOptT; - }; - - - }; - -public: - typedef typename Message::Field FieldBase; - typedef typename cc_mqttsn::field::GwId::ValueType GwIdValueType; - typedef typename cc_mqttsn::field::TopicName::ValueType TopicNameType; - typedef typename cc_mqttsn::field::Data::ValueType DataType; - typedef typename cc_mqttsn::field::TopicId::ValueType TopicIdType; - typedef typename cc_mqttsn::field::ClientId::ValueType ClientIdType; - - struct GwInfo - { - GwInfo() = default; - - Timestamp m_timestamp = 0; - GwIdValueType m_id = 0; - //GwAddValueType m_addr; - unsigned m_duration = 0; - }; - - typedef details::GwInfoStorageTypeT GwInfoStorage; - - typedef cc_mqttsn::message::Advertise AdvertiseMsg; - typedef cc_mqttsn::message::Searchgw SearchgwMsg; - typedef cc_mqttsn::message::Gwinfo GwinfoMsg; - typedef cc_mqttsn::message::Connect ConnectMsg; - typedef cc_mqttsn::message::Connack ConnackMsg; - typedef cc_mqttsn::message::Willtopicreq WilltopicreqMsg; - typedef cc_mqttsn::message::Willtopic WilltopicMsg; - typedef cc_mqttsn::message::Willmsgreq WillmsgreqMsg; - typedef cc_mqttsn::message::Willmsg WillmsgMsg; - typedef cc_mqttsn::message::Register RegisterMsg; - typedef cc_mqttsn::message::Regack RegackMsg; - typedef cc_mqttsn::message::Publish PublishMsg; - typedef cc_mqttsn::message::Puback PubackMsg; - typedef cc_mqttsn::message::Pubrec PubrecMsg; - typedef cc_mqttsn::message::Pubrel PubrelMsg; - typedef cc_mqttsn::message::Pubcomp PubcompMsg; - typedef cc_mqttsn::message::Subscribe SubscribeMsg; - typedef cc_mqttsn::message::Suback SubackMsg; - typedef cc_mqttsn::message::Unsubscribe UnsubscribeMsg; - typedef cc_mqttsn::message::Unsuback UnsubackMsg; - typedef cc_mqttsn::message::Pingreq PingreqMsg; - typedef cc_mqttsn::message::Pingresp PingrespMsg; - typedef cc_mqttsn::message::Disconnect DisconnectMsg; - typedef cc_mqttsn::message::Willtopicupd WilltopicupdMsg; - typedef cc_mqttsn::message::Willtopicresp WilltopicrespMsg; - typedef cc_mqttsn::message::Willmsgupd WillmsgupdMsg; - typedef cc_mqttsn::message::Willmsgresp WillmsgrespMsg; - - BasicClient() = default; - ~BasicClient() noexcept = default; - - typedef typename Message::ReadIterator ReadIterator; - - const GwInfoStorage& gwInfos() const - { - return m_gwInfos; - } - - void setRetryPeriod(unsigned val) - { - static const auto MaxVal = - std::numeric_limits::max() / 1000; - m_retryPeriod = std::min(val * 1000, MaxVal); - } - - void setRetryCount(unsigned val) - { - m_retryCount = std::max(1U, val); - } - - void setBroadcastRadius(std::uint8_t val) - { - m_broadcastRadius = val; - } - - void setNextTickProgramCallback(CC_MqttsnNextTickProgramFn cb, void* data) - { - if (cb != nullptr) { - m_nextTickProgramFn = cb; - m_nextTickProgramData = data; - } - } - - void setCancelNextTickWaitCallback(CC_MqttsnCancelNextTickWaitFn cb, void* data) - { - if (cb != nullptr) { - m_cancelNextTickWaitFn = cb; - m_cancelNextTickWaitData = data; - } - } - - void setSendOutputDataCallback(CC_MqttsnSendOutputDataFn cb, void* data) - { - if (cb != nullptr) { - m_sendOutputDataFn = cb; - m_sendOutputDataData = data; - } - } - - void setGwStatusReportCallback(CC_MqttsnGwStatusReportFn cb, void* data) - { - m_gwStatusReportFn = cb; - m_gwStatusReportData = data; - } - - void setGwDisconnectReportCallback(CC_MqttsnGwDisconnectReportFn cb, void* data) - { - m_gwDisconnectReportFn = cb; - m_gwDisconnectReportData = data; - } - - void setMessageReportCallback(CC_MqttsnMessageReportFn cb, void* data) - { - m_msgReportFn = cb; - m_msgReportData = data; - } - - void setSearchgwEnabled(bool value) - { - m_searchgwEnabled = value; - } - - void sendSearchGw() - { - sendGwSearchReq(); - } - - void discardGw(std::uint8_t gwId) - { - auto guard = apiCall(); - auto iter = - std::find_if( - m_gwInfos.begin(), m_gwInfos.end(), - [gwId](typename GwInfoStorage::const_reference elem) -> bool - { - return elem.m_id == gwId; - }); - - if (iter == m_gwInfos.end()) { - return; - } - - m_gwInfos.erase(iter); - reportGwStatus(gwId, CC_MqttsnGwStatus_Discarded); - } - - void discardAllGw() - { - auto guard = apiCall(); - - if (m_gwStatusReportFn == nullptr) { - m_gwInfos.clear(); - return; - } - - typedef details::GwInfoStorageTypeT GwIdStorage; - GwIdStorage ids; - ids.reserve(m_gwInfos.size()); - std::transform( - m_gwInfos.begin(), m_gwInfos.end(), std::back_inserter(ids), - [](typename GwInfoStorage::const_reference elem) -> std::uint8_t - { - return elem.m_id; - }); - - m_gwInfos.clear(); - for (auto gwId : ids) { - reportGwStatus(gwId, CC_MqttsnGwStatus_Discarded); - } - } - - CC_MqttsnErrorCode start() - { - if (m_running) { - return CC_MqttsnErrorCode_AlreadyStarted; - } - - if ((m_nextTickProgramFn == nullptr) || - (m_cancelNextTickWaitFn == nullptr) || - (m_sendOutputDataFn == nullptr) || - (m_msgReportFn == nullptr)) { - return CC_MqttsnErrorCode_BadParam; - } - - m_running = true; - - m_gwInfos.clear(); - m_regInfos.clear(); - m_nextTimeoutTimestamp = 0; - m_lastGwSearchTimestamp = 0; - m_lastRecvMsgTimestamp = 0; - m_lastSentMsgTimestamp = 0; - m_lastPingTimestamp = 0; - - m_pingCount = 0; - m_connectionStatus = ConnectionStatus::Disconnected; - - m_currOp = Op::None; - m_tickDelay = 0U; - - checkGwSearchReq(); - programNextTimeout(); - return CC_MqttsnErrorCode_Success; - } - - CC_MqttsnErrorCode stop() - { - if (!m_running) { - return CC_MqttsnErrorCode_NotStarted; - } - - auto guard = apiCall(); - m_running = false; - return CC_MqttsnErrorCode_Success; - } - - void tick() - { - if (!m_running) { - return; - } - - COMMS_ASSERT(m_callStackCount == 0U); - m_timestamp += m_tickDelay; - m_tickDelay = 0U; - - checkTimeouts(); - programNextTimeout(); - } - - std::size_t processData(ReadIterator& iter, std::size_t len) - { - if (!m_running) { - return 0U; - } - - auto guard = apiCall(); - std::size_t consumed = 0; - while (true) { - auto iterTmp = iter; - MsgPtr msg; - auto es = m_stack.read(msg, iterTmp, len - consumed); - if (es == comms::ErrorStatus::NotEnoughData) { - break; - } - - if (es == comms::ErrorStatus::ProtocolError) { - ++iter; - continue; - } - - if (es == comms::ErrorStatus::Success) { - COMMS_ASSERT(msg); - m_lastRecvMsgTimestamp = m_timestamp; - msg->dispatch(*this); - } - - consumed += static_cast(std::distance(iter, iterTmp)); - iter = iterTmp; - } - - return consumed; - } - - bool cancel() - { - if (m_currOp == Op::None) { - return false; - } - - COMMS_ASSERT(m_running); - auto guard = apiCall(); - - auto fn = getFinaliseFunc(); - COMMS_ASSERT(fn != nullptr); - (this->*(fn))(CC_MqttsnAsyncOpStatus_Aborted); - return true; - } - - CC_MqttsnErrorCode connect( - const char* clientId, - unsigned short keepAlivePeriod, - bool cleanSession, - const CC_MqttsnWillInfo* willInfo, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data) - { - if (!m_running) { - return CC_MqttsnErrorCode_NotStarted; - } - - if (m_connectionStatus != ConnectionStatus::Disconnected) { - return CC_MqttsnErrorCode_AlreadyConnected; - } - - if (m_currOp != Op::None) { - return CC_MqttsnErrorCode_Busy; - } - - auto guard = apiCall(); - - m_currOp = Op::Connect; - - auto* connectOp = newAsyncOp(callback, data); - - if (willInfo != nullptr) { - connectOp->m_willInfo = *willInfo; - connectOp->m_hasWill = true; - } - connectOp->m_clientId = clientId; - connectOp->m_keepAlivePeriod = keepAlivePeriod; - connectOp->m_cleanSession = cleanSession; - - bool result = doConnect(); - static_cast(result); - COMMS_ASSERT(result); - return CC_MqttsnErrorCode_Success; - } - - CC_MqttsnErrorCode reconnect( - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data) - { - if (!m_running) { - return CC_MqttsnErrorCode_NotStarted; - } - - if (m_connectionStatus == ConnectionStatus::Disconnected) { - return CC_MqttsnErrorCode_NotConnected; - } - - if (m_currOp != Op::None) { - return CC_MqttsnErrorCode_Busy; - } - - if (callback == nullptr) { - return CC_MqttsnErrorCode_BadParam; - } - - auto guard = apiCall(); - - m_currOp = Op::Connect; - auto* op = newAsyncOp(callback, data); - op->m_clientId = m_clientId.c_str(); - op->m_keepAlivePeriod = static_castm_keepAlivePeriod)>(m_keepAlivePeriod / 1000); - op->m_hasWill = false; - op->m_cleanSession = false; - - bool result = doConnect(); - static_cast(result); - COMMS_ASSERT(result); - - return CC_MqttsnErrorCode_Success; - } - - CC_MqttsnErrorCode disconnect( - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data) - { - if (!m_running) { - return CC_MqttsnErrorCode_NotStarted; - } - - if (m_connectionStatus == ConnectionStatus::Disconnected) { - return CC_MqttsnErrorCode_NotConnected; - } - - if (m_currOp != Op::None) { - return CC_MqttsnErrorCode_Busy; - } - - auto guard = apiCall(); - m_currOp = Op::Disconnect; - auto* op = newAsyncOp(callback, data); - static_cast(op); - - bool result = doDisconnect(); - static_cast(result); - COMMS_ASSERT(result); - - return CC_MqttsnErrorCode_Success; - } - - CC_MqttsnErrorCode publish( - CC_MqttsnTopicId topicId, - const std::uint8_t* msg, - std::size_t msgLen, - CC_MqttsnQoS qos, - bool retain, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data) - { - if (!m_running) { - return CC_MqttsnErrorCode_NotStarted; - } - - if ((m_connectionStatus != ConnectionStatus::Connected) && (qos != CC_MqttsnQoS_NoGwPublish)) { - return CC_MqttsnErrorCode_NotConnected; - } - - if (m_currOp != Op::None) { - return CC_MqttsnErrorCode_Busy; - } - - if ((qos < CC_MqttsnQoS_NoGwPublish) || - (CC_MqttsnQoS_ExactlyOnceDelivery < qos)) { - return CC_MqttsnErrorCode_BadParam; - } - - if ((callback == nullptr) && (CC_MqttsnQoS_AtLeastOnceDelivery <= qos)) { - return CC_MqttsnErrorCode_BadParam; - } - - auto guard = apiCall(); - - if (CC_MqttsnQoS_AtLeastOnceDelivery <= qos) { - m_currOp = Op::PublishId; - auto* pubOp = newAsyncOp(callback, data); - pubOp->m_topicId = topicId; - pubOp->m_msg = msg; - pubOp->m_msgLen = msgLen; - pubOp->m_qos = qos; - pubOp->m_retain = retain; - - bool result = doPublishId(); - static_cast(result); - COMMS_ASSERT(result); - } - else { - sendPublish( - topicId, - 0, - msg, - msgLen, - TopicIdTypeVal::PredefinedTopicId, - details::translateQosValue(qos), - retain, - false); - if (callback != nullptr) { - callback(data, CC_MqttsnAsyncOpStatus_Successful); - } - } - - return CC_MqttsnErrorCode_Success; - } - - CC_MqttsnErrorCode publish( - const char* topic, - const std::uint8_t* msg, - std::size_t msgLen, - CC_MqttsnQoS qos, - bool retain, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data) - { - if (!m_running) { - return CC_MqttsnErrorCode_NotStarted; - } - - if ((m_connectionStatus != ConnectionStatus::Connected) && (qos != CC_MqttsnQoS_NoGwPublish)) { - return CC_MqttsnErrorCode_NotConnected; - } - - - if (m_currOp != Op::None) { - return CC_MqttsnErrorCode_Busy; - } - - if ((qos < CC_MqttsnQoS_AtMostOnceDelivery) || - (CC_MqttsnQoS_ExactlyOnceDelivery < qos) || - (callback == nullptr)) { - return CC_MqttsnErrorCode_BadParam; - } - - auto guard = apiCall(); - m_currOp = Op::Publish; - auto* pubOp = newOp(); - pubOp->m_topic = topic; - pubOp->m_msg = msg; - pubOp->m_msgLen = msgLen; - pubOp->m_qos = qos; - pubOp->m_retain = retain; - pubOp->m_cb = callback; - pubOp->m_cbData = data; - pubOp->m_shortName = isShortTopicName(topic); - if (pubOp->m_shortName) { - pubOp->m_topicId = shortTopicToTopicId(topic); - } - - bool result = doPublish(); - static_cast(result); - COMMS_ASSERT(result); - - return CC_MqttsnErrorCode_Success; - } - - CC_MqttsnErrorCode subscribe( - CC_MqttsnTopicId topicId, - CC_MqttsnQoS qos, - CC_MqttsnSubscribeCompleteReportFn callback, - void* data - ) - { - if (!m_running) { - return CC_MqttsnErrorCode_NotStarted; - } - - if (m_connectionStatus != ConnectionStatus::Connected) { - return CC_MqttsnErrorCode_NotConnected; - } - - if (m_currOp != Op::None) { - return CC_MqttsnErrorCode_Busy; - } - - if ((qos < CC_MqttsnQoS_AtMostOnceDelivery) || - (CC_MqttsnQoS_ExactlyOnceDelivery < qos) || - (callback == nullptr)) { - return CC_MqttsnErrorCode_BadParam; - } - - auto guard = apiCall(); - - m_currOp = Op::SubscribeId; - auto* op = newOp(); - op->m_topicId = topicId; - op->m_qos = qos; - op->m_cb = callback; - op->m_cbData = data; - op->m_msgId = allocMsgId(); - - bool result = doSubscribeId(); - static_cast(result); - COMMS_ASSERT(result); - - return CC_MqttsnErrorCode_Success; - } - - CC_MqttsnErrorCode subscribe( - const char* topic, - CC_MqttsnQoS qos, - CC_MqttsnSubscribeCompleteReportFn callback, - void* data - ) - { - if (!m_running) { - return CC_MqttsnErrorCode_NotStarted; - } - - if (m_connectionStatus != ConnectionStatus::Connected) { - return CC_MqttsnErrorCode_NotConnected; - } - - if (m_currOp != Op::None) { - return CC_MqttsnErrorCode_Busy; - } - - if ((qos < CC_MqttsnQoS_AtMostOnceDelivery) || - (CC_MqttsnQoS_ExactlyOnceDelivery < qos) || - (topic == nullptr) || - (callback == nullptr)) { - return CC_MqttsnErrorCode_BadParam; - } - - auto guard = apiCall(); - - m_currOp = Op::Subscribe; - auto* op = newOp(); - op->m_topic = topic; - op->m_qos = qos; - op->m_cb = callback; - op->m_cbData = data; - op->m_msgId = allocMsgId(); - op->m_usingShortTopicName = isShortTopicName(topic); - if (op->m_usingShortTopicName) { - op->m_topicId = shortTopicToTopicId(topic); - } - - bool result = doSubscribe(); - static_cast(result); - COMMS_ASSERT(result); - - return CC_MqttsnErrorCode_Success; - } - - CC_MqttsnErrorCode unsubscribe( - CC_MqttsnTopicId topicId, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data - ) - { - if (!m_running) { - return CC_MqttsnErrorCode_NotStarted; - } - - if (m_connectionStatus != ConnectionStatus::Connected) { - return CC_MqttsnErrorCode_NotConnected; - } - - if (m_currOp != Op::None) { - return CC_MqttsnErrorCode_Busy; - } - - if (callback == nullptr) { - return CC_MqttsnErrorCode_BadParam; - } - - auto guard = apiCall(); - - m_currOp = Op::UnsubscribeId; - auto* op = newAsyncOp(callback, data); - op->m_topicId = topicId; - op->m_msgId = allocMsgId(); - - bool result = doUnsubscribeId(); - static_cast(result); - COMMS_ASSERT(result); - - return CC_MqttsnErrorCode_Success; - } - - CC_MqttsnErrorCode unsubscribe( - const char* topic, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data) - { - if (!m_running) { - return CC_MqttsnErrorCode_NotStarted; - } - - if (m_connectionStatus != ConnectionStatus::Connected) { - return CC_MqttsnErrorCode_NotConnected; - } - - if (m_currOp != Op::None) { - return CC_MqttsnErrorCode_Busy; - } - - if (callback == nullptr) { - return CC_MqttsnErrorCode_BadParam; - } - - auto guard = apiCall(); - - m_currOp = Op::Unsubscribe; - auto* op = newAsyncOp(callback, data); - op->m_topic = topic; - op->m_msgId = allocMsgId(); - op->m_usingShortTopicName = isShortTopicName(topic); - if (op->m_usingShortTopicName) { - op->m_topicId = shortTopicToTopicId(topic); - } - - bool result = doUnsubscribe(); - static_cast(result); - COMMS_ASSERT(result); - - return CC_MqttsnErrorCode_Success; - } - - CC_MqttsnErrorCode willUpdate( - const CC_MqttsnWillInfo* willInfo, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data) - { - if (!m_running) { - return CC_MqttsnErrorCode_NotStarted; - } - - if (m_connectionStatus != ConnectionStatus::Connected) { - return CC_MqttsnErrorCode_NotConnected; - } - - if (m_currOp != Op::None) { - return CC_MqttsnErrorCode_Busy; - } - - if (callback == nullptr) { - return CC_MqttsnErrorCode_BadParam; - } - - auto guard = apiCall(); - - m_currOp = Op::Connect; - auto* op = newAsyncOp(callback, data); - op->m_clientId = m_clientId.c_str(); - op->m_keepAlivePeriod = static_castm_keepAlivePeriod)>(m_keepAlivePeriod / 1000); - if (willInfo != nullptr) { - op->m_willInfo = *willInfo; - } - op->m_hasWill = true; - op->m_cleanSession = false; - - bool result = doConnect(); - static_cast(result); - COMMS_ASSERT(result); - - return CC_MqttsnErrorCode_Success; - } - - CC_MqttsnErrorCode willTopicUpdate( - const char* topic, - CC_MqttsnQoS qos, - bool retain, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data) - { - if (!m_running) { - return CC_MqttsnErrorCode_NotStarted; - } - - if (m_connectionStatus != ConnectionStatus::Connected) { - return CC_MqttsnErrorCode_NotConnected; - } - - if (m_currOp != Op::None) { - return CC_MqttsnErrorCode_Busy; - } - - if (callback == nullptr) { - return CC_MqttsnErrorCode_BadParam; - } - - auto guard = apiCall(); - - m_currOp = Op::WillTopicUpdate; - auto* op = newAsyncOp(callback, data); - op->m_topic = topic; - op->m_qos = qos; - op->m_retain = retain; - - bool result = doWillTopicUpdate(); - static_cast(result); - COMMS_ASSERT(result); - - return CC_MqttsnErrorCode_Success; - } - - CC_MqttsnErrorCode willMsgUpdate( - const unsigned char* msg, - unsigned msgLen, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data) - { - if (!m_running) { - return CC_MqttsnErrorCode_NotStarted; - } - - if (m_connectionStatus != ConnectionStatus::Connected) { - return CC_MqttsnErrorCode_NotConnected; - } - - if (m_currOp != Op::None) { - return CC_MqttsnErrorCode_Busy; - } - - if (callback == nullptr) { - return CC_MqttsnErrorCode_BadParam; - } - - auto guard = apiCall(); - - m_currOp = Op::WillMsgUpdate; - auto* op = newAsyncOp(callback, data); - op->m_msg = msg; - op->m_msgLen = msgLen; - - bool result = doWillMsgUpdate(); - static_cast(result); - COMMS_ASSERT(result); - - return CC_MqttsnErrorCode_Success; - } - - CC_MqttsnErrorCode sleep( - std::uint16_t duration, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data) - { - if (!m_running) { - return CC_MqttsnErrorCode_NotStarted; - } - - if (m_connectionStatus == ConnectionStatus::Disconnected) { - return CC_MqttsnErrorCode_NotConnected; - } - - if (m_currOp != Op::None) { - return CC_MqttsnErrorCode_Busy; - } - - if (callback == nullptr) { - return CC_MqttsnErrorCode_BadParam; - } - - auto guard = apiCall(); - - m_currOp = Op::Sleep; - auto* op = newAsyncOp(callback, data); - op->m_duration = duration; - - bool result = doSleep(); - static_cast(result); - COMMS_ASSERT(result); - - return CC_MqttsnErrorCode_Success; - } - - CC_MqttsnErrorCode checkMessages( - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data) - { - if (!m_running) { - return CC_MqttsnErrorCode_NotStarted; - } - - if (m_connectionStatus != ConnectionStatus::Asleep) { - return CC_MqttsnErrorCode_NotSleeping; - } - - if (m_currOp != Op::None) { - return CC_MqttsnErrorCode_Busy; - } - - if (callback == nullptr) { - return CC_MqttsnErrorCode_BadParam; - } - - auto guard = apiCall(); - - m_currOp = Op::CheckMessages; - auto* op = newAsyncOp(callback, data); - static_cast(op); - - bool result = doCheckMessages(); - static_cast(result); - COMMS_ASSERT(result); - - return CC_MqttsnErrorCode_Success; - } - - void handle(AdvertiseMsg& msg) - { - auto durationVal = msg.field_duration().value() * 3000U; - - auto iter = findGwInfo(msg.field_gwId().value()); - if (iter != m_gwInfos.end()) { - iter->m_timestamp = m_timestamp; - iter->m_duration = durationVal; - return; - } - - if (!addNewGw(msg.field_gwId().value(), durationVal)) { - return; - } - - reportGwStatus(msg.field_gwId().value(), CC_MqttsnGwStatus_Available); - } - - void handle(GwinfoMsg& msg) - { - //auto& addrField = std::get(fields); - auto iter = findGwInfo(msg.field_gwId().value()); - if (iter != m_gwInfos.end()) { - iter->m_timestamp = m_timestamp; -// if (!addrField.value().empty()) { -// iter->m_addr = addrField.value(); -// } - return; - } - - if (!addNewGw(msg.field_gwId().value(), std::numeric_limits::max() * 1000)) { - return; - } - - COMMS_ASSERT(!m_gwInfos.empty()); -// if (!addrField.value().empty()) { -// iter->m_addr = addrField.value(); -// } - - reportGwStatus(msg.field_gwId().value(), CC_MqttsnGwStatus_Available); - } - - void handle(ConnackMsg& msg) - { - if (m_currOp != Op::Connect) { - return; - } - - auto* op = opPtr(); - bool willReported = (op->m_willTopicSent && op->m_willMsgSent); - if (op->m_hasWill && (!willReported)) { - return; - } - - auto returnCode = msg.field_returnCode().value(); - - if (returnCode == ReturnCodeVal::Accepted) { - m_connectionStatus = ConnectionStatus::Connected; - } - - do { - if (op->m_clientId == nullptr) { - m_clientId.clear(); - break; - } - - if (op->m_clientId == m_clientId.c_str()) { - break; - } - - m_clientId = op->m_clientId; - } while (false); - - m_keepAlivePeriod = op->m_keepAlivePeriod * 1000U; - finaliseConnectOp(retCodeToStatus(returnCode)); - } - - void handle(WilltopicreqMsg& msg) - { - static_cast(msg); - if (m_currOp != Op::Connect) { - return; - } - - auto* op = opPtr(); - bool emptyTopic = - (op->m_willInfo.topic == nullptr) || (op->m_willInfo.topic[0] == '\0'); - - op->m_lastMsgTimestamp = m_timestamp; - op->m_willTopicSent = true; - if (emptyTopic) { - op->m_willMsgSent = true; - } - - sendWilltopic(op->m_willInfo.topic, op->m_willInfo.qos, op->m_willInfo.retain); - } - - void handle(WillmsgreqMsg& msg) - { - static_cast(msg); - if (m_currOp != Op::Connect) { - return; - } - - auto* op = opPtr(); - if (!op->m_willTopicSent) { - return; - } - - op->m_lastMsgTimestamp = m_timestamp; - WillmsgMsg outMsg; - - if (op->m_willInfo.msg != nullptr) { - auto& dataStorage = outMsg.field_willMsg().value(); - comms::util::assign(dataStorage, op->m_willInfo.msg, op->m_willInfo.msg + op->m_willInfo.msgLen); - } - - op->m_willMsgSent = true; - sendMessage(outMsg); - } - - void handle(RegisterMsg& msg) - { - auto& topic = msg.field_topicName().value(); - updateRegInfo(topic.data(), topic.size(), msg.field_topicId().value(), true); - - RegackMsg ackMsg; - ackMsg.field_topicId().value() = msg.field_topicId().value(); - ackMsg.field_msgId().value() = msg.field_msgId().value(); - ackMsg.field_returnCode().value() = ReturnCodeVal::Accepted; - sendMessage(ackMsg); - } - - void handle(RegackMsg& msg) - { - if (m_currOp != Op::Publish) { - return; - } - - auto* op = opPtr(); - - if (!op->m_didRegistration) { - return; - } - - if (msg.field_msgId().value() != op->m_msgId) { - return; - } - - auto retCodeValue = msg.field_returnCode().value(); - - if (retCodeValue != ReturnCodeVal::Accepted) { - finalisePublishOp(retCodeToStatus(retCodeValue)); - return; - } - - op->m_lastMsgTimestamp = m_timestamp; - op->m_registered = true; - op->m_topicId = msg.field_topicId().value(); - op->m_attempt = 0; - - updateRegInfo(op->m_topic, std::strlen(op->m_topic), op->m_topicId); - bool result = doPublish(); - static_cast(result); - COMMS_ASSERT(result); - } - - void handle(PublishMsg& msg) - { - auto reportMsgFunc = - [this, &msg](const char* topicName) - { - auto msgInfo = CC_MqttsnMessageInfo(); - - msgInfo.topic = topicName; - if (topicName == nullptr) { - msgInfo.topicId = msg.field_topicId().value(); - } - - msgInfo.msg = &(*msg.field_data().value().begin()); - msgInfo.msgLen = static_cast(msg.field_data().value().size()); - msgInfo.qos = details::translateQosValue(msg.field_flags().field_qos().value()); - msgInfo.retain = msg.field_flags().field_mid().getBitValue_Retain(); - - COMMS_ASSERT(m_msgReportFn != nullptr); - m_msgReportFn(m_msgReportData, &msgInfo); - }; - - auto iter = m_regInfos.end(); - if (msg.field_flags().field_topicIdType().value() == TopicIdTypeVal::Normal) { - iter = std::find_if( - m_regInfos.begin(), m_regInfos.end(), - [&msg](typename RegInfosList::const_reference elem) -> bool - { - return elem.m_allocated && (elem.m_topicId == msg.field_topicId().value()); - }); - - if (iter == m_regInfos.end()) { - sendPuback(msg.field_topicId().value(), msg.field_msgId().value(), ReturnCodeVal::InvalidTopicId); - } - } - - const char* topicName = nullptr; - if (iter != m_regInfos.end()) { - topicName = iter->m_topic.c_str(); - } - - char shortTopicName[3] = {0}; - bool usingShortTopic = - msg.field_flags().field_topicIdType().value() == TopicIdTypeVal::ShortTopicName; - if (usingShortTopic) { - COMMS_ASSERT(iter == m_regInfos.end()); - topicIdToShortTopic(msg.field_topicId().value(), &shortTopicName[0]); - topicName = &shortTopicName[0]; - } - - if ((msg.field_flags().field_qos().value() < cc_mqttsn::field::QosVal::AtLeastOnceDelivery) || - (cc_mqttsn::field::QosVal::ExactlyOnceDelivery < msg.field_flags().field_qos().value())) { - - if ((topicName == nullptr) && - (msg.field_flags().field_topicIdType().value() != TopicIdTypeVal::PredefinedTopicId)) { - return; - } - - reportMsgFunc(topicName); - return; - } - - if ((topicName == nullptr) && - (msg.field_flags().field_topicIdType().value() != TopicIdTypeVal::PredefinedTopicId)) { - m_lastInMsg = LastInMsgInfo(); - return; - } - - if (msg.field_flags().field_qos().value() == cc_mqttsn::field::QosVal::AtLeastOnceDelivery) { - sendPuback(msg.field_topicId().value(), msg.field_msgId().value(), ReturnCodeVal::Accepted); - reportMsgFunc(topicName); - return; - } - - COMMS_ASSERT(msg.field_flags().field_qos().value() == cc_mqttsn::field::QosVal::ExactlyOnceDelivery); - - bool newMessage = - ((!msg.field_flags().field_high().getBitValue_Dup()) || - (msg.field_topicId().value() != m_lastInMsg.m_topicId) || - (msg.field_msgId().value() != m_lastInMsg.m_msgId) || - (m_lastInMsg.m_reported) || - (usingShortTopic != m_lastInMsg.m_usingShortTopicName)); - - if (newMessage) { - m_lastInMsg = LastInMsgInfo(); - - m_lastInMsg.m_topicId = msg.field_topicId().value(); - m_lastInMsg.m_msgId = msg.field_msgId().value(); - m_lastInMsg.m_retain = msg.field_flags().field_mid().getBitValue_Retain(); - m_lastInMsg.m_usingShortTopicName = usingShortTopic; - if (usingShortTopic) { - std::copy(std::begin(shortTopicName), std::end(shortTopicName), std::begin(m_lastInMsg.m_shortTopic)); - } - } - - auto& msgData = msg.field_data().value(); - m_lastInMsg.m_msgData.assign(msgData.begin(), msgData.end()); - - PubrecMsg recMsg; - recMsg.field_msgId().value() = msg.field_msgId().value(); - sendMessage(recMsg); - } - - void handle(PubackMsg& msg) - { - auto retCodeValue = msg.field_returnCode().value(); - - if (retCodeValue == ReturnCodeVal::InvalidTopicId) { - - auto iter = std::find_if( - m_regInfos.begin(), m_regInfos.end(), - [&msg](typename RegInfosList::const_reference elem) -> bool - { - return elem.m_allocated && (elem.m_topicId == msg.field_topicId().value()); - }); - - if (iter != m_regInfos.end()) { - dropRegInfo(iter); - } - } - - if ((m_currOp != Op::Publish) && (m_currOp != Op::PublishId)) { - return; - } - - auto* op = opPtr(); - - - if ((msg.field_topicId().value() != op->m_topicId) || - (msg.field_msgId().value() != op->m_msgId)) { - return; - } - - if ((op->m_qos == CC_MqttsnQoS_ExactlyOnceDelivery) && - (retCodeValue == ReturnCodeVal::Accepted)) { - // PUBREC is expected instead - return; - } - - do { - if ((op->m_qos < CC_MqttsnQoS_AtLeastOnceDelivery) || - (retCodeValue != ReturnCodeVal::InvalidTopicId) || - (m_currOp != Op::Publish) || - (opPtr()->m_didRegistration)) { - break; - } - - op->m_lastMsgTimestamp = m_timestamp; - op->m_topicId = 0U; - op->m_attempt = 0; - opPtr()->m_registered = false; - doPublish(); - return; - } while (false); - - finalisePublishOp(retCodeToStatus(retCodeValue)); - } - - void handle(PubrecMsg& msg) - { - if ((m_currOp != Op::Publish) && (m_currOp != Op::PublishId)) { - return; - } - - auto* op = opPtr(); - - if (msg.field_msgId().value() != op->m_msgId) { - return; - } - - op->m_lastMsgTimestamp = m_timestamp; - op->m_ackReceived = true; - sendPubrel(op->m_msgId); - } - - void handle(PubrelMsg& msg) - { - if (m_lastInMsg.m_msgId != msg.field_msgId().value()) { - m_lastInMsg = LastInMsgInfo(); - return; - } - - PubcompMsg compMsg; - compMsg.field_msgId().value() = msg.field_msgId().value(); - sendMessage(compMsg); - - if (!m_lastInMsg.m_reported) { - auto msgInfo = CC_MqttsnMessageInfo(); - - if (m_lastInMsg.m_usingShortTopicName) { - msgInfo.topic = &m_lastInMsg.m_shortTopic[0]; - } - else { - auto iter = std::find_if( - m_regInfos.begin(), m_regInfos.end(), - [this](typename RegInfosList::const_reference elem) -> bool - { - return elem.m_allocated && (elem.m_topicId == m_lastInMsg.m_topicId); - }); - - if (iter != m_regInfos.end()) { - msgInfo.topic = iter->m_topic.c_str(); - } - - msgInfo.topicId = m_lastInMsg.m_topicId; - } - - msgInfo.msg = &(*m_lastInMsg.m_msgData.begin()); - msgInfo.msgLen = static_cast(m_lastInMsg.m_msgData.size()); - msgInfo.qos = CC_MqttsnQoS_ExactlyOnceDelivery; - msgInfo.retain = m_lastInMsg.m_retain; - - m_lastInMsg.m_reported = true; - - COMMS_ASSERT(m_msgReportFn != nullptr); - m_msgReportFn(m_msgReportData, &msgInfo); - } - } - - void handle(PubcompMsg& msg) - { - if ((m_currOp != Op::Publish) && (m_currOp != Op::PublishId)) { - return; - } - - auto* op = opPtr(); - - if ((msg.field_msgId().value() != op->m_msgId) || - (!op->m_ackReceived)) { - return; - } - - finalisePublishOp(CC_MqttsnAsyncOpStatus_Successful); - } - - void handle(SubackMsg& msg) - { - if ((m_currOp != Op::Subscribe) && (m_currOp != Op::SubscribeId)) { - return; - } - - // NOTE: Without "volatile" keyword below g++-9 in Release mode - // optimizes away assignments to some internal fields, like qos - // before call to finaliseSubscribeOp(), which results - // in incorrect values reported in the callback. - volatile auto* op = opPtr(); - - if (msg.field_msgId().value() != op->m_msgId) { - return; - } - - auto retCodeValue = msg.field_returnCode().value(); - if ((retCodeValue == ReturnCodeVal::InvalidTopicId) && - (m_currOp == Op::Subscribe) && - (opPtr()->m_topicId != 0U)) { - - auto iter = std::find_if( - m_regInfos.begin(), m_regInfos.end(), - [this](typename RegInfosList::const_reference elem) -> bool - { - return elem.m_allocated && (elem.m_topicId == opPtr()->m_topicId); - }); - - if (iter != m_regInfos.end()) { - dropRegInfo(iter); - } - - op->m_attempt = 0; - bool result = doSubscribe(); - static_cast(result); - COMMS_ASSERT(result); - return; - } - - auto qosValue = details::translateQosValue(msg.field_flags().field_qos().value()); - if (retCodeValue != ReturnCodeVal::Accepted) { - op->m_qos = qosValue; - finaliseSubscribeOp(retCodeToStatus(retCodeValue)); - return; - } - - if ((m_currOp == Op::SubscribeId) && - (opPtr()->m_topicId != msg.field_topicId().value())) { - if (!doSubscribeId()) { - finaliseSubscribeOp(CC_MqttsnAsyncOpStatus_InvalidId); - } - return; - } - - if ((m_currOp == Op::Subscribe) && (msg.field_topicId().value() != 0U)) { - auto* topicStr = opPtr()->m_topic; - updateRegInfo(topicStr, std::strlen(topicStr), msg.field_topicId().value(), true); - } - - op->m_qos = qosValue; - finaliseSubscribeOp(CC_MqttsnAsyncOpStatus_Successful); - } - - void handle(UnsubackMsg& msg) - { - if ((m_currOp != Op::Unsubscribe) && (m_currOp != Op::UnsubscribeId)) { - return; - } - - auto* op = opPtr(); - - if (msg.field_msgId().value() != op->m_msgId) { - return; - } - - do { - if (m_currOp != Op::Unsubscribe) { - break; - } - - auto* downcastedOp = opPtr(); - - if (downcastedOp->m_topicId == 0U) { - break; - } - - auto iter = std::find_if( - m_regInfos.begin(), m_regInfos.end(), - [downcastedOp](typename RegInfosList::const_reference elem) -> bool - { - return elem.m_allocated && (elem.m_topicId == downcastedOp->m_topicId); - }); - - if (iter == m_regInfos.end()) { - break; - } - - iter->m_locked = false; - } while (false); - - finaliseUnsubscribeOp(CC_MqttsnAsyncOpStatus_Successful); - } - - void handle(PingreqMsg& msg) - { - static_cast(msg); - PingrespMsg outMsg; - sendMessage(outMsg); - } - - void handle(PingrespMsg& msg) - { - static_cast(msg); - bool pinging = (0U < m_pingCount); - m_pingCount = 0U; - - if (pinging || (m_currOp != Op::CheckMessages)) { - return; - } - - // checking messages in asleep mode - COMMS_ASSERT(m_connectionStatus == ConnectionStatus::Asleep); - finaliseCheckMessagesOp(CC_MqttsnAsyncOpStatus_Successful); - } - - void handle(DisconnectMsg& msg) - { - static_cast(msg); - - if (m_currOp == Op::Disconnect) { - finaliseDisconnectOp(CC_MqttsnAsyncOpStatus_Successful); - return; - } - - if (m_currOp == Op::Sleep) { - m_connectionStatus = ConnectionStatus::Asleep; - finaliseSleepOp(CC_MqttsnAsyncOpStatus_Successful); - return; - } - - if (m_currOp == Op::None) { - reportGwDisconnected(); - return; - } - - cancel(); - reportGwDisconnected(); - } - - void handle(WilltopicrespMsg& msg) - { - if (m_currOp != Op::WillTopicUpdate) { - return; - } - - finaliseWillTopicUpdateOp(retCodeToStatus(msg.field_returnCode().value())); - } - - void handle(WillmsgrespMsg& msg) - { - if (m_currOp != Op::WillMsgUpdate) { - return; - } - - finaliseWillMsgUpdateOp(retCodeToStatus(msg.field_returnCode().value())); - } - - void handle(Message& msg) - { - static_cast(msg); - } - -private: - - typedef typename comms::util::TupleAsAlignedUnion< - std::tuple< - ConnectOp, - DisconnectOp, - PublishIdOp, - PublishOp, - SubscribeIdOp, - SubscribeOp, - UnsubscribeIdOp, - UnsubscribeOp, - WillTopicUpdateOp, - WillMsgUpdateOp, - SleepOp, - CheckMessagesOp - > - >::Type OpStorageType; - - using InputMessages = cc_mqttsn::input::ProtClientInputMessages; - typedef cc_mqttsn::frame::Frame ProtStack; - typedef typename ProtStack::MsgPtr MsgPtr; - - struct RegInfo - { - Timestamp m_timestamp = 0U; - TopicNameType m_topic; - TopicIdType m_topicId = 0U; - bool m_allocated = false; - bool m_locked = false; - }; - - typedef details::RegInfoStorageTypeT RegInfosList; - - struct LastInMsgInfo - { - DataType m_msgData; - CC_MqttsnTopicId m_topicId = 0; - std::uint16_t m_msgId = 0; - char m_shortTopic[3] = {0}; - bool m_retain = false; - bool m_reported = false; - bool m_usingShortTopicName = false; - }; - - void updateRegInfo(const char* topic, std::size_t topicLen, TopicIdType topicId, bool locked = false) - { - auto iter = std::find_if( - m_regInfos.begin(), m_regInfos.end(), - [topic, topicLen](typename RegInfosList::const_reference elem) -> bool - { - return - elem.m_allocated && - elem.m_topic.size() == topicLen && - std::equal(elem.m_topic.begin(), elem.m_topic.end(), topic); - }); - - if (iter != m_regInfos.end()) { - iter->m_timestamp = m_timestamp; - iter->m_topicId = topicId; - iter->m_locked = (iter->m_locked || locked); - return; - } - - iter = std::find_if( - m_regInfos.begin(), m_regInfos.end(), - [](typename RegInfosList::const_reference elem) -> bool - { - return !elem.m_allocated; - }); - - if (iter != m_regInfos.end()) { - iter->m_timestamp = m_timestamp; - iter->m_topic.assign(topic, topic + topicLen); - iter->m_topicId = topicId; - iter->m_allocated = true; - iter->m_locked = locked; - return; - } - - RegInfo info; - info.m_timestamp = m_timestamp; - info.m_topic.assign(topic, topicLen); - info.m_topicId = topicId; - info.m_allocated = true; - info.m_locked = locked; - - if (m_regInfos.size() < m_regInfos.max_size()) { - m_regInfos.push_back(std::move(info)); - return; - } - - iter = std::min_element( - m_regInfos.begin(), m_regInfos.end(), - [](typename RegInfosList::const_reference elem1, - typename RegInfosList::const_reference elem2) -> bool - { - if (elem1.m_locked == elem2.m_locked) { - return (elem1.m_timestamp < elem2.m_timestamp); - } - - if (elem1.m_locked) { - return false; - } - - return true; - }); - - COMMS_ASSERT(iter != m_regInfos.end()); - *iter = info; - } - - void dropRegInfo(typename RegInfosList::iterator iter) - { - *iter = RegInfo(); - } - - template - TOp* opPtr() - { - return reinterpret_cast(&m_opStorage); - } - - template - TOp* newOp() - { - static_assert(sizeof(TOp) <= sizeof(m_opStorage), "Invalid storage size"); - auto op = new (&m_opStorage) TOp(); - op->m_lastMsgTimestamp = m_timestamp; - return op; - } - - template - TOp* newAsyncOp(CC_MqttsnAsyncOpCompleteReportFn cb, void* cbData) - { - static_assert(std::is_base_of::value, "Invalid base"); - - auto op = newOp(); - op->m_cb = cb; - op->m_cbData = cbData; - return op; - } - - typename GwInfoStorage::iterator findGwInfo(GwIdValueType id) - { - return - std::find_if( - m_gwInfos.begin(), m_gwInfos.end(), - [id](typename GwInfoStorage::const_reference elem) -> bool - { - return id == elem.m_id; - }); - } - - bool updateTimestamp() - { - if (!isTimerActive()) { - return false; - } - - COMMS_ASSERT(m_cancelNextTickWaitFn != nullptr); - m_timestamp += m_cancelNextTickWaitFn(m_cancelNextTickWaitData); - m_tickDelay = 0U; - checkTimeouts(); - return true; - } - - unsigned calcGwReleaseTimeout() const - { - if ((m_gwInfos.empty()) || (m_connectionStatus == ConnectionStatus::Asleep)) { - return NoTimeout; - } - - auto iter = std::min_element( - m_gwInfos.begin(), m_gwInfos.end(), - [](typename GwInfoStorage::const_reference elem1, - typename GwInfoStorage::const_reference elem2) -> bool - { - return (elem1.m_timestamp + elem1.m_duration) < - (elem2.m_timestamp + elem2.m_duration); - }); - - COMMS_ASSERT(iter != m_gwInfos.end()); - auto finalTimestamp = iter->m_timestamp + iter->m_duration; - if (finalTimestamp < m_timestamp) { - constexpr bool Gateways_are_not_cleaned_up_properly = false; - static_cast(Gateways_are_not_cleaned_up_properly); - COMMS_ASSERT(Gateways_are_not_cleaned_up_properly); - return 0U; - } - - return static_cast(finalTimestamp - m_timestamp); - } - - unsigned calcSearchGwSendTimeout() - { - if ((!m_searchgwEnabled) || - (!m_gwInfos.empty()) || - (m_connectionStatus == ConnectionStatus::Asleep)) { - return NoTimeout; - } - - if (m_lastGwSearchTimestamp == 0) { - return 0U; - } - - auto nextSearchTimestamp = m_lastGwSearchTimestamp + m_retryPeriod; - if (nextSearchTimestamp <= m_timestamp) { - return 0U; - } - - return static_cast(nextSearchTimestamp - m_timestamp); - } - - unsigned calcCurrentOpTimeout() - { - if (m_currOp == Op::None) { - return NoTimeout; - } - - auto* op = opPtr(); - auto nextOpTimestamp = op->m_lastMsgTimestamp + m_retryPeriod; - if (nextOpTimestamp <= m_timestamp) { - return 1U; - } - - return static_cast(nextOpTimestamp - m_timestamp); - } - - unsigned calcPingTimeout() - { - if (m_connectionStatus != ConnectionStatus::Connected) { - return NoTimeout; - } - - auto pingTimestamp = m_lastPingTimestamp + m_retryPeriod; - if (m_pingCount == 0) { - pingTimestamp = - std::min(m_lastSentMsgTimestamp, m_lastRecvMsgTimestamp) + m_keepAlivePeriod; - } - - if (pingTimestamp <= m_timestamp) { - return 1U; - } - - return static_cast(pingTimestamp - m_timestamp); - } - - void programNextTimeout() - { - if (!m_running) { - return; - } - - unsigned delay = NoTimeout; - delay = std::min(delay, calcGwReleaseTimeout()); - delay = std::min(delay, std::max(1U, calcSearchGwSendTimeout())); - delay = std::min(delay, calcCurrentOpTimeout()); - delay = std::min(delay, calcPingTimeout()); - - if (delay == NoTimeout) { - return; - } - - COMMS_ASSERT(m_nextTickProgramFn != nullptr); - m_nextTickProgramFn(m_nextTickProgramData, delay); - m_nextTimeoutTimestamp = m_timestamp + delay; - m_tickDelay = delay; - } - - void checkAvailableGateways() - { - auto checkMustRemoveFunc = - [this](typename GwInfoStorage::const_reference elem) -> bool - { - COMMS_ASSERT(elem.m_duration != 0U); - return ((elem.m_timestamp + elem.m_duration) <= m_timestamp); - }; - - typedef details::GwInfoStorageTypeT GwIdStorage; - GwIdStorage idsToRemove; - - for (auto& info : m_gwInfos) { - if (checkMustRemoveFunc(info)) { - idsToRemove.push_back(info.m_id); - } - } - - if (idsToRemove.empty()) { - return; - } - - m_gwInfos.erase( - std::remove_if( - m_gwInfos.begin(), m_gwInfos.end(), - checkMustRemoveFunc), - m_gwInfos.end()); - - for (auto id : idsToRemove) { - reportGwStatus(id, CC_MqttsnGwStatus_TimedOut); - } - } - - void checkGwSearchReq() - { - if (calcSearchGwSendTimeout() == 0) { - sendGwSearchReq(); - } - } - - void checkPing() - { - if (m_connectionStatus != ConnectionStatus::Connected) { - return; - } - - if (m_pingCount == 0) { - // first ping; - COMMS_ASSERT(m_lastSentMsgTimestamp != 0); - COMMS_ASSERT(m_lastRecvMsgTimestamp != 0); - - bool needsToSendPing = - ((m_lastSentMsgTimestamp + m_keepAlivePeriod) <= m_timestamp) || - ((m_lastRecvMsgTimestamp + m_keepAlivePeriod) <= m_timestamp); - - if (needsToSendPing) { - sendPing(); - } - - return; - } - - COMMS_ASSERT(0U < m_lastPingTimestamp); - if (m_timestamp < (m_lastPingTimestamp + m_retryPeriod)) { - return; - } - - if (m_retryCount <= m_pingCount) { - reportGwDisconnected(); - return; - } - - sendPing(); - } - - void checkOpTimeout() - { - if (m_currOp == Op::None) { - return; - } - - auto* op = opPtr(); - if (m_timestamp < (op->m_lastMsgTimestamp + m_retryPeriod)) { - return; - } - - typedef bool (BasicClient::*DoOpFunc)(); - static const DoOpFunc OpTimeoutFuncMap[] = - { - &BasicClient::doConnect, - &BasicClient::doDisconnect, - &BasicClient::doPublishId, - &BasicClient::doPublish, - &BasicClient::doSubscribeId, - &BasicClient::doSubscribe, - &BasicClient::doUnsubscribeId, - &BasicClient::doUnsubscribe, - &BasicClient::doWillTopicUpdate, - &BasicClient::doWillMsgUpdate, - &BasicClient::doSleep, - &BasicClient::doCheckMessages - }; - static const std::size_t OpTimeoutFuncMapSize = - std::extent::value; - - static_assert(OpTimeoutFuncMapSize == (static_cast(Op::NumOfValues) - 1U), - "Map above is incorrect"); - - COMMS_ASSERT((static_cast(m_currOp) - 1) < OpTimeoutFuncMapSize); - auto fn = OpTimeoutFuncMap[static_cast(m_currOp) - 1]; - - COMMS_ASSERT(fn != nullptr); - if ((this->*(fn))()) { - op->m_lastMsgTimestamp = m_timestamp; - return; - } - - auto finaliseFn = getFinaliseFunc(); - COMMS_ASSERT(finaliseFn != nullptr); - (this->*finaliseFn)(CC_MqttsnAsyncOpStatus_NoResponse); - } - - void checkTimeouts() - { - if (m_timestamp < m_nextTimeoutTimestamp) { - return; - } - - checkAvailableGateways(); - checkGwSearchReq(); - checkPing(); - checkOpTimeout(); - } - - bool addNewGw(GwIdValueType id, unsigned duration) - { - if (m_gwInfos.max_size() <= m_gwInfos.size()) { - return false; - } - - m_gwInfos.emplace_back(); - auto& newElem = m_gwInfos.back(); - newElem.m_timestamp = m_timestamp; - newElem.m_id = id; - newElem.m_duration = duration; - return true; - } - - void reportGwStatus(GwIdValueType id, CC_MqttsnGwStatus status) - { - if (m_gwStatusReportFn != nullptr) { - m_gwStatusReportFn(m_gwStatusReportData, id, status); - } - } - - void sendGwSearchReq() - { - SearchgwMsg msg; - msg.field_radius().value() = m_broadcastRadius; - sendMessage(msg, true); - m_lastGwSearchTimestamp = m_timestamp; - } - - void sendMessage(const Message& msg, bool broadcast = false) - { - if (m_sendOutputDataFn == nullptr) { - constexpr bool Unexpected_send = false; - static_cast(Unexpected_send); - COMMS_ASSERT(Unexpected_send); - return; - } - - m_writeBuf.resize(std::max(m_writeBuf.size(), m_stack.length(msg))); - COMMS_ASSERT(!m_writeBuf.empty()); - auto writeIter = comms::writeIteratorFor(&m_writeBuf[0]); - auto es = m_stack.write(msg, writeIter, m_writeBuf.size()); - COMMS_ASSERT(es == comms::ErrorStatus::Success); - if (es != comms::ErrorStatus::Success) { - // Buffer is too small - return; - } - - auto writtenBytes = static_cast( - std::distance(comms::writeIteratorFor(&m_writeBuf[0]), writeIter)); - - m_lastSentMsgTimestamp = m_timestamp; - m_sendOutputDataFn(m_sendOutputDataData, &m_writeBuf[0], writtenBytes, broadcast); - } - - template - void finaliseOp() - { - auto* op = opPtr(); - op->~TOp(); - m_currOp = Op::None; - } - - bool doConnect() - { - COMMS_ASSERT (m_currOp == Op::Connect); - - auto* op = opPtr(); - if (m_retryCount <= op->m_attempt) { - return false; - } - - ++op->m_attempt; - sendConnect(op->m_clientId, op->m_keepAlivePeriod, op->m_cleanSession, op->m_hasWill); - return true; - } - - bool doDisconnect() - { - COMMS_ASSERT (m_currOp == Op::Disconnect); - - auto* op = opPtr(); - if (m_retryCount <= op->m_attempt) { - return false; - } - - ++op->m_attempt; - - DisconnectMsg msg; - msg.field_duration().setMissing(); - sendMessage(msg); - return true; - } - - bool doPublishId() - { - COMMS_ASSERT (m_currOp == Op::PublishId); - - auto* op = opPtr(); - if (m_retryCount <= op->m_attempt) { - return false; - } - - bool firstAttempt = (op->m_attempt == 0U); - ++op->m_attempt; - - if (firstAttempt && (CC_MqttsnQoS_AtLeastOnceDelivery <= op->m_qos)) { - op->m_msgId = allocMsgId(); - } - - if ((!firstAttempt) && - (op->m_ackReceived) && - (CC_MqttsnQoS_ExactlyOnceDelivery <= op->m_qos)) { - sendPubrel(op->m_msgId); - return true; - } - - sendPublish( - op->m_topicId, - op->m_msgId, - op->m_msg, - op->m_msgLen, - TopicIdTypeVal::PredefinedTopicId, - details::translateQosValue(op->m_qos), - op->m_retain, - !firstAttempt); - - if (op->m_qos <= CC_MqttsnQoS_AtMostOnceDelivery) { - finalisePublishOp(CC_MqttsnAsyncOpStatus_Successful); - } - - return true; - } - - bool doPublish() - { - COMMS_ASSERT (m_currOp == Op::Publish); - - auto* op = opPtr(); - if (m_retryCount <= op->m_attempt) { - return false; - } - - bool firstAttempt = (op->m_attempt == 0U); - - ++op->m_attempt; - - do { - if (op->m_registered || op->m_shortName) { - break; - } - - auto iter = std::find_if( - m_regInfos.begin(), m_regInfos.end(), - [op](typename RegInfosList::const_reference elem) -> bool - { - return elem.m_allocated && (elem.m_topic == op->m_topic); - }); - - if (iter != m_regInfos.end()) { - op->m_registered = true; - op->m_topicId = iter->m_topicId; - iter->m_timestamp = m_timestamp; - break; - } - - op->m_didRegistration = true; - op->m_msgId = allocMsgId(); - sendRegister(op->m_msgId, op->m_topic); - return true; - } while (false); - - op->m_msgId = 0U; - if (firstAttempt && (CC_MqttsnQoS_AtLeastOnceDelivery <= op->m_qos)) { - op->m_msgId = allocMsgId(); - } - - COMMS_ASSERT((op->m_registered) || (op->m_shortName)); - - if ((!firstAttempt) && - (op->m_ackReceived) && - (CC_MqttsnQoS_ExactlyOnceDelivery <= op->m_qos)) { - sendPubrel(op->m_msgId); - return true; - } - - auto topicIdType = TopicIdTypeVal::Normal; - if (op->m_shortName) { - topicIdType = TopicIdTypeVal::ShortTopicName; - } - - sendPublish( - op->m_topicId, - op->m_msgId, - op->m_msg, - op->m_msgLen, - topicIdType, - details::translateQosValue(op->m_qos), - op->m_retain, - !firstAttempt); - - if (op->m_qos <= CC_MqttsnQoS_AtMostOnceDelivery) { - finalisePublishOp(CC_MqttsnAsyncOpStatus_Successful); - } - - return true; - } - - bool doSubscribeId() - { - COMMS_ASSERT (m_currOp == Op::SubscribeId); - - auto* op = opPtr(); - if (m_retryCount <= op->m_attempt) { - return false; - } - - bool firstAttempt = (op->m_attempt == 0U); - ++op->m_attempt; - - SubscribeMsg msg; - msg.field_flags().field_topicIdType().value() = TopicIdTypeVal::PredefinedTopicId; - msg.field_flags().field_qos().value() = details::translateQosValue(op->m_qos); - msg.field_flags().field_high().setBitValue_Dup(!firstAttempt); - msg.field_msgId().value() = op->m_msgId; - msg.field_topicId().field().value() = op->m_topicId; - msg.doRefresh(); - COMMS_ASSERT(msg.field_topicId().doesExist()); - sendMessage(msg); - return true; - } - - bool doSubscribe() - { - COMMS_ASSERT (m_currOp == Op::Subscribe); - - auto* op = opPtr(); - if (m_retryCount <= op->m_attempt) { - return false; - } - - bool firstAttempt = (op->m_attempt == 0U); - ++op->m_attempt; -\ - COMMS_ASSERT((op->m_topicId == 0) || (op->m_usingShortTopicName)); - - SubscribeMsg msg; - if (op->m_topicId == 0U) { - msg.field_flags().field_topicIdType().value() = TopicIdTypeVal::Normal; - msg.field_topicName().field().value() = op->m_topic; - } - else { - msg.field_flags().field_topicIdType().value() = TopicIdTypeVal::ShortTopicName; - msg.field_topicId().field().value() = op->m_topicId; - } - - msg.field_flags().field_qos().value() = details::translateQosValue(op->m_qos); - msg.field_flags().field_high().setBitValue_Dup(!firstAttempt); - msg.field_msgId().value() = op->m_msgId; - msg.doRefresh(); - COMMS_ASSERT((op->m_topicId != 0U) || (msg.field_topicName().doesExist())); - COMMS_ASSERT((op->m_topicId != 0U) || (msg.field_topicId().isMissing())); - COMMS_ASSERT((op->m_topicId == 0U) || (msg.field_topicName().isMissing())); - COMMS_ASSERT((op->m_topicId == 0U) || (msg.field_topicId().doesExist())); - - sendMessage(msg); - return true; - } - - bool doUnsubscribeId() - { - COMMS_ASSERT (m_currOp == Op::UnsubscribeId); - - auto* op = opPtr(); - if (m_retryCount <= op->m_attempt) { - return false; - } - - ++op->m_attempt; - - UnsubscribeMsg msg; - msg.field_flags().field_topicIdType().value() = TopicIdTypeVal::PredefinedTopicId; - msg.field_msgId().value() = op->m_msgId; - msg.field_topicId().field().value() = op->m_topicId; - msg.doRefresh(); - COMMS_ASSERT(msg.field_topicId().doesExist()); - sendMessage(msg); - return true; - } - - bool doUnsubscribe() - { - COMMS_ASSERT (m_currOp == Op::Unsubscribe); - - auto* op = opPtr(); - if (m_retryCount <= op->m_attempt) { - return false; - } - - ++op->m_attempt; - COMMS_ASSERT((op->m_topicId == 0) || (op->m_usingShortTopicName)); - - UnsubscribeMsg msg; - - if (op->m_topicId == 0U) { - msg.field_flags().field_topicIdType().value() = TopicIdTypeVal::Normal; - msg.field_topicName().field().value() = op->m_topic; - } - else { - msg.field_flags().field_topicIdType().value() = TopicIdTypeVal::ShortTopicName; - msg.field_topicId().field().value() = op->m_topicId; - } - - msg.field_msgId().value() = op->m_msgId; - msg.doRefresh(); - COMMS_ASSERT((op->m_topicId != 0U) || (msg.field_topicName().doesExist())); - COMMS_ASSERT((op->m_topicId != 0U) || (msg.field_topicId().isMissing())); - COMMS_ASSERT((op->m_topicId == 0U) || (msg.field_topicName().isMissing())); - COMMS_ASSERT((op->m_topicId == 0U) || (msg.field_topicId().doesExist())); - - sendMessage(msg); - return true; - } - - bool doWillTopicUpdate() - { - COMMS_ASSERT (m_currOp == Op::WillTopicUpdate); - - auto* op = opPtr(); - if (m_retryCount <= op->m_attempt) { - return false; - } - - ++op->m_attempt; - WilltopicupdMsg msg; - bool topicEmpty = ((op->m_topic == nullptr) || (op->m_topic[0] == '\0')); - if (!topicEmpty) { - msg.field_flags().field().field_qos().value() = details::translateQosValue(op->m_qos); - msg.field_flags().field().field_mid().setBitValue_Retain(op->m_retain); - msg.field_willTopic().value() = op->m_topic; - } - - msg.doRefresh(); - COMMS_ASSERT(topicEmpty || msg.field_flags().doesExist()); - COMMS_ASSERT((!topicEmpty) || msg.field_flags().isMissing()); - - sendMessage(msg); - return true; - } - - bool doWillMsgUpdate() - { - COMMS_ASSERT (m_currOp == Op::WillMsgUpdate); - - auto* op = opPtr(); - if (m_retryCount <= op->m_attempt) { - return false; - } - - ++op->m_attempt; - - WillmsgupdMsg msg; - auto* msgBodyBeg = op->m_msg; - if (msgBodyBeg != nullptr) { - auto& dataStorage = msg.field_willMsg().value(); - comms::util::assign(dataStorage, msgBodyBeg, msgBodyBeg + op->m_msgLen); - } - sendMessage(msg); - return true; - } - - bool doSleep() - { - COMMS_ASSERT (m_currOp == Op::Sleep); - - auto* op = opPtr(); - if (m_retryCount <= op->m_attempt) { - return false; - } - - ++op->m_attempt; - - DisconnectMsg msg; - msg.field_duration().setExists(); - msg.field_duration().field().value() = op->m_duration; - sendMessage(msg); - return true; - } - - bool doCheckMessages() - { - COMMS_ASSERT (m_currOp == Op::CheckMessages); - - auto* op = opPtr(); - if (m_retryCount <= op->m_attempt) { - return false; - } - - ++op->m_attempt; - - PingreqMsg msg; - auto& clientIdStorage = msg.field_clientId().value(); - using ClientIdStorage = typename std::decay::type; - clientIdStorage = ClientIdStorage(m_clientId.c_str(), m_clientId.size()); - sendMessage(msg); - return true; - } - - void sendConnect( - const char* clientId, - std::uint16_t keepAlivePeriod, - bool cleanSession, - bool hasWill) - { - ConnectMsg msg; - msg.field_flags().field_mid().setBitValue_CleanSession(cleanSession); - msg.field_flags().field_mid().setBitValue_Will(hasWill); - msg.field_duration().value() = keepAlivePeriod; - if (clientId != nullptr) { - msg.field_clientId().value() = clientId; - } - sendMessage(msg); - } - - void sendWilltopic( - const char* topic, - CC_MqttsnQoS qos, - bool retain) - { - WilltopicMsg msg; - bool topicEmpty = ((topic == nullptr) || (topic[0] == '\0')); - if (!topicEmpty) { - msg.field_flags().field().field_qos().value() = details::translateQosValue(qos); - msg.field_flags().field().field_mid().setBitValue_Retain(retain); - msg.field_willTopic().value() = topic; - } - - msg.doRefresh(); - COMMS_ASSERT(topicEmpty || msg.field_flags().doesExist()); - COMMS_ASSERT((!topicEmpty) || msg.field_flags().isMissing()); - sendMessage(msg); - } - - void sendPing() - { - ++m_pingCount; - m_lastPingTimestamp = m_timestamp; - PingreqMsg msg; - sendMessage(msg); - } - - void sendPublish( - CC_MqttsnTopicId topicId, - std::uint16_t msgId, - const std::uint8_t* msg, - std::size_t msgLen, - TopicIdTypeVal topicIdType, - cc_mqttsn::field::QosVal qos, - bool retain, - bool duplicate) - { - PublishMsg pubMsg; - pubMsg.field_flags().field_topicIdType().value() = topicIdType; - pubMsg.field_flags().field_mid().setBitValue_Retain(retain); - pubMsg.field_flags().field_qos().value() = qos; - pubMsg.field_flags().field_high().setBitValue_Dup(duplicate); - pubMsg.field_topicId().value() = topicId; - pubMsg.field_msgId().value() = msgId; - - auto& dataStorage = pubMsg.field_data().value(); - comms::util::assign(dataStorage, msg, msg + msgLen); - sendMessage(pubMsg); - } - - void sendPuback( - CC_MqttsnTopicId topicId, - std::uint16_t msgId, - ReturnCodeVal retCode) - { - PubackMsg msg; - msg.field_topicId().value() = topicId; - msg.field_msgId().value() = msgId; - msg.field_returnCode().value() = retCode; - sendMessage(msg); - } - - void sendRegister( - std::uint16_t msgId, - const char* topic) - { - RegisterMsg msg; - msg.field_msgId().value() = msgId; - msg.field_topicName().value() = topic; - sendMessage(msg); - } - - void sendPubrel(std::uint16_t msgId) - { - PubrelMsg msg; - msg.field_msgId().value() = msgId; - sendMessage(msg); - } - - void reportGwDisconnected() - { - m_connectionStatus = ConnectionStatus::Disconnected; - - if (m_gwDisconnectReportFn != nullptr) { - m_gwDisconnectReportFn(m_gwDisconnectReportData); - } - } - - std::uint16_t allocMsgId() - { - ++m_msgId; - return m_msgId; - } - - template - void finaliseAsyncOp(CC_MqttsnAsyncOpStatus status) - { - COMMS_ASSERT(m_currOp == TCurr); - - static_assert(std::is_base_of::value, "Invalid base"); - auto* op = opPtr(); - auto* cb = op->m_cb; - auto* cbData = op->m_cbData; - - finaliseOp(); - COMMS_ASSERT(m_currOp == Op::None); - COMMS_ASSERT(cb != nullptr); - cb(cbData, status); - } - - template - void finaliseAsyncDoubleOp(CC_MqttsnAsyncOpStatus status) - { - COMMS_ASSERT((m_currOp == TCurr1) || (m_currOp == TCurr2)); - - static_assert(std::is_base_of::value, "Invalid base"); - static_assert(std::is_base_of::value, "Invalid base"); - auto* op = opPtr(); - auto* cb = op->m_cb; - auto* cbData = op->m_cbData; - - if (m_currOp == TCurr1) { - finaliseOp(); - } - else { - finaliseOp(); - } - - COMMS_ASSERT(m_currOp == Op::None); - COMMS_ASSERT(cb != nullptr); - cb(cbData, status); - } - - void finaliseConnectOp(CC_MqttsnAsyncOpStatus status) - { - finaliseAsyncOp(status); - } - - void finaliseDisconnectOp(CC_MqttsnAsyncOpStatus status) - { - finaliseAsyncOp(status); - } - - void finalisePublishOp(CC_MqttsnAsyncOpStatus status) - { - finaliseAsyncDoubleOp(status); - } - - void finaliseSubscribeOp(CC_MqttsnAsyncOpStatus status) - { - COMMS_ASSERT((m_currOp == Op::Subscribe) || (m_currOp == Op::SubscribeId)); - auto* op = opPtr(); - auto* cb = op->m_cb; - auto* cbData = op->m_cbData; - - if (m_currOp == Op::Subscribe) { - finaliseOp(); - } - else { - finaliseOp(); - } - COMMS_ASSERT(m_currOp == Op::None); - COMMS_ASSERT(cb != nullptr); - cb(cbData, status, op->m_qos); - } - - void finaliseUnsubscribeOp(CC_MqttsnAsyncOpStatus status) - { - finaliseAsyncDoubleOp(status); - } - - void finaliseWillTopicUpdateOp(CC_MqttsnAsyncOpStatus status) - { - finaliseAsyncOp(status); - } - - void finaliseWillMsgUpdateOp(CC_MqttsnAsyncOpStatus status) - { - finaliseAsyncOp(status); - } - - void finaliseSleepOp(CC_MqttsnAsyncOpStatus status) - { - finaliseAsyncOp(status); - } - - void finaliseCheckMessagesOp(CC_MqttsnAsyncOpStatus status) - { - finaliseAsyncOp(status); - } - - FinaliseFunc getFinaliseFunc() const - { - static const FinaliseFunc Map[] = - { - &BasicClient::finaliseConnectOp, - &BasicClient::finaliseDisconnectOp, - &BasicClient::finalisePublishOp, - &BasicClient::finalisePublishOp, - &BasicClient::finaliseSubscribeOp, - &BasicClient::finaliseSubscribeOp, - &BasicClient::finaliseUnsubscribeOp, - &BasicClient::finaliseUnsubscribeOp, - &BasicClient::finaliseWillTopicUpdateOp, - &BasicClient::finaliseWillMsgUpdateOp, - &BasicClient::finaliseSleepOp, - &BasicClient::finaliseCheckMessagesOp, - }; - static const std::size_t MapSize = - std::extent::value; - - static_assert(MapSize == (static_cast(Op::NumOfValues) - 1U), - "Map above is incorrect"); - - auto opIdx = static_cast(m_currOp) - 1; - COMMS_ASSERT(opIdx < MapSize); - return Map[opIdx]; - } - - CC_MqttsnAsyncOpStatus retCodeToStatus(ReturnCodeVal val) - { - static const CC_MqttsnAsyncOpStatus Map[] = { - /* ReturnCodeVal_Accepted */ CC_MqttsnAsyncOpStatus_Successful, - /* ReturnCodeVal_Congestion */ CC_MqttsnAsyncOpStatus_Congestion, - /* ReturnCodeVal_InvalidTopicId */ CC_MqttsnAsyncOpStatus_InvalidId, - /* ReturnCodeVal_NotSupported */ CC_MqttsnAsyncOpStatus_NotSupported - }; - - static const std::size_t MapSize = std::extent::value; - - static_assert(MapSize == static_cast(ReturnCodeVal::ValuesLimit), - "Map is incorrect"); - - CC_MqttsnAsyncOpStatus status = CC_MqttsnAsyncOpStatus_NotSupported; - if (static_cast(val) < MapSize) { - status = Map[static_cast(val)]; - } - - return status; - } - - void apiCallExit() - { - COMMS_ASSERT(0U < m_callStackCount); - --m_callStackCount; - if (m_callStackCount == 0U) { - programNextTimeout(); - } - } - -#ifdef _MSC_VER - // VC compiler - auto apiCall() -#else - auto apiCall() -> decltype(comms::util::makeScopeGuard(std::bind(&BasicClient::apiCallExit, this))) -#endif - { - ++m_callStackCount; - if (m_callStackCount == 1U) { - updateTimestamp(); - } - - return - comms::util::makeScopeGuard( - std::bind( - &BasicClient::apiCallExit, - this)); - } - - bool isTimerActive() const - { - return (m_tickDelay != 0U); - } - - static bool isShortTopicName(const char* topic) - { - COMMS_ASSERT(topic != nullptr); - auto checkCharFunc = - [](char ch) - { - return - (ch != '\0') && - (ch != '+') && - (ch != '#'); - }; - - if ((!checkCharFunc(topic[0])) || - (!checkCharFunc(topic[1]))) { - return false; - } - - return topic[2] == '\0'; - } - - static CC_MqttsnTopicId shortTopicToTopicId(const char* topic) - { - COMMS_ASSERT(topic[0] != '\0'); - COMMS_ASSERT(topic[1] != '\0'); - - return - static_cast( - (static_cast(topic[0]) << 8) | static_cast(topic[1])); - } - - static void topicIdToShortTopic(CC_MqttsnTopicId topicId, char* topicOut) - { - topicOut[0] = static_cast((topicId >> 8) & 0xff); - topicOut[1] = static_cast(topicId & 0xff); - topicOut[2] = '\0'; - } - - - ProtStack m_stack; - GwInfoStorage m_gwInfos; - Timestamp m_timestamp = DefaultStartTimestamp; - Timestamp m_nextTimeoutTimestamp = 0; - Timestamp m_lastGwSearchTimestamp = 0; - Timestamp m_lastRecvMsgTimestamp = 0; - Timestamp m_lastSentMsgTimestamp = 0; - Timestamp m_lastPingTimestamp = 0; - ClientIdType m_clientId; - - unsigned m_callStackCount = 0U; - unsigned m_pingCount = 0; - unsigned m_retryPeriod = DefaultRetryPeriod; - unsigned m_retryCount = DefaultRetryCount; - unsigned m_keepAlivePeriod = 0; - ConnectionStatus m_connectionStatus = ConnectionStatus::Disconnected; - std::uint16_t m_msgId = 0; - std::uint8_t m_broadcastRadius = DefaultBroadcastRadius; - - unsigned m_tickDelay = 0U; - bool m_running = false; - bool m_searchgwEnabled = true; - - Op m_currOp = Op::None; - OpStorageType m_opStorage; - - RegInfosList m_regInfos; - - LastInMsgInfo m_lastInMsg; - - CC_MqttsnNextTickProgramFn m_nextTickProgramFn = nullptr; - void* m_nextTickProgramData = nullptr; - - CC_MqttsnCancelNextTickWaitFn m_cancelNextTickWaitFn = nullptr; - void* m_cancelNextTickWaitData = nullptr; - - CC_MqttsnSendOutputDataFn m_sendOutputDataFn = nullptr; - void* m_sendOutputDataData = nullptr; - - CC_MqttsnGwStatusReportFn m_gwStatusReportFn = nullptr; - void* m_gwStatusReportData = nullptr; - - CC_MqttsnGwDisconnectReportFn m_gwDisconnectReportFn = nullptr; - void* m_gwDisconnectReportData = nullptr; - - CC_MqttsnMessageReportFn m_msgReportFn = nullptr; - void* m_msgReportData = nullptr; - - WriteBufStorage m_writeBuf; - - static const unsigned DefaultAdvertisePeriod = 30 * 60 * 1000; - static const unsigned DefaultRetryPeriod = 15 * 1000; - static const unsigned DefaultRetryCount = 3; - static const std::uint8_t DefaultBroadcastRadius = 0U; - - static const unsigned NoTimeout = std::numeric_limits::max(); - static const Timestamp DefaultStartTimestamp = 100; -}; - -} // namespace cc_mqttsn_client - - diff --git a/client.old/src/basic/CMakeLists.txt b/client.old/src/basic/CMakeLists.txt deleted file mode 100644 index 08330de4..00000000 --- a/client.old/src/basic/CMakeLists.txt +++ /dev/null @@ -1,98 +0,0 @@ -macro (mqttsn_append_option list opt val) - if (NOT "${val}" STREQUAL "") - if (NOT "${${list}}" STREQUAL "") - set (${list} "${${list}},") - endif () - - set (${list} "${${list}}${opt}<${val}>") - endif () -endmacro () - -###################################################################### - -include_directories( - ${CMAKE_CURRENT_SOURCE_DIR} -) - -if ((CC_MQTTSN_CLIENT_DEFAULT_LIB) AND - ("${CC_MQTTSN_DEFAULT_CLIENT_CONFIG_FILE}" STREQUAL "")) - gen_lib_mqttsn_client("${DEFAULT_CLIENT_NAME}" "" TRUE "") -endif () - -if (NOT "${CC_MQTTSN_DEFAULT_CLIENT_CONFIG_FILE}" STREQUAL "") - set (CC_MQTTSN_CUSTOM_CLIENT_CONFIG_FILES - ${CC_MQTTSN_DEFAULT_CLIENT_CONFIG_FILE} - ${CC_MQTTSN_CUSTOM_CLIENT_CONFIG_FILES}) -endif () - -foreach (f ${CC_MQTTSN_CUSTOM_CLIENT_CONFIG_FILES}) - if (NOT EXISTS "${f}") - message (WARNING "File ${f} does not exist") - continue() - endif () - - include (${f}) - - if ("${f}" STREQUAL "${CC_MQTTSN_DEFAULT_CLIENT_CONFIG_FILE}") - set (CC_MQTTSN_CUSTOM_CLIENT_NAME "${DEFAULT_CLIENT_NAME}") - elseif ("${CC_MQTTSN_CUSTOM_CLIENT_NAME}" STREQUAL "") - message (WARNING "File ${f} does not specify client name!") - continue () - endif () - - set (mqttsn_library_opts) - - mqttsn_append_option ( - mqttsn_library_opts - "cc_mqttsn_client::option::ClientIdStaticStorageSize" - "${CC_MQTTSN_CUSTOM_CLIENT_ID_STATIC_STORAGE_SIZE}" - ) - - mqttsn_append_option ( - mqttsn_library_opts - "cc_mqttsn_client::option::GwAddStaticStorageSize" - "${CC_MQTTSN_CUSTOM_CLIENT_GW_ADDR_STATIC_STORAGE_SIZE}" - ) - - mqttsn_append_option ( - mqttsn_library_opts - "cc_mqttsn_client::option::TopicNameStaticStorageSize" - "${CC_MQTTSN_CUSTOM_CLIENT_TOPIC_NAME_STATIC_STORAGE_SIZE}" - ) - - mqttsn_append_option ( - mqttsn_library_opts - "cc_mqttsn_client::option::MessageDataStaticStorageSize" - "${CC_MQTTSN_CUSTOM_CLIENT_MSG_DATA_STATIC_STORAGE_SIZE}" - ) - - mqttsn_append_option ( - mqttsn_library_opts - "cc_mqttsn_client::option::ClientsAllocLimit" - "${CC_MQTTSN_CUSTOM_CLIENT_ALLOC_LIMIT}" - ) - - mqttsn_append_option ( - mqttsn_library_opts - "cc_mqttsn_client::option::TrackedGatewaysLimit" - "${CC_MQTTSN_CUSTOM_CLIENT_TRACKED_GW_LIMIT}" - ) - - mqttsn_append_option ( - mqttsn_library_opts - "cc_mqttsn_client::option::RegisteredTopicsLimit" - "${CC_MQTTSN_CUSTOM_CLIENT_REGISTERED_TOPICS_LIMIT}" - ) - - set (extra_flags) - if (CMAKE_COMPILER_IS_GNUCC AND CC_MQTTSN_CUSTOM_CLIENT_NO_STDLIB) - set (extra_flags "-nostdlib") - endif () - - - gen_lib_mqttsn_client("${CC_MQTTSN_CUSTOM_CLIENT_NAME}" "${mqttsn_library_opts}" TRUE "${extra_flags}") - - if (CC_MQTTSN_CUSTOM_CLIENT_NO_STDLIB) - target_compile_definitions("cc_mqttsn_${CC_MQTTSN_CUSTOM_CLIENT_NAME}_client" PRIVATE "-DNOSTDLIB") - endif () -endforeach() diff --git a/client.old/src/basic/ClientMgr.h b/client.old/src/basic/ClientMgr.h deleted file mode 100644 index 5b505c15..00000000 --- a/client.old/src/basic/ClientMgr.h +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#pragma once - -#include "comms/comms.h" - -namespace cc_mqttsn_client -{ - -namespace details -{ - -template -struct ClientAllocatorType; - -template -struct ClientAllocatorType -{ - typedef comms::util::alloc::InPlacePool Type; -}; - -template -struct ClientAllocatorType -{ - typedef comms::util::alloc::DynMemory Type; -}; - -template -using ClientAllocatorTypeT = - typename ClientAllocatorType::Type; - - -} // namespace details - -template -class ClientMgr -{ - typedef details::ClientAllocatorTypeT Alloc; -public: - typedef TClient Client; - - typedef typename Alloc::Ptr ClientPtr; - - ClientPtr alloc() - { - return m_alloc.template alloc(); - } - - void free(Client* client) { - auto ptr = m_alloc.wrap(client); - static_cast(ptr); - } - -private: - Alloc m_alloc; -}; - -} // namespace cc_mqttsn_client - - diff --git a/client.old/src/basic/ParsedOptions.h b/client.old/src/basic/ParsedOptions.h deleted file mode 100644 index 3f393b17..00000000 --- a/client.old/src/basic/ParsedOptions.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include "details/OptionsParser.h" - -#pragma once - -namespace cc_mqttsn_client -{ - -template -using ParsedOptions = details::OptionsParser; - -} // namespace cc_mqttsn_client - - diff --git a/client.old/src/basic/details/OptionsParser.h b/client.old/src/basic/details/OptionsParser.h deleted file mode 100644 index d56eb2bc..00000000 --- a/client.old/src/basic/details/OptionsParser.h +++ /dev/null @@ -1,123 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#pragma once - -#include "option.h" - -namespace cc_mqttsn_client -{ - -namespace details -{ - -template -class OptionsParser; - -template <> -class OptionsParser<> -{ -public: - static const bool HasClientsAllocLimit = false; - static const bool HasTrackedGatewaysLimit = false; - static const bool HasRegisteredTopicsLimit = false; - static const bool HasGwAddStaticStorageSize = false; - static const bool HasClientIdStaticStorageSize = false; - static const bool HasTopicNameStaticStorageSize = false; - static const bool HasMessageDataStaticStorageSize = false; -}; - -template -class OptionsParser< - cc_mqttsn_client::option::ClientsAllocLimit, - TOptions...> : public OptionsParser -{ - typedef cc_mqttsn_client::option::ClientsAllocLimit Option; -public: - static const bool HasClientsAllocLimit = true; - static const std::size_t ClientsAllocLimit = Option::Value; -}; - -template -class OptionsParser< - cc_mqttsn_client::option::TrackedGatewaysLimit, - TOptions...> : public OptionsParser -{ - typedef cc_mqttsn_client::option::TrackedGatewaysLimit Option; -public: - static const bool HasTrackedGatewaysLimit = true; - static const std::size_t TrackedGatewaysLimit = Option::Value; -}; - -template -class OptionsParser< - cc_mqttsn_client::option::RegisteredTopicsLimit, - TOptions...> : public OptionsParser -{ - typedef cc_mqttsn_client::option::RegisteredTopicsLimit Option; -public: - static const bool HasRegisteredTopicsLimit = true; - static const std::size_t RegisteredTopicsLimit = Option::Value; -}; - -template -class OptionsParser< - cc_mqttsn_client::option::GwAddStaticStorageSize, - TOptions...> : public OptionsParser -{ - typedef cc_mqttsn_client::option::GwAddStaticStorageSize Option; -public: - static const bool HasGwAddStaticStorageSize = true; - static const std::size_t GwAddStaticStorageSize = Option::Value; -}; - -template -class OptionsParser< - cc_mqttsn_client::option::ClientIdStaticStorageSize, - TOptions...> : public OptionsParser -{ - typedef cc_mqttsn_client::option::ClientIdStaticStorageSize Option; -public: - static const bool HasClientIdStaticStorageSize = true; - static const std::size_t ClientIdStaticStorageSize = Option::Value; -}; - -template -class OptionsParser< - cc_mqttsn_client::option::TopicNameStaticStorageSize, - TOptions...> : public OptionsParser -{ - typedef cc_mqttsn_client::option::TopicNameStaticStorageSize Option; -public: - static const bool HasTopicNameStaticStorageSize = true; - static const std::size_t TopicNameStaticStorageSize = Option::Value; -}; - -template -class OptionsParser< - cc_mqttsn_client::option::MessageDataStaticStorageSize, - TOptions...> : public OptionsParser -{ - typedef cc_mqttsn_client::option::MessageDataStaticStorageSize Option; -public: - static const bool HasMessageDataStaticStorageSize = true; - static const std::size_t MessageDataStaticStorageSize = Option::Value; -}; - - -template -class OptionsParser< - std::tuple, - TOptions...> : public OptionsParser -{ -}; - - -} // namespace details - -} // namespace cc_mqttsn_client - diff --git a/client.old/src/basic/details/WriteBufStorageType.h b/client.old/src/basic/details/WriteBufStorageType.h deleted file mode 100644 index 8779764a..00000000 --- a/client.old/src/basic/details/WriteBufStorageType.h +++ /dev/null @@ -1,72 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#pragma once - -#include "comms/comms.h" - -namespace cc_mqttsn_client -{ - -namespace details -{ - -template -class WriteBufStorageType; - -template -class WriteBufStorageType -{ - static_assert( - TOpts::HasGwAddStaticStorageSize && - TOpts::HasClientIdStaticStorageSize && - TOpts::HasTopicNameStaticStorageSize && - TOpts::HasMessageDataStaticStorageSize, - "Not all static storages"); - static const std::size_t Size1 = - (TOpts::GwAddStaticStorageSize > TOpts::ClientIdStaticStorageSize) ? - TOpts::GwAddStaticStorageSize : TOpts::ClientIdStaticStorageSize; - - static const std::size_t Size2 = - (Size1 > TOpts::TopicNameStaticStorageSize) ? - Size1 : TOpts::TopicNameStaticStorageSize; - - static const std::size_t Size3 = - (Size2 > TOpts::MessageDataStaticStorageSize) ? - Size2 : TOpts::MessageDataStaticStorageSize; - - static const std::size_t FinalSize = Size3; - static const std::size_t MaxOverhead = 10U; - -public: - typedef comms::util::StaticVector Type; - -}; - -template -class WriteBufStorageType -{ -public: - typedef std::vector Type; -}; - -template -using WriteBufStorageTypeT = - typename WriteBufStorageType< - TOpts, - TOpts::HasGwAddStaticStorageSize && - TOpts::HasClientIdStaticStorageSize && - TOpts::HasTopicNameStaticStorageSize && - TOpts::HasMessageDataStaticStorageSize - >::Type; - - -} // namespace details - -} // namespace cc_mqttsn_client - - diff --git a/client.old/src/basic/option.h b/client.old/src/basic/option.h deleted file mode 100644 index e4d72a9f..00000000 --- a/client.old/src/basic/option.h +++ /dev/null @@ -1,65 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#pragma once - -#include - -namespace cc_mqttsn_client -{ - -namespace option -{ - -template -struct ClientsAllocLimit -{ - static const std::size_t Value = TLimit; -}; - -template -struct TrackedGatewaysLimit -{ - static const std::size_t Value = TLimit; -}; - -template -struct RegisteredTopicsLimit -{ - static const std::size_t Value = TLimit; -}; - -template -struct GwAddStaticStorageSize -{ - static const std::size_t Value = TSize; -}; - -template -struct ClientIdStaticStorageSize -{ - static const std::size_t Value = TSize; -}; - -template -struct TopicNameStaticStorageSize -{ - static const std::size_t Value = TSize; -}; - -template -struct MessageDataStaticStorageSize -{ - static const std::size_t Value = TSize; -}; - - -} // namespace option - -} // namespace cc_mqttsn_client - - diff --git a/client.old/templ/client.cpp.templ b/client.old/templ/client.cpp.templ deleted file mode 100644 index 9d88a751..00000000 --- a/client.old/templ/client.cpp.templ +++ /dev/null @@ -1,348 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include "##NAME##client.h" -#include "BasicClient.h" -#include "ClientMgr.h" -#include "option.h" -#include "ParsedOptions.h" - -namespace -{ - -typedef std::tuple< - ##CLIENT_OPTS## -> ClientOptions; - -typedef cc_mqttsn_client::ParsedOptions ParsedClientOptions; - -typedef cc_mqttsn_client::BasicClient MqttsnClient; -typedef cc_mqttsn_client::ClientMgr MqttsnClientMgr; - -MqttsnClientMgr& getClientMgr() -{ - static MqttsnClientMgr Mgr; - return Mgr; -} - -} // namespace - -CC_MqttsnClientHandle cc_mqttsn_##NAME##client_new() -{ - auto client = getClientMgr().alloc(); - auto handle = CC_MqttsnClientHandle(); - handle.m_ptr = client.release(); - return handle; -} - -void cc_mqttsn_##NAME##client_free(CC_MqttsnClientHandle client) -{ - getClientMgr().free(reinterpret_cast(client.m_ptr)); -} - -void cc_mqttsn_##NAME##client_set_next_tick_program_callback( - CC_MqttsnClientHandle client, - CC_MqttsnNextTickProgramFn fn, - void* data) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - clientObj->setNextTickProgramCallback(fn, data); -} - -void cc_mqttsn_##NAME##client_set_cancel_next_tick_wait_callback( - CC_MqttsnClientHandle client, - CC_MqttsnCancelNextTickWaitFn fn, - void* data) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - clientObj->setCancelNextTickWaitCallback(fn, data); -} - -void cc_mqttsn_##NAME##client_set_send_output_data_callback( - CC_MqttsnClientHandle client, - CC_MqttsnSendOutputDataFn fn, - void* data) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - clientObj->setSendOutputDataCallback(fn, data); -} - -void cc_mqttsn_##NAME##client_set_gw_status_report_callback( - CC_MqttsnClientHandle client, - CC_MqttsnGwStatusReportFn fn, - void* data) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - clientObj->setGwStatusReportCallback(fn, data); -} - -void cc_mqttsn_##NAME##client_set_gw_disconnect_report_callback( - CC_MqttsnClientHandle client, - CC_MqttsnGwDisconnectReportFn fn, - void* data) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - clientObj->setGwDisconnectReportCallback(fn, data); -} - -void cc_mqttsn_##NAME##client_set_message_report_callback( - CC_MqttsnClientHandle client, - CC_MqttsnMessageReportFn fn, - void* data) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - clientObj->setMessageReportCallback(fn, data); -} - -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_start(CC_MqttsnClientHandle client) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - return clientObj->start(); -} - -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_stop(CC_MqttsnClientHandle client) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - return clientObj->stop(); -} - -unsigned cc_mqttsn_##NAME##client_process_data( - CC_MqttsnClientHandle client, - const unsigned char* from, - unsigned len) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - return static_cast(clientObj->processData(from, len)); -} - -void cc_mqttsn_##NAME##client_tick(CC_MqttsnClientHandle client) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - clientObj->tick(); -} - -void cc_mqttsn_##NAME##client_set_retry_period(CC_MqttsnClientHandle client, unsigned value) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - clientObj->setRetryPeriod(value); -} - -void cc_mqttsn_##NAME##client_set_retry_count(CC_MqttsnClientHandle client, unsigned value) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - clientObj->setRetryCount(value); -} - -void cc_mqttsn_##NAME##client_set_default_broadcast_radius(CC_MqttsnClientHandle client, unsigned char value) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - clientObj->setBroadcastRadius(value); -} - -void cc_mqttsn_##NAME##client_set_searchgw_enabled( - CC_MqttsnClientHandle client, - bool value) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - clientObj->setSearchgwEnabled(value); -} - -void cc_mqttsn_##NAME##client_search_gw(CC_MqttsnClientHandle client) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - clientObj->sendSearchGw(); -} - -void cc_mqttsn_##NAME##client_discard_gw( - CC_MqttsnClientHandle client, - unsigned char gwId) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - clientObj->discardGw(gwId); -} - -void cc_mqttsn_##NAME##client_discard_all_gw(CC_MqttsnClientHandle client) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - clientObj->discardAllGw(); -} - -bool cc_mqttsn_##NAME##client_cancel(CC_MqttsnClientHandle client) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - return clientObj->cancel(); -} - -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect( - CC_MqttsnClientHandle client, - const char* clientId, - unsigned short keepAliveSeconds, - bool cleanSession, - const CC_MqttsnWillInfo* willInfo, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - return clientObj->connect( - clientId, - keepAliveSeconds, - cleanSession, - willInfo, - callback, - data); -} - -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_reconnect( - CC_MqttsnClientHandle client, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data -) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - return clientObj->reconnect(callback, data); -} - -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect( - CC_MqttsnClientHandle client, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - return clientObj->disconnect(callback, data); -} - -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_publish_id( - CC_MqttsnClientHandle client, - CC_MqttsnTopicId topicId, - const unsigned char* msg, - unsigned msgLen, - CC_MqttsnQoS qos, - bool retain, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - return clientObj->publish(topicId, msg, msgLen, qos, retain, callback, data); -} - -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_publish( - CC_MqttsnClientHandle client, - const char* topic, - const unsigned char* msg, - unsigned msgLen, - CC_MqttsnQoS qos, - bool retain, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - return clientObj->publish(topic, msg, msgLen, qos, retain, callback, data); -} - -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_subscribe_id( - CC_MqttsnClientHandle client, - CC_MqttsnTopicId topicId, - CC_MqttsnQoS qos, - CC_MqttsnSubscribeCompleteReportFn callback, - void* data -) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - return clientObj->subscribe(topicId, qos, callback, data); -} - -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_subscribe( - CC_MqttsnClientHandle client, - const char* topic, - CC_MqttsnQoS qos, - CC_MqttsnSubscribeCompleteReportFn callback, - void* data -) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - return clientObj->subscribe(topic, qos, callback, data); -} - -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_unsubscribe_id( - CC_MqttsnClientHandle client, - CC_MqttsnTopicId topicId, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data -) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - return clientObj->unsubscribe(topicId, callback, data); -} - - -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_unsubscribe( - CC_MqttsnClientHandle client, - const char* topic, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data -) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - return clientObj->unsubscribe(topic, callback, data); -} - -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_will_update( - CC_MqttsnClientHandle client, - const CC_MqttsnWillInfo* willInfo, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data -) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - return clientObj->willUpdate(willInfo, callback, data); -} - -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_will_topic_update( - CC_MqttsnClientHandle client, - const char* topic, - CC_MqttsnQoS qos, - bool retain, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data -) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - return clientObj->willTopicUpdate(topic, qos, retain, callback, data); -} - -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_will_msg_update( - CC_MqttsnClientHandle client, - const unsigned char* msg, - unsigned msgLen, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data -) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - return clientObj->willMsgUpdate(msg, msgLen, callback, data); -} - -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_sleep( - CC_MqttsnClientHandle client, - unsigned short duration, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data -) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - return clientObj->sleep(duration, callback, data); -} - -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_check_messages( - CC_MqttsnClientHandle client, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data -) -{ - auto* clientObj = reinterpret_cast(client.m_ptr); - return clientObj->checkMessages(callback, data); -} diff --git a/client.old/templ/client.h.templ b/client.old/templ/client.h.templ deleted file mode 100644 index a8ac00ba..00000000 --- a/client.old/templ/client.h.templ +++ /dev/null @@ -1,556 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -/// @file -/// @brief Functions of MQTT-SN client library. - -#pragma once - -#include "cc_mqttsn_client/common.h" - -#ifdef __cplusplus -extern "C" { -#endif // #ifdef __cplusplus - -/// @brief Allocate new client. -/// @details When work with the client is complete, @ref cc_mqttsn_##NAME##client_free() -/// function must be invoked. -/// @return Handle to allocated client object. This handle needs to be passed -/// as first parameter to all other API functions. -CC_MqttsnClientHandle cc_mqttsn_##NAME##client_new(); - -/// @brief Free previously allocated client. -/// @details When used communication channel to the gateway is no longer -/// needed, the client data structes allocated with -/// cc_mqttsn_##NAME##client_new() must be released using this function. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -void cc_mqttsn_##NAME##client_free(CC_MqttsnClientHandle client); - -/// @brief Set callback to call when time measurement is required. -/// @details The MQTT-SN client may require to measure time. When such -/// measurement is required, the provided callback will be invoked with -/// the timeout duration in milliseconds. After requested time expires, -/// the @ref cc_mqttsn_##NAME##client_tick() function must be invoked. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] fn Callback function. -/// @param[in] data Pointer to any user data structure. It will passed as one -/// of the parameters in callback invocation. May be NULL. -void cc_mqttsn_##NAME##client_set_next_tick_program_callback( - CC_MqttsnClientHandle client, - CC_MqttsnNextTickProgramFn fn, - void* data); - -/// @brief Set callback to terminate current time measurement. -/// @details The client may request termination of currently running time -/// measurement, previously requested via callback, which was set using -/// @ref cc_mqttsn_##NAME##client_set_next_tick_program_callback() function. This function -/// sets appropriate callback. When invoked, it must returne number of -/// elapsed milliseconds since previoius time measurement request. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] fn Callback function. -/// @param[in] data Pointer to any user data structure. It will passed as one -/// of the parameters in callback invocation. May be NULL. -void cc_mqttsn_##NAME##client_set_cancel_next_tick_wait_callback( - CC_MqttsnClientHandle client, - CC_MqttsnCancelNextTickWaitFn fn, - void* data); - -/// @brief Set callback to send raw data over I/O link. -/// @details The callback is invoked when there is a need to send data -/// to the gateway. The callback is invoked for every single message -/// that need to be sent as a single datagram. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] fn Callback function. -/// @param[in] data Pointer to any user data structure. It will passed as one -/// of the parameters in callback invocation. May be NULL. -void cc_mqttsn_##NAME##client_set_send_output_data_callback( - CC_MqttsnClientHandle client, - CC_MqttsnSendOutputDataFn fn, - void* data); - -/// @brief Set callback to report status of the gateway. -/// @details The callback is invoked when gateway status has changed. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] fn Callback function. -/// @param[in] data Pointer to any user data structure. It will passed as one -/// of the parameters in callback invocation. May be NULL. -void cc_mqttsn_##NAME##client_set_gw_status_report_callback( - CC_MqttsnClientHandle client, - CC_MqttsnGwStatusReportFn fn, - void* data); - -/// @brief Set callback to report unsolicited disconnection of the gateway. -/// @details The callback will be invoked when gateway sends unsolicited -/// @b DISCONNECT message or does not reply to @b PINGREQ message. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] fn Callback function. -/// @param[in] data Pointer to any user data structure. It will passed as one -/// of the parameters in callback invocation. May be NULL. -void cc_mqttsn_##NAME##client_set_gw_disconnect_report_callback( - CC_MqttsnClientHandle client, - CC_MqttsnGwDisconnectReportFn fn, - void* data); - -/// @brief Set callback to report incoming messages. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] fn Callback function. -/// @param[in] data Pointer to any user data structure. It will passed as one -/// of the parameters in callback invocation. May be NULL. -void cc_mqttsn_##NAME##client_set_message_report_callback( - CC_MqttsnClientHandle client, - CC_MqttsnMessageReportFn fn, - void* data); - -/// @brief Start the library's operation. -/// @details The function will check whether all necessary callback functions -/// were set. In not @ref CC_MqttsnErrorCode_BadParam will be returned. -/// If search for gateways is enabled (see description of -/// cc_mqttsn_##NAME##client_set_searchgw_enabled()), the library may -/// send @b SEARCHGW message by invoking the callback, set by -/// cc_mqttsn_##NAME##client_set_send_output_data_callback(). -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @return Error code indicating success/failure status of the operation. -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_start(CC_MqttsnClientHandle client); - -/// @brief Stop the library's operation. -/// @details The operation may be resumed using cc_mqttsn_##NAME##client_start(). -/// @param[in] client Handle returned by cc_mqttsn_##NAME##client_new() function. -/// @return Error code indicating success/failure status of the operation -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_stop(CC_MqttsnClientHandle client); - -/// @brief Provide data, received over I/O link, to the library for processing. -/// @details This call may cause invocation of some callbacks, such as -/// request to cancel the currently running time measurement, send some messages to -/// the gateway, report incoming application message, and (re)start time -/// measurement. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] buf Pointer to the buffer of data to process. -/// @param[in] bufLen Number of bytes in the data buffer. -/// @return Number of processed bytes. -/// @note The function returns number of bytes that were actually consumed, and -/// can be removed from the holding buffer. -unsigned cc_mqttsn_##NAME##client_process_data(CC_MqttsnClientHandle client, const unsigned char* buf, unsigned bufLen); - -/// @brief Notify client about requested time expiry. -/// @details The reported amount of milliseconds needs to be from the -/// last request to program timer via callback (set by -/// cc_mqttsn_##NAME##client_set_next_tick_program_callback()). -/// It can be less than actually requested via the callback. If this -/// function is called, the library assumes that previously requested -/// timeout measurement is not in progress any more, and will request -/// new measurement if needed. -/// This call may cause invocation of some other callbacks, such as a request -/// to send new data to the gateway. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -void cc_mqttsn_##NAME##client_tick(CC_MqttsnClientHandle client); - -/// @brief Set retry period to wait between resending unacknowledged message to the gateway. -/// @details Some messages, sent to the gateway, may require acknowledgement by -/// the latter. The delay (in seconds) between such attempts to resend the -/// message may be specified using this function. The default value is -/// @b 15 seconds. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] value Number of @b seconds to wait before making an attempt to resend. -void cc_mqttsn_##NAME##client_set_retry_period(CC_MqttsnClientHandle client, unsigned value); - -/// @brief Set number of retry attempts to perform before reporting unsuccessful result of the operation. -/// @details Some messages, sent to the gateway, may require acknowledgement by -/// the latter. The amount of retry attempts before reporting unsuccessful result -/// of the operation may be specified using this function. The default value -/// is @b 3. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] value Number of retry attempts. -void cc_mqttsn_##NAME##client_set_retry_count(CC_MqttsnClientHandle client, unsigned value); - -/// @brief Set broadcast radius. -/// @details When searching for gateways, the client library broadcasts @b SEARCHGW -/// messages. It contains the broadcast radius value. This value can be -/// set using this function. Default radius value is 0. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] value Broadcast radius. -void cc_mqttsn_##NAME##client_set_default_broadcast_radius(CC_MqttsnClientHandle client, unsigned char value); - -/// @brief Enable/Disable search for gateways. -/// @details According to @b MQTT-SN protocol specification the client may -/// search for the gateways by broadcasting @b SEARCHGW message. -/// If address of the gateway is known (such as known UDP host), there may -/// be no need to send these messages. To enable/disable search for available -/// gateways can be done using this function. By default the search for -/// the gateway is @b enabled. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] value @b true to enable, and @b false to disable. -void cc_mqttsn_##NAME##client_set_searchgw_enabled(CC_MqttsnClientHandle client, bool value); - -/// @brief Send @b SEARCHGW message. -/// @details This function performs one send of @b SEARCHGW message regardless -/// of whether the search for gateways is enabled or disabled. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -void cc_mqttsn_##NAME##client_search_gw(CC_MqttsnClientHandle client); - -/// @brief Discard information about the gateway. -/// @details The client library maintains the list of gateways that either -/// advertised their presence or replied to previously sent @b SEARCHGW -/// messages. This function causes information about specified gateway -/// to be dropped. If such information exists, the callback, set -/// using @ref cc_mqttsn_##NAME##client_set_gw_status_report_callback() function, -/// will be invoked reporting @ref CC_MqttsnGwStatus_Discarded as status value. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] gwId ID of the gateway. -void cc_mqttsn_##NAME##client_discard_gw(CC_MqttsnClientHandle client, unsigned char gwId); - -/// @brief Discard information about all gateways. -/// @details The client library maintains the list of gateways that either -/// advertised their presence or replied to previously sent @b SEARCHGW -/// messages. This function causes the callback, set -/// using @ref cc_mqttsn_##NAME##client_set_gw_status_report_callback() function, -/// to be invoked reporting @ref CC_MqttsnGwStatus_Discarded as status value for -/// all the recorded gateways. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -void cc_mqttsn_##NAME##client_discard_all_gw(CC_MqttsnClientHandle client); - -/// @brief Cancel current asynchronous operation. -/// @details The library provides support for multiple asynchronous operations, -/// which report their completion via provided callback. The library also -/// doesn't support start of the new operation before previous one completed. -/// This function provides an ability to cancel existing operation to allow -/// issue of the new request. When successfully cancelled the callback of -/// the asyncrhonous operation will report @ref CC_MqttsnAsyncOpStatus_Aborted -/// as operation result status. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @return true in case an asynchronous operation was cancelled, -/// false otherwise. -bool cc_mqttsn_##NAME##client_cancel(CC_MqttsnClientHandle client); - -/// @brief Issue the connect request to the gateway. -/// @details This function needs to be called to connect to the gateway. -/// When the operation is complete, the provided callback -/// will be invoked. Note, that the function cannot be successfully called when -/// client is already connected to the gateway. In order to change the -/// will information use @ref cc_mqttsn_##NAME##client_will_update() function. -/// If some other operation timed out, use @ref cc_mqttsn_##NAME##client_reconnect() -/// to refresh the connection. -/// In order to change client ID and/or "keep alive" value disconnect -/// first (using @ref cc_mqttsn_##NAME##client_disconnect()) and then try to -/// connect again. -/// -/// @b IMPORTANT : The buffers containing client ID string, as well as -/// will topic and message (if such exist) must be preserved intact -/// until the connect operation is complete (provided -/// callback is invoked). The @b CC_MqttsnWillInfo structure passed as -/// @b willInfo parameter may reside on the stack and be destructed -/// immediately after this function returns, but the buffers its data -/// members point to must be preserved. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] clientId Client ID string. -/// @param[in] keepAliveSeconds Maximum number of seconds allowed between the -/// messages sent to the gateway. If no publish is performed during this -/// timeframe, the library will generate @b PINGREQ message and send to the -/// gateway. -/// @param[in] cleanSession Request to establish clean session. -/// @param[in] willInfo Requested will information, can be NULL. -/// @param[in] callback Callback to be invoked when operation is complete, -/// must @b NOT be NULL. -/// @param[in] data Pointer to any user data, it will be passed as the first -/// parameter to the invoked completion report callback, can be NULL. -/// @return Error code indicating success/failure status of the operation. -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect( - CC_MqttsnClientHandle client, - const char* clientId, - unsigned short keepAliveSeconds, - bool cleanSession, - const CC_MqttsnWillInfo* willInfo, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data); - -/// @brief Reconnect to the gateway using previously used client ID and keep alive -/// period. -/// @details The invocation will work only when in "connected" or "asleep" state, -/// It will not work after invocation of @ref cc_mqttsn_##NAME##client_disconnect(). -/// When the operation is complete, the provided callback -/// will be invoked. If successful completion is reported, the client -/// is properly connected to the gateway again. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] callback Callback to be invoked when operation is complete, -/// must @b NOT be NULL. -/// @param[in] data Pointer to any user data, it will be passed as the first -/// parameter to the invoked completion report callback, can be NULL. -/// @return Error code indicating success/failure status of the operation. -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_reconnect( - CC_MqttsnClientHandle client, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data -); - -/// @brief Disconnect from the gateway. -/// @details When the operation is complete, the provided callback -/// will be invoked. Regardless of the reported disconnect result, -/// the library assumes "disconnected" internal state and some functions, -/// such as requests to publish and/or subscribe may not work. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] callback Callback to be invoked when operation is complete, -/// must @b NOT be NULL. -/// @param[in] data Pointer to any user data, it will be passed as the first -/// parameter to the invoked completion report callback, can be NULL. -/// @return Error code indicating success/failure status of the operation. -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect( - CC_MqttsnClientHandle client, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data); - -/// @brief Publish message with predefined topic ID. -/// @details When publish operation is complete, the provided callback -/// will be invoked. Note, that -/// the callback will be invoked immediately for publish operation with -/// QoS=-1 or QoS=0. -/// -/// @b IMPORTANT : The buffer containing message data must be preserved -/// intact until the end of the operation (provided callback is invoked). -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] topicId Predefined topic ID. -/// @param[in] msg Pointer to buffer containing data to be published. -/// @param[in] msgLen Size of the buffer containing data to be published. -/// @param[in] qos Quality of service level. -/// @param[in] retain Retain flag. -/// @param[in] callback Callback to be invoked when operation is complete. -/// It can be NULL for publish requests with QoS=-1 or QoS=0. Such publish -/// requests do not require acknowledgement from the gateway and report -/// their completion immediatelly. -/// @param[in] data Pointer to any user data, it will be passed as the first -/// parameter to the invoked completion report callback, can be NULL. -/// @return Error code indicating success/failure status of the operation. -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_publish_id( - CC_MqttsnClientHandle client, - CC_MqttsnTopicId topicId, - const unsigned char* msg, - unsigned msgLen, - CC_MqttsnQoS qos, - bool retain, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data -); - -/// @brief Publish message with topic string. -/// @details When publish operation is complete, the provided callback -/// will be invoked. Note, that -/// the callback MAY be invoked immediately for publish operation with -/// QoS=0 if requested topic is already registered. -/// -/// @b IMPORTANT : The buffer containing message data must be preserved -/// intact until the end of the operation (provided callback is invoked). -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] topic Topic string. -/// @param[in] msg Pointer to buffer containing data to be published. -/// @param[in] msgLen Size of the buffer containing data to be published. -/// @param[in] qos Quality of service level. -/// @param[in] retain Retain flag. -/// @param[in] callback Callback to be invoked when operation is complete, -/// must @b NOT be NULL. -/// @param[in] data Pointer to any user data, it will be passed as the first -/// parameter to the invoked completion report callback, can be NULL. -/// @return Error code indicating success/failure status of the operation. -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_publish( - CC_MqttsnClientHandle client, - const char* topic, - const unsigned char* msg, - unsigned msgLen, - CC_MqttsnQoS qos, - bool retain, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data -); - -/// @brief Subscribe to topic having predefined topic ID. -/// @details When subscribe operation is complete, the provided callback -/// will be invoked. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] topicId Predefined topic ID. -/// @param[in] qos Maximum level of quality of service the messages are sent to this client. -/// @param[in] callback Callback to be invoked when operation is complete, -/// must @b NOT be NULL. -/// @param[in] data Pointer to any user data, it will be passed as the first -/// parameter to the invoked completion report callback, can be NULL. -/// @return Error code indicating success/failure status of the operation. -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_subscribe_id( - CC_MqttsnClientHandle client, - CC_MqttsnTopicId topicId, - CC_MqttsnQoS qos, - CC_MqttsnSubscribeCompleteReportFn callback, - void* data -); - -/// @brief Subscribe to topic. -/// @details When subscribe operation is complete, the provided callback -/// will be invoked. -/// -/// @b IMPORTANT : The buffer containing topic string must be preserved -/// intact until the end of the operation (provided callback is invoked). -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] topic Topic string. -/// @param[in] qos Maximum level of quality of service the messages are sent to this client. -/// @param[in] callback Callback to be invoked when operation is complete, -/// must @b NOT be NULL. -/// @param[in] data Pointer to any user data, it will be passed as the first -/// parameter to the invoked completion report callback, can be NULL. -/// @return Error code indicating success/failure status of the operation. -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_subscribe( - CC_MqttsnClientHandle client, - const char* topic, - CC_MqttsnQoS qos, - CC_MqttsnSubscribeCompleteReportFn callback, - void* data -); - -/// @brief Unsubscribe from messages having predefined topic ID. -/// @details When unsubscribe operation is complete, the provided callback -/// will be invoked. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] topicId Predefined topic ID. -/// @param[in] callback Callback to be invoked when operation is complete, -/// must @b NOT be NULL. -/// @param[in] data Pointer to any user data, it will be passed as the first -/// parameter to the invoked completion report callback, can be NULL. -/// @return Error code indicating success/failure status of the operation. -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_unsubscribe_id( - CC_MqttsnClientHandle client, - CC_MqttsnTopicId topicId, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data -); - -/// @brief Unsubscribe from messages having specified topic. -/// @details When unsubscribe operation is complete, the provided callback -/// will be invoked. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] topic Topic string. -/// @param[in] callback Callback to be invoked when operation is complete, -/// must @b NOT be NULL. -/// @param[in] data Pointer to any user data, it will be passed as the first -/// parameter to the invoked completion report callback, can be NULL. -/// @return Error code indicating success/failure status of the operation. -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_unsubscribe( - CC_MqttsnClientHandle client, - const char* topic, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data -); - -/// @brief Update will recorded with the gateway/broker. -/// @details When the operation is complete, the provided callback -/// will be invoked. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] willInfo New will full information. The parameter can be NULL -/// or the @b topic data member of the @ref CC_MqttsnWillInfo struct may be -/// null. In this case the request means erase current will information -/// recorded with the gateway/broker. -/// @param[in] callback Callback to be invoked when operation is complete, -/// must @b NOT be NULL. -/// @param[in] data Pointer to any user data, it will be passed as the first -/// parameter to the invoked completion report callback, can be NULL. -/// @return Error code indicating success/failure status of the operation. -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_will_update( - CC_MqttsnClientHandle client, - const CC_MqttsnWillInfo* willInfo, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data -); - -/// @brief Update topic of the will recorded with the gateway/broker. -/// @details When the operation is complete, the provided callback -/// will be invoked. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] topic New will topic string. The parameter can be NULL. -/// In this case the request means erase current will information -/// recorded with the gateway/broker. -/// @param[in] qos Quality of service for the will. -/// @param[in] retain Retain flag. -/// @param[in] callback Callback to be invoked when operation is complete, -/// must @b NOT be NULL. -/// @param[in] data Pointer to any user data, it will be passed as the first -/// parameter to the invoked completion report callback, can be NULL. -/// @return Error code indicating success/failure status of the operation. -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_will_topic_update( - CC_MqttsnClientHandle client, - const char* topic, - CC_MqttsnQoS qos, - bool retain, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data -); - -/// @brief Update will message body recorded with the gateway/broker. -/// @details When the operation is complete, the provided callback -/// will be invoked. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] msg Pointer to message body buffer -/// @param[in] msgLen Length of message body buffer. -/// @param[in] callback Callback to be invoked when operation is complete, -/// must @b NOT be NULL. -/// @param[in] data Pointer to any user data, it will be passed as the first -/// parameter to the invoked completion report callback, can be NULL. -/// @return Error code indicating success/failure status of the operation. -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_will_msg_update( - CC_MqttsnClientHandle client, - const unsigned char* msg, - unsigned msgLen, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data -); - -/// @brief Notify gateway about entering the sleep state. -/// @details When the operation is complete, the provided callback -/// will be invoked. If successful completion is reported, the client may -/// enter the low power mode and do not send any messages within the -/// "keep alive" period specified in @ref cc_mqttsn_##NAME##client_connect() -/// invocation. However, there is a need to either wake up (using -/// @ref cc_mqttsn_##NAME##client_reconnect()) or check for the accumulated messages -/// (using @ref cc_mqttsn_##NAME##client_check_messages()) within the period -/// specified in this function invocation. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] duration Muximal number of seconds the client will remain in -/// the sleep state until next check of accumulated pending messages -/// (use @ref cc_mqttsn_##NAME##client_check_messages() for this purpose) or -/// full wake up (use @ref cc_mqttsn_##NAME##client_reconnect() for this purpose). -/// @param[in] callback Callback to be invoked when operation is complete, -/// must @b NOT be NULL. -/// @param[in] data Pointer to any user data, it will be passed as the first -/// parameter to the invoked completion report callback, can be NULL. -/// @return Error code indicating success/failure status of the operation. -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_sleep( - CC_MqttsnClientHandle client, - unsigned short duration, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data -); - -/// @brief Check for accumulated pending messages the gateway has to report. -/// @details Can be invoked after successful entering the sleep state using -/// @ref cc_mqttsn_##NAME##client_sleep(). -/// According to MQTT-SN protocol specification, this function will -/// force sending @b PINGREQ message to the gateway. The gateway then will -/// deliver all the pending messages, which may cause multiple invocation -/// of message report callback (set with -/// @ref cc_mqttsn_##NAME##client_set_message_report_callback()). After gateway -/// reports all accumulated messages, it will send @b PINGRESP message, -/// which will case invocation of the provided callback. After the -/// callback is invoked, the client may return to "asleep" state and will -/// be required to check messages again or wake up after number of seconds -/// provided in @ref cc_mqttsn_##NAME##client_sleep() call. -/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_new() function. -/// @param[in] callback Callback to be invoked when operation is complete, -/// must @b NOT be NULL. -/// @param[in] data Pointer to any user data, it will be passed as the first -/// parameter to the invoked completion report callback, can be NULL. -/// @return Error code indicating success/failure status of the operation. -CC_MqttsnErrorCode cc_mqttsn_##NAME##client_check_messages( - CC_MqttsnClientHandle client, - CC_MqttsnAsyncOpCompleteReportFn callback, - void* data -); - -#ifdef __cplusplus -} -#endif diff --git a/client.old/test/CMakeLists.txt b/client.old/test/CMakeLists.txt deleted file mode 100644 index 1f942521..00000000 --- a/client.old/test/CMakeLists.txt +++ /dev/null @@ -1,54 +0,0 @@ -add_subdirectory (bare_metal_app) - -if (NOT BUILD_TESTING) - return () -endif () - -set (COMPONENT_NAME "cc.mqttsn.client") -set (COMMON_TEST_CLIENT_LIB "CommonTestClient") - -################################################################# - -function (lib_common_test_client) - set (src - "CommonTestClient.cpp" - "DataProcessor.cpp" - ) - add_library (${COMMON_TEST_CLIENT_LIB} STATIC ${src}) - target_link_libraries(${COMMON_TEST_CLIENT_LIB} PUBLIC cc::${DEFAULT_CLIENT_LIB_TGT} cc::cc_mqttsn cc::comms) -endfunction () - -################################################################# - -function (test_func test_suite_name) - set (tests "${CMAKE_CURRENT_SOURCE_DIR}/${test_suite_name}.th") - - set (name "${COMPONENT_NAME}.${test_suite_name}Test") - - set (valgrand_args) - if (VALGRIND_EXECUTABLE) - set (valgrand_args VALGRIND_EXECUTABLE ${VALGRIND_EXECUTABLE}) - endif () - - cc_cxxtest_add_test( - NAME ${name} - SRC ${tests} - ${valgrand_args}) - - target_link_libraries(${name} PRIVATE ${COMMON_TEST_CLIENT_LIB} cc::${DEFAULT_CLIENT_LIB_TGT} cxxtest::cxxtest) -endfunction () - -################################################################# - -function (test_client_basic) - test_func ("ClientBasic") -endfunction () - -################################################################# - -include_directories ( - "${CMAKE_BINARY_DIR}/client/src/basic" -) - -lib_common_test_client() -test_client_basic() diff --git a/client.old/test/ClientBasic.th b/client.old/test/ClientBasic.th deleted file mode 100644 index 3590df8e..00000000 --- a/client.old/test/ClientBasic.th +++ /dev/null @@ -1,3957 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include -#include - -#include "comms/comms.h" -#include "cc_mqttsn_client/common.h" -#include "client.h" - -CC_DISABLE_WARNINGS() -#include "cxxtest/TestSuite.h" -CC_ENABLE_WARNINGS() - -#include "CommonTestClient.h" -#include "DataProcessor.h" - -class ClientBasic : public CxxTest::TestSuite -{ -public: - void test1(); - void test2(); - void test3(); - void test4(); - void test5(); - void test6(); - void test7(); - void test8(); - void test9(); - void test10(); - void test11(); - void test12(); - void test13(); - void test14(); - void test15(); - void test16(); - void test17(); - void test18(); - void test19(); - void test20(); - void test21(); - void test22(); - void test23(); - void test24(); - void test25(); - void test26(); - void test27(); - void test28(); - void test29(); - void test30(); - void test31(); - void test32(); - void test33(); - void test34(); - void test35(); - void test36(); - void test37(); - void test38(); - void test39(); - void test40(); - void test41(); - void test42(); - void test43(); - void test44(); - void test45(); - void test46(); - -private: - typedef DataProcessor::DataBuf DataBuf; - - typedef DataProcessor::AdvertiseMsg AdvertiseMsg; - typedef DataProcessor::SearchgwMsg SearchgwMsg; - typedef DataProcessor::GwinfoMsg GwinfoMsg; - typedef DataProcessor::ConnectMsg ConnectMsg; - typedef DataProcessor::ConnackMsg ConnackMsg; - typedef DataProcessor::WilltopicreqMsg WilltopicreqMsg; - typedef DataProcessor::WilltopicMsg WilltopicMsg; - typedef DataProcessor::WillmsgreqMsg WillmsgreqMsg; - typedef DataProcessor::WillmsgMsg WillmsgMsg; - typedef DataProcessor::RegisterMsg RegisterMsg; - typedef DataProcessor::RegackMsg RegackMsg; - typedef DataProcessor::PublishMsg PublishMsg; - typedef DataProcessor::PubackMsg PubackMsg; - typedef DataProcessor::PubrecMsg PubrecMsg; - typedef DataProcessor::PubrelMsg PubrelMsg; - typedef DataProcessor::PubcompMsg PubcompMsg; - typedef DataProcessor::SubscribeMsg SubscribeMsg; - typedef DataProcessor::UnsubscribeMsg UnsubscribeMsg; - typedef DataProcessor::PingreqMsg PingreqMsg; - typedef DataProcessor::PingrespMsg PingrespMsg; - typedef DataProcessor::DisconnectMsg DisconnectMsg; - typedef DataProcessor::WilltopicupdMsg WilltopicupdMsg; - typedef DataProcessor::WillmsgupdMsg WillmsgupdMsg; - - using TopicIdTypeVal = cc_mqttsn::field::TopicIdTypeVal; - - static const std::string DefaultClientId; - static const unsigned DefaultAdvertisePeriod = 5 * 60 * 1000; - static const unsigned MaxPeriod = std::numeric_limits::max() * 1000; - static const unsigned DefaultRetryTimeout = 5 * 1000; - static const unsigned DefaultRetryCount = 3; - static const unsigned char DefaultBroadcastRadius = 0; - static const unsigned short DefaultKeepAlive = 60; - - typedef std::vector DataSeq; - - struct ReportedMsgInfo - { - ReportedMsgInfo(const CC_MqttsnMessageInfo& info) - : m_topic((info.topic != nullptr) ? info.topic : std::string()), - m_topicId(info.topicId), - m_data(info.msg, info.msg + info.msgLen), - m_qos(info.qos), - m_retain(info.retain) - { - } - - std::string m_topic; - CC_MqttsnTopicId m_topicId = 0; - std::vector m_data; - CC_MqttsnQoS m_qos = CC_MqttsnQoS_NoGwPublish; - bool m_retain = false; - }; - - struct ReportedGwInfo - { - ReportedGwInfo(std::uint16_t id, CC_MqttsnGwStatus status) - : m_id(id), - m_status(status) - { - } - - unsigned short m_id = 0U; - CC_MqttsnGwStatus m_status = CC_MqttsnGwStatus_Invalid; - }; - - struct TestBasicState - { - unsigned m_nextRequestedTicks = 0; - unsigned m_nextElapsedTicks = 0; - DataSeq m_nextOutput; - bool m_broadcast; - std::list m_reportedMsgs; - std::list m_reportedGws; - std::list m_reportedGwDisconnects; - std::list m_reportedConnects; - std::list m_reportedDisconnects; - std::list m_reportedPublishes; - std::list > m_reportedSubscribes; - std::list m_reportedUnsubscribes; - std::list m_reportedWillUpdates; - std::list m_reportedWillTopicUpdates; - std::list m_reportedWillMsgUpdates; - std::list m_reportedSleeps; - std::list m_reportedReconnects; - std::list m_reportedMessageChecks; - }; - - template - static void clearState(TState& state) - { - state = TState(); - } - - static void verifyNoOtherEvent(TestBasicState& state) - { - TS_ASSERT(state.m_reportedMsgs.empty()); - TS_ASSERT(state.m_reportedGws.empty()); - TS_ASSERT(state.m_reportedGwDisconnects.empty()); - TS_ASSERT(state.m_reportedConnects.empty()); - TS_ASSERT(state.m_reportedDisconnects.empty()); - TS_ASSERT(state.m_reportedPublishes.empty()); - TS_ASSERT(state.m_reportedSubscribes.empty()); - TS_ASSERT(state.m_reportedUnsubscribes.empty()); - TS_ASSERT(state.m_reportedWillUpdates.empty()); - TS_ASSERT(state.m_reportedWillTopicUpdates.empty()); - TS_ASSERT(state.m_reportedWillMsgUpdates.empty()); - TS_ASSERT(state.m_reportedSleeps.empty()); - TS_ASSERT(state.m_reportedReconnects.empty()); - TS_ASSERT(state.m_reportedMessageChecks.empty()); - } - - static void dataFromGw(CommonTestClient& client, const DataBuf& buf, const std::string& msg) - { - if (!msg.empty()) { - TS_TRACE("--> " + msg); - } - client.inputData(&buf[0], buf.size()); - } - - typedef CommonTestClient::Ptr ClientPtr; - static ClientPtr allocClient(TestBasicState* state = nullptr, DataProcessor* dataProc = nullptr) - { - auto ptr = CommonTestClient::alloc(); - assert(ptr); - ptr->setRetryPeriod(DefaultRetryTimeout / 1000); - ptr->setRetryCount(DefaultRetryCount); - ptr->setBroadcastRadius(DefaultBroadcastRadius); - - ptr->setGwDisconnectReportCallback( - [state]() - { - TS_TRACE("GW disconnect reported: "); - if (state != nullptr) { - state->m_reportedGwDisconnects.push_back(true); - } - }); - - ptr->setConnectCompleteCallback( - [state](CC_MqttsnAsyncOpStatus val) - { - TS_TRACE("Connect complete with status: " + std::to_string((int)val)); - if (state != nullptr) { - state->m_reportedConnects.push_back(val); - } - }); - - ptr->setDisconnectCompleteCallback( - [state](CC_MqttsnAsyncOpStatus val) - { - TS_TRACE("Disconnect complete with status: " + std::to_string((int)val)); - if (state != nullptr) { - state->m_reportedDisconnects.push_back(val); - } - }); - - ptr->setPublishCompleteCallback( - [state](CC_MqttsnAsyncOpStatus val) - { - TS_TRACE("Publish complete with status: " + std::to_string((int)val)); - if (state != nullptr) { - state->m_reportedPublishes.push_back(val); - } - }); - - ptr->setSubsribeCompleteCallback( - [state](CC_MqttsnAsyncOpStatus val, CC_MqttsnQoS qos) - { - TS_TRACE("Subscribe complete with status: " + std::to_string((int)val)); - if (state != nullptr) { - state->m_reportedSubscribes.push_back(std::make_pair(val, qos)); - } - }); - - ptr->setUnsubsribeCompleteCallback( - [state](CC_MqttsnAsyncOpStatus val) - { - TS_TRACE("Unsubscribe complete with status: " + std::to_string((int)val)); - if (state != nullptr) { - state->m_reportedUnsubscribes.push_back(val); - } - }); - - ptr->setWillUpdateCompleteCallback( - [state](CC_MqttsnAsyncOpStatus val) - { - TS_TRACE("Will update complete with status: " + std::to_string((int)val)); - if (state != nullptr) { - state->m_reportedWillUpdates.push_back(val); - } - }); - - ptr->setWillTopicUpdateCompleteCallback( - [state](CC_MqttsnAsyncOpStatus val) - { - TS_TRACE("Will topic update complete with status: " + std::to_string((int)val)); - if (state != nullptr) { - state->m_reportedWillTopicUpdates.push_back(val); - } - }); - - ptr->setWillMsgUpdateCompleteCallback( - [state](CC_MqttsnAsyncOpStatus val) - { - TS_TRACE("Will msg update complete with status: " + std::to_string((int)val)); - if (state != nullptr) { - state->m_reportedWillMsgUpdates.push_back(val); - } - }); - - ptr->setSleepCompleteCallback( - [state](CC_MqttsnAsyncOpStatus val) - { - TS_TRACE("Sleep complete with status: " + std::to_string((int)val)); - if (state != nullptr) { - state->m_reportedSleeps.push_back(val); - } - }); - - ptr->setReconnectCompleteCallback( - [state](CC_MqttsnAsyncOpStatus val) - { - TS_TRACE("Reconnect complete with status: " + std::to_string((int)val)); - if (state != nullptr) { - state->m_reportedReconnects.push_back(val); - } - }); - - ptr->setCheckMessagesCompleteCallback( - [state](CC_MqttsnAsyncOpStatus val) - { - TS_TRACE("Check messages complete with status: " + std::to_string((int)val)); - if (state != nullptr) { - state->m_reportedMessageChecks.push_back(val); - } - }); - - ptr->setMessageReportCallback( - [state](const CC_MqttsnMessageInfo& msgInfo) - { - if (msgInfo.topic != nullptr) { - TS_TRACE(std::string("Received message with topic ") + msgInfo.topic); - } - else { - TS_TRACE(std::string("Received message with topic ID ") + std::to_string(msgInfo.topicId)); - } - - if (state != nullptr) { - state->m_reportedMsgs.push_back(msgInfo); - } - }); - ptr->setGwStatusReportCallback( - [state](unsigned short gwId, CC_MqttsnGwStatus status) - { - TS_TRACE(std::string("GW info reported: id=") + std::to_string(gwId) + "; status=" + std::to_string(status)); - if (state != nullptr) { - state->m_reportedGws.push_back(ReportedGwInfo(gwId, status)); - } - }); - - if (state != nullptr) { - ptr->setProgramNextTickCallback( - [state](unsigned duration) noexcept - { - state->m_nextRequestedTicks = duration; - }); - - ptr->setCancelNextTickCallback( - [state]() noexcept -> unsigned - { - return state->m_nextElapsedTicks; - }); - - ptr->setSendDataCallback( - [state, dataProc](const std::uint8_t* buf, unsigned bufLen, bool broadcast) - { - if (!state->m_nextOutput.empty()) { - TS_TRACE("ERROR: unconsumed message!"); - if (dataProc != nullptr) { - processOutput(*state, *dataProc, state->m_broadcast); - } - } - assert(state->m_nextOutput.empty()); -// TS_TRACE("Sending " + std::to_string(bufLen) + " bytes; broadcast=" + std::to_string(broadcast)); - state->m_nextOutput.insert(state->m_nextOutput.end(), buf, buf + bufLen); - state->m_broadcast = broadcast; - }); - - } - - if (dataProc != nullptr) - { - dataProc->setSearchgwMsgReportCallback( - [](const SearchgwMsg& msg) - { - static_cast(msg); - TS_TRACE("<-- SEARCHGW"); - }); - - dataProc->setConnectMsgReportCallback( - [](const ConnectMsg& msg) - { - static_cast(msg); - TS_TRACE("<-- CONNECT"); - }); - - dataProc->setWilltopicMsgReportCallback( - [](const WilltopicMsg& msg) - { - static_cast(msg); - TS_TRACE("<-- WILLTOPIC"); - }); - - dataProc->setWillmsgMsgReportCallback( - [](const WillmsgMsg& msg) - { - static_cast(msg); - TS_TRACE("<-- WILLMSG"); - }); - - dataProc->setRegisterMsgReportCallback( - [](const RegisterMsg& msg) - { - static_cast(msg); - TS_TRACE("<-- REGISTER"); - }); - - dataProc->setRegackMsgReportCallback( - [](const RegackMsg& msg) - { - static_cast(msg); - TS_TRACE("<-- REGACK"); - }); - - dataProc->setPublishMsgReportCallback( - [](const PublishMsg& msg) - { - static_cast(msg); - TS_TRACE("<-- PUBLISH"); - }); - - dataProc->setPubackMsgReportCallback( - [](const PubackMsg& msg) - { - static_cast(msg); - TS_TRACE("<-- PUBACK"); - }); - - dataProc->setPubrecMsgReportCallback( - [](const PubrecMsg& msg) - { - static_cast(msg); - TS_TRACE("<-- PUBREC"); - }); - - dataProc->setPubrelMsgReportCallback( - [](const PubrelMsg& msg) - { - static_cast(msg); - TS_TRACE("<-- PUBREL"); - }); - - dataProc->setPubcompMsgReportCallback( - [](const PubcompMsg& msg) - { - static_cast(msg); - TS_TRACE("<-- PUBCOMP"); - }); - - dataProc->setSubscribeMsgReportCallback( - [](const SubscribeMsg& msg) - { - static_cast(msg); - TS_TRACE("<-- SUBSCRIBE"); - }); - - dataProc->setUnsubscribeMsgReportCallback( - [](const UnsubscribeMsg& msg) - { - static_cast(msg); - TS_TRACE("<-- UNSUBSCRIBE"); - }); - - dataProc->setPingreqMsgReportCallback( - [](const PingreqMsg& msg) - { - static_cast(msg); - TS_TRACE("<-- PINGREQ"); - }); - - dataProc->setPingrespMsgReportCallback( - [](const PingrespMsg& msg) - { - static_cast(msg); - TS_TRACE("<-- PINGRESP"); - }); - - dataProc->setDisconnectMsgReportCallback( - [](const DisconnectMsg& msg) - { - static_cast(msg); - TS_TRACE("<-- DISCONNECT"); - }); - - dataProc->setWilltopicupdMsgReportCallback( - [](const WilltopicupdMsg& msg) - { - static_cast(msg); - TS_TRACE("<-- WILLTOPICUPD"); - }); - - dataProc->setWillmsgupdMsgReportCallback( - [](const WillmsgupdMsg& msg) - { - static_cast(msg); - TS_TRACE("<-- WILLMSGUPD"); - }); - } - - return ptr; - } - - static void doGwInfo(CommonTestClient& client, DataProcessor& dataProc, TestBasicState& state) - { - verifySent_SearchgwMsg(state, dataProc, DefaultBroadcastRadius); - - clearState(state); - static const std::uint8_t GwId = 5U; - auto gwInfoData = dataProc.prepareGwinfoMsg(GwId); - TS_ASSERT(!gwInfoData.empty()); - - state.m_nextElapsedTicks = 1 * 1000; - dataFromGw(client, gwInfoData, "GWINFO"); - verifyCb_ReportedGw(state, GwId, CC_MqttsnGwStatus_Available); - verifyNoOtherEvent(state); - } - - static const std::string& doConnect( - CommonTestClient& client, - DataProcessor& dataProc, - TestBasicState& state, - bool hasWill = false) - { - - static const std::string ClientId = "test_client"; - static const std::string WillTopic("/this/is/will"); - static const std::vector WillData{ - 0x1, 0x2, 0x3, 0x4 - }; - static const CC_MqttsnQoS WillQos = CC_MqttsnQoS_AtLeastOnceDelivery; - static const bool WillRetain = false; - - static const auto WillInfoCreateFunc = - []() -> CC_MqttsnWillInfo - { - auto willInfo = CC_MqttsnWillInfo(); - willInfo.topic = WillTopic.c_str(); - willInfo.msg = &WillData[0]; - willInfo.msgLen = static_cast(WillData.size()); - willInfo.qos = WillQos; - willInfo.retain = WillRetain; - return willInfo; - }; - - static const CC_MqttsnWillInfo WillInfo = WillInfoCreateFunc(); - TS_TRACE("Connecting..."); - state.m_nextElapsedTicks = 0; - - const CC_MqttsnWillInfo* willInfo = nullptr; - if (hasWill) { - willInfo = &WillInfo; - } - auto result = - client.connect(DefaultClientId.c_str(), DefaultKeepAlive, true, willInfo); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_ConnectMsg(state, dataProc, ClientId, DefaultKeepAlive, true, willInfo != nullptr); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - - if (hasWill) { - auto willTopicReq = dataProc.prepareWilltopicreqMsg(); - dataFromGw(client, willTopicReq, "WILLTOPICREQ"); - verifySent_WilltopicMsg(state, dataProc, WillTopic, WillQos, WillRetain); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - auto willMsgReq = dataProc.prepareWillmsgreqMsg(); - dataFromGw(client, willMsgReq, "WILLMSGREQ"); - verifySent_WillmsgMsg(state, dataProc, WillData); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - } - - auto connackMsg = dataProc.prepareConnackMsg(cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(client, connackMsg, "CONNACK"); - verifyCb_ReportedConnect(state, CC_MqttsnAsyncOpStatus_Successful); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - verifyNoOtherEvent(state); - return ClientId; - } - - static void startClient(CommonTestClient& client) - { - auto result = client.start(); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - } - - static void doTopicRegister( - CommonTestClient& client, - DataProcessor& dataProc, - TestBasicState& state, - const std::string& topic, - CC_MqttsnTopicId topicId, - std::uint16_t msgId) - { - clearState(state); - auto registerMsg = dataProc.prepareRegisterMsg(topicId, msgId, topic); - state.m_nextElapsedTicks = 1000; - dataFromGw(client, registerMsg, "REGISTER"); - verifySent_RegackMsg(state, dataProc, topicId, msgId); - verifyNoOtherEvent(state); - } - - static void processOutput( - const TestBasicState& state, - DataProcessor& dataProc, - bool broadcast = false) - { - TS_ASSERT(!state.m_nextOutput.empty()); - TS_ASSERT_EQUALS(state.m_broadcast, broadcast); - dataProc.checkWrittenMsg(&state.m_nextOutput[0], state.m_nextOutput.size()); - } - - static void verifySent_SearchgwMsg( - const TestBasicState& state, - DataProcessor& dataProc, - unsigned short radius) - { - bool sent = false; - auto oldCb = dataProc.setSearchgwMsgReportCallback( - [&sent, radius](const SearchgwMsg& msg) - { - sent = true; - TS_TRACE("<-- SEARCHGW"); - TS_ASSERT_EQUALS(msg.field_radius().value(), radius); - }); - - processOutput(state, dataProc, true); - TS_ASSERT(sent); - - dataProc.setSearchgwMsgReportCallback(std::move(oldCb)); - } - - static void verifySent_ConnectMsg( - const TestBasicState& state, - DataProcessor& dataProc, - const std::string& clientId, - unsigned short keepAlivePeriod, - bool cleanSession, - bool hasWill) - { - bool sent = false; - auto oldCb = dataProc.setConnectMsgReportCallback( - [&sent, &clientId, keepAlivePeriod, cleanSession, hasWill](const ConnectMsg& msg) - { - sent = true; - TS_TRACE("<-- CONNECT"); - TS_ASSERT_EQUALS(msg.field_flags().field_mid().getBitValue_CleanSession(), cleanSession); - TS_ASSERT_EQUALS(msg.field_flags().field_mid().getBitValue_Will(), hasWill); - TS_ASSERT_EQUALS(msg.field_protocolId().value(), 1); - TS_ASSERT_EQUALS(msg.field_duration().value(), keepAlivePeriod); - TS_ASSERT_EQUALS(msg.field_clientId().value(), clientId); - }); - - processOutput(state, dataProc); - TS_ASSERT(sent); - - dataProc.setConnectMsgReportCallback(std::move(oldCb)); - } - - static void verifySent_WilltopicMsg( - const TestBasicState& state, - DataProcessor& dataProc, - const std::string& topic, - CC_MqttsnQoS qos, - bool retain) - { - bool sent = false; - auto oldCb = dataProc.setWilltopicMsgReportCallback( - [&sent, &topic, qos, retain](const WilltopicMsg& msg) - { - typedef typename std::decay::type MsgType; - static_assert(MsgType::InterfaceOptions::HasReadIterator, - "Does not define read iterator"); - static_assert(!MsgType::ImplOptions::HasNoReadImpl, - "Does not implement read"); - static_assert(MsgType::ImplOptions::HasMsgType, - "Does not define message type"); - - sent = true; - TS_TRACE("<-- WILLTOPIC"); - TS_ASSERT_EQUALS(msg.field_willTopic().value(), topic); - if (!topic.empty()) { - TS_ASSERT(msg.field_flags().doesExist()); - TS_ASSERT_EQUALS(msg.field_flags().field().field_mid().getBitValue_Retain(), retain); - TS_ASSERT_EQUALS(CommonTestClient::transformQos(msg.field_flags().field().field_qos().value()), qos); - } - else { - TS_ASSERT(msg.field_flags().isMissing()); - } - - }); - - processOutput(state, dataProc); - TS_ASSERT(sent); - dataProc.setWilltopicMsgReportCallback(std::move(oldCb)); - } - - static void verifySent_WillmsgMsg( - const TestBasicState& state, - DataProcessor& dataProc, - const std::vector& data) - { - bool sent = false; - auto oldCb = dataProc.setWillmsgMsgReportCallback( - [&sent, &data](const WillmsgMsg& msg) - { - sent = true; - TS_TRACE("<-- WILLMSG"); - TS_ASSERT_EQUALS(msg.field_willMsg().value(), data); - }); - - processOutput(state, dataProc); - TS_ASSERT(sent); - dataProc.setWillmsgMsgReportCallback(std::move(oldCb)); - } - - static std::uint16_t verifySent_RegisterMsg( - const TestBasicState& state, - DataProcessor& dataProc, - const std::string& topic) - { - std::uint16_t msgId = 0; - bool sent = false; - auto oldCb = dataProc.setRegisterMsgReportCallback( - [&msgId, &sent, &topic](const RegisterMsg& msg) - { - sent = true; - TS_TRACE("<-- REGISTER"); - TS_ASSERT_EQUALS(msg.field_topicId().value(), 0U); - TS_ASSERT_EQUALS(msg.field_topicName().value(), topic); - msgId = msg.field_msgId().value(); - }); - - processOutput(state, dataProc); - TS_ASSERT(sent); - dataProc.setRegisterMsgReportCallback(std::move(oldCb)); - return msgId; - - } - - static void verifySent_RegackMsg( - const TestBasicState& state, - DataProcessor& dataProc, - CC_MqttsnTopicId topicId, - std::uint16_t msgId, - cc_mqttsn::field::ReturnCodeVal retCode = cc_mqttsn::field::ReturnCodeVal::Accepted) - { - bool sent = false; - auto oldCb = dataProc.setRegackMsgReportCallback( - [&sent, topicId, msgId, retCode](const RegackMsg& msg) - { - sent = true; - TS_TRACE("<-- REGACK"); - - TS_ASSERT_EQUALS(msg.field_topicId().value(), topicId); - TS_ASSERT_EQUALS(msg.field_msgId().value(), msgId); - TS_ASSERT_EQUALS(msg.field_returnCode().value(), retCode); - }); - - processOutput(state, dataProc); - TS_ASSERT(sent); - dataProc.setRegackMsgReportCallback(std::move(oldCb)); - } - - static std::uint16_t verifySent_PublishMsg( - const TestBasicState& state, - DataProcessor& dataProc, - CC_MqttsnTopicId topicId, - const DataSeq& data, - TopicIdTypeVal topicIdType, - CC_MqttsnQoS qos, - bool retain, - bool duplicate) - { - std::uint16_t msgId = 0; - bool sent = false; - auto oldCb = dataProc.setPublishMsgReportCallback( - [&msgId, &sent, &data, topicId, topicIdType, qos, retain, duplicate](const PublishMsg& msg) - { - sent = true; - - TS_TRACE("<-- PUBLISH"); - TS_ASSERT_EQUALS(msg.field_flags().field_topicIdType().value(), topicIdType); - TS_ASSERT_EQUALS(msg.field_flags().field_mid().getBitValue_Retain(), retain); - TS_ASSERT_EQUALS(CommonTestClient::transformQos(msg.field_flags().field_qos().value()), qos); - TS_ASSERT_EQUALS(msg.field_flags().field_high().getBitValue_Dup(), duplicate); - TS_ASSERT_EQUALS(msg.field_topicId().value(), topicId); - TS_ASSERT_EQUALS(msg.field_data().value(), data); - - msgId = msg.field_msgId().value(); - TS_TRACE("msg_id=" + std::to_string(msgId)); - }); - - processOutput(state, dataProc); - TS_ASSERT(sent); - dataProc.setPublishMsgReportCallback(std::move(oldCb)); - return msgId; - } - - static void verifySent_PubackMsg( - const TestBasicState& state, - DataProcessor& dataProc, - CC_MqttsnTopicId topicId, - std::uint16_t msgId, - cc_mqttsn::field::ReturnCodeVal retCode = cc_mqttsn::field::ReturnCodeVal::Accepted) - { - bool sent = false; - auto oldCb = dataProc.setPubackMsgReportCallback( - [&sent, topicId, msgId, retCode](const PubackMsg& msg) - { - TS_TRACE("<-- PUBACK"); - sent = true; - - TS_ASSERT_EQUALS(msg.field_topicId().value(), topicId); - TS_ASSERT_EQUALS(msg.field_msgId().value(), msgId); - TS_ASSERT_EQUALS(msg.field_returnCode().value(), retCode); - }); - - processOutput(state, dataProc); - TS_ASSERT(sent); - dataProc.setPubackMsgReportCallback(std::move(oldCb)); - } - - static void verifySent_PubrecMsg( - const TestBasicState& state, - DataProcessor& dataProc, - std::uint16_t msgId) - { - bool sent = false; - auto oldCb = dataProc.setPubrecMsgReportCallback( - [&sent, msgId](const PubrecMsg& msg) - { - TS_TRACE("<-- PUBREC"); - sent = true; - - TS_ASSERT_EQUALS(msg.field_msgId().value(), msgId); - }); - - processOutput(state, dataProc); - TS_ASSERT(sent); - dataProc.setPubrecMsgReportCallback(std::move(oldCb)); - } - - static void verifySent_PubrelMsg( - const TestBasicState& state, - DataProcessor& dataProc, - std::uint16_t msgId) - { - bool sent = false; - auto oldCb = dataProc.setPubrelMsgReportCallback( - [&](const PubrelMsg& msg) - { - sent = true; - TS_TRACE("<-- PUBREL"); - TS_ASSERT_EQUALS(msg.field_msgId().value(), msgId); - - }); - - processOutput(state, dataProc); - TS_ASSERT(sent); - dataProc.setPubrelMsgReportCallback(std::move(oldCb)); - } - - static void verifySent_PubcompMsg( - const TestBasicState& state, - DataProcessor& dataProc, - std::uint16_t msgId) - { - bool sent = false; - auto oldCb = dataProc.setPubcompMsgReportCallback( - [&sent, msgId](const PubcompMsg& msg) - { - TS_TRACE("<-- PUBCOMP"); - sent = true; - TS_ASSERT_EQUALS(msg.field_msgId().value(), msgId); - }); - - processOutput(state, dataProc); - TS_ASSERT(sent); - dataProc.setPubcompMsgReportCallback(std::move(oldCb)); - } - - static std::uint16_t verifySent_SubsribeMsg( - const TestBasicState& state, - DataProcessor& dataProc, - const std::string& topic, - CC_MqttsnQoS qos, - bool duplicate) - { - bool sent = false; - std::uint16_t msgId = 0U; - auto oldCb = dataProc.setSubscribeMsgReportCallback( - [&sent, &msgId, &topic, qos, duplicate](const SubscribeMsg& msg) - { - sent = true; - TS_TRACE("<-- SUBSRIBE"); - - bool shortName = - (topic.size() == 2U) && - (topic.find_first_of("#+") == std::string::npos); - - if (shortName) { - TS_TRACE("Short topic detected"); - TS_ASSERT_EQUALS(msg.field_flags().field_topicIdType().value(), TopicIdTypeVal::ShortTopicName); - TS_ASSERT(msg.field_topicId().doesExist()); - TS_ASSERT(msg.field_topicName().isMissing()); - TS_ASSERT_EQUALS(topic.size(), 2U); - auto topicId = ((std::uint16_t)topic[0] << 8) | ((std::uint8_t)topic[1]); - TS_ASSERT_EQUALS(msg.field_topicId().field().value(), topicId); - } - else { - TS_ASSERT_EQUALS(msg.field_flags().field_topicIdType().value(), TopicIdTypeVal::Normal); - TS_ASSERT(msg.field_topicId().isMissing()); - TS_ASSERT(msg.field_topicName().doesExist()); - TS_ASSERT_EQUALS(msg.field_topicName().field().value(), topic); - } - - TS_ASSERT_EQUALS(transformQos(msg.field_flags().field_qos().value()), qos); - TS_ASSERT_EQUALS(msg.field_flags().field_high().getBitValue_Dup(), duplicate); - - - msgId = msg.field_msgId().value(); - }); - - processOutput(state, dataProc); - TS_ASSERT(sent); - dataProc.setSubscribeMsgReportCallback(std::move(oldCb)); - return msgId; - } - - static std::uint16_t verifySent_SubsribeMsg( - const TestBasicState& state, - DataProcessor& dataProc, - CC_MqttsnTopicId topicId, - CC_MqttsnQoS qos, - bool duplicate) - { - bool sent = false; - std::uint16_t msgId = 0U; - auto oldCb = dataProc.setSubscribeMsgReportCallback( - [&sent, &msgId, topicId, qos, duplicate](const SubscribeMsg& msg) - { - sent = true; - TS_TRACE("<-- SUBSCRIBE"); - - TS_ASSERT_EQUALS(msg.field_flags().field_topicIdType().value(), TopicIdTypeVal::PredefinedTopicId); - TS_ASSERT_EQUALS(transformQos(msg.field_flags().field_qos().value()), qos); - TS_ASSERT_EQUALS(msg.field_flags().field_high().getBitValue_Dup(), duplicate); - TS_ASSERT(msg.field_topicId().doesExist()); - TS_ASSERT(msg.field_topicName().isMissing()); - TS_ASSERT_EQUALS(msg.field_topicId().field().value(), topicId); - - msgId = msg.field_msgId().value(); - }); - - processOutput(state, dataProc); - TS_ASSERT(sent); - dataProc.setSubscribeMsgReportCallback(std::move(oldCb)); - return msgId; - } - - static std::uint16_t verifySent_UnsubsribeMsg( - const TestBasicState& state, - DataProcessor& dataProc, - const std::string& topic) - { - bool sent = false; - std::uint16_t msgId = 0U; - auto oldCb = dataProc.setUnsubscribeMsgReportCallback( - [&sent, &msgId, &topic](const UnsubscribeMsg& msg) - { - sent = true; - TS_TRACE("<-- UNSUBSRIBE"); - - bool shortName = - (topic.size() == 2U) && - (topic.find_first_of("#+") == std::string::npos); - - if (shortName) { - TS_ASSERT_EQUALS(msg.field_flags().field_topicIdType().value(), TopicIdTypeVal::ShortTopicName); - TS_ASSERT(msg.field_topicId().doesExist()); - TS_ASSERT(msg.field_topicName().isMissing()); - TS_ASSERT_EQUALS(topic.size(), 2U); - auto topicId = ((std::uint16_t)topic[0] << 8) | ((std::uint8_t)topic[1]); - TS_ASSERT_EQUALS(msg.field_topicId().field().value(), topicId); - } - else { - TS_ASSERT_EQUALS(msg.field_flags().field_topicIdType().value(), TopicIdTypeVal::Normal); - TS_ASSERT(msg.field_topicId().isMissing()); - TS_ASSERT(msg.field_topicName().doesExist()); - TS_ASSERT_EQUALS(msg.field_topicName().field().value(), topic); - } - - msgId = msg.field_msgId().value(); - }); - - processOutput(state, dataProc); - TS_ASSERT(sent); - dataProc.setUnsubscribeMsgReportCallback(std::move(oldCb)); - return msgId; - } - - static std::uint16_t verifySent_UnsubsribeMsg( - const TestBasicState& state, - DataProcessor& dataProc, - CC_MqttsnTopicId topicId) - { - bool sent = false; - std::uint16_t msgId = 0U; - auto oldCb = dataProc.setUnsubscribeMsgReportCallback( - [&sent, &msgId, topicId](const UnsubscribeMsg& msg) - { - sent = true; - TS_TRACE("<-- UNSUBSCRIBE"); - - TS_ASSERT_EQUALS(msg.field_flags().field_topicIdType().value(), TopicIdTypeVal::PredefinedTopicId); - TS_ASSERT(msg.field_topicId().doesExist()); - TS_ASSERT(msg.field_topicName().isMissing()); - TS_ASSERT_EQUALS(msg.field_topicId().field().value(), topicId); - - msgId = msg.field_msgId().value(); - }); - - processOutput(state, dataProc); - TS_ASSERT(sent); - dataProc.setUnsubscribeMsgReportCallback(std::move(oldCb)); - return msgId; - } - - static void verifySent_PingreqMsg( - const TestBasicState& state, - DataProcessor& dataProc, - const std::string& clientId = std::string()) - { - bool sent = false; - auto oldCb = dataProc.setPingreqMsgReportCallback( - [&sent, &clientId](const PingreqMsg& msg) - { - sent = true; - TS_TRACE("<-- PINGREQ"); - TS_ASSERT_EQUALS(msg.field_clientId().value(), clientId); - }); - - processOutput(state, dataProc); - TS_ASSERT(sent); - dataProc.setPingreqMsgReportCallback(std::move(oldCb)); - } - - static void verifySent_PingrespMsg( - const TestBasicState& state, - DataProcessor& dataProc) - { - bool sent = false; - auto oldCb = dataProc.setPingrespMsgReportCallback( - [&sent](const PingrespMsg& msg) - { - sent = true; - static_cast(msg); - TS_TRACE("<-- PINGRESP"); - }); - - processOutput(state, dataProc); - TS_ASSERT(sent); - dataProc.setPingrespMsgReportCallback(std::move(oldCb)); - } - - static void verifySent_DisconnectMsg( - const TestBasicState& state, - DataProcessor& dataProc, - std::uint16_t duration = 0U) - { - bool sent = false; - auto oldCb = dataProc.setDisconnectMsgReportCallback( - [&sent, duration](const DisconnectMsg& msg) - { - sent = true; - static_cast(msg); - TS_TRACE("<-- DISCONNECT"); - - if (duration != 0) { - TS_ASSERT(msg.field_duration().doesExist()); - TS_ASSERT_EQUALS(msg.field_duration().field().value(), duration); - } - }); - - processOutput(state, dataProc); - TS_ASSERT(sent); - dataProc.setDisconnectMsgReportCallback(std::move(oldCb)); - } - - static void verifySent_WilltopicupdMsg( - const TestBasicState& state, - DataProcessor& dataProc, - const std::string& topic, - CC_MqttsnQoS qos = CC_MqttsnQoS(), - bool retain = false) - { - bool sent = false; - auto oldCb = dataProc.setWilltopicupdMsgReportCallback( - [&sent, &topic, qos, retain](const WilltopicupdMsg& msg) - { - sent = true; - static_cast(msg); - TS_TRACE("<-- WILLTOPICUPD"); - - TS_ASSERT_EQUALS(msg.field_willTopic().value(), topic); - if (!topic.empty()) { - TS_ASSERT_EQUALS(transformQos(msg.field_flags().field().field_qos().value()), qos); - TS_ASSERT_EQUALS(msg.field_flags().field().field_mid().getBitValue_Retain(), retain); - TS_ASSERT(msg.field_flags().doesExist()); - } - else { - TS_ASSERT(msg.field_flags().isMissing()); - } - }); - - processOutput(state, dataProc); - TS_ASSERT(sent); - dataProc.setWilltopicupdMsgReportCallback(std::move(oldCb)); - } - - static void verifySent_WillmsgupdMsg( - const TestBasicState& state, - DataProcessor& dataProc, - const DataSeq& data) - { - bool sent = false; - auto oldCb = dataProc.setWillmsgupdMsgReportCallback( - [&sent, &data](const WillmsgupdMsg& msg) - { - sent = true; - static_cast(msg); - TS_TRACE("<-- WILLMSGUPD"); - - TS_ASSERT_EQUALS(msg.field_willMsg().value(), data); - }); - - processOutput(state, dataProc); - TS_ASSERT(sent); - dataProc.setWillmsgupdMsgReportCallback(std::move(oldCb)); - } - - static void verifyCb_ReportedGw( - TestBasicState& state, - std::uint16_t gwId, - CC_MqttsnGwStatus status) - { - TS_ASSERT(!state.m_reportedGws.empty()); - if (state.m_reportedGws.empty()) { - return; - } - - auto& gw = state.m_reportedGws.front(); - TS_ASSERT_EQUALS(gw.m_id, gwId); - TS_ASSERT_EQUALS(gw.m_status, status); - state.m_reportedGws.pop_front(); - } - - static void verifyCb_ReportedMessage( - TestBasicState& state, - const std::string& topic, - CC_MqttsnTopicId topicId, - const std::vector& data, - CC_MqttsnQoS qos, - bool retain) - { - TS_ASSERT(!state.m_reportedMsgs.empty()); - if (state.m_reportedMsgs.empty()) { - return; - } - - auto& msg = state.m_reportedMsgs.front(); - TS_ASSERT_EQUALS(msg.m_topic, topic); - TS_ASSERT_EQUALS(msg.m_topicId, topicId); - TS_ASSERT_EQUALS(msg.m_data, data); - TS_ASSERT_EQUALS(msg.m_qos, qos); - TS_ASSERT_EQUALS(msg.m_retain, retain); - state.m_reportedMsgs.pop_front(); - } - - static void verifyCb_ReportedGwDisconnect( - TestBasicState& state) - { - TS_ASSERT(!state.m_reportedGwDisconnects.empty()); - if (state.m_reportedGwDisconnects.empty()) { - return; - } - - state.m_reportedGwDisconnects.pop_front(); - } - - static void verifyCb_ReportedConnect( - TestBasicState& state, - CC_MqttsnAsyncOpStatus status) - { - TS_ASSERT(!state.m_reportedConnects.empty()); - if (state.m_reportedConnects.empty()) { - return; - } - - TS_ASSERT_EQUALS(state.m_reportedConnects.front(), status); - state.m_reportedConnects.pop_front(); - } - - static void verifyCb_ReportedDisconnect( - TestBasicState& state, - CC_MqttsnAsyncOpStatus status) - { - TS_ASSERT(!state.m_reportedDisconnects.empty()); - if (state.m_reportedDisconnects.empty()) { - return; - } - - TS_ASSERT_EQUALS(state.m_reportedDisconnects.front(), status); - state.m_reportedDisconnects.pop_front(); - } - - static void verifyCb_ReportedPublish( - TestBasicState& state, - CC_MqttsnAsyncOpStatus status) - { - TS_ASSERT(!state.m_reportedPublishes.empty()); - if (state.m_reportedPublishes.empty()) { - return; - } - - TS_ASSERT_EQUALS(state.m_reportedPublishes.front(), status); - state.m_reportedPublishes.pop_front(); - } - - static void verifyCb_ReportedSubsribe( - TestBasicState& state, - CC_MqttsnAsyncOpStatus status, - CC_MqttsnQoS qos) - { - TS_ASSERT(!state.m_reportedSubscribes.empty()); - if (state.m_reportedSubscribes.empty()) { - return; - } - - auto& elem = state.m_reportedSubscribes.front(); - - TS_ASSERT_EQUALS(elem.first, status); - TS_ASSERT_EQUALS(elem.second, qos); - state.m_reportedSubscribes.pop_front(); - } - - static void verifyCb_ReportedUnsubsribe( - TestBasicState& state, - CC_MqttsnAsyncOpStatus status) - { - TS_ASSERT(!state.m_reportedUnsubscribes.empty()); - if (state.m_reportedUnsubscribes.empty()) { - return; - } - - auto& elem = state.m_reportedUnsubscribes.front(); - - TS_ASSERT_EQUALS(elem, status); - state.m_reportedUnsubscribes.pop_front(); - } - - static void verifyCb_ReportedWillUpdate( - TestBasicState& state, - CC_MqttsnAsyncOpStatus status) - { - TS_ASSERT(!state.m_reportedWillUpdates.empty()); - if (state.m_reportedWillUpdates.empty()) { - return; - } - - auto& elem = state.m_reportedWillUpdates.front(); - - TS_ASSERT_EQUALS(elem, status); - state.m_reportedWillUpdates.pop_front(); - } - - static void verifyCb_ReportedWillTopicUpdate( - TestBasicState& state, - CC_MqttsnAsyncOpStatus status) - { - TS_ASSERT(!state.m_reportedWillTopicUpdates.empty()); - if (state.m_reportedWillTopicUpdates.empty()) { - return; - } - - auto& elem = state.m_reportedWillTopicUpdates.front(); - - TS_ASSERT_EQUALS(elem, status); - state.m_reportedWillTopicUpdates.pop_front(); - } - - static void verifyCb_ReportedWillMsgUpdate( - TestBasicState& state, - CC_MqttsnAsyncOpStatus status) - { - TS_ASSERT(!state.m_reportedWillMsgUpdates.empty()); - if (state.m_reportedWillMsgUpdates.empty()) { - return; - } - - auto& elem = state.m_reportedWillMsgUpdates.front(); - - TS_ASSERT_EQUALS(elem, status); - state.m_reportedWillMsgUpdates.pop_front(); - } - - static void verifyCb_ReportedSleep( - TestBasicState& state, - CC_MqttsnAsyncOpStatus status) - { - TS_ASSERT(!state.m_reportedSleeps.empty()); - if (state.m_reportedSleeps.empty()) { - return; - } - - auto& elem = state.m_reportedSleeps.front(); - - TS_ASSERT_EQUALS(elem, status); - state.m_reportedSleeps.pop_front(); - } - - static void verifyCb_ReportedReconnect( - TestBasicState& state, - CC_MqttsnAsyncOpStatus status) - { - TS_ASSERT(!state.m_reportedReconnects.empty()); - if (state.m_reportedReconnects.empty()) { - return; - } - - auto& elem = state.m_reportedReconnects.front(); - - TS_ASSERT_EQUALS(elem, status); - state.m_reportedReconnects.pop_front(); - } - - static void verifyCb_ReportedMessageChecks( - TestBasicState& state, - CC_MqttsnAsyncOpStatus status) - { - TS_ASSERT(!state.m_reportedMessageChecks.empty()); - if (state.m_reportedMessageChecks.empty()) { - return; - } - - auto& elem = state.m_reportedMessageChecks.front(); - - TS_ASSERT_EQUALS(elem, status); - state.m_reportedMessageChecks.pop_front(); - } - - static CC_MqttsnQoS transformQos(cc_mqttsn::field::QosVal val) - { - return CommonTestClient::transformQos(val); - } - - static cc_mqttsn::field::QosVal transformQos(CC_MqttsnQoS val) - { - return CommonTestClient::transformQos(val); - } -}; - -const std::string ClientBasic::DefaultClientId("test_client"); - -void ClientBasic::test1() -{ - auto client = allocClient(); - static_cast(client); -} - -void ClientBasic::test2() -{ - DataProcessor dataProc; - TestBasicState state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - TS_TRACE("Started"); - verifySent_SearchgwMsg(state, dataProc, DefaultBroadcastRadius); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - TS_TRACE("Tick"); - client->tick(); - verifySent_SearchgwMsg(state, dataProc, DefaultBroadcastRadius); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - static const std::uint8_t GwId = 5U; - auto gwInfoData = dataProc.prepareGwinfoMsg(GwId); - TS_ASSERT(!gwInfoData.empty()); - - assert(state.m_nextRequestedTicks == 0U); - state.m_nextElapsedTicks = 1 * 1000; - dataFromGw(*client, gwInfoData, "GWINFO"); - - verifyCb_ReportedGw(state, GwId, CC_MqttsnGwStatus_Available); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, MaxPeriod); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - verifyCb_ReportedGw(state, GwId, CC_MqttsnGwStatus_TimedOut); - verifySent_SearchgwMsg(state, dataProc, DefaultBroadcastRadius); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - auto advertiseData = dataProc.prepareAdvertiseMsg(GwId, (DefaultAdvertisePeriod / 1000) + 1); - TS_ASSERT(!advertiseData.empty()); - state.m_nextElapsedTicks = 1 * 1000; - - dataFromGw(*client, advertiseData, "ADVERTISE"); - verifyCb_ReportedGw(state, GwId, CC_MqttsnGwStatus_Available); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, (DefaultAdvertisePeriod + 1000) * 3); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - verifyCb_ReportedGw(state, GwId, CC_MqttsnGwStatus_TimedOut); - verifySent_SearchgwMsg(state, dataProc, DefaultBroadcastRadius); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); -} - -void ClientBasic::test3() -{ - // Connect without will - DataProcessor dataProc; - struct Test3State : public TestBasicState - { - CC_MqttsnAsyncOpStatus m_connectionStatus = CC_MqttsnAsyncOpStatus_Invalid; - }; - - Test3State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - - static const std::string ClientId = "bla"; - static const unsigned short KeepAlive = 60; - - client->setConnectCompleteCallback( - [&](CC_MqttsnAsyncOpStatus val) - { - // First connect is expected to be timed out - TS_TRACE("Connect complete with status: " + std::to_string((int)val)); - TS_ASSERT_EQUALS(val, CC_MqttsnAsyncOpStatus_NoResponse); - - client->setConnectCompleteCallback( - [&](CC_MqttsnAsyncOpStatus val2) - { - TS_TRACE("Connect complete with status: " + std::to_string((int)val2)); - TS_ASSERT_EQUALS(val2, CC_MqttsnAsyncOpStatus_Successful); - state.m_connectionStatus = val2; - }); - - TS_TRACE("Reconnecting..."); - auto result2 = - client->connect(ClientId.c_str(), KeepAlive, true, nullptr); - - TS_ASSERT_EQUALS(result2, CC_MqttsnErrorCode_Success); - }); - - TS_TRACE("Connecting..."); - state.m_nextElapsedTicks = 0; - auto result = - client->connect(ClientId.c_str(), KeepAlive, true, nullptr); - - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - - verifySent_ConnectMsg(state, dataProc, ClientId, KeepAlive, true, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - clearState(state); - client->tick(); - verifySent_ConnectMsg(state, dataProc, ClientId, KeepAlive, true, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - verifySent_ConnectMsg(state, dataProc, ClientId, KeepAlive, true, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - - // Timeout and new connect attempt is expected here - - verifySent_ConnectMsg(state, dataProc, ClientId, KeepAlive, true, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - clearState(state); - auto connackMsg = dataProc.prepareConnackMsg(cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, connackMsg, "CONNACK"); - - TS_ASSERT_EQUALS(state.m_connectionStatus, CC_MqttsnAsyncOpStatus_Successful); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, KeepAlive * 1000); - verifyNoOtherEvent(state); -} - -void ClientBasic::test4() -{ - // Connect with will - DataProcessor dataProc; - TestBasicState state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - - static const std::string ClientId = "bla"; - static const unsigned short KeepAlive = 60; - static const std::string WillTopic("/this/is/will"); - static const std::vector WillData{ - 0x1, 0x2, 0x3, 0x4 - }; - static const CC_MqttsnQoS WillQos = CC_MqttsnQoS_AtLeastOnceDelivery; - static const bool WillRetain = false; - - TS_TRACE("Connecting..."); - auto willInfo = CC_MqttsnWillInfo(); - willInfo.topic = WillTopic.c_str(); - willInfo.msg = &WillData[0]; - willInfo.msgLen = static_cast(WillData.size()); - willInfo.qos = WillQos; - willInfo.retain = WillRetain; - - state.m_nextElapsedTicks = 0; - auto result = - client->connect(ClientId.c_str(), KeepAlive, true, &willInfo); - - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_ConnectMsg(state, dataProc, ClientId, KeepAlive, true, true); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - - // Send ack message, check ignored - state.m_nextElapsedTicks = 1000; - auto connackMsg = dataProc.prepareConnackMsg(cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, connackMsg, "CONNACK"); - // Ack must be ignored - TS_ASSERT(state.m_nextOutput.empty()) - - auto willTopicReq = dataProc.prepareWilltopicreqMsg(); - dataFromGw(*client, willTopicReq, "WILLTOPICREQ"); - verifySent_WilltopicMsg(state, dataProc, WillTopic, WillQos, WillRetain); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); // Timeout is expected, new connect attempt - - verifySent_ConnectMsg(state, dataProc, ClientId, KeepAlive, true, true); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - dataFromGw(*client, willTopicReq, "WILLTOPICREQ"); - verifySent_WilltopicMsg(state, dataProc, WillTopic, WillQos, WillRetain); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto willMsgReq = dataProc.prepareWillmsgreqMsg(); - dataFromGw(*client, willMsgReq, "WILLMSGREQ"); - verifySent_WillmsgMsg(state, dataProc, WillData); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - static const unsigned NextTicks = 1000; - state.m_nextElapsedTicks = NextTicks; - dataFromGw(*client, connackMsg, "CONNACK"); - - // check connected - verifyCb_ReportedConnect(state, CC_MqttsnAsyncOpStatus_Successful); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, (KeepAlive * 1000) - NextTicks); - verifyNoOtherEvent(state); -} - -void ClientBasic::test5() -{ - DataProcessor dataProc; - TestBasicState state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - auto nextTicks = state.m_nextRequestedTicks; - TS_ASSERT_EQUALS(nextTicks, DefaultKeepAlive * 1000); - - client->tick(); - verifySent_PingreqMsg(state, dataProc); - verifyNoOtherEvent(state); - - clearState(state); - static const unsigned NextTicks = 1000; - state.m_nextElapsedTicks = NextTicks; - auto pingRespMsg = dataProc.preparePingrespMsg(); - dataFromGw(*client, pingRespMsg, "PINGRESP"); - - TS_ASSERT(state.m_nextOutput.empty()); - TS_ASSERT(!state.m_broadcast); - - nextTicks = state.m_nextRequestedTicks; - TS_ASSERT_EQUALS(nextTicks, (DefaultKeepAlive * 1000) - NextTicks); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - verifySent_PingreqMsg(state, dataProc); - verifyNoOtherEvent(state); - - nextTicks = state.m_nextRequestedTicks; - clearState(state); - client->tick(); - verifySent_PingreqMsg(state, dataProc); - verifyNoOtherEvent(state); - - nextTicks = state.m_nextRequestedTicks; - clearState(state); - client->tick(); - verifySent_PingreqMsg(state, dataProc); - verifyNoOtherEvent(state); - - nextTicks = state.m_nextRequestedTicks; - clearState(state); - client->tick(); - verifyCb_ReportedGwDisconnect(state); - verifyNoOtherEvent(state); -} - -void ClientBasic::test6() -{ - DataProcessor dataProc; - TestBasicState state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto pingreqMsg = dataProc.preparePingreqMsg(); - dataFromGw(*client, pingreqMsg, "PINGREQ"); - verifySent_PingrespMsg(state, dataProc); - verifyNoOtherEvent(state); - - clearState(state); - auto disconnectMsg = dataProc.prepareDisconnectMsg(); - dataFromGw(*client, disconnectMsg, "DISCONNECT"); - verifyCb_ReportedGwDisconnect(state); - verifyNoOtherEvent(state); -} - -void ClientBasic::test7() -{ - DataProcessor dataProc; - TestBasicState state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto result = client->disconnect(); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_DisconnectMsg(state, dataProc); - verifyNoOtherEvent(state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - - state.m_nextElapsedTicks = 1000; - auto disconnectMsg = dataProc.prepareDisconnectMsg(); - dataFromGw(*client, disconnectMsg, "DISCONNECT"); - verifyCb_ReportedDisconnect(state, CC_MqttsnAsyncOpStatus_Successful); - verifyNoOtherEvent(state); -} - -void ClientBasic::test8() -{ - DataProcessor dataProc; - TestBasicState state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto result = client->disconnect(); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_DisconnectMsg(state, dataProc); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - verifySent_DisconnectMsg(state, dataProc); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - verifySent_DisconnectMsg(state, dataProc); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - - TS_ASSERT(state.m_nextOutput.empty()); - verifyCb_ReportedDisconnect(state, CC_MqttsnAsyncOpStatus_NoResponse); - verifyNoOtherEvent(state); -} - -void ClientBasic::test9() -{ - DataProcessor dataProc; - typedef TestBasicState Test9State; - Test9State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - - static const std::string ClientId = "bla"; - static const unsigned short KeepAlive = 60; - - TS_TRACE("Connecting..."); - state.m_nextElapsedTicks = 0; - auto result = - client->connect(ClientId.c_str(), KeepAlive, true, nullptr); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_ConnectMsg(state, dataProc, ClientId, KeepAlive, true, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - verifySent_ConnectMsg(state, dataProc, ClientId, KeepAlive, true, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - bool cancelResult = client->cancel(); - TS_ASSERT(cancelResult); - verifyCb_ReportedConnect(state, CC_MqttsnAsyncOpStatus_Aborted); - verifyNoOtherEvent(state); -} - -void ClientBasic::test10() -{ - DataProcessor dataProc; - typedef TestBasicState Test10State; - Test10State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto result = client->disconnect(); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_DisconnectMsg(state, dataProc); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - state.m_nextElapsedTicks = 1000; - bool cancelResult = client->cancel(); - TS_ASSERT(cancelResult); - verifyCb_ReportedDisconnect(state, CC_MqttsnAsyncOpStatus_Aborted); - verifyNoOtherEvent(state); -} - -void ClientBasic::test11() -{ - DataProcessor dataProc; - typedef TestBasicState Test11State; - Test11State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const CC_MqttsnTopicId TopicId = 0x0123; - static const auto TopicIdType = TopicIdTypeVal::PredefinedTopicId; - static const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtMostOnceDelivery; - static const bool Retain = true; - static const std::vector Data = { - 0x01, 0x02, 0x03, 0x04, 0xab, 0xcd, 0xef - }; - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto result = client->publishId(TopicId, &Data[0], Data.size(), Qos, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_PublishMsg(state, dataProc, TopicId, Data, TopicIdType, Qos, Retain, false); - verifyCb_ReportedPublish(state, CC_MqttsnAsyncOpStatus_Successful); - verifyNoOtherEvent(state); -} - -void ClientBasic::test12() -{ - DataProcessor dataProc; - typedef TestBasicState Test12State; - Test12State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - - static const CC_MqttsnTopicId TopicId = 0x0123; - static const auto TopicIdType = TopicIdTypeVal::PredefinedTopicId; - static const CC_MqttsnQoS Qos = CC_MqttsnQoS_NoGwPublish; - static const bool Retain = true; - static const std::vector Data = { - 0x01, 0x02, 0x03, 0x04, 0xab, 0xcd, 0xef - }; - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto result = client->publishId(TopicId, &Data[0], Data.size(), CC_MqttsnQoS_AtMostOnceDelivery, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_NotConnected); - TS_ASSERT(state.m_reportedPublishes.empty()); - TS_ASSERT(state.m_nextOutput.empty()); - verifyNoOtherEvent(state); - - result = client->publishId(TopicId, &Data[0], Data.size(), Qos, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_PublishMsg(state, dataProc, TopicId, Data, TopicIdType, Qos, Retain, false); - verifyCb_ReportedPublish(state, CC_MqttsnAsyncOpStatus_Successful); - verifyNoOtherEvent(state); -} - -void ClientBasic::test13() -{ - DataProcessor dataProc; - typedef TestBasicState Test13State; - Test13State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const CC_MqttsnTopicId TopicId = 0x0123; - static const auto TopicIdType = TopicIdTypeVal::PredefinedTopicId; - static const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; - static const bool Retain = true; - static const std::vector Data = { - 0x01, 0x02, 0x03, 0x04, 0xab, 0xcd, 0xef - }; - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto result = client->publishId(TopicId, &Data[0], Data.size(), Qos, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_PublishMsg(state, dataProc, TopicId, Data, TopicIdTypeVal::PredefinedTopicId, Qos, Retain, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - TS_ASSERT(state.m_reportedPublishes.empty()); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - auto msgId = verifySent_PublishMsg(state, dataProc, TopicId, Data, TopicIdType, Qos, Retain, true); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - TS_ASSERT(state.m_reportedPublishes.empty()); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto pubackMsg = dataProc.preparePubackMsg(TopicId, static_cast(msgId + 1), cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, pubackMsg, "PUBACK"); - TS_ASSERT(state.m_reportedPublishes.empty()); - TS_ASSERT(state.m_nextOutput.empty()); // must be ignored - verifyNoOtherEvent(state); - - clearState(state); - pubackMsg = dataProc.preparePubackMsg(TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, pubackMsg, "PUBACK"); - verifyCb_ReportedPublish(state, CC_MqttsnAsyncOpStatus_Successful); - verifyNoOtherEvent(state); -} - -void ClientBasic::test14() -{ - DataProcessor dataProc; - typedef TestBasicState Test14State; - Test14State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const CC_MqttsnTopicId TopicId = 0xabcd; - static const auto TopicIdType = TopicIdTypeVal::PredefinedTopicId; - static const CC_MqttsnQoS Qos = CC_MqttsnQoS_ExactlyOnceDelivery; - static const bool Retain = false; - static const std::vector Data = { - 0x01, 0x02, 0x03, 0x04, 0xab, 0xcd, 0xef - }; - - clearState(state); - static const unsigned NextTicks = 1000; - state.m_nextElapsedTicks = NextTicks; - auto result = client->publishId(TopicId, &Data[0], Data.size(), Qos, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - auto msgId = verifySent_PublishMsg(state, dataProc, TopicId, Data, TopicIdType, Qos, Retain, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - TS_ASSERT(state.m_reportedPublishes.empty()); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = NextTicks; - auto pubackMsg = dataProc.preparePubackMsg(TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::InvalidTopicId); - dataFromGw(*client, pubackMsg, "PUBACK"); - verifyCb_ReportedPublish(state, CC_MqttsnAsyncOpStatus_InvalidId); - verifyNoOtherEvent(state); -} - -void ClientBasic::test15() -{ - DataProcessor dataProc; - typedef TestBasicState Test15State; - Test15State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const CC_MqttsnTopicId TopicId = 0xabcd; - static const auto TopicIdType = TopicIdTypeVal::PredefinedTopicId; - static const CC_MqttsnQoS Qos = CC_MqttsnQoS_ExactlyOnceDelivery; - static const bool Retain = true; - static const std::vector Data = { - 0x01, 0x02, 0x03, 0x04, 0xab, 0xcd, 0xef - }; - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto result = client->publishId(TopicId, &Data[0], Data.size(), Qos, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - - auto msgId = verifySent_PublishMsg(state, dataProc, TopicId, Data, TopicIdType, Qos, Retain, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - TS_ASSERT(state.m_reportedPublishes.empty()); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 2000; - auto pubrecMsg = dataProc.preparePubrecMsg(msgId); - dataFromGw(*client, pubrecMsg, "PUBREC"); - verifySent_PubrelMsg(state, dataProc, msgId); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - TS_ASSERT(state.m_reportedPublishes.empty()); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - verifySent_PubrelMsg(state, dataProc, msgId); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - TS_ASSERT(state.m_reportedPublishes.empty()); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto pubcompMsg = dataProc.preparePubcompMsg(msgId); - dataFromGw(*client, pubcompMsg, "PUBCOMP"); - verifyCb_ReportedPublish(state, CC_MqttsnAsyncOpStatus_Successful); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, (DefaultKeepAlive * 1000) - 1000); - verifyNoOtherEvent(state); -} - -void ClientBasic::test16() -{ - DataProcessor dataProc; - typedef TestBasicState Test16State; - Test16State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const std::string Topic("this/is/topic"); - static const auto TopicIdType = TopicIdTypeVal::Normal; - static const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtMostOnceDelivery; - static const bool Retain = false; - static const std::vector Data = { - 0x01, 0x02, 0x03, 0x04, 0xab, 0xcd, 0xef - }; - - static const CC_MqttsnTopicId TopicId = 0x1234; - clearState(state); - - state.m_nextElapsedTicks = 1000; - auto result = client->publish(Topic, &Data[0], Data.size(), Qos, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - auto msgId = verifySent_RegisterMsg(state, dataProc, Topic); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - TS_ASSERT(state.m_reportedPublishes.empty()); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto regackMsg = dataProc.prepareRegackMsg(TopicId, static_cast(msgId + 1), cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, regackMsg, "REGACK"); - TS_ASSERT(state.m_reportedPublishes.empty()); - TS_ASSERT(state.m_nextOutput.empty()); - verifyNoOtherEvent(state); - - regackMsg = dataProc.prepareRegackMsg(TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, regackMsg, "REGACK"); - msgId = verifySent_PublishMsg(state, dataProc, TopicId, Data, TopicIdType, Qos, Retain, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - verifyCb_ReportedPublish(state, CC_MqttsnAsyncOpStatus_Successful); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - - result = client->publish(Topic, &Data[0], Data.size(), Qos, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - msgId = verifySent_PublishMsg(state, dataProc, TopicId, Data, TopicIdType, Qos, Retain, false); - verifyCb_ReportedPublish(state, CC_MqttsnAsyncOpStatus_Successful); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000 - state.m_nextElapsedTicks); - verifyNoOtherEvent(state); -} - -void ClientBasic::test17() -{ - DataProcessor dataProc; - TestBasicState state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const std::string Topic("this/is/topic"); - static const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtMostOnceDelivery; - static const bool Retain = false; - static const std::vector Data = { - 0x01, 0x02, 0x03, 0x04, 0xab, 0xcd, 0xef - }; - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto result = client->publish(Topic, &Data[0], Data.size(), Qos, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_RegisterMsg(state, dataProc, Topic); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - TS_ASSERT(state.m_reportedPublishes.empty()); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - verifySent_RegisterMsg(state, dataProc, Topic); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - TS_ASSERT(state.m_reportedPublishes.empty()); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - verifySent_RegisterMsg(state, dataProc, Topic); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - TS_ASSERT(state.m_reportedPublishes.empty()); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - verifyCb_ReportedPublish(state, CC_MqttsnAsyncOpStatus_NoResponse); - TS_ASSERT(state.m_nextOutput.empty()); - verifyNoOtherEvent(state); - - clearState(state); - client->reconnect(); - verifySent_ConnectMsg(state, dataProc, DefaultClientId, DefaultKeepAlive, false, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - auto connackMsg = dataProc.prepareConnackMsg(cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, connackMsg, "CONNACK"); - verifyCb_ReportedReconnect(state, CC_MqttsnAsyncOpStatus_Successful); - verifyNoOtherEvent(state); -} - -void ClientBasic::test18() -{ - DataProcessor dataProc; - typedef TestBasicState Test18State; - Test18State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const std::string Topic("this/is/topic"); - static const auto TopicIdType = TopicIdTypeVal::Normal; - static const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; - static const bool Retain = false; - static const std::vector Data = { - 0x01, 0x02, 0x03, 0x04, 0xab, 0xcd, 0xef - }; - static const CC_MqttsnTopicId TopicId = 0x1234; - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto result = client->publish(Topic, &Data[0], Data.size(), Qos, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_RegisterMsg(state, dataProc, Topic); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - TS_ASSERT(state.m_reportedPublishes.empty()); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - TS_ASSERT(state.m_reportedPublishes.empty()); - verifySent_RegisterMsg(state, dataProc, Topic); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - TS_ASSERT(state.m_reportedPublishes.empty()); - auto msgId = verifySent_RegisterMsg(state, dataProc, Topic); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto regackMsg = dataProc.prepareRegackMsg(TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, regackMsg, "REGACK"); - TS_ASSERT(state.m_reportedPublishes.empty()); - verifySent_PublishMsg(state, dataProc, TopicId, Data, TopicIdType, Qos, Retain, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - TS_ASSERT(state.m_reportedPublishes.empty()); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - TS_ASSERT(state.m_reportedPublishes.empty()); - msgId = verifySent_PublishMsg(state, dataProc, TopicId, Data, TopicIdType, Qos, Retain, true); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - static const unsigned NextTicks = 1000; - state.m_nextElapsedTicks = NextTicks; - auto pubackMsg = dataProc.preparePubackMsg(TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, pubackMsg, "PUBACK"); - verifyCb_ReportedPublish(state, CC_MqttsnAsyncOpStatus_Successful); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, (DefaultKeepAlive * 1000) - NextTicks); - verifyNoOtherEvent(state); -} - -void ClientBasic::test19() -{ - DataProcessor dataProc; - typedef TestBasicState Test19State; - Test19State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const std::string Topic("this/is/topic"); - static const auto TopicIdType = TopicIdTypeVal::Normal; - static const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; - static const bool Retain = false; - static const std::vector Data = { - 0x01, 0x02, 0x03, 0x04, 0xab, 0xcd, 0xef - }; - static const CC_MqttsnTopicId TopicId = 0x1234; - - clearState(state); - static const unsigned NextTicks = 1000; - state.m_nextElapsedTicks = NextTicks; - auto result = client->publish(Topic, &Data[0], Data.size(), Qos, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - TS_ASSERT(state.m_reportedPublishes.empty()); - auto msgId = verifySent_RegisterMsg(state, dataProc, Topic); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = NextTicks; - auto regackMsg = dataProc.prepareRegackMsg(TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, regackMsg, "REGACK"); - TS_ASSERT(state.m_reportedPublishes.empty()); - msgId = verifySent_PublishMsg(state, dataProc, TopicId, Data, TopicIdType, Qos, Retain, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = NextTicks; - auto pubackMsg = dataProc.preparePubackMsg(TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, pubackMsg, "PUBACK"); - verifyCb_ReportedPublish(state, CC_MqttsnAsyncOpStatus_Successful); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, (DefaultKeepAlive * 1000) - NextTicks); - verifyNoOtherEvent(state); - - clearState(state); - result = client->publish(Topic, &Data[0], Data.size(), Qos, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - msgId = verifySent_PublishMsg(state, dataProc, TopicId, Data, TopicIdType, Qos, Retain, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - TS_ASSERT(state.m_reportedPublishes.empty()); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = NextTicks; - pubackMsg = dataProc.preparePubackMsg(TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::InvalidTopicId); - dataFromGw(*client, pubackMsg, "PUBACK"); - TS_ASSERT(state.m_reportedPublishes.empty()); - msgId = verifySent_RegisterMsg(state, dataProc, Topic); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = NextTicks; - regackMsg = dataProc.prepareRegackMsg(TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, regackMsg, "REGACK"); - TS_ASSERT(state.m_reportedPublishes.empty()); - msgId = verifySent_PublishMsg(state, dataProc, TopicId, Data, TopicIdType, Qos, Retain, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = NextTicks; - pubackMsg = dataProc.preparePubackMsg(TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, pubackMsg, "PUBACK"); - verifyCb_ReportedPublish(state, CC_MqttsnAsyncOpStatus_Successful); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, (DefaultKeepAlive * 1000) - NextTicks); - verifyNoOtherEvent(state); -} - -void ClientBasic::test20() -{ - DataProcessor dataProc; - typedef TestBasicState Test20State; - Test20State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const std::string Topic("this/is/topic"); - static const auto TopicIdType = TopicIdTypeVal::Normal; - static const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; - static const bool Retain = false; - static const std::vector Data = { - 0x01, 0x02, 0x03, 0x04, 0xab, 0xcd, 0xef - }; - static const CC_MqttsnTopicId TopicId = 0x1234; - - clearState(state); - static const unsigned NextTicks = 1000; - state.m_nextElapsedTicks = NextTicks; - auto result = client->publish(Topic, &Data[0], Data.size(), Qos, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - TS_ASSERT(state.m_reportedPublishes.empty()); - auto msgId = verifySent_RegisterMsg(state, dataProc, Topic); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = NextTicks; - auto regackMsg = dataProc.prepareRegackMsg(TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, regackMsg, "REGACK"); - TS_ASSERT(state.m_reportedPublishes.empty()); - msgId = verifySent_PublishMsg(state, dataProc, TopicId, Data, TopicIdType, Qos, Retain, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = NextTicks; - auto pubackMsg = dataProc.preparePubackMsg(TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::InvalidTopicId); - dataFromGw(*client, pubackMsg, "PUBACK"); - verifyCb_ReportedPublish(state, CC_MqttsnAsyncOpStatus_InvalidId); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, (DefaultKeepAlive * 1000) - NextTicks); - verifyNoOtherEvent(state); -} - -void ClientBasic::test21() -{ - DataProcessor dataProc; - typedef TestBasicState Test21State; - Test21State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const std::string Topic("this/is/topic"); - static const auto TopicIdType = TopicIdTypeVal::Normal; - static const CC_MqttsnQoS Qos = CC_MqttsnQoS_ExactlyOnceDelivery; - static const bool Retain = false; - static const std::vector Data = { - 0x01, 0x02, 0x03, 0x04, 0xab, 0xcd, 0xef - }; - static const CC_MqttsnTopicId TopicId = 0x1234; - - clearState(state); - static const unsigned NextTicks = 1000; - state.m_nextElapsedTicks = NextTicks; - auto result = client->publish(Topic, &Data[0], Data.size(), Qos, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_RegisterMsg(state, dataProc, Topic); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = NextTicks; - client->cancel(); - verifyCb_ReportedPublish(state, CC_MqttsnAsyncOpStatus_Aborted); - verifyNoOtherEvent(state); - - result = client->publish(Topic, &Data[0], Data.size(), Qos, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - auto msgId = verifySent_RegisterMsg(state, dataProc, Topic); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = NextTicks; - auto regackMsg = dataProc.prepareRegackMsg(TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, regackMsg, "REGACK"); - msgId = verifySent_PublishMsg(state, dataProc, TopicId, Data, TopicIdType, Qos, Retain, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = NextTicks; - auto pubrecMsg = dataProc.preparePubrecMsg(msgId); - dataFromGw(*client, pubrecMsg, "PUBREC"); - verifySent_PubrelMsg(state, dataProc, msgId); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = NextTicks; - client->cancel(); - verifyCb_ReportedPublish(state, CC_MqttsnAsyncOpStatus_Aborted); - verifyNoOtherEvent(state); - - clearState(state); - result = client->publish(Topic, &Data[0], Data.size(), Qos, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - msgId = verifySent_PublishMsg(state, dataProc, TopicId, Data, TopicIdType, Qos, Retain, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = NextTicks; - pubrecMsg = dataProc.preparePubrecMsg(msgId); - dataFromGw(*client, pubrecMsg, "PUBREC"); - verifySent_PubrelMsg(state, dataProc, msgId); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = NextTicks; - auto pubcompMsg = dataProc.preparePubcompMsg(msgId); - dataFromGw(*client, pubcompMsg, "PUBCOMP"); - verifyCb_ReportedPublish(state, CC_MqttsnAsyncOpStatus_Successful); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, (DefaultKeepAlive * 1000) - NextTicks); - verifyNoOtherEvent(state); -} - -void ClientBasic::test22() -{ - DataProcessor dataProc; - typedef TestBasicState Test22State; - Test22State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const std::string Topic("this/is/topic"); - static const std::uint16_t RegMsgId = 0x1234; - static const CC_MqttsnTopicId TopicId = 0x2222; - static const auto TopicIdType = TopicIdTypeVal::Normal; - static const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtMostOnceDelivery; - static const bool Retain = false; - static const std::vector Data = { - 0x01, 0x02, 0x03, 0x04, 0xab, 0xcd, 0xef - }; - - clearState(state); - auto registerMsg = dataProc.prepareRegisterMsg(TopicId, RegMsgId, Topic); - state.m_nextElapsedTicks = 1000; - dataFromGw(*client, registerMsg, "REGISTER"); - verifySent_RegackMsg(state, dataProc, TopicId, RegMsgId); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto result = client->publish(Topic, &Data[0], Data.size(), Qos, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_PublishMsg(state, dataProc, TopicId, Data, TopicIdType, Qos, Retain, false); - verifyCb_ReportedPublish(state, CC_MqttsnAsyncOpStatus_Successful); - verifyNoOtherEvent(state); -} - -void ClientBasic::test23() -{ - DataProcessor dataProc; - typedef TestBasicState Test23State; - Test23State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const std::string Topic1("this/is/topic1"); - static const std::string Topic2("this/is/topic2"); - static const CC_MqttsnTopicId Topic1Id = 0x1234; - static const CC_MqttsnTopicId Topic2Id = 0x4321; - static const std::uint16_t RegMsgId = 0x2222; - static const CC_MqttsnQoS Qos1 = CC_MqttsnQoS_ExactlyOnceDelivery; - static const CC_MqttsnQoS Qos2 = CC_MqttsnQoS_AtLeastOnceDelivery; - static const bool Retain = true; - static const std::vector Data = { - 0x01, 0x02, 0x03, 0x04, 0xab, 0xcd, 0xef - }; - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto result = client->publish(Topic1, &Data[0], Data.size(), Qos1, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - auto msgId = verifySent_RegisterMsg(state, dataProc, Topic1); - verifyNoOtherEvent(state); - - clearState(state); - static const unsigned NextTicks = 1000; - state.m_nextElapsedTicks = NextTicks; - auto regackMsg = dataProc.prepareRegackMsg(Topic1Id, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, regackMsg, "REGACK"); - msgId = verifySent_PublishMsg(state, dataProc, Topic1Id, Data, TopicIdTypeVal::Normal, Qos1, Retain, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = NextTicks; - doTopicRegister(*client, dataProc, state, Topic2, Topic2Id, RegMsgId); - - clearState(state); - state.m_nextElapsedTicks = NextTicks; - auto pubrecMsg = dataProc.preparePubrecMsg(msgId); - dataFromGw(*client, pubrecMsg, "PUBREC"); - verifySent_PubrelMsg(state, dataProc, msgId); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = NextTicks; - auto pubcompMsg = dataProc.preparePubcompMsg(msgId); - dataFromGw(*client, pubcompMsg, "PUBCOMP"); - verifyCb_ReportedPublish(state, CC_MqttsnAsyncOpStatus_Successful); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, (DefaultKeepAlive * 1000) - NextTicks); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->publish(Topic2, &Data[0], Data.size(), Qos2, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - - msgId = verifySent_PublishMsg(state, dataProc, Topic2Id, Data, TopicIdTypeVal::Normal, Qos2, Retain, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto pubackMsg = dataProc.preparePubackMsg(Topic2Id, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, pubackMsg, "PUBACK"); - verifyCb_ReportedPublish(state, CC_MqttsnAsyncOpStatus_Successful); - verifyNoOtherEvent(state); -} - -void ClientBasic::test24() -{ - DataProcessor dataProc; - typedef TestBasicState Test24State; - Test24State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const CC_MqttsnTopicId TopicId = 0x1234; - static const std::uint16_t MsgId = 0x5555; - static const auto TopicIdType = TopicIdTypeVal::PredefinedTopicId; - static const auto Qos = cc_mqttsn::field::QosVal::AtMostOnceDelivery; - static const bool Retain = false; - static const std::vector Data = { - 0x01, 0x02, 0x03, 0x04, 0xab, 0xcd, 0xef - }; - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto publishMsg = dataProc.preparePublishMsg(TopicId, MsgId, Data, TopicIdType, Qos, Retain, false); - dataFromGw(*client, publishMsg, "PUBLISH"); - verifyCb_ReportedMessage(state, std::string(), TopicId, Data, transformQos(Qos), Retain); - TS_ASSERT(state.m_nextOutput.empty()); - verifyNoOtherEvent(state); - - static const char* Topic = "/this/is/topic"; - static const std::uint16_t RegMsgId = 0x1222; - doTopicRegister(*client, dataProc, state, Topic, TopicId, RegMsgId); - - clearState(state); - state.m_nextElapsedTicks = 1000; - publishMsg = dataProc.preparePublishMsg(TopicId, static_cast(MsgId + 1), Data, TopicIdTypeVal::Normal, Qos, Retain, false); - dataFromGw(*client, publishMsg, "PUBLISH"); - verifyCb_ReportedMessage(state, Topic, 0, Data, transformQos(Qos), Retain); - TS_ASSERT(state.m_nextOutput.empty()); - TS_ASSERT(state.m_reportedMsgs.empty()); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - publishMsg = dataProc.preparePublishMsg(static_cast(TopicId + 1), static_cast(MsgId + 2), Data, TopicIdTypeVal::Normal, Qos, Retain, false); - dataFromGw(*client, publishMsg, "PUBLISH"); - verifySent_PubackMsg(state, dataProc, static_cast(TopicId + 1), static_cast(MsgId + 2), cc_mqttsn::field::ReturnCodeVal::InvalidTopicId); - TS_ASSERT(state.m_reportedMsgs.empty()); - verifyNoOtherEvent(state); -} - -void ClientBasic::test25() -{ - DataProcessor dataProc; - typedef TestBasicState Test25State; - - Test25State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const std::string Topic = "/this/is/topic"; - static const std::uint16_t RegMsgId = 0x1222; - static const CC_MqttsnTopicId TopicId = 0x1234; - - clearState(state); - doTopicRegister(*client, dataProc, state, Topic, TopicId, RegMsgId); - - static const std::uint16_t MsgId = 0x5555; - static const auto TopicIdType = TopicIdTypeVal::Normal; - static const auto Qos = cc_mqttsn::field::QosVal::AtLeastOnceDelivery; - static const bool Retain = false; - static const std::vector Data = { - 0x01, 0x02, 0x03, 0x04, 0xab, 0xcd, 0xef - }; - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto expectedMsgId = MsgId; - auto publishMsg = dataProc.preparePublishMsg(TopicId, expectedMsgId, Data, TopicIdType, Qos, Retain, false); - dataFromGw(*client, publishMsg, "PUBLISH"); - verifySent_PubackMsg(state, dataProc, TopicId, expectedMsgId); - verifyCb_ReportedMessage(state, Topic, 0, Data, transformQos(Qos), Retain); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - publishMsg = dataProc.preparePublishMsg(TopicId, expectedMsgId, Data, TopicIdType, Qos, Retain, true); - dataFromGw(*client, publishMsg, "PUBLISH"); - verifySent_PubackMsg(state, dataProc, TopicId, expectedMsgId); - verifyCb_ReportedMessage(state, Topic, 0, Data, transformQos(Qos), Retain); // Duplicate is reported for Qos 1 - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - expectedMsgId = static_cast(MsgId + 1); - publishMsg = dataProc.preparePublishMsg(TopicId, expectedMsgId, Data, TopicIdType, Qos, Retain, true); - dataFromGw(*client, publishMsg, "PUBLISH"); - verifySent_PubackMsg(state, dataProc, TopicId, expectedMsgId); - verifyCb_ReportedMessage(state, Topic, 0, Data, transformQos(Qos), Retain); - verifyNoOtherEvent(state); -} - -void ClientBasic::test26() -{ - DataProcessor dataProc; - typedef TestBasicState Test26State; - - Test26State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const std::string Topic = "/this/is/topic"; - static const std::uint16_t RegMsgId = 0x1222; - static const CC_MqttsnTopicId TopicId = 0x1234; - - clearState(state); - doTopicRegister(*client, dataProc, state, Topic, TopicId, RegMsgId); - - static const std::uint16_t MsgId = 0x5555; - static const auto TopicIdType = TopicIdTypeVal::PredefinedTopicId; - static const auto Qos = cc_mqttsn::field::QosVal::ExactlyOnceDelivery; - static const bool Retain = false; - static const std::vector Data = { - 0x01, 0x02, 0x03, 0x04, 0xab, 0xcd, 0xef - }; - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto expectedMsgId = MsgId; - auto publishMsg = dataProc.preparePublishMsg(TopicId, expectedMsgId, Data, TopicIdType, Qos, Retain, false); - dataFromGw(*client, publishMsg, "PUBLISH"); - verifySent_PubrecMsg(state, dataProc, expectedMsgId); - TS_ASSERT(state.m_reportedMsgs.empty()); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto pubrelMsg = dataProc.preparePubrelMsg(expectedMsgId); - dataFromGw(*client, pubrelMsg, "PUBREL"); - verifySent_PubcompMsg(state, dataProc, expectedMsgId); - verifyCb_ReportedMessage(state, Topic, TopicId, Data, transformQos(Qos), Retain); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - publishMsg = dataProc.preparePublishMsg(TopicId, expectedMsgId, Data, TopicIdType, Qos, Retain, true); - dataFromGw(*client, publishMsg, "PUBLISH"); - verifySent_PubrecMsg(state, dataProc, expectedMsgId); - TS_ASSERT(state.m_reportedMsgs.empty()); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - pubrelMsg = dataProc.preparePubrelMsg(expectedMsgId); - dataFromGw(*client, pubrelMsg, "PUBREL"); - verifySent_PubcompMsg(state, dataProc, expectedMsgId); - // Although marked as duplicate, it is considered to be a new message - verifyCb_ReportedMessage(state, Topic, TopicId, Data, transformQos(Qos), Retain); - verifyNoOtherEvent(state); - - clearState(state); - ++expectedMsgId; - state.m_nextElapsedTicks = 1000; - publishMsg = dataProc.preparePublishMsg(TopicId, expectedMsgId, Data, TopicIdType, Qos, Retain, true); - dataFromGw(*client, publishMsg, "PUBLISH"); - verifySent_PubrecMsg(state, dataProc, expectedMsgId); - TS_ASSERT(state.m_reportedMsgs.empty()); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - pubrelMsg = dataProc.preparePubrelMsg(expectedMsgId); - dataFromGw(*client, pubrelMsg, "PUBREL"); - verifySent_PubcompMsg(state, dataProc, expectedMsgId); - verifyCb_ReportedMessage(state, Topic, TopicId, Data, transformQos(Qos), Retain); - verifyNoOtherEvent(state); -} - -void ClientBasic::test27() -{ - DataProcessor dataProc; - typedef TestBasicState Test27State; - - Test27State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const CC_MqttsnTopicId TopicId = 0x1234; - static const auto Qos = CC_MqttsnQoS_AtLeastOnceDelivery; - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto result = client->subscribe(TopicId, Qos); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - auto msgId = verifySent_SubsribeMsg(state, dataProc, TopicId, Qos, false); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto subackMsg = dataProc.prepareSubackMsg(transformQos(Qos), TopicId, static_cast(msgId + 1), cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, subackMsg, "SUBACK"); - TS_ASSERT(state.m_reportedSubscribes.empty()); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - auto msgId2 = verifySent_SubsribeMsg(state, dataProc, TopicId, Qos, true); - TS_ASSERT_EQUALS(msgId, msgId2); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - subackMsg = dataProc.prepareSubackMsg(transformQos(Qos), TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, subackMsg, "SUBACK"); - verifyCb_ReportedSubsribe(state, CC_MqttsnAsyncOpStatus_Successful, Qos); - verifyNoOtherEvent(state); -} - -void ClientBasic::test28() -{ - DataProcessor dataProc; - typedef TestBasicState Test28State; - Test28State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - - static const std::string Topic("/this/is/topic"); - static const auto Qos1 = CC_MqttsnQoS_ExactlyOnceDelivery; - static const auto Qos2 = CC_MqttsnQoS_AtLeastOnceDelivery; - - auto result = client->subscribe(Topic, Qos1); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_NotConnected); - - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->subscribe(Topic, Qos1); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - auto msgId = verifySent_SubsribeMsg(state, dataProc, Topic, Qos1, false); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->subscribe(Topic, Qos2); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Busy); - verifyNoOtherEvent(state); - - static const CC_MqttsnTopicId TopicId = 0x1111; - clearState(state); - state.m_nextElapsedTicks = 1000; - auto subackMsg = dataProc.prepareSubackMsg(transformQos(Qos2), TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::Congestion); - dataFromGw(*client, subackMsg, "SUBACK"); - verifyCb_ReportedSubsribe(state, CC_MqttsnAsyncOpStatus_Congestion, Qos2); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->subscribe(Topic, Qos1); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - msgId = verifySent_SubsribeMsg(state, dataProc, Topic, Qos1, false); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - subackMsg = dataProc.prepareSubackMsg(transformQos(Qos2), TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, subackMsg, "SUBACK"); - verifyCb_ReportedSubsribe(state, CC_MqttsnAsyncOpStatus_Successful, Qos2); - verifyNoOtherEvent(state); - - static const DataSeq Data = { - 0x0, 0x1, 0x2, 0xa, 0xb, 0xc - }; - static const bool Retain = true; - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->publish(Topic, &Data[0], Data.size(), Qos2, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - msgId = verifySent_PublishMsg(state, dataProc, TopicId, Data, TopicIdTypeVal::Normal, Qos2, Retain, false); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto pubackMsg = dataProc.preparePubackMsg(TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, pubackMsg, "PUBACK"); - verifyCb_ReportedPublish(state, CC_MqttsnAsyncOpStatus_Successful); - verifyNoOtherEvent(state); -} - -void ClientBasic::test29() -{ - DataProcessor dataProc; - typedef TestBasicState Test29State; - Test29State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const std::string Topic("/this/is/topic"); - static const auto Qos = CC_MqttsnQoS_ExactlyOnceDelivery; - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto result = client->subscribe(Topic, Qos); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_SubsribeMsg(state, dataProc, Topic, Qos, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 5000; - client->cancel(); - verifyCb_ReportedSubsribe(state, CC_MqttsnAsyncOpStatus_Aborted, Qos); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->subscribe(Topic, Qos); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_SubsribeMsg(state, dataProc, Topic, Qos, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - verifySent_SubsribeMsg(state, dataProc, Topic, Qos, true); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - verifySent_SubsribeMsg(state, dataProc, Topic, Qos, true); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - verifyCb_ReportedSubsribe(state, CC_MqttsnAsyncOpStatus_NoResponse, Qos); - verifyNoOtherEvent(state); - - static const CC_MqttsnTopicId TopicId = 0x1233; - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->subscribe(TopicId, Qos); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - auto msgId = verifySent_SubsribeMsg(state, dataProc, TopicId, Qos, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto subackMsg = dataProc.prepareSubackMsg(transformQos(Qos), 0, msgId, cc_mqttsn::field::ReturnCodeVal::InvalidTopicId); - dataFromGw(*client, subackMsg, "SUBACK"); - verifyCb_ReportedSubsribe(state, CC_MqttsnAsyncOpStatus_InvalidId, Qos); - verifyNoOtherEvent(state); -} - -void ClientBasic::test30() -{ - DataProcessor dataProc; - typedef TestBasicState Test30State; - Test30State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const std::string Topic("/this/is/topic"); - static const CC_MqttsnTopicId TopicId = 0x1111; - static const auto Qos = CC_MqttsnQoS_AtLeastOnceDelivery; - static const std::uint16_t RegMsgId = 1; - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto registerMsg = dataProc.prepareRegisterMsg(TopicId, RegMsgId, Topic); - dataFromGw(*client, registerMsg, "REGISTER"); - verifySent_RegackMsg(state, dataProc, TopicId, RegMsgId); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto result = client->subscribe(Topic, Qos); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - // Even if topic is pre-registered, use subscribe by topic name - auto msgId = verifySent_SubsribeMsg(state, dataProc, Topic, Qos, false); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto subackMsg = dataProc.prepareSubackMsg(transformQos(Qos), TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, subackMsg, "SUBACK"); - verifyCb_ReportedSubsribe(state, CC_MqttsnAsyncOpStatus_Successful, Qos); - verifyNoOtherEvent(state); - - static const DataSeq Data = { 0x0, 0x1, 0x2 }; - static const bool Retain = true; - clearState(state); - state.m_nextElapsedTicks = 1000; - client->publish(Topic, &Data[0], Data.size(), Qos, Retain); - msgId = verifySent_PublishMsg(state, dataProc, TopicId, Data, TopicIdTypeVal::Normal, Qos, Retain, false); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto pubackMsg = dataProc.preparePubackMsg(TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::InvalidTopicId); - dataFromGw(*client, pubackMsg, "PUBACK"); - TS_ASSERT(state.m_reportedPublishes.empty()); - verifySent_RegisterMsg(state, dataProc, Topic); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - client->cancel(); - verifyCb_ReportedPublish(state, CC_MqttsnAsyncOpStatus_Aborted); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->subscribe(Topic, Qos); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - msgId = verifySent_SubsribeMsg(state, dataProc, Topic, Qos, false); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - subackMsg = dataProc.prepareSubackMsg(transformQos(Qos), TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, subackMsg, "SUBACK"); - verifyCb_ReportedSubsribe(state, CC_MqttsnAsyncOpStatus_Successful, Qos); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - client->publish(Topic, &Data[0], Data.size(), Qos, Retain); - msgId = verifySent_PublishMsg(state, dataProc, TopicId, Data, TopicIdTypeVal::Normal, Qos, Retain, false); - pubackMsg = dataProc.preparePubackMsg(TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, pubackMsg, "PUBACK"); - verifyCb_ReportedPublish(state, CC_MqttsnAsyncOpStatus_Successful); - verifyNoOtherEvent(state); -} - -void ClientBasic::test31() -{ - DataProcessor dataProc; - TestBasicState state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - - static const std::string Topic("/this/is/topic"); - - auto result = client->unsubscribe(Topic); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_NotConnected); - - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->unsubscribe(Topic); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - auto msgId = verifySent_UnsubsribeMsg(state, dataProc, Topic); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->unsubscribe(Topic); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Busy); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto unsubackMsg = dataProc.prepareUnsubackMsg(msgId); - dataFromGw(*client, unsubackMsg, "UNSUBACK"); - verifyCb_ReportedUnsubsribe(state, CC_MqttsnAsyncOpStatus_Successful); - verifyNoOtherEvent(state); -} - -void ClientBasic::test32() -{ - DataProcessor dataProc; - TestBasicState state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - - static const CC_MqttsnTopicId TopicId = 1111; - - auto result = client->unsubscribe(TopicId); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_NotConnected); - - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->unsubscribe(TopicId); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_UnsubsribeMsg(state, dataProc, TopicId); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->unsubscribe(TopicId); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Busy); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - client->cancel(); - verifyCb_ReportedUnsubsribe(state, CC_MqttsnAsyncOpStatus_Aborted); - verifyNoOtherEvent(state); - - result = client->unsubscribe(TopicId); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_UnsubsribeMsg(state, dataProc, TopicId); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - verifySent_UnsubsribeMsg(state, dataProc, TopicId); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - verifySent_UnsubsribeMsg(state, dataProc, TopicId); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - verifyCb_ReportedUnsubsribe(state, CC_MqttsnAsyncOpStatus_NoResponse); - verifyNoOtherEvent(state); - - result = client->unsubscribe(TopicId); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - auto msgId = verifySent_UnsubsribeMsg(state, dataProc, TopicId); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - auto unsubackMsg = dataProc.prepareUnsubackMsg(msgId); - dataFromGw(*client, unsubackMsg, "UNSUBACK"); - verifyCb_ReportedUnsubsribe(state, CC_MqttsnAsyncOpStatus_Successful); - verifyNoOtherEvent(state); -} - -void ClientBasic::test33() -{ - DataProcessor dataProc; - TestBasicState state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const std::string Topic("/bla/bla/bla"); - static const CC_MqttsnTopicId TopicId = 1111; - static const std::uint16_t RegMsgId = 5555; - clearState(state); - state.m_nextElapsedTicks = 1000; - doTopicRegister(*client, dataProc, state, Topic, TopicId, RegMsgId); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto result = client->unsubscribe(Topic); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - // Unsubscribe by full topic even if pre-registered - auto msgId = verifySent_UnsubsribeMsg(state, dataProc, Topic); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - auto unsubackMsg = dataProc.prepareUnsubackMsg(msgId); - dataFromGw(*client, unsubackMsg, "UNSUBACK"); - verifyCb_ReportedUnsubsribe(state, CC_MqttsnAsyncOpStatus_Successful); - verifyNoOtherEvent(state); -} - -void ClientBasic::test34() -{ - DataProcessor dataProc; - TestBasicState state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const std::string WillTopic("will/topic"); - static const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; - static const bool Retain = true; - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto result = client->willTopicUpdate(WillTopic, Qos, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_WilltopicupdMsg(state, dataProc, WillTopic, Qos, Retain); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto msgRespMsg = dataProc.prepareWillmsgrespMsg(cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, msgRespMsg, "WILLMSGRESP"); - TS_ASSERT(state.m_nextOutput.empty()); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto topicRespMsg = dataProc.prepareWilltopicrespMsg(cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, topicRespMsg, "WILLTOPICRESP"); - verifyCb_ReportedWillTopicUpdate(state, CC_MqttsnAsyncOpStatus_Successful); - verifyNoOtherEvent(state); - - static const DataSeq Data = {0x0, 0x2, 0x3}; - result = client->willMsgUpdate(&Data[0], Data.size()); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_WillmsgupdMsg(state, dataProc, Data); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - dataFromGw(*client, topicRespMsg, "WILLTOPICRESP"); - TS_ASSERT(state.m_nextOutput.empty()); - TS_ASSERT(state.m_reportedWillMsgUpdates.empty()); - TS_ASSERT(state.m_reportedWillMsgUpdates.empty()); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout - 1000); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - dataFromGw(*client, msgRespMsg, "WILLMSGRESP"); - verifyCb_ReportedWillMsgUpdate(state, CC_MqttsnAsyncOpStatus_Successful); - verifyNoOtherEvent(state); -} - -void ClientBasic::test35() -{ - DataProcessor dataProc; - TestBasicState state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const std::string WillTopic("will/topic"); - static const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; - static const bool Retain = true; - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto result = client->willTopicUpdate(WillTopic, Qos, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_WilltopicupdMsg(state, dataProc, WillTopic, Qos, Retain); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - TS_ASSERT(state.m_reportedWillUpdates.empty()); - verifySent_WilltopicupdMsg(state, dataProc, WillTopic, Qos, Retain); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - TS_ASSERT(state.m_reportedWillUpdates.empty()); - verifySent_WilltopicupdMsg(state, dataProc, WillTopic, Qos, Retain); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - verifyCb_ReportedWillTopicUpdate(state, CC_MqttsnAsyncOpStatus_NoResponse); - verifyNoOtherEvent(state); -} - -void ClientBasic::test36() -{ - DataProcessor dataProc; - TestBasicState state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const std::string WillTopic("will/topic"); - static const DataSeq Data; - static const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; - static const bool Retain = true; - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto result = client->willTopicUpdate(WillTopic.c_str(), Qos, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_WilltopicupdMsg(state, dataProc, WillTopic, Qos, Retain); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto topicRespMsg = dataProc.prepareWilltopicrespMsg(cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, topicRespMsg, "WILLTOPICRESP"); - verifyCb_ReportedWillTopicUpdate(state, CC_MqttsnAsyncOpStatus_Successful); - verifyNoOtherEvent(state); - - result = client->willMsgUpdate(nullptr, 0); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_WillmsgupdMsg(state, dataProc, Data); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - TS_ASSERT(state.m_reportedWillUpdates.empty()); - verifySent_WillmsgupdMsg(state, dataProc, Data); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - TS_ASSERT(state.m_reportedWillUpdates.empty()); - verifySent_WillmsgupdMsg(state, dataProc, Data); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - verifyCb_ReportedWillMsgUpdate(state, CC_MqttsnAsyncOpStatus_NoResponse); - verifyNoOtherEvent(state); -} - -void ClientBasic::test37() -{ - DataProcessor dataProc; - TestBasicState state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const std::string WillTopic("will/topic"); - static const DataSeq Data; - static const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; - static const bool Retain = true; - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto result = client->willTopicUpdate(WillTopic.c_str(), Qos, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_WilltopicupdMsg(state, dataProc, WillTopic, Qos, Retain); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - client->cancel(); - verifyCb_ReportedWillTopicUpdate(state, CC_MqttsnAsyncOpStatus_Aborted); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->willMsgUpdate(nullptr, 0); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_WillmsgupdMsg(state, dataProc, Data); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - client->cancel(); - verifyCb_ReportedWillMsgUpdate(state, CC_MqttsnAsyncOpStatus_Aborted); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto msgRespMsg = dataProc.prepareWillmsgrespMsg(cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, msgRespMsg, "WILLMSGRESP"); - TS_ASSERT(state.m_nextOutput.empty()); - verifyNoOtherEvent(state); -} - -void ClientBasic::test38() -{ - DataProcessor dataProc; - TestBasicState state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const std::uint16_t Duration = 10000; - clearState(state); - state.m_nextElapsedTicks = 1000; - auto result = client->sleep(Duration); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_DisconnectMsg(state, dataProc, Duration); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - client->cancel(); - verifyCb_ReportedSleep(state, CC_MqttsnAsyncOpStatus_Aborted); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->sleep(Duration); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_DisconnectMsg(state, dataProc, Duration); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - verifySent_DisconnectMsg(state, dataProc, Duration); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - verifySent_DisconnectMsg(state, dataProc, Duration); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - verifyCb_ReportedSleep(state, CC_MqttsnAsyncOpStatus_NoResponse); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->sleep(Duration); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_DisconnectMsg(state, dataProc, Duration); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto disconnectMsg = dataProc.prepareDisconnectMsg(Duration); - dataFromGw(*client, disconnectMsg, "DISCONNECT"); - verifyCb_ReportedSleep(state, CC_MqttsnAsyncOpStatus_Successful); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, 0); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - static const std::string ClientId = "test_client"; - result = client->connect(ClientId.c_str(), DefaultKeepAlive, true, nullptr); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_AlreadyConnected); - verifyNoOtherEvent(state); -} - -void ClientBasic::test39() -{ - DataProcessor dataProc; - TestBasicState state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - auto& clientId = doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - auto result = client->checkMessages(); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_NotSleeping); - - static const std::uint16_t Duration = 10000; - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->sleep(Duration); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_DisconnectMsg(state, dataProc, Duration); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto disconnectMsg = dataProc.prepareDisconnectMsg(Duration); - dataFromGw(*client, disconnectMsg, "DISCONNECT"); - verifyCb_ReportedSleep(state, CC_MqttsnAsyncOpStatus_Successful); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, 0); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 2000; - result = client->checkMessages(); - verifySent_PingreqMsg(state, dataProc, clientId); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - static const CC_MqttsnTopicId TopicId = 0x1111; - static const std::uint16_t MsgId = 0x2222; - static const DataSeq Data = {0x0, 0x1, 0x2}; - static const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtMostOnceDelivery; - static const bool Retain = false; - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto publishMsg = - dataProc.preparePublishMsg( - TopicId, - MsgId, - Data, - TopicIdTypeVal::PredefinedTopicId, - transformQos(Qos), - false, - false); - - dataFromGw(*client, publishMsg, "PUBLISH"); - dataFromGw(*client, publishMsg, "PUBLISH"); - dataFromGw(*client, publishMsg, "PUBLISH"); - - verifyCb_ReportedMessage(state, std::string(), TopicId, Data, Qos, Retain); - verifyCb_ReportedMessage(state, std::string(), TopicId, Data, Qos, Retain); - verifyCb_ReportedMessage(state, std::string(), TopicId, Data, Qos, Retain); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto pingrespMsg = dataProc.preparePingrespMsg(); - dataFromGw(*client, pingrespMsg, "PINGRESP"); - verifyCb_ReportedMessageChecks(state, CC_MqttsnAsyncOpStatus_Successful); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, 0); - verifyNoOtherEvent(state); - - result = client->reconnect(); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_ConnectMsg(state, dataProc, DefaultClientId, DefaultKeepAlive, false, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - client->tick(); - verifySent_ConnectMsg(state, dataProc, DefaultClientId, DefaultKeepAlive, false, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto connackMsg = dataProc.prepareConnackMsg(cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, connackMsg, "CONNACK"); - verifyCb_ReportedReconnect(state, CC_MqttsnAsyncOpStatus_Successful); - verifyNoOtherEvent(state); -} - -void ClientBasic::test40() -{ - DataProcessor dataProc; - TestBasicState state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state, true); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - static const std::string WillTopic("will/topic/upd"); - static const DataBuf WillData = {1, 1, 1, 1}; - static const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtLeastOnceDelivery; - static const bool Retain = true; - - auto info = CC_MqttsnWillInfo(); - info.topic = WillTopic.c_str(); - info.msg = &WillData[0]; - info.msgLen = static_cast(WillData.size()); - info.qos = Qos; - info.retain = Retain; - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto result = client->willUpdate(&info); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_ConnectMsg(state, dataProc, DefaultClientId, DefaultKeepAlive, false, true); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto topicReqMsg = dataProc.prepareWilltopicreqMsg(); - dataFromGw(*client, topicReqMsg, "WILLTOPICREQ"); - verifySent_WilltopicMsg(state, dataProc, WillTopic, Qos, Retain); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto msgReqMsg = dataProc.prepareWillmsgreqMsg(); - dataFromGw(*client, msgReqMsg, "WILLMSGREQ"); - verifySent_WillmsgMsg(state, dataProc, WillData); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto connackMsg = dataProc.prepareConnackMsg(cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, connackMsg, "CONNACK"); - verifyCb_ReportedWillUpdate(state, CC_MqttsnAsyncOpStatus_Successful); - verifyNoOtherEvent(state); -} - -void ClientBasic::test41() -{ - DataProcessor dataProc; - TestBasicState state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - doConnect(*client, dataProc, state, true); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - auto info = CC_MqttsnWillInfo(); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto result = client->willUpdate(&info); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_ConnectMsg(state, dataProc, DefaultClientId, DefaultKeepAlive, false, true); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto topicReqMsg = dataProc.prepareWilltopicreqMsg(); - dataFromGw(*client, topicReqMsg, "WILLTOPICREQ"); - verifySent_WilltopicMsg(state, dataProc, std::string(), CC_MqttsnQoS_AtMostOnceDelivery, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto connackMsg = dataProc.prepareConnackMsg(cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, connackMsg, "CONNACK"); - verifyCb_ReportedWillUpdate(state, CC_MqttsnAsyncOpStatus_Successful); - verifyNoOtherEvent(state); -} - -void ClientBasic::test42() -{ - // Connect with will - DataProcessor dataProc; - TestBasicState state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - - static const unsigned short KeepAlive = 60; - - TS_TRACE("Connecting..."); - - state.m_nextElapsedTicks = 0; - auto result = - client->connect(nullptr, KeepAlive, true, nullptr); - - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - verifySent_ConnectMsg(state, dataProc, std::string(), KeepAlive, true, false); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultRetryTimeout); - verifyNoOtherEvent(state); - - clearState(state); - static const unsigned NextTicks = 1000; - state.m_nextElapsedTicks = NextTicks; - auto connackMsg = dataProc.prepareConnackMsg(cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, connackMsg, "CONNACK"); - - // check connected - verifyCb_ReportedConnect(state, CC_MqttsnAsyncOpStatus_Successful); - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, (KeepAlive * 1000) - NextTicks); - verifyNoOtherEvent(state); -} - -void ClientBasic::test43() -{ - // Connect with will - DataProcessor dataProc; - TestBasicState state; - - auto client = allocClient(&state, &dataProc); - client->setSearchgwEnabled(false); - startClient(*client); - TS_ASSERT(state.m_nextOutput.empty()); - verifyNoOtherEvent(state); -} - -void ClientBasic::test44() -{ - DataProcessor dataProc; - typedef TestBasicState Test44State; - Test44State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - - static const std::string Topic("tt"); // Short topic - static const auto Qos1 = CC_MqttsnQoS_ExactlyOnceDelivery; - static const auto Qos2 = CC_MqttsnQoS_AtLeastOnceDelivery; - - auto result = client->subscribe(Topic, Qos1); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_NotConnected); - - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->subscribe(Topic, Qos1); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - auto msgId = verifySent_SubsribeMsg(state, dataProc, Topic, Qos1, false); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->subscribe(Topic, Qos2); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Busy); - verifyNoOtherEvent(state); - - static const auto TopicId = - static_cast( - (static_cast(Topic[0]) << 8U) | static_cast(Topic[1])); - clearState(state); - state.m_nextElapsedTicks = 1000; - auto subackMsg = dataProc.prepareSubackMsg(transformQos(Qos2), TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::Congestion); - dataFromGw(*client, subackMsg, "SUBACK"); - verifyCb_ReportedSubsribe(state, CC_MqttsnAsyncOpStatus_Congestion, Qos2); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->subscribe(Topic, Qos1); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - msgId = verifySent_SubsribeMsg(state, dataProc, Topic, Qos1, false); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - subackMsg = dataProc.prepareSubackMsg(transformQos(Qos2), TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, subackMsg, "SUBACK"); - verifyCb_ReportedSubsribe(state, CC_MqttsnAsyncOpStatus_Successful, Qos2); - verifyNoOtherEvent(state); - - static const DataSeq Data = { - 0x0, 0x1, 0x2, 0xa, 0xb, 0xc - }; - static const bool Retain = true; - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->publish(Topic, &Data[0], Data.size(), Qos2, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - msgId = verifySent_PublishMsg(state, dataProc, TopicId, Data, TopicIdTypeVal::ShortTopicName, Qos2, Retain, false); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto pubackMsg = dataProc.preparePubackMsg(TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, pubackMsg, "PUBACK"); - verifyCb_ReportedPublish(state, CC_MqttsnAsyncOpStatus_Successful); - verifyNoOtherEvent(state); -} - -void ClientBasic::test45() -{ - DataProcessor dataProc; - typedef TestBasicState Test45State; - Test45State state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - - static const std::string Topic("#"); // Wildcard topic - static const auto Qos1 = CC_MqttsnQoS_ExactlyOnceDelivery; - static const auto Qos2 = CC_MqttsnQoS_AtLeastOnceDelivery; - - auto result = client->subscribe(Topic, Qos1); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_NotConnected); - - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->subscribe(Topic, Qos1); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - auto msgId = verifySent_SubsribeMsg(state, dataProc, Topic, Qos1, false); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->subscribe(Topic, Qos2); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Busy); - verifyNoOtherEvent(state); - - static const CC_MqttsnTopicId TopicId = 0x1111; - clearState(state); - state.m_nextElapsedTicks = 1000; - auto subackMsg = dataProc.prepareSubackMsg(transformQos(Qos2), TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::Congestion); - dataFromGw(*client, subackMsg, "SUBACK"); - verifyCb_ReportedSubsribe(state, CC_MqttsnAsyncOpStatus_Congestion, Qos2); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->subscribe(Topic, Qos1); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - msgId = verifySent_SubsribeMsg(state, dataProc, Topic, Qos1, false); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - subackMsg = dataProc.prepareSubackMsg(transformQos(Qos2), TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, subackMsg, "SUBACK"); - verifyCb_ReportedSubsribe(state, CC_MqttsnAsyncOpStatus_Successful, Qos2); - verifyNoOtherEvent(state); - - static const DataSeq Data = { - 0x0, 0x1, 0x2, 0xa, 0xb, 0xc - }; - static const bool Retain = true; - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->publish(Topic, &Data[0], Data.size(), Qos2, Retain); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - msgId = verifySent_PublishMsg(state, dataProc, TopicId, Data, TopicIdTypeVal::Normal, Qos2, Retain, false); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto pubackMsg = dataProc.preparePubackMsg(TopicId, msgId, cc_mqttsn::field::ReturnCodeVal::Accepted); - dataFromGw(*client, pubackMsg, "PUBACK"); - verifyCb_ReportedPublish(state, CC_MqttsnAsyncOpStatus_Successful); - verifyNoOtherEvent(state); -} - -void ClientBasic::test46() -{ - DataProcessor dataProc; - TestBasicState state; - - auto client = allocClient(&state, &dataProc); - startClient(*client); - - doGwInfo(*client, dataProc, state); - clearState(state); - - static const std::string Topic("tt"); // Short topic - - auto result = client->unsubscribe(Topic); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_NotConnected); - - clearState(state); - doConnect(*client, dataProc, state); - - TS_ASSERT_EQUALS(state.m_nextRequestedTicks, DefaultKeepAlive * 1000); - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->unsubscribe(Topic); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Success); - auto msgId = verifySent_UnsubsribeMsg(state, dataProc, Topic); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - result = client->unsubscribe(Topic); - TS_ASSERT_EQUALS(result, CC_MqttsnErrorCode_Busy); - verifyNoOtherEvent(state); - - clearState(state); - state.m_nextElapsedTicks = 1000; - auto unsubackMsg = dataProc.prepareUnsubackMsg(msgId); - dataFromGw(*client, unsubackMsg, "UNSUBACK"); - verifyCb_ReportedUnsubsribe(state, CC_MqttsnAsyncOpStatus_Successful); - verifyNoOtherEvent(state); -} diff --git a/client.old/test/CommonTestClient.cpp b/client.old/test/CommonTestClient.cpp deleted file mode 100644 index c971165e..00000000 --- a/client.old/test/CommonTestClient.cpp +++ /dev/null @@ -1,737 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include "CommonTestClient.h" - -#include -#include - -namespace -{ - -ClientLibFuncs createDefaultLibFuncs() -{ - ClientLibFuncs funcs; - funcs.m_newFunc = &cc_mqttsn_client_new; - funcs.m_freeFunc = &cc_mqttsn_client_free; - funcs.m_nextTickProgramCallbackSetFunc = &cc_mqttsn_client_set_next_tick_program_callback; - funcs.m_cancelNextTickCallbackSetFunc = &cc_mqttsn_client_set_cancel_next_tick_wait_callback; - funcs.m_sentOutDataCallbackSetFunc = &cc_mqttsn_client_set_send_output_data_callback; - funcs.m_gwStatusReportCallbackSetFunc = &cc_mqttsn_client_set_gw_status_report_callback; - funcs.m_gwDisconnectReportCallbackSetFunc = &cc_mqttsn_client_set_gw_disconnect_report_callback; - funcs.m_msgReportCallbackSetFunc = &cc_mqttsn_client_set_message_report_callback; - funcs.m_startFunc = &cc_mqttsn_client_start; - funcs.m_processDataFunc = &cc_mqttsn_client_process_data; - funcs.m_tickFunc = &cc_mqttsn_client_tick; - funcs.m_setRetryPeriodFunc = &cc_mqttsn_client_set_retry_period; - funcs.m_setRetryCountFunc = &cc_mqttsn_client_set_retry_count; - funcs.m_setBroadcastRadius = &cc_mqttsn_client_set_default_broadcast_radius; - funcs.m_setSearchgwEnabledFunc = &cc_mqttsn_client_set_searchgw_enabled; - funcs.m_cancelFunc = &cc_mqttsn_client_cancel; - funcs.m_connectFunc = &cc_mqttsn_client_connect; - funcs.m_disconnectFunc = &cc_mqttsn_client_disconnect; - funcs.m_publishIdFunc = &cc_mqttsn_client_publish_id; - funcs.m_publishFunc = &cc_mqttsn_client_publish; - funcs.m_subscribeIdFunc = &cc_mqttsn_client_subscribe_id; - funcs.m_subscribeFunc = &cc_mqttsn_client_subscribe; - funcs.m_unsubscribeIdFunc = &cc_mqttsn_client_unsubscribe_id; - funcs.m_unsubscribeFunc = &cc_mqttsn_client_unsubscribe; - funcs.m_willUpdateFunc = &cc_mqttsn_client_will_update; - funcs.m_willTopicUpdateFunc = &cc_mqttsn_client_will_topic_update; - funcs.m_willMsgUpdateFunc = &cc_mqttsn_client_will_msg_update; - funcs.m_sleepFunc = &cc_mqttsn_client_sleep; - funcs.m_reconnectFunc = &cc_mqttsn_client_reconnect; - funcs.m_checkMessagesFunc = &cc_mqttsn_client_check_messages; - return funcs; -} - -} // namespace - -const ClientLibFuncs CommonTestClient::DefaultFuncs = createDefaultLibFuncs(); - -CommonTestClient::~CommonTestClient() -{ - assert(m_libFuncs.m_freeFunc != nullptr); - m_libFuncs.m_freeFunc(m_client); -} - -CommonTestClient::ProgramNextTickCallback CommonTestClient::setProgramNextTickCallback( - ProgramNextTickCallback&& func) -{ - ProgramNextTickCallback old(std::move(m_programNextTickCallback)); - m_programNextTickCallback = std::move(func); - return old; -} - -CommonTestClient::CancelNextTickCallback CommonTestClient::setCancelNextTickCallback( - CancelNextTickCallback&& func) -{ - CancelNextTickCallback old(std::move(m_cancelNextTickCallback)); - m_cancelNextTickCallback = std::move(func); - return old; -} - -CommonTestClient::SendDataCallback CommonTestClient::setSendDataCallback( - SendDataCallback&& func) -{ - SendDataCallback old(std::move(m_sendDataCallback)); - m_sendDataCallback = std::move(func); - return old; -} - -CommonTestClient::GwStatusReportCallback CommonTestClient::setGwStatusReportCallback( - GwStatusReportCallback&& func) -{ - GwStatusReportCallback old(std::move(m_gwStatusReportCallback)); - m_gwStatusReportCallback = std::move(func); - return old; -} - -CommonTestClient::GwDisconnectReportCallback CommonTestClient::setGwDisconnectReportCallback( - GwDisconnectReportCallback&& func) -{ - GwDisconnectReportCallback old(std::move(m_gwDisconnectReportCallback)); - m_gwDisconnectReportCallback = std::move(func); - return old; -} - -CommonTestClient::MessageReportCallback CommonTestClient::setMessageReportCallback( - MessageReportCallback&& func) -{ - MessageReportCallback old(std::move(m_msgReportCallback)); - m_msgReportCallback = std::move(func); - return old; -} -CommonTestClient::AsyncOpCompleteCallback CommonTestClient::setConnectCompleteCallback( - AsyncOpCompleteCallback&& func) -{ - AsyncOpCompleteCallback old(std::move(m_connectCompleteCallback)); - m_connectCompleteCallback = std::move(func); - return old; -} - -CommonTestClient::AsyncOpCompleteCallback CommonTestClient::setDisconnectCompleteCallback( - AsyncOpCompleteCallback&& func) -{ - AsyncOpCompleteCallback old(std::move(m_disconnectCompleteCallback)); - m_disconnectCompleteCallback = std::move(func); - return old; -} - -CommonTestClient::AsyncOpCompleteCallback CommonTestClient::setPublishCompleteCallback( - AsyncOpCompleteCallback&& func) -{ - AsyncOpCompleteCallback old(std::move(m_publishCompleteCallback)); - m_publishCompleteCallback = std::move(func); - return old; -} - -CommonTestClient::SubscribeCompleteCallback CommonTestClient::setSubsribeCompleteCallback( - SubscribeCompleteCallback&& func) -{ - SubscribeCompleteCallback old(std::move(m_subscribeCompleteCallback)); - m_subscribeCompleteCallback = std::move(func); - return old; -} - -CommonTestClient::AsyncOpCompleteCallback CommonTestClient::setUnsubsribeCompleteCallback( - AsyncOpCompleteCallback&& func) -{ - AsyncOpCompleteCallback old(std::move(m_unsubscribeCompleteCallback)); - m_unsubscribeCompleteCallback = std::move(func); - return old; -} - -CommonTestClient::AsyncOpCompleteCallback CommonTestClient::setWillUpdateCompleteCallback( - AsyncOpCompleteCallback&& func) -{ - AsyncOpCompleteCallback old(std::move(m_willUpdateCompleteCallback)); - m_willUpdateCompleteCallback = std::move(func); - return old; -} - -CommonTestClient::AsyncOpCompleteCallback CommonTestClient::setWillTopicUpdateCompleteCallback( - AsyncOpCompleteCallback&& func) -{ - AsyncOpCompleteCallback old(std::move(m_willTopicUpdateCompleteCallback)); - m_willTopicUpdateCompleteCallback = std::move(func); - return old; -} - -CommonTestClient::AsyncOpCompleteCallback CommonTestClient::setWillMsgUpdateCompleteCallback( - AsyncOpCompleteCallback&& func) -{ - AsyncOpCompleteCallback old(std::move(m_willMsgUpdateCompleteCallback)); - m_willMsgUpdateCompleteCallback = std::move(func); - return old; -} - -CommonTestClient::AsyncOpCompleteCallback CommonTestClient::setSleepCompleteCallback( - AsyncOpCompleteCallback&& func) -{ - AsyncOpCompleteCallback old(std::move(m_sleepCompleteCallback)); - m_sleepCompleteCallback = std::move(func); - return old; -} - -CommonTestClient::AsyncOpCompleteCallback CommonTestClient::setReconnectCompleteCallback( - AsyncOpCompleteCallback&& func) -{ - AsyncOpCompleteCallback old(std::move(m_reconnectCompleteCallback)); - m_reconnectCompleteCallback = std::move(func); - return old; -} - -CommonTestClient::AsyncOpCompleteCallback CommonTestClient::setCheckMessagesCompleteCallback( - AsyncOpCompleteCallback&& func) -{ - AsyncOpCompleteCallback old(std::move(m_checkMessagesCompleteCallback)); - m_checkMessagesCompleteCallback = std::move(func); - return old; -} - -CommonTestClient::Ptr CommonTestClient::alloc(const ClientLibFuncs& libFuncs) -{ - return Ptr(new CommonTestClient(libFuncs)); -} - -CC_MqttsnErrorCode CommonTestClient::start() -{ - assert(m_libFuncs.m_startFunc != nullptr); - return (m_libFuncs.m_startFunc)(m_client); -} - -void CommonTestClient::inputData(const std::uint8_t* buf, std::size_t bufLen) -{ - assert(m_inData.empty()); - m_inData.insert(m_inData.end(), buf, buf + bufLen); - assert(m_libFuncs.m_processDataFunc != nullptr); - assert(!m_inData.empty()); - unsigned count = (m_libFuncs.m_processDataFunc)(m_client, &m_inData[0], static_cast(m_inData.size())); - if (m_inData.size() < count) { - std::cout << "Processed " << count << " bytes, while having only " << m_inData.size() << std::endl; - } - assert(count <= m_inData.size()); - m_inData.erase(m_inData.begin(), m_inData.begin() + count); -} - -void CommonTestClient::tick() -{ - assert(m_libFuncs.m_tickFunc != nullptr); - (m_libFuncs.m_tickFunc)(m_client); -} - -void CommonTestClient::setRetryPeriod(unsigned ms) -{ - assert(m_libFuncs.m_setRetryPeriodFunc != nullptr); - (m_libFuncs.m_setRetryPeriodFunc)(m_client, ms); -} - -void CommonTestClient::setRetryCount(unsigned value) -{ - assert(m_libFuncs.m_setRetryCountFunc != nullptr); - (m_libFuncs.m_setRetryCountFunc)(m_client, value); -} - -void CommonTestClient::setBroadcastRadius(unsigned char val) -{ - assert(m_libFuncs.m_setBroadcastRadius != nullptr); - (m_libFuncs.m_setBroadcastRadius)(m_client, val); -} - -void CommonTestClient::setSearchgwEnabled(bool value) -{ - assert(m_libFuncs.m_setSearchgwEnabledFunc != nullptr); - (m_libFuncs.m_setSearchgwEnabledFunc)(m_client, value); -} - -CC_MqttsnErrorCode CommonTestClient::connect( - const char* clientId, - unsigned short keepAliveSeconds, - bool cleanSession, - const CC_MqttsnWillInfo* willInfo) -{ - assert(m_libFuncs.m_connectFunc != nullptr); - - return - (m_libFuncs.m_connectFunc)( - m_client, - clientId, - keepAliveSeconds, - cleanSession, - willInfo, - &CommonTestClient::connectCompleteCallback, - this); -} - -bool CommonTestClient::cancel() -{ - assert(m_libFuncs.m_cancelFunc != nullptr); - return (m_libFuncs.m_cancelFunc)(m_client); -} - -CC_MqttsnErrorCode CommonTestClient::disconnect() -{ - assert(m_libFuncs.m_disconnectFunc != nullptr); - return (m_libFuncs.m_disconnectFunc)( - m_client, - &CommonTestClient::disconnectCompleteCallback, - this); -} - -CC_MqttsnErrorCode CommonTestClient::publishId( - CC_MqttsnTopicId topicId, - const std::uint8_t* msg, - std::size_t msgLen, - CC_MqttsnQoS qos, - bool retain) -{ - assert(m_libFuncs.m_publishIdFunc != nullptr); - return (m_libFuncs.m_publishIdFunc)( - m_client, - topicId, - msg, - static_cast(msgLen), - qos, - retain, - &CommonTestClient::publishCompleteCallback, - this); -} - -CC_MqttsnErrorCode CommonTestClient::publish( - const std::string& topic, - const std::uint8_t* msg, - std::size_t msgLen, - CC_MqttsnQoS qos, - bool retain) -{ - assert(m_libFuncs.m_publishFunc != nullptr); - return (m_libFuncs.m_publishFunc)( - m_client, - topic.c_str(), - msg, - static_cast(msgLen), - qos, - retain, - &CommonTestClient::publishCompleteCallback, - this); -} - -CC_MqttsnErrorCode CommonTestClient::subscribe( - const std::string& topic, - CC_MqttsnQoS qos) -{ - assert(m_libFuncs.m_subscribeFunc != nullptr); - return (m_libFuncs.m_subscribeFunc)( - m_client, - topic.c_str(), - qos, - &CommonTestClient::subsribeCompleteCallback, - this); -} - -CC_MqttsnErrorCode CommonTestClient::subscribe( - CC_MqttsnTopicId topicId, - CC_MqttsnQoS qos) -{ - assert(m_libFuncs.m_subscribeIdFunc != nullptr); - return (m_libFuncs.m_subscribeIdFunc)( - m_client, - topicId, - qos, - &CommonTestClient::subsribeCompleteCallback, - this); -} - -CC_MqttsnErrorCode CommonTestClient::unsubscribe(const std::string& topic) -{ - assert(m_libFuncs.m_unsubscribeFunc != nullptr); - return (m_libFuncs.m_unsubscribeFunc)( - m_client, - topic.c_str(), - &CommonTestClient::unsubsribeCompleteCallback, - this); -} - -CC_MqttsnErrorCode CommonTestClient::unsubscribe(CC_MqttsnTopicId topicId) -{ - assert(m_libFuncs.m_unsubscribeIdFunc != nullptr); - return (m_libFuncs.m_unsubscribeIdFunc)( - m_client, - topicId, - &CommonTestClient::unsubsribeCompleteCallback, - this); -} - -CC_MqttsnErrorCode CommonTestClient::willUpdate( - const CC_MqttsnWillInfo* willInfo) -{ - assert(m_libFuncs.m_willUpdateFunc != nullptr); - return (m_libFuncs.m_willUpdateFunc)( - m_client, - willInfo, - &CommonTestClient::willUpdateCompleteCallback, - this); -} - -CC_MqttsnErrorCode CommonTestClient::willTopicUpdate( - const std::string& topic, - CC_MqttsnQoS qos, - bool retain) -{ - assert(m_libFuncs.m_willTopicUpdateFunc != nullptr); - return (m_libFuncs.m_willTopicUpdateFunc)( - m_client, - topic.c_str(), - qos, - retain, - &CommonTestClient::willTopicUpdateCompleteCallback, - this); -} - -CC_MqttsnErrorCode CommonTestClient::willMsgUpdate( - const std::uint8_t* msg, - std::size_t msgLen) -{ - assert(m_libFuncs.m_willMsgUpdateFunc != nullptr); - return (m_libFuncs.m_willMsgUpdateFunc)( - m_client, - msg, - static_cast(msgLen), - &CommonTestClient::willMsgUpdateCompleteCallback, - this); -} - -CC_MqttsnErrorCode CommonTestClient::sleep(std::uint16_t duration) -{ - assert(m_libFuncs.m_sleepFunc != nullptr); - return (m_libFuncs.m_sleepFunc)( - m_client, - duration, - &CommonTestClient::sleepCompleteCallback, - this); -} - -CC_MqttsnErrorCode CommonTestClient::reconnect() -{ - assert(m_libFuncs.m_reconnectFunc != nullptr); - return (m_libFuncs.m_reconnectFunc)( - m_client, - &CommonTestClient::reconnectCompleteCallback, - this); -} - -CC_MqttsnErrorCode CommonTestClient::checkMessages() -{ - assert(m_libFuncs.m_checkMessagesFunc != nullptr); - return (m_libFuncs.m_checkMessagesFunc)( - m_client, - &CommonTestClient::checkMessagesCompleteCallback, - this); -} - -CC_MqttsnQoS CommonTestClient::transformQos(cc_mqttsn::field::QosVal val) -{ - static_assert( - static_cast(cc_mqttsn::field::QosVal::AtMostOnceDelivery) == CC_MqttsnQoS_AtMostOnceDelivery, - "Invalid mapping"); - - static_assert( - static_cast(cc_mqttsn::field::QosVal::AtLeastOnceDelivery) == CC_MqttsnQoS_AtLeastOnceDelivery, - "Invalid mapping"); - - static_assert( - static_cast(cc_mqttsn::field::QosVal::ExactlyOnceDelivery) == CC_MqttsnQoS_ExactlyOnceDelivery, - "Invalid mapping"); - - if (val == cc_mqttsn::field::QosVal::NoGwPublish) { - return CC_MqttsnQoS_NoGwPublish; - } - - return static_cast(val); -} - -cc_mqttsn::field::QosVal CommonTestClient::transformQos(CC_MqttsnQoS val) -{ - - if (val == CC_MqttsnQoS_NoGwPublish) { - return cc_mqttsn::field::QosVal::NoGwPublish; - } - - return static_cast(val); -} - -CommonTestClient::CommonTestClient(const ClientLibFuncs& libFuncs) - : m_libFuncs(libFuncs), - m_client((libFuncs.m_newFunc)()) -{ - assert(m_libFuncs.m_nextTickProgramCallbackSetFunc != nullptr); - assert(m_libFuncs.m_cancelNextTickCallbackSetFunc != nullptr); - assert(m_libFuncs.m_sentOutDataCallbackSetFunc != nullptr); - assert(m_libFuncs.m_gwStatusReportCallbackSetFunc != nullptr); - assert(m_libFuncs.m_gwDisconnectReportCallbackSetFunc != nullptr); - assert(m_libFuncs.m_msgReportCallbackSetFunc != nullptr); - - (m_libFuncs.m_nextTickProgramCallbackSetFunc)(m_client, &CommonTestClient::nextTickProgramCallback, this); - (m_libFuncs.m_cancelNextTickCallbackSetFunc)(m_client, &CommonTestClient::cancelNextTickCallback, this); - (m_libFuncs.m_sentOutDataCallbackSetFunc)(m_client, &CommonTestClient::sendOutputDataCallback, this); - (m_libFuncs.m_gwStatusReportCallbackSetFunc)(m_client, &CommonTestClient::gwStatusReportCallback, this); - (m_libFuncs.m_gwDisconnectReportCallbackSetFunc)(m_client, &CommonTestClient::gwDisconnectReportCallback, this); - (m_libFuncs.m_msgReportCallbackSetFunc)(m_client, &CommonTestClient::msgReportCallback, this); - // TODO: callbacks -} - -void CommonTestClient::programNextTick(unsigned duration) -{ - if (m_programNextTickCallback) { - ProgramNextTickCallback tmp(m_programNextTickCallback); - tmp(duration); - } -} - -unsigned CommonTestClient::cancelNextTick() -{ - if (m_cancelNextTickCallback) { - CancelNextTickCallback tmp(m_cancelNextTickCallback); - return tmp(); - } - - constexpr bool Should_not_happen = false; - static_cast(Should_not_happen); - assert(Should_not_happen); - return 0; -} - -void CommonTestClient::sendOutputData(const unsigned char* buf, unsigned bufLen, bool broadcast) -{ - if (m_sendDataCallback) { - SendDataCallback tmp(m_sendDataCallback); - tmp(buf, bufLen, broadcast); - } -} - -void CommonTestClient::reportGwStatus(unsigned short gwId, CC_MqttsnGwStatus status) -{ - if (m_gwStatusReportCallback) { - GwStatusReportCallback tmp(m_gwStatusReportCallback); - tmp(gwId, status); - } -} - -void CommonTestClient::reportGwDisconnect() -{ - if (m_gwDisconnectReportCallback) { - GwDisconnectReportCallback tmp(m_gwDisconnectReportCallback); - tmp(); - } -} - -void CommonTestClient::reportMessage(const CC_MqttsnMessageInfo* msgInfo) -{ - if (m_msgReportCallback) { - assert(msgInfo != nullptr); - MessageReportCallback tmp(m_msgReportCallback); - tmp(*msgInfo); - } -} - -void CommonTestClient::reportConnectComplete(CC_MqttsnAsyncOpStatus status) -{ - if (m_connectCompleteCallback) { - AsyncOpCompleteCallback tmp(m_connectCompleteCallback); - tmp(status); - } -} - -void CommonTestClient::reportDisconnectComplete(CC_MqttsnAsyncOpStatus status) -{ - if (m_disconnectCompleteCallback) { - AsyncOpCompleteCallback tmp(m_disconnectCompleteCallback); - tmp(status); - } -} - -void CommonTestClient::reportPublishComplete(CC_MqttsnAsyncOpStatus status) -{ - if (m_publishCompleteCallback) { - AsyncOpCompleteCallback tmp(m_publishCompleteCallback); - tmp(status); - } -} - -void CommonTestClient::reportSubsribeComplete(CC_MqttsnAsyncOpStatus status, CC_MqttsnQoS qos) -{ - if (m_subscribeCompleteCallback) { - SubscribeCompleteCallback tmp(m_subscribeCompleteCallback); - tmp(status, qos); - } -} - -void CommonTestClient::reportUnsubsribeComplete(CC_MqttsnAsyncOpStatus status) -{ - if (m_unsubscribeCompleteCallback) { - AsyncOpCompleteCallback tmp(m_unsubscribeCompleteCallback); - tmp(status); - } -} - -void CommonTestClient::reportWillUpdateComplete(CC_MqttsnAsyncOpStatus status) -{ - if (m_willUpdateCompleteCallback) { - AsyncOpCompleteCallback tmp(m_willUpdateCompleteCallback); - tmp(status); - } -} - -void CommonTestClient::reportWillTopicUpdateComplete(CC_MqttsnAsyncOpStatus status) -{ - if (m_willTopicUpdateCompleteCallback) { - AsyncOpCompleteCallback tmp(m_willTopicUpdateCompleteCallback); - tmp(status); - } -} - -void CommonTestClient::reportWillMsgUpdateComplete(CC_MqttsnAsyncOpStatus status) -{ - if (m_willMsgUpdateCompleteCallback) { - AsyncOpCompleteCallback tmp(m_willMsgUpdateCompleteCallback); - tmp(status); - } -} - -void CommonTestClient::reportSleepComplete(CC_MqttsnAsyncOpStatus status) -{ - if (m_sleepCompleteCallback) { - AsyncOpCompleteCallback tmp(m_sleepCompleteCallback); - tmp(status); - } -} - -void CommonTestClient::reportReconnectComplete(CC_MqttsnAsyncOpStatus status) -{ - if (m_reconnectCompleteCallback) { - AsyncOpCompleteCallback tmp(m_reconnectCompleteCallback); - tmp(status); - } -} - -void CommonTestClient::reportCheckMessagesComplete(CC_MqttsnAsyncOpStatus status) -{ - if (m_checkMessagesCompleteCallback) { - AsyncOpCompleteCallback tmp(m_checkMessagesCompleteCallback); - tmp(status); - } -} - -void CommonTestClient::nextTickProgramCallback(void* data, unsigned duration) -{ - assert(data != nullptr); - reinterpret_cast(data)->programNextTick(duration); -} - -unsigned CommonTestClient::cancelNextTickCallback(void* data) -{ - assert(data != nullptr); - return reinterpret_cast(data)->cancelNextTick(); -} - -void CommonTestClient::sendOutputDataCallback( - void* data, - const unsigned char* buf, - unsigned bufLen, - bool broadcast) -{ - assert(data != nullptr); - reinterpret_cast(data)->sendOutputData(buf, bufLen, broadcast); -} - -void CommonTestClient::gwStatusReportCallback( - void* data, - unsigned char gwId, - CC_MqttsnGwStatus status) -{ - assert(data != nullptr); - reinterpret_cast(data)->reportGwStatus(gwId, status); -} - -void CommonTestClient::gwDisconnectReportCallback(void* data) -{ - assert(data != nullptr); - reinterpret_cast(data)->reportGwDisconnect(); -} - -void CommonTestClient::msgReportCallback(void* data, const CC_MqttsnMessageInfo* msgInfo) -{ - assert(data != nullptr); - reinterpret_cast(data)->reportMessage(msgInfo); -} - -void CommonTestClient::publishCompleteCallback(void* data, CC_MqttsnAsyncOpStatus status) -{ - assert(data != nullptr); - reinterpret_cast(data)->reportPublishComplete(status); -} - -void CommonTestClient::connectCompleteCallback(void* data, CC_MqttsnAsyncOpStatus status) -{ - assert(data != nullptr); - reinterpret_cast(data)->reportConnectComplete(status); -} - -void CommonTestClient::disconnectCompleteCallback(void* data, CC_MqttsnAsyncOpStatus status) -{ - assert(data != nullptr); - reinterpret_cast(data)->reportDisconnectComplete(status); -} - -void CommonTestClient::subsribeCompleteCallback(void* data, CC_MqttsnAsyncOpStatus status, CC_MqttsnQoS qos) -{ - assert(data != nullptr); - reinterpret_cast(data)->reportSubsribeComplete(status, qos); -} - -void CommonTestClient::unsubsribeCompleteCallback(void* data, CC_MqttsnAsyncOpStatus status) -{ - assert(data != nullptr); - reinterpret_cast(data)->reportUnsubsribeComplete(status); -} - -void CommonTestClient::willUpdateCompleteCallback(void* data, CC_MqttsnAsyncOpStatus status) -{ - assert(data != nullptr); - reinterpret_cast(data)->reportWillUpdateComplete(status); -} - -void CommonTestClient::willTopicUpdateCompleteCallback(void* data, CC_MqttsnAsyncOpStatus status) -{ - assert(data != nullptr); - reinterpret_cast(data)->reportWillTopicUpdateComplete(status); -} - -void CommonTestClient::willMsgUpdateCompleteCallback(void* data, CC_MqttsnAsyncOpStatus status) -{ - assert(data != nullptr); - reinterpret_cast(data)->reportWillMsgUpdateComplete(status); -} - -void CommonTestClient::sleepCompleteCallback(void* data, CC_MqttsnAsyncOpStatus status) -{ - assert(data != nullptr); - reinterpret_cast(data)->reportSleepComplete(status); -} - -void CommonTestClient::reconnectCompleteCallback(void* data, CC_MqttsnAsyncOpStatus status) -{ - assert(data != nullptr); - reinterpret_cast(data)->reportReconnectComplete(status); -} - -void CommonTestClient::checkMessagesCompleteCallback(void* data, CC_MqttsnAsyncOpStatus status) -{ - assert(data != nullptr); - reinterpret_cast(data)->reportCheckMessagesComplete(status); -} diff --git a/client.old/test/CommonTestClient.h b/client.old/test/CommonTestClient.h deleted file mode 100644 index a900a794..00000000 --- a/client.old/test/CommonTestClient.h +++ /dev/null @@ -1,249 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - - -#pragma once - -#include -#include -#include -#include -#include - -#include "cc_mqttsn_client/common.h" -#include "cc_mqttsn/field/QosCommon.h" -#include "client.h" - -typedef decltype(&cc_mqttsn_client_new) ClientNewFunc; -typedef decltype(&cc_mqttsn_client_free) ClientFreeFunc; -typedef decltype(&cc_mqttsn_client_set_next_tick_program_callback) NextTickProgramCallbackSetFunc; -typedef decltype(&cc_mqttsn_client_set_cancel_next_tick_wait_callback) CancelNextTickCallbackSetFunc; -typedef decltype(&cc_mqttsn_client_set_send_output_data_callback) SendOutDataCallbackSetFunc; -typedef decltype(&cc_mqttsn_client_set_gw_status_report_callback) GwStatusReportCallbackSetFunc; -typedef decltype(&cc_mqttsn_client_set_gw_disconnect_report_callback) GwDisconnectsReportCallbackSetFunc; -typedef decltype(&cc_mqttsn_client_set_message_report_callback) MessageReportCallbackSetFunc; -typedef decltype(&cc_mqttsn_client_start) StartFunc; -typedef decltype(&cc_mqttsn_client_process_data) ProcessDataFunc; -typedef decltype(&cc_mqttsn_client_tick) TickFunc; -typedef decltype(&cc_mqttsn_client_set_retry_period) SetRetryPeriodFunc; -typedef decltype(&cc_mqttsn_client_set_retry_count) SetRetryCountFunc; -typedef decltype(&cc_mqttsn_client_set_default_broadcast_radius) SetBroadcastRadiusFunc; -typedef decltype(&cc_mqttsn_client_set_searchgw_enabled) SetSearchgwEnabledFunc; -typedef decltype(&cc_mqttsn_client_cancel) CancelFunc; -typedef decltype(&cc_mqttsn_client_connect) ConnectFunc; -typedef decltype(&cc_mqttsn_client_disconnect) DisconnectFunc; -typedef decltype(&cc_mqttsn_client_publish_id) PublishIdFunc; -typedef decltype(&cc_mqttsn_client_publish) PublishFunc; -typedef decltype(&cc_mqttsn_client_subscribe_id) SubscribeIdFunc; -typedef decltype(&cc_mqttsn_client_subscribe) SubscribeFunc; -typedef decltype(&cc_mqttsn_client_unsubscribe_id) UnsubscribeIdFunc; -typedef decltype(&cc_mqttsn_client_unsubscribe) UnsubscribeFunc; -typedef decltype(&cc_mqttsn_client_will_update) WillUpdateFunc; -typedef decltype(&cc_mqttsn_client_will_topic_update) WillTopicUpdateFunc; -typedef decltype(&cc_mqttsn_client_will_msg_update) WillMsgUpdateFunc; -typedef decltype(&cc_mqttsn_client_sleep) SleepFunc; -typedef decltype(&cc_mqttsn_client_reconnect) ReconnectFunc; -typedef decltype(&cc_mqttsn_client_check_messages) CheckMessagesFunc; - - -struct ClientLibFuncs -{ - ClientNewFunc m_newFunc = nullptr; - ClientFreeFunc m_freeFunc = nullptr; - NextTickProgramCallbackSetFunc m_nextTickProgramCallbackSetFunc = nullptr; - CancelNextTickCallbackSetFunc m_cancelNextTickCallbackSetFunc = nullptr; - SendOutDataCallbackSetFunc m_sentOutDataCallbackSetFunc = nullptr; - GwStatusReportCallbackSetFunc m_gwStatusReportCallbackSetFunc = nullptr; - GwDisconnectsReportCallbackSetFunc m_gwDisconnectReportCallbackSetFunc = nullptr; - MessageReportCallbackSetFunc m_msgReportCallbackSetFunc = nullptr; - StartFunc m_startFunc = nullptr; - ProcessDataFunc m_processDataFunc = nullptr; - TickFunc m_tickFunc = nullptr; - SetRetryPeriodFunc m_setRetryPeriodFunc = nullptr; - SetRetryCountFunc m_setRetryCountFunc = nullptr; - SetBroadcastRadiusFunc m_setBroadcastRadius = nullptr; - SetSearchgwEnabledFunc m_setSearchgwEnabledFunc = nullptr; - CancelFunc m_cancelFunc = nullptr; - ConnectFunc m_connectFunc = nullptr; - DisconnectFunc m_disconnectFunc = nullptr; - PublishIdFunc m_publishIdFunc = nullptr; - PublishFunc m_publishFunc = nullptr; - SubscribeIdFunc m_subscribeIdFunc = nullptr; - SubscribeFunc m_subscribeFunc = nullptr; - UnsubscribeIdFunc m_unsubscribeIdFunc = nullptr; - UnsubscribeFunc m_unsubscribeFunc = nullptr; - WillUpdateFunc m_willUpdateFunc = nullptr; - WillTopicUpdateFunc m_willTopicUpdateFunc = nullptr; - WillMsgUpdateFunc m_willMsgUpdateFunc = nullptr; - SleepFunc m_sleepFunc = nullptr; - ReconnectFunc m_reconnectFunc = nullptr; - CheckMessagesFunc m_checkMessagesFunc = nullptr; -}; - -class CommonTestClient -{ -public: - typedef std::unique_ptr Ptr; - typedef std::function ProgramNextTickCallback; - typedef std::function CancelNextTickCallback; - typedef std::function SendDataCallback; - typedef std::function GwStatusReportCallback; - typedef std::function GwDisconnectReportCallback; - typedef std::function MessageReportCallback; - typedef std::function AsyncOpCompleteCallback; - typedef std::function SubscribeCompleteCallback; - - ~CommonTestClient(); - - ProgramNextTickCallback setProgramNextTickCallback(ProgramNextTickCallback&& func); - CancelNextTickCallback setCancelNextTickCallback(CancelNextTickCallback&& func); - SendDataCallback setSendDataCallback(SendDataCallback&& func); - GwStatusReportCallback setGwStatusReportCallback(GwStatusReportCallback&& func); - GwDisconnectReportCallback setGwDisconnectReportCallback(GwDisconnectReportCallback&& func); - MessageReportCallback setMessageReportCallback(MessageReportCallback&& func); - AsyncOpCompleteCallback setConnectCompleteCallback(AsyncOpCompleteCallback&& func); - AsyncOpCompleteCallback setDisconnectCompleteCallback(AsyncOpCompleteCallback&& func); - AsyncOpCompleteCallback setPublishCompleteCallback(AsyncOpCompleteCallback&& func); - SubscribeCompleteCallback setSubsribeCompleteCallback(SubscribeCompleteCallback&& func); - AsyncOpCompleteCallback setUnsubsribeCompleteCallback(AsyncOpCompleteCallback&& func); - AsyncOpCompleteCallback setWillUpdateCompleteCallback(AsyncOpCompleteCallback&& func); - AsyncOpCompleteCallback setWillTopicUpdateCompleteCallback(AsyncOpCompleteCallback&& func); - AsyncOpCompleteCallback setWillMsgUpdateCompleteCallback(AsyncOpCompleteCallback&& func); - AsyncOpCompleteCallback setSleepCompleteCallback(AsyncOpCompleteCallback&& func); - AsyncOpCompleteCallback setReconnectCompleteCallback(AsyncOpCompleteCallback&& func); - AsyncOpCompleteCallback setCheckMessagesCompleteCallback(AsyncOpCompleteCallback&& func); - - static Ptr alloc(const ClientLibFuncs& libFuncs = DefaultFuncs); - CC_MqttsnErrorCode start(); - void inputData(const std::uint8_t* buf, std::size_t bufLen); - void tick(); - void setRetryPeriod(unsigned ms); - void setRetryCount(unsigned value); - void setBroadcastRadius(unsigned char val); - void setSearchgwEnabled(bool value); - - bool cancel(); - CC_MqttsnErrorCode connect( - const char* clientId, - unsigned short keepAliveSeconds, - bool cleanSession, - const CC_MqttsnWillInfo* willInfo); - - CC_MqttsnErrorCode disconnect(); - - CC_MqttsnErrorCode publishId( - CC_MqttsnTopicId topicId, - const std::uint8_t* msg, - std::size_t msgLen, - CC_MqttsnQoS qos, - bool retain); - - CC_MqttsnErrorCode publish( - const std::string& topic, - const std::uint8_t* msg, - std::size_t msgLen, - CC_MqttsnQoS qos, - bool retain); - - CC_MqttsnErrorCode subscribe( - const std::string& topic, - CC_MqttsnQoS qos); - - CC_MqttsnErrorCode subscribe( - CC_MqttsnTopicId topicId, - CC_MqttsnQoS qos); - - CC_MqttsnErrorCode unsubscribe(const std::string& topic); - - CC_MqttsnErrorCode unsubscribe(CC_MqttsnTopicId topicId); - - CC_MqttsnErrorCode willUpdate( - const CC_MqttsnWillInfo* willInfo); - - CC_MqttsnErrorCode willTopicUpdate( - const std::string& topic, - CC_MqttsnQoS qos, - bool retain); - - CC_MqttsnErrorCode willMsgUpdate( - const std::uint8_t* msg, - std::size_t msgLen); - - CC_MqttsnErrorCode sleep(std::uint16_t duration); - - CC_MqttsnErrorCode reconnect(); - - CC_MqttsnErrorCode checkMessages(); - - static CC_MqttsnQoS transformQos(cc_mqttsn::field::QosVal val); - static cc_mqttsn::field::QosVal transformQos(CC_MqttsnQoS val); - -private: - typedef std::vector InputData; - - CommonTestClient(const ClientLibFuncs& libFuncs); - - void programNextTick(unsigned duration); - unsigned cancelNextTick(); - void sendOutputData(const unsigned char* buf, unsigned bufLen, bool broadcast); - void reportGwStatus(unsigned short gwId, CC_MqttsnGwStatus status); - void reportGwDisconnect(); - void reportMessage(const CC_MqttsnMessageInfo* msgInfo); - void reportConnectComplete(CC_MqttsnAsyncOpStatus status); - void reportDisconnectComplete(CC_MqttsnAsyncOpStatus status); - void reportPublishComplete(CC_MqttsnAsyncOpStatus status); - void reportSubsribeComplete(CC_MqttsnAsyncOpStatus status, CC_MqttsnQoS qos); - void reportUnsubsribeComplete(CC_MqttsnAsyncOpStatus status); - void reportWillUpdateComplete(CC_MqttsnAsyncOpStatus status); - void reportWillTopicUpdateComplete(CC_MqttsnAsyncOpStatus status); - void reportWillMsgUpdateComplete(CC_MqttsnAsyncOpStatus status); - void reportSleepComplete(CC_MqttsnAsyncOpStatus status); - void reportReconnectComplete(CC_MqttsnAsyncOpStatus status); - void reportCheckMessagesComplete(CC_MqttsnAsyncOpStatus status); - - static void nextTickProgramCallback(void* data, unsigned duration); - static unsigned cancelNextTickCallback(void* data); - static void sendOutputDataCallback(void* data, const unsigned char* buf, unsigned bufLen, bool broadcast); - static void gwStatusReportCallback(void* data, unsigned char gwId, CC_MqttsnGwStatus status); - static void gwDisconnectReportCallback(void* data); - static void msgReportCallback(void* data, const CC_MqttsnMessageInfo* msgInfo); - static void connectCompleteCallback(void* data, CC_MqttsnAsyncOpStatus status); - static void disconnectCompleteCallback(void* data, CC_MqttsnAsyncOpStatus status); - static void publishCompleteCallback(void* data, CC_MqttsnAsyncOpStatus status); - static void subsribeCompleteCallback(void* data, CC_MqttsnAsyncOpStatus status, CC_MqttsnQoS qos); - static void unsubsribeCompleteCallback(void* data, CC_MqttsnAsyncOpStatus status); - static void willUpdateCompleteCallback(void* data, CC_MqttsnAsyncOpStatus status); - static void willTopicUpdateCompleteCallback(void* data, CC_MqttsnAsyncOpStatus status); - static void willMsgUpdateCompleteCallback(void* data, CC_MqttsnAsyncOpStatus status); - static void sleepCompleteCallback(void* data, CC_MqttsnAsyncOpStatus status); - static void reconnectCompleteCallback(void* data, CC_MqttsnAsyncOpStatus status); - static void checkMessagesCompleteCallback(void* data, CC_MqttsnAsyncOpStatus status); - - ClientLibFuncs m_libFuncs; - CC_MqttsnClientHandle m_client; - InputData m_inData; - - ProgramNextTickCallback m_programNextTickCallback; - CancelNextTickCallback m_cancelNextTickCallback; - SendDataCallback m_sendDataCallback; - GwStatusReportCallback m_gwStatusReportCallback; - GwDisconnectReportCallback m_gwDisconnectReportCallback; - MessageReportCallback m_msgReportCallback; - AsyncOpCompleteCallback m_connectCompleteCallback; - AsyncOpCompleteCallback m_disconnectCompleteCallback; - AsyncOpCompleteCallback m_publishCompleteCallback; - SubscribeCompleteCallback m_subscribeCompleteCallback; - AsyncOpCompleteCallback m_unsubscribeCompleteCallback; - AsyncOpCompleteCallback m_willUpdateCompleteCallback; - AsyncOpCompleteCallback m_willTopicUpdateCompleteCallback; - AsyncOpCompleteCallback m_willMsgUpdateCompleteCallback; - AsyncOpCompleteCallback m_sleepCompleteCallback; - AsyncOpCompleteCallback m_reconnectCompleteCallback; - AsyncOpCompleteCallback m_checkMessagesCompleteCallback; - - static const ClientLibFuncs DefaultFuncs; -}; diff --git a/client.old/test/DataProcessor.cpp b/client.old/test/DataProcessor.cpp deleted file mode 100644 index f2718769..00000000 --- a/client.old/test/DataProcessor.cpp +++ /dev/null @@ -1,502 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include "DataProcessor.h" - -#include - -DataProcessor::~DataProcessor() = default; - -DataProcessor::SearchgwMsgReportCallback DataProcessor::setSearchgwMsgReportCallback(SearchgwMsgReportCallback&& func) -{ - SearchgwMsgReportCallback old(std::move(m_searchgwMsgReportCallback)); - m_searchgwMsgReportCallback = std::move(func); - return old; -} - -DataProcessor::ConnectMsgReportCallback DataProcessor::setConnectMsgReportCallback(ConnectMsgReportCallback&& func) -{ - ConnectMsgReportCallback old(std::move(m_connectMsgReportCallback)); - m_connectMsgReportCallback = std::move(func); - return old; -} - -DataProcessor::WilltopicMsgReportCallback DataProcessor::setWilltopicMsgReportCallback( - WilltopicMsgReportCallback&& func) -{ - WilltopicMsgReportCallback old = std::move(m_willtopicMsgReportCallback); - m_willtopicMsgReportCallback = std::move(func); - return old; -} - -DataProcessor::WillmsgMsgReportCallback DataProcessor::setWillmsgMsgReportCallback( - WillmsgMsgReportCallback&& func) -{ - WillmsgMsgReportCallback old = std::move(m_willmsgMsgReportCallback); - m_willmsgMsgReportCallback = std::move(func); - return old; -} - -DataProcessor::RegisterMsgReportCallback DataProcessor::setRegisterMsgReportCallback( - RegisterMsgReportCallback&& func) -{ - RegisterMsgReportCallback old = std::move(m_registerMsgReportCallback); - m_registerMsgReportCallback = std::move(func); - return old; -} - -DataProcessor::RegackMsgReportCallback DataProcessor::setRegackMsgReportCallback( - RegackMsgReportCallback&& func) -{ - RegackMsgReportCallback old = std::move(m_regackMsgReportCallback); - m_regackMsgReportCallback = std::move(func); - return old; -} - -DataProcessor::PublishMsgReportCallback DataProcessor::setPublishMsgReportCallback( - PublishMsgReportCallback&& func) -{ - PublishMsgReportCallback old = std::move(m_publishMsgReportCallback); - m_publishMsgReportCallback = std::move(func); - return old; -} - -DataProcessor::PubackMsgReportCallback DataProcessor::setPubackMsgReportCallback( - PubackMsgReportCallback&& func) -{ - PubackMsgReportCallback old = std::move(m_pubackMsgReportCallback); - m_pubackMsgReportCallback = std::move(func); - return old; -} - -DataProcessor::PubrecMsgReportCallback DataProcessor::setPubrecMsgReportCallback( - PubrecMsgReportCallback&& func) -{ - PubrecMsgReportCallback old = std::move(m_pubrecMsgReportCallback); - m_pubrecMsgReportCallback = std::move(func); - return old; -} - -DataProcessor::PubrelMsgReportCallback DataProcessor::setPubrelMsgReportCallback( - PubrelMsgReportCallback&& func) -{ - PubrelMsgReportCallback old = std::move(m_pubrelMsgReportCallback); - m_pubrelMsgReportCallback = std::move(func); - return old; -} - -DataProcessor::PubcompMsgReportCallback DataProcessor::setPubcompMsgReportCallback( - PubcompMsgReportCallback&& func) -{ - PubcompMsgReportCallback old = std::move(m_pubcompMsgReportCallback); - m_pubcompMsgReportCallback = std::move(func); - return old; -} - -DataProcessor::SubscribeMsgReportCallback DataProcessor::setSubscribeMsgReportCallback( - SubscribeMsgReportCallback&& func) -{ - SubscribeMsgReportCallback old = std::move(m_subscribeMsgReportCallback); - m_subscribeMsgReportCallback = std::move(func); - return old; -} - -DataProcessor::UnsubscribeMsgReportCallback DataProcessor::setUnsubscribeMsgReportCallback( - UnsubscribeMsgReportCallback&& func) -{ - UnsubscribeMsgReportCallback old = std::move(m_unsubscribeMsgReportCallback); - m_unsubscribeMsgReportCallback = std::move(func); - return old; -} - -DataProcessor::PingreqMsgReportCallback DataProcessor::setPingreqMsgReportCallback( - PingreqMsgReportCallback&& func) -{ - PingreqMsgReportCallback old = std::move(m_pingreqMsgReportCallback); - m_pingreqMsgReportCallback = std::move(func); - return old; -} - -DataProcessor::PingrespMsgReportCallback DataProcessor::setPingrespMsgReportCallback( - PingrespMsgReportCallback&& func) -{ - PingrespMsgReportCallback old = std::move(m_pingrespMsgReportCallback); - m_pingrespMsgReportCallback = std::move(func); - return old; -} - -DataProcessor::DisconnectMsgReportCallback DataProcessor::setDisconnectMsgReportCallback( - DisconnectMsgReportCallback&& func) -{ - DisconnectMsgReportCallback old = std::move(m_disconnectMsgReportCallback); - m_disconnectMsgReportCallback = std::move(func); - return old; -} - -DataProcessor::WilltopicupdMsgReportCallback -DataProcessor::setWilltopicupdMsgReportCallback( - WilltopicupdMsgReportCallback&& func) -{ - WilltopicupdMsgReportCallback old = std::move(m_willtopicupdMsgReportCallback); - m_willtopicupdMsgReportCallback = std::move(func); - return old; -} - -DataProcessor::WillmsgupdMsgReportCallback -DataProcessor::setWillmsgupdMsgReportCallback( - WillmsgupdMsgReportCallback&& func) -{ - WillmsgupdMsgReportCallback old = std::move(m_willmsgupdMsgReportCallback); - m_willmsgupdMsgReportCallback = std::move(func); - return old; -} - -void DataProcessor::handle(SearchgwMsg& msg) -{ - if (m_searchgwMsgReportCallback) { - m_searchgwMsgReportCallback(msg); - } -} - -void DataProcessor::handle(ConnectMsg& msg) -{ - if (m_connectMsgReportCallback) { - m_connectMsgReportCallback(msg); - } -} - -void DataProcessor::handle(WilltopicMsg& msg) -{ - if (m_willtopicMsgReportCallback) { - m_willtopicMsgReportCallback(msg); - } -} - -void DataProcessor::handle(WillmsgMsg& msg) -{ - if (m_willmsgMsgReportCallback) { - m_willmsgMsgReportCallback(msg); - } -} - -void DataProcessor::handle(RegisterMsg& msg) -{ - if (m_registerMsgReportCallback) { - m_registerMsgReportCallback(msg); - } -} - -void DataProcessor::handle(RegackMsg& msg) -{ - if (m_regackMsgReportCallback) { - m_regackMsgReportCallback(msg); - } -} - -void DataProcessor::handle(PublishMsg& msg) -{ - if (m_publishMsgReportCallback) { - m_publishMsgReportCallback(msg); - } -} - -void DataProcessor::handle(PubackMsg& msg) -{ - if (m_pubackMsgReportCallback) { - m_pubackMsgReportCallback(msg); - } -} - -void DataProcessor::handle(PubrecMsg& msg) -{ - if (m_pubrecMsgReportCallback) { - m_pubrecMsgReportCallback(msg); - } -} - -void DataProcessor::handle(PubrelMsg& msg) -{ - if (m_pubrelMsgReportCallback) { - m_pubrelMsgReportCallback(msg); - } -} - -void DataProcessor::handle(PubcompMsg& msg) -{ - if (m_pubcompMsgReportCallback) { - m_pubcompMsgReportCallback(msg); - } -} - -void DataProcessor::handle(SubscribeMsg& msg) -{ - if (m_subscribeMsgReportCallback) { - m_subscribeMsgReportCallback(msg); - } -} - -void DataProcessor::handle(UnsubscribeMsg& msg) -{ - if (m_unsubscribeMsgReportCallback) { - m_unsubscribeMsgReportCallback(msg); - } -} - -void DataProcessor::handle(PingreqMsg& msg) -{ - if (m_pingreqMsgReportCallback) { - m_pingreqMsgReportCallback(msg); - } -} - -void DataProcessor::handle(PingrespMsg& msg) -{ - if (m_pingrespMsgReportCallback) { - m_pingrespMsgReportCallback(msg); - } -} - -void DataProcessor::handle(DisconnectMsg& msg) -{ - if (m_disconnectMsgReportCallback) { - m_disconnectMsgReportCallback(msg); - } -} - -void DataProcessor::handle(WilltopicupdMsg& msg) -{ - if (m_willtopicupdMsgReportCallback) { - m_willtopicupdMsgReportCallback(msg); - } -} - -void DataProcessor::handle(WillmsgupdMsg& msg) -{ - if (m_willmsgupdMsgReportCallback) { - m_willmsgupdMsgReportCallback(msg); - } -} - -void DataProcessor::handle(TestMessage& msg) -{ - std::cout << "ERROR: unhandled message of type: " << static_cast(msg.getId()) << std::endl; - constexpr bool Handling_function_not_provided = false; - static_cast(Handling_function_not_provided); - assert(Handling_function_not_provided); -} - -void DataProcessor::checkWrittenMsg(const std::uint8_t* buf, std::size_t len) -{ - ProtStack::MsgPtr msg; - auto readIter = comms::readIteratorFor(buf); - auto es = m_stack.read(msg, readIter, len); - static_cast(es); - if (es != comms::ErrorStatus::Success) { - std::cout << "es = " << static_cast(es) << std::endl; - std::cout << "Data: " << std::hex; - std::copy_n(buf, len, std::ostream_iterator(std::cout, " ")); - std::cout << std::dec << std::endl; - } - assert(es == comms::ErrorStatus::Success); - assert(msg); - msg->dispatch(*this); -} - -void DataProcessor::checkWrittenMsg(const DataBuf& data) -{ - checkWrittenMsg(&data[0], data.size()); -} - -DataProcessor::DataBuf DataProcessor::prepareInput(const TestMessage& msg) -{ - DataBuf buf(m_stack.length(msg)); - assert(buf.size() == m_stack.length(msg)); - auto writeIter = comms::writeIteratorFor(&buf[0]); - auto es = m_stack.write(msg, writeIter, buf.size()); - static_cast(es); - assert(es == comms::ErrorStatus::Success); - return buf; -} - -DataProcessor::DataBuf DataProcessor::prepareGwinfoMsg(std::uint8_t id) -{ - GwinfoMsg msg; - msg.field_gwId().value() = id; - assert(msg.length() == 1U); - auto buf = prepareInput(msg); - assert(buf.size() == 3U); - return buf; -} - -DataProcessor::DataBuf DataProcessor::prepareAdvertiseMsg(std::uint8_t id, unsigned short duration) -{ - AdvertiseMsg msg; - msg.field_gwId().value() = id; - msg.field_duration().value() = duration; - auto buf = prepareInput(msg); - assert(buf.size() == 5U); - return buf; -} - -DataProcessor::DataBuf DataProcessor::prepareConnackMsg(cc_mqttsn::field::ReturnCodeVal val) -{ - ConnackMsg msg; - msg.field_returnCode().value() = val; - auto buf = prepareInput(msg); - assert(buf.size() == 3U); - return buf; -} - -DataProcessor::DataBuf DataProcessor::prepareWilltopicreqMsg() -{ - return prepareInput(WilltopicreqMsg()); -} - -DataProcessor::DataBuf DataProcessor::prepareWillmsgreqMsg() -{ - return prepareInput(WillmsgreqMsg()); -} - -DataProcessor::DataBuf DataProcessor::prepareRegisterMsg( - std::uint16_t topicId, - std::uint16_t msgId, - const std::string& topicName) -{ - RegisterMsg msg; - msg.field_topicId().value() = topicId; - msg.field_msgId().value() = msgId; - msg.field_topicName().value() = topicName; - return prepareInput(msg); -} - -DataProcessor::DataBuf DataProcessor::prepareRegackMsg( - std::uint16_t topicId, - std::uint16_t msgId, - cc_mqttsn::field::ReturnCodeVal retCode) -{ - RegackMsg msg; - msg.field_topicId().value() = topicId; - msg.field_msgId().value() = msgId; - msg.field_returnCode().value() = retCode; - return prepareInput(msg); -} - -DataProcessor::DataBuf DataProcessor::preparePublishMsg( - std::uint16_t topicId, - std::uint16_t msgId, - const std::vector& data, - TopicIdTypeVal topicIdType, - cc_mqttsn::field::QosVal qos, - bool retain, - bool duplicate) -{ - PublishMsg msg; - msg.field_flags().field_topicIdType().value() = topicIdType; - msg.field_flags().field_mid().setBitValue_Retain(retain); - msg.field_flags().field_qos().value() = qos; - msg.field_flags().field_high().setBitValue_Dup(duplicate); - msg.field_topicId().value() = topicId; - msg.field_msgId().value() = msgId; - msg.field_data().value().assign(data.begin(), data.end()); - return prepareInput(msg); -} - -DataProcessor::DataBuf DataProcessor::preparePubackMsg( - CC_MqttsnTopicId topicId, - std::uint16_t msgId, - cc_mqttsn::field::ReturnCodeVal retCode) -{ - PubackMsg msg; - msg.field_topicId().value() = topicId; - msg.field_msgId().value() = msgId; - msg.field_returnCode().value() = retCode; - return prepareInput(msg); -} - -DataProcessor::DataBuf DataProcessor::preparePubrecMsg(std::uint16_t msgId) -{ - PubrecMsg msg; - msg.field_msgId().value() = msgId; - return prepareInput(msg); -} - -DataProcessor::DataBuf DataProcessor::preparePubrelMsg(std::uint16_t msgId) -{ - PubrelMsg msg; - msg.field_msgId().value() = msgId; - return prepareInput(msg); -} - -DataProcessor::DataBuf DataProcessor::preparePubcompMsg(std::uint16_t msgId) -{ - PubcompMsg msg; - msg.field_msgId().value() = msgId; - return prepareInput(msg); -} - -DataProcessor::DataBuf DataProcessor::prepareSubackMsg( - cc_mqttsn::field::QosVal qos, - CC_MqttsnTopicId topicId, - std::uint16_t msgId, - cc_mqttsn::field::ReturnCodeVal retCode) -{ - SubackMsg msg; - msg.field_flags().field_qos().value() = qos; - msg.field_topicId().value() = topicId; - msg.field_msgId().value() = msgId; - msg.field_returnCode().value() = retCode; - return prepareInput(msg); -} - -DataProcessor::DataBuf DataProcessor::prepareUnsubackMsg( - std::uint16_t msgId) -{ - UnsubackMsg msg; - msg.field_msgId().value() = msgId; - return prepareInput(msg); -} - -DataProcessor::DataBuf DataProcessor::preparePingreqMsg() -{ - return prepareInput(PingreqMsg()); -} - -DataProcessor::DataBuf DataProcessor::preparePingrespMsg() -{ - return prepareInput(PingrespMsg()); -} - -DataProcessor::DataBuf DataProcessor::prepareDisconnectMsg(std::uint16_t duration) -{ - DisconnectMsg msg; - - if (duration != 0U) { - msg.field_duration().setExists(); - msg.field_duration().field().value() = duration; - } - else { - msg.field_duration().setMissing(); - } - return prepareInput(msg); -} - -DataProcessor::DataBuf DataProcessor::prepareWilltopicrespMsg( - cc_mqttsn::field::ReturnCodeVal retCode) -{ - WilltopicrespMsg msg; - msg.field_returnCode().value() = retCode; - return prepareInput(msg); -} - -DataProcessor::DataBuf DataProcessor::prepareWillmsgrespMsg( - cc_mqttsn::field::ReturnCodeVal retCode) -{ - WillmsgrespMsg msg; - msg.field_returnCode().value() = retCode; - return prepareInput(msg); -} - diff --git a/client.old/test/DataProcessor.h b/client.old/test/DataProcessor.h deleted file mode 100644 index 135dcd55..00000000 --- a/client.old/test/DataProcessor.h +++ /dev/null @@ -1,189 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#pragma once - -#include -#include -#include -#include - -#include "comms/comms.h" -#include "cc_mqttsn/frame/Frame.h" -#include "cc_mqttsn/input/ProtMessages.h" -#include "cc_mqttsn/Message.h" -#include "cc_mqttsn_client/common.h" - -class DataProcessor; - -typedef cc_mqttsn::Message< - comms::option::IdInfoInterface, - comms::option::ReadIterator, - comms::option::WriteIterator, - comms::option::Handler, - comms::option::LengthInfoInterface -> TestMessage; - -typedef cc_mqttsn::input::ProtMessages AllTestMessages; - -class DataProcessor : public comms::GenericHandler -{ - typedef comms::GenericHandler Base; -public: - typedef std::vector DataBuf; - using TopicIdTypeVal = cc_mqttsn::field::TopicIdTypeVal; - - CC_MQTTSN_ALIASES_FOR_PROT_MESSAGES_DEFAULT_OPTIONS(, Msg, TestMessage); - - - virtual ~DataProcessor(); - - typedef std::function SearchgwMsgReportCallback; - SearchgwMsgReportCallback setSearchgwMsgReportCallback(SearchgwMsgReportCallback&& func); - - typedef std::function ConnectMsgReportCallback; - ConnectMsgReportCallback setConnectMsgReportCallback(ConnectMsgReportCallback&& func); - - typedef std::function WilltopicMsgReportCallback; - WilltopicMsgReportCallback setWilltopicMsgReportCallback(WilltopicMsgReportCallback&& func); - - typedef std::function WillmsgMsgReportCallback; - WillmsgMsgReportCallback setWillmsgMsgReportCallback(WillmsgMsgReportCallback&& func); - - typedef std::function RegisterMsgReportCallback; - RegisterMsgReportCallback setRegisterMsgReportCallback(RegisterMsgReportCallback&& func); - - typedef std::function RegackMsgReportCallback; - RegackMsgReportCallback setRegackMsgReportCallback(RegackMsgReportCallback&& func); - - typedef std::function PublishMsgReportCallback; - PublishMsgReportCallback setPublishMsgReportCallback(PublishMsgReportCallback&& func); - - typedef std::function PubackMsgReportCallback; - PubackMsgReportCallback setPubackMsgReportCallback(PubackMsgReportCallback&& func); - - typedef std::function PubrecMsgReportCallback; - PubrecMsgReportCallback setPubrecMsgReportCallback(PubrecMsgReportCallback&& func); - - typedef std::function PubrelMsgReportCallback; - PubrelMsgReportCallback setPubrelMsgReportCallback(PubrelMsgReportCallback&& func); - - typedef std::function PubcompMsgReportCallback; - PubcompMsgReportCallback setPubcompMsgReportCallback(PubcompMsgReportCallback&& func); - - typedef std::function SubscribeMsgReportCallback; - SubscribeMsgReportCallback setSubscribeMsgReportCallback(SubscribeMsgReportCallback&& func); - - typedef std::function UnsubscribeMsgReportCallback; - UnsubscribeMsgReportCallback setUnsubscribeMsgReportCallback(UnsubscribeMsgReportCallback&& func); - - typedef std::function PingreqMsgReportCallback; - PingreqMsgReportCallback setPingreqMsgReportCallback(PingreqMsgReportCallback&& func); - - typedef std::function PingrespMsgReportCallback; - PingrespMsgReportCallback setPingrespMsgReportCallback(PingrespMsgReportCallback&& func); - - typedef std::function DisconnectMsgReportCallback; - DisconnectMsgReportCallback setDisconnectMsgReportCallback(DisconnectMsgReportCallback&& func); - - typedef std::function WilltopicupdMsgReportCallback; - WilltopicupdMsgReportCallback setWilltopicupdMsgReportCallback(WilltopicupdMsgReportCallback&& func); - - typedef std::function WillmsgupdMsgReportCallback; - WillmsgupdMsgReportCallback setWillmsgupdMsgReportCallback(WillmsgupdMsgReportCallback&& func); - - using Base::handle; - virtual void handle(SearchgwMsg& msg) override; - virtual void handle(ConnectMsg& msg) override; - virtual void handle(WilltopicMsg& msg) override; - virtual void handle(WillmsgMsg& msg) override; - virtual void handle(RegisterMsg& msg) override; - virtual void handle(RegackMsg& msg) override; - virtual void handle(PublishMsg& msg) override; - virtual void handle(PubackMsg& msg) override; - virtual void handle(PubrecMsg& msg) override; - virtual void handle(PubrelMsg& msg) override; - virtual void handle(PubcompMsg& msg) override; - virtual void handle(SubscribeMsg& msg) override; - virtual void handle(UnsubscribeMsg& msg) override; - virtual void handle(PingreqMsg& msg) override; - virtual void handle(PingrespMsg& msg) override; - virtual void handle(DisconnectMsg& msg) override; - virtual void handle(WilltopicupdMsg& msg) override; - virtual void handle(WillmsgupdMsg& msg) override; - virtual void handle(TestMessage& msg) override; - - - void checkWrittenMsg(const std::uint8_t* buf, std::size_t len); - void checkWrittenMsg(const DataBuf& data); - DataBuf prepareInput(const TestMessage& msg); - - DataBuf prepareGwinfoMsg(std::uint8_t id); - DataBuf prepareAdvertiseMsg(std::uint8_t id, unsigned short duration); - DataBuf prepareConnackMsg(cc_mqttsn::field::ReturnCodeVal val); - DataBuf prepareWilltopicreqMsg(); - DataBuf prepareWillmsgreqMsg(); - DataBuf prepareRegisterMsg( - std::uint16_t topicId, - std::uint16_t msgId, - const std::string& topicName); - DataBuf prepareRegackMsg( - std::uint16_t topicId, - std::uint16_t msgId, - cc_mqttsn::field::ReturnCodeVal retCode); - DataBuf preparePublishMsg( - std::uint16_t topicId, - std::uint16_t msgId, - const std::vector& data, - TopicIdTypeVal topicIdType, - cc_mqttsn::field::QosVal qos, - bool retain, - bool duplicate); - DataBuf preparePubackMsg( - CC_MqttsnTopicId topicId, - std::uint16_t msgId, - cc_mqttsn::field::ReturnCodeVal retCode); - DataBuf preparePubrecMsg(std::uint16_t msgId); - DataBuf preparePubrelMsg(std::uint16_t msgId); - DataBuf preparePubcompMsg(std::uint16_t msgId); - DataBuf prepareSubackMsg( - cc_mqttsn::field::QosVal qos, - CC_MqttsnTopicId topicId, - std::uint16_t msgId, - cc_mqttsn::field::ReturnCodeVal retCode); - DataBuf prepareUnsubackMsg(std::uint16_t msgId); - DataBuf preparePingreqMsg(); - DataBuf preparePingrespMsg(); - DataBuf prepareDisconnectMsg(std::uint16_t duration = 0); - DataBuf prepareWilltopicrespMsg(cc_mqttsn::field::ReturnCodeVal retCode); - DataBuf prepareWillmsgrespMsg(cc_mqttsn::field::ReturnCodeVal retCode); - -private: - typedef cc_mqttsn::frame::Frame ProtStack; - - ProtStack m_stack; - SearchgwMsgReportCallback m_searchgwMsgReportCallback; - ConnectMsgReportCallback m_connectMsgReportCallback; - WilltopicMsgReportCallback m_willtopicMsgReportCallback; - WillmsgMsgReportCallback m_willmsgMsgReportCallback; - RegisterMsgReportCallback m_registerMsgReportCallback; - RegackMsgReportCallback m_regackMsgReportCallback; - PublishMsgReportCallback m_publishMsgReportCallback; - PubackMsgReportCallback m_pubackMsgReportCallback; - PubrecMsgReportCallback m_pubrecMsgReportCallback; - PubrelMsgReportCallback m_pubrelMsgReportCallback; - PubcompMsgReportCallback m_pubcompMsgReportCallback; - SubscribeMsgReportCallback m_subscribeMsgReportCallback; - UnsubscribeMsgReportCallback m_unsubscribeMsgReportCallback; - PingreqMsgReportCallback m_pingreqMsgReportCallback; - PingrespMsgReportCallback m_pingrespMsgReportCallback; - DisconnectMsgReportCallback m_disconnectMsgReportCallback; - WilltopicupdMsgReportCallback m_willtopicupdMsgReportCallback; - WillmsgupdMsgReportCallback m_willmsgupdMsgReportCallback; -}; - - diff --git a/client.old/test/bare_metal_app/CMakeLists.txt b/client.old/test/bare_metal_app/CMakeLists.txt deleted file mode 100644 index 1c074f93..00000000 --- a/client.old/test/bare_metal_app/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -if ((NOT CMAKE_CROSSCOMPILING) OR (NOT CC_MQTTSN_BARE_METAL_IMAGE_TEST)) - return () -endif () - - -set (STARTUP_LIB_NAME "startup") -set (LINKER_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/raspberrypi.ld") - -function (link_app tgt) - - set_target_properties (${tgt} - PROPERTIES LINK_FLAGS "-Wl,-Map,kernel.map -T ${LINKER_SCRIPT}") - - add_custom_command ( - TARGET ${tgt} - POST_BUILD - COMMAND - ${CMAKE_OBJCOPY} $ -O binary - $/${tgt}.img - COMMAND - ${CMAKE_OBJDUMP} -C -D -S $ > - $/${tgt}.list -# COMMAND -# ${CMAKE_OBJDUMP} -t $ > -# $/kernel.syms -# COMMAND -# ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/image/${tgt} -# COMMAND -# ${CMAKE_COMMAND} -E copy $/kernel.img ${CMAKE_BINARY_DIR}/image/${tgt} - ) - -endfunction () - -add_subdirectory (startup) -add_subdirectory (test_client_build) - - diff --git a/client.old/test/bare_metal_app/raspberrypi.ld b/client.old/test/bare_metal_app/raspberrypi.ld deleted file mode 100644 index bf2273a2..00000000 --- a/client.old/test/bare_metal_app/raspberrypi.ld +++ /dev/null @@ -1,61 +0,0 @@ -MEMORY -{ - RESERVED (r) : ORIGIN = 0x00000000, LENGTH = 32K - RAM (rwx) : ORIGIN = 0x00008000, LENGTH = 64M -} - -ENTRY(_entry) - -SECTIONS { - .start : { - KEEP(*(.init)) - KEEP(*(.fini)) - } > RAM = 0 - - /** - * This is the main code section. - * - **/ - .text : { - *(.text) - } > RAM - - .data : { - *(.data) - } > RAM - - .bss : - { - __bss_start__ = .; - *(.bss) - *(.bss.*) - __bss_end__ = .; - } > RAM - - .init.array : - { - __init_array_start = .; - *(.init_array) - *(.init_array.*) - __init_array_end = .; - } > RAM - - /* .ARM.exidx is required for exception handling. It is required only - for "test_cpp" applications that link in stdlib */ - .ARM.exidx : - { - __exidx_start = .; - *(.ARM.exidx* .gnu.linkonce.armexidx.*) - __exidx_end = .; - } >RAM - - /** - * Currently no heap - **/ - - /** - * Stack starts at the top of the RAM, and moves down! - **/ - __stack = ORIGIN(RAM) + LENGTH(RAM); -} - diff --git a/client.old/test/bare_metal_app/startup/CMakeLists.txt b/client.old/test/bare_metal_app/startup/CMakeLists.txt deleted file mode 100644 index b605f785..00000000 --- a/client.old/test/bare_metal_app/startup/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ - -function (lib_startup) - set (name "${STARTUP_LIB_NAME}") - - set (src - "${CMAKE_CURRENT_SOURCE_DIR}/startup.s") - - - add_library(${name} STATIC ${src}) - -endfunction () - -################################################################# - -# Make sure there are not "-D or -O" options -set (CMAKE_BUILD_TYPE "Debug") -set (CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} -march=armv6z") -enable_language (ASM) - -lib_startup () diff --git a/client.old/test/bare_metal_app/startup/startup.s b/client.old/test/bare_metal_app/startup/startup.s deleted file mode 100644 index 6ac19800..00000000 --- a/client.old/test/bare_metal_app/startup/startup.s +++ /dev/null @@ -1,110 +0,0 @@ -;@ -;@ Copyright 2013 - 2024 (C). Alex Robenko. All rights reserved. -;@ -;@ This Source Code Form is subject to the terms of the Mozilla Public -;@ License, v. 2.0. If a copy of the MPL was not distributed with this -;@ file, You can obtain one at http://mozilla.org/MPL/2.0/. - -.extern __stack; -.extern __bss_start__ -.extern __bss_end__ -.extern __init_array_start -.extern __init_array_end -.extern main -.extern interruptHandler - - .section .init - .globl _entry - -_entry: - - ldr pc,reset_handler_ptr ;@ Processor Reset handler - ldr pc,undefined_handler_ptr ;@ Undefined instruction handler - ldr pc,swi_handler_ptr ;@ Software interrupt - ldr pc,prefetch_handler_ptr ;@ Prefetch/abort handler. - ldr pc,data_handler_ptr ;@ Data abort handler/ - ldr pc,unused_handler_ptr ;@ - ldr pc,irq_handler_ptr ;@ IRQ handler - ldr pc,fiq_handler_ptr ;@ Fast interrupt handler. - - ;@ Set the branch addresses -reset_handler_ptr: .word reset -undefined_handler_ptr: .word hang -swi_handler_ptr: .word hang -prefetch_handler_ptr: .word hang -data_handler_ptr: .word hang -unused_handler_ptr: .word hang -irq_handler_ptr: .word irq_handler -fiq_handler_ptr: .word hang - -reset: - ;@ Disable interrupts - cpsid if - - ;@ Copy interrupt vector to its place - ldr r0,=_entry - mov r1,#0x0000 - - ;@ Here we copy the branching instructions - ldmia r0!,{r2,r3,r4,r5,r6,r7,r8,r9} - stmia r1!,{r2,r3,r4,r5,r6,r7,r8,r9} - - ;@ Here we copy the branching addresses - ldmia r0!,{r2,r3,r4,r5,r6,r7,r8,r9} - stmia r1!,{r2,r3,r4,r5,r6,r7,r8,r9} - - ;@ Set interrupt stacks - ldr r0,=__stack; - mov r1,#0x1000 ;@ interrupt stacks have 4K size - - ;@ FIQ mode - cps 0x11 - mov sp,r0 - sub r0,r0,r1 - - ;@ IRQ mode - cps 0x12 - mov sp,r0 - sub r0,r0,r1 - - ;@ Supervisor mode with disabled interrupts - cpsid if,0x13 - mov sp,r0 - - ;@ Zero bss section - ldr r0, =__bss_start__ - ldr r1, =__bss_end__ - mov r2, #0 - -bss_zero_loop: - cmp r0,r1 - it lt - strlt r2,[r0], #4 - blt bss_zero_loop - - ;@ Call constructors of all global objects - ldr r0, =__init_array_start - ldr r1, =__init_array_end - -globals_init_loop: - cmp r0,r1 - it lt - ldrlt r2, [r0], #4 - blxlt r2 - blt globals_init_loop - - ;@ Main function - bl main - b reset ;@ restart if main function returns - - .section .text - -hang: - b hang - -irq_handler: - push {r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,lr} - bl interruptHandler - pop {r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,lr} - subs pc,lr,#4 - diff --git a/client.old/test/bare_metal_app/test_client_build/CMakeLists.txt b/client.old/test/bare_metal_app/test_client_build/CMakeLists.txt deleted file mode 100644 index 90d224b5..00000000 --- a/client.old/test/bare_metal_app/test_client_build/CMakeLists.txt +++ /dev/null @@ -1,39 +0,0 @@ -set (BARE_METAL_CLIENT_OPTS "cc_mqttsn_client::option::ClientIdStaticStorageSize<21>,cc_mqttsn_client::option::GwAddStaticStorageSize<4>,cc_mqttsn_client::option::TopicNameStaticStorageSize<128>,cc_mqttsn_client::option::MessageDataStaticStorageSize<256>,cc_mqttsn_client::option::ClientsAllocLimit<1>,cc_mqttsn_client::option::TrackedGatewaysLimit<1>,cc_mqttsn_client::option::RegisteredTopicsLimit<5>") - -set (TEST_BARE_METAL_CLIENT_NAME "test_bare_metal") -set (MQTTSN_BARE_METAL_CLIENT_LIB_NAME "cc_mqttsn_${TEST_BARE_METAL_CLIENT_NAME}_client") - -gen_lib_mqttsn_client("${TEST_BARE_METAL_CLIENT_NAME}" "${BARE_METAL_CLIENT_OPTS}" FALSE "-nostdlib") -target_compile_definitions("${MQTTSN_BARE_METAL_CLIENT_LIB_NAME}" PRIVATE "-DNOSTDLIB") - -include_directories ( - ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_BINARY_DIR}/client/src/basic - ${CMAKE_SOURCE_DIR}/client/src/basic) - -################################################################# - -function (bin_client_build) - set (name "test_client_build") - - set (src - "${CMAKE_CURRENT_SOURCE_DIR}/main.c" - "${CMAKE_CURRENT_SOURCE_DIR}/stub.cpp") - - set (link - ${MQTTSN_BARE_METAL_CLIENT_LIB_NAME} - ${STARTUP_LIB_NAME}) - - add_executable(${name} ${src}) - target_link_libraries (${name} ${link}) - target_compile_definitions("${name}" PRIVATE "-DNOSTDLIB") - link_app (${name}) -endfunction () - -################################################################# - -if (CMAKE_COMPILER_IS_GNUCC) - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -nostdlib --std=c++11") -endif () - -bin_client_build() diff --git a/client.old/test/bare_metal_app/test_client_build/main.c b/client.old/test/bare_metal_app/test_client_build/main.c deleted file mode 100644 index e72fcee7..00000000 --- a/client.old/test/bare_metal_app/test_client_build/main.c +++ /dev/null @@ -1,171 +0,0 @@ -// -// Copyright 2014 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -// This function is required by common startup code -#include -#include - -#include "test_bare_metal_client.h" - -void interruptHandler() -{ -} - -void programNextTick(void* data, unsigned ms) -{ -} - -unsigned cancelTick(void* data) -{ - return 0U; -} - -void connectComplete(void* data, CC_MqttsnAsyncOpStatus status) -{ -} - -void disconnectComplete(void* data, CC_MqttsnAsyncOpStatus status) -{ -} - -void gwDisconnected(void* data) -{ -} - -void sendOutputData(void* data, const unsigned char* buf, unsigned bufSize, bool broadcast) -{ -} - -void publishCallback(void* data, CC_MqttsnAsyncOpStatus status) -{ -} - -void subscribeCallback(void* data, CC_MqttsnAsyncOpStatus status, CC_MqttsnQoS qos) -{ -} - -void unsubscribeCallback(void* data, CC_MqttsnAsyncOpStatus status) -{ -} - -void gwStatusReport(void* data, unsigned char gwId, CC_MqttsnGwStatus status) -{ -} - -void msgReport(void* data, const CC_MqttsnMessageInfo* msgInfo) -{ -} - -void willUpdated(void* data, CC_MqttsnAsyncOpStatus status) -{ -} - -int main(int argc, const char** argv) -{ - static const unsigned char Seq[] = {0x00, 0xf0}; - static const unsigned SeqSize = sizeof(Seq)/sizeof(Seq[0]); - static const char* Topic = "/this/is/topic"; - static const unsigned char Data[] = { - 0x00, 0x01, 0x02, 0x03 - }; - static const unsigned DataSize = sizeof(Data)/sizeof(Data[0]); - const unsigned char* from = &Seq[0]; - CC_MqttsnClientHandle client = cc_mqttsn_test_bare_metal_client_new(); - CC_MqttsnWillInfo willInfo; - - - cc_mqttsn_test_bare_metal_client_set_next_tick_program_callback(client, &programNextTick, NULL); - cc_mqttsn_test_bare_metal_client_set_cancel_next_tick_wait_callback(client, &cancelTick, NULL); - cc_mqttsn_test_bare_metal_client_set_send_output_data_callback(client, &sendOutputData, NULL); - cc_mqttsn_test_bare_metal_client_set_gw_disconnect_report_callback(client, &gwDisconnected, NULL); - cc_mqttsn_test_bare_metal_client_set_gw_status_report_callback(client, &gwStatusReport, NULL); - cc_mqttsn_test_bare_metal_client_set_message_report_callback(client, &msgReport, NULL); - if (cc_mqttsn_test_bare_metal_client_start(client) == 0) { - return -1; - } - - cc_mqttsn_test_bare_metal_client_process_data(client, from, SeqSize); - cc_mqttsn_test_bare_metal_client_tick(client); - cc_mqttsn_test_bare_metal_client_connect(client, "my_id", 60, true, NULL, &connectComplete, NULL); - cc_mqttsn_test_bare_metal_client_publish( - client, - Topic, - &Data[0], - DataSize, - CC_MqttsnQoS_ExactlyOnceDelivery, - false, - &publishCallback, - NULL); - - cc_mqttsn_test_bare_metal_client_publish_id( - client, - 0x1234, - &Data[0], - DataSize, - CC_MqttsnQoS_ExactlyOnceDelivery, - false, - &publishCallback, - NULL); - - cc_mqttsn_test_bare_metal_client_subscribe( - client, - Topic, - CC_MqttsnQoS_ExactlyOnceDelivery, - &subscribeCallback, - NULL); - - cc_mqttsn_test_bare_metal_client_subscribe_id( - client, - 0x1111, - CC_MqttsnQoS_ExactlyOnceDelivery, - &subscribeCallback, - NULL); - - cc_mqttsn_test_bare_metal_client_cancel(client); - - cc_mqttsn_test_bare_metal_client_unsubscribe( - client, - Topic, - &unsubscribeCallback, - NULL); - - cc_mqttsn_test_bare_metal_client_unsubscribe_id( - client, - 0x1111, - &unsubscribeCallback, - NULL); - - cc_mqttsn_test_bare_metal_client_sleep( - client, - 10000, - &unsubscribeCallback, - NULL); - - cc_mqttsn_test_bare_metal_client_check_messages( - client, - &unsubscribeCallback, - NULL); - - cc_mqttsn_test_bare_metal_client_set_retry_period(client, 10); - cc_mqttsn_test_bare_metal_client_set_retry_count(client, 3); - cc_mqttsn_test_bare_metal_client_set_default_broadcast_radius(client, 0); - cc_mqttsn_test_bare_metal_client_set_searchgw_enabled(client, true); - cc_mqttsn_test_bare_metal_client_search_gw(client); - cc_mqttsn_test_bare_metal_client_discard_gw(client, 0); - cc_mqttsn_test_bare_metal_client_discard_all_gw(client); - cc_mqttsn_test_bare_metal_client_reconnect(client, &connectComplete, NULL); - - cc_mqttsn_test_bare_metal_client_will_update(client, &willInfo, &willUpdated, NULL); - cc_mqttsn_test_bare_metal_client_will_topic_update(client, Topic, CC_MqttsnQoS_ExactlyOnceDelivery, false, &willUpdated, NULL); - cc_mqttsn_test_bare_metal_client_will_msg_update(client, Data, DataSize, &willUpdated, NULL); - - cc_mqttsn_test_bare_metal_client_disconnect(client, &disconnectComplete, NULL); - cc_mqttsn_test_bare_metal_client_stop(client); - cc_mqttsn_test_bare_metal_client_free(client); - while (true) {}; - return 0; -} diff --git a/client.old/test/bare_metal_app/test_client_build/stub.cpp b/client.old/test/bare_metal_app/test_client_build/stub.cpp deleted file mode 100644 index 08ece01a..00000000 --- a/client.old/test/bare_metal_app/test_client_build/stub.cpp +++ /dev/null @@ -1,86 +0,0 @@ -// -// Copyright 2014 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include - -// This function may be required by _mainCRTSturtup when using a particular compiler -extern "C" -void exit() -{ - while (true) {} -} - -void operator delete(void *) -{ -} - -// Required when there is pure virtual function -extern "C" void __cxa_pure_virtual() -{ - while (true) {} -} - -extern "C" int __aeabi_atexit( - void *object, - void (*destructor)(void *), - void *dso_handle) -{ - static_cast(object); - static_cast(destructor); - static_cast(dso_handle); - return 0; -} - -// The pointer to this variable will be passed to __aeabi_atexit -void* __dso_handle = nullptr; - -extern "C" -void* memset(void* dest, int ch, std::size_t count) -{ - static_cast(dest); - static_cast(ch); - static_cast(count); - return nullptr; -} - -extern "C" -void* memmove( void* dest, const void* src, std::size_t count ) -{ - static_cast(dest); - static_cast(src); - static_cast(count); - return nullptr; -} - -extern "C" -void* memcpy( void* dest, const void* src, std::size_t count ) -{ - static_cast(dest); - static_cast(src); - static_cast(count); - return nullptr; -} - -extern "C" -int memcmp(const void*, const void*, std::size_t) -{ - return 0; -} - -extern "C" -std::size_t strlen(const char*) -{ - return 0U; -} - -extern "C" -void __aeabi_uidiv() -{ -} - - - From 03c84ba01177b11c4ddb962a457f766792439411 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 13 Aug 2024 08:10:59 +1000 Subject: [PATCH 096/106] Removed old gateway implementation. --- gateway/app/CMakeLists.txt | 1 - gateway/app/udp/CMakeLists.txt | 43 ---- gateway/app/udp/GatewayWrapper.cpp | 56 ----- gateway/app/udp/GatewayWrapper.h | 49 ---- gateway/app/udp/Mgr.cpp | 248 -------------------- gateway/app/udp/Mgr.h | 67 ------ gateway/app/udp/SessionWrapper.cpp | 351 ----------------------------- gateway/app/udp/SessionWrapper.h | 119 ---------- gateway/app/udp/main.cpp | 74 ------ 9 files changed, 1008 deletions(-) delete mode 100644 gateway/app/udp/CMakeLists.txt delete mode 100644 gateway/app/udp/GatewayWrapper.cpp delete mode 100644 gateway/app/udp/GatewayWrapper.h delete mode 100644 gateway/app/udp/Mgr.cpp delete mode 100644 gateway/app/udp/Mgr.h delete mode 100644 gateway/app/udp/SessionWrapper.cpp delete mode 100644 gateway/app/udp/SessionWrapper.h delete mode 100644 gateway/app/udp/main.cpp diff --git a/gateway/app/CMakeLists.txt b/gateway/app/CMakeLists.txt index 0c97658b..83c8aaf0 100644 --- a/gateway/app/CMakeLists.txt +++ b/gateway/app/CMakeLists.txt @@ -2,5 +2,4 @@ if (NOT CC_MQTTSN_GATEWAY_APPS) return () endif () -#add_subdirectory (udp) add_subdirectory (gateway) \ No newline at end of file diff --git a/gateway/app/udp/CMakeLists.txt b/gateway/app/udp/CMakeLists.txt deleted file mode 100644 index bb195c6a..00000000 --- a/gateway/app/udp/CMakeLists.txt +++ /dev/null @@ -1,43 +0,0 @@ -function (bin_gateway_udp) - set (name "cc_mqttsn_gateway_udp") - - if (NOT Qt5Core_FOUND) - message(WARNING "Can NOT build ${name} due to missing Qt5Core library") - return() - endif () - - if (NOT Qt5Network_FOUND) - message(WARNING "Can NOT build ${name} due to missing Qt5Network library") - return() - endif () - - - set (src - main.cpp - Mgr.cpp - GatewayWrapper.cpp - SessionWrapper.cpp - ) - - qt5_wrap_cpp( - moc - Mgr.h - GatewayWrapper.h - SessionWrapper.h - ) - - add_executable(${name} ${src} ${moc}) - target_link_libraries(${name} cc::${MQTTSN_GATEWAY_LIB_NAME} Qt5::Network Qt5::Core) - - install ( - TARGETS ${name} - DESTINATION ${CMAKE_INSTALL_BINDIR}) - -endfunction () - -########################################################### - -find_package(Qt5Core) -find_package(Qt5Network) - -bin_gateway_udp() diff --git a/gateway/app/udp/GatewayWrapper.cpp b/gateway/app/udp/GatewayWrapper.cpp deleted file mode 100644 index de8d1c99..00000000 --- a/gateway/app/udp/GatewayWrapper.cpp +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include "GatewayWrapper.h" - -#include -#include - -namespace cc_mqttsn_gateway -{ - -namespace app -{ - -namespace udp -{ - -GatewayWrapper::GatewayWrapper(const Config& config) - : m_config(config) -{ - connect( - &m_timer, SIGNAL(timeout()), - this, SLOT(tickTimeout())); -} - -bool GatewayWrapper::start(SendDataReqCb&& sendCb) -{ - m_gw.setAdvertisePeriod(m_config.advertisePeriod()); - m_gw.setGatewayId(m_config.gatewayId()); - m_gw.setNextTickProgramReqCb( - [this](unsigned ms) - { - m_timer.setSingleShot(true); - m_timer.start(ms); - }); - - m_gw.setSendDataReqCb(std::move(sendCb)); - return m_gw.start(); -} - -void GatewayWrapper::tickTimeout() -{ - m_gw.tick(); -} - -} // namespace udp - -} // namespace app - -} // namespace cc_mqttsn_gateway - - diff --git a/gateway/app/udp/GatewayWrapper.h b/gateway/app/udp/GatewayWrapper.h deleted file mode 100644 index cc0cf85b..00000000 --- a/gateway/app/udp/GatewayWrapper.h +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#pragma once - -#include -#include - -#include "cc_mqttsn_gateway/Config.h" -#include "cc_mqttsn_gateway/Gateway.h" - -namespace cc_mqttsn_gateway -{ - -namespace app -{ - -namespace udp -{ - -class GatewayWrapper : public QObject -{ - Q_OBJECT -public: - typedef unsigned short PortType; - - explicit GatewayWrapper(const Config& config); - - typedef cc_mqttsn_gateway::Gateway::SendDataReqCb SendDataReqCb; - bool start(SendDataReqCb&& sendCb); - -private slots: - void tickTimeout(); - -private: - const Config& m_config; - cc_mqttsn_gateway::Gateway m_gw; - QTimer m_timer; -}; - -} // namespace udp - -} // namespace app - -} // namespace cc_mqttsn_gateway diff --git a/gateway/app/udp/Mgr.cpp b/gateway/app/udp/Mgr.cpp deleted file mode 100644 index 1007dafb..00000000 --- a/gateway/app/udp/Mgr.cpp +++ /dev/null @@ -1,248 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include "Mgr.h" - -#include -#include -#include - -namespace cc_mqttsn_gateway -{ - -namespace app -{ - -namespace udp -{ - -namespace -{ - -const std::string UdpListenPortKey("udp_listen_port"); -const std::string UdpBroadcastPortKey("udp_broadcast_port"); -const std::string SpaceChars(" \t"); -const std::uint16_t DefaultListenPort = 1883; -const std::uint16_t DefaultBroadcastPort = 1883; - -std::uint16_t getPortInfo( - const Config& config, - const std::string& key, - std::uint16_t defaultValue) -{ - auto& map = config.configMap(); - auto iter = map.find(key); - if ((iter == map.end()) || - (iter->second.empty())) { - return defaultValue; - } - - try { - auto& valStr = iter->second; - auto spacePos = valStr.find_first_of(SpaceChars); - if (spacePos == std::string::npos) { - return static_cast(std::stoul(valStr)); - } - - - std::string portStr(valStr.begin(), valStr.begin() + spacePos); - return static_cast(std::stoul(portStr)); - } - catch (...) { - // nothing to do - } - - return defaultValue; -} - -} // namespace - -Mgr::Mgr(const Config& config) - : m_config(config), - m_gw(config) -{ - connect( - &m_socket, SIGNAL(readyRead()), - this, SLOT(readClientData())); - connect( - &m_socket, SIGNAL(error(QAbstractSocket::SocketError)), - this, SLOT(socketErrorOccurred(QAbstractSocket::SocketError))); - -} - -Mgr::~Mgr() -{ - m_socket.blockSignals(true); - m_socket.flush(); -} - -bool Mgr::start() -{ - m_port = getPortInfo(m_config, UdpListenPortKey, DefaultListenPort); - if (!doListen()) { - std::cerr << "ERROR: Failed to listen to incomming connections" << std::endl; - return false; - } - - if (m_config.advertisePeriod() == 0) { - return true; - } - - m_broadcastPort = getPortInfo(m_config, UdpBroadcastPortKey, DefaultBroadcastPort); - auto broadcastFunc = - [this](const std::uint8_t* buf, std::size_t bufSize) - { - broadcastAdvertise(buf, bufSize); - }; - return m_gw.start(std::move(broadcastFunc)); -} - -void Mgr::readClientData() -{ - typedef std::vector DataBuf; - - DataBuf data; - QHostAddress senderAddress; - quint16 senderPort; - - while (m_socket.hasPendingDatagrams()) { - data.resize(m_socket.pendingDatagramSize()); - [[maybe_unused]] auto readBytes = - m_socket.readDatagram( - reinterpret_cast(&data[0]), - data.size(), - &senderAddress, - &senderPort); - assert(readBytes == static_cast(data.size())); - - if (data == m_lastAdvertise) { - continue; - } - - auto addrStr = senderAddress.toString(); - auto url = QString("%1:%2").arg(addrStr).arg(senderPort); - auto iter = m_sessions.find(url); - if (iter != m_sessions.end()) { - assert(iter->second != nullptr); - iter->second->dataFromClient(&data[0], data.size()); - continue; - } - - std::unique_ptr session(new SessionWrapper(m_config, this)); - session->setClientAddr(addrStr); - session->setClientPort(senderPort); - - auto& sessionRef = *session; - session->setSendDataReqCb( - [this, &sessionRef](const std::uint8_t* buf, const std::size_t bufLen) - { - sendToClient(sessionRef, buf, bufLen); - }); - - session->setTermNotifyCb( - [this](const SessionWrapper& s) - { - auto key = QString("%1:%2").arg(s.getClientAddr()).arg(s.getClientPort()); - auto it = m_sessions.find(key); - if (it == m_sessions.end()) { - [[maybe_unused]] constexpr bool Session_not_found = false; - assert(Session_not_found); - return; - } - - m_socket.flush(); - m_sessions.erase(it); - }); - - m_sessions.insert(std::make_pair(url, session.get())); - auto sessionPtr = session.release(); - - if (!sessionPtr->start()) { - [[maybe_unused]] constexpr bool Should_not_happen = false; - assert(Should_not_happen); - continue; - } - - sessionPtr->dataFromClient(&data[0], data.size()); - } -} - -void Mgr::socketErrorOccurred([[maybe_unused]] QAbstractSocket::SocketError err) -{ - std::cerr << "ERROR: UDP Socket: " << m_socket.errorString().toStdString() << std::endl; -} - -bool Mgr::doListen() -{ - if (m_port == 0) { - return false; - } - - if (!m_socket.bind(QHostAddress::AnyIPv4, m_port, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint)) { - std::cerr << "ERROR: Failed to bind UDP socket to local port " << m_port << std::endl; - return false; - } - - if (!m_socket.open(QUdpSocket::ReadWrite)) { - std::cerr << "ERROR: Failed to open UDP socket" << std::endl; - return false; - } - - return true; -} - -void Mgr::sendToClient( - const SessionWrapper& session, - const std::uint8_t* buf, - std::size_t bufSize) -{ - std::size_t writtenCount = 0; - while (writtenCount < bufSize) { - auto remSize = bufSize - writtenCount; - auto count = - m_socket.writeDatagram( - reinterpret_cast(&buf[writtenCount]), - remSize, - QHostAddress(session.getClientAddr()), - session.getClientPort()); - - if (count < 0) { - std::cerr << "ERROR: Failed to write to UDP socket!" << std::endl; - return; - } - - writtenCount += count; - } -} - -void Mgr::broadcastAdvertise(const std::uint8_t* buf, std::size_t bufSize) -{ - m_lastAdvertise.assign(buf, buf + bufSize); - std::size_t writtenCount = 0; - while (writtenCount < bufSize) { - auto remSize = bufSize - writtenCount; - auto count = - m_socket.writeDatagram( - reinterpret_cast(&buf[writtenCount]), - remSize, - QHostAddress::Broadcast, - m_broadcastPort); - - if (count < 0) { - std::cerr << "ERROR: Failed to broadcast advertise data!" << std::endl; - return; - } - - writtenCount += count; - } -} - -} // namespace udp - -} // namespace app - -} // namespace cc_mqttsn_gateway diff --git a/gateway/app/udp/Mgr.h b/gateway/app/udp/Mgr.h deleted file mode 100644 index a456c220..00000000 --- a/gateway/app/udp/Mgr.h +++ /dev/null @@ -1,67 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#pragma once - -#include -#include -#include -#include - -#include -#include - -#include "cc_mqttsn_gateway/Config.h" -#include "GatewayWrapper.h" -#include "SessionWrapper.h" - -namespace cc_mqttsn_gateway -{ - -namespace app -{ - -namespace udp -{ - -class Mgr : public QObject -{ - Q_OBJECT -public: - explicit Mgr(const Config& config); - ~Mgr(); - bool start(); - -private slots: - void readClientData(); - void socketErrorOccurred(QAbstractSocket::SocketError err); - -private: - typedef unsigned short PortType; - typedef std::map SessionMap; - - bool doListen(); - void sendToClient( - const SessionWrapper& session, - const std::uint8_t* buf, - std::size_t bufSize); - void broadcastAdvertise(const std::uint8_t* buf, std::size_t bufSize); - - const Config& m_config; - PortType m_port = 0; - PortType m_broadcastPort = 0; - QUdpSocket m_socket; - GatewayWrapper m_gw; - std::vector m_lastAdvertise; - SessionMap m_sessions; -}; - -} // namespace udp - -} // namespace app - -} // namespace cc_mqttsn_gateway diff --git a/gateway/app/udp/SessionWrapper.cpp b/gateway/app/udp/SessionWrapper.cpp deleted file mode 100644 index 4a725dfc..00000000 --- a/gateway/app/udp/SessionWrapper.cpp +++ /dev/null @@ -1,351 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include "SessionWrapper.h" - -#include -#include -#include -#include - -namespace cc_mqttsn_gateway -{ - -namespace app -{ - -namespace udp -{ - -namespace -{ - -const std::string WildcardStr("*"); - -} // namespace - -SessionWrapper::SessionWrapper( - const Config& config, - QObject* parent) - : Base(parent), - m_config(config) -{ - m_session.setNextTickProgramReqCb( - [this](unsigned ms) - { - programNextTick(ms); - }); - - m_session.setCancelTickWaitReqCb( - [this]() -> unsigned - { - return cancelTick(); - }); - - m_session.setSendDataBrokerReqCb( - [this](const std::uint8_t* buf, std::size_t bufSize) - { - sendDataToBroker(buf, bufSize); - }); - - m_session.setTerminationReqCb( - [this]() - { - termSession(); - }); - - m_session.setBrokerReconnectReqCb( - [this]() - { - reconnectBroker(); - }); - - m_session.setClientConnectedReportCb( - [this](const std::string& clientId) - { - addPredefinedTopicsFor(clientId); - }); - - m_session.setAuthInfoReqCb( - [this](const std::string& clientId) -> AuthInfo - { - return getAuthInfoFor(clientId); - }); - - m_session.setGatewayId(m_config.gatewayId()); - m_session.setRetryPeriod(m_config.retryPeriod()); - m_session.setRetryCount(m_config.retryCount()); - m_session.setDefaultClientId(m_config.defaultClientId()); - m_session.setPubOnlyKeepAlive(m_config.pubOnlyKeepAlive()); - m_session.setSleepingClientMsgLimit(m_config.sleepingClientMsgLimit()); - - auto topicIdAllocRange = m_config.topicIdAllocRange(); - m_session.setTopicIdAllocationRange(topicIdAllocRange.first, topicIdAllocRange.second); - - addPredefinedTopicsFor(WildcardStr); - - connect( - &m_timer, SIGNAL(timeout()), - this, SLOT(tickTimeout())); - - connect( - &m_brokerSocket, SIGNAL(connected()), - this, SLOT(brokerConnected())); - connect( - &m_brokerSocket, SIGNAL(disconnected()), - this, SLOT(brokerDisconnected())); - connect( - &m_brokerSocket, SIGNAL(readyRead()), - this, SLOT(readFromBrokerSocket())); - connect( - &m_brokerSocket, SIGNAL(error(QAbstractSocket::SocketError)), - this, SLOT(brokerSocketErrorOccurred(QAbstractSocket::SocketError))); - -} - -SessionWrapper::~SessionWrapper() = default; - -bool SessionWrapper::start() -{ - if (!m_session.start()) { - std::cerr << "Failed to start new session" << std::endl; - return false; - } - - connectToBroker(); - return true; -} - -void SessionWrapper::tickTimeout() -{ - m_reqTicks = 0U; - m_session.tick(); -} - -void SessionWrapper::brokerConnected() -{ - m_session.setBrokerConnected(true); - m_reconnectRequested = false; -} - -void SessionWrapper::brokerDisconnected() -{ - m_session.setBrokerConnected(false); - if (m_reconnectRequested) { - connectToBroker(); - } -} - -void SessionWrapper::readFromBrokerSocket() -{ - auto data = m_brokerSocket.readAll(); - -// std::cout << "(BROKER) --> " << std::hex; -// for (auto byte : data) { -// std::cout << std::setw(2) << std::setfill('0') << static_cast(byte) << ' '; -// } -// std::cout << std::dec << std::endl; - - if (m_terminating) { - return; - } - - auto* buf = reinterpret_cast(data.constData()); - std::size_t bufSize = data.size(); - - if (!m_brokerData.empty()) { - m_brokerData.insert(m_brokerData.end(), buf, buf + bufSize); - buf = &m_brokerData[0]; - bufSize = m_brokerData.size(); - } - - std::size_t consumed = m_session.dataFromBroker(buf, bufSize); - if (bufSize <= consumed) { - m_brokerData.clear(); - return; - } - - if (!m_brokerData.empty()) { - m_brokerData.erase(m_brokerData.begin(), m_brokerData.begin() + consumed); - return; - } - - m_brokerData.assign(buf + consumed, buf + bufSize); -} - -void SessionWrapper::brokerSocketErrorOccurred([[maybe_unused]] QAbstractSocket::SocketError err) -{ - std::cerr << "ERROR: TCP Socket: " << m_brokerSocket.errorString().toStdString() << std::endl; -} - -void SessionWrapper::programNextTick(unsigned ms) -{ - assert(!m_terminating); - m_reqTicks = ms; - m_timer.setSingleShot(true); - m_timer.start(ms); -} - -unsigned SessionWrapper::cancelTick() -{ - auto rem = m_timer.remainingTime(); - unsigned result = 0U; - do { - if (static_cast(m_reqTicks) <= rem) { - break; - } - - result = m_reqTicks - rem; - } while (false); - - m_timer.stop(); - return result; -} - -void SessionWrapper::sendDataToBroker(const std::uint8_t* buf, std::size_t bufSize) -{ - std::size_t writtenCount = 0; - while (writtenCount < bufSize) { - auto remSize = bufSize - writtenCount; - auto count = - m_brokerSocket.write( - reinterpret_cast(&buf[writtenCount]), - remSize); - if (count < 0) { - std::cerr << "Failed to write to TCP socket" << std::endl; - return; - } - - writtenCount += count; - } -} - -void SessionWrapper::termSession() -{ - if (m_terminating) { - return; - } - - m_terminating = true; - m_timer.stop(); - m_brokerSocket.blockSignals(true); - m_brokerSocket.flush(); - m_brokerSocket.disconnectFromHost(); - assert(m_termNotifyCb); - m_termNotifyCb(*this); - deleteLater(); -} - -void SessionWrapper::reconnectBroker() -{ - m_reconnectRequested = true; - assert(m_brokerSocket.state() == QTcpSocket::ConnectedState); - m_brokerSocket.disconnectFromHost(); -} - -void SessionWrapper::connectToBroker() -{ - auto host = QString::fromStdString(m_config.brokerTcpHostAddress()); - auto port = m_config.brokerTcpHostPort(); - m_brokerSocket.connectToHost(host, port); -} - -void SessionWrapper::addPredefinedTopicsFor(const std::string& clientId) -{ - auto& predefinedTopics = m_config.predefinedTopics(); - auto iter = std::lower_bound( - predefinedTopics.begin(), predefinedTopics.end(), clientId, - [](Config::PredefinedTopicsList::const_reference elem, const std::string& cId) -> bool - { - return elem.clientId < cId; - }); - - while (iter != predefinedTopics.end()) { - if (iter->clientId != clientId) { - return; - } - - m_session.addPredefinedTopic(iter->topic, iter->topicId); - ++iter; - } -} - -SessionWrapper::AuthInfo SessionWrapper::getAuthInfoFor(const std::string& clientId) -{ - auto& authInfos = m_config.authInfos(); - - auto findElemFunc = - [&authInfos](const std::string& cId) -> Config::AuthInfosList::const_iterator - { - return std::lower_bound( - authInfos.begin(), authInfos.end(), cId, - [](Config::AuthInfosList::const_reference elem, const std::string& val) -> bool - { - return elem.clientId < val; - }); - }; - - auto iter = findElemFunc(clientId); - if ((iter == authInfos.end()) || - (iter->clientId != clientId)) { - iter = findElemFunc(WildcardStr); - } - - if ((iter == authInfos.end()) || - (iter->clientId != clientId)) { - return AuthInfo(); - } - - typedef decltype(m_session)::BinaryData BinaryData; - BinaryData data; - data.reserve(iter->password.size()); - - unsigned pos = 0U; - while (pos < iter->password.size()) { - auto remSize = iter->password.size() - pos; - const char* remStr = &iter->password[pos]; - - static const std::string BackSlashStr("\\\\"); - if ((BackSlashStr.size() <= remSize) && - (std::equal(BackSlashStr.begin(), BackSlashStr.end(), remStr))) { - data.push_back(static_cast('\\')); - pos = static_cast(pos + BackSlashStr.size()); - continue; - } - - static const std::string HexNumStr("\\x"); - static const std::size_t HexNumSize = HexNumStr.size() + 2; - if ((HexNumSize <= remSize) && - (std::equal(HexNumStr.begin(), HexNumStr.end(), remStr))) { - try { - auto* numStrBegin = &remStr[2]; - auto* numStrEnd = numStrBegin + 2; - std::string numStr(numStrBegin, numStrEnd); - auto byte = static_cast(std::stoul(numStr)); - data.push_back(byte); - pos = static_cast(pos + HexNumSize); - continue; - } - catch (...) { - // do nothing, fall through - } - } - - data.push_back(static_cast(iter->password[pos])); - ++pos; - } - - return std::make_pair(iter->username, std::move(data)); -} - -} // namespace udp - -} // namespace app - -} // namespace cc_mqttsn_gateway - - diff --git a/gateway/app/udp/SessionWrapper.h b/gateway/app/udp/SessionWrapper.h deleted file mode 100644 index 4bdacd7c..00000000 --- a/gateway/app/udp/SessionWrapper.h +++ /dev/null @@ -1,119 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#pragma once - -#include -#include -#include - -#include -#include -#include -#include - -#include "cc_mqttsn_gateway/Config.h" -#include "cc_mqttsn_gateway/Session.h" - -namespace cc_mqttsn_gateway -{ - -namespace app -{ - -namespace udp -{ - -class SessionWrapper : public QObject -{ - Q_OBJECT - typedef QObject Base; -public: - typedef unsigned short PortType; - typedef cc_mqttsn_gateway::Session::AuthInfo AuthInfo; - - SessionWrapper(const Config& config, QObject* parent); - ~SessionWrapper(); - - - typedef std::function TermNotifyCb; - template - void setTermNotifyCb(TFunc&& cb) - { - m_termNotifyCb = std::forward(cb); - } - - template - void setSendDataReqCb(TFunc&& cb) - { - m_session.setSendDataClientReqCb(std::forward(cb)); - } - - bool start(); - - void dataFromClient(const std::uint8_t* buf, const std::size_t bufLen) - { - m_session.dataFromClient(buf, bufLen); - } - - void setClientAddr(const QString& value) - { - m_clientAddr = value; - } - - const QString& getClientAddr() const - { - return m_clientAddr; - } - - void setClientPort(PortType value) - { - m_clientPort = value; - } - - PortType getClientPort() const - { - return m_clientPort; - } - -private slots: - void tickTimeout(); - void brokerConnected(); - void brokerDisconnected(); - void readFromBrokerSocket(); - void brokerSocketErrorOccurred(QAbstractSocket::SocketError err); - -private: - typedef std::vector DataBuf; - - void programNextTick(unsigned ms); - unsigned cancelTick(); - void sendDataToBroker(const std::uint8_t* buf, std::size_t bufSize); - void termSession(); - void reconnectBroker(); - void connectToBroker(); - void addPredefinedTopicsFor(const std::string& clientId); - AuthInfo getAuthInfoFor(const std::string& clientId); - - const Config& m_config; - QTcpSocket m_brokerSocket; - cc_mqttsn_gateway::Session m_session; - QTimer m_timer; - unsigned m_reqTicks = 0; - bool m_reconnectRequested = false; - DataBuf m_brokerData; - TermNotifyCb m_termNotifyCb; - QString m_clientAddr; - PortType m_clientPort = 0; - bool m_terminating = false; -}; - -} // namespace udp - -} // namespace app - -} // namespace cc_mqttsn_gateway diff --git a/gateway/app/udp/main.cpp b/gateway/app/udp/main.cpp deleted file mode 100644 index 39089c58..00000000 --- a/gateway/app/udp/main.cpp +++ /dev/null @@ -1,74 +0,0 @@ -// -// Copyright 2016 - 2024 (C). Alex Robenko. All rights reserved. -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include -#include - -#include -#include -#include -#include -#include - -#include "Mgr.h" -#include "cc_mqttsn_gateway/Config.h" - -namespace -{ - -const QString ConfigOptStr("config"); - -void prepareCommandLineOptions(QCommandLineParser& parser) -{ - parser.addHelpOption(); - - QCommandLineOption configOpt( - QStringList() << "c" << ConfigOptStr, - QCoreApplication::translate("main", "Configuration file."), - QCoreApplication::translate("main", "filename") - ); - parser.addOption(configOpt); - -} - -} // namespace - -int main(int argc, char *argv[]) -{ - QCoreApplication app(argc, argv); - QCommandLineParser parser; - prepareCommandLineOptions(parser); - parser.process(app); - - cc_mqttsn_gateway::Config config; - do { - if (!parser.isSet(ConfigOptStr)) { - break; - } - - auto configFile = parser.value(ConfigOptStr).toStdString(); - std::ifstream stream(configFile); - if (!stream) { - std::cerr << "WARNING: Failed to open configuration file \"" << - configFile << "\", using default configuration." << std::endl; - break; - } - - config.read(stream); - } while (false); - - cc_mqttsn_gateway::app::udp::Mgr gw(config); - if (!gw.start()) { - std::cerr << "Failed to start!" << std::endl; - return -1; - } - - return app.exec(); -} - - - From 0fe6447fe9a8ac9e338bbc1bd4d33b0a5382c99a Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 13 Aug 2024 08:46:27 +1000 Subject: [PATCH 097/106] Infstrastructure for extra unit-testing. --- .github/workflows/actions_build.yml | 11 +- client/lib/script/Qos0Config.cmake | 5 + client/lib/script/Qos1Config.cmake | 5 + client/lib/src/op/SendOp.cpp | 6 +- client/lib/src/op/SendOp.h | 4 +- client/lib/test/CMakeLists.txt | 71 ++++++---- client/lib/test/bm/UnitTestBmBase.cpp | 130 ++++++++++++++++++ client/lib/test/bm/UnitTestBmBase.h | 16 +++ .../lib/test/{ => default}/UnitTestConnect.th | 0 .../{ => default}/UnitTestDefaultBase.cpp | 0 .../test/{ => default}/UnitTestDefaultBase.h | 0 .../test/{ => default}/UnitTestDisconnect.th | 0 .../test/{ => default}/UnitTestGwDiscover.th | 0 .../lib/test/{ => default}/UnitTestPublish.th | 0 .../lib/test/{ => default}/UnitTestReceive.th | 0 .../lib/test/{ => default}/UnitTestSleep.th | 0 .../test/{ => default}/UnitTestSubscribe.th | 0 .../test/{ => default}/UnitTestUnsubscribe.th | 0 client/lib/test/{ => default}/UnitTestWill.th | 0 client/lib/test/no_gw/UnitTestNoGwBase.cpp | 130 ++++++++++++++++++ client/lib/test/no_gw/UnitTestNoGwBase.h | 16 +++ client/lib/test/qos0/UnitTestQos0Base.cpp | 130 ++++++++++++++++++ client/lib/test/qos0/UnitTestQos0Base.h | 16 +++ client/lib/test/qos1/UnitTestQos1Base.cpp | 130 ++++++++++++++++++ client/lib/test/qos1/UnitTestQos1Base.h | 16 +++ script/full_debug_build.sh | 2 +- 26 files changed, 648 insertions(+), 40 deletions(-) create mode 100644 client/lib/script/Qos0Config.cmake create mode 100644 client/lib/script/Qos1Config.cmake create mode 100644 client/lib/test/bm/UnitTestBmBase.cpp create mode 100644 client/lib/test/bm/UnitTestBmBase.h rename client/lib/test/{ => default}/UnitTestConnect.th (100%) rename client/lib/test/{ => default}/UnitTestDefaultBase.cpp (100%) rename client/lib/test/{ => default}/UnitTestDefaultBase.h (100%) rename client/lib/test/{ => default}/UnitTestDisconnect.th (100%) rename client/lib/test/{ => default}/UnitTestGwDiscover.th (100%) rename client/lib/test/{ => default}/UnitTestPublish.th (100%) rename client/lib/test/{ => default}/UnitTestReceive.th (100%) rename client/lib/test/{ => default}/UnitTestSleep.th (100%) rename client/lib/test/{ => default}/UnitTestSubscribe.th (100%) rename client/lib/test/{ => default}/UnitTestUnsubscribe.th (100%) rename client/lib/test/{ => default}/UnitTestWill.th (100%) create mode 100644 client/lib/test/no_gw/UnitTestNoGwBase.cpp create mode 100644 client/lib/test/no_gw/UnitTestNoGwBase.h create mode 100644 client/lib/test/qos0/UnitTestQos0Base.cpp create mode 100644 client/lib/test/qos0/UnitTestQos0Base.h create mode 100644 client/lib/test/qos1/UnitTestQos1Base.cpp create mode 100644 client/lib/test/qos1/UnitTestQos1Base.h diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index 4ed5b663..77a53e45 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -50,7 +50,8 @@ jobs: run: | cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{matrix.type}} -DCMAKE_CXX_STANDARD=${{matrix.cpp}} \ -DCMAKE_INSTALL_PREFIX=install -DCMAKE_PREFIX_PATH=${{runner.workspace}}/build/install \ - -DCC_MQTTSN_BUILD_UNIT_TESTS=ON -DCC_MQTTSN_WITH_SANITIZERS=ON + -DCC_MQTTSN_BUILD_UNIT_TESTS=ON -DCC_MQTTSN_WITH_SANITIZERS=ON \ + -DCC_MQTTSN_CUSTOM_CLIENT_CONFIG_FILES="$GITHUB_WORKSPACE/client/lib/script/BareMetalTestConfig.cmake;$GITHUB_WORKSPACE/client/lib/script/NoGwDiscoverConfig.cmake;$GITHUB_WORKSPACE/client/lib/script/Qos1Config.cmake;$GITHUB_WORKSPACE/client/lib/script/Qos0Config.cmake" env: CC: gcc-${{matrix.cc_ver}} CXX: g++-${{matrix.cc_ver}} @@ -118,7 +119,8 @@ jobs: run: | cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{matrix.type}} -DCMAKE_CXX_STANDARD=${{matrix.cpp}} \ -DCMAKE_INSTALL_PREFIX=install -DCMAKE_PREFIX_PATH=${{runner.workspace}}/build/install \ - -DCC_MQTTSN_BUILD_UNIT_TESTS=ON -DCC_MQTTSN_WITH_SANITIZERS=${{env.WITH_SANITIZERS}} + -DCC_MQTTSN_BUILD_UNIT_TESTS=ON -DCC_MQTTSN_WITH_SANITIZERS=${{env.WITH_SANITIZERS}} \ + -DCC_MQTTSN_CUSTOM_CLIENT_CONFIG_FILES="$GITHUB_WORKSPACE/client/lib/script/BareMetalTestConfig.cmake;$GITHUB_WORKSPACE/client/lib/script/NoGwDiscoverConfig.cmake;$GITHUB_WORKSPACE/client/lib/script/Qos1Config.cmake;$GITHUB_WORKSPACE/client/lib/script/Qos0Config.cmake" env: CC: clang-${{matrix.cc_ver}} CXX: clang++-${{matrix.cc_ver}} @@ -178,7 +180,7 @@ jobs: cmake %GITHUB_WORKSPACE% -A ${{matrix.arch}} -DCMAKE_BUILD_TYPE=${{matrix.type}} -DCMAKE_INSTALL_PREFIX=install ^ -DCMAKE_PREFIX_PATH=${{runner.workspace}}/build/install -DCMAKE_CXX_STANDARD=${{matrix.cpp}} ^ -DBoost_USE_STATIC_LIBS=ON -DCC_MQTTSN_BUILD_UNIT_TESTS=ON ^ - -DCC_MQTTSN_CLIENT_APPS=OFF -DCC_MQTTSN_GATEWAY_APPS=ON + -DCC_MQTTSN_CUSTOM_CLIENT_CONFIG_FILES="%GITHUB_WORKSPACE%/client/lib/script/BareMetalTestConfig.cmake;%GITHUB_WORKSPACE%/client/lib/script/NoGwDiscoverConfig.cmake;%GITHUB_WORKSPACE%/client/lib/script/Qos1Config.cmake;%GITHUB_WORKSPACE%/client/lib/script/Qos0Config.cmake" - name: Build Target working-directory: ${{runner.workspace}}/build @@ -235,7 +237,8 @@ jobs: cmake %GITHUB_WORKSPACE% -A ${{matrix.arch}} -DCMAKE_BUILD_TYPE=${{matrix.type}} -DCMAKE_INSTALL_PREFIX=install ^ -DCMAKE_PREFIX_PATH=${{runner.workspace}}/build/install -DCMAKE_CXX_STANDARD=${{matrix.cpp}} ^ -DBoost_USE_STATIC_LIBS=ON -DCC_MQTTSN_BUILD_UNIT_TESTS=ON ^ - -DCC_MQTTSN_CLIENT_APPS=OFF -DCC_MQTTSN_GATEWAY_APPS=${{env.HAS_BOOST}} + -DCC_MQTTSN_CLIENT_APPS=${{env.HAS_BOOST}} -DCC_MQTTSN_GATEWAY_APPS=${{env.HAS_BOOST}} ^ + -DCC_MQTTSN_CUSTOM_CLIENT_CONFIG_FILES="%GITHUB_WORKSPACE%/client/lib/script/BareMetalTestConfig.cmake;%GITHUB_WORKSPACE%/client/lib/script/NoGwDiscoverConfig.cmake;%GITHUB_WORKSPACE%/client/lib/script/Qos1Config.cmake;%GITHUB_WORKSPACE%/client/lib/script/Qos0Config.cmake" env: HAS_BOOST: "${{ matrix.arch == 'x64' && 'ON' || 'OFF' }}" diff --git a/client/lib/script/Qos0Config.cmake b/client/lib/script/Qos0Config.cmake new file mode 100644 index 00000000..1a756996 --- /dev/null +++ b/client/lib/script/Qos0Config.cmake @@ -0,0 +1,5 @@ +# Name of the client API +set (CC_MQTTSN_CLIENT_CUSTOM_NAME "qos0") + +# Set max Qos +set (CC_MQTTSN_CLIENT_MAX_QOS 0) diff --git a/client/lib/script/Qos1Config.cmake b/client/lib/script/Qos1Config.cmake new file mode 100644 index 00000000..b6f92d7f --- /dev/null +++ b/client/lib/script/Qos1Config.cmake @@ -0,0 +1,5 @@ +# Name of the client API +set (CC_MQTTSN_CLIENT_CUSTOM_NAME "qos1") + +# Set max Qos +set (CC_MQTTSN_CLIENT_MAX_QOS 1) diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index b320769b..043316d1 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -273,7 +273,6 @@ void SendOp::handle(RegackMsg& msg) } } -#if CC_MQTTSN_CLIENT_MAX_QOS > 0 void SendOp::handle(PubackMsg& msg) { if ((m_suspended) || @@ -330,7 +329,7 @@ void SendOp::handle(PubackMsg& msg) completeOpInternal(status, &info); } -#if CC_MQTTSN_CLIENT_MAX_QOS > 1 +#if CC_MQTTSN_CLIENT_MAX_QOS >=2 void SendOp::handle(PubrecMsg& msg) { if ((m_suspended) || @@ -364,8 +363,7 @@ void SendOp::handle(PubcompMsg& msg) completeOpInternal(CC_MqttsnAsyncOpStatus_Complete); } -#endif // #if CC_MQTTSN_CLIENT_MAX_QOS > 1 -#endif // #if CC_MQTTSN_CLIENT_MAX_QOS > 0 +#endif // #if CC_MQTTSN_CLIENT_MAX_QOS >=2 Op::Type SendOp::typeImpl() const { diff --git a/client/lib/src/op/SendOp.h b/client/lib/src/op/SendOp.h index cbc378ff..6082162f 100644 --- a/client/lib/src/op/SendOp.h +++ b/client/lib/src/op/SendOp.h @@ -46,11 +46,11 @@ class SendOp final : public Op using Base::handle; void handle(RegackMsg& msg) override; -#if CC_MQTTSN_CLIENT_MAX_QOS > 0 void handle(PubackMsg& msg) override; +#if CC_MQTTSN_CLIENT_MAX_QOS >=2 void handle(PubrecMsg& msg) override; void handle(PubcompMsg& msg) override; -#endif // #if CC_MQTTSN_CLIENT_MAX_QOS > 0 +#endif // CC_MQTTSN_CLIENT_MAX_QOS >=2 protected: virtual Type typeImpl() const override; diff --git a/client/lib/test/CMakeLists.txt b/client/lib/test/CMakeLists.txt index 92ad7117..9082a36b 100644 --- a/client/lib/test/CMakeLists.txt +++ b/client/lib/test/CMakeLists.txt @@ -19,9 +19,9 @@ target_include_directories( ################################## -function (cc_mqttsn_client_add_unit_test name test_lib) - set (src ${CMAKE_CURRENT_SOURCE_DIR}/${name}.th) - cc_cxxtest_add_test (NAME cc.mqttsn.client.${name} SRC ${src}) +function (cc_mqttsn_client_add_unit_test src test_lib) + get_filename_component(name ${src} NAME_WE) + cc_cxxtest_add_test (NAME cc.mqttsn.client.${name} SRC ${CMAKE_CURRENT_SOURCE_DIR}/${src}) target_link_libraries(cc.mqttsn.client.${name} PRIVATE ${test_lib} cxxtest::cxxtest) endfunction () @@ -30,7 +30,7 @@ endfunction () if (TARGET cc::cc_mqttsn_client) set (DEFAULT_BASE_LIB_NAME "UnitTestDefaultBase") set (DEFAULT_BASE_SRC - "UnitTestDefaultBase.cpp") + "default/UnitTestDefaultBase.cpp") add_library(${DEFAULT_BASE_LIB_NAME} STATIC ${DEFAULT_BASE_SRC}) target_link_libraries(${DEFAULT_BASE_LIB_NAME} PUBLIC ${COMMON_BASE_LIB_NAME} cc::cc_mqttsn_client) @@ -39,24 +39,21 @@ if (TARGET cc::cc_mqttsn_client) $ ) - cc_mqttsn_client_add_unit_test(UnitTestGwDiscover ${DEFAULT_BASE_LIB_NAME}) - cc_mqttsn_client_add_unit_test(UnitTestConnect ${DEFAULT_BASE_LIB_NAME}) - cc_mqttsn_client_add_unit_test(UnitTestDisconnect ${DEFAULT_BASE_LIB_NAME}) - cc_mqttsn_client_add_unit_test(UnitTestPublish ${DEFAULT_BASE_LIB_NAME}) - cc_mqttsn_client_add_unit_test(UnitTestReceive ${DEFAULT_BASE_LIB_NAME}) - cc_mqttsn_client_add_unit_test(UnitTestSubscribe ${DEFAULT_BASE_LIB_NAME}) - cc_mqttsn_client_add_unit_test(UnitTestUnsubscribe ${DEFAULT_BASE_LIB_NAME}) - cc_mqttsn_client_add_unit_test(UnitTestWill ${DEFAULT_BASE_LIB_NAME}) - cc_mqttsn_client_add_unit_test(UnitTestSleep ${DEFAULT_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(default/UnitTestGwDiscover.th ${DEFAULT_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(default/UnitTestConnect.th ${DEFAULT_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(default/UnitTestDisconnect.th ${DEFAULT_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(default/UnitTestPublish.th ${DEFAULT_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(default/UnitTestReceive.th ${DEFAULT_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(default/UnitTestSubscribe.th ${DEFAULT_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(default/UnitTestUnsubscribe.th ${DEFAULT_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(default/UnitTestWill.th ${DEFAULT_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(default/UnitTestSleep.th ${DEFAULT_BASE_LIB_NAME}) endif () -# TODO -return () - if (TARGET cc::cc_mqttsn_bm_client) set (BM_BASE_LIB_NAME "UnitTestBmBase") set (BM_BASE_SRC - "UnitTestBmBase.cpp") + "bm/UnitTestBmBase.cpp") add_library(${BM_BASE_LIB_NAME} STATIC ${BM_BASE_SRC}) target_link_libraries(${BM_BASE_LIB_NAME} PUBLIC ${COMMON_BASE_LIB_NAME} cc::cc_mqttsn_bm_client) @@ -65,16 +62,17 @@ if (TARGET cc::cc_mqttsn_bm_client) $ ) - cc_mqttsn_client_add_unit_test(UnitTestBmClient ${BM_BASE_LIB_NAME}) - cc_mqttsn_client_add_unit_test(UnitTestBmConnect ${BM_BASE_LIB_NAME}) - cc_mqttsn_client_add_unit_test(UnitTestBmPublish ${BM_BASE_LIB_NAME}) - cc_mqttsn_client_add_unit_test(UnitTestBmReceive ${BM_BASE_LIB_NAME}) +# cc_mqttsn_client_add_unit_test(UnitTestBmClient ${BM_BASE_LIB_NAME}) +# cc_mqttsn_client_add_unit_test(UnitTestBmConnect ${BM_BASE_LIB_NAME}) +# cc_mqttsn_client_add_unit_test(UnitTestBmPublish ${BM_BASE_LIB_NAME}) +# cc_mqttsn_client_add_unit_test(UnitTestBmReceive ${BM_BASE_LIB_NAME}) endif () + if (TARGET cc::cc_mqttsn_qos1_client) set (QOS1_BASE_LIB_NAME "UnitTestQos1Base") set (QOS1_BASE_SRC - "UnitTestQos1Base.cpp") + "qos1/UnitTestQos1Base.cpp") add_library(${QOS1_BASE_LIB_NAME} STATIC ${QOS1_BASE_SRC}) target_link_libraries(${QOS1_BASE_LIB_NAME} PUBLIC ${COMMON_BASE_LIB_NAME} cc::cc_mqttsn_qos1_client) @@ -83,15 +81,15 @@ if (TARGET cc::cc_mqttsn_qos1_client) $ ) - cc_mqttsn_client_add_unit_test(UnitTestQos1Publish ${QOS1_BASE_LIB_NAME}) - cc_mqttsn_client_add_unit_test(UnitTestQos1Receive ${QOS1_BASE_LIB_NAME}) - cc_mqttsn_client_add_unit_test(UnitTestQos1Subscribe ${QOS1_BASE_LIB_NAME}) +# cc_mqttsn_client_add_unit_test(UnitTestQos1Publish ${QOS1_BASE_LIB_NAME}) +# cc_mqttsn_client_add_unit_test(UnitTestQos1Receive ${QOS1_BASE_LIB_NAME}) +# cc_mqttsn_client_add_unit_test(UnitTestQos1Subscribe ${QOS1_BASE_LIB_NAME}) endif () if (TARGET cc::cc_mqttsn_qos0_client) set (QOS0_BASE_LIB_NAME "UnitTestQos0Base") set (QOS0_BASE_SRC - "UnitTestQos0Base.cpp") + "qos0/UnitTestQos0Base.cpp") add_library(${QOS0_BASE_LIB_NAME} STATIC ${QOS0_BASE_SRC}) target_link_libraries(${QOS0_BASE_LIB_NAME} PUBLIC ${COMMON_BASE_LIB_NAME} cc::cc_mqttsn_qos0_client) @@ -100,7 +98,22 @@ if (TARGET cc::cc_mqttsn_qos0_client) $ ) - cc_mqttsn_client_add_unit_test(UnitTestQos0Publish ${QOS0_BASE_LIB_NAME}) - cc_mqttsn_client_add_unit_test(UnitTestQos0Receive ${QOS0_BASE_LIB_NAME}) - cc_mqttsn_client_add_unit_test(UnitTestQos0Subscribe ${QOS0_BASE_LIB_NAME}) +# cc_mqttsn_client_add_unit_test(UnitTestQos0Publish ${QOS0_BASE_LIB_NAME}) +# cc_mqttsn_client_add_unit_test(UnitTestQos0Receive ${QOS0_BASE_LIB_NAME}) +# cc_mqttsn_client_add_unit_test(UnitTestQos0Subscribe ${QOS0_BASE_LIB_NAME}) endif () + +if (TARGET cc::cc_mqttsn_no_gw_client) + set (NO_GW_BASE_LIB_NAME "UnitTestNoGwBase") + set (NO_GW_BASE_SRC + "no_gw/UnitTestNoGwBase.cpp") + + add_library(${NO_GW_BASE_LIB_NAME} STATIC ${NO_GW_BASE_SRC}) + target_link_libraries(${NO_GW_BASE_LIB_NAME} PUBLIC ${COMMON_BASE_LIB_NAME} cc::cc_mqttsn_no_gw_client) + target_include_directories( + ${NO_GW_BASE_LIB_NAME} INTERFACE + $ + ) + + +endif () diff --git a/client/lib/test/bm/UnitTestBmBase.cpp b/client/lib/test/bm/UnitTestBmBase.cpp new file mode 100644 index 00000000..7b821c68 --- /dev/null +++ b/client/lib/test/bm/UnitTestBmBase.cpp @@ -0,0 +1,130 @@ +#include "UnitTestBmBase.h" + +#include "bm_client.h" + +const UnitTestBmBase::LibFuncs& UnitTestBmBase::getFuncs() +{ + static LibFuncs funcs; + funcs.m_alloc = &cc_mqttsn_bm_client_alloc; + funcs.m_free = &cc_mqttsn_bm_client_free; + funcs.m_tick = &cc_mqttsn_bm_client_tick; + funcs.m_process_data = &cc_mqttsn_bm_client_process_data; + funcs.m_set_default_retry_period = &cc_mqttsn_bm_client_set_default_retry_period; + funcs.m_get_default_retry_period = &cc_mqttsn_bm_client_get_default_retry_period; + funcs.m_set_default_retry_count = &cc_mqttsn_bm_client_set_default_retry_count; + funcs.m_get_default_retry_count = &cc_mqttsn_bm_client_get_default_retry_count; + funcs.m_set_default_broadcast_radius = &cc_mqttsn_bm_client_set_default_broadcast_radius; + funcs.m_get_default_broadcast_radius = &cc_mqttsn_bm_client_get_default_broadcast_radius; + funcs.m_get_available_gateways_count = &cc_mqttsn_bm_client_get_available_gateways_count; + funcs.m_init_gateway_info = &cc_mqttsn_bm_client_init_gateway_info; + funcs.m_get_available_gateway_info = &cc_mqttsn_bm_client_get_available_gateway_info; + funcs.m_set_available_gateway_info = &cc_mqttsn_bm_client_set_available_gateway_info; + funcs.m_discard_available_gateway_info = &cc_mqttsn_bm_client_discard_available_gateway_info; + funcs.m_discard_all_gateway_infos = &cc_mqttsn_bm_client_discard_all_gateway_infos; + funcs.m_set_default_gw_adv_duration = &cc_mqttsn_bm_client_set_default_gw_adv_duration; + funcs.m_get_default_gw_adv_duration = &cc_mqttsn_bm_client_get_default_gw_adv_duration; + funcs.m_set_allowed_adv_losses = &cc_mqttsn_bm_client_set_allowed_adv_losses; + funcs.m_get_allowed_adv_losses = &cc_mqttsn_bm_client_get_allowed_adv_losses; + funcs.m_set_verify_outgoing_topic_enabled = &cc_mqttsn_bm_client_set_verify_outgoing_topic_enabled; + funcs.m_get_verify_outgoing_topic_enabled = &cc_mqttsn_bm_client_get_verify_outgoing_topic_enabled; + funcs.m_set_verify_incoming_topic_enabled = &cc_mqttsn_bm_client_set_verify_incoming_topic_enabled; + funcs.m_get_verify_incoming_topic_enabled = &cc_mqttsn_bm_client_get_verify_incoming_topic_enabled; + funcs.m_set_verify_incoming_msg_subscribed = &cc_mqttsn_bm_client_set_verify_incoming_msg_subscribed; + funcs.m_get_verify_incoming_msg_subscribed = &cc_mqttsn_bm_client_get_verify_incoming_msg_subscribed; + funcs.m_set_outgoing_topic_id_storage_limit = &cc_mqttsn_bm_client_set_outgoing_topic_id_storage_limit; + funcs.m_get_outgoing_topic_id_storage_limit = &cc_mqttsn_bm_client_get_outgoing_topic_id_storage_limit; + funcs.m_set_incoming_topic_id_storage_limit = &cc_mqttsn_bm_client_set_incoming_topic_id_storage_limit; + funcs.m_get_incoming_topic_id_storage_limit = &cc_mqttsn_bm_client_get_incoming_topic_id_storage_limit; + funcs.m_asleep_check_messages = &cc_mqttsn_bm_client_asleep_check_messages; + funcs.m_search_prepare = &cc_mqttsn_bm_client_search_prepare; + funcs.m_search_set_retry_period = &cc_mqttsn_bm_client_search_set_retry_period; + funcs.m_search_get_retry_period = &cc_mqttsn_bm_client_search_get_retry_period; + funcs.m_search_set_retry_count = &cc_mqttsn_bm_client_search_set_retry_count; + funcs.m_search_get_retry_count = &cc_mqttsn_bm_client_search_get_retry_count; + funcs.m_search_set_broadcast_radius = &cc_mqttsn_bm_client_search_set_broadcast_radius; + funcs.m_search_get_broadcast_radius = &cc_mqttsn_bm_client_search_get_broadcast_radius; + funcs.m_search_send = &cc_mqttsn_bm_client_search_send; + funcs.m_search_cancel = &cc_mqttsn_bm_client_search_cancel; + funcs.m_search = &cc_mqttsn_bm_client_search; + funcs.m_connect_prepare = &cc_mqttsn_bm_client_connect_prepare; + funcs.m_connect_set_retry_period = &cc_mqttsn_bm_client_connect_set_retry_period; + funcs.m_connect_get_retry_period = &cc_mqttsn_bm_client_connect_get_retry_period; + funcs.m_connect_set_retry_count = &cc_mqttsn_bm_client_connect_set_retry_count; + funcs.m_connect_get_retry_count = &cc_mqttsn_bm_client_connect_get_retry_count; + funcs.m_connect_init_config = &cc_mqttsn_bm_client_connect_init_config; + funcs.m_connect_config = &cc_mqttsn_bm_client_connect_config; + funcs.m_connect_init_config_will = &cc_mqttsn_bm_client_connect_init_config_will; + funcs.m_connect_config_will = &cc_mqttsn_bm_client_connect_config_will; + funcs.m_connect_send = &cc_mqttsn_bm_client_connect_send; + funcs.m_connect_cancel = &cc_mqttsn_bm_client_connect_cancel; + funcs.m_connect = &cc_mqttsn_bm_client_connect; + funcs.m_get_connection_status = &cc_mqttsn_bm_client_get_connection_status; + funcs.m_disconnect_prepare = &cc_mqttsn_bm_client_disconnect_prepare; + funcs.m_disconnect_set_retry_period = &cc_mqttsn_bm_client_disconnect_set_retry_period; + funcs.m_disconnect_get_retry_period = &cc_mqttsn_bm_client_disconnect_get_retry_period; + funcs.m_disconnect_set_retry_count = &cc_mqttsn_bm_client_disconnect_set_retry_count; + funcs.m_disconnect_get_retry_count = &cc_mqttsn_bm_client_disconnect_get_retry_count; + funcs.m_disconnect_send = &cc_mqttsn_bm_client_disconnect_send; + funcs.m_disconnect_cancel = &cc_mqttsn_bm_client_disconnect_cancel; + funcs.m_disconnect = &cc_mqttsn_bm_client_disconnect; + funcs.m_subscribe_prepare = &cc_mqttsn_bm_client_subscribe_prepare; + funcs.m_subscribe_set_retry_period = &cc_mqttsn_bm_client_subscribe_set_retry_period; + funcs.m_subscribe_get_retry_period = &cc_mqttsn_bm_client_subscribe_get_retry_period; + funcs.m_subscribe_set_retry_count = &cc_mqttsn_bm_client_subscribe_set_retry_count; + funcs.m_subscribe_get_retry_count = &cc_mqttsn_bm_client_subscribe_get_retry_count; + funcs.m_subscribe_init_config = &cc_mqttsn_bm_client_subscribe_init_config; + funcs.m_subscribe_config = &cc_mqttsn_bm_client_subscribe_config; + funcs.m_subscribe_send = &cc_mqttsn_bm_client_subscribe_send; + funcs.m_subscribe_cancel = &cc_mqttsn_bm_client_subscribe_cancel; + funcs.m_subscribe = &cc_mqttsn_bm_client_subscribe; + funcs.m_unsubscribe_prepare = &cc_mqttsn_bm_client_unsubscribe_prepare; + funcs.m_unsubscribe_set_retry_period = &cc_mqttsn_bm_client_unsubscribe_set_retry_period; + funcs.m_unsubscribe_get_retry_period = &cc_mqttsn_bm_client_unsubscribe_get_retry_period; + funcs.m_unsubscribe_set_retry_count = &cc_mqttsn_bm_client_unsubscribe_set_retry_count; + funcs.m_unsubscribe_get_retry_count = &cc_mqttsn_bm_client_unsubscribe_get_retry_count; + funcs.m_unsubscribe_init_config = &cc_mqttsn_bm_client_unsubscribe_init_config; + funcs.m_unsubscribe_config = &cc_mqttsn_bm_client_unsubscribe_config; + funcs.m_unsubscribe_send = &cc_mqttsn_bm_client_unsubscribe_send; + funcs.m_unsubscribe_cancel = &cc_mqttsn_bm_client_unsubscribe_cancel; + funcs.m_unsubscribe = &cc_mqttsn_bm_client_unsubscribe; + funcs.m_publish_prepare = &cc_mqttsn_bm_client_publish_prepare; + funcs.m_publish_set_retry_period = &cc_mqttsn_bm_client_publish_set_retry_period; + funcs.m_publish_get_retry_period = &cc_mqttsn_bm_client_publish_get_retry_period; + funcs.m_publish_set_retry_count = &cc_mqttsn_bm_client_publish_set_retry_count; + funcs.m_publish_get_retry_count = &cc_mqttsn_bm_client_publish_get_retry_count; + funcs.m_publish_init_config = &cc_mqttsn_bm_client_publish_init_config; + funcs.m_publish_config = &cc_mqttsn_bm_client_publish_config; + funcs.m_publish_send = &cc_mqttsn_bm_client_publish_send; + funcs.m_publish_cancel = &cc_mqttsn_bm_client_publish_cancel; + funcs.m_publish = &cc_mqttsn_bm_client_publish; + funcs.m_will_prepare = &cc_mqttsn_bm_client_will_prepare; + funcs.m_will_set_retry_period = &cc_mqttsn_bm_client_will_set_retry_period; + funcs.m_will_get_retry_period = &cc_mqttsn_bm_client_will_get_retry_period; + funcs.m_will_set_retry_count = &cc_mqttsn_bm_client_will_set_retry_count; + funcs.m_will_get_retry_count = &cc_mqttsn_bm_client_will_get_retry_count; + funcs.m_will_init_config = &cc_mqttsn_bm_client_will_init_config; + funcs.m_will_config = &cc_mqttsn_bm_client_will_config; + funcs.m_will_send = &cc_mqttsn_bm_client_will_send; + funcs.m_will_cancel = &cc_mqttsn_bm_client_will_cancel; + funcs.m_will = &cc_mqttsn_bm_client_will; + funcs.m_sleep_prepare = &cc_mqttsn_bm_client_sleep_prepare; + funcs.m_sleep_set_retry_period = &cc_mqttsn_bm_client_sleep_set_retry_period; + funcs.m_sleep_get_retry_period = &cc_mqttsn_bm_client_sleep_get_retry_period; + funcs.m_sleep_set_retry_count = &cc_mqttsn_bm_client_sleep_set_retry_count; + funcs.m_sleep_get_retry_count = &cc_mqttsn_bm_client_sleep_get_retry_count; + funcs.m_sleep_init_config = &cc_mqttsn_bm_client_sleep_init_config; + funcs.m_sleep_config = &cc_mqttsn_bm_client_sleep_config; + funcs.m_sleep_send = &cc_mqttsn_bm_client_sleep_send; + funcs.m_sleep_cancel = &cc_mqttsn_bm_client_sleep_cancel; + funcs.m_sleep = &cc_mqttsn_bm_client_sleep; + funcs.m_set_next_tick_program_callback = &cc_mqttsn_bm_client_set_next_tick_program_callback; + funcs.m_set_cancel_next_tick_wait_callback = &cc_mqttsn_bm_client_set_cancel_next_tick_wait_callback; + funcs.m_set_send_output_data_callback = &cc_mqttsn_bm_client_set_send_output_data_callback; + funcs.m_set_gw_status_report_callback = &cc_mqttsn_bm_client_set_gw_status_report_callback; + funcs.m_set_gw_disconnect_report_callback = &cc_mqttsn_bm_client_set_gw_disconnect_report_callback; + funcs.m_set_message_report_callback = &cc_mqttsn_bm_client_set_message_report_callback; + funcs.m_set_error_log_callback = &cc_mqttsn_bm_client_set_error_log_callback; + funcs.m_set_gwinfo_delay_request_callback = &cc_mqttsn_bm_client_set_gwinfo_delay_request_callback; + + return funcs; +} diff --git a/client/lib/test/bm/UnitTestBmBase.h b/client/lib/test/bm/UnitTestBmBase.h new file mode 100644 index 00000000..994a4c95 --- /dev/null +++ b/client/lib/test/bm/UnitTestBmBase.h @@ -0,0 +1,16 @@ +#pragma once + +#include "UnitTestCommonBase.h" + +class UnitTestBmBase : public UnitTestCommonBase +{ + using Base = UnitTestCommonBase; +protected: + + UnitTestBmBase(): + Base(getFuncs()) + { + } + + static const LibFuncs& getFuncs(); +}; \ No newline at end of file diff --git a/client/lib/test/UnitTestConnect.th b/client/lib/test/default/UnitTestConnect.th similarity index 100% rename from client/lib/test/UnitTestConnect.th rename to client/lib/test/default/UnitTestConnect.th diff --git a/client/lib/test/UnitTestDefaultBase.cpp b/client/lib/test/default/UnitTestDefaultBase.cpp similarity index 100% rename from client/lib/test/UnitTestDefaultBase.cpp rename to client/lib/test/default/UnitTestDefaultBase.cpp diff --git a/client/lib/test/UnitTestDefaultBase.h b/client/lib/test/default/UnitTestDefaultBase.h similarity index 100% rename from client/lib/test/UnitTestDefaultBase.h rename to client/lib/test/default/UnitTestDefaultBase.h diff --git a/client/lib/test/UnitTestDisconnect.th b/client/lib/test/default/UnitTestDisconnect.th similarity index 100% rename from client/lib/test/UnitTestDisconnect.th rename to client/lib/test/default/UnitTestDisconnect.th diff --git a/client/lib/test/UnitTestGwDiscover.th b/client/lib/test/default/UnitTestGwDiscover.th similarity index 100% rename from client/lib/test/UnitTestGwDiscover.th rename to client/lib/test/default/UnitTestGwDiscover.th diff --git a/client/lib/test/UnitTestPublish.th b/client/lib/test/default/UnitTestPublish.th similarity index 100% rename from client/lib/test/UnitTestPublish.th rename to client/lib/test/default/UnitTestPublish.th diff --git a/client/lib/test/UnitTestReceive.th b/client/lib/test/default/UnitTestReceive.th similarity index 100% rename from client/lib/test/UnitTestReceive.th rename to client/lib/test/default/UnitTestReceive.th diff --git a/client/lib/test/UnitTestSleep.th b/client/lib/test/default/UnitTestSleep.th similarity index 100% rename from client/lib/test/UnitTestSleep.th rename to client/lib/test/default/UnitTestSleep.th diff --git a/client/lib/test/UnitTestSubscribe.th b/client/lib/test/default/UnitTestSubscribe.th similarity index 100% rename from client/lib/test/UnitTestSubscribe.th rename to client/lib/test/default/UnitTestSubscribe.th diff --git a/client/lib/test/UnitTestUnsubscribe.th b/client/lib/test/default/UnitTestUnsubscribe.th similarity index 100% rename from client/lib/test/UnitTestUnsubscribe.th rename to client/lib/test/default/UnitTestUnsubscribe.th diff --git a/client/lib/test/UnitTestWill.th b/client/lib/test/default/UnitTestWill.th similarity index 100% rename from client/lib/test/UnitTestWill.th rename to client/lib/test/default/UnitTestWill.th diff --git a/client/lib/test/no_gw/UnitTestNoGwBase.cpp b/client/lib/test/no_gw/UnitTestNoGwBase.cpp new file mode 100644 index 00000000..4e25dcdc --- /dev/null +++ b/client/lib/test/no_gw/UnitTestNoGwBase.cpp @@ -0,0 +1,130 @@ +#include "UnitTestNoGwBase.h" + +#include "no_gw_client.h" + +const UnitTestNoGwBase::LibFuncs& UnitTestNoGwBase::getFuncs() +{ + static LibFuncs funcs; + funcs.m_alloc = &cc_mqttsn_no_gw_client_alloc; + funcs.m_free = &cc_mqttsn_no_gw_client_free; + funcs.m_tick = &cc_mqttsn_no_gw_client_tick; + funcs.m_process_data = &cc_mqttsn_no_gw_client_process_data; + funcs.m_set_default_retry_period = &cc_mqttsn_no_gw_client_set_default_retry_period; + funcs.m_get_default_retry_period = &cc_mqttsn_no_gw_client_get_default_retry_period; + funcs.m_set_default_retry_count = &cc_mqttsn_no_gw_client_set_default_retry_count; + funcs.m_get_default_retry_count = &cc_mqttsn_no_gw_client_get_default_retry_count; + funcs.m_set_default_broadcast_radius = &cc_mqttsn_no_gw_client_set_default_broadcast_radius; + funcs.m_get_default_broadcast_radius = &cc_mqttsn_no_gw_client_get_default_broadcast_radius; + funcs.m_get_available_gateways_count = &cc_mqttsn_no_gw_client_get_available_gateways_count; + funcs.m_init_gateway_info = &cc_mqttsn_no_gw_client_init_gateway_info; + funcs.m_get_available_gateway_info = &cc_mqttsn_no_gw_client_get_available_gateway_info; + funcs.m_set_available_gateway_info = &cc_mqttsn_no_gw_client_set_available_gateway_info; + funcs.m_discard_available_gateway_info = &cc_mqttsn_no_gw_client_discard_available_gateway_info; + funcs.m_discard_all_gateway_infos = &cc_mqttsn_no_gw_client_discard_all_gateway_infos; + funcs.m_set_default_gw_adv_duration = &cc_mqttsn_no_gw_client_set_default_gw_adv_duration; + funcs.m_get_default_gw_adv_duration = &cc_mqttsn_no_gw_client_get_default_gw_adv_duration; + funcs.m_set_allowed_adv_losses = &cc_mqttsn_no_gw_client_set_allowed_adv_losses; + funcs.m_get_allowed_adv_losses = &cc_mqttsn_no_gw_client_get_allowed_adv_losses; + funcs.m_set_verify_outgoing_topic_enabled = &cc_mqttsn_no_gw_client_set_verify_outgoing_topic_enabled; + funcs.m_get_verify_outgoing_topic_enabled = &cc_mqttsn_no_gw_client_get_verify_outgoing_topic_enabled; + funcs.m_set_verify_incoming_topic_enabled = &cc_mqttsn_no_gw_client_set_verify_incoming_topic_enabled; + funcs.m_get_verify_incoming_topic_enabled = &cc_mqttsn_no_gw_client_get_verify_incoming_topic_enabled; + funcs.m_set_verify_incoming_msg_subscribed = &cc_mqttsn_no_gw_client_set_verify_incoming_msg_subscribed; + funcs.m_get_verify_incoming_msg_subscribed = &cc_mqttsn_no_gw_client_get_verify_incoming_msg_subscribed; + funcs.m_set_outgoing_topic_id_storage_limit = &cc_mqttsn_no_gw_client_set_outgoing_topic_id_storage_limit; + funcs.m_get_outgoing_topic_id_storage_limit = &cc_mqttsn_no_gw_client_get_outgoing_topic_id_storage_limit; + funcs.m_set_incoming_topic_id_storage_limit = &cc_mqttsn_no_gw_client_set_incoming_topic_id_storage_limit; + funcs.m_get_incoming_topic_id_storage_limit = &cc_mqttsn_no_gw_client_get_incoming_topic_id_storage_limit; + funcs.m_asleep_check_messages = &cc_mqttsn_no_gw_client_asleep_check_messages; + funcs.m_search_prepare = &cc_mqttsn_no_gw_client_search_prepare; + funcs.m_search_set_retry_period = &cc_mqttsn_no_gw_client_search_set_retry_period; + funcs.m_search_get_retry_period = &cc_mqttsn_no_gw_client_search_get_retry_period; + funcs.m_search_set_retry_count = &cc_mqttsn_no_gw_client_search_set_retry_count; + funcs.m_search_get_retry_count = &cc_mqttsn_no_gw_client_search_get_retry_count; + funcs.m_search_set_broadcast_radius = &cc_mqttsn_no_gw_client_search_set_broadcast_radius; + funcs.m_search_get_broadcast_radius = &cc_mqttsn_no_gw_client_search_get_broadcast_radius; + funcs.m_search_send = &cc_mqttsn_no_gw_client_search_send; + funcs.m_search_cancel = &cc_mqttsn_no_gw_client_search_cancel; + funcs.m_search = &cc_mqttsn_no_gw_client_search; + funcs.m_connect_prepare = &cc_mqttsn_no_gw_client_connect_prepare; + funcs.m_connect_set_retry_period = &cc_mqttsn_no_gw_client_connect_set_retry_period; + funcs.m_connect_get_retry_period = &cc_mqttsn_no_gw_client_connect_get_retry_period; + funcs.m_connect_set_retry_count = &cc_mqttsn_no_gw_client_connect_set_retry_count; + funcs.m_connect_get_retry_count = &cc_mqttsn_no_gw_client_connect_get_retry_count; + funcs.m_connect_init_config = &cc_mqttsn_no_gw_client_connect_init_config; + funcs.m_connect_config = &cc_mqttsn_no_gw_client_connect_config; + funcs.m_connect_init_config_will = &cc_mqttsn_no_gw_client_connect_init_config_will; + funcs.m_connect_config_will = &cc_mqttsn_no_gw_client_connect_config_will; + funcs.m_connect_send = &cc_mqttsn_no_gw_client_connect_send; + funcs.m_connect_cancel = &cc_mqttsn_no_gw_client_connect_cancel; + funcs.m_connect = &cc_mqttsn_no_gw_client_connect; + funcs.m_get_connection_status = &cc_mqttsn_no_gw_client_get_connection_status; + funcs.m_disconnect_prepare = &cc_mqttsn_no_gw_client_disconnect_prepare; + funcs.m_disconnect_set_retry_period = &cc_mqttsn_no_gw_client_disconnect_set_retry_period; + funcs.m_disconnect_get_retry_period = &cc_mqttsn_no_gw_client_disconnect_get_retry_period; + funcs.m_disconnect_set_retry_count = &cc_mqttsn_no_gw_client_disconnect_set_retry_count; + funcs.m_disconnect_get_retry_count = &cc_mqttsn_no_gw_client_disconnect_get_retry_count; + funcs.m_disconnect_send = &cc_mqttsn_no_gw_client_disconnect_send; + funcs.m_disconnect_cancel = &cc_mqttsn_no_gw_client_disconnect_cancel; + funcs.m_disconnect = &cc_mqttsn_no_gw_client_disconnect; + funcs.m_subscribe_prepare = &cc_mqttsn_no_gw_client_subscribe_prepare; + funcs.m_subscribe_set_retry_period = &cc_mqttsn_no_gw_client_subscribe_set_retry_period; + funcs.m_subscribe_get_retry_period = &cc_mqttsn_no_gw_client_subscribe_get_retry_period; + funcs.m_subscribe_set_retry_count = &cc_mqttsn_no_gw_client_subscribe_set_retry_count; + funcs.m_subscribe_get_retry_count = &cc_mqttsn_no_gw_client_subscribe_get_retry_count; + funcs.m_subscribe_init_config = &cc_mqttsn_no_gw_client_subscribe_init_config; + funcs.m_subscribe_config = &cc_mqttsn_no_gw_client_subscribe_config; + funcs.m_subscribe_send = &cc_mqttsn_no_gw_client_subscribe_send; + funcs.m_subscribe_cancel = &cc_mqttsn_no_gw_client_subscribe_cancel; + funcs.m_subscribe = &cc_mqttsn_no_gw_client_subscribe; + funcs.m_unsubscribe_prepare = &cc_mqttsn_no_gw_client_unsubscribe_prepare; + funcs.m_unsubscribe_set_retry_period = &cc_mqttsn_no_gw_client_unsubscribe_set_retry_period; + funcs.m_unsubscribe_get_retry_period = &cc_mqttsn_no_gw_client_unsubscribe_get_retry_period; + funcs.m_unsubscribe_set_retry_count = &cc_mqttsn_no_gw_client_unsubscribe_set_retry_count; + funcs.m_unsubscribe_get_retry_count = &cc_mqttsn_no_gw_client_unsubscribe_get_retry_count; + funcs.m_unsubscribe_init_config = &cc_mqttsn_no_gw_client_unsubscribe_init_config; + funcs.m_unsubscribe_config = &cc_mqttsn_no_gw_client_unsubscribe_config; + funcs.m_unsubscribe_send = &cc_mqttsn_no_gw_client_unsubscribe_send; + funcs.m_unsubscribe_cancel = &cc_mqttsn_no_gw_client_unsubscribe_cancel; + funcs.m_unsubscribe = &cc_mqttsn_no_gw_client_unsubscribe; + funcs.m_publish_prepare = &cc_mqttsn_no_gw_client_publish_prepare; + funcs.m_publish_set_retry_period = &cc_mqttsn_no_gw_client_publish_set_retry_period; + funcs.m_publish_get_retry_period = &cc_mqttsn_no_gw_client_publish_get_retry_period; + funcs.m_publish_set_retry_count = &cc_mqttsn_no_gw_client_publish_set_retry_count; + funcs.m_publish_get_retry_count = &cc_mqttsn_no_gw_client_publish_get_retry_count; + funcs.m_publish_init_config = &cc_mqttsn_no_gw_client_publish_init_config; + funcs.m_publish_config = &cc_mqttsn_no_gw_client_publish_config; + funcs.m_publish_send = &cc_mqttsn_no_gw_client_publish_send; + funcs.m_publish_cancel = &cc_mqttsn_no_gw_client_publish_cancel; + funcs.m_publish = &cc_mqttsn_no_gw_client_publish; + funcs.m_will_prepare = &cc_mqttsn_no_gw_client_will_prepare; + funcs.m_will_set_retry_period = &cc_mqttsn_no_gw_client_will_set_retry_period; + funcs.m_will_get_retry_period = &cc_mqttsn_no_gw_client_will_get_retry_period; + funcs.m_will_set_retry_count = &cc_mqttsn_no_gw_client_will_set_retry_count; + funcs.m_will_get_retry_count = &cc_mqttsn_no_gw_client_will_get_retry_count; + funcs.m_will_init_config = &cc_mqttsn_no_gw_client_will_init_config; + funcs.m_will_config = &cc_mqttsn_no_gw_client_will_config; + funcs.m_will_send = &cc_mqttsn_no_gw_client_will_send; + funcs.m_will_cancel = &cc_mqttsn_no_gw_client_will_cancel; + funcs.m_will = &cc_mqttsn_no_gw_client_will; + funcs.m_sleep_prepare = &cc_mqttsn_no_gw_client_sleep_prepare; + funcs.m_sleep_set_retry_period = &cc_mqttsn_no_gw_client_sleep_set_retry_period; + funcs.m_sleep_get_retry_period = &cc_mqttsn_no_gw_client_sleep_get_retry_period; + funcs.m_sleep_set_retry_count = &cc_mqttsn_no_gw_client_sleep_set_retry_count; + funcs.m_sleep_get_retry_count = &cc_mqttsn_no_gw_client_sleep_get_retry_count; + funcs.m_sleep_init_config = &cc_mqttsn_no_gw_client_sleep_init_config; + funcs.m_sleep_config = &cc_mqttsn_no_gw_client_sleep_config; + funcs.m_sleep_send = &cc_mqttsn_no_gw_client_sleep_send; + funcs.m_sleep_cancel = &cc_mqttsn_no_gw_client_sleep_cancel; + funcs.m_sleep = &cc_mqttsn_no_gw_client_sleep; + funcs.m_set_next_tick_program_callback = &cc_mqttsn_no_gw_client_set_next_tick_program_callback; + funcs.m_set_cancel_next_tick_wait_callback = &cc_mqttsn_no_gw_client_set_cancel_next_tick_wait_callback; + funcs.m_set_send_output_data_callback = &cc_mqttsn_no_gw_client_set_send_output_data_callback; + funcs.m_set_gw_status_report_callback = &cc_mqttsn_no_gw_client_set_gw_status_report_callback; + funcs.m_set_gw_disconnect_report_callback = &cc_mqttsn_no_gw_client_set_gw_disconnect_report_callback; + funcs.m_set_message_report_callback = &cc_mqttsn_no_gw_client_set_message_report_callback; + funcs.m_set_error_log_callback = &cc_mqttsn_no_gw_client_set_error_log_callback; + funcs.m_set_gwinfo_delay_request_callback = &cc_mqttsn_no_gw_client_set_gwinfo_delay_request_callback; + + return funcs; +} diff --git a/client/lib/test/no_gw/UnitTestNoGwBase.h b/client/lib/test/no_gw/UnitTestNoGwBase.h new file mode 100644 index 00000000..eab706c8 --- /dev/null +++ b/client/lib/test/no_gw/UnitTestNoGwBase.h @@ -0,0 +1,16 @@ +#pragma once + +#include "UnitTestCommonBase.h" + +class UnitTestNoGwBase : public UnitTestCommonBase +{ + using Base = UnitTestCommonBase; +protected: + + UnitTestNoGwBase(): + Base(getFuncs()) + { + } + + static const LibFuncs& getFuncs(); +}; \ No newline at end of file diff --git a/client/lib/test/qos0/UnitTestQos0Base.cpp b/client/lib/test/qos0/UnitTestQos0Base.cpp new file mode 100644 index 00000000..2556c6da --- /dev/null +++ b/client/lib/test/qos0/UnitTestQos0Base.cpp @@ -0,0 +1,130 @@ +#include "UnitTestQos0Base.h" + +#include "qos0_client.h" + +const UnitTestQos0Base::LibFuncs& UnitTestQos0Base::getFuncs() +{ + static LibFuncs funcs; + funcs.m_alloc = &cc_mqttsn_qos0_client_alloc; + funcs.m_free = &cc_mqttsn_qos0_client_free; + funcs.m_tick = &cc_mqttsn_qos0_client_tick; + funcs.m_process_data = &cc_mqttsn_qos0_client_process_data; + funcs.m_set_default_retry_period = &cc_mqttsn_qos0_client_set_default_retry_period; + funcs.m_get_default_retry_period = &cc_mqttsn_qos0_client_get_default_retry_period; + funcs.m_set_default_retry_count = &cc_mqttsn_qos0_client_set_default_retry_count; + funcs.m_get_default_retry_count = &cc_mqttsn_qos0_client_get_default_retry_count; + funcs.m_set_default_broadcast_radius = &cc_mqttsn_qos0_client_set_default_broadcast_radius; + funcs.m_get_default_broadcast_radius = &cc_mqttsn_qos0_client_get_default_broadcast_radius; + funcs.m_get_available_gateways_count = &cc_mqttsn_qos0_client_get_available_gateways_count; + funcs.m_init_gateway_info = &cc_mqttsn_qos0_client_init_gateway_info; + funcs.m_get_available_gateway_info = &cc_mqttsn_qos0_client_get_available_gateway_info; + funcs.m_set_available_gateway_info = &cc_mqttsn_qos0_client_set_available_gateway_info; + funcs.m_discard_available_gateway_info = &cc_mqttsn_qos0_client_discard_available_gateway_info; + funcs.m_discard_all_gateway_infos = &cc_mqttsn_qos0_client_discard_all_gateway_infos; + funcs.m_set_default_gw_adv_duration = &cc_mqttsn_qos0_client_set_default_gw_adv_duration; + funcs.m_get_default_gw_adv_duration = &cc_mqttsn_qos0_client_get_default_gw_adv_duration; + funcs.m_set_allowed_adv_losses = &cc_mqttsn_qos0_client_set_allowed_adv_losses; + funcs.m_get_allowed_adv_losses = &cc_mqttsn_qos0_client_get_allowed_adv_losses; + funcs.m_set_verify_outgoing_topic_enabled = &cc_mqttsn_qos0_client_set_verify_outgoing_topic_enabled; + funcs.m_get_verify_outgoing_topic_enabled = &cc_mqttsn_qos0_client_get_verify_outgoing_topic_enabled; + funcs.m_set_verify_incoming_topic_enabled = &cc_mqttsn_qos0_client_set_verify_incoming_topic_enabled; + funcs.m_get_verify_incoming_topic_enabled = &cc_mqttsn_qos0_client_get_verify_incoming_topic_enabled; + funcs.m_set_verify_incoming_msg_subscribed = &cc_mqttsn_qos0_client_set_verify_incoming_msg_subscribed; + funcs.m_get_verify_incoming_msg_subscribed = &cc_mqttsn_qos0_client_get_verify_incoming_msg_subscribed; + funcs.m_set_outgoing_topic_id_storage_limit = &cc_mqttsn_qos0_client_set_outgoing_topic_id_storage_limit; + funcs.m_get_outgoing_topic_id_storage_limit = &cc_mqttsn_qos0_client_get_outgoing_topic_id_storage_limit; + funcs.m_set_incoming_topic_id_storage_limit = &cc_mqttsn_qos0_client_set_incoming_topic_id_storage_limit; + funcs.m_get_incoming_topic_id_storage_limit = &cc_mqttsn_qos0_client_get_incoming_topic_id_storage_limit; + funcs.m_asleep_check_messages = &cc_mqttsn_qos0_client_asleep_check_messages; + funcs.m_search_prepare = &cc_mqttsn_qos0_client_search_prepare; + funcs.m_search_set_retry_period = &cc_mqttsn_qos0_client_search_set_retry_period; + funcs.m_search_get_retry_period = &cc_mqttsn_qos0_client_search_get_retry_period; + funcs.m_search_set_retry_count = &cc_mqttsn_qos0_client_search_set_retry_count; + funcs.m_search_get_retry_count = &cc_mqttsn_qos0_client_search_get_retry_count; + funcs.m_search_set_broadcast_radius = &cc_mqttsn_qos0_client_search_set_broadcast_radius; + funcs.m_search_get_broadcast_radius = &cc_mqttsn_qos0_client_search_get_broadcast_radius; + funcs.m_search_send = &cc_mqttsn_qos0_client_search_send; + funcs.m_search_cancel = &cc_mqttsn_qos0_client_search_cancel; + funcs.m_search = &cc_mqttsn_qos0_client_search; + funcs.m_connect_prepare = &cc_mqttsn_qos0_client_connect_prepare; + funcs.m_connect_set_retry_period = &cc_mqttsn_qos0_client_connect_set_retry_period; + funcs.m_connect_get_retry_period = &cc_mqttsn_qos0_client_connect_get_retry_period; + funcs.m_connect_set_retry_count = &cc_mqttsn_qos0_client_connect_set_retry_count; + funcs.m_connect_get_retry_count = &cc_mqttsn_qos0_client_connect_get_retry_count; + funcs.m_connect_init_config = &cc_mqttsn_qos0_client_connect_init_config; + funcs.m_connect_config = &cc_mqttsn_qos0_client_connect_config; + funcs.m_connect_init_config_will = &cc_mqttsn_qos0_client_connect_init_config_will; + funcs.m_connect_config_will = &cc_mqttsn_qos0_client_connect_config_will; + funcs.m_connect_send = &cc_mqttsn_qos0_client_connect_send; + funcs.m_connect_cancel = &cc_mqttsn_qos0_client_connect_cancel; + funcs.m_connect = &cc_mqttsn_qos0_client_connect; + funcs.m_get_connection_status = &cc_mqttsn_qos0_client_get_connection_status; + funcs.m_disconnect_prepare = &cc_mqttsn_qos0_client_disconnect_prepare; + funcs.m_disconnect_set_retry_period = &cc_mqttsn_qos0_client_disconnect_set_retry_period; + funcs.m_disconnect_get_retry_period = &cc_mqttsn_qos0_client_disconnect_get_retry_period; + funcs.m_disconnect_set_retry_count = &cc_mqttsn_qos0_client_disconnect_set_retry_count; + funcs.m_disconnect_get_retry_count = &cc_mqttsn_qos0_client_disconnect_get_retry_count; + funcs.m_disconnect_send = &cc_mqttsn_qos0_client_disconnect_send; + funcs.m_disconnect_cancel = &cc_mqttsn_qos0_client_disconnect_cancel; + funcs.m_disconnect = &cc_mqttsn_qos0_client_disconnect; + funcs.m_subscribe_prepare = &cc_mqttsn_qos0_client_subscribe_prepare; + funcs.m_subscribe_set_retry_period = &cc_mqttsn_qos0_client_subscribe_set_retry_period; + funcs.m_subscribe_get_retry_period = &cc_mqttsn_qos0_client_subscribe_get_retry_period; + funcs.m_subscribe_set_retry_count = &cc_mqttsn_qos0_client_subscribe_set_retry_count; + funcs.m_subscribe_get_retry_count = &cc_mqttsn_qos0_client_subscribe_get_retry_count; + funcs.m_subscribe_init_config = &cc_mqttsn_qos0_client_subscribe_init_config; + funcs.m_subscribe_config = &cc_mqttsn_qos0_client_subscribe_config; + funcs.m_subscribe_send = &cc_mqttsn_qos0_client_subscribe_send; + funcs.m_subscribe_cancel = &cc_mqttsn_qos0_client_subscribe_cancel; + funcs.m_subscribe = &cc_mqttsn_qos0_client_subscribe; + funcs.m_unsubscribe_prepare = &cc_mqttsn_qos0_client_unsubscribe_prepare; + funcs.m_unsubscribe_set_retry_period = &cc_mqttsn_qos0_client_unsubscribe_set_retry_period; + funcs.m_unsubscribe_get_retry_period = &cc_mqttsn_qos0_client_unsubscribe_get_retry_period; + funcs.m_unsubscribe_set_retry_count = &cc_mqttsn_qos0_client_unsubscribe_set_retry_count; + funcs.m_unsubscribe_get_retry_count = &cc_mqttsn_qos0_client_unsubscribe_get_retry_count; + funcs.m_unsubscribe_init_config = &cc_mqttsn_qos0_client_unsubscribe_init_config; + funcs.m_unsubscribe_config = &cc_mqttsn_qos0_client_unsubscribe_config; + funcs.m_unsubscribe_send = &cc_mqttsn_qos0_client_unsubscribe_send; + funcs.m_unsubscribe_cancel = &cc_mqttsn_qos0_client_unsubscribe_cancel; + funcs.m_unsubscribe = &cc_mqttsn_qos0_client_unsubscribe; + funcs.m_publish_prepare = &cc_mqttsn_qos0_client_publish_prepare; + funcs.m_publish_set_retry_period = &cc_mqttsn_qos0_client_publish_set_retry_period; + funcs.m_publish_get_retry_period = &cc_mqttsn_qos0_client_publish_get_retry_period; + funcs.m_publish_set_retry_count = &cc_mqttsn_qos0_client_publish_set_retry_count; + funcs.m_publish_get_retry_count = &cc_mqttsn_qos0_client_publish_get_retry_count; + funcs.m_publish_init_config = &cc_mqttsn_qos0_client_publish_init_config; + funcs.m_publish_config = &cc_mqttsn_qos0_client_publish_config; + funcs.m_publish_send = &cc_mqttsn_qos0_client_publish_send; + funcs.m_publish_cancel = &cc_mqttsn_qos0_client_publish_cancel; + funcs.m_publish = &cc_mqttsn_qos0_client_publish; + funcs.m_will_prepare = &cc_mqttsn_qos0_client_will_prepare; + funcs.m_will_set_retry_period = &cc_mqttsn_qos0_client_will_set_retry_period; + funcs.m_will_get_retry_period = &cc_mqttsn_qos0_client_will_get_retry_period; + funcs.m_will_set_retry_count = &cc_mqttsn_qos0_client_will_set_retry_count; + funcs.m_will_get_retry_count = &cc_mqttsn_qos0_client_will_get_retry_count; + funcs.m_will_init_config = &cc_mqttsn_qos0_client_will_init_config; + funcs.m_will_config = &cc_mqttsn_qos0_client_will_config; + funcs.m_will_send = &cc_mqttsn_qos0_client_will_send; + funcs.m_will_cancel = &cc_mqttsn_qos0_client_will_cancel; + funcs.m_will = &cc_mqttsn_qos0_client_will; + funcs.m_sleep_prepare = &cc_mqttsn_qos0_client_sleep_prepare; + funcs.m_sleep_set_retry_period = &cc_mqttsn_qos0_client_sleep_set_retry_period; + funcs.m_sleep_get_retry_period = &cc_mqttsn_qos0_client_sleep_get_retry_period; + funcs.m_sleep_set_retry_count = &cc_mqttsn_qos0_client_sleep_set_retry_count; + funcs.m_sleep_get_retry_count = &cc_mqttsn_qos0_client_sleep_get_retry_count; + funcs.m_sleep_init_config = &cc_mqttsn_qos0_client_sleep_init_config; + funcs.m_sleep_config = &cc_mqttsn_qos0_client_sleep_config; + funcs.m_sleep_send = &cc_mqttsn_qos0_client_sleep_send; + funcs.m_sleep_cancel = &cc_mqttsn_qos0_client_sleep_cancel; + funcs.m_sleep = &cc_mqttsn_qos0_client_sleep; + funcs.m_set_next_tick_program_callback = &cc_mqttsn_qos0_client_set_next_tick_program_callback; + funcs.m_set_cancel_next_tick_wait_callback = &cc_mqttsn_qos0_client_set_cancel_next_tick_wait_callback; + funcs.m_set_send_output_data_callback = &cc_mqttsn_qos0_client_set_send_output_data_callback; + funcs.m_set_gw_status_report_callback = &cc_mqttsn_qos0_client_set_gw_status_report_callback; + funcs.m_set_gw_disconnect_report_callback = &cc_mqttsn_qos0_client_set_gw_disconnect_report_callback; + funcs.m_set_message_report_callback = &cc_mqttsn_qos0_client_set_message_report_callback; + funcs.m_set_error_log_callback = &cc_mqttsn_qos0_client_set_error_log_callback; + funcs.m_set_gwinfo_delay_request_callback = &cc_mqttsn_qos0_client_set_gwinfo_delay_request_callback; + + return funcs; +} diff --git a/client/lib/test/qos0/UnitTestQos0Base.h b/client/lib/test/qos0/UnitTestQos0Base.h new file mode 100644 index 00000000..490ccb12 --- /dev/null +++ b/client/lib/test/qos0/UnitTestQos0Base.h @@ -0,0 +1,16 @@ +#pragma once + +#include "UnitTestCommonBase.h" + +class UnitTestQos0Base : public UnitTestCommonBase +{ + using Base = UnitTestCommonBase; +protected: + + UnitTestQos0Base(): + Base(getFuncs()) + { + } + + static const LibFuncs& getFuncs(); +}; \ No newline at end of file diff --git a/client/lib/test/qos1/UnitTestQos1Base.cpp b/client/lib/test/qos1/UnitTestQos1Base.cpp new file mode 100644 index 00000000..9f7cb370 --- /dev/null +++ b/client/lib/test/qos1/UnitTestQos1Base.cpp @@ -0,0 +1,130 @@ +#include "UnitTestQos1Base.h" + +#include "qos1_client.h" + +const UnitTestQos1Base::LibFuncs& UnitTestQos1Base::getFuncs() +{ + static LibFuncs funcs; + funcs.m_alloc = &cc_mqttsn_qos1_client_alloc; + funcs.m_free = &cc_mqttsn_qos1_client_free; + funcs.m_tick = &cc_mqttsn_qos1_client_tick; + funcs.m_process_data = &cc_mqttsn_qos1_client_process_data; + funcs.m_set_default_retry_period = &cc_mqttsn_qos1_client_set_default_retry_period; + funcs.m_get_default_retry_period = &cc_mqttsn_qos1_client_get_default_retry_period; + funcs.m_set_default_retry_count = &cc_mqttsn_qos1_client_set_default_retry_count; + funcs.m_get_default_retry_count = &cc_mqttsn_qos1_client_get_default_retry_count; + funcs.m_set_default_broadcast_radius = &cc_mqttsn_qos1_client_set_default_broadcast_radius; + funcs.m_get_default_broadcast_radius = &cc_mqttsn_qos1_client_get_default_broadcast_radius; + funcs.m_get_available_gateways_count = &cc_mqttsn_qos1_client_get_available_gateways_count; + funcs.m_init_gateway_info = &cc_mqttsn_qos1_client_init_gateway_info; + funcs.m_get_available_gateway_info = &cc_mqttsn_qos1_client_get_available_gateway_info; + funcs.m_set_available_gateway_info = &cc_mqttsn_qos1_client_set_available_gateway_info; + funcs.m_discard_available_gateway_info = &cc_mqttsn_qos1_client_discard_available_gateway_info; + funcs.m_discard_all_gateway_infos = &cc_mqttsn_qos1_client_discard_all_gateway_infos; + funcs.m_set_default_gw_adv_duration = &cc_mqttsn_qos1_client_set_default_gw_adv_duration; + funcs.m_get_default_gw_adv_duration = &cc_mqttsn_qos1_client_get_default_gw_adv_duration; + funcs.m_set_allowed_adv_losses = &cc_mqttsn_qos1_client_set_allowed_adv_losses; + funcs.m_get_allowed_adv_losses = &cc_mqttsn_qos1_client_get_allowed_adv_losses; + funcs.m_set_verify_outgoing_topic_enabled = &cc_mqttsn_qos1_client_set_verify_outgoing_topic_enabled; + funcs.m_get_verify_outgoing_topic_enabled = &cc_mqttsn_qos1_client_get_verify_outgoing_topic_enabled; + funcs.m_set_verify_incoming_topic_enabled = &cc_mqttsn_qos1_client_set_verify_incoming_topic_enabled; + funcs.m_get_verify_incoming_topic_enabled = &cc_mqttsn_qos1_client_get_verify_incoming_topic_enabled; + funcs.m_set_verify_incoming_msg_subscribed = &cc_mqttsn_qos1_client_set_verify_incoming_msg_subscribed; + funcs.m_get_verify_incoming_msg_subscribed = &cc_mqttsn_qos1_client_get_verify_incoming_msg_subscribed; + funcs.m_set_outgoing_topic_id_storage_limit = &cc_mqttsn_qos1_client_set_outgoing_topic_id_storage_limit; + funcs.m_get_outgoing_topic_id_storage_limit = &cc_mqttsn_qos1_client_get_outgoing_topic_id_storage_limit; + funcs.m_set_incoming_topic_id_storage_limit = &cc_mqttsn_qos1_client_set_incoming_topic_id_storage_limit; + funcs.m_get_incoming_topic_id_storage_limit = &cc_mqttsn_qos1_client_get_incoming_topic_id_storage_limit; + funcs.m_asleep_check_messages = &cc_mqttsn_qos1_client_asleep_check_messages; + funcs.m_search_prepare = &cc_mqttsn_qos1_client_search_prepare; + funcs.m_search_set_retry_period = &cc_mqttsn_qos1_client_search_set_retry_period; + funcs.m_search_get_retry_period = &cc_mqttsn_qos1_client_search_get_retry_period; + funcs.m_search_set_retry_count = &cc_mqttsn_qos1_client_search_set_retry_count; + funcs.m_search_get_retry_count = &cc_mqttsn_qos1_client_search_get_retry_count; + funcs.m_search_set_broadcast_radius = &cc_mqttsn_qos1_client_search_set_broadcast_radius; + funcs.m_search_get_broadcast_radius = &cc_mqttsn_qos1_client_search_get_broadcast_radius; + funcs.m_search_send = &cc_mqttsn_qos1_client_search_send; + funcs.m_search_cancel = &cc_mqttsn_qos1_client_search_cancel; + funcs.m_search = &cc_mqttsn_qos1_client_search; + funcs.m_connect_prepare = &cc_mqttsn_qos1_client_connect_prepare; + funcs.m_connect_set_retry_period = &cc_mqttsn_qos1_client_connect_set_retry_period; + funcs.m_connect_get_retry_period = &cc_mqttsn_qos1_client_connect_get_retry_period; + funcs.m_connect_set_retry_count = &cc_mqttsn_qos1_client_connect_set_retry_count; + funcs.m_connect_get_retry_count = &cc_mqttsn_qos1_client_connect_get_retry_count; + funcs.m_connect_init_config = &cc_mqttsn_qos1_client_connect_init_config; + funcs.m_connect_config = &cc_mqttsn_qos1_client_connect_config; + funcs.m_connect_init_config_will = &cc_mqttsn_qos1_client_connect_init_config_will; + funcs.m_connect_config_will = &cc_mqttsn_qos1_client_connect_config_will; + funcs.m_connect_send = &cc_mqttsn_qos1_client_connect_send; + funcs.m_connect_cancel = &cc_mqttsn_qos1_client_connect_cancel; + funcs.m_connect = &cc_mqttsn_qos1_client_connect; + funcs.m_get_connection_status = &cc_mqttsn_qos1_client_get_connection_status; + funcs.m_disconnect_prepare = &cc_mqttsn_qos1_client_disconnect_prepare; + funcs.m_disconnect_set_retry_period = &cc_mqttsn_qos1_client_disconnect_set_retry_period; + funcs.m_disconnect_get_retry_period = &cc_mqttsn_qos1_client_disconnect_get_retry_period; + funcs.m_disconnect_set_retry_count = &cc_mqttsn_qos1_client_disconnect_set_retry_count; + funcs.m_disconnect_get_retry_count = &cc_mqttsn_qos1_client_disconnect_get_retry_count; + funcs.m_disconnect_send = &cc_mqttsn_qos1_client_disconnect_send; + funcs.m_disconnect_cancel = &cc_mqttsn_qos1_client_disconnect_cancel; + funcs.m_disconnect = &cc_mqttsn_qos1_client_disconnect; + funcs.m_subscribe_prepare = &cc_mqttsn_qos1_client_subscribe_prepare; + funcs.m_subscribe_set_retry_period = &cc_mqttsn_qos1_client_subscribe_set_retry_period; + funcs.m_subscribe_get_retry_period = &cc_mqttsn_qos1_client_subscribe_get_retry_period; + funcs.m_subscribe_set_retry_count = &cc_mqttsn_qos1_client_subscribe_set_retry_count; + funcs.m_subscribe_get_retry_count = &cc_mqttsn_qos1_client_subscribe_get_retry_count; + funcs.m_subscribe_init_config = &cc_mqttsn_qos1_client_subscribe_init_config; + funcs.m_subscribe_config = &cc_mqttsn_qos1_client_subscribe_config; + funcs.m_subscribe_send = &cc_mqttsn_qos1_client_subscribe_send; + funcs.m_subscribe_cancel = &cc_mqttsn_qos1_client_subscribe_cancel; + funcs.m_subscribe = &cc_mqttsn_qos1_client_subscribe; + funcs.m_unsubscribe_prepare = &cc_mqttsn_qos1_client_unsubscribe_prepare; + funcs.m_unsubscribe_set_retry_period = &cc_mqttsn_qos1_client_unsubscribe_set_retry_period; + funcs.m_unsubscribe_get_retry_period = &cc_mqttsn_qos1_client_unsubscribe_get_retry_period; + funcs.m_unsubscribe_set_retry_count = &cc_mqttsn_qos1_client_unsubscribe_set_retry_count; + funcs.m_unsubscribe_get_retry_count = &cc_mqttsn_qos1_client_unsubscribe_get_retry_count; + funcs.m_unsubscribe_init_config = &cc_mqttsn_qos1_client_unsubscribe_init_config; + funcs.m_unsubscribe_config = &cc_mqttsn_qos1_client_unsubscribe_config; + funcs.m_unsubscribe_send = &cc_mqttsn_qos1_client_unsubscribe_send; + funcs.m_unsubscribe_cancel = &cc_mqttsn_qos1_client_unsubscribe_cancel; + funcs.m_unsubscribe = &cc_mqttsn_qos1_client_unsubscribe; + funcs.m_publish_prepare = &cc_mqttsn_qos1_client_publish_prepare; + funcs.m_publish_set_retry_period = &cc_mqttsn_qos1_client_publish_set_retry_period; + funcs.m_publish_get_retry_period = &cc_mqttsn_qos1_client_publish_get_retry_period; + funcs.m_publish_set_retry_count = &cc_mqttsn_qos1_client_publish_set_retry_count; + funcs.m_publish_get_retry_count = &cc_mqttsn_qos1_client_publish_get_retry_count; + funcs.m_publish_init_config = &cc_mqttsn_qos1_client_publish_init_config; + funcs.m_publish_config = &cc_mqttsn_qos1_client_publish_config; + funcs.m_publish_send = &cc_mqttsn_qos1_client_publish_send; + funcs.m_publish_cancel = &cc_mqttsn_qos1_client_publish_cancel; + funcs.m_publish = &cc_mqttsn_qos1_client_publish; + funcs.m_will_prepare = &cc_mqttsn_qos1_client_will_prepare; + funcs.m_will_set_retry_period = &cc_mqttsn_qos1_client_will_set_retry_period; + funcs.m_will_get_retry_period = &cc_mqttsn_qos1_client_will_get_retry_period; + funcs.m_will_set_retry_count = &cc_mqttsn_qos1_client_will_set_retry_count; + funcs.m_will_get_retry_count = &cc_mqttsn_qos1_client_will_get_retry_count; + funcs.m_will_init_config = &cc_mqttsn_qos1_client_will_init_config; + funcs.m_will_config = &cc_mqttsn_qos1_client_will_config; + funcs.m_will_send = &cc_mqttsn_qos1_client_will_send; + funcs.m_will_cancel = &cc_mqttsn_qos1_client_will_cancel; + funcs.m_will = &cc_mqttsn_qos1_client_will; + funcs.m_sleep_prepare = &cc_mqttsn_qos1_client_sleep_prepare; + funcs.m_sleep_set_retry_period = &cc_mqttsn_qos1_client_sleep_set_retry_period; + funcs.m_sleep_get_retry_period = &cc_mqttsn_qos1_client_sleep_get_retry_period; + funcs.m_sleep_set_retry_count = &cc_mqttsn_qos1_client_sleep_set_retry_count; + funcs.m_sleep_get_retry_count = &cc_mqttsn_qos1_client_sleep_get_retry_count; + funcs.m_sleep_init_config = &cc_mqttsn_qos1_client_sleep_init_config; + funcs.m_sleep_config = &cc_mqttsn_qos1_client_sleep_config; + funcs.m_sleep_send = &cc_mqttsn_qos1_client_sleep_send; + funcs.m_sleep_cancel = &cc_mqttsn_qos1_client_sleep_cancel; + funcs.m_sleep = &cc_mqttsn_qos1_client_sleep; + funcs.m_set_next_tick_program_callback = &cc_mqttsn_qos1_client_set_next_tick_program_callback; + funcs.m_set_cancel_next_tick_wait_callback = &cc_mqttsn_qos1_client_set_cancel_next_tick_wait_callback; + funcs.m_set_send_output_data_callback = &cc_mqttsn_qos1_client_set_send_output_data_callback; + funcs.m_set_gw_status_report_callback = &cc_mqttsn_qos1_client_set_gw_status_report_callback; + funcs.m_set_gw_disconnect_report_callback = &cc_mqttsn_qos1_client_set_gw_disconnect_report_callback; + funcs.m_set_message_report_callback = &cc_mqttsn_qos1_client_set_message_report_callback; + funcs.m_set_error_log_callback = &cc_mqttsn_qos1_client_set_error_log_callback; + funcs.m_set_gwinfo_delay_request_callback = &cc_mqttsn_qos1_client_set_gwinfo_delay_request_callback; + + return funcs; +} diff --git a/client/lib/test/qos1/UnitTestQos1Base.h b/client/lib/test/qos1/UnitTestQos1Base.h new file mode 100644 index 00000000..a074cf0b --- /dev/null +++ b/client/lib/test/qos1/UnitTestQos1Base.h @@ -0,0 +1,16 @@ +#pragma once + +#include "UnitTestCommonBase.h" + +class UnitTestQos1Base : public UnitTestCommonBase +{ + using Base = UnitTestCommonBase; +protected: + + UnitTestQos1Base(): + Base(getFuncs()) + { + } + + static const LibFuncs& getFuncs(); +}; \ No newline at end of file diff --git a/script/full_debug_build.sh b/script/full_debug_build.sh index 93fa3baa..fff8400e 100755 --- a/script/full_debug_build.sh +++ b/script/full_debug_build.sh @@ -16,7 +16,7 @@ mkdir -p ${BUILD_DIR} ${SCRIPT_DIR}/prepare_externals.sh CONFIGS_DIR="${ROOT_DIR}/client/lib/script" -CONFIGS="${CONFIGS_DIR}/BareMetalTestConfig.cmake;${CONFIGS_DIR}/NoGwDiscoverConfig.cmake" +CONFIGS="${CONFIGS_DIR}/BareMetalTestConfig.cmake;${CONFIGS_DIR}/NoGwDiscoverConfig.cmake;${CONFIGS_DIR}/Qos1Config.cmake;${CONFIGS_DIR}/Qos0Config.cmake" cd ${BUILD_DIR} cmake .. -DCMAKE_INSTALL_PREFIX=${COMMON_INSTALL_DIR} -DCMAKE_BUILD_TYPE=Debug \ From ec777dd7a569fc916fa37a1adfe9055529212cd2 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 14 Aug 2024 10:14:45 +1000 Subject: [PATCH 098/106] More client unit-testing. --- client/lib/test/CMakeLists.txt | 21 ++-- client/lib/test/UnitTestCommonBase.cpp | 27 ++-- client/lib/test/UnitTestCommonBase.h | 1 + client/lib/test/bm/UnitTestBmClient.th | 31 +++++ client/lib/test/bm/UnitTestBmConnect.th | 32 +++++ client/lib/test/bm/UnitTestBmPublish.th | 62 ++++++++++ client/lib/test/no_gw/UnitTestNoGwDiscover.th | 72 +++++++++++ client/lib/test/qos0/UnitTestQos0Publish.th | 68 +++++++++++ client/lib/test/qos0/UnitTestQos0Receive.th | 115 ++++++++++++++++++ client/lib/test/qos0/UnitTestQos0Subscribe.th | 59 +++++++++ client/lib/test/qos1/UnitTestQos1Publish.th | 59 +++++++++ client/lib/test/qos1/UnitTestQos1Receive.th | 69 +++++++++++ client/lib/test/qos1/UnitTestQos1Subscribe.th | 55 +++++++++ 13 files changed, 651 insertions(+), 20 deletions(-) create mode 100644 client/lib/test/bm/UnitTestBmClient.th create mode 100644 client/lib/test/bm/UnitTestBmConnect.th create mode 100644 client/lib/test/bm/UnitTestBmPublish.th create mode 100644 client/lib/test/no_gw/UnitTestNoGwDiscover.th create mode 100644 client/lib/test/qos0/UnitTestQos0Publish.th create mode 100644 client/lib/test/qos0/UnitTestQos0Receive.th create mode 100644 client/lib/test/qos0/UnitTestQos0Subscribe.th create mode 100644 client/lib/test/qos1/UnitTestQos1Publish.th create mode 100644 client/lib/test/qos1/UnitTestQos1Receive.th create mode 100644 client/lib/test/qos1/UnitTestQos1Subscribe.th diff --git a/client/lib/test/CMakeLists.txt b/client/lib/test/CMakeLists.txt index 9082a36b..511fc12a 100644 --- a/client/lib/test/CMakeLists.txt +++ b/client/lib/test/CMakeLists.txt @@ -62,10 +62,9 @@ if (TARGET cc::cc_mqttsn_bm_client) $ ) -# cc_mqttsn_client_add_unit_test(UnitTestBmClient ${BM_BASE_LIB_NAME}) -# cc_mqttsn_client_add_unit_test(UnitTestBmConnect ${BM_BASE_LIB_NAME}) -# cc_mqttsn_client_add_unit_test(UnitTestBmPublish ${BM_BASE_LIB_NAME}) -# cc_mqttsn_client_add_unit_test(UnitTestBmReceive ${BM_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(bm/UnitTestBmClient.th ${BM_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(bm/UnitTestBmConnect.th ${BM_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(bm/UnitTestBmPublish.th ${BM_BASE_LIB_NAME}) endif () @@ -81,9 +80,9 @@ if (TARGET cc::cc_mqttsn_qos1_client) $ ) -# cc_mqttsn_client_add_unit_test(UnitTestQos1Publish ${QOS1_BASE_LIB_NAME}) -# cc_mqttsn_client_add_unit_test(UnitTestQos1Receive ${QOS1_BASE_LIB_NAME}) -# cc_mqttsn_client_add_unit_test(UnitTestQos1Subscribe ${QOS1_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(qos1/UnitTestQos1Publish.th ${QOS1_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(qos1/UnitTestQos1Receive.th ${QOS1_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(qos1/UnitTestQos1Subscribe.th ${QOS1_BASE_LIB_NAME}) endif () if (TARGET cc::cc_mqttsn_qos0_client) @@ -98,9 +97,9 @@ if (TARGET cc::cc_mqttsn_qos0_client) $ ) -# cc_mqttsn_client_add_unit_test(UnitTestQos0Publish ${QOS0_BASE_LIB_NAME}) -# cc_mqttsn_client_add_unit_test(UnitTestQos0Receive ${QOS0_BASE_LIB_NAME}) -# cc_mqttsn_client_add_unit_test(UnitTestQos0Subscribe ${QOS0_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(qos0/UnitTestQos0Publish.th ${QOS0_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(qos0/UnitTestQos0Receive.th ${QOS0_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(qos0/UnitTestQos0Subscribe.th ${QOS0_BASE_LIB_NAME}) endif () if (TARGET cc::cc_mqttsn_no_gw_client) @@ -115,5 +114,5 @@ if (TARGET cc::cc_mqttsn_no_gw_client) $ ) - + cc_mqttsn_client_add_unit_test(no_gw/UnitTestNoGwDiscover.th ${NO_GW_BASE_LIB_NAME}) endif () diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index 2cc19672..1344e32f 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -279,19 +279,28 @@ void UnitTestCommonBase::unitTestTearDown() UnitTestCommonBase::UnitTestClientPtr UnitTestCommonBase::unitTestAllocClient(bool enableLog) { UnitTestClientPtr client(m_funcs.m_alloc(), UnitTestDeleter(m_funcs)); - m_funcs.m_set_next_tick_program_callback(client.get(), &UnitTestCommonBase::unitTestTickProgramCb, this); - m_funcs.m_set_cancel_next_tick_wait_callback(client.get(), &UnitTestCommonBase::unitTestCancelTickWaitCb, this); - m_funcs.m_set_send_output_data_callback(client.get(), &UnitTestCommonBase::unitTestSendOutputDataCb, this); - m_funcs.m_set_gw_status_report_callback(client.get(), &UnitTestCommonBase::unitTestGwStatusReportCb, this); - m_funcs.m_set_gw_disconnect_report_callback(client.get(), &UnitTestCommonBase::unitTestGwDisconnectReportCb, this); - m_funcs.m_set_message_report_callback(client.get(), &UnitTestCommonBase::unitTestMessageReportCb, this); - m_funcs.m_set_gwinfo_delay_request_callback(client.get(), &UnitTestCommonBase::unitTestGwinfoDelayRequestCb, this); + if (client) { + unitTestAssignCallbacks(client.get(), enableLog); + } + + return client; +} + +void UnitTestCommonBase::unitTestAssignCallbacks(CC_MqttsnClient* client, bool enableLog) +{ + test_assert(client != nullptr); + m_funcs.m_set_next_tick_program_callback(client, &UnitTestCommonBase::unitTestTickProgramCb, this); + m_funcs.m_set_cancel_next_tick_wait_callback(client, &UnitTestCommonBase::unitTestCancelTickWaitCb, this); + m_funcs.m_set_send_output_data_callback(client, &UnitTestCommonBase::unitTestSendOutputDataCb, this); + m_funcs.m_set_gw_status_report_callback(client, &UnitTestCommonBase::unitTestGwStatusReportCb, this); + m_funcs.m_set_gw_disconnect_report_callback(client, &UnitTestCommonBase::unitTestGwDisconnectReportCb, this); + m_funcs.m_set_message_report_callback(client, &UnitTestCommonBase::unitTestMessageReportCb, this); + m_funcs.m_set_gwinfo_delay_request_callback(client, &UnitTestCommonBase::unitTestGwinfoDelayRequestCb, this); if (enableLog) { - m_funcs.m_set_error_log_callback(client.get(), &UnitTestCommonBase::unitTestErrorLogCb, this); + m_funcs.m_set_error_log_callback(client, &UnitTestCommonBase::unitTestErrorLogCb, this); } - return client; } void UnitTestCommonBase::unitTestClientInputData(CC_MqttsnClient* client, const UnitTestData& data, CC_MqttsnDataOrigin origin) diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index 1adbb055..d8cbbd68 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -375,6 +375,7 @@ class UnitTestCommonBase void unitTestTearDown(); UnitTestClientPtr unitTestAllocClient(bool enableLog = false); + void unitTestAssignCallbacks(CC_MqttsnClient* client, bool enableLog = false); void unitTestClientInputData(CC_MqttsnClient* client, const UnitTestData& data, CC_MqttsnDataOrigin origin); void unitTestClientInputMessage(CC_MqttsnClient* client, const UnitTestMessage& msg, CC_MqttsnDataOrigin origin = CC_MqttsnDataOrigin_ConnectedGw); void unitTestPushSearchgwResponseDelay(unsigned val); diff --git a/client/lib/test/bm/UnitTestBmClient.th b/client/lib/test/bm/UnitTestBmClient.th new file mode 100644 index 00000000..2738f095 --- /dev/null +++ b/client/lib/test/bm/UnitTestBmClient.th @@ -0,0 +1,31 @@ +#include "UnitTestBmBase.h" +#include "UnitTestProtocolDefs.h" + +#include + +class UnitTestBmClient : public CxxTest::TestSuite, public UnitTestBmBase +{ +public: + void test1(); + +private: + virtual void setUp() override + { + unitTestSetUp(); + } + + virtual void tearDown() override + { + unitTestTearDown(); + } +}; + +void UnitTestBmClient::test1() +{ + // Testing allocation + auto client1 = unitTestAllocClient(); + TS_ASSERT(client1); + + auto client2 = unitTestAllocClient(); + TS_ASSERT(!client2); +} diff --git a/client/lib/test/bm/UnitTestBmConnect.th b/client/lib/test/bm/UnitTestBmConnect.th new file mode 100644 index 00000000..08cc13eb --- /dev/null +++ b/client/lib/test/bm/UnitTestBmConnect.th @@ -0,0 +1,32 @@ +#include "UnitTestBmBase.h" +#include "UnitTestProtocolDefs.h" + +#include + +class UnitTestBmConnect : public CxxTest::TestSuite, public UnitTestBmBase +{ +public: + void test1(); + +private: + virtual void setUp() override + { + unitTestSetUp(); + } + + virtual void tearDown() override + { + unitTestTearDown(); + } +}; + +void UnitTestBmConnect::test1() +{ + // Simple connect and ack + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + TS_ASSERT_DIFFERS(client, nullptr); + unitTestDoConnectBasic(client, __FUNCTION__); +} + diff --git a/client/lib/test/bm/UnitTestBmPublish.th b/client/lib/test/bm/UnitTestBmPublish.th new file mode 100644 index 00000000..a3076169 --- /dev/null +++ b/client/lib/test/bm/UnitTestBmPublish.th @@ -0,0 +1,62 @@ +#include "UnitTestBmBase.h" +#include "UnitTestProtocolDefs.h" + +#include + +class UnitTestBmPublish : public CxxTest::TestSuite, public UnitTestBmBase +{ +public: + void test1(); + +private: + virtual void setUp() override + { + unitTestSetUp(); + } + + virtual void tearDown() override + { + unitTestTearDown(); + } +}; + +void UnitTestBmPublish::test1() +{ + // Qos0 publish + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + TS_ASSERT_DIFFERS(client, nullptr); + unitTestDoConnectBasic(client, __FUNCTION__); + + const CC_MqttsnTopicId TopicId = 123; + const UnitTestData Data = { 0x1, 0x2, 0x3, 0x4, 0x5}; + const CC_MqttsnQoS Qos = CC_MqttsnQoS_AtMostOnceDelivery; + const bool Retain = true; + + CC_MqttsnPublishConfig config; + apiPublishInitConfig(&config); + + config.m_topicId = TopicId; + config.m_data = &Data[0]; + config.m_dataLen = static_cast(Data.size()); + config.m_qos = Qos; + config.m_retain = Retain; + + auto* publish = apiPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish, nullptr); + + auto ec = apiPublishConfig(publish, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto report = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(report->m_status, CC_MqttsnAsyncOpStatus_Complete); + + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + TS_ASSERT(sentMsg); +} diff --git a/client/lib/test/no_gw/UnitTestNoGwDiscover.th b/client/lib/test/no_gw/UnitTestNoGwDiscover.th new file mode 100644 index 00000000..4693d097 --- /dev/null +++ b/client/lib/test/no_gw/UnitTestNoGwDiscover.th @@ -0,0 +1,72 @@ +#include "UnitTestNoGwBase.h" +#include "UnitTestProtocolDefs.h" + +#include "comms/units.h" + +#include + +class UnitTestGwDiscover : public CxxTest::TestSuite, public UnitTestNoGwBase +{ +public: + void test1(); + void test2(); + void test3(); + +private: + virtual void setUp() override + { + unitTestSetUp(); + } + + virtual void tearDown() override + { + unitTestTearDown(); + } +}; + +void UnitTestGwDiscover::test1() +{ + // Testing ingore of the ADVERTISE message + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const std::uint8_t GwId = 1U; + const unsigned AdvDurationMin = 10U; + + UnitTestAdvertiseMsg advertiseMsg; + advertiseMsg.field_gwId().setValue(GwId); + comms::units::setMinutes(advertiseMsg.field_duration(), AdvDurationMin); + unitTestClientInputMessage(client, advertiseMsg, CC_MqttsnDataOrigin_Any); + TS_ASSERT(!unitTestHasGwInfoReport()); +} + +void UnitTestGwDiscover::test2() +{ + // Testing inability to search + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + auto ec = CC_MqttsnErrorCode_ValuesLimit; + auto search = apiSearchPrepare(client, &ec); + TS_ASSERT_EQUALS(search, nullptr); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_NotSupported); +} + +void UnitTestGwDiscover::test3() +{ + // Testing ingore of the GWINFO message + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const std::uint8_t GwId = 1U; + const UnitTestData Addr = {0, 1, 2, 3}; + UnitTestGwinfoMsg gwinfoMsg; + gwinfoMsg.field_gwId().setValue(GwId); + comms::util::assign(gwinfoMsg.field_gwAdd().value(), Addr.begin(), Addr.end()); + unitTestClientInputMessage(client, gwinfoMsg, CC_MqttsnDataOrigin_Any); + + TS_ASSERT(!unitTestHasGwInfoReport()); +} diff --git a/client/lib/test/qos0/UnitTestQos0Publish.th b/client/lib/test/qos0/UnitTestQos0Publish.th new file mode 100644 index 00000000..b6b90885 --- /dev/null +++ b/client/lib/test/qos0/UnitTestQos0Publish.th @@ -0,0 +1,68 @@ +#include "UnitTestQos0Base.h" +#include "UnitTestProtocolDefs.h" + +#include + +class UnitTestQos0Publish : public CxxTest::TestSuite, public UnitTestQos0Base +{ +public: + void test1(); + +private: + virtual void setUp() override + { + unitTestSetUp(); + } + + virtual void tearDown() override + { + unitTestTearDown(); + } +}; + +void UnitTestQos0Publish::test1() +{ + // Rejecting Qos1 and Qos2 configuration + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + TS_ASSERT_DIFFERS(client, nullptr); + unitTestDoConnectBasic(client, __FUNCTION__); + + const CC_MqttsnTopicId TopicId = 123; + const UnitTestData Data = { 0x1, 0x2, 0x3, 0x4, 0x5}; + + CC_MqttsnPublishConfig config; + apiPublishInitConfig(&config); + + config.m_topicId = TopicId; + config.m_data = &Data[0]; + config.m_dataLen = static_cast(Data.size()); + config.m_qos = CC_MqttsnQoS_ExactlyOnceDelivery; + + auto* publish = apiPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish, nullptr); + + auto ec = apiPublishConfig(publish, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_BadParam); + + config.m_qos = CC_MqttsnQoS_AtLeastOnceDelivery; + ec = apiPublishConfig(publish, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_BadParam); + + + config.m_qos = CC_MqttsnQoS_AtMostOnceDelivery; + ec = apiPublishConfig(publish, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + TS_ASSERT(unitTestHasPublishCompleteReport()); + auto report = unitTestPublishCompleteReport(); + TS_ASSERT_EQUALS(report->m_status, CC_MqttsnAsyncOpStatus_Complete); + + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + TS_ASSERT(sentMsg); +} diff --git a/client/lib/test/qos0/UnitTestQos0Receive.th b/client/lib/test/qos0/UnitTestQos0Receive.th new file mode 100644 index 00000000..7ab71b63 --- /dev/null +++ b/client/lib/test/qos0/UnitTestQos0Receive.th @@ -0,0 +1,115 @@ +#include "UnitTestQos0Base.h" +#include "UnitTestProtocolDefs.h" + +#include + +class UnitTestQos0Receive : public CxxTest::TestSuite, public UnitTestQos0Base +{ +public: + void test1(); + void test2(); + +private: + virtual void setUp() override + { + unitTestSetUp(); + } + + virtual void tearDown() override + { + unitTestTearDown(); + } + + using TopicIdType = UnitTestPublishMsg::Field_flags::Field_topicIdType::ValueType; + using RetCode = UnitTestPubackMsg::Field_returnCode::ValueType; +}; + +void UnitTestQos0Receive::test1() +{ + // Rejecting Qos2 messages + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + TS_ASSERT_DIFFERS(client, nullptr); + unitTestDoConnectBasic(client, __FUNCTION__); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const CC_MqttsnTopicId TopicId = 123; + const UnitTestData Data = { 0x1, 0x2, 0x3, 0x4, 0x5}; + const std::uint16_t MsgId = 1U; + + unitTestDoSubscribeTopicId(client, TopicId, CC_MqttsnQoS_AtMostOnceDelivery); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + UnitTestPublishMsg publishMsg; + publishMsg.field_flags().field_qos().setValue(CC_MqttsnQoS_ExactlyOnceDelivery); + publishMsg.field_flags().field_topicIdType().value() = TopicIdType::PredefinedTopicId; + publishMsg.field_topicId().setValue(TopicId); + publishMsg.field_msgId().setValue(MsgId); + publishMsg.field_data().value() = Data; + + unitTestClientInputMessage(client, publishMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pubackMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubackMsg, nullptr); + TS_ASSERT_EQUALS(pubackMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(pubackMsg->field_msgId().value(), MsgId); + TS_ASSERT_EQUALS(pubackMsg->field_returnCode().value(), RetCode::NotSupported); + } + + TS_ASSERT(!unitTestHasReceivedMessage()); +} + +void UnitTestQos0Receive::test2() +{ + // Rejecting Qos1 messages + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + TS_ASSERT_DIFFERS(client, nullptr); + unitTestDoConnectBasic(client, __FUNCTION__); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const CC_MqttsnTopicId TopicId = 123; + const UnitTestData Data = { 0x1, 0x2, 0x3, 0x4, 0x5}; + const std::uint16_t MsgId = 1U; + + unitTestDoSubscribeTopicId(client, TopicId, CC_MqttsnQoS_AtMostOnceDelivery); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + UnitTestPublishMsg publishMsg; + publishMsg.field_flags().field_qos().setValue(CC_MqttsnQoS_AtLeastOnceDelivery); + publishMsg.field_flags().field_topicIdType().value() = TopicIdType::PredefinedTopicId; + publishMsg.field_topicId().setValue(TopicId); + publishMsg.field_msgId().setValue(MsgId); + publishMsg.field_data().value() = Data; + + unitTestClientInputMessage(client, publishMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pubackMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubackMsg, nullptr); + TS_ASSERT_EQUALS(pubackMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(pubackMsg->field_msgId().value(), MsgId); + TS_ASSERT_EQUALS(pubackMsg->field_returnCode().value(), RetCode::NotSupported); + } + + TS_ASSERT(!unitTestHasReceivedMessage()); +} diff --git a/client/lib/test/qos0/UnitTestQos0Subscribe.th b/client/lib/test/qos0/UnitTestQos0Subscribe.th new file mode 100644 index 00000000..a851108b --- /dev/null +++ b/client/lib/test/qos0/UnitTestQos0Subscribe.th @@ -0,0 +1,59 @@ +#include "UnitTestQos0Base.h" +#include "UnitTestProtocolDefs.h" + +#include + +class UnitTestQos0Subscribe : public CxxTest::TestSuite, public UnitTestQos0Base +{ +public: + void test1(); + +private: + virtual void setUp() override + { + unitTestSetUp(); + } + + virtual void tearDown() override + { + unitTestTearDown(); + } +}; + +void UnitTestQos0Subscribe::test1() +{ + // Rejection QoS1 and QoS2 subscription attempt + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + TS_ASSERT_DIFFERS(client, nullptr); + unitTestDoConnectBasic(client, __FUNCTION__); + + CC_MqttsnSubscribeConfig subscribeConfig; + apiSubscribeInitConfig(&subscribeConfig); + subscribeConfig.m_topic = "#"; + subscribeConfig.m_qos = CC_MqttsnQoS_ExactlyOnceDelivery; + + auto* subscribe = apiSubscribePrepare(client, nullptr); + TS_ASSERT_DIFFERS(subscribe, nullptr); + + auto ec = apiSubscribeConfig(subscribe, &subscribeConfig); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_BadParam); + + subscribeConfig.m_qos = CC_MqttsnQoS_AtLeastOnceDelivery; + ec = apiSubscribeConfig(subscribe, &subscribeConfig); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_BadParam); + + subscribeConfig.m_qos = CC_MqttsnQoS_AtMostOnceDelivery; + ec = apiSubscribeConfig(subscribe, &subscribeConfig); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestSubscribeSend(subscribe); + TS_ASSERT(!unitTestHasSubscribeCompleteReport()); + + auto sentMsg = unitTestPopOutputMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqttsn::MsgId_Subscribe); + auto* subscribeMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(subscribeMsg, nullptr); +} \ No newline at end of file diff --git a/client/lib/test/qos1/UnitTestQos1Publish.th b/client/lib/test/qos1/UnitTestQos1Publish.th new file mode 100644 index 00000000..dc7709ab --- /dev/null +++ b/client/lib/test/qos1/UnitTestQos1Publish.th @@ -0,0 +1,59 @@ +#include "UnitTestQos1Base.h" +#include "UnitTestProtocolDefs.h" + +#include + +class UnitTestQos1Publish : public CxxTest::TestSuite, public UnitTestQos1Base +{ +public: + void test1(); + +private: + virtual void setUp() override + { + unitTestSetUp(); + } + + virtual void tearDown() override + { + unitTestTearDown(); + } +}; + +void UnitTestQos1Publish::test1() +{ + // Rejecting Qos2 configuration + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + TS_ASSERT_DIFFERS(client, nullptr); + unitTestDoConnectBasic(client, __FUNCTION__); + + const CC_MqttsnTopicId TopicId = 123; + const UnitTestData Data = { 0x1, 0x2, 0x3, 0x4, 0x5}; + + CC_MqttsnPublishConfig config; + apiPublishInitConfig(&config); + + config.m_topicId = TopicId; + config.m_data = &Data[0]; + config.m_dataLen = static_cast(Data.size()); + config.m_qos = CC_MqttsnQoS_ExactlyOnceDelivery; + + auto* publish = apiPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish, nullptr); + + auto ec = apiPublishConfig(publish, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_BadParam); + + config.m_qos = CC_MqttsnQoS_AtLeastOnceDelivery; + ec = apiPublishConfig(publish, &config); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestPublishSend(publish); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + TS_ASSERT(sentMsg); +} diff --git a/client/lib/test/qos1/UnitTestQos1Receive.th b/client/lib/test/qos1/UnitTestQos1Receive.th new file mode 100644 index 00000000..2b75a4a0 --- /dev/null +++ b/client/lib/test/qos1/UnitTestQos1Receive.th @@ -0,0 +1,69 @@ +#include "UnitTestQos1Base.h" +#include "UnitTestProtocolDefs.h" + +#include + +class UnitTestQos1Receive : public CxxTest::TestSuite, public UnitTestQos1Base +{ +public: + void test1(); + +private: + virtual void setUp() override + { + unitTestSetUp(); + } + + virtual void tearDown() override + { + unitTestTearDown(); + } + + using TopicIdType = UnitTestPublishMsg::Field_flags::Field_topicIdType::ValueType; + using RetCode = UnitTestPubackMsg::Field_returnCode::ValueType; +}; + +void UnitTestQos1Receive::test1() +{ + // Rejecting Qos2 messages + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + TS_ASSERT_DIFFERS(client, nullptr); + unitTestDoConnectBasic(client, __FUNCTION__); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + const CC_MqttsnTopicId TopicId = 123; + const UnitTestData Data = { 0x1, 0x2, 0x3, 0x4, 0x5}; + const std::uint16_t MsgId = 1U; + + unitTestDoSubscribeTopicId(client, TopicId, CC_MqttsnQoS_AtLeastOnceDelivery); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + { + UnitTestPublishMsg publishMsg; + publishMsg.field_flags().field_qos().setValue(CC_MqttsnQoS_ExactlyOnceDelivery); + publishMsg.field_flags().field_topicIdType().value() = TopicIdType::PredefinedTopicId; + publishMsg.field_topicId().setValue(TopicId); + publishMsg.field_msgId().setValue(MsgId); + publishMsg.field_data().value() = Data; + + unitTestClientInputMessage(client, publishMsg); + } + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* pubackMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubackMsg, nullptr); + TS_ASSERT_EQUALS(pubackMsg->field_topicId().value(), TopicId); + TS_ASSERT_EQUALS(pubackMsg->field_msgId().value(), MsgId); + TS_ASSERT_EQUALS(pubackMsg->field_returnCode().value(), RetCode::NotSupported); + } + + TS_ASSERT(!unitTestHasReceivedMessage()); +} diff --git a/client/lib/test/qos1/UnitTestQos1Subscribe.th b/client/lib/test/qos1/UnitTestQos1Subscribe.th new file mode 100644 index 00000000..cd60d372 --- /dev/null +++ b/client/lib/test/qos1/UnitTestQos1Subscribe.th @@ -0,0 +1,55 @@ +#include "UnitTestQos1Base.h" +#include "UnitTestProtocolDefs.h" + +#include + +class UnitTestQos1Subscribe : public CxxTest::TestSuite, public UnitTestQos1Base +{ +public: + void test1(); + +private: + virtual void setUp() override + { + unitTestSetUp(); + } + + virtual void tearDown() override + { + unitTestTearDown(); + } +}; + +void UnitTestQos1Subscribe::test1() +{ + // Rejection QoS2 subscription attempt + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + TS_ASSERT_DIFFERS(client, nullptr); + unitTestDoConnectBasic(client, __FUNCTION__); + + CC_MqttsnSubscribeConfig subscribeConfig; + apiSubscribeInitConfig(&subscribeConfig); + subscribeConfig.m_topic = "#"; + subscribeConfig.m_qos = CC_MqttsnQoS_ExactlyOnceDelivery; + + auto* subscribe = apiSubscribePrepare(client, nullptr); + TS_ASSERT_DIFFERS(subscribe, nullptr); + + auto ec = apiSubscribeConfig(subscribe, &subscribeConfig); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_BadParam); + + subscribeConfig.m_qos = CC_MqttsnQoS_AtLeastOnceDelivery; + ec = apiSubscribeConfig(subscribe, &subscribeConfig); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestSubscribeSend(subscribe); + TS_ASSERT(!unitTestHasSubscribeCompleteReport()); + + auto sentMsg = unitTestPopOutputMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqttsn::MsgId_Subscribe); + auto* subscribeMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(subscribeMsg, nullptr); +} \ No newline at end of file From e93eac789b1c1ead488886a6bfbd988cb360fd3c Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 14 Aug 2024 13:55:22 +1000 Subject: [PATCH 099/106] Added publish client application. --- client/app/CMakeLists.txt | 2 +- client/app/common/AppClient.cpp | 120 ++++++++++++++++--- client/app/common/AppClient.h | 12 +- client/app/common/ProgramOptions.cpp | 96 ++++++++++++++- client/app/common/ProgramOptions.h | 20 ++++ client/app/common/Session.cpp | 4 +- client/app/common/Session.h | 6 +- client/app/common/UdpSession.cpp | 7 +- client/app/pub/CMakeLists.txt | 13 ++ client/app/pub/Pub.cpp | 113 +++++++++++++++++ client/app/pub/Pub.h | 33 +++++ client/app/pub/main.cpp | 51 ++++++++ client/lib/include/cc_mqttsn_client/common.h | 4 +- client/lib/test/UnitTestCommonBase.cpp | 10 +- 14 files changed, 460 insertions(+), 31 deletions(-) create mode 100644 client/app/pub/CMakeLists.txt create mode 100644 client/app/pub/Pub.cpp create mode 100644 client/app/pub/Pub.h create mode 100644 client/app/pub/main.cpp diff --git a/client/app/CMakeLists.txt b/client/app/CMakeLists.txt index f022edb2..f3fa8d32 100644 --- a/client/app/CMakeLists.txt +++ b/client/app/CMakeLists.txt @@ -20,5 +20,5 @@ set (COMMON_APPS_LIB "cc_mqttsn_client_apps_lib") add_subdirectory (common) add_subdirectory (gw_discover) -#add_subdirectory (pub) +add_subdirectory (pub) #add_subdirectory (sub) \ No newline at end of file diff --git a/client/app/common/AppClient.cpp b/client/app/common/AppClient.cpp index 68a1c885..01f90bf7 100644 --- a/client/app/common/AppClient.cpp +++ b/client/app/common/AppClient.cpp @@ -86,6 +86,13 @@ bool AppClient::start(int argc, const char* argv[]) return false; } + if (m_opts.connectNoCleanSession()) { + auto ec = cc_mqttsn_client_set_verify_incoming_msg_subscribed(m_client.get(), false); + if (ec != CC_MqttsnErrorCode_Success) { + logError() << "Failed to disable incoming message subscribed verification" << std::endl; + } + } + return startImpl(); } @@ -187,6 +194,11 @@ std::ostream& AppClient::logError() return std::cerr << "ERROR: "; } +std::ostream& AppClient::logInfo() +{ + return std::cout << "INFO: "; +} + void AppClient::doTerminate(int result) { m_result = result; @@ -195,15 +207,6 @@ void AppClient::doTerminate(int result) void AppClient::doComplete() { - // if (m_opts.willTopic().empty()) { - // auto ec = ::cc_mqttsn_client_disconnect(m_client.get()); - // if (ec != CC_MqttsnErrorCode_Success) { - // logError() << "Failed to send disconnect with ec=" << toString(ec) << std::endl; - // doTerminate(); - // return; - // } - // } - boost::asio::post( m_io, [this]() @@ -212,6 +215,45 @@ void AppClient::doComplete() }); } +bool AppClient::doConnect() +{ + auto config = CC_MqttsnConnectConfig(); + cc_mqttsn_client_connect_init_config(&config); + + auto clientId = m_opts.connectClientId(); + if (!clientId.empty()) { + config.m_clientId = clientId.c_str(); + } + + config.m_duration = m_opts.connectKeepAlive(); + config.m_cleanSession = !m_opts.connectNoCleanSession(); + + auto willConfig = CC_MqttsnWillConfig(); + CC_MqttsnWillConfig* willConfigPtr = nullptr; + auto willTopic = m_opts.willTopic(); + auto willData = parseBinaryData(m_opts.willMessage()); + if (!willTopic.empty()) { + cc_mqttsn_client_connect_init_config_will(&willConfig); + willConfig.m_topic = willTopic.c_str(); + willConfig.m_data = willData.data(); + willConfig.m_dataLen = static_cast(willData.size()); + willConfig.m_qos = static_cast(m_opts.willQos()); + willConfigPtr = &willConfig; + } + + if (m_opts.verbose()) { + logInfo() << "Attempting connection" << std::endl; + } + + auto ec = cc_mqttsn_client_connect(m_client.get(), &config, willConfigPtr, &AppClient::connectCompleteCb, this); + if (ec != CC_MqttsnErrorCode_Success) { + logError() << "Failed to initiate connection to the gateway" << std::endl; + return false; + } + + return true; +} + bool AppClient::startImpl() { return true; @@ -222,6 +264,10 @@ void AppClient::messageReceivedImpl([[maybe_unused]] const CC_MqttsnMessageInfo* { } +void AppClient::connectCompleteImpl() +{ +} + std::vector AppClient::parseBinaryData(const std::string& val) { std::vector result; @@ -308,6 +354,28 @@ std::string AppClient::toString(CC_MqttsnAsyncOpStatus val) return Map[idx] + " (" + std::to_string(val) + ')'; } + +std::string AppClient::toString(CC_MqttsnReturnCode val) +{ + static const std::string Map[] = { + /* CC_MqttsnReturnCode_Accepted */ "Accepted", + /* CC_MqttsnReturnCode_Conjestion */ "Conjestion", + /* CC_MqttsnReturnCode_InvalidTopicId */ "Invalid Topic ID", + /* CC_MqttsnReturnCode_NotSupported */ "Not supported", + }; + + static constexpr std::size_t MapSize = std::extent::value; + static_assert(MapSize == CC_MqttsnReturnCode_ValuesLimit); + + auto idx = static_cast(val); + if (MapSize <= idx) { + assert(false); // Should not happen + return std::to_string(val); + } + + return Map[idx] + " (" + std::to_string(val) + ')'; +} + void AppClient::nextTickProgramInternal(unsigned duration) { m_lastWaitProgram = Clock::now(); @@ -354,15 +422,10 @@ bool AppClient::createSession() } m_session->setDataReportCb( - [this](const Addr& addr, const std::uint8_t* buf, std::size_t bufLen) + [this](const std::uint8_t* buf, std::size_t bufLen, const Addr& addr, CC_MqttsnDataOrigin origin) { assert(m_client); m_lastAddr = addr; - auto origin = CC_MqttsnDataOrigin_Any; - if (addr == m_gwAddr) { - origin = CC_MqttsnDataOrigin_ConnectedGw; - } - ::cc_mqttsn_client_process_data(m_client.get(), buf, static_cast(bufLen), origin); }); @@ -382,6 +445,28 @@ bool AppClient::createSession() return true; } +void AppClient::connectCompleteInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info) +{ + if (status != CC_MqttsnAsyncOpStatus_Complete) { + logError() << "Failed to connect with status: " << toString(status) << std::endl; + doTerminate(); + return; + } + + assert(info != nullptr); + if (info->m_returnCode != CC_MqttsnReturnCode_Accepted) { + logError() << "Connection rejected with return code: " << toString(info->m_returnCode) << std::endl; + doTerminate(); + return; + } + + if (m_opts.verbose()) { + logInfo() << "Connection established" << std::endl; + } + + connectCompleteImpl(); +} + void AppClient::sendDataCb(void* data, const unsigned char* buf, unsigned bufLen, unsigned broadcastRadius) { asThis(data)->sendDataInternal(buf, bufLen, broadcastRadius); @@ -407,4 +492,9 @@ unsigned AppClient::cancelNextTickWaitCb(void* data) return asThis(data)->cancelNextTickWaitInternal(); } +void AppClient::connectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info) +{ + return asThis(data)->connectCompleteInternal(status, info); +} + } // namespace cc_mqttsn_client_app diff --git a/client/app/common/AppClient.h b/client/app/common/AppClient.h index 2a27ffb1..01e6b80c 100644 --- a/client/app/common/AppClient.h +++ b/client/app/common/AppClient.h @@ -60,12 +60,15 @@ class AppClient } static std::ostream& logError(); + static std::ostream& logInfo(); void doTerminate(int result = 1); void doComplete(); + bool doConnect(); virtual bool startImpl(); virtual void messageReceivedImpl(const CC_MqttsnMessageInfo* info); + virtual void connectCompleteImpl(); static std::vector parseBinaryData(const std::string& val); @@ -74,12 +77,8 @@ class AppClient return m_lastAddr; } - void setGwAddr(const Addr& addr) - { - m_gwAddr = addr; - } - static std::string toString(CC_MqttsnAsyncOpStatus val); + static std::string toString(CC_MqttsnReturnCode val); private: using ClientPtr = std::unique_ptr; @@ -90,12 +89,14 @@ class AppClient unsigned cancelNextTickWaitInternal(); void sendDataInternal(const unsigned char* buf, unsigned bufLen, unsigned broadcastRadius); bool createSession(); + void connectCompleteInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info); static void sendDataCb(void* data, const unsigned char* buf, unsigned bufLen, unsigned broadcastRadius); static void messageReceivedCb(void* data, const CC_MqttsnMessageInfo* info); static void logMessageCb(void* data, const char* msg); static void nextTickProgramCb(void* data, unsigned duration); static unsigned cancelNextTickWaitCb(void* data); + static void connectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info); boost::asio::io_context& m_io; int& m_result; @@ -105,7 +106,6 @@ class AppClient ClientPtr m_client; SessionPtr m_session; Addr m_lastAddr; - Addr m_gwAddr; }; } // namespace cc_mqttsn_client_app diff --git a/client/app/common/ProgramOptions.cpp b/client/app/common/ProgramOptions.cpp index a501f7a7..b4437b1b 100644 --- a/client/app/common/ProgramOptions.cpp +++ b/client/app/common/ProgramOptions.cpp @@ -18,6 +18,7 @@ namespace { constexpr std::uint16_t DefaultPort = 1883U; +constexpr unsigned DefaultKeepAlive = 60; } // namespace @@ -39,7 +40,7 @@ void ProgramOptions::addNetwork() opts.add_options() ("network-gateway,g", po::value()->default_value("127.0.0.1"), "Gateway address to connect to") ("network-broadcast,b", po::value()->default_value("255.255.255.255"), "Address to broadcast to") - ("network-port,p", po::value()->default_value(DefaultPort), "Network remove port") + ("network-port,p", po::value()->default_value(DefaultPort), "Network remote port") ("network-local-port,P", po::value()->default_value(0), "Network local port") ; @@ -57,6 +58,44 @@ void ProgramOptions::addDiscover() m_desc.add(opts); } +void ProgramOptions::addConnect() +{ + po::options_description opts("Connect Options"); + opts.add_options() + ("connect-client-id,c", po::value()->default_value(std::string()), "Client ID") + ("connect-keep-alive,k", po::value()->default_value(DefaultKeepAlive), "Protocol \"keep alive\" configuration") + ("connect-no-clean-session,l", "Do not force clean session upon connection") + ; + + m_desc.add(opts); +} + +void ProgramOptions::addWill() +{ + po::options_description opts("Will Options"); + opts.add_options() + ("will-topic", po::value()->default_value(std::string()), "Will topic, when not provided means no will") + ("will-message", po::value()->default_value(std::string()), "Will message data, use \"\\x\" prefix before hex value of each byte for binary string") + ("will-qos", po::value()->default_value(0U), "Will QoS value") + ; + + m_desc.add(opts); +} + +void ProgramOptions::addPublish() +{ + po::options_description opts("Publish Options"); + opts.add_options() + ("pub-topic,t", po::value()->default_value(std::string()), "Publish topic, must be empty when topic ID is not 0") + ("pub-topic-id,i", po::value()->default_value(0U), "Publish topic id, must be 0 when topic is specified") + ("pub-message,m", po::value()->default_value(std::string()), "Publish message data, use \"\\x\" prefix before hex value of each byte for binary string") + ("pub-qos,q", po::value()->default_value(0U), "Publish QoS value") + ("pub-retain", "Publish retained message") + ; + + m_desc.add(opts); +} + void ProgramOptions::printHelp() { std::cout << m_desc << std::endl; @@ -116,4 +155,59 @@ unsigned ProgramOptions::discoverTimeout() const return m_vm["discover-timeout"].as(); } +std::string ProgramOptions::connectClientId() const +{ + return m_vm["connect-client-id"].as(); +} + +unsigned ProgramOptions::connectKeepAlive() const +{ + return m_vm["connect-keep-alive"].as(); +} + +bool ProgramOptions::connectNoCleanSession() const +{ + return m_vm.count("connect-no-clean-session") > 0U; +} + +std::string ProgramOptions::willTopic() const +{ + return m_vm["will-topic"].as(); +} + +std::string ProgramOptions::willMessage() const +{ + return m_vm["will-message"].as(); +} + +unsigned ProgramOptions::willQos() const +{ + return m_vm["will-qos"].as(); +} + +std::string ProgramOptions::pubTopic() const +{ + return m_vm["pub-topic"].as(); +} + +std::uint16_t ProgramOptions::pubTopicId() const +{ + return m_vm["pub-topic-id"].as(); +} + +std::string ProgramOptions::pubMessage() const +{ + return m_vm["pub-message"].as(); +} + +unsigned ProgramOptions::pubQos() const +{ + return m_vm["pub-qos"].as(); +} + +bool ProgramOptions::pubRetain() const +{ + return m_vm.count("pub-retain") > 0U; +} + } // namespace cc_mqttsn_client_app diff --git a/client/app/common/ProgramOptions.h b/client/app/common/ProgramOptions.h index bd7079af..abd572c1 100644 --- a/client/app/common/ProgramOptions.h +++ b/client/app/common/ProgramOptions.h @@ -30,6 +30,9 @@ class ProgramOptions void addCommon(); void addNetwork(); void addDiscover(); + void addConnect(); + void addWill(); + void addPublish(); void printHelp(); @@ -50,6 +53,23 @@ class ProgramOptions bool discoverExitOnFirst() const; unsigned discoverTimeout() const; + // Connect Options + std::string connectClientId() const; + unsigned connectKeepAlive() const; + bool connectNoCleanSession() const; + + // Will Options + std::string willTopic() const; + std::string willMessage() const; + unsigned willQos() const; + + // Publish Options + std::string pubTopic() const; + std::uint16_t pubTopicId() const; + std::string pubMessage() const; + unsigned pubQos() const; + bool pubRetain() const; + private: boost::program_options::variables_map m_vm; OptDesc m_desc; diff --git a/client/app/common/Session.cpp b/client/app/common/Session.cpp index 30f6424b..aef0444b 100644 --- a/client/app/common/Session.cpp +++ b/client/app/common/Session.cpp @@ -44,10 +44,10 @@ std::ostream& Session::logError() return std::cerr << "ERROR: "; } -void Session::reportData(const Addr& addr, const std::uint8_t* buf, std::size_t bufLen) +void Session::reportData(const std::uint8_t* buf, std::size_t bufLen, const Addr& addr, CC_MqttsnDataOrigin origin) { assert(m_dataReportCb); - m_dataReportCb(addr, buf, bufLen); + m_dataReportCb(buf, bufLen, addr, origin); } void Session::reportNetworkError() diff --git a/client/app/common/Session.h b/client/app/common/Session.h index 006982b9..d093c7c5 100644 --- a/client/app/common/Session.h +++ b/client/app/common/Session.h @@ -9,6 +9,8 @@ #include "ProgramOptions.h" +#include "cc_mqttsn_client/common.h" + #include #include @@ -38,7 +40,7 @@ class Session sendDataImpl(buf, bufLen, broadcastRadius); } - using DataReportCb = std::function; + using DataReportCb = std::function; template void setDataReportCb(TFunc&& func) { @@ -68,7 +70,7 @@ class Session static std::ostream& logError(); - void reportData(const Addr& addr, const std::uint8_t* buf, std::size_t bufLen); + void reportData(const std::uint8_t* buf, std::size_t bufLen, const Addr& addr, CC_MqttsnDataOrigin origin); void reportNetworkError(); virtual bool startImpl() = 0; diff --git a/client/app/common/UdpSession.cpp b/client/app/common/UdpSession.cpp index f563d519..0b5d33aa 100644 --- a/client/app/common/UdpSession.cpp +++ b/client/app/common/UdpSession.cpp @@ -121,7 +121,12 @@ void UdpSession::doRead() auto remoteAddr = m_senderEndpoint.address().to_v4().to_bytes(); Addr addrToReport(remoteAddr.begin(), remoteAddr.end()); - reportData(addrToReport, m_inBuf.data(), bytesCount); + auto origin = CC_MqttsnDataOrigin_Any; + if (m_senderEndpoint == m_remoteEndpoint) { + origin = CC_MqttsnDataOrigin_ConnectedGw; + } + + reportData(m_inBuf.data(), bytesCount, addrToReport, origin); doRead(); }); } diff --git a/client/app/pub/CMakeLists.txt b/client/app/pub/CMakeLists.txt new file mode 100644 index 00000000..a454e3e7 --- /dev/null +++ b/client/app/pub/CMakeLists.txt @@ -0,0 +1,13 @@ +set (name "cc_mqttsn_client_pub") +set (src + main.cpp + Pub.cpp +) + +add_executable(${name} ${src}) +target_link_libraries(${name} ${COMMON_APPS_LIB}) + +install ( + TARGETS ${name} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) \ No newline at end of file diff --git a/client/app/pub/Pub.cpp b/client/app/pub/Pub.cpp new file mode 100644 index 00000000..bc4703f9 --- /dev/null +++ b/client/app/pub/Pub.cpp @@ -0,0 +1,113 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "Pub.h" + +#include +#include +#include +#include + +namespace cc_mqttsn_client_app +{ + +namespace +{ + +Pub* asThis(void* data) +{ + return reinterpret_cast(data); +} + +} // namespace + + +Pub::Pub(boost::asio::io_context& io, int& result) : + Base(io, result) +{ + opts().addCommon(); + opts().addNetwork(); + opts().addConnect(); + opts().addWill(); + opts().addPublish(); +} + +bool Pub::startImpl() +{ + auto topic = opts().pubTopic(); + auto topicId = opts().pubTopicId(); + if (topic.empty() && topicId == 0) { + logError() << "Neither topic nor topic ID are specified" << std::endl; + return false; + } + + if ((!topic.empty()) && (topicId != 0)) { + logError() << "Both topic topic topic ID are specified" << std::endl; + return false; + } + + return doConnect(); +} + +void Pub::connectCompleteImpl() +{ + auto config = CC_MqttsnPublishConfig(); + cc_mqttsn_client_publish_init_config(&config); + + auto topic = opts().pubTopic(); + auto data = parseBinaryData(opts().pubMessage()); + + if (!topic.empty()) { + config.m_topic = topic.c_str(); + } + + config.m_topicId = opts().pubTopicId(); + config.m_data = data.data(); + config.m_dataLen = static_cast(data.size()); + config.m_qos = static_cast(opts().pubQos()); + config.m_retain = opts().pubRetain(); + + auto ec = cc_mqttsn_client_publish(client(), &config, &Pub::publishCompleteCb, this); + if (ec == CC_MqttsnErrorCode_Success) { + return; + } + + logError() << "Failed to initiate publish operation with error code: " << toString(ec) << std::endl; + doTerminate(); +} + +void Pub::publishCompleteInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnPublishInfo* info) +{ + if (status != CC_MqttsnAsyncOpStatus_Complete) { + logError() << "Publish failed with status: " << toString(status) << std::endl; + doTerminate(); + return; + } + + if ((info != nullptr) && (info->m_returnCode != CC_MqttsnReturnCode_Accepted)) { + logError() << "Publish rejected with return code: " << toString(info->m_returnCode) << std::endl; + doTerminate(); + return; + } + + if (opts().verbose()) { + logInfo() << "Publish complete" << std::endl; + } + + doComplete(); +} + +void Pub::publishCompleteCb( + void* data, + [[maybe_unused]] CC_MqttsnPublishHandle handle, + CC_MqttsnAsyncOpStatus status, + const CC_MqttsnPublishInfo* info) +{ + asThis(data)->publishCompleteInternal(status, info); +} + +} // namespace cc_mqttsn_client_app diff --git a/client/app/pub/Pub.h b/client/app/pub/Pub.h new file mode 100644 index 00000000..6f111125 --- /dev/null +++ b/client/app/pub/Pub.h @@ -0,0 +1,33 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "AppClient.h" +#include "ProgramOptions.h" + +#include + +namespace cc_mqttsn_client_app +{ + +class Pub : public AppClient +{ + using Base = AppClient; +public: + Pub(boost::asio::io_context& io, int& result); + +protected: + virtual bool startImpl() override; + virtual void connectCompleteImpl() override; + +private: + void publishCompleteInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnPublishInfo* info); + static void publishCompleteCb(void* data, CC_MqttsnPublishHandle handle, CC_MqttsnAsyncOpStatus status, const CC_MqttsnPublishInfo* info); +}; + +} // namespace cc_mqttsn_client_app diff --git a/client/app/pub/main.cpp b/client/app/pub/main.cpp new file mode 100644 index 00000000..fa38a7dc --- /dev/null +++ b/client/app/pub/main.cpp @@ -0,0 +1,51 @@ + +#include "Pub.h" + +#include + +#include +#include +#include + +int main(int argc, const char* argv[]) +{ + int result = 0U; + try { + boost::asio::io_context io; + + boost::asio::signal_set signals(io, SIGINT, SIGTERM); + signals.async_wait( + [&io, &result](const boost::system::error_code& ec, int sigNum) + { + if (ec == boost::asio::error::operation_aborted) { + return; + } + + if (ec) { + std::cerr << "ERROR: Unexpected error in signal handling: " << ec.message() << std::endl; + result = 150; + io.stop(); + return; + } + + std::cerr << "Terminated with signal " << sigNum << std::endl; + result = 100; + io.stop(); + }); + + cc_mqttsn_client_app::Pub app(io, result); + + if (!app.start(argc, argv)) { + return -1; + } + + io.run(); + } + catch (const std::exception& ec) + { + std::cerr << "ERROR: Unexpected exception: " << ec.what() << std::endl; + result = 200; + } + + return result; +} \ No newline at end of file diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index c593a487..62745fcc 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -225,8 +225,8 @@ typedef struct const char* m_topic; ///< Topic the message was published with. May be NULL if message is reported with predefined topic ID. const unsigned char* m_data; ///< Pointer to reported message binary data. unsigned m_dataLen; ///< Number of bytes in reported message binary data. - CC_MqttsnQoS m_qos; ///< QoS level the message was received with. CC_MqttsnTopicId m_topicId; ///< Predefined topic ID. This data member is used only if topic field has value NULL. + CC_MqttsnQoS m_qos; ///< QoS level the message was received with. bool m_retained; ///< Retain flag of the message. } CC_MqttsnMessageInfo; @@ -296,9 +296,9 @@ typedef struct typedef struct { const char* m_topic; ///< Publish topic. - CC_MqttsnTopicId m_topicId; ///< Pre-defined topic ID, should be @b 0 when topic is not NULL. const unsigned char* m_data; ///< Publish data (message). unsigned m_dataLen; ///< Publish data (message) length. + CC_MqttsnTopicId m_topicId; ///< Pre-defined topic ID, should be @b 0 when topic is not NULL. CC_MqttsnQoS m_qos; ///< Publish message QoS. bool m_retain; ///< Publish message retain configuration. } CC_MqttsnPublishConfig; diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index 1344e32f..a753b52d 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -556,7 +556,15 @@ void UnitTestCommonBase::unitTestDoConnect(CC_MqttsnClient* client, const CC_Mqt auto* connectMsg = dynamic_cast(sentMsg.get()); test_assert(connectMsg != nullptr); if (config != nullptr) { - test_assert(connectMsg->field_clientId().value() == config->m_clientId); + if (config->m_clientId != nullptr) { + if (connectMsg->field_clientId().value() != config->m_clientId) { + std::cerr << "FAILURE: \"" << connectMsg->field_clientId().value().c_str() << "\" != \"" << config->m_clientId << "\"" << std::endl; + } + test_assert(connectMsg->field_clientId().value() == config->m_clientId); + } + else { + test_assert(connectMsg->field_clientId().value().empty()); + } test_assert(connectMsg->field_duration().value() == config->m_duration); test_assert(connectMsg->field_flags().field_mid().getBitValue_CleanSession() == config->m_cleanSession); } From c07ad63972c1ac12a99fe0443d9d727caa702639 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 14 Aug 2024 15:02:39 +1000 Subject: [PATCH 100/106] Added client subscribe application. --- client/app/CMakeLists.txt | 2 +- client/app/common/AppClient.cpp | 202 +++++++++++-------- client/app/common/AppClient.h | 7 +- client/app/common/ProgramOptions.cpp | 55 +++++ client/app/common/ProgramOptions.h | 11 +- client/app/pub/Pub.cpp | 7 +- client/app/sub/CMakeLists.txt | 13 ++ client/app/sub/Sub.cpp | 142 +++++++++++++ client/app/sub/Sub.h | 36 ++++ client/app/sub/main.cpp | 51 +++++ client/lib/include/cc_mqttsn_client/common.h | 3 +- client/lib/src/op/SendOp.cpp | 8 +- client/lib/test/bm/UnitTestBmConnect.th | 2 +- client/lib/test/bm/UnitTestBmPublish.th | 2 +- 14 files changed, 447 insertions(+), 94 deletions(-) create mode 100644 client/app/sub/CMakeLists.txt create mode 100644 client/app/sub/Sub.cpp create mode 100644 client/app/sub/Sub.h create mode 100644 client/app/sub/main.cpp diff --git a/client/app/CMakeLists.txt b/client/app/CMakeLists.txt index f3fa8d32..4b1d2f7e 100644 --- a/client/app/CMakeLists.txt +++ b/client/app/CMakeLists.txt @@ -21,4 +21,4 @@ set (COMMON_APPS_LIB "cc_mqttsn_client_apps_lib") add_subdirectory (common) add_subdirectory (gw_discover) add_subdirectory (pub) -#add_subdirectory (sub) \ No newline at end of file +add_subdirectory (sub) \ No newline at end of file diff --git a/client/app/common/AppClient.cpp b/client/app/common/AppClient.cpp index 01f90bf7..5bde8db7 100644 --- a/client/app/common/AppClient.cpp +++ b/client/app/common/AppClient.cpp @@ -27,44 +27,34 @@ AppClient* asThis(void* data) return reinterpret_cast(data); } -// std::string toString(CC_MqttsnQoS val) -// { -// static const std::string Map[] = { -// /* CC_MqttsnQoS_AtMostOnceDelivery */ "QoS0 - At Most Once Delivery", -// /* CC_MqttsnQoS_AtLeastOnceDelivery */ "QoS1 - At Least Once Delivery", -// /* CC_MqttsnQoS_ExactlyOnceDelivery */ "QoS2 - Exactly Once Delivery", -// }; -// static constexpr std::size_t MapSize = std::extent::value; -// static_assert(MapSize == CC_MqttsnQoS_ValuesLimit); - -// auto idx = static_cast(val); -// if (MapSize <= idx) { -// assert(false); // Should not happen -// return std::to_string(val); -// } - -// return Map[idx] + " (" + std::to_string(val) + ')'; -// } - -// void printQos(const char* prefix, CC_MqttsnQoS val) -// { -// std::cout << "\t" << prefix << ": " << toString(val) << '\n'; -// } - -// void printBool(const char* prefix, bool val) -// { -// std::cout << '\t' << prefix << ": " << std::boolalpha << val << '\n'; -// } - -// void printConnectReturnCode(CC_MqttsnConnectReturnCode val) -// { -// std::cout << "\tReturn Code: " << AppClient::toString(val) << '\n'; -// } - -// void printSubscribeReturnCode(CC_MqttsnSubscribeReturnCode val) -// { -// std::cout << "\tReturn Code: " << AppClient::toString(val) << '\n'; -// } +std::string toString(CC_MqttsnQoS val) +{ + static const std::string Map[] = { + /* CC_MqttsnQoS_AtMostOnceDelivery */ "QoS0 - At Most Once Delivery", + /* CC_MqttsnQoS_AtLeastOnceDelivery */ "QoS1 - At Least Once Delivery", + /* CC_MqttsnQoS_ExactlyOnceDelivery */ "QoS2 - Exactly Once Delivery", + }; + static constexpr std::size_t MapSize = std::extent::value; + static_assert(MapSize == CC_MqttsnQoS_ValuesLimit); + + auto idx = static_cast(val); + if (MapSize <= idx) { + assert(false); // Should not happen + return std::to_string(val); + } + + return Map[idx] + " (" + std::to_string(val) + ')'; +} + +void printQos(const char* prefix, CC_MqttsnQoS val) +{ + std::cout << "\t" << prefix << ": " << toString(val) << '\n'; +} + +void printBool(const char* prefix, bool val) +{ + std::cout << '\t' << prefix << ": " << std::boolalpha << val << '\n'; +} } // namespace @@ -128,52 +118,52 @@ std::string AppClient::toString(CC_MqttsnErrorCode val) return Map[idx] + " (" + std::to_string(val) + ')'; } -// std::string AppClient::toString(const std::uint8_t* data, unsigned dataLen, bool forceBinary) -// { -// bool binary = forceBinary; -// if (!binary) { -// binary = -// std::any_of( -// data, data + dataLen, -// [](auto byte) -// { -// if (std::isprint(static_cast(byte)) == 0) { -// return true; -// } - -// if (byte > 0x7e) { -// return true; -// } - -// return false; -// }); -// } - -// if (!binary) { -// return std::string(reinterpret_cast(data), dataLen); -// } - -// std::stringstream stream; -// stream << std::hex; -// for (auto idx = 0U; idx < dataLen; ++idx) { -// stream << std::setw(2) << std::setfill('0') << static_cast(data[idx]) << ' '; -// } -// return stream.str(); -// } - -// void AppClient::print(const CC_MqttsnMessageInfo& info, bool printMessage) -// { -// std::cout << "[INFO]: Message Info:\n"; -// if (printMessage) { -// std::cout << -// "\tTopic: " << info.m_topic << '\n' << -// "\tData: " << toString(info.m_data, info.m_dataLen, m_opts.subBinary()) << '\n'; -// } - -// printQos("QoS", info.m_qos); -// printBool("Retained", info.m_retained); -// std::cout << std::endl; -// } +std::string AppClient::toString(const std::uint8_t* data, unsigned dataLen, bool forceBinary) +{ + bool binary = forceBinary; + if (!binary) { + binary = + std::any_of( + data, data + dataLen, + [](auto byte) + { + if (std::isprint(static_cast(byte)) == 0) { + return true; + } + + if (byte > 0x7e) { + return true; + } + + return false; + }); + } + + if (!binary) { + return std::string(reinterpret_cast(data), dataLen); + } + + std::stringstream stream; + stream << std::hex; + for (auto idx = 0U; idx < dataLen; ++idx) { + stream << std::setw(2) << std::setfill('0') << static_cast(data[idx]) << ' '; + } + return stream.str(); +} + +void AppClient::print(const CC_MqttsnMessageInfo& info, bool printMessage) +{ + std::cout << "[INFO]: Message Info:\n"; + if (printMessage) { + std::cout << + "\tTopic: " << info.m_topic << '\n' << + "\tData: " << toString(info.m_data, info.m_dataLen, m_opts.subBinary()) << '\n'; + } + + printQos("QoS", info.m_qos); + printBool("Retained", info.m_retained); + std::cout << std::endl; +} AppClient::AppClient(boost::asio::io_context& io, int& result) : m_io(io), @@ -191,12 +181,12 @@ AppClient::AppClient(boost::asio::io_context& io, int& result) : std::ostream& AppClient::logError() { - return std::cerr << "ERROR: "; + return std::cerr << "[ERROR] "; } std::ostream& AppClient::logInfo() { - return std::cout << "INFO: "; + return std::cout << "[INFO] "; } void AppClient::doTerminate(int result) @@ -254,6 +244,21 @@ bool AppClient::doConnect() return true; } +bool AppClient::doDisconnect() +{ + if (m_opts.verbose()) { + logInfo() << "Attempting disconnection" << std::endl; + } + + auto ec = cc_mqttsn_client_disconnect(m_client.get(), &AppClient::disconnectCompleteCb, this); + if (ec != CC_MqttsnErrorCode_Success) { + logError() << "Failed to initiate disconnection from the gateway" << std::endl; + return false; + } + + return true; +} + bool AppClient::startImpl() { return true; @@ -268,6 +273,11 @@ void AppClient::connectCompleteImpl() { } +void AppClient::disconnectCompleteImpl() +{ + doComplete(); +} + std::vector AppClient::parseBinaryData(const std::string& val) { std::vector result; @@ -467,6 +477,21 @@ void AppClient::connectCompleteInternal(CC_MqttsnAsyncOpStatus status, const CC_ connectCompleteImpl(); } +void AppClient::disconnectCompleteInternal(CC_MqttsnAsyncOpStatus status) +{ + if (status != CC_MqttsnAsyncOpStatus_Complete) { + logError() << "Failed to disconnect with status: " << toString(status) << std::endl; + doTerminate(); + return; + } + + if (m_opts.verbose()) { + logInfo() << "Disconnected" << std::endl; + } + + disconnectCompleteImpl(); +} + void AppClient::sendDataCb(void* data, const unsigned char* buf, unsigned bufLen, unsigned broadcastRadius) { asThis(data)->sendDataInternal(buf, bufLen, broadcastRadius); @@ -497,4 +522,9 @@ void AppClient::connectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, con return asThis(data)->connectCompleteInternal(status, info); } +void AppClient::disconnectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status) +{ + return asThis(data)->disconnectCompleteInternal(status); +} + } // namespace cc_mqttsn_client_app diff --git a/client/app/common/AppClient.h b/client/app/common/AppClient.h index 01e6b80c..ccea178c 100644 --- a/client/app/common/AppClient.h +++ b/client/app/common/AppClient.h @@ -35,7 +35,8 @@ class AppClient static std::string toString(CC_MqttsnErrorCode val); //static std::string toString(CC_MqttsnAsyncOpStatus val); - //void print(const CC_MqttsnMessageInfo& info, bool printMessage = true); + static std::string toString(const std::uint8_t* data, unsigned dataLen, bool forceBinary = false); + void print(const CC_MqttsnMessageInfo& info, bool printMessage = true); protected: using Timer = boost::asio::steady_timer; @@ -65,10 +66,12 @@ class AppClient void doTerminate(int result = 1); void doComplete(); bool doConnect(); + bool doDisconnect(); virtual bool startImpl(); virtual void messageReceivedImpl(const CC_MqttsnMessageInfo* info); virtual void connectCompleteImpl(); + virtual void disconnectCompleteImpl(); static std::vector parseBinaryData(const std::string& val); @@ -90,6 +93,7 @@ class AppClient void sendDataInternal(const unsigned char* buf, unsigned bufLen, unsigned broadcastRadius); bool createSession(); void connectCompleteInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info); + void disconnectCompleteInternal(CC_MqttsnAsyncOpStatus status); static void sendDataCb(void* data, const unsigned char* buf, unsigned bufLen, unsigned broadcastRadius); static void messageReceivedCb(void* data, const CC_MqttsnMessageInfo* info); @@ -97,6 +101,7 @@ class AppClient static void nextTickProgramCb(void* data, unsigned duration); static unsigned cancelNextTickWaitCb(void* data); static void connectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info); + static void disconnectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status); boost::asio::io_context& m_io; int& m_result; diff --git a/client/app/common/ProgramOptions.cpp b/client/app/common/ProgramOptions.cpp index b4437b1b..d35e8b4c 100644 --- a/client/app/common/ProgramOptions.cpp +++ b/client/app/common/ProgramOptions.cpp @@ -91,6 +91,21 @@ void ProgramOptions::addPublish() ("pub-message,m", po::value()->default_value(std::string()), "Publish message data, use \"\\x\" prefix before hex value of each byte for binary string") ("pub-qos,q", po::value()->default_value(0U), "Publish QoS value") ("pub-retain", "Publish retained message") + ("pub-no-disconnect", "Do not gracefuly disconnect when publish is complete") + ; + + m_desc.add(opts); +} + +void ProgramOptions::addSubscribe() +{ + po::options_description opts("Subscribe Options"); + opts.add_options() + ("sub-topic,t", po::value>(), "Subscribe to topic, can be used multiple times.") + ("sub-topic-id,i", po::value>(), "Subscribe topic id, can be used multiple times") + ("sub-qos,q", po::value()->default_value(2U), "Subscribe max QoS value") + ("sub-no-retained", "Ignore retained messages") + ("sub-binary", "Force binary output of the received message data") ; m_desc.add(opts); @@ -210,4 +225,44 @@ bool ProgramOptions::pubRetain() const return m_vm.count("pub-retain") > 0U; } +bool ProgramOptions::pubNoDisconnect() const +{ + return m_vm.count("pub-no-disconnect") > 0U; +} + +std::vector ProgramOptions::subTopics() const +{ + std::vector result; + if (m_vm.count("sub-topic") > 0U) { + result = m_vm["sub-topic"].as>(); + } + + return result; +} + +std::vector ProgramOptions::subTopicIds() const +{ + std::vector result; + if (m_vm.count("sub-topic-id") > 0U) { + result = m_vm["sub-topic-id"].as>(); + } + + return result; +} + +unsigned ProgramOptions::subQos() const +{ + return m_vm["sub-qos"].as(); +} + +bool ProgramOptions::subNoRetained() const +{ + return m_vm.count("sub-no-retained") > 0U; +} + +bool ProgramOptions::subBinary() const +{ + return m_vm.count("sub-binary") > 0U; +} + } // namespace cc_mqttsn_client_app diff --git a/client/app/common/ProgramOptions.h b/client/app/common/ProgramOptions.h index abd572c1..b924298e 100644 --- a/client/app/common/ProgramOptions.h +++ b/client/app/common/ProgramOptions.h @@ -33,6 +33,7 @@ class ProgramOptions void addConnect(); void addWill(); void addPublish(); + void addSubscribe(); void printHelp(); @@ -68,7 +69,15 @@ class ProgramOptions std::uint16_t pubTopicId() const; std::string pubMessage() const; unsigned pubQos() const; - bool pubRetain() const; + bool pubRetain() const; + bool pubNoDisconnect() const; + + // Subscribe Options + std::vector subTopics() const; + std::vector subTopicIds() const; + unsigned subQos() const; + bool subNoRetained() const; + bool subBinary() const; private: boost::program_options::variables_map m_vm; diff --git a/client/app/pub/Pub.cpp b/client/app/pub/Pub.cpp index bc4703f9..c06039bd 100644 --- a/client/app/pub/Pub.cpp +++ b/client/app/pub/Pub.cpp @@ -98,7 +98,12 @@ void Pub::publishCompleteInternal(CC_MqttsnAsyncOpStatus status, const CC_Mqttsn logInfo() << "Publish complete" << std::endl; } - doComplete(); + if (opts().pubNoDisconnect()) { + doComplete(); + return; + } + + doDisconnect(); } void Pub::publishCompleteCb( diff --git a/client/app/sub/CMakeLists.txt b/client/app/sub/CMakeLists.txt new file mode 100644 index 00000000..465d515d --- /dev/null +++ b/client/app/sub/CMakeLists.txt @@ -0,0 +1,13 @@ +set (name "cc_mqttsn_client_sub") +set (src + main.cpp + Sub.cpp +) + +add_executable(${name} ${src}) +target_link_libraries(${name} ${COMMON_APPS_LIB}) + +install ( + TARGETS ${name} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) \ No newline at end of file diff --git a/client/app/sub/Sub.cpp b/client/app/sub/Sub.cpp new file mode 100644 index 00000000..842045d8 --- /dev/null +++ b/client/app/sub/Sub.cpp @@ -0,0 +1,142 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "Sub.h" + +#include +#include +#include +#include + +namespace cc_mqttsn_client_app +{ + +namespace +{ + +Sub* asThis(void* data) +{ + return reinterpret_cast(data); +} + +} // namespace + + +Sub::Sub(boost::asio::io_context& io, int& result) : + Base(io, result) +{ + opts().addCommon(); + opts().addNetwork(); + opts().addConnect(); + opts().addWill(); + opts().addSubscribe(); +} + +bool Sub::startImpl() +{ + return doConnect(); +} + +void Sub::messageReceivedImpl(const CC_MqttsnMessageInfo* info) +{ + assert(info != nullptr); + assert(info->m_topic != nullptr); + if (info->m_retained && opts().subNoRetained()) { + return; + } + + if (opts().verbose()) { + print(*info); + } + else { + std::cout << info->m_topic << ": " << toString(info->m_data, info->m_dataLen, opts().subBinary()) << std::endl; + } +} + +void Sub::connectCompleteImpl() +{ + auto topics = opts().subTopics(); + auto topicIds = opts().subTopicIds(); + + if (topics.empty() && topicIds.empty()) { + logInfo() << "Not subscription topics provided"; + return; + } + + if (opts().verbose()) { + logInfo() << "Subscribing..." << std::endl; + } + + auto doSubscribe = + [this](const std::string& topic, std::uint16_t topicId) + { + auto config = CC_MqttsnSubscribeConfig(); + cc_mqttsn_client_subscribe_init_config(&config); + + if (!topic.empty()) { + config.m_topic = topic.c_str(); + } + else { + config.m_topicId = topicId; + } + + config.m_qos = static_cast(opts().subQos()); + + auto ec = cc_mqttsn_client_subscribe(client(), &config, &Sub::subscribeCompleteCb, this); + if (ec != CC_MqttsnErrorCode_Success) { + if (!topic.empty()) { + logError() << "Failed to initiate subscribe subscribe to topic \"" << topic << "\" with error: " << toString(ec) << std::endl; + } + else { + logError() << "Failed to initiate subscribe subscribe to topic ID " << topicId << " with error: " << toString(ec) << std::endl; + } + return; + } + + ++m_subCount; + }; + + for (auto& t : topics) { + doSubscribe(t, 0U); + } + + for (auto id : topicIds) { + doSubscribe(std::string(), id); + } +} + +void Sub::subscribeCompleteInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info) +{ + do { + if (status != CC_MqttsnAsyncOpStatus_Complete) { + logError() << "Subscribe failed with status: " << toString(status) << std::endl; + break; + } + + if ((info != nullptr) && (info->m_returnCode != CC_MqttsnReturnCode_Accepted)) { + logError() << "Subscribe rejected with return code: " << toString(info->m_returnCode) << std::endl; + break; + } + + } while (false); + + --m_subCount; + if ((m_subCount == 0) && opts().verbose()) { + logInfo() << "Subscription complete, waiting for messages" << std::endl; + } +} + +void Sub::subscribeCompleteCb( + void* data, + [[maybe_unused]] CC_MqttsnSubscribeHandle handle, + CC_MqttsnAsyncOpStatus status, + const CC_MqttsnSubscribeInfo* info) +{ + asThis(data)->subscribeCompleteInternal(status, info); +} + +} // namespace cc_mqttsn_client_app diff --git a/client/app/sub/Sub.h b/client/app/sub/Sub.h new file mode 100644 index 00000000..eb522ee4 --- /dev/null +++ b/client/app/sub/Sub.h @@ -0,0 +1,36 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "AppClient.h" +#include "ProgramOptions.h" + +#include + +namespace cc_mqttsn_client_app +{ + +class Sub : public AppClient +{ + using Base = AppClient; +public: + Sub(boost::asio::io_context& io, int& result); + +protected: + virtual bool startImpl() override; + virtual void messageReceivedImpl(const CC_MqttsnMessageInfo* info) override; + virtual void connectCompleteImpl() override; + +private: + void subscribeCompleteInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info); + static void subscribeCompleteCb(void* data, CC_MqttsnSubscribeHandle handle, CC_MqttsnAsyncOpStatus status, const CC_MqttsnSubscribeInfo* info); + + unsigned m_subCount = 0U; +}; + +} // namespace cc_mqttsn_client_app diff --git a/client/app/sub/main.cpp b/client/app/sub/main.cpp new file mode 100644 index 00000000..d90f1b4c --- /dev/null +++ b/client/app/sub/main.cpp @@ -0,0 +1,51 @@ + +#include "Sub.h" + +#include + +#include +#include +#include + +int main(int argc, const char* argv[]) +{ + int result = 0U; + try { + boost::asio::io_context io; + + boost::asio::signal_set signals(io, SIGINT, SIGTERM); + signals.async_wait( + [&io, &result](const boost::system::error_code& ec, int sigNum) + { + if (ec == boost::asio::error::operation_aborted) { + return; + } + + if (ec) { + std::cerr << "ERROR: Unexpected error in signal handling: " << ec.message() << std::endl; + result = 150; + io.stop(); + return; + } + + std::cerr << "Terminated with signal " << sigNum << std::endl; + result = 100; + io.stop(); + }); + + cc_mqttsn_client_app::Sub app(io, result); + + if (!app.start(argc, argv)) { + return -1; + } + + io.run(); + } + catch (const std::exception& ec) + { + std::cerr << "ERROR: Unexpected exception: " << ec.what() << std::endl; + result = 200; + } + + return result; +} \ No newline at end of file diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index 62745fcc..7b994afe 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -43,7 +43,8 @@ typedef enum { CC_MqttsnQoS_AtMostOnceDelivery = 0, ///< QoS=0. At most once delivery. CC_MqttsnQoS_AtLeastOnceDelivery = 1, ///< QoS=1. At least once delivery. - CC_MqttsnQoS_ExactlyOnceDelivery = 2 ///< QoS=2. Exactly once delivery. + CC_MqttsnQoS_ExactlyOnceDelivery = 2, ///< QoS=2. Exactly once delivery. + CC_MqttsnQoS_ValuesLimit ///< Limit for the values } CC_MqttsnQoS; /// @brief Error code returned by various API functions. diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index 043316d1..610b3717 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -298,7 +298,13 @@ void SendOp::handle(PubackMsg& msg) } using TopicIdType = PublishMsg::Field_flags::Field_topicIdType::ValueType; - if (m_publishMsg.field_flags().field_topicIdType().value() != TopicIdType::Normal) { + auto topicIdType = m_publishMsg.field_flags().field_topicIdType().value(); + + if (topicIdType == TopicIdType::PredefinedTopicId) { + break; + } + + if (topicIdType != TopicIdType::Normal) { errorLog("Unexpected return code for the publish"); break; } diff --git a/client/lib/test/bm/UnitTestBmConnect.th b/client/lib/test/bm/UnitTestBmConnect.th index 08cc13eb..e5fcfd6a 100644 --- a/client/lib/test/bm/UnitTestBmConnect.th +++ b/client/lib/test/bm/UnitTestBmConnect.th @@ -27,6 +27,6 @@ void UnitTestBmConnect::test1() auto* client = clientPtr.get(); TS_ASSERT_DIFFERS(client, nullptr); - unitTestDoConnectBasic(client, __FUNCTION__); + unitTestDoConnectBasic(client, "test1"); } diff --git a/client/lib/test/bm/UnitTestBmPublish.th b/client/lib/test/bm/UnitTestBmPublish.th index a3076169..3b6a81b9 100644 --- a/client/lib/test/bm/UnitTestBmPublish.th +++ b/client/lib/test/bm/UnitTestBmPublish.th @@ -27,7 +27,7 @@ void UnitTestBmPublish::test1() auto* client = clientPtr.get(); TS_ASSERT_DIFFERS(client, nullptr); - unitTestDoConnectBasic(client, __FUNCTION__); + unitTestDoConnectBasic(client, "test1"); const CC_MqttsnTopicId TopicId = 123; const UnitTestData Data = { 0x1, 0x2, 0x3, 0x4, 0x5}; From 1e9c679524139d10fac7ac260168a4d604d2a666 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 14 Aug 2024 16:32:43 +1000 Subject: [PATCH 101/106] Updated custom client build instructions. --- client/lib/script/BareMetalTestConfig.cmake | 2 +- .../lib/script/DefineDefaultConfigVars.cmake | 2 +- client/lib/script/WriteConfigHeader.cmake | 2 +- client/lib/templ/Config.h.templ | 4 +- doc/BUILD.md | 19 +- doc/custom_client_build.md | 529 +++++++++++++----- 6 files changed, 384 insertions(+), 174 deletions(-) diff --git a/client/lib/script/BareMetalTestConfig.cmake b/client/lib/script/BareMetalTestConfig.cmake index 5b4632fe..3cd536c3 100644 --- a/client/lib/script/BareMetalTestConfig.cmake +++ b/client/lib/script/BareMetalTestConfig.cmake @@ -32,7 +32,7 @@ set(CC_MQTTSN_CLIENT_DATA_FIELD_FIXED_LEN 128) set(CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE 512) # Limit the amount of outstanding publish (send) operations -set (CC_MQTTSN_CLIENT_ASYNC_SEND_LIMIT 6) +set (CC_MQTTSN_CLIENT_ASYNC_PUBS_LIMIT 6) # Limit the amount of outstanding subscribe operations set(CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT 1) diff --git a/client/lib/script/DefineDefaultConfigVars.cmake b/client/lib/script/DefineDefaultConfigVars.cmake index 8b30757c..453ec396 100644 --- a/client/lib/script/DefineDefaultConfigVars.cmake +++ b/client/lib/script/DefineDefaultConfigVars.cmake @@ -23,7 +23,7 @@ set_default_var_value(CC_MQTTSN_CLIENT_WILL_DATA_FIELD_FIXED_LEN 0) set_default_var_value(CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN 0) set_default_var_value(CC_MQTTSN_CLIENT_DATA_FIELD_FIXED_LEN 0) set_default_var_value(CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE 0) -set_default_var_value(CC_MQTTSN_CLIENT_ASYNC_SEND_LIMIT 0) +set_default_var_value(CC_MQTTSN_CLIENT_ASYNC_PUBS_LIMIT 0) set_default_var_value(CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT 0) set_default_var_value(CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT 0) set_default_var_value(CC_MQTTSN_CLIENT_HAS_ERROR_LOG TRUE) diff --git a/client/lib/script/WriteConfigHeader.cmake b/client/lib/script/WriteConfigHeader.cmake index a37f05e5..3defed87 100644 --- a/client/lib/script/WriteConfigHeader.cmake +++ b/client/lib/script/WriteConfigHeader.cmake @@ -49,7 +49,7 @@ replace_in_text (CC_MQTTSN_CLIENT_GATEWAY_INFOS_MAX_LIMIT) replace_in_text (CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN) replace_in_text (CC_MQTTSN_CLIENT_HAS_WILL_CPP) replace_in_text (CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE) -replace_in_text (CC_MQTTSN_CLIENT_ASYNC_SEND_LIMIT) +replace_in_text (CC_MQTTSN_CLIENT_ASYNC_PUBS_LIMIT) replace_in_text (CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT) replace_in_text (CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT) replace_in_text (CC_MQTTSN_CLIENT_HAS_ERROR_LOG_CPP) diff --git a/client/lib/templ/Config.h.templ b/client/lib/templ/Config.h.templ index 92a41244..6d2d6e3d 100644 --- a/client/lib/templ/Config.h.templ +++ b/client/lib/templ/Config.h.templ @@ -12,7 +12,7 @@ struct Config static constexpr unsigned GatewayAddrLen = ##CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN##; static constexpr unsigned MaxOutputPacketSize = ##CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE##; static constexpr bool HasWill = ##CC_MQTTSN_CLIENT_HAS_WILL_CPP##; - static constexpr unsigned SendOpsLimit = ##CC_MQTTSN_CLIENT_ASYNC_SEND_LIMIT##; + static constexpr unsigned SendOpsLimit = ##CC_MQTTSN_CLIENT_ASYNC_PUBS_LIMIT##; static constexpr unsigned SubscribeOpsLimit = ##CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT##; static constexpr unsigned UnsubscribeOpsLimit = ##CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT##; static constexpr bool HasErrorLog = ##CC_MQTTSN_CLIENT_HAS_ERROR_LOG_CPP##; @@ -27,7 +27,7 @@ struct Config static_assert(HasDynMemAlloc || (!HasGatewayDiscovery) || (GatewayAddrLen > 0U), "Must use CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN in configuration to limit length of the gateway addr"); static_assert(HasDynMemAlloc || (!HasGatewayDiscovery) || (GatewayInfoxMaxLimit > 0U), "Must use CC_MQTTSN_CLIENT_GATEWAY_INFOS_MAX_LIMIT in configuration to limit amount of gateways to store"); static_assert(HasDynMemAlloc || (MaxOutputPacketSize > 0U), "Must use CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE in configuration to limit packet size"); - static_assert(HasDynMemAlloc || (SendOpsLimit > 0U), "Must use CC_MQTTSN_CLIENT_ASYNC_SEND_LIMIT in configuration to limit amount of messages to send"); + static_assert(HasDynMemAlloc || (SendOpsLimit > 0U), "Must use CC_MQTTSN_CLIENT_ASYNC_PUBS_LIMIT in configuration to limit amount of messages to send"); static_assert(HasDynMemAlloc || (SubscribeOpsLimit > 0U), "Must use CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT in configuration to limit amount of unfinished subscribes."); static_assert(HasDynMemAlloc || (UnsubscribeOpsLimit > 0U), "Must use CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT in configuration to limit amount of unfinished unsubscribes."); static_assert(HasDynMemAlloc || (!HasSubTopicVerification) || (SubFiltersLimit > 0U), "Must use CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT in configuration to limit amount of subscribe filters"); diff --git a/doc/BUILD.md b/doc/BUILD.md index c3cd6577..81eba15f 100644 --- a/doc/BUILD.md +++ b/doc/BUILD.md @@ -17,23 +17,10 @@ The provided libraries depend on several external projects: These dependencies are expected to be built independenty and access to them provided via standard **CMAKE_PREFIX_PATH** cmake variable. There are also scripts ( -[script/prepare_externals.sh](script/prepare_externals.sh) for Linux and -[script/prepare_externals.bat](script/prepare_externals.bat) for Windows) +[script/prepare_externals.sh](../script/prepare_externals.sh) for Linux and +[script/prepare_externals.bat](../script/prepare_externals.bat) for Windows) which can help in preparation of these dependencies. They are also used -in configuration of the [github actions](.github/workflows/actions_build.yml) and -[appveyor](.appveyor.yml). - - -## Choosing C++ Standard -Since CMake v3.1 it became possible to set version of C++ standard by setting -**CMAKE_CXX_STANDARD** variable. If no value of this variable is set in command -line arguments, default value **11** will be assigned to it. In order to use -c++14 standard in compilation, set the variable value to **14**. - -Please **NOTE**, that some older versions of the _clang_ compiler habe problems -compiling valid c++11 constructs used in this project. -Hence, the compilation will fail unless the compilation is -configured to use c++14 standard. +in configuration of the [github actions](../.github/workflows/actions_build.yml). ## Build and Install Examples diff --git a/doc/custom_client_build.md b/doc/custom_client_build.md index 2d34490c..7732069f 100644 --- a/doc/custom_client_build.md +++ b/doc/custom_client_build.md @@ -15,8 +15,8 @@ hold strings and [std::vector](http://en.cppreference.com/w/cpp/container/vector) type to hold various lists, because there is no known and predefined limit on string length and/or number of elements in the list. However if such limit is specified, the -library will use [StaticString](https://github.com/commschamp/comms/blob/master/include/comms/util/StaticString.h) -and [StaticVector](https://github.com/commschamp/comms/blob/master/include/comms/util/StaticVector.h) +library will use [comms::util::StaticString](https://github.com/commschamp/comms/blob/master/include/comms/util/StaticString.h) +and [comms::util::StaticVector](https://github.com/commschamp/comms/blob/master/include/comms/util/StaticVector.h) from the [COMMS library](https://github.com/commschamp/comms) instead. These classes don't use exceptions and/or dynamic memory allocation and can be suitable for bare-metal systems. @@ -26,188 +26,411 @@ This variable specifies the name of the custom client library. It will influence the names of the API functions. The **default** client build (controlled by **CC_MQTTSN_CLIENT_DEFAULT_LIB** option) prefixes all the functions with `cc_mqttsn_client_`, while client with custom name will produce -functions having `mqttsn__client_` prefix. For example having the +functions having `cc_mqttsn__client_` prefix. For example having the `set (CC_MQTTSN_CUSTOM_CLIENT_NAME "my_name")` statement in configuration file will produce a library which prefixes all API functions with -`mqttsn_my_name_client_`. +`cc_mqttsn_my_name_client_`. The **CC_MQTTSN_CUSTOM_CLIENT_NAME** variable is a **must have** one, without it the custom build of the client library won't be possible. -### CC_MQTTSN_CUSTOM_CLIENT_ID_STATIC_STORAGE_SIZE -The MQTT-SN needs to store the client ID string. By default it is stored using -[std::string](http://en.cppreference.com/w/cpp/string/basic_string) type. The -**CC_MQTTSN_CUSTOM_CLIENT_ID_STATIC_STORAGE_SIZE** variable can be used to set -the limit to the client ID string known at compile time, as the result the -[StaticString](https://github.com/commschamp/comms/blob/master/include/comms/util/StaticString.h) -type will be used instead. +--- +### CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC +Specify whether usage of the dynamic memory allocation is allowed. The value +defaults to **TRUE**. Setting the configuration to **FALSE** will ensure +that all the necessary configuration to avoid dynamic memory allocation has +been provided. In case some of the configuration is missing the compilation will +fail on `static_assert()` invocation with a message of what variable hasn't been +set property. ``` -# Allow up to 20 characters in client ID string -set(CC_MQTTSN_CUSTOM_CLIENT_ID_STATIC_STORAGE_SIZE 20) +# Disable dynamic memory allocation +set (CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC FALSE) ``` -### CC_MQTTSN_CUSTOM_CLIENT_GW_ADDR_STATIC_STORAGE_SIZE -The MQTT-SN protocol defines **SEARCHGW** message, which can contain the -address of the gateway. The client library doesn't make any assumptions on -maximum length of the address information. As the result it uses -[std::vector](http://en.cppreference.com/w/cpp/container/vector) type to -store such information. If the limit of the address's length is known at compile -time, use **CC_MQTTSN_CUSTOM_CLIENT_GW_ADDR_STATIC_STORAGE_SIZE** variable to -specify the limit. It will cause -[StaticVector](https://github.com/commschamp/comms/blob/master/include/comms/util/StaticVector.h) -to be used instead. +--- +### CC_MQTTSN_CLIENT_ALLOC_LIMIT +The client library allows allocation of multiple client managing objects +(**cc_mqttsn_client_alloc()** function). By default the value is **0**, +which means there is no limit on such +allocations. As a result every such object is dynamically allocated. +However, if there is a known limit for number of client managing objects, the +library will be requested to allocate, use **CC_MQTTSN_CLIENT_ALLOC_LIMIT** +variable to specify such limit. If the limit is specified the library will +use static pool for allocation and won't use heap for this purpose. ``` -# The address of GW can be stored within 4 bytes (IP4 for example) -set(CC_MQTTSN_CUSTOM_CLIENT_GW_ADDR_STATIC_STORAGE_SIZE 4) +# Only 1 client object will be allocated +set(CC_MQTTSN_CLIENT_ALLOC_LIMIT 1) ``` +Having **CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC** set to **FALSE** requires setting +of the **CC_MQTTSN_CLIENT_ALLOC_LIMIT** to a non-**0** value. + +--- +### CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY +The client library implements support for gateway discovery. When the +**CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY** variable is set to **TRUE** (default) +the functionality is enabled and the library allows runtime gateway discovery +control via the API. When the **CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY** variable is +set to **FALSE** the relevant code is removed by the compiler resulting in smaller +code size and relevant API being stubbed. -### CC_MQTTSN_CUSTOM_CLIENT_TOPIC_NAME_STATIC_STORAGE_SIZE -Similar to the client id, the maximum length of the topic string is not known, and -[std::string](http://en.cppreference.com/w/cpp/string/basic_string) type is -used as the result. Use **CC_MQTTSN_CUSTOM_CLIENT_TOPIC_NAME_STATIC_STORAGE_SIZE** -option to limit the maximum length and force usage of -[StaticString](https://github.com/commschamp/comms/blob/master/include/comms/util/StaticString.h) -instead. ``` -# The topics contain no more than 64 characters -set(CC_MQTTSN_CUSTOM_CLIENT_TOPIC_NAME_STATIC_STORAGE_SIZE 64) +# Disable gateway discovery +set(CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY FALSE) ``` -### CC_MQTTSN_CUSTOM_CLIENT_MSG_DATA_STATIC_STORAGE_SIZE -The MQTT-SN protocol defines **PUBLISH** message, which contains binary data -being published. If there is no known limit for the length of such data, the -[std::vector](http://en.cppreference.com/w/cpp/container/vector) type will be -used. However, if there is a limit known at compile time, the -**CC_MQTTSN_CUSTOM_CLIENT_MSG_DATA_STATIC_STORAGE_SIZE** can be used to specify the -limit and force usage of -[StaticVector](https://github.com/commschamp/comms/blob/master/include/comms/util/StaticVector.h) -type instead. +--- +### CC_MQTTSN_CLIENT_GATEWAY_INFOS_MAX_LIMIT +When gateway discovery is enabled (**CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY** is set to **TRUE**) +the client library maintains the list of discovered gateways it its internal data structures. +When the **CC_MQTTSN_CLIENT_GATEWAY_INFOS_MAX_LIMIT** variable is set to **0** (default), it means +that there is no limit to the amount of stored gateway infos and as the result +`std::vector<...>` is used to store them in memory. +When the **CC_MQTTSN_CLIENT_GATEWAY_INFOS_MAX_LIMIT** +variable is set to a non-**0** value the +[comms::util::StaticVector](https://github.com/commschamp/comms/blob/master/include/comms/util/StaticVector.h) +is used instead. It can be useful for bare-metal embedded systems without heap. + ``` -# The message data is no more than 128 bytes long -set(CC_MQTTSN_CUSTOM_CLIENT_MSG_DATA_STATIC_STORAGE_SIZE 128) +# Allow up to 5 gateways +set(CC_MQTTSN_CLIENT_GATEWAY_INFOS_MAX_LIMIT 5) ``` -### CC_MQTTSN_CUSTOM_CLIENT_ALLOC_LIMIT -The client library allows allocation of multiple client managing objects -(**cc_mqttsn_client_alloc()** function). By default, there is no limit on such -allocations, and as a result every such object is dynamically allocated. -However, if there is a known limit for number of client managing objects, the -library will be requested to allocate, use ***CC_MQTTSN_CUSTOM_CLIENT_ALLOC_LIMIT** -variable to specify such limit. If the limit is specified the library will -use static pool for allocation and won't use heap for this purpose. +Having **CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC** set to **FALSE** and +**CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY** set to **TRUE** requires setting +of the **CC_MQTTSN_CLIENT_GATEWAY_INFOS_MAX_LIMIT** to a non-**0** value. + +--- +### CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN +When gateway discovery is enabled (**CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY** is set to **TRUE**) +the client library maintains the list of discovered gateways with their respective +node addresses it its internal data structures. +When the **CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN** variable is set to **0** (default), it means +that there is no limit to the length of the stored gateway address and as the result +`std::vector<...>` is used to store it in memory. +When the **CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN** +variable is set to a non-**0** value the +[comms::util::StaticVector](https://github.com/commschamp/comms/blob/master/include/comms/util/StaticVector.h) +is used instead. It can be useful for bare-metal embedded systems without heap. + ``` -# Only 1 MQTT-SN client will be allocated -set(CC_MQTTSN_CUSTOM_CLIENT_ALLOC_LIMIT 1) -``` - -### CC_MQTTSN_CUSTOM_CLIENT_TRACKED_GW_LIMIT -The client library monitors and keep information about available gateways. When -the number of possible gateways is not known such information is stored using -[std::vector](http://en.cppreference.com/w/cpp/container/vector) type. However, -if there is a known limit on number of the available gateways on the nenwork, -the client library may be compiled to use -[StaticVector](https://github.com/commschamp/comms/blob/master/include/comms/util/StaticVector.h) -instead. Use **CC_MQTTSN_CUSTOM_CLIENT_TRACKED_GW_LIMIT** variable for this purpose. -``` -# The library doesn't need to support more than 1 gateway -set(CC_MQTTSN_CUSTOM_CLIENT_TRACKED_GW_LIMIT 1) -``` - -### CC_MQTTSN_CUSTOM_CLIENT_REGISTERED_TOPICS_LIMIT -The MQTT-SN protocol uses topic IDs in its **PUBLISH** message, and requires -topic strings to be registered with the gateway, which in turn is responsible -to allocate numeric IDs for them. The client object must store the registration -information. By default, there is no known limit on how many topics need to be -registered. As the result the library uses -[std::vector](http://en.cppreference.com/w/cpp/container/vector) to store all -the required information. However, if there is predefined limit for number of topics -the client object must be able to keep inside, the **CC_MQTTSN_CUSTOM_CLIENT_REGISTERED_TOPICS_LIMIT** -variable can be used to force usage of -[StaticVector](https://github.com/commschamp/comms/blob/master/include/comms/util/StaticVector.h) -type instead. -``` -# No need to support registration of more than 32 topic strings -set(CC_MQTTSN_CUSTOM_CLIENT_REGISTERED_TOPICS_LIMIT 32) -``` - -### CC_MQTTSN_CUSTOM_CLIENT_NO_STDLIB -Sometimes the bare metal applications are compiled without standard library -(using `-nostdlib` option with gcc compiler). To prevent the client library from -explicitly calling functions provided by the standard library, -set **CC_MQTTSN_CUSTOM_CLIENT_NO_STDLIB** value to `TRUE`. -``` -# Don't use standard library functions. -set(CC_MQTTSN_CUSTOM_CLIENT_NO_STDLIB TRUE) -``` -**NOTE**, that it doesn't prevent the library from using **STL**, and the compiler -may still generate code invoking functions like `memcpy` or `memset`. It will -be a responsibility of the driving code to implement required functionality. - -Also **NOTE**, that the library is implemented in **C++** (although it provides -**C** API). It requires manual invocation of the global and static objects' -constructors prior to invocation of the `main()` function. - -It is recommended to read -[Practical Guide to Bare Metal C++](https://arobenko.github.io/bare_metal_cpp) -free e-book, especially -[Know Your Compiler Output](https://arobenko.github.io/bare_metal_cpp/#_know_your_compiler_output) -chapter. It will guide the reader through necessary functions that may need -to be implemented to make the bare-metal application, written in C++, work. - -## Example for Bare-Metal Without Heap Configuration -The content of the custom client configuration file, which explicitly specifies -all unknown compile time limits and constants to prevent usage of dynamic -memory allocation and STL types like [std::string](http://en.cppreference.com/w/cpp/string/basic_string) -and [std::vector](http://en.cppreference.com/w/cpp/container/vector), may look -like this: +# Allow up to 4 bytes per gateway address +set(CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN 4) ``` -# Name of the client API -set (CC_MQTTSN_CUSTOM_CLIENT_NAME "bare_metal") -# Use up to 20 characters for client ID -set(CC_MQTTSN_CUSTOM_CLIENT_ID_STATIC_STORAGE_SIZE 20) +Having **CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC** set to **FALSE** and +**CC_MQTTSN_CLIENT_HAS_GATEWAY_DISCOVERY** set to **TRUE** requires setting +of the **CC_MQTTSN_CLIENT_GATEWAY_ADDR_FIXED_LEN** to a non-**0** value. -# The address of GW can be stored within 2 bytes -set(CC_MQTTSN_CUSTOM_CLIENT_GW_ADDR_STATIC_STORAGE_SIZE 2) +--- +### CC_MQTTSN_CLIENT_CLIENT_ID_FIELD_FIXED_LEN +To limit the length of the string used to store the "Client ID" information, use +the **CC_MQTTSN_CLIENT_CLIENT_ID_FIELD_FIXED_LEN** variable. When it is set to **0** (default), +it means that there is no limit to the length of the string value and as the result +`std::string` is used to store it in memory. +When the **CC_MQTTSN_CLIENT_CLIENT_ID_FIELD_FIXED_LEN** +variable is set to a non-**0** value the +[comms::util::StaticString](https://github.com/commschamp/comms/blob/master/include/comms/util/StaticString.h) +is used instead. It can be useful for bare-metal embedded systems without heap. -# Support only topics containing no more than 64 characters -set(CC_MQTTSN_CUSTOM_CLIENT_TOPIC_NAME_STATIC_STORAGE_SIZE 64) +``` +# Limit the max "client ID" length +set(CC_MQTTSN_CLIENT_CLIENT_ID_FIELD_FIXED_LEN 50) +``` -# The message data is no more than 128 bytes long -set(CC_MQTTSN_CUSTOM_CLIENT_MSG_DATA_STATIC_STORAGE_SIZE 128) +Having **CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC** set to **FALSE** requires setting +of the **CC_MQTTSN_CLIENT_CLIENT_ID_FIELD_FIXED_LEN** to a non-**0** value. -# The library won't support more than 1 allocated client object -set(CC_MQTTSN_CUSTOM_CLIENT_ALLOC_LIMIT 1) +--- +### CC_MQTTSN_CLIENT_HAS_WILL +The client library implements support for will update messages. When the +**CC_MQTTSN_CLIENT_HAS_WILL** variable is set to **TRUE** (default) +the functionality is enabled and the library allows runtime will +control via the API. When the **CC_MQTTSN_CLIENT_HAS_WILL** variable is +set to **FALSE** the relevant code is removed by the compiler resulting in smaller +code size and relevant API being stubbed. -# The library doesn't need to support more than 1 gateway -set(CC_MQTTSN_CUSTOM_CLIENT_TRACKED_GW_LIMIT 1) +``` +# Disable will functionality +set(CC_MQTTSN_CLIENT_HAS_WILL FALSE) +``` -# No need to support registration of more than 8 topic strings -set(CC_MQTTSN_CUSTOM_CLIENT_REGISTERED_TOPICS_LIMIT 8) +--- +### CC_MQTTSN_CLIENT_WILL_TOPIC_FIELD_FIXED_LEN +When the will functionality is enabled (**CC_MQTTSN_CLIENT_HAS_WILL** is set to **TRUE**) +and the **CC_MQTTSN_CLIENT_WILL_TOPIC_FIELD_FIXED_LEN** variable is set to **0** (default), it means +that there is no limit to the length of the will topic string and as the result +`std::string` is used to store it in memory. +When the **CC_MQTTSN_CLIENT_WILL_TOPIC_FIELD_FIXED_LEN** +variable is set to a non-**0** value the +[comms::util::StaticString](https://github.com/commschamp/comms/blob/master/include/comms/util/StaticString.h) +is used instead. It can be useful for bare-metal embedded systems without heap. -# Don't use standard library functions. -set(CC_MQTTSN_CUSTOM_CLIENT_NO_STDLIB TRUE) ``` -As the result of such configuration, the static library `cc_mqttsn_bare_metal_client` -will be generated, which will contain functions defined in -`include/cc_mqttsn_client/bare_metal_client.h" header file: -```c -CC_MqttsnClientHandle mqttsn_bare_metal_client_new(); +# Allow up to 32 bytes per will topic +set(CC_MQTTSN_CLIENT_WILL_TOPIC_FIELD_FIXED_LEN 32) +``` + +Having **CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC** set to **FALSE** and +**CC_MQTTSN_CLIENT_HAS_WILL** set to **TRUE** requires setting +of the **CC_MQTTSN_CLIENT_WILL_TOPIC_FIELD_FIXED_LEN** to a non-**0** value. + +--- +### CC_MQTTSN_CLIENT_WILL_DATA_FIELD_FIXED_LEN +When the will functionality is enabled (**CC_MQTTSN_CLIENT_HAS_WILL** is set to **TRUE**) +and the **CC_MQTTSN_CLIENT_WILL_DATA_FIELD_FIXED_LEN** variable is set to **0** (default), it means +that there is no limit to the length of the will topic string and as the result +`std::vector<...>` is used to store it in memory. +When the **CC_MQTTSN_CLIENT_WILL_DATA_FIELD_FIXED_LEN** +variable is set to a non-**0** value the +[comms::util::StaticVector](https://github.com/commschamp/comms/blob/master/include/comms/util/StaticVector.h) +is used instead. It can be useful for bare-metal embedded systems without heap. + +``` +# Allow up to 64 bytes per will topic +set(CC_MQTTSN_CLIENT_WILL_DATA_FIELD_FIXED_LEN 64) +``` +Having **CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC** set to **FALSE** and +**CC_MQTTSN_CLIENT_HAS_WILL** set to **TRUE** requires setting +of the **CC_MQTTSN_CLIENT_WILL_DATA_FIELD_FIXED_LEN** to a non-**0** value. + +--- +### CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN +When the **CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN** variable is set to **0** (default), it means +that there is no limit to the length of the topic string used in various messages and as the result +`std::string` is used to store it in memory. +When the **CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN** +variable is set to a non-**0** value the +[comms::util::StaticString](https://github.com/commschamp/comms/blob/master/include/comms/util/StaticString.h) +is used instead. It can be useful for bare-metal embedded systems without heap. + +``` +# Allow up to 32 bytes per topic +set(CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN 32) +``` + +Having **CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC** set to **FALSE** requires setting +of the **CC_MQTTSN_CLIENT_TOPIC_FIELD_FIXED_LEN** to a non-**0** value. + +--- +### CC_MQTTSN_CLIENT_DATA_FIELD_FIXED_LEN +When the **CC_MQTTSN_CLIENT_DATA_FIELD_FIXED_LEN** variable is set to **0** (default), it means +that there is no limit to the length of the binary data used in various messages and as the result +`std::vector<...>` is used to store it in memory. +When the **CC_MQTTSN_CLIENT_DATA_FIELD_FIXED_LEN** +variable is set to a non-**0** value the +[comms::util::StaticVector](https://github.com/commschamp/comms/blob/master/include/comms/util/StaticVector.h) +is used instead. It can be useful for bare-metal embedded systems without heap. + +``` +# Allow up to 128 bytes per binary data +set(CC_MQTTSN_CLIENT_DATA_FIELD_FIXED_LEN 128) +``` + +Having **CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC** set to **FALSE** requires setting +of the **CC_MQTTSN_CLIENT_DATA_FIELD_FIXED_LEN** to a non-**0** value. + +--- +### CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE +When serializing the output message the client library needs to allocate an output +buffer. When set to **0** (default), the output buffer type will be dynamic sized +`std::vector`. When the non-**0** value is assigned to the variable, the +[comms::util::StaticVector](https://github.com/commschamp/comms/blob/master/include/comms/util/StaticVector.h) +is used instead. It can be useful for bare-metal embedded systems without heap. + +``` +# Limit the length of the buffer required to store serialized message +set (CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE 1024) +``` + +Having **CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC** set to **FALSE** requires setting +of the **CC_MQTTSN_CLIENT_MAX_OUTPUT_PACKET_SIZE** to a non-**0** value. + +--- +### CC_MQTTSN_CLIENT_ASYNC_PUBS_LIMIT +The library supports issuing multiple "publish" operation in parallel (even though +the protocol requires the messages being sent one at a time). The relevant +information needs to be stored in memory. +When the value of the **CC_MQTTSN_CLIENT_ASYNC_PUBS_LIMIT** +variable is **0** (default), it means that there is no limit for such operations. In such chase the +client library uses `std::vector<...>` to store the relevant operation states in memory. +When **CC_MQTTSN_CLIENT_ASYNC_PUBS_LIMIT** is set to a non-**0** value the +[comms::util::StaticVector](https://github.com/commschamp/comms/blob/master/include/comms/util/StaticVector.h) +is used instead. It can be useful for bare-metal embedded systems without heap. + +``` +# Limit the amount allowed incomplete publish operations +set (CC_MQTTSN_CLIENT_ASYNC_PUBS_LIMIT 6) +``` + +Having **CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC** set to **FALSE** requires setting +of the **CC_MQTTSN_CLIENT_ASYNC_PUBS_LIMIT** to a non-**0** value. + +--- +### CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT +The client library allows issuing multiple asynchronous subscription operation. The operation +state needs to be preserved in the memory until +the appropriate acknowledgement message is received from the broker. Setting +the **CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT** variable to **0** (default) means there +is no limit to parallel subscription operations and their relative states are +stored using `std::vector<...>` storage type. +When the **CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT** +variable is set to a non-**0** value the +[comms::util::StaticVector](https://github.com/commschamp/comms/blob/master/include/comms/util/StaticVector.h) +is used instead. It can be useful for bare-metal embedded systems without heap. + +``` +# Limit the amount of ongoing (unacknowledged) subscribe operations +set (CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT 3) +``` + +Having **CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC** set to **FALSE** requires setting +of the **CC_MQTTSN_CLIENT_ASYNC_SUBS_LIMIT** to a non-**0** value. + +--- +### CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT +The client library allows issuing multiple asynchronous unsubscription operation. The operation +state needs to be preserved in the memory until +the appropriate acknowledgement message is received from the broker. Setting +the **CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT** variable to **0** (default) means there +is no limit to parallel unsubscription operations and their relative states are +stored using `std::vector<...>` storage type. +When the **CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT** +variable is set to a non-**0** value the +[comms::util::StaticVector](https://github.com/commschamp/comms/blob/master/include/comms/util/StaticVector.h) +is used instead. It can be useful for bare-metal embedded systems without heap. + +``` +# Limit the amount of ongoing (unacknowledged) unsubscribe operations +set (CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT 1) +``` + +Having **CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC** set to **FALSE** requires setting +of the **CC_MQTTSN_CLIENT_ASYNC_UNSUBS_LIMIT** to a non-**0** value. + +--- +### CC_MQTTSN_CLIENT_IN_REG_TOPICS_LIMIT +When receiving application messages from the gateway, the latter may issue a +topic registration request. When the **CC_MQTTSN_CLIENT_IN_REG_TOPICS_LIMIT** variable +is set to to **0** (default) the amount of such topic records is unlimited, i.e. +stored using `std::vector<...>` storage type. When the +**CC_MQTTSN_CLIENT_IN_REG_TOPICS_LIMIT** variable is set to non-**0** value the +[comms::util::StaticVector](https://github.com/commschamp/comms/blob/master/include/comms/util/StaticVector.h) +is used instead. It can be useful for bare-metal embedded systems without heap. + +``` +# Limit the amount of stored incoming registered topic IDs +set (CC_MQTTSN_CLIENT_IN_REG_TOPICS_LIMIT 1) +``` + +Having **CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC** set to **FALSE** requires setting +of the **CC_MQTTSN_CLIENT_IN_REG_TOPICS_LIMIT** to a non-**0** value. + + +--- +### CC_MQTTSN_CLIENT_OUT_REG_TOPICS_LIMIT +Similar to the **CC_MQTTSN_CLIENT_IN_REG_TOPICS_LIMIT** the +**CC_MQTTSN_CLIENT_OUT_REG_TOPICS_LIMIT** variable controls the storage type +for the topic IDs for the outgoing messages. + +``` +# Limit the amount of stored outgoing registered topic IDs +set (CC_MQTTSN_CLIENT_OUT_REG_TOPICS_LIMIT 1) +``` + +Having **CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC** set to **FALSE** requires setting +of the **CC_MQTTSN_CLIENT_OUT_REG_TOPICS_LIMIT** to a non-**0** value. + +--- +### CC_MQTTSN_CLIENT_HAS_ERROR_LOG +The client library allows reporting various error log messages via callback. +When **CC_MQTTSN_CLIENT_HAS_ERROR_LOG** variable is set to **TRUE** (default) such +error reporting is enabled. Setting the **CC_MQTTSN_CLIENT_HAS_ERROR_LOG** to +**FALSE** removes the error reporting functionality and as the result +reduces the code size and may slightly improve the runtime performance. + +``` +# Disable the error logging functionality +set (CC_MQTTSN_CLIENT_HAS_ERROR_LOG FALSE) +``` -void mqttsn_bare_metal_client_free(CC_MqttsnClientHandle client); +--- +### CC_MQTTSN_CLIENT_HAS_TOPIC_FORMAT_VERIFICATION +The client library implements verification of the used topics format to be a +valid one. When the **CC_MQTTSN_CLIENT_HAS_TOPIC_FORMAT_VERIFICATION** variable is +set to **TRUE** (default) the functionality is enabled and the library allows +runtime control of the feature via the API. When the **CC_MQTTSN_CLIENT_HAS_TOPIC_FORMAT_VERIFICATION** +is set to **FALSE** the relevant verification code is removed by the compiler +resulting in smaller code size and improved runtime performance. -void mqttsn_bare_metal_client_set_next_tick_program_callback( - CC_MqttsnClientHandle client, - CC_MqttsnNextTickProgramCb fn, - void* data); - -void mqttsn_bare_metal_client_set_cancel_next_tick_wait_callback( - CC_MqttsnClientHandle client, - CC_MqttsnCancelNextTickWaitCb fn, - void* data); +``` +# Disable the topic format verification functionality +set (CC_MQTTSN_CLIENT_HAS_TOPIC_FORMAT_VERIFICATION FALSE) +``` + +--- +### CC_MQTTSN_CLIENT_HAS_SUB_TOPIC_VERIFICATION +The client library implements tracking of the subscribed topics and discarding +the "rogue" messages from the broker if the received message is not supposed to +be received. When the **CC_MQTTSN_CLIENT_HAS_SUB_TOPIC_VERIFICATION** variable is +set to **TRUE** (default) the functionality is enabled and the library allows +runtime control of the feature via the API. When the **CC_MQTTSN_CLIENT_HAS_SUB_TOPIC_VERIFICATION** +is set to **FALSE** the relevant verification code is removed by the compiler +resulting in smaller code size and improved runtime performance. + +``` +# Disable the verification that the relevant subscription was performed when the message is reported from the broker +set (CC_MQTTSN_CLIENT_HAS_SUB_TOPIC_VERIFICATION FALSE) +``` + +--- +### CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT +When the subscription topic verification is enabled +(**CC_MQTTSN_CLIENT_HAS_SUB_TOPIC_VERIFICATION** is set to **TRUE**) the client +library needs to preserve the subscribed topics in the memory. When the +**CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT** variable is set to **0** (default), it means +that there is no limit to the amount of such topics and as the result +`std::vector<...>` is used to store them in memory. +When the **CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT** +variable is set to a non-**0** value the +[comms::util::StaticVector](https://github.com/commschamp/comms/blob/master/include/comms/util/StaticVector.h) +is used instead. It can be useful for bare-metal embedded systems without heap. + +``` +# Limit the amount of topic filters to store when the subscription verification is enabled +#set (CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT 20) +``` + +Having **CC_MQTTSN_CLIENT_HAS_DYN_MEM_ALLOC** set to **FALSE** and +**CC_MQTTSN_CLIENT_HAS_TOPIC_FORMAT_VERIFICATION** set to **TRUE** requires setting +of the **CC_MQTTSN_CLIENT_SUB_FILTERS_LIMIT** to a non-**0** value. + +--- +### CC_MQTTSN_CLIENT_MAX_QOS +By default the library supports all the QoS values (0 to 2). It is possible to +disable support for high QoS values at compile time and as the result reducing +the library's code size. It can be useful for bare-metal embedded system with +a small ROM size. + +``` +# Support only QoS0 and QoS1 messages +set (CC_MQTTSN_CLIENT_MAX_QOS 1) +``` + +--- +## Example for Bare-Metal Without Heap Configuration +The content of the custom client configuration file, which explicitly specifies +all compile time limits and constants to prevent usage of dynamic +memory allocation and STL types like [std::string](http://en.cppreference.com/w/cpp/string/basic_string) +and [std::vector](http://en.cppreference.com/w/cpp/container/vector), may look +like [this](../client/lib/script/BareMetalTestConfig.cmake): + +Setting "bm" as a custom client name results in having a static library called `cc_mqtt311_bm_client`. +All the API functions are defined in `cc_mqtt311_client/bm_client.h` header file: +```c +CC_Mqtt311ClientHandle cc_mqtt311_bm_client_alloc(); + +void cc_mqtt311_bm_client_free(CC_Mqtt311ClientHandle client); ... - ``` -**NOTE**, that all the functions have **mqttsn_bare_metal_** prefix due to the -fact of setting value of **CC_MQTTSN_CUSTOM_CLIENT_NAME** variable to "bare_metal" string. + From 211524893728b36dfc8139af97fcda23712fd8b1 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 14 Aug 2024 16:49:53 +1000 Subject: [PATCH 102/106] Updated build instructions. --- CMakeLists.txt | 5 ++ doc/BUILD.md | 86 ++++++++++++++++++++++++++-------- gateway/lib/src/CMakeLists.txt | 6 ++- 3 files changed, 77 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7347d87f..cb31cab4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,9 @@ project ("cc_mqttsn_libs") option (CC_MQTTSN_WARN_AS_ERR "Do NOT treat warning as error" ON) option (CC_MQTTSN_CLIENT_DEFAULT_LIB "Build and install default variant of MQTT-SN Client library" ON) +option (CC_MQTTSN_CLIENT_LIB_FORCE_PIC "Force Position Independent Code (PIC) when compiling client library(ies)" OFF) option (CC_MQTTSN_GATEWAY "Build and install MQTT-SN client library(ies) and applications" ON) +option (CC_MQTTSN_GATEWAY_LIB_FORCE_PIC "Force Position Independent Code (PIC) when compiling gateway library" OFF) option (CC_MQTTSN_CLIENT_APPS "Build and install client applications" ${CC_MQTTSN_CLIENT_DEFAULT_LIB}) option (CC_MQTTSN_GATEWAY_APPS "Build and install gateway applications" ${CC_MQTTSN_GATEWAY}) option (CC_MQTTSN_BUILD_UNIT_TESTS "Build unittests." OFF) @@ -11,6 +13,9 @@ option (CC_MQTTSN_UNIT_TEST_WITH_VALGRIND "Disable valgrind in unittests." OFF) option (CC_MQTTSN_USE_CCACHE "Use ccache on unix system" OFF) option (CC_MQTTSN_WITH_SANITIZERS "Build with sanitizers" OFF) +# CMake built-in options +option (BUILD_SHARED_LIBS "Build as shared libraries" OFF) + # Extra variables # CC_MQTTSN_CUSTOM_CLIENT_CONFIG_FILES - List of custom client configuration files # CC_MQTTSN_DEFAULT_CLIENT_CONFIG_FILE - Custom congiruation of the default client. diff --git a/doc/BUILD.md b/doc/BUILD.md index 81eba15f..ffd8622b 100644 --- a/doc/BUILD.md +++ b/doc/BUILD.md @@ -22,42 +22,90 @@ via standard **CMAKE_PREFIX_PATH** cmake variable. There are also scripts ( which can help in preparation of these dependencies. They are also used in configuration of the [github actions](../.github/workflows/actions_build.yml). -## Build and Install Examples +The provided **applications** use [Boost](https://www.boost.org) libraries to +parse their command line arguments as well as manage their event loop / network +connections. In case the application are compiled and the [Boost](https://www.boost.org) libraries +do not reside in a default system location, use [BOOST_ROOT](https://cmake.org/cmake/help/latest/module/FindBoost.html#hints) +variable to specify their install directory. It is recommended to also use `-DBoost_USE_STATIC_LIBS=ON` parameter to force +linkage with static Boost libraries (especially on Windows). -The examples below are Linux/Unix system oriented, i.e. they use **make** utility -to build the "install" target after configuration with **cmake**. +## Choosing C++ Standard +The default and minimal required C++ standard version to build this project is **17**. However it +is possible to increase it using the **CMAKE_CXX_STANDARD** cmake variable. + +## Building as Shared Library +By default the library is built as static one. It is possible to build it as a shared library +by using the built-in **BUILD_SHARED_LIBS** cmake option + +## Forcing Position Independent Code +When the libraries are built as **shared** ones then the position independent code is enabled +automatically. However, when the libraries built as **static**, but will become part of +some other shared libraries, then forcefully enabling the position independent code may be required. +Use provided **CC_MQTTSN_CLIENT_LIB_FORCE_PIC** and/or **CC_MQTTSN_GATEWAY_LIB_FORCE_PIC** cmake +options to enable it. -On Windows -systems with Visual Studio compiler, the CMake utility generates Visual Studio -solution files by default. Build "install" project. It is also possible to -generate Makefile-s on Windows by providing additional **-G "NMake Makefiles"** option -to **cmake**. In this case use **nmake** utility instead of **make**. +Note that it is also possible to force position independent code using global +**CMAKE_POSITION_INDEPENDENT_CODE** variable set to **ON**. In such case all the +application will also be compiled with position independent code. + +## Examples of Build and Install +The examples below are Linux/Unix system oriented, i.e. they use **make** utility +to build the "install" target after configuration with **cmake**. For Windows +platforms please remember to use **-G** option to specify the generator and with +later versions of cmake also **-A** option to specify the architecture, such as +**-G "NMake Makefiles"** (required environment setup beforehand) or +**cmake -G "Visual Studio 16 2019" -A x64**. Please review the examples below and use appropriate option that suites your -needs. Remember to add **-DCMAKE_BUILD_TYPE=Release** option for release +needs. Remember to use **-DCMAKE_BUILD_TYPE=Release** option for release builds. -### Build MQTT-SN Client/Gateway Libraries and Applications + +### Build All Library and Applications +``` +$> mkdir build && cd build +$> cmake .. -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH=/path/to/comms/install\;/path/to/cc.mqtt311.generated/install\;/path/to/cc.mqttsn.generated/install +$> cmake --build . --config Release --target install +``` + +### Build Only Libraries Without Applications ``` +$> mkdir build && cd build $> cmake .. -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_PREFIX_PATH=/path/to/comms/install\;/path/to/cc.mqttsn.generated/install\;/path/to/cc.mqtt311.generated/install + -DCMAKE_PREFIX_PATH=/path/to/comms/install\;/path/to/cc.mqtt311.generated/install;/path/to/cc.mqttsn.generated/install \ + -DCC_MQTTSN_CLIENT_APPS=OFF -DCC_MQTTSN_GATEWAY_APPS=OFF +$> cmake --build . --config Release --target install ``` -### Build Two Custom Client Libraries +### Build AS Shared Libraries +``` +$> mkdir build && cd build +$> cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON \ + -DCMAKE_PREFIX_PATH=/path/to/comms/install\;/path/to/cc.mqtt311.generated/install;/path/to/cc.mqttsn.generated/install +$> cmake --build . --config Release --target install +``` + +### Build Static Libraries With Position Independent Code +``` +$> mkdir build && cd build +$> cmake .. -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH=/path/to/comms/install\;/path/to/cc.mqtt311.generated/install;/path/to/cc.mqttsn.generated/install \ + -DCC_MQTTSN_CLIENT_LIB_FORCE_PIC=ON -DCC_MQTTSN_GATEWAY_LIB_FORCE_PIC=ON +$> cmake --build . --config Release --target install +``` + +### Build Two Custom Client Libraries Without Gateway See [custom_client_build.md](custom_client_build.md) for details on custom build configuration ``` -$> cmake .. -DCMAKE_BUILD_TYPE=Release -DCC_MQTTSN_CLIENT_DEFAULT_LIB=OFF \ - -DCC_MQTTSN_GATEWAY=OFF \ +$> mkdir build && cd build +$> cmake .. -DCMAKE_BUILD_TYPE=Release -DCC_MQTTSN_CLIENT_DEFAULT_LIB=OFF -DCC_MQTTSN_GATEWAY=OFF \ -DCC_MQTTSN_CUSTOM_CLIENT_CONFIG_FILES=config1.cmake\;config2.cmake \ -DCMAKE_PREFIX_PATH=/path/to/comms/install\;/path/to/cc.mqttsn.generated/install +$> cmake --build . --config Release --target install ``` -### Build Gateway Library and Application(s) -``` -$> cmake .. -DCMAKE_BUILD_TYPE=Release -DCC_MQTTSN_CLIENT_DEFAULT_LIB=OFF \ - -DCMAKE_PREFIX_PATH=/path/to/comms/install\;/path/to/cc.mqttsn.generated/install\;/path/to/cc.mqtt311.generated/install -``` diff --git a/gateway/lib/src/CMakeLists.txt b/gateway/lib/src/CMakeLists.txt index edf771b0..3d789d45 100644 --- a/gateway/lib/src/CMakeLists.txt +++ b/gateway/lib/src/CMakeLists.txt @@ -24,7 +24,7 @@ function (lib_mqttsn_gateway) session_op/WillUpdate.cpp ) - add_library (${name} STATIC ${src}) + add_library (${name} ${src}) add_library (cc::${name} ALIAS ${name}) target_link_libraries(${name} PRIVATE cc::cc_mqtt311 cc::cc_mqttsn cc::comms) @@ -34,6 +34,10 @@ function (lib_mqttsn_gateway) INTERFACE_LINK_LIBRARIES "" ) + if (CC_MQTTSN_GATEWAY_LIB_FORCE_PIC) + set_property(TARGET ${name} PROPERTY POSITION_INDEPENDENT_CODE ON) + endif () + target_include_directories( ${name} BEFORE PUBLIC From 2df4b5025968388fedc4973693782b72849c1ec9 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 15 Aug 2024 08:21:48 +1000 Subject: [PATCH 103/106] Updated README. --- README.md | 69 ++++++++++++++++++++++++++----------------------------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index d546e97c..d8c93078 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +# Overview The [MQTT-SN](https://www.oasis-open.org/committees/download.php/66091/MQTT-SN_spec_v1.2.pdf) protocol is a sibling of [MQTT](http://mqtt.org). While **MQTT** is designed to be used over a **reliable stream** transport protocol (such as TCP/IP), the **MQTT-SN** @@ -14,15 +15,15 @@ and **without errors**. This repository provides **single threaded**, **asynchronous**, **non-blocking**, easy to use, suitable for **embedded** platforms, well documented libraries -of MQTT-SN client and gateway, which are +of MQTT-SN **client** and **gateway**, which are completely generic and allow usage of MQTT-SN protocol over any I/O link. See details below. # Client Library This repository provides the MQTT-SN **client** library. It is implemented -using C++(11) programming language, but provides **C** interface. The library's +using C++(17) programming language, but provides **C** interface. The library's code doesn't use [RTTI](https://en.wikipedia.org/wiki/Run-time_type_information) -or exceptions, but by default +or exceptions. By default the library's implementation uses C++ STL data types, such as [std::string](http://en.cppreference.com/w/cpp/string/basic_string) and [std::vector](http://en.cppreference.com/w/cpp/container/vector). However, @@ -32,18 +33,23 @@ refer to [doc/custom_client_build.md](doc/custom_client_build.md) for instructio how to do it. The doxygen generated documentation of the library with its full tutorial can -be downloaded from the [release artefacts](releases). +be downloaded from the [release artefacts](releases) or browsed +[online](https://commschamp.github.io/cc_mqttsn_client_doc). # Client Applications -The [Client Library](#client-library) described above is I/O link agnostic, -it allows to do any additional packatisation of the sent data to allow -correct delivery of the messages. This repository also provides a couple of -MQTT-SN **client** "publish"(**cc_mqttsn_pub_udp**) and "subscribe" -(**cc_mqttsn_sub_udp**) example applications, which use -UDP/IP as its datagram transport layer. These applications are also using -[Qt5](https://doc.qt.io/qt-5/) framework for their operation. It means that if -proper QT5 libraries are not installed or can not be found, these applications won't -be compiled. +This repository also provides extra utilities (example applications) which +use the [client library](#client-library) described above. + +* **cc_mqttsn_gw_discover** - Discover available gateways client application +* **cc_mqttsn_client_pub** - Publish client application +* **cc_mqttsn_client_sub** - Subscribe client application + +These applications use [Boost](https://www.boost.org) libraries, +([boost::program_options](https://www.boost.org/doc/libs/1_83_0/doc/html/program_options.html) +to parse the command line arguments and +[boost::asio](https://www.boost.org/doc/libs/1_83_0/doc/html/boost_asio.html) to run +the events loop and manage network connection(s)). Currently only UDP/IP network +connection type is supported. # Gateway Library Just like the [Client Library](#client-library) allows additional data @@ -53,36 +59,27 @@ which provides the required core functionality and allows implementation of any gateway application, suitable for the type of I/O link to the client(s) being used. -This repository provides such a library. It is implemented using C++(11) +This repository provides such a library. It is implemented using C++(17) programming language and provides both **C++** and **C** interfaces to use. The gateway uses [v3.1.1](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.pdf) of the MQTT protocol to connect to and communicate with the broker. The doxygen generated documentation of the library with its full tutorial can -be downloaded from the [release artefacts](releases). +be downloaded from the [release artefacts](releases) or browsed +[online](https://commschamp.github.io/cc_mqttsn_gateway_doc). # Gateway Applications -The [Gateway Library](#gateway-library) described above is I/O link agnostic, -it allows to do any additional packatisation of the sent data to allow -correct delivery of the messages. This repository also provides the -MQTT-SN gateway(**cc_mqttsn_gateway_udp**) example application, which uses -UDP/IP as its datagram transport layer. The application also using -[Qt5](https://doc.qt.io/qt-5/) framework for its operation. It means that if -proper QT5 libraries are not installed or can not be found, the gateway example -application won't be compiled. - -# Spec Deviations -There are a couple of deviations from official MQTT-SN -[v1.2](https://www.oasis-open.org/committees/download.php/66091/MQTT-SN_spec_v1.2.pdf) -spec. - -- The [client](#client-library) does NOT send **GWINFO** messages as a response -to **SEARCHGW** message from other clients, on behalf of the gateway. -- The [gateway](#gateway-library) sends **GWINFO** as a response to the -**SEARCHGW** message directly to the requesting client, not broadcasting it -like the protocol specifies. - -All other behaviour is implemented as specified. +This repository also provides extra applications which +use the [gateway library](#gateway-library) described above. + +* **cc_mqttsn_gateway_app** - Transparent gateway application. + +These applications use [Boost](https://www.boost.org) libraries, +([boost::program_options](https://www.boost.org/doc/libs/1_83_0/doc/html/program_options.html) +to parse the command line arguments and +[boost::asio](https://www.boost.org/doc/libs/1_83_0/doc/html/boost_asio.html) to run +the events loop and manage network connection(s)). Currently only UDP/IP network +connection type is supported. # How to Build Detailed instructions on how to build and install all the components can be From f0ab7ea5aa377ed3a2ec915c0b450ff952e926b0 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 15 Aug 2024 08:28:57 +1000 Subject: [PATCH 104/106] Cosmetic improvement to the CMake configuration. --- CMakeLists.txt | 6 +++--- client/lib/src/ClientImpl.h | 1 - doc/BUILD.md | 6 +++--- gateway/CMakeLists.txt | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cb31cab4..9a8103e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,10 +4,10 @@ project ("cc_mqttsn_libs") option (CC_MQTTSN_WARN_AS_ERR "Do NOT treat warning as error" ON) option (CC_MQTTSN_CLIENT_DEFAULT_LIB "Build and install default variant of MQTT-SN Client library" ON) option (CC_MQTTSN_CLIENT_LIB_FORCE_PIC "Force Position Independent Code (PIC) when compiling client library(ies)" OFF) -option (CC_MQTTSN_GATEWAY "Build and install MQTT-SN client library(ies) and applications" ON) -option (CC_MQTTSN_GATEWAY_LIB_FORCE_PIC "Force Position Independent Code (PIC) when compiling gateway library" OFF) option (CC_MQTTSN_CLIENT_APPS "Build and install client applications" ${CC_MQTTSN_CLIENT_DEFAULT_LIB}) -option (CC_MQTTSN_GATEWAY_APPS "Build and install gateway applications" ${CC_MQTTSN_GATEWAY}) +option (CC_MQTTSN_GATEWAY_LIB "Build and install MQTT-SN gateway library" ON) +option (CC_MQTTSN_GATEWAY_LIB_FORCE_PIC "Force Position Independent Code (PIC) when compiling gateway library" OFF) +option (CC_MQTTSN_GATEWAY_APPS "Build and install gateway applications" ${CC_MQTTSN_GATEWAY_LIB}) option (CC_MQTTSN_BUILD_UNIT_TESTS "Build unittests." OFF) option (CC_MQTTSN_UNIT_TEST_WITH_VALGRIND "Disable valgrind in unittests." OFF) option (CC_MQTTSN_USE_CCACHE "Use ccache on unix system" OFF) diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index 95d9c91a..03550159 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -262,7 +262,6 @@ class ClientImpl final : public ProtMsgHandler using OpPtrsList = ObjListType; - // using OpToDeletePtrsList = ObjListType; using OutputBuf = ObjListType; void doApiEnter(); diff --git a/doc/BUILD.md b/doc/BUILD.md index ffd8622b..69902b98 100644 --- a/doc/BUILD.md +++ b/doc/BUILD.md @@ -61,7 +61,7 @@ needs. Remember to use **-DCMAKE_BUILD_TYPE=Release** option for release builds. -### Build All Library and Applications +### Build All Libraries and Applications ``` $> mkdir build && cd build $> cmake .. -DCMAKE_BUILD_TYPE=Release \ @@ -78,7 +78,7 @@ $> cmake .. -DCMAKE_BUILD_TYPE=Release \ $> cmake --build . --config Release --target install ``` -### Build AS Shared Libraries +### Build as Shared Libraries ``` $> mkdir build && cd build $> cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON \ @@ -100,7 +100,7 @@ See [custom_client_build.md](custom_client_build.md) for details on custom build configuration ``` $> mkdir build && cd build -$> cmake .. -DCMAKE_BUILD_TYPE=Release -DCC_MQTTSN_CLIENT_DEFAULT_LIB=OFF -DCC_MQTTSN_GATEWAY=OFF \ +$> cmake .. -DCMAKE_BUILD_TYPE=Release -DCC_MQTTSN_CLIENT_DEFAULT_LIB=OFF -DCC_MQTTSN_GATEWAY_LIB=OFF \ -DCC_MQTTSN_CUSTOM_CLIENT_CONFIG_FILES=config1.cmake\;config2.cmake \ -DCMAKE_PREFIX_PATH=/path/to/comms/install\;/path/to/cc.mqttsn.generated/install $> cmake --build . --config Release --target install diff --git a/gateway/CMakeLists.txt b/gateway/CMakeLists.txt index e2fb1c8b..7437718d 100644 --- a/gateway/CMakeLists.txt +++ b/gateway/CMakeLists.txt @@ -1,4 +1,4 @@ -if (NOT CC_MQTTSN_GATEWAY) +if (NOT CC_MQTTSN_GATEWAY_LIB) return () endif () From 6b18f07d18b7b51d1434aabdccc47481579c2f8d Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 15 Aug 2024 16:23:48 +1000 Subject: [PATCH 105/106] Using v2.8.1 of the cc.mqttsn.generated in the github actions. --- .github/workflows/actions_build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index 77a53e45..59d8f745 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -4,7 +4,7 @@ on: [push] env: COMMS_TAG: v5.2.5 - CC_MQTTSN_TAG: develop + CC_MQTTSN_TAG: v2.8.1 CC_MQTT311_TAG: v2.8 jobs: From 888fcfe7f6bb146dc9dca40e237fb657c50925c5 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 16 Aug 2024 07:58:05 +1000 Subject: [PATCH 106/106] Requiring v2.8.1 of the cc.mqttsn.generated and v2.8 of the cc.mqtt311.generated. --- client/lib/src/ProtocolDefs.h | 2 +- gateway/lib/src/common.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/lib/src/ProtocolDefs.h b/client/lib/src/ProtocolDefs.h index 7fbd4584..a0f3be23 100644 --- a/client/lib/src/ProtocolDefs.h +++ b/client/lib/src/ProtocolDefs.h @@ -20,7 +20,7 @@ #include -static_assert(COMMS_MAKE_VERSION(2, 7, 1) <= cc_mqttsn::version(), +static_assert(COMMS_MAKE_VERSION(2, 8, 1) <= cc_mqttsn::version(), "The version of cc.mqttsn.generated library is too old"); namespace cc_mqttsn_client diff --git a/gateway/lib/src/common.h b/gateway/lib/src/common.h index 1caf8b93..cfae09e0 100644 --- a/gateway/lib/src/common.h +++ b/gateway/lib/src/common.h @@ -20,10 +20,10 @@ #include "RegMgr.h" -static_assert(COMMS_MAKE_VERSION(2, 7, 1) <= cc_mqttsn::version(), +static_assert(COMMS_MAKE_VERSION(2, 8, 1) <= cc_mqttsn::version(), "The version of cc.mqttsn.generated library is too old"); -static_assert(COMMS_MAKE_VERSION(2, 7, 0) <= cc_mqtt311::version(), +static_assert(COMMS_MAKE_VERSION(2, 8, 0) <= cc_mqtt311::version(), "The version of cc.mqtt311.generated library is too old"); namespace cc_mqttsn_gateway