From 7c719f2b2dcac1dd0c6636f7cf63f377e65219da Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 6 Mar 2024 09:00:53 +1000 Subject: [PATCH 01/48] Updating next version to be v0.3.2 --- client/lib/include/cc_mqtt5_client/common.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/lib/include/cc_mqtt5_client/common.h b/client/lib/include/cc_mqtt5_client/common.h index bb1989d..3280d28 100644 --- a/client/lib/include/cc_mqtt5_client/common.h +++ b/client/lib/include/cc_mqtt5_client/common.h @@ -25,7 +25,7 @@ extern "C" { /// @brief Patch level of the library /// @ingroup global -#define CC_MQTT5_CLIENT_PATCH_VERSION 1U +#define CC_MQTT5_CLIENT_PATCH_VERSION 2U /// @brief Macro to create numeric version as single unsigned number /// @ingroup global From 8faf185f643faa69c252f3bbfe98ba772813e178 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 28 Mar 2024 08:31:09 +1000 Subject: [PATCH 02/48] Added documentation of the CC_Mqtt5DisconnectInfo struct. --- client/lib/include/cc_mqtt5_client/common.h | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/client/lib/include/cc_mqtt5_client/common.h b/client/lib/include/cc_mqtt5_client/common.h index 3280d28..35fb62c 100644 --- a/client/lib/include/cc_mqtt5_client/common.h +++ b/client/lib/include/cc_mqtt5_client/common.h @@ -342,13 +342,15 @@ typedef struct unsigned m_userPropsCount; ///< Amount of elements in the "User Properties" array, defaults to 0, not added when 0. } CC_Mqtt5AuthInfo; +/// @brief Broker disconnection information +/// @ingroup global typedef struct { - CC_Mqtt5ReasonCode m_reasonCode; - const char* m_reasonStr; - const char* m_serverRef; - const CC_Mqtt5UserProp* m_userProps; - unsigned m_userPropsCount; + CC_Mqtt5ReasonCode m_reasonCode; ///< "Reason Code" reported by the broker + const char* m_reasonStr; ///< "Reason String" property, NULL if not reported + const char* m_serverRef; ///< "Server Reference" property, NULL if not reported + const CC_Mqtt5UserProp* m_userProps; ///< Pointer to "User Properties" array, can be NULL + unsigned m_userPropsCount; ///< Amount of elements in the "User Properties" array, defaults to 0, not added when 0. } CC_Mqtt5DisconnectInfo; /// @brief Configuration structure of the "disconnect" operation. @@ -503,6 +505,9 @@ typedef unsigned (*CC_Mqtt5CancelNextTickWaitCb)(void* data); typedef void (*CC_Mqtt5SendOutputDataCb)(void* data, const unsigned char* buf, unsigned bufLen); /// @brief Callback used to report unsolicited disconnection of the broker. +/// @details When invoked the "info" is present if and only if the +/// broker disconnection report is due to the reception of the @b DISCONNECT +/// message from the broker. /// @param[in] data Pointer to user data object, passed as the last parameter to /// the request call. /// @param[in] info Extra disconnect information when reported by the broker. Can be NULL. From 65d6d5a74a9d170aa2f3fab153aa993b8b4f626f Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 28 Mar 2024 08:50:30 +1000 Subject: [PATCH 03/48] Documenting disconnection due to "keep alive" timeout. --- client/lib/doxygen/main.dox | 10 ++++++++++ client/lib/src/op/KeepAliveOp.cpp | 1 + 2 files changed, 11 insertions(+) diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index 1c9e583..f30ce1f 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -1474,6 +1474,16 @@ /// The information passed in the @b DISCONNECT message is used to populate reported /// disconnection information. /// +/// @subsection cc_mqtt5_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 PING 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_Mqtt5AsyncOpStatus_BrokerDisconnected as their status report. +/// @li Invokes the @ref cc_mqtt5_client_callbacks_broker_disconnect "disconnection report callback" +/// registered using the @b cc_mqtt5_client_set_broker_disconnect_report_callback() function +/// without any disconnection information. +/// /// @subsection cc_mqtt5_client_unsolicited_disconnect_protocol_error Detecting Protocol Error /// In case the broker doesn't fully comply with the MQTT5 specification or there is /// some unexpected data corruption the library responds in the following way: diff --git a/client/lib/src/op/KeepAliveOp.cpp b/client/lib/src/op/KeepAliveOp.cpp index b8df0f7..5092f38 100644 --- a/client/lib/src/op/KeepAliveOp.cpp +++ b/client/lib/src/op/KeepAliveOp.cpp @@ -168,6 +168,7 @@ void KeepAliveOp::sendPing() void KeepAliveOp::pingTimeoutInternal() { + errorLog("The broker did not respond to PING"); COMMS_ASSERT(!m_respTimer.isActive()); sendDisconnectWithReason(DisconnectReason::KeepAliveTimeout); client().notifyDisconnected(true); From 0f4655690fa8ac20f72ef598fbe9bcfa3b46ca81 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 29 Mar 2024 15:33:50 +1000 Subject: [PATCH 04/48] Implementing clause MQTT-4.4.0-1. Preserving "send" and "recv" ops when broker is disconnected. --- client/lib/src/ClientImpl.cpp | 57 ++++++++++++++++++++----- client/lib/src/ClientImpl.h | 16 +++++-- client/lib/src/op/ConnectOp.cpp | 4 +- client/lib/src/op/DisconnectOp.cpp | 2 +- client/lib/src/op/KeepAliveOp.cpp | 8 ++-- client/lib/src/op/Op.cpp | 2 +- client/lib/src/op/SendOp.cpp | 8 ++++ client/lib/src/op/SendOp.h | 1 + client/lib/test/unit/UnitTestPublish.th | 20 ++++----- 9 files changed, 86 insertions(+), 32 deletions(-) diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 223e9df..5f288ee 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -51,7 +51,7 @@ void updateEc(CC_Mqtt5ErrorCode* ec, CC_Mqtt5ErrorCode val) ClientImpl::~ClientImpl() { COMMS_ASSERT(m_apiEnterCount == 0U); - terminateAllOps(CC_Mqtt5AsyncOpStatus_Aborted); + terminateOps(CC_Mqtt5AsyncOpStatus_Aborted, TerminateMode_AbortSendRecvOps); } CC_Mqtt5ErrorCode ClientImpl::init() @@ -84,7 +84,7 @@ CC_Mqtt5ErrorCode ClientImpl::init() } } - terminateAllOps(CC_Mqtt5AsyncOpStatus_Aborted); + terminateOps(CC_Mqtt5AsyncOpStatus_Aborted, TerminateMode_KeepSendRecvOps); m_ops.clear(); bool firstConnect = m_sessionState.m_firstConnect; m_sessionState = SessionState(); @@ -118,7 +118,7 @@ unsigned ClientImpl::processData(const std::uint8_t* iter, unsigned len) [this, &disconnectReason]() { sendDisconnectMsg(disconnectReason); - notifyDisconnected(true, CC_Mqtt5AsyncOpStatus_ProtocolError); + brokerDisconnected(true, CC_Mqtt5AsyncOpStatus_ProtocolError); }); unsigned consumed = 0; @@ -728,7 +728,7 @@ void ClientImpl::handle(PublishMsg& msg) } while (false); if (disconnectSent) { - notifyDisconnected(true); + brokerDisconnected(true); return; } } @@ -936,19 +936,44 @@ void ClientImpl::doApiGuard() auto guard = apiEnter(); } -void ClientImpl::notifyConnected() +void ClientImpl::brokerConnected(bool sessionPresent) { m_sessionState.m_connected = true; - createKeepAliveOpIfNeeded(); + + do { + if (sessionPresent) { + for (auto& sendOpPtr : m_sendOps) { + sendOpPtr->postReconnectionResend(); + } + + 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_Mqtt5AsyncOpStatus_Aborted); + } + } while (false); + + createKeepAliveOpIfNeeded(); } -void ClientImpl::notifyDisconnected(bool reportDisconnection, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5DisconnectInfo* info) +void ClientImpl::brokerDisconnected( + bool reportDisconnection, + CC_Mqtt5AsyncOpStatus status, + const CC_Mqtt5DisconnectInfo* info) { COMMS_ASSERT(reportDisconnection || (info == nullptr)); m_sessionState.m_initialized = false; // Require re-initialization m_sessionState.m_connected = false; - terminateAllOps(status); + terminateOps(status, TerminateMode_KeepSendRecvOps); if (reportDisconnection) { COMMS_ASSERT(m_brokerDisconnectReportCb != nullptr); @@ -1016,13 +1041,23 @@ void ClientImpl::createKeepAliveOpIfNeeded() m_keepAliveOps.push_back(std::move(ptr)); } -void ClientImpl::terminateAllOps(CC_Mqtt5AsyncOpStatus status) +void ClientImpl::terminateOps(CC_Mqtt5AsyncOpStatus status, TerminateMode mode) { m_sessionState.m_terminating = true; for (auto* op : m_ops) { - if (op != nullptr) { - op->terminateOp(status); + 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); } } diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index b463299..cce6d83 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -135,8 +135,11 @@ class ClientImpl final : public ProtMsgHandler CC_Mqtt5ErrorCode sendMessage(const ProtMessage& msg); void opComplete(const op::Op* op); void doApiGuard(); - void notifyConnected(); - void notifyDisconnected(bool reportDisconnection, CC_Mqtt5AsyncOpStatus status = CC_Mqtt5AsyncOpStatus_BrokerDisconnected, const CC_Mqtt5DisconnectInfo* info = nullptr); + void brokerConnected(bool sessionPresent); + void brokerDisconnected( + bool reportDisconnection, + CC_Mqtt5AsyncOpStatus status = CC_Mqtt5AsyncOpStatus_BrokerDisconnected, + const CC_Mqtt5DisconnectInfo* info = nullptr); void reportMsgInfo(const CC_Mqtt5MessageInfo& info); TimerMgr& timerMgr() @@ -205,10 +208,17 @@ class ClientImpl final : public ProtMsgHandler using OpToDeletePtrsList = ObjListType; using OutputBuf = ObjListType; + enum TerminateMode + { + TerminateMode_KeepSendRecvOps, + TerminateMode_AbortSendRecvOps, + TerminateMode_NumOfValues + }; + void doApiEnter(); void doApiExit(); void createKeepAliveOpIfNeeded(); - void terminateAllOps(CC_Mqtt5AsyncOpStatus status); + void terminateOps(CC_Mqtt5AsyncOpStatus status, TerminateMode mode); void cleanOps(); void errorLogInternal(const char* msg); void sendDisconnectMsg(DisconnectMsg::Field_reasonCode::Field::ValueType reason); diff --git a/client/lib/src/op/ConnectOp.cpp b/client/lib/src/op/ConnectOp.cpp index b2f61db..0224875 100644 --- a/client/lib/src/op/ConnectOp.cpp +++ b/client/lib/src/op/ConnectOp.cpp @@ -711,7 +711,7 @@ void ConnectOp::handle(ConnackMsg& msg) state.m_highQosSendLimit = std::min(state.m_highQosSendLimit, Config::SendMaxLimit); } - client().notifyConnected(); + client().brokerConnected(response.m_sessionPresent); } void ConnectOp::handle(DisconnectMsg& msg) @@ -751,7 +751,7 @@ void ConnectOp::handle(DisconnectMsg& msg) completeOpInternal(CC_Mqtt5AsyncOpStatus_BrokerDisconnected); // No members access after this point, the op will be deleted - cl.notifyDisconnected(true, CC_Mqtt5AsyncOpStatus_BrokerDisconnected, &info); + cl.brokerDisconnected(true, CC_Mqtt5AsyncOpStatus_BrokerDisconnected, &info); } void ConnectOp::handle(AuthMsg& msg) diff --git a/client/lib/src/op/DisconnectOp.cpp b/client/lib/src/op/DisconnectOp.cpp index cd12895..0de1a08 100644 --- a/client/lib/src/op/DisconnectOp.cpp +++ b/client/lib/src/op/DisconnectOp.cpp @@ -84,7 +84,7 @@ CC_Mqtt5ErrorCode DisconnectOp::send() auto& clientObj = client(); clientObj.sendMessage(m_disconnectMsg); opComplete(); // No members access after this point, the op will be deleted - clientObj.notifyDisconnected(false); + clientObj.brokerDisconnected(false); return CC_Mqtt5ErrorCode_Success; } diff --git a/client/lib/src/op/KeepAliveOp.cpp b/client/lib/src/op/KeepAliveOp.cpp index 5092f38..93b491f 100644 --- a/client/lib/src/op/KeepAliveOp.cpp +++ b/client/lib/src/op/KeepAliveOp.cpp @@ -60,7 +60,7 @@ void KeepAliveOp::handle(DisconnectMsg& msg) if (!msg.doValid()) { // Required by the [MQTT-3.14.1-1] clause of the spec sendDisconnectWithReason(client(), DisconnectReason::MalformedPacket); - client().notifyDisconnected(true, CC_Mqtt5AsyncOpStatus_BrokerDisconnected); + client().brokerDisconnected(true, CC_Mqtt5AsyncOpStatus_BrokerDisconnected); return; } @@ -94,7 +94,7 @@ void KeepAliveOp::handle(DisconnectMsg& msg) } } - client().notifyDisconnected(true, CC_Mqtt5AsyncOpStatus_BrokerDisconnected, &info); + client().brokerDisconnected(true, CC_Mqtt5AsyncOpStatus_BrokerDisconnected, &info); // No members access after this point, the op will be deleted } @@ -171,13 +171,13 @@ void KeepAliveOp::pingTimeoutInternal() errorLog("The broker did not respond to PING"); COMMS_ASSERT(!m_respTimer.isActive()); sendDisconnectWithReason(DisconnectReason::KeepAliveTimeout); - client().notifyDisconnected(true); + client().brokerDisconnected(true); } void KeepAliveOp::sessionExpiryTimeoutInternal() { COMMS_ASSERT(!m_sessionExpiryTimer.isActive()); - client().notifyDisconnected(true); + client().brokerDisconnected(true); } void KeepAliveOp::sendPingCb(void* data) diff --git a/client/lib/src/op/Op.cpp b/client/lib/src/op/Op.cpp index a3aa779..3799a49 100644 --- a/client/lib/src/op/Op.cpp +++ b/client/lib/src/op/Op.cpp @@ -134,7 +134,7 @@ void Op::sendDisconnectWithReason(DisconnectReason reason) void Op::terminationWithReasonStatic(ClientImpl& client, DisconnectReason reason) { sendDisconnectWithReason(client, reason); - client.notifyDisconnected(true); + client.brokerDisconnected(true); } void Op::terminationWithReason(DisconnectReason reason) diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index 0e21f00..ad5c4f5 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -628,6 +628,14 @@ CC_Mqtt5ErrorCode SendOp::cancel() return CC_Mqtt5ErrorCode_Success; } +void SendOp::postReconnectionResend() +{ + assert(m_sendAttempts > 0U); + --m_sendAttempts; + m_responseTimer.cancel(); + responseTimeoutInternal(); // Emulating timeout will resend the message again with DUP flag (if needed). +} + Op::Type SendOp::typeImpl() const { return Type_Send; diff --git a/client/lib/src/op/SendOp.h b/client/lib/src/op/SendOp.h index e1ad6fe..bd47edc 100644 --- a/client/lib/src/op/SendOp.h +++ b/client/lib/src/op/SendOp.h @@ -59,6 +59,7 @@ class SendOp final : public Op unsigned getResendAttempts() const; CC_Mqtt5ErrorCode send(CC_Mqtt5PublishCompleteCb cb, void* cbData); CC_Mqtt5ErrorCode cancel(); + void postReconnectionResend(); protected: virtual Type typeImpl() const override; diff --git a/client/lib/test/unit/UnitTestPublish.th b/client/lib/test/unit/UnitTestPublish.th index 9b97318..10dda9a 100644 --- a/client/lib/test/unit/UnitTestPublish.th +++ b/client/lib/test/unit/UnitTestPublish.th @@ -1201,11 +1201,11 @@ void UnitTestPublish::test14() TS_ASSERT_EQUALS(tickReq->m_requested, SessionExpiryInterval * 1000); // Expecting to measure session expiry interval unitTestTick(); // Session expiry - TS_ASSERT(unitTestIsPublishComplete()); - auto& responseInfo = unitTestPublishResponseInfo(); - TS_ASSERT_EQUALS(responseInfo.m_status, CC_Mqtt5AsyncOpStatus_BrokerDisconnected); - unitTestPopPublishResponseInfo(); - TS_ASSERT(unitTestIsDisconnected()); + TS_ASSERT(!unitTestIsPublishComplete()); // Stored until reconnect + // auto& responseInfo = unitTestPublishResponseInfo(); + // TS_ASSERT_EQUALS(responseInfo.m_status, CC_Mqtt5AsyncOpStatus_BrokerDisconnected); + // unitTestPopPublishResponseInfo(); + // TS_ASSERT(unitTestIsDisconnected()); } void UnitTestPublish::test15() @@ -1283,11 +1283,11 @@ void UnitTestPublish::test15() TS_ASSERT_EQUALS(tickReq->m_requested, SessionExpiryInterval * 1000); // Expecting to measure session expiry interval unitTestTick(); // Session expiry - TS_ASSERT(unitTestIsPublishComplete()); - auto& responseInfo = unitTestPublishResponseInfo(); - TS_ASSERT_EQUALS(responseInfo.m_status, CC_Mqtt5AsyncOpStatus_BrokerDisconnected); - unitTestPopPublishResponseInfo(); - TS_ASSERT(unitTestIsDisconnected()); + TS_ASSERT(!unitTestIsPublishComplete()); // Stored in session + // auto& responseInfo = unitTestPublishResponseInfo(); + // TS_ASSERT_EQUALS(responseInfo.m_status, CC_Mqtt5AsyncOpStatus_BrokerDisconnected); + // unitTestPopPublishResponseInfo(); + // TS_ASSERT(unitTestIsDisconnected()); } void UnitTestPublish::test16() From 7fd43be3cc7fb6ee15e20a8cfabc45e4d33064d3 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 29 Mar 2024 16:36:43 +1000 Subject: [PATCH 05/48] Testing resend of the "send" messages after reconnection. --- client/lib/src/ClientImpl.cpp | 28 +- client/lib/src/ClientImpl.h | 7 + client/lib/src/ClientState.h | 36 +++ client/lib/src/SessionState.h | 5 - client/lib/src/op/ConnectOp.cpp | 7 +- client/lib/src/op/KeepAliveOp.cpp | 2 +- client/lib/src/op/Op.cpp | 6 +- client/lib/src/op/RecvOp.cpp | 2 +- client/lib/src/op/SendOp.cpp | 2 +- client/lib/src/op/SubscribeOp.cpp | 2 +- client/lib/src/op/UnsubscribeOp.cpp | 2 +- client/lib/test/unit/UnitTestCommonBase.cpp | 2 +- client/lib/test/unit/UnitTestCommonBase.h | 2 +- client/lib/test/unit/UnitTestConnect.th | 12 +- client/lib/test/unit/UnitTestPublish.th | 337 +++++++++++++++++++- client/lib/test/unit/UnitTestReceive.th | 6 +- client/lib/test/unit/UnitTestSubscribe.th | 6 +- client/lib/test/unit/UnitTestUnsubscribe.th | 6 +- 18 files changed, 413 insertions(+), 57 deletions(-) create mode 100644 client/lib/src/ClientState.h diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 5f288ee..86ae9da 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -85,13 +85,9 @@ CC_Mqtt5ErrorCode ClientImpl::init() } terminateOps(CC_Mqtt5AsyncOpStatus_Aborted, TerminateMode_KeepSendRecvOps); - m_ops.clear(); - bool firstConnect = m_sessionState.m_firstConnect; m_sessionState = SessionState(); m_sessionState.m_initialized = true; - m_sessionState.m_firstConnect = firstConnect; - COMMS_ASSERT(m_timerMgr.getMinWait() == 0U); - COMMS_ASSERT(m_timerMgr.allocCount() == 0U); + m_clientState.m_networkDisconnected = false; return CC_Mqtt5ErrorCode_Success; } @@ -106,9 +102,10 @@ void ClientImpl::tick(unsigned ms) unsigned ClientImpl::processData(const std::uint8_t* iter, unsigned len) { auto guard = apiEnter(); - COMMS_ASSERT(!m_sessionState.m_networkDisconnected); + COMMS_ASSERT(!m_clientState.m_networkDisconnected); - if (m_sessionState.m_networkDisconnected) { + if (m_clientState.m_networkDisconnected) { + errorLog("Incoming data when network is disconnected"); return 0U; } @@ -183,7 +180,7 @@ unsigned ClientImpl::processData(const std::uint8_t* iter, unsigned len) void ClientImpl::notifyNetworkDisconnected(bool disconnected) { auto guard = apiEnter(); - m_sessionState.m_networkDisconnected = disconnected; + m_clientState.m_networkDisconnected = disconnected; if (disconnected) { for (auto& aliasInfo : m_sessionState.m_sendTopicAliases) { aliasInfo.m_lowQosRegRemCount = aliasInfo.m_lowQosRegCountRequest; @@ -200,7 +197,7 @@ void ClientImpl::notifyNetworkDisconnected(bool disconnected) bool ClientImpl::isNetworkDisconnected() const { - return m_sessionState.m_networkDisconnected; + return m_clientState.m_networkDisconnected; } op::ConnectOp* ClientImpl::connectPrepare(CC_Mqtt5ErrorCode* ec) @@ -232,7 +229,7 @@ op::ConnectOp* ClientImpl::connectPrepare(CC_Mqtt5ErrorCode* ec) break; } - if (m_sessionState.m_networkDisconnected) { + if (m_clientState.m_networkDisconnected) { errorLog("Network is disconnected."); updateEc(ec, CC_Mqtt5ErrorCode_NetworkDisconnected); break; @@ -288,7 +285,7 @@ op::DisconnectOp* ClientImpl::disconnectPrepare(CC_Mqtt5ErrorCode* ec) break; } - if (m_sessionState.m_networkDisconnected) { + if (m_clientState.m_networkDisconnected) { errorLog("Network is disconnected."); updateEc(ec, CC_Mqtt5ErrorCode_NetworkDisconnected); break; @@ -338,7 +335,7 @@ op::SubscribeOp* ClientImpl::subscribePrepare(CC_Mqtt5ErrorCode* ec) break; } - if (m_sessionState.m_networkDisconnected) { + if (m_clientState.m_networkDisconnected) { errorLog("Network is disconnected."); updateEc(ec, CC_Mqtt5ErrorCode_NetworkDisconnected); break; @@ -388,7 +385,7 @@ op::UnsubscribeOp* ClientImpl::unsubscribePrepare(CC_Mqtt5ErrorCode* ec) break; } - if (m_sessionState.m_networkDisconnected) { + if (m_clientState.m_networkDisconnected) { errorLog("Network is disconnected."); updateEc(ec, CC_Mqtt5ErrorCode_NetworkDisconnected); break; @@ -438,7 +435,7 @@ op::SendOp* ClientImpl::publishPrepare(CC_Mqtt5ErrorCode* ec) break; } - if (m_sessionState.m_networkDisconnected) { + if (m_clientState.m_networkDisconnected) { errorLog("Network is disconnected."); updateEc(ec, CC_Mqtt5ErrorCode_NetworkDisconnected); break; @@ -495,7 +492,7 @@ op::ReauthOp* ClientImpl::reauthPrepare(CC_Mqtt5ErrorCode* ec) break; } - if (m_sessionState.m_networkDisconnected) { + if (m_clientState.m_networkDisconnected) { errorLog("Network is disconnected."); updateEc(ec, CC_Mqtt5ErrorCode_NetworkDisconnected); break; @@ -938,6 +935,7 @@ void ClientImpl::doApiGuard() void ClientImpl::brokerConnected(bool sessionPresent) { + m_clientState.m_firstConnect = false; m_sessionState.m_connected = true; do { diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index cce6d83..55f2c22 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -7,6 +7,7 @@ #pragma once +#include "ClientState.h" #include "ConfigState.h" #include "ExtConfig.h" #include "ObjAllocator.h" @@ -152,6 +153,11 @@ class ClientImpl final : public ProtMsgHandler return m_configState; } + ClientState& clientState() + { + return m_clientState; + } + SessionState& sessionState() { return m_sessionState; @@ -253,6 +259,7 @@ class ClientImpl final : public ProtMsgHandler void* m_errorLogData = nullptr; ConfigState m_configState; + ClientState m_clientState; SessionState m_sessionState; ReuseState m_reuseState; diff --git a/client/lib/src/ClientState.h b/client/lib/src/ClientState.h new file mode 100644 index 0000000..9656057 --- /dev/null +++ b/client/lib/src/ClientState.h @@ -0,0 +1,36 @@ +// +// 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 "ExtConfig.h" +#include "ObjListType.h" +#include "ProtocolDefs.h" +#include "TopicAliasDefs.h" + +#include "cc_mqtt5_client/common.h" + +#include + +namespace cc_mqtt5_client +{ + +struct ClientState +{ + using PacketIdsList = ObjListType; + + static constexpr unsigned DefaultKeepAlive = 60; + static constexpr unsigned DefaultTopicAliasMax = 10; + + PacketIdsList m_allocatedPacketIds; + std::uint16_t m_lastPacketId = 0U; + + bool m_firstConnect = true; + bool m_networkDisconnected = false; +}; + +} // namespace cc_mqtt5_client diff --git a/client/lib/src/SessionState.h b/client/lib/src/SessionState.h index d8af586..28784f4 100644 --- a/client/lib/src/SessionState.h +++ b/client/lib/src/SessionState.h @@ -21,7 +21,6 @@ namespace cc_mqtt5_client struct SessionState { - using PacketIdsList = ObjListType; using AuthMethodStorageType = ConnectMsg::Field_properties::ValueType::value_type::Field_authMethod::Field_value::ValueType; static constexpr unsigned DefaultKeepAlive = 60; @@ -40,9 +39,7 @@ struct SessionState unsigned m_maxSendTopicAlias = 0U; unsigned m_maxRecvPacketSize = 0U; unsigned m_maxSendPacketSize = 0U; - PacketIdsList m_allocatedPacketIds; CC_Mqtt5QoS m_pubMaxQos = CC_Mqtt5QoS_ExactlyOnceDelivery; - std::uint16_t m_lastPacketId = 0U; bool m_problemInfoAllowed = false; bool m_initialized = false; bool m_connected = false; @@ -51,8 +48,6 @@ struct SessionState bool m_subIdsAvailable = false; bool m_retainAvailable = false; bool m_sharedSubsAvailable = false; - bool m_firstConnect = true; - bool m_networkDisconnected = false; }; } // namespace cc_mqtt5_client diff --git a/client/lib/src/op/ConnectOp.cpp b/client/lib/src/op/ConnectOp.cpp index 0224875..db36bff 100644 --- a/client/lib/src/op/ConnectOp.cpp +++ b/client/lib/src/op/ConnectOp.cpp @@ -45,7 +45,7 @@ CC_Mqtt5ErrorCode ConnectOp::configBasic(const CC_Mqtt5ConnectBasicConfig& confi return CC_Mqtt5ErrorCode_BadParam; } - if ((!config.m_cleanStart) && (client().sessionState().m_firstConnect)) { + if ((!config.m_cleanStart) && (client().clientState().m_firstConnect)) { errorLog("Clean start flag needs to be set on the first connection attempt"); return CC_Mqtt5ErrorCode_BadParam; } @@ -480,7 +480,7 @@ CC_Mqtt5ErrorCode ConnectOp::send(CC_Mqtt5ConnectCompleteCb cb, void* cbData) return CC_Mqtt5ErrorCode_InternalError; } - if ((!m_connectMsg.field_flags().field_low().getBitValue_cleanStart()) && (client().sessionState().m_firstConnect)) { + if ((!m_connectMsg.field_flags().field_low().getBitValue_cleanStart()) && (client().clientState().m_firstConnect)) { errorLog("Clean start flag needs to be set on the first connection attempt, perform configuration first."); return CC_Mqtt5ErrorCode_InsufficientConfig; } @@ -704,7 +704,6 @@ void ConnectOp::handle(ConnackMsg& msg) state.m_subIdsAvailable = response.m_subIdsAvailable; state.m_retainAvailable = response.m_retainAvailable; state.m_sharedSubsAvailable = response.m_sharedSubsAvailable; - state.m_firstConnect = false; state.m_problemInfoAllowed = m_requestProblemInfo; if constexpr (Config::SendMaxLimit > 0U) { @@ -964,7 +963,7 @@ void ConnectOp::terminateOpImpl(CC_Mqtt5AsyncOpStatus status) void ConnectOp::networkConnectivityChangedImpl() { - if (client().sessionState().m_networkDisconnected) { + if (client().clientState().m_networkDisconnected) { completeOpInternal(CC_Mqtt5AsyncOpStatus_BrokerDisconnected); return; } diff --git a/client/lib/src/op/KeepAliveOp.cpp b/client/lib/src/op/KeepAliveOp.cpp index 93b491f..f604157 100644 --- a/client/lib/src/op/KeepAliveOp.cpp +++ b/client/lib/src/op/KeepAliveOp.cpp @@ -110,7 +110,7 @@ Op::Type KeepAliveOp::typeImpl() const void KeepAliveOp::networkConnectivityChangedImpl() { - bool networkDisconnected = client().sessionState().m_networkDisconnected; + bool networkDisconnected = client().clientState().m_networkDisconnected; m_pingTimer.setSuspended(networkDisconnected); m_recvTimer.setSuspended(networkDisconnected); m_respTimer.setSuspended(networkDisconnected); diff --git a/client/lib/src/op/Op.cpp b/client/lib/src/op/Op.cpp index 3799a49..3dc97d8 100644 --- a/client/lib/src/op/Op.cpp +++ b/client/lib/src/op/Op.cpp @@ -66,7 +66,7 @@ void Op::doApiGuard() std::uint16_t Op::allocPacketId() { static constexpr auto MaxPacketId = std::numeric_limits::max(); - auto& allocatedPacketIds = m_client.sessionState().m_allocatedPacketIds; + auto& allocatedPacketIds = m_client.clientState().m_allocatedPacketIds; if ((allocatedPacketIds.max_size() <= allocatedPacketIds.size()) || (MaxPacketId <= allocatedPacketIds.size())) { @@ -74,7 +74,7 @@ std::uint16_t Op::allocPacketId() return 0U; } - auto& lastPacketId = m_client.sessionState().m_lastPacketId; + auto& lastPacketId = m_client.clientState().m_lastPacketId; auto nextPacketId = static_cast(lastPacketId + 1U); if (nextPacketId == 0U) { @@ -106,7 +106,7 @@ void Op::releasePacketId(std::uint16_t id) return; } - auto& allocatedPacketIds = m_client.sessionState().m_allocatedPacketIds; + 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; diff --git a/client/lib/src/op/RecvOp.cpp b/client/lib/src/op/RecvOp.cpp index 996b3c7..d50cbf5 100644 --- a/client/lib/src/op/RecvOp.cpp +++ b/client/lib/src/op/RecvOp.cpp @@ -455,7 +455,7 @@ Op::Type RecvOp::typeImpl() const void RecvOp::networkConnectivityChangedImpl() { - m_responseTimer.setSuspended(client().sessionState().m_networkDisconnected); + m_responseTimer.setSuspended(client().clientState().m_networkDisconnected); } void RecvOp::restartResponseTimer() diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index ad5c4f5..6edb61f 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -649,7 +649,7 @@ void SendOp::terminateOpImpl(CC_Mqtt5AsyncOpStatus status) void SendOp::networkConnectivityChangedImpl() { - m_responseTimer.setSuspended(client().sessionState().m_networkDisconnected); + m_responseTimer.setSuspended(client().clientState().m_networkDisconnected); } void SendOp::restartResponseTimer() diff --git a/client/lib/src/op/SubscribeOp.cpp b/client/lib/src/op/SubscribeOp.cpp index 14517e5..39ee150 100644 --- a/client/lib/src/op/SubscribeOp.cpp +++ b/client/lib/src/op/SubscribeOp.cpp @@ -324,7 +324,7 @@ void SubscribeOp::terminateOpImpl(CC_Mqtt5AsyncOpStatus status) void SubscribeOp::networkConnectivityChangedImpl() { - m_timer.setSuspended(client().sessionState().m_networkDisconnected); + m_timer.setSuspended(client().clientState().m_networkDisconnected); } void SubscribeOp::completeOpInternal(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response) diff --git a/client/lib/src/op/UnsubscribeOp.cpp b/client/lib/src/op/UnsubscribeOp.cpp index 88e1199..fc62f53 100644 --- a/client/lib/src/op/UnsubscribeOp.cpp +++ b/client/lib/src/op/UnsubscribeOp.cpp @@ -291,7 +291,7 @@ void UnsubscribeOp::terminateOpImpl(CC_Mqtt5AsyncOpStatus status) void UnsubscribeOp::networkConnectivityChangedImpl() { - m_timer.setSuspended(client().sessionState().m_networkDisconnected); + m_timer.setSuspended(client().clientState().m_networkDisconnected); } void UnsubscribeOp::completeOpInternal(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5UnsubscribeResponse* response) diff --git a/client/lib/test/unit/UnitTestCommonBase.cpp b/client/lib/test/unit/UnitTestCommonBase.cpp index d2434d8..2c3bdaf 100644 --- a/client/lib/test/unit/UnitTestCommonBase.cpp +++ b/client/lib/test/unit/UnitTestCommonBase.cpp @@ -964,7 +964,7 @@ bool UnitTestCommonBase::unitTestIsInitialized(CC_Mqtt5Client* client) const return m_funcs.m_is_initialized(client); } -void UnitTestCommonBase::unitTestNotifyClientDisconnected(CC_Mqtt5Client* client, bool disconnected) +void UnitTestCommonBase::unitTestNotifyNetworkDisconnected(CC_Mqtt5Client* client, bool disconnected) { m_funcs.m_notify_network_disconnected(client, disconnected); } diff --git a/client/lib/test/unit/UnitTestCommonBase.h b/client/lib/test/unit/UnitTestCommonBase.h index 3e5cb82..93061e4 100644 --- a/client/lib/test/unit/UnitTestCommonBase.h +++ b/client/lib/test/unit/UnitTestCommonBase.h @@ -435,7 +435,7 @@ class UnitTestCommonBase UnitTestClientPtr unitTestAlloc(); CC_Mqtt5ErrorCode unitTestInit(CC_Mqtt5Client* client); bool unitTestIsInitialized(CC_Mqtt5Client* client) const; - void unitTestNotifyClientDisconnected(CC_Mqtt5Client* client, bool disconnected); + void unitTestNotifyNetworkDisconnected(CC_Mqtt5Client* client, bool disconnected); bool unitTestIsNetworkDisconnected(CC_Mqtt5Client* client); CC_Mqtt5ErrorCode unitTestSetDefaultResponseTimeout(CC_Mqtt5Client* client, unsigned ms); CC_Mqtt5ErrorCode unitTestPubTopicAliasAlloc(CC_Mqtt5Client* client, const char* topic, unsigned char qos0RegsCount); diff --git a/client/lib/test/unit/UnitTestConnect.th b/client/lib/test/unit/UnitTestConnect.th index 85d0e28..1429d43 100644 --- a/client/lib/test/unit/UnitTestConnect.th +++ b/client/lib/test/unit/UnitTestConnect.th @@ -899,7 +899,7 @@ void UnitTestConnect::test10() TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Connect); unitTestTick(1000); - unitTestNotifyClientDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client, true); TS_ASSERT(unitTestIsConnectComplete()); auto& connackInfo = unitTestConnectResponseInfo(); @@ -928,19 +928,19 @@ void UnitTestConnect::test11() unitTestTick(10000); // 10 seconds - unitTestNotifyClientDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client, true); tickReq = unitTestTickReq(); TS_ASSERT_EQUALS(tickReq->m_requested, SessionExpiryInterval * 1000); // Expecting to measure session expiry interval unitTestTick(5000); // 5 seconds - unitTestNotifyClientDisconnected(client, false); + unitTestNotifyNetworkDisconnected(client, false); tickReq = unitTestTickReq(); TS_ASSERT_EQUALS(tickReq->m_requested, 50000); // Remaining period of the keep alive unitTestTick(45000); // 45 seconds - unitTestNotifyClientDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client, true); tickReq = unitTestTickReq(); TS_ASSERT_EQUALS(tickReq->m_requested, SessionExpiryInterval * 1000); // Expecting to measure session expiry interval @@ -1238,7 +1238,7 @@ void UnitTestConnect::test16() TS_ASSERT(!unitTestIsConnected(client)); TS_ASSERT(unitTestCheckNoTicks()); - unitTestNotifyClientDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client, true); } void UnitTestConnect::test17() @@ -1363,7 +1363,7 @@ void UnitTestConnect::test20() unitTestPerformConnect(client, &basicConfig, nullptr, nullptr, nullptr, &responseConfig); TS_ASSERT(!unitTestIsConnected(client)); TS_ASSERT(!unitTestIsNetworkDisconnected(client)); - unitTestNotifyClientDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client, true); TS_ASSERT(unitTestIsNetworkDisconnected(client)); [[maybe_unused]] auto ec = unitTestInit(client); TS_ASSERT(!unitTestIsNetworkDisconnected(client)); diff --git a/client/lib/test/unit/UnitTestPublish.th b/client/lib/test/unit/UnitTestPublish.th index 10dda9a..09b386d 100644 --- a/client/lib/test/unit/UnitTestPublish.th +++ b/client/lib/test/unit/UnitTestPublish.th @@ -41,6 +41,10 @@ public: void test32(); void test33(); void test34(); + void test35(); + void test36(); + void test37(); + void test38(); private: virtual void setUp() override @@ -1181,20 +1185,20 @@ void UnitTestPublish::test14() TS_ASSERT_EQUALS(tickReq->m_requested, ResponseTimeout); unitTestTick(1000); - unitTestNotifyClientDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client, true); TS_ASSERT(!unitTestIsPublishComplete()); tickReq = unitTestTickReq(); TS_ASSERT_EQUALS(tickReq->m_requested, SessionExpiryInterval * 1000); // Expecting to measure session expiry interval unitTestTick(5000); - unitTestNotifyClientDisconnected(client, false); + unitTestNotifyNetworkDisconnected(client, false); TS_ASSERT(!unitTestIsPublishComplete()); tickReq = unitTestTickReq(); TS_ASSERT_EQUALS(tickReq->m_requested, ResponseTimeout - 1000); // Resuming PUBACK timer - unitTestNotifyClientDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client, true); TS_ASSERT(!unitTestIsPublishComplete()); tickReq = unitTestTickReq(); @@ -1250,14 +1254,14 @@ void UnitTestPublish::test15() TS_ASSERT_EQUALS(tickReq->m_requested, ResponseTimeout); unitTestTick(1000); - unitTestNotifyClientDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client, true); TS_ASSERT(!unitTestIsPublishComplete()); tickReq = unitTestTickReq(); TS_ASSERT_EQUALS(tickReq->m_requested, SessionExpiryInterval * 1000); // Expecting to measure session expiry interval unitTestTick(5000); - unitTestNotifyClientDisconnected(client, false); + unitTestNotifyNetworkDisconnected(client, false); TS_ASSERT(!unitTestIsPublishComplete()); tickReq = unitTestTickReq(); @@ -1276,7 +1280,7 @@ void UnitTestPublish::test15() TS_ASSERT_EQUALS(pubrelMsg->field_packetId().value(), pubrecMsg.field_packetId().value()); unitTestTick(1000); - unitTestNotifyClientDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client, true); TS_ASSERT(!unitTestIsPublishComplete()); tickReq = unitTestTickReq(); @@ -2018,8 +2022,8 @@ void UnitTestPublish::test26() } while (false); unitTestTick(1000); - unitTestNotifyClientDisconnected(client, true); - unitTestNotifyClientDisconnected(client, false); + unitTestNotifyNetworkDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client, false); do { auto* publish = unitTestPublishPrepare(client, nullptr); @@ -2533,4 +2537,321 @@ void UnitTestPublish::test34() TS_ASSERT_EQUALS(publishMsg2->field_packetId().field().value(), packetId2); TS_ASSERT(!unitTestIsPublishComplete()); +} + +void UnitTestPublish::test35() +{ + // Testing preserving of the PUBLISH (QoS1) operation after the broker disconnection. + // [MQTT-4.6.0-1] + + auto* client = unitTestAllocAndInitClient(); + + unitTestPerformBasicConnect(client, __FUNCTION__); + TS_ASSERT(unitTestIsConnected(client)); + + const std::string Topic1("some/topic1"); + const UnitTestData Data = {0x1, 0x2, 0x3}; + + auto config = CC_Mqtt5PublishBasicConfig(); + unitTestPublishInitConfigBasic(&config); + + config.m_topic = Topic1.c_str(); + config.m_data = &Data[0]; + config.m_dataLen = static_cast(Data.size()); + config.m_qos = CC_Mqtt5QoS_AtLeastOnceDelivery; + + auto* publish1 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish1, nullptr); + + auto ec = unitTestPublishConfigBasic(publish1, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish1); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + auto sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(publishMsg->field_packetId().doesExist()); + auto packetId1 = publishMsg->field_packetId().field().value(); + + unitTestTick(1000); + unitTestNotifyNetworkDisconnected(client, true); + TS_ASSERT(unitTestIsDisconnected()); + TS_ASSERT(!unitTestIsPublishComplete()); + TS_ASSERT(unitTestCheckNoTicks()); + + ec = unitTestInit(client); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + // Reconnection with attempt to restore the session + auto connectConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&connectConfig); + + connectConfig.m_clientId = __FUNCTION__; + connectConfig.m_cleanStart = false; + + auto connectRespConfig = UnitTestConnectResponseConfig(); + connectRespConfig.m_sessionPresent = true; + unitTestPerformConnect(client, &connectConfig, nullptr, nullptr, nullptr, &connectRespConfig); + + // Checking the PUBLISH is present + TS_ASSERT(!unitTestIsPublishComplete()); + TS_ASSERT(unitTestHasSentMessage()); + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(publishMsg->transportField_flags().field_dup().getBitValue_bit()); + TS_ASSERT(publishMsg->field_packetId().doesExist()); + TS_ASSERT_EQUALS(publishMsg->field_packetId().field().value(), packetId1); + + unitTestTick(1000); + UnitTestPubackMsg pubackMsg; + pubackMsg.field_packetId().value() = publishMsg->field_packetId().field().value(); + unitTestReceiveMessage(pubackMsg); + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); +} + +void UnitTestPublish::test36() +{ + // Testing preserving of the PUBLISH (QoS2) operation after the broker disconnection. + // [MQTT-4.6.0-1] + + auto* client = unitTestAllocAndInitClient(); + + unitTestPerformBasicConnect(client, __FUNCTION__); + TS_ASSERT(unitTestIsConnected(client)); + + const std::string Topic1("some/topic1"); + const UnitTestData Data = {0x1, 0x2, 0x3}; + + auto config = CC_Mqtt5PublishBasicConfig(); + unitTestPublishInitConfigBasic(&config); + + config.m_topic = Topic1.c_str(); + config.m_data = &Data[0]; + config.m_dataLen = static_cast(Data.size()); + config.m_qos = CC_Mqtt5QoS_ExactlyOnceDelivery; + + auto* publish1 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish1, nullptr); + + auto ec = unitTestPublishConfigBasic(publish1, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish1); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + auto sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(publishMsg->field_packetId().doesExist()); + auto packetId1 = publishMsg->field_packetId().field().value(); + + unitTestTick(1000); + unitTestNotifyNetworkDisconnected(client, true); + TS_ASSERT(unitTestIsDisconnected()); + TS_ASSERT(!unitTestIsPublishComplete()); + TS_ASSERT(unitTestCheckNoTicks()); + + ec = unitTestInit(client); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + // Reconnection with attempt to restore the session + auto connectConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&connectConfig); + + connectConfig.m_clientId = __FUNCTION__; + connectConfig.m_cleanStart = false; + + auto connectRespConfig = UnitTestConnectResponseConfig(); + connectRespConfig.m_sessionPresent = true; + unitTestPerformConnect(client, &connectConfig, nullptr, nullptr, nullptr, &connectRespConfig); + + // Checking the PUBLISH is present + TS_ASSERT(!unitTestIsPublishComplete()); + TS_ASSERT(unitTestHasSentMessage()); + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(publishMsg->transportField_flags().field_dup().getBitValue_bit()); + TS_ASSERT(publishMsg->field_packetId().doesExist()); + TS_ASSERT_EQUALS(publishMsg->field_packetId().field().value(), packetId1); + + unitTestTick(1000); + UnitTestPubrecMsg pubrecMsg; + pubrecMsg.field_packetId().value() = publishMsg->field_packetId().field().value(); + unitTestReceiveMessage(pubrecMsg); + + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrel); + auto* pubrelMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrelMsg, nullptr); + TS_ASSERT_EQUALS(pubrelMsg->field_packetId().value(), pubrecMsg.field_packetId().value()); + + unitTestTick(1000); + UnitTestPubcompMsg pubcompMsg; + pubcompMsg.field_packetId().value() = pubrecMsg.field_packetId().value(); + unitTestReceiveMessage(pubcompMsg); + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); +} + +void UnitTestPublish::test37() +{ + // Testing preserving of the PUBLISH (QoS2) operation after the broker disconnection and after reception of PUBREC. + // [MQTT-4.6.0-1] + + auto* client = unitTestAllocAndInitClient(); + + unitTestPerformBasicConnect(client, __FUNCTION__); + TS_ASSERT(unitTestIsConnected(client)); + + const std::string Topic1("some/topic1"); + const UnitTestData Data = {0x1, 0x2, 0x3}; + + auto config = CC_Mqtt5PublishBasicConfig(); + unitTestPublishInitConfigBasic(&config); + + config.m_topic = Topic1.c_str(); + config.m_data = &Data[0]; + config.m_dataLen = static_cast(Data.size()); + config.m_qos = CC_Mqtt5QoS_ExactlyOnceDelivery; + + auto* publish1 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish1, nullptr); + + auto ec = unitTestPublishConfigBasic(publish1, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish1); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + auto sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(publishMsg->field_packetId().doesExist()); + auto packetId1 = publishMsg->field_packetId().field().value(); + + unitTestTick(1000); + UnitTestPubrecMsg pubrecMsg; + pubrecMsg.field_packetId().value() = publishMsg->field_packetId().field().value(); + unitTestReceiveMessage(pubrecMsg); + + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrel); + auto* pubrelMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrelMsg, nullptr); + TS_ASSERT_EQUALS(pubrelMsg->field_packetId().value(), pubrecMsg.field_packetId().value()); + + unitTestTick(1000); + unitTestNotifyNetworkDisconnected(client, true); + TS_ASSERT(unitTestIsDisconnected()); + TS_ASSERT(!unitTestIsPublishComplete()); + TS_ASSERT(unitTestCheckNoTicks()); + + ec = unitTestInit(client); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + // Reconnection with attempt to restore the session + auto connectConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&connectConfig); + + connectConfig.m_clientId = __FUNCTION__; + connectConfig.m_cleanStart = false; + + auto connectRespConfig = UnitTestConnectResponseConfig(); + connectRespConfig.m_sessionPresent = true; + unitTestPerformConnect(client, &connectConfig, nullptr, nullptr, nullptr, &connectRespConfig); + + // Checking the PUBLISH op is still present + TS_ASSERT(!unitTestIsPublishComplete()); + TS_ASSERT(unitTestHasSentMessage()); + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrel); + pubrelMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrelMsg, nullptr); + TS_ASSERT_EQUALS(pubrelMsg->field_packetId().value(), packetId1); + + unitTestTick(1000); + UnitTestPubcompMsg pubcompMsg; + pubcompMsg.field_packetId().value() = pubrecMsg.field_packetId().value(); + unitTestReceiveMessage(pubcompMsg); + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); +} + +void UnitTestPublish::test38() +{ + // Testing preserving of the PUBLISH (QoS1) operation after the broker disconnection and termination upon reconnect + // [MQTT-4.6.0-1] + + auto* client = unitTestAllocAndInitClient(); + + unitTestPerformBasicConnect(client, __FUNCTION__); + TS_ASSERT(unitTestIsConnected(client)); + + const std::string Topic1("some/topic1"); + const UnitTestData Data = {0x1, 0x2, 0x3}; + + auto config = CC_Mqtt5PublishBasicConfig(); + unitTestPublishInitConfigBasic(&config); + + config.m_topic = Topic1.c_str(); + config.m_data = &Data[0]; + config.m_dataLen = static_cast(Data.size()); + config.m_qos = CC_Mqtt5QoS_AtLeastOnceDelivery; + + auto* publish1 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish1, nullptr); + + auto ec = unitTestPublishConfigBasic(publish1, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish1); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + auto sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + + unitTestTick(1000); + unitTestNotifyNetworkDisconnected(client, true); + TS_ASSERT(unitTestIsDisconnected()); + TS_ASSERT(!unitTestIsPublishComplete()); + TS_ASSERT(unitTestCheckNoTicks()); + + ec = unitTestInit(client); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + // Reconnection with attempt to restore the session, but the clean session is reported + unitTestPerformBasicConnect(client, __FUNCTION__, true); + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo.m_status, CC_Mqtt5AsyncOpStatus_Aborted); + unitTestPopPublishResponseInfo(); } \ No newline at end of file diff --git a/client/lib/test/unit/UnitTestReceive.th b/client/lib/test/unit/UnitTestReceive.th index 1831821..fc3c5a9 100644 --- a/client/lib/test/unit/UnitTestReceive.th +++ b/client/lib/test/unit/UnitTestReceive.th @@ -488,13 +488,13 @@ void UnitTestReceive::test6() TS_ASSERT_EQUALS(tickReq->m_requested, ResponseTimeout); unitTestTick(1000); - unitTestNotifyClientDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client, true); tickReq = unitTestTickReq(); TS_ASSERT_EQUALS(tickReq->m_requested, SessionExpiryInterval * 1000); // Expecting to measure session expiry interval unitTestTick(5000); - unitTestNotifyClientDisconnected(client, false); + unitTestNotifyNetworkDisconnected(client, false); tickReq = unitTestTickReq(); TS_ASSERT_EQUALS(tickReq->m_requested, ResponseTimeout - 1000); @@ -553,7 +553,7 @@ void UnitTestReceive::test7() TS_ASSERT_EQUALS(tickReq->m_requested, ResponseTimeout); unitTestTick(1000); - unitTestNotifyClientDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client, true); tickReq = unitTestTickReq(); TS_ASSERT_EQUALS(tickReq->m_requested, SessionExpiryInterval * 1000); // Expecting to measure session expiry interval diff --git a/client/lib/test/unit/UnitTestSubscribe.th b/client/lib/test/unit/UnitTestSubscribe.th index 1160cb9..5530e00 100644 --- a/client/lib/test/unit/UnitTestSubscribe.th +++ b/client/lib/test/unit/UnitTestSubscribe.th @@ -400,20 +400,20 @@ void UnitTestSubscribe::test5() TS_ASSERT_EQUALS(tickReq->m_requested, ResponseTimeout); unitTestTick(1000); - unitTestNotifyClientDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client, true); TS_ASSERT(!unitTestIsSubscribeComplete()); tickReq = unitTestTickReq(); TS_ASSERT_EQUALS(tickReq->m_requested, SessionExpiryInterval * 1000); // Expecting to measure session expiry interval unitTestTick(5000); - unitTestNotifyClientDisconnected(client, false); + unitTestNotifyNetworkDisconnected(client, false); TS_ASSERT(!unitTestIsSubscribeComplete()); tickReq = unitTestTickReq(); TS_ASSERT_EQUALS(tickReq->m_requested, ResponseTimeout - 1000); // Resuming SUBACK timer - unitTestNotifyClientDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client, true); TS_ASSERT(!unitTestIsSubscribeComplete()); tickReq = unitTestTickReq(); diff --git a/client/lib/test/unit/UnitTestUnsubscribe.th b/client/lib/test/unit/UnitTestUnsubscribe.th index ff69110..794a1b6 100644 --- a/client/lib/test/unit/UnitTestUnsubscribe.th +++ b/client/lib/test/unit/UnitTestUnsubscribe.th @@ -341,20 +341,20 @@ void UnitTestUnsubscribe::test5() TS_ASSERT_EQUALS(tickReq->m_requested, ResponseTimeout); unitTestTick(1000); - unitTestNotifyClientDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client, true); TS_ASSERT(!unitTestIsUnsubscribeComplete()); tickReq = unitTestTickReq(); TS_ASSERT_EQUALS(tickReq->m_requested, SessionExpiryInterval * 1000); // Expecting to measure session expiry interval unitTestTick(5000); - unitTestNotifyClientDisconnected(client, false); + unitTestNotifyNetworkDisconnected(client, false); TS_ASSERT(!unitTestIsUnsubscribeComplete()); tickReq = unitTestTickReq(); TS_ASSERT_EQUALS(tickReq->m_requested, ResponseTimeout - 1000); // Resuming UNSUBACK timer - unitTestNotifyClientDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client, true); TS_ASSERT(!unitTestIsUnsubscribeComplete()); tickReq = unitTestTickReq(); From 65abb16ff9f536db2ef4ec05b6c493f6cc0133a3 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 29 Mar 2024 18:57:05 +1000 Subject: [PATCH 06/48] Testing continuation of the QoS2 message reception after the re-connection to the broker. --- client/lib/src/ClientImpl.cpp | 6 +- client/lib/src/op/RecvOp.cpp | 5 + client/lib/src/op/RecvOp.h | 1 + client/lib/test/unit/UnitTestCommonBase.cpp | 10 +- client/lib/test/unit/UnitTestCommonBase.h | 2 +- client/lib/test/unit/UnitTestPublish.th | 11 +- client/lib/test/unit/UnitTestReceive.th | 250 ++++++++++++++++++++ 7 files changed, 275 insertions(+), 10 deletions(-) diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 86ae9da..475ff5b 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -942,7 +942,11 @@ void ClientImpl::brokerConnected(bool sessionPresent) if (sessionPresent) { for (auto& sendOpPtr : m_sendOps) { sendOpPtr->postReconnectionResend(); - } + } + + for (auto& recvOpPtr : m_recvOps) { + recvOpPtr->postReconnectionResume(); + } break; } diff --git a/client/lib/src/op/RecvOp.cpp b/client/lib/src/op/RecvOp.cpp index d50cbf5..bab90f7 100644 --- a/client/lib/src/op/RecvOp.cpp +++ b/client/lib/src/op/RecvOp.cpp @@ -448,6 +448,11 @@ void RecvOp::reset() m_info = CC_Mqtt5MessageInfo(); } +void RecvOp::postReconnectionResume() +{ + networkConnectivityChangedImpl(); +} + Op::Type RecvOp::typeImpl() const { return Type_Recv; diff --git a/client/lib/src/op/RecvOp.h b/client/lib/src/op/RecvOp.h index db20add..3ff05b1 100644 --- a/client/lib/src/op/RecvOp.h +++ b/client/lib/src/op/RecvOp.h @@ -36,6 +36,7 @@ class RecvOp final : public Op } void reset(); + void postReconnectionResume(); protected: virtual Type typeImpl() const override; diff --git a/client/lib/test/unit/UnitTestCommonBase.cpp b/client/lib/test/unit/UnitTestCommonBase.cpp index 2c3bdaf..9ec1705 100644 --- a/client/lib/test/unit/UnitTestCommonBase.cpp +++ b/client/lib/test/unit/UnitTestCommonBase.cpp @@ -612,7 +612,7 @@ void UnitTestCommonBase::unitTestPerformConnect( test_assert(!unitTestIsConnectComplete()); auto* tickReq = unitTestTickReq(); - test_assert(tickReq->m_requested == UnitTestDefaultOpTimeoutMs); + test_assert(tickReq->m_requested <= UnitTestDefaultOpTimeoutMs); if (authConfig != nullptr) { const UnitTestData AuthData = {0x11, 0x22, 0x33, 0x44}; @@ -953,7 +953,7 @@ CC_Mqtt5ErrorCode UnitTestCommonBase::unitTestInit(CC_Mqtt5Client* client) test_assert(m_funcs.m_init != nullptr); auto ec = m_funcs.m_init(client); if (ec == CC_Mqtt5ErrorCode_Success) { - unitTestClearState(); + unitTestClearState(true); } return ec; } @@ -1194,9 +1194,11 @@ void UnitTestCommonBase::unitTestSetMessageReceivedReportCb(CC_Mqtt5ClientHandle return m_funcs.m_set_message_received_report_callback(handle, cb, data); } -void UnitTestCommonBase::unitTestClearState() +void UnitTestCommonBase::unitTestClearState(bool preserveTicks) { - m_tickReq.clear(); + if (!preserveTicks) { + m_tickReq.clear(); + } m_sentData.clear(); m_receivedData.clear(); m_connectResp.clear(); diff --git a/client/lib/test/unit/UnitTestCommonBase.h b/client/lib/test/unit/UnitTestCommonBase.h index 93061e4..67d3646 100644 --- a/client/lib/test/unit/UnitTestCommonBase.h +++ b/client/lib/test/unit/UnitTestCommonBase.h @@ -484,7 +484,7 @@ class UnitTestCommonBase private: - void unitTestClearState(); + void unitTestClearState(bool preserveTicks = false); static void unitTestErrorLogCb(void* obj, const char* msg); static void unitTestBrokerDisconnectedCb(void* obj, const CC_Mqtt5DisconnectInfo* info); diff --git a/client/lib/test/unit/UnitTestPublish.th b/client/lib/test/unit/UnitTestPublish.th index 09b386d..7d4a5bd 100644 --- a/client/lib/test/unit/UnitTestPublish.th +++ b/client/lib/test/unit/UnitTestPublish.th @@ -2542,7 +2542,7 @@ void UnitTestPublish::test34() void UnitTestPublish::test35() { // Testing preserving of the PUBLISH (QoS1) operation after the broker disconnection. - // [MQTT-4.6.0-1] + // [MQTT-4.4.0-1] auto* client = unitTestAllocAndInitClient(); @@ -2585,6 +2585,7 @@ void UnitTestPublish::test35() ec = unitTestInit(client); TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + TS_ASSERT(unitTestCheckNoTicks()); // Reconnection with attempt to restore the session auto connectConfig = CC_Mqtt5ConnectBasicConfig(); @@ -2623,7 +2624,7 @@ void UnitTestPublish::test35() void UnitTestPublish::test36() { // Testing preserving of the PUBLISH (QoS2) operation after the broker disconnection. - // [MQTT-4.6.0-1] + // [MQTT-4.4.0-1] auto* client = unitTestAllocAndInitClient(); @@ -2658,7 +2659,7 @@ void UnitTestPublish::test36() TS_ASSERT(publishMsg->field_packetId().doesExist()); auto packetId1 = publishMsg->field_packetId().field().value(); - unitTestTick(1000); + unitTestTick(100); unitTestNotifyNetworkDisconnected(client, true); TS_ASSERT(unitTestIsDisconnected()); TS_ASSERT(!unitTestIsPublishComplete()); @@ -2666,6 +2667,7 @@ void UnitTestPublish::test36() ec = unitTestInit(client); TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + TS_ASSERT(unitTestCheckNoTicks()); // Reconnection with attempt to restore the session auto connectConfig = CC_Mqtt5ConnectBasicConfig(); @@ -2716,7 +2718,7 @@ void UnitTestPublish::test36() void UnitTestPublish::test37() { // Testing preserving of the PUBLISH (QoS2) operation after the broker disconnection and after reception of PUBREC. - // [MQTT-4.6.0-1] + // [MQTT-4.4.0-1] auto* client = unitTestAllocAndInitClient(); @@ -2771,6 +2773,7 @@ void UnitTestPublish::test37() ec = unitTestInit(client); TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + TS_ASSERT(unitTestCheckNoTicks()); // Reconnection with attempt to restore the session auto connectConfig = CC_Mqtt5ConnectBasicConfig(); diff --git a/client/lib/test/unit/UnitTestReceive.th b/client/lib/test/unit/UnitTestReceive.th index fc3c5a9..105c65d 100644 --- a/client/lib/test/unit/UnitTestReceive.th +++ b/client/lib/test/unit/UnitTestReceive.th @@ -28,6 +28,9 @@ public: void test19(); void test20(); void test21(); + void test22(); + void test23(); + void test24(); private: virtual void setUp() override @@ -1432,3 +1435,250 @@ void UnitTestReceive::test21() TS_ASSERT_EQUALS(msgInfo2.m_data, Data); unitTestPopReceivedMessageInfo(); } + +void UnitTestReceive::test22() +{ + // Testing resuming reception of the PUBLISH (QoS2) after reconnection. + // [MQTT-4.4.0-1] + auto* client = unitTestAllocAndInitClient(); + + unitTestPerformBasicConnect(client, __FUNCTION__); + TS_ASSERT(unitTestIsConnected(client)); + + unitTestPerformBasicSubscribe(client, "#"); + unitTestTick(1000); + + const std::string Topic = "some/topic"; + const UnitTestData Data = {'h', 'e', 'l', 'l', 'o'}; + const unsigned PacketId = 10; + + UnitTestPublishMsg publishMsg; + publishMsg.transportField_flags().field_qos().value() = UnitTestPublishMsg::TransportField_flags::Field_qos::ValueType::ExactlyOnceDelivery; + publishMsg.field_packetId().field().setValue(PacketId); + publishMsg.field_topic().value() = Topic; + publishMsg.field_payload().value() = Data; + publishMsg.doRefresh(); + unitTestReceiveMessage(publishMsg); + + TS_ASSERT(!unitTestHasMessageRecieved()); + auto sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); + auto* pubrecMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrecMsg, nullptr); + TS_ASSERT_EQUALS(pubrecMsg->field_packetId().value(), PacketId); + TS_ASSERT(pubrecMsg->field_reasonCode().isMissing()); + TS_ASSERT(pubrecMsg->field_properties().isMissing()); + + unitTestTick(100); + unitTestNotifyNetworkDisconnected(client, true); + TS_ASSERT(unitTestIsDisconnected()); + TS_ASSERT(!unitTestIsPublishComplete()); + TS_ASSERT(unitTestCheckNoTicks()); + + auto ec = unitTestInit(client); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + TS_ASSERT(unitTestCheckNoTicks()); + + // Reconnection with attempt to restore the session + auto connectConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&connectConfig); + + connectConfig.m_clientId = __FUNCTION__; + connectConfig.m_cleanStart = false; + + auto connectRespConfig = UnitTestConnectResponseConfig(); + connectRespConfig.m_sessionPresent = true; + unitTestPerformConnect(client, &connectConfig, nullptr, nullptr, nullptr, &connectRespConfig); + + unitTestTick(100); + publishMsg.transportField_flags().field_dup().setBitValue_bit(true); + unitTestReceiveMessage(publishMsg); + + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); + pubrecMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrecMsg, nullptr); + TS_ASSERT_EQUALS(pubrecMsg->field_packetId().value(), PacketId); + TS_ASSERT(pubrecMsg->field_reasonCode().isMissing()); + + UnitTestPubrelMsg pubrelMsg; + pubrelMsg.field_packetId().setValue(PacketId); + unitTestReceiveMessage(pubrelMsg); + + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubcomp); + auto* pubcompMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubcompMsg, nullptr); + TS_ASSERT_EQUALS(pubcompMsg->field_packetId().value(), PacketId); + TS_ASSERT(pubcompMsg->field_reasonCode().isMissing()); + TS_ASSERT(pubcompMsg->field_properties().isMissing()); + + TS_ASSERT(unitTestHasMessageRecieved()); + auto& msgInfo = unitTestReceivedMessageInfo(); + TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo.m_data, Data); + unitTestPopReceivedMessageInfo(); +} + +void UnitTestReceive::test23() +{ + // Testing resuming reception of the PUBLISH (QoS2) after reconnection, assuming PUBREC was received by the broker + // [MQTT-4.4.0-1] + auto* client = unitTestAllocAndInitClient(); + + unitTestPerformBasicConnect(client, __FUNCTION__); + TS_ASSERT(unitTestIsConnected(client)); + + unitTestPerformBasicSubscribe(client, "#"); + unitTestTick(1000); + + const std::string Topic = "some/topic"; + const UnitTestData Data = {'h', 'e', 'l', 'l', 'o'}; + const unsigned PacketId = 10; + + UnitTestPublishMsg publishMsg; + publishMsg.transportField_flags().field_qos().value() = UnitTestPublishMsg::TransportField_flags::Field_qos::ValueType::ExactlyOnceDelivery; + publishMsg.field_packetId().field().setValue(PacketId); + publishMsg.field_topic().value() = Topic; + publishMsg.field_payload().value() = Data; + publishMsg.doRefresh(); + unitTestReceiveMessage(publishMsg); + + TS_ASSERT(!unitTestHasMessageRecieved()); + auto sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); + auto* pubrecMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrecMsg, nullptr); + TS_ASSERT_EQUALS(pubrecMsg->field_packetId().value(), PacketId); + TS_ASSERT(pubrecMsg->field_reasonCode().isMissing()); + TS_ASSERT(pubrecMsg->field_properties().isMissing()); + + unitTestTick(100); + unitTestNotifyNetworkDisconnected(client, true); + TS_ASSERT(unitTestIsDisconnected()); + TS_ASSERT(!unitTestIsPublishComplete()); + TS_ASSERT(unitTestCheckNoTicks()); + + auto ec = unitTestInit(client); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + TS_ASSERT(unitTestCheckNoTicks()); + + // Reconnection with attempt to restore the session + auto connectConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&connectConfig); + + connectConfig.m_clientId = __FUNCTION__; + connectConfig.m_cleanStart = false; + + auto connectRespConfig = UnitTestConnectResponseConfig(); + connectRespConfig.m_sessionPresent = true; + unitTestPerformConnect(client, &connectConfig, nullptr, nullptr, nullptr, &connectRespConfig); + + unitTestTick(100); + + UnitTestPubrelMsg pubrelMsg; + pubrelMsg.field_packetId().setValue(PacketId); + unitTestReceiveMessage(pubrelMsg); + + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubcomp); + auto* pubcompMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubcompMsg, nullptr); + TS_ASSERT_EQUALS(pubcompMsg->field_packetId().value(), PacketId); + TS_ASSERT(pubcompMsg->field_reasonCode().isMissing()); + TS_ASSERT(pubcompMsg->field_properties().isMissing()); + + TS_ASSERT(unitTestHasMessageRecieved()); + auto& msgInfo = unitTestReceivedMessageInfo(); + TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo.m_data, Data); + unitTestPopReceivedMessageInfo(); +} + +void UnitTestReceive::test24() +{ + // Testing discarding reseption of the PUBLISH (QoS2) after reconnection without session present. + // [MQTT-4.4.0-1] + auto* client = unitTestAllocAndInitClient(); + + unitTestPerformBasicConnect(client, __FUNCTION__); + TS_ASSERT(unitTestIsConnected(client)); + + unitTestPerformBasicSubscribe(client, "#"); + unitTestTick(1000); + + const std::string Topic = "some/topic"; + const UnitTestData Data = {'h', 'e', 'l', 'l', 'o'}; + const unsigned PacketId = 10; + + UnitTestPublishMsg publishMsg; + publishMsg.transportField_flags().field_qos().value() = UnitTestPublishMsg::TransportField_flags::Field_qos::ValueType::ExactlyOnceDelivery; + publishMsg.field_packetId().field().setValue(PacketId); + publishMsg.field_topic().value() = Topic; + publishMsg.field_payload().value() = Data; + publishMsg.doRefresh(); + unitTestReceiveMessage(publishMsg); + + TS_ASSERT(!unitTestHasMessageRecieved()); + auto sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); + auto* pubrecMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrecMsg, nullptr); + TS_ASSERT_EQUALS(pubrecMsg->field_packetId().value(), PacketId); + TS_ASSERT(pubrecMsg->field_reasonCode().isMissing()); + TS_ASSERT(pubrecMsg->field_properties().isMissing()); + + unitTestTick(100); + unitTestNotifyNetworkDisconnected(client, true); + TS_ASSERT(unitTestIsDisconnected()); + TS_ASSERT(!unitTestIsPublishComplete()); + TS_ASSERT(unitTestCheckNoTicks()); + + auto ec = unitTestInit(client); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + TS_ASSERT(unitTestCheckNoTicks()); + + // Reconnection with attempt to restore the session + unitTestPerformBasicConnect(client, __FUNCTION__, false); + + unitTestPerformBasicSubscribe(client, "#"); + unitTestTick(1000); + + // Resending the same publish without DUP, expected to be accepted (due to discard of the previous message) + unitTestTick(100); + unitTestReceiveMessage(publishMsg); + + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); + pubrecMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrecMsg, nullptr); + TS_ASSERT_EQUALS(pubrecMsg->field_packetId().value(), PacketId); + TS_ASSERT(pubrecMsg->field_reasonCode().isMissing()); + + UnitTestPubrelMsg pubrelMsg; + pubrelMsg.field_packetId().setValue(PacketId); + unitTestReceiveMessage(pubrelMsg); + + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubcomp); + auto* pubcompMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubcompMsg, nullptr); + TS_ASSERT_EQUALS(pubcompMsg->field_packetId().value(), PacketId); + TS_ASSERT(pubcompMsg->field_reasonCode().isMissing()); + TS_ASSERT(pubcompMsg->field_properties().isMissing()); + + TS_ASSERT(unitTestHasMessageRecieved()); + auto& msgInfo = unitTestReceivedMessageInfo(); + TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo.m_data, Data); + unitTestPopReceivedMessageInfo(); +} + From ecdb224ff105d4403ab32c6110346296d7b034df Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sat, 30 Mar 2024 17:43:33 +1000 Subject: [PATCH 07/48] Mention preserving of the "PUBLISH" operation when broker is disconnected in the doxygen documentation. --- client/lib/doxygen/main.dox | 97 ++++++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 39 deletions(-) diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index f30ce1f..d2a9ba3 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -3,12 +3,12 @@ /// @section cc_mqtt5_client_overview Overview /// The MQTT5 Client Library from the CommsChampion Ecosystem /// provides simple, asynchronous, non-blocking, -/// and easy to use interface to operate MQTT5 client. The library doesn't +/// and easy to use interface to operate MQTT v5 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 MQTT5 capable broker. +/// of I/O link being used to communicate its data to the MQTT v5 capable broker. /// /// It is a responsibility of the calling application to manage network connectivity -/// as well as measure time required for the correct operation of the MQTT5 protocol. +/// as well as measure time required for the correct operation of the MQTT v5 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. @@ -27,7 +27,7 @@ /// @endcode /// /// @section cc_mqtt5_client_allocation Client Allocation -/// The library supports multiple independent MQTT5 client sessions. The +/// The library supports multiple independent MQTT v5 client sessions. The /// allocation of data structures relevant to a single client is performed /// using cc_mqtt5_client_alloc() function. /// @code @@ -122,7 +122,7 @@ /// See also the documentation of the @ref CC_Mqtt5MessageReceivedReportCb callback function definition. /// /// @section cc_mqtt5_client_time Time Measurement -/// For the correct operation of the MQTT5 client side of the protocol, the library +/// For the correct operation of the MQTT v5 client side of the protocol, the library /// requires an ability to measure time. This responsibility is delegated to the /// application. /// @@ -203,7 +203,7 @@ /// it is necessary to perform re-initialization of the library to be able to re-connect /// to the broker and continue operation. /// -/// @b IMPORTANT: According to the MQTT5 specification, when broker disconnection is reported, the +/// @b IMPORTANT: According to the MQTT v5 specification, when broker disconnection is reported, the /// client needs to close network connection. It is a responsibility of the application to do so. /// /// In other words, after the broker disconnection is reported the application is responsible to @@ -253,7 +253,7 @@ /// the old and programming new tick timeout. /// /// @section cc_mqtt5_client_concepts Operating Concepts -/// The library abstracts away multiple MQTT5 protocol based "operations". Every such operation +/// The library abstracts away multiple MQTT v5 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. @@ -324,7 +324,7 @@ /// To retrieve the configured response timeout use the @b cc_mqtt5_client_connect_get_response_timeout() function. /// /// @subsection cc_mqtt5_client_connect_basic Basic Configuration of "Connect" Operation -/// The "basic" configuration means no extra MQTT5 properties assigned to the message. +/// The "basic" configuration means no extra MQTT v5 properties assigned to the message. /// @code /// CC_Mqtt5ConnectBasicConfig basicConfig; /// @@ -343,7 +343,7 @@ /// } /// @endcode /// -/// **IMPORTANT**: MQTT5 specification allows reconnection to the broker while +/// **IMPORTANT**: MQTT v5 specification allows reconnection to the broker while /// requesting previous session restoration (via "clean start" bit). To prevent /// potential errors of the client and broker inner states being out of sync, the /// @b first "connect" operation requires setting the @ref CC_Mqtt5ConnectBasicConfig::m_cleanStart @@ -379,9 +379,9 @@ /// @b IMPORTANT: The @b cc_mqtt5_client_connect_config_will() function /// mustn't be called more than once for a single "connect" operation. Otherwise /// it may result in setting multiple will properties of the same type, which -/// is the "Protocol Error" according to the MQTT5 specification. +/// is the "Protocol Error" according to the MQTT v5 specification. /// -/// The MQTT5 specification allows adding several "User Properties" specific to +/// The MQTT v5 specification allows adding several "User Properties" specific to /// the will. The library allows such assignment using multiple invocations of /// the @b cc_mqtt5_client_connect_add_will_user_prop() function. /// @code @@ -397,7 +397,7 @@ /// See also documentation of the @ref CC_Mqtt5UserProp structure. /// /// @subsection cc_mqtt5_client_connect_extra Extra Properties Configuration -/// To add extra MQTT5 specific properties to the connection request use +/// To add extra MQTT v5 specific properties to the connection request use /// @b cc_mqtt5_client_connect_config_extra() function. /// @code /// CC_Mqtt5ConnectExtraConfig extraConfig; @@ -421,7 +421,7 @@ /// @b IMPORTANT: The @b cc_mqtt5_client_connect_config_extra() function /// mustn't be called more than once for a single "connect" operation. Otherwise /// it may result in setting multiple properties of the same type, which -/// is the "Protocol Error" according to the MQTT5 specification. +/// is the "Protocol Error" according to the MQTT v5 specification. /// /// @subsection cc_mqtt5_client_connect_auth Extended Authentication Handshake Configuration /// In case the extended authentication handshake is needed use @b cc_mqtt5_client_connect_config_auth() function. @@ -472,10 +472,10 @@ /// @b IMPORTANT: The @b cc_mqtt5_client_connect_config_auth() function /// mustn't be called more than once for a single "connect" operation. Otherwise /// it may result in setting multiple properties of the same type, which -/// is the "Protocol Error" according to the MQTT5 specification. +/// is the "Protocol Error" according to the MQTT v5 specification. /// /// @subsection cc_mqtt5_client_connect_user_prop Adding "User Properties" -/// The MQTT5 specification allows attaching any number of the "User Properties" to +/// The MQTT v5 specification allows attaching any number of the "User Properties" to /// the @b CONNECT message. The library allows such assignment using multiple invocations of /// the @b cc_mqtt5_client_connect_add_user_prop() function. /// @code @@ -541,7 +541,7 @@ /// containing wildcards, when the reported @ref CC_Mqtt5ConnectResponse::m_wildcardSubAvailable /// is @b false, will be rejected with @ref CC_Mqtt5ErrorCode_BadParam. /// -/// Quote from the MQTT5 specification: +/// Quote from the MQTT v5 specification: /// @code /// If a Server sends a CONNACK packet containing a Reason code of 128 or greater /// it MUST then close the Network Connection [MQTT-3.2.2-7] @@ -610,7 +610,7 @@ /// @endcode /// /// @subsection cc_mqtt5_client_disconnect_user_prop Adding "User Properties" -/// The MQTT5 specification allows attaching any number of the "User Properties" to +/// The MQTT v5 specification allows attaching any number of the "User Properties" to /// the @b DISCONNECT message. The library allows such assignment using multiple invocations of /// the @b cc_mqtt5_client_disconnect_add_user_prop() function. /// @code @@ -735,7 +735,7 @@ /// function. /// /// @subsection cc_mqtt5_client_subscribe_extra Extra Properties Configuration -/// To add extra MQTT5 specific properties to the subscription request use +/// To add extra MQTT v5 specific properties to the subscription request use /// @b cc_mqtt5_client_subscribe_config_extra() function. /// @code /// CC_Mqtt5SubscribeExtraConfig extraConfig; @@ -759,7 +759,7 @@ /// configuration object is not really necessary, because it has only a single @ref CC_Mqtt5SubscribeExtraConfig::m_subId member, /// which is going to be overwritten as part of the configuration. However, calling /// the @b cc_mqtt5_client_subscribe_init_config_extra() is still recommended to make the -/// application's code future updates proof. Potentially the MQTT5 specification and as the +/// application's code future updates proof. Potentially the MQTT v5 specification and as the /// result this library can be updated by adding new property to the @ref /// CC_Mqtt5SubscribeExtraConfig struct. Having the "unnecessary" call to the /// @b cc_mqtt5_client_subscribe_init_config_extra() function makes sure @@ -773,7 +773,7 @@ /// handling functionality. /// /// @subsection cc_mqtt5_client_subscribe_user_prop Adding "User Properties" -/// The MQTT5 specification allows attaching any number of the "User Properties" to +/// The MQTT v5 specification allows attaching any number of the "User Properties" to /// the @b SUBSCRIBE message. The library allows such assignment using multiple invocations of /// the @b cc_mqtt5_client_subscribe_add_user_prop() function. /// @code @@ -898,7 +898,7 @@ /// configuration object is not really necessary, because it has only a single @ref CC_Mqtt5UnsubscribeTopicConfig::m_topic member, /// which is going to be overwritten as part of the configuration. However, calling /// the @b cc_mqtt5_client_unsubscribe_init_config_topic() is still recommended to make the -/// application's code future updates proof. Potentially the MQTT5 specification and as the +/// application's code future updates proof. Potentially the MQTT v5 specification and as the /// result this library can be updated by adding new fields to the @ref /// CC_Mqtt5UnsubscribeTopicConfig struct. Having the "unnecessary" call to the /// @b cc_mqtt5_client_unsubscribe_init_config_topic() function makes sure @@ -925,7 +925,7 @@ /// function. /// /// @subsection cc_mqtt5_client_unsubscribe_user_prop Adding "User Properties" -/// The MQTT5 specification allows attaching any number of the "User Properties" to +/// The MQTT v5 specification allows attaching any number of the "User Properties" to /// the @b UNSUBSCRIBE message. The library allows such assignment using multiple invocations of /// the @b cc_mqtt5_client_unsubscribe_add_user_prop() function. /// @code @@ -1027,7 +1027,7 @@ /// To retrieve the configured response timeout use the @b cc_mqtt5_client_publish_get_response_timeout() function. /// /// @subsection cc_mqtt5_client_publish_resend Configuring "Publish" Re-Send Attempts -/// The MQTT5 specification has a mechanism of insured delivery of the published +/// The MQTT v5 specification has a mechanism of insured delivery of the published /// message to the broker. In the case of not 100% reliable connection the messages /// can get lost and needs to be re-sent. The default amount of re-sends is @b 2, i.e. /// when the first send is not acknowledged by the broker, it is tried again with @@ -1045,7 +1045,7 @@ /// To retrieve the configured resend attempts number use the @b cc_mqtt5_client_publish_get_resend_attempts() function. /// /// @subsection cc_mqtt5_client_publish_basic Basic Configuration of "Publish" Operation -/// The "basic" configuration means no extra MQTT5 properties assigned to the message, with +/// The "basic" configuration means no extra MQTT v5 properties assigned to the message, with /// one small exception of using topic alias (covered @ref cc_mqtt5_client_publish_alias "below" as a separate subject). /// @code /// CC_Mqtt5PublishBasicConfig basicConfig; @@ -1088,7 +1088,7 @@ /// "subscribe", "unsubscribe", and "publish" filter / topic formats. /// /// @subsection cc_mqtt5_client_publish_extra Extra Properties Configuration -/// To add extra MQTT5 specific properties to the publish request use +/// To add extra MQTT v5 specific properties to the publish request use /// @b cc_mqtt5_client_publish_config_extra() function. /// @code /// CC_Mqtt5PublishExtraConfig extraConfig; @@ -1110,7 +1110,7 @@ /// See also documentation of the @ref CC_Mqtt5PublishExtraConfig structure. /// /// @subsection cc_mqtt5_client_publish_user_prop Adding "User Properties" -/// The MQTT5 specification allows attaching any number of the "User Properties" to +/// The MQTT v5 specification allows attaching any number of the "User Properties" to /// the @b PUBLISH message. The library allows such assignment using multiple invocations of /// the @b cc_mqtt5_client_publish_add_user_prop() function. /// @code @@ -1202,7 +1202,7 @@ /// cancelled @b without callback invocation. /// /// @subsection cc_mqtt5_client_publish_alias Using Topic Alias -/// The MQTT5 specification allows usage of the numeric topic aliases to +/// The MQTT v5 specification allows usage of the numeric topic aliases to /// be able to reduce amount of bytes per publish. In order for the library to start /// using topic aliases, they need to be allocated (registered) using the /// @b cc_mqtt5_client_pub_topic_alias_alloc() function. @@ -1228,7 +1228,7 @@ /// "publish" operations there is an acknowledgement message from the broker indicating /// that the topic alias is received and known to the broker. However, when QoS0 only /// publishes are performed, the reception of the topic alias registration request -/// by the broker is not ensured. Also the MQTT5 specification explicitly states +/// by the broker is not ensured. Also the MQTT v5 specification explicitly states /// that it is "Protocol Error" to use unknown topic alias. /// @code{.unparsed} /// If the PUBLISH packet contains a Topic Alias, the receiver processes it as follows: @@ -1316,7 +1316,7 @@ /// reported to the application. /// /// @section cc_mqtt5_client_reauth Re-Authenticating -/// The MQTT5 specification allows performing re-authentication after the successful +/// The MQTT v5 specification allows performing re-authentication after the successful /// @ref cc_mqtt5_client_connect "\"connect\"" operation using the same authentication method. /// To do so use the @ref reauth "reauth" operation. /// @@ -1396,7 +1396,7 @@ /// @b IMPORTANT: The @b cc_mqtt5_client_reauth_config_auth() function /// mustn't be called more than once for a single "reauth" operation. Otherwise /// it may result in setting multiple properties of the same type, which -/// is the "Protocol Error" according to the MQTT5 specification. +/// is the "Protocol Error" according to the MQTT v5 specification. /// /// @subsection cc_mqtt5_client_reauth_send Sending Re-Authentication Request /// When all the necessary configurations are performed for the allocated "reauth" @@ -1462,13 +1462,30 @@ /// callback will be invoked with the @ref CC_Mqtt5AsyncOpStatus_BrokerDisconnected status. /// /// @section cc_mqtt5_client_unsolicited_disconnect Unsolicited Broker Disconnection -/// The unsolicited broker disconnection can happen in one of the following cases. +/// When broker disconnection is detected all the incomplete asynchronous operations +/// (except @ref cc_mqtt5_client_publish "publish") will be terminated with the an appropriate +/// @ref CC_Mqtt5AsyncOpStatus "status" report. The incomplete @ref cc_mqtt5_client_publish "publish" +/// operations will be preserved due to the following spec clause: +/// @code +/// When a Client reconnects with Clean Start set to 0 and a session is present, both the Client and Server +/// MUST resend any unacknowledged PUBLISH packets (where QoS > 0) and PUBREL packets using their +/// original Packet Identifiers. This is the only circumstance where a Client or Server is REQUIRED to resend +/// messages. Clients and Servers MUST NOT resend messages at any other time [MQTT-4.4.0-1]. +/// @endcode +/// The incomplete @ref cc_mqtt5_client_publish "publish" operation as well as reception of the +/// incoming message need to be preserved between the sessions. In case the broker reports +/// a new (clean) session after the reconnection, the callbacks of all the incomplete +/// @ref cc_mqtt5_client_publish "publish" operations will be invoked with the +/// @ref CC_Mqtt5AsyncOpStatus_Aborted status. +/// +/// The unsolicited broker disconnection can happen in one of the following scenarios: /// /// @subsection cc_mqtt5_client_unsolicited_disconnect_msg Receiving DISCONNECT message /// The broker can initiate disconnection any time by simply sending @b DISCONNECT 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_Mqtt5AsyncOpStatus_BrokerDisconnected as their status report. +/// @li Terminates and invokes the callbacks of previously initiated but incomplete operations +/// (except @ref cc_mqtt5_client_publish "publish") passing +/// the @ref CC_Mqtt5AsyncOpStatus_BrokerDisconnected as their status report. /// @li Invokes the @ref cc_mqtt5_client_callbacks_broker_disconnect "disconnection report callback" /// registered using the @b cc_mqtt5_client_set_broker_disconnect_report_callback() function. /// The information passed in the @b DISCONNECT message is used to populate reported @@ -1478,26 +1495,28 @@ /// 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 PING 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_Mqtt5AsyncOpStatus_BrokerDisconnected as their status report. +/// @li Terminates and invokes the callbacks of previously initiated but incomplete operations +/// (except @ref cc_mqtt5_client_publish "publish") passing +/// the @ref CC_Mqtt5AsyncOpStatus_BrokerDisconnected as their status report. /// @li Invokes the @ref cc_mqtt5_client_callbacks_broker_disconnect "disconnection report callback" /// registered using the @b cc_mqtt5_client_set_broker_disconnect_report_callback() function /// without any disconnection information. /// /// @subsection cc_mqtt5_client_unsolicited_disconnect_protocol_error Detecting Protocol Error -/// In case the broker doesn't fully comply with the MQTT5 specification or there is +/// In case the broker doesn't fully comply with the MQTT v5 specification or there is /// some unexpected data corruption the library responds in the following way: /// @li Sends @b DISCONNECT message to the broker reporting error. /// @li Terminates and invokes the callback of the operation that detected the protocol error with /// the @ref CC_Mqtt5AsyncOpStatus_ProtocolError status report. -/// @li Terminates and invokes the callbacks of all other previously initiated but incomplete operations passing +/// @li Terminates and invokes the callbacks of all other previously initiated but incomplete operations +/// (except @ref cc_mqtt5_client_publish "publish") passing /// the @ref CC_Mqtt5AsyncOpStatus_BrokerDisconnected as their status report. /// @li Invokes the @ref cc_mqtt5_client_callbacks_broker_disconnect "disconnection report callback" /// registered using the @b cc_mqtt5_client_set_broker_disconnect_report_callback() function /// without any disconnection information. /// /// With all said above it means that if application receives @ref CC_Mqtt5AsyncOpStatus_ProtocolError or -/// @ref CC_Mqtt5AsyncOpStatus_BrokerDisconnected status in one of its callback, then +/// @ref CC_Mqtt5AsyncOpStatus_BrokerDisconnected status in the operation callback, then /// the application is expected to wait for the @ref cc_mqtt5_client_callbacks_broker_disconnect "disconnection report callback" /// which will follow. /// @@ -1510,7 +1529,7 @@ /// @li Perform the @ref cc_mqtt5_client_connect "\"connect\"" operation. /// /// @section cc_mqtt5_client_network_disconnect Network Disconnection -/// The MQTT5 specification also tries to support intermittent network connection by +/// The MQTT v5 specification also tries to support intermittent network connection by /// setting the "Session Expiry Interval" property during the @ref cc_mqtt5_client_connect "\"connect\"" /// operation (see @ref CC_Mqtt5ConnectExtraConfig::m_sessionExpiryInterval). Such /// network disconnection is usually detected by the failing @b read or @b write From 13f2099d59efba2a0e7fb8da3e30b5fafc3db778 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sun, 31 Mar 2024 11:49:16 +1000 Subject: [PATCH 08/48] Reporting message reception immediately on reception of the PUBLISH. --- client/lib/doxygen/main.dox | 20 +- client/lib/src/ClientImpl.cpp | 10 + client/lib/src/op/RecvOp.cpp | 141 +++------- client/lib/src/op/RecvOp.h | 11 - client/lib/src/op/SendOp.cpp | 3 +- client/lib/test/unit/UnitTestPublish.th | 91 +++++++ client/lib/test/unit/UnitTestReceive.th | 346 ++++++++++++++++-------- 7 files changed, 397 insertions(+), 225 deletions(-) diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index d2a9ba3..5779f39 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -1307,13 +1307,29 @@ /// @endcode /// To retrieve the current configuration use the @b cc_mqtt5_client_get_verify_incoming_topic_enabled() function. /// +/// To insure the required in-order reception of the messages, the +/// @ref cc_mqtt5_client_callbacks_message "message report callback" is invoked immediately on +/// reception of the @b PUBLISH message. Just like it is described in section 4.3 of the +/// MQTT v5 specification. +/// /// The @b QoS2 publish operation initiated by the broker requires exchange of multiple messages /// between the broker and the client. When the library responds with the @b PUBREC /// message, the broker is expected to send @b PUBREL back. The library uses the /// @ref cc_mqtt5_client_response_timeout configuration to measure the time frame /// during which it allows the reception of the corresponding @b PUBREL message. If the latter doesn't -/// arrive in time, the whole @b PUBLISH request gets discarded and not -/// reported to the application. +/// arrive in time, the inner state of the message reception gets discarded resulting in +/// the future rejection of the relevant @b PUBREL from the broker (if such arrives). +/// Depending on case whether the @b PUBREC message was actually received by the broker +/// and how the broker is implemented one of the following scenarios is possible: +/// @li The broker has NOT received the @b PUBREC and will resend its @b PUBLISH +/// resulting in the duplicate delivery of the message. +/// @li The broker has received the @b PUBREC and according to MQTT v5 specification +/// it is not allowed to perform @b PUBLISH again. Th broker might decide +/// to either drop its message delivery state (best case scenario) or +/// treat it as protocol error and disconnect the client. +/// +/// With all said above it might be necessary to increase the @ref cc_mqtt5_client_response_timeout +/// "response timeout" for slow networks. /// /// @section cc_mqtt5_client_reauth Re-Authenticating /// The MQTT v5 specification allows performing re-authentication after the successful diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 475ff5b..5dfca8f 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -977,6 +977,16 @@ void ClientImpl::brokerDisconnected( terminateOps(status, TerminateMode_KeepSendRecvOps); + for (auto& op : m_recvOps) { + assert(op); + op->networkConnectivityChanged(); + } + + for (auto& op : m_sendOps) { + assert(op); + op->networkConnectivityChanged(); + } + if (reportDisconnection) { COMMS_ASSERT(m_brokerDisconnectReportCb != nullptr); m_brokerDisconnectReportCb(m_brokerDisconnectReportData, info); diff --git a/client/lib/src/op/RecvOp.cpp b/client/lib/src/op/RecvOp.cpp index bab90f7..2d5f85e 100644 --- a/client/lib/src/op/RecvOp.cpp +++ b/client/lib/src/op/RecvOp.cpp @@ -86,8 +86,7 @@ bool isTopicMatch(const TopicFilterStr& filter, const TopicStr& topic) RecvOp::RecvOp(ClientImpl& client) : Base(client), - m_responseTimer(client.timerMgr().allocTimer()), - m_info(CC_Mqtt5MessageInfo()) + m_responseTimer(client.timerMgr().allocTimer()) { COMMS_ASSERT(m_responseTimer.isValid()); } @@ -97,16 +96,26 @@ void RecvOp::handle(PublishMsg& msg) using Qos = PublishMsg::TransportField_flags::Field_qos::ValueType; auto qos = msg.transportField_flags().field_qos().value(); + if (qos > Qos::ExactlyOnceDelivery) { + terminationWithReason(DisconnectReason::QosNotSupported); + return; + } + if ((qos == Qos::ExactlyOnceDelivery) && (m_packetId != 0U) && - (msg.field_packetId().doesExist()) && - (msg.field_packetId().field().value() != m_packetId)) { - // Applicable to other RecvOp being handled in parallel - return; - } + (msg.field_packetId().doesExist())) { + + if (msg.field_packetId().field().value() != m_packetId) { + // Applicable to other RecvOp being handled in parallel + return; + } - if (qos > Qos::ExactlyOnceDelivery) { - terminationWithReason(DisconnectReason::QosNotSupported); + // If dispatched to this op, duplicate has been detected + assert(msg.transportField_flags().field_dup().getBitValue_bit()); + PubrecMsg pubrecMsg; + pubrecMsg.field_packetId().setValue(m_packetId); + sendMessage(pubrecMsg); + restartResponseTimer(); return; } @@ -240,37 +249,38 @@ void RecvOp::handle(PublishMsg& msg) } } - m_info.m_topic = topicPtr->c_str(); + auto info = CC_Mqtt5MessageInfo(); + info.m_topic = topicPtr->c_str(); auto& data = msg.field_payload().value(); - comms::cast_assign(m_info.m_dataLen) = data.size(); + comms::cast_assign(info.m_dataLen) = data.size(); if (!data.empty()) { - m_info.m_data = &data[0]; + info.m_data = &data[0]; } if (propsHandler.m_responseTopic != nullptr) { - m_info.m_responseTopic = propsHandler.m_responseTopic->field_value().value().c_str(); + info.m_responseTopic = propsHandler.m_responseTopic->field_value().value().c_str(); } if (propsHandler.m_correlationData != nullptr) { auto& correlationData = propsHandler.m_correlationData->field_value().value(); - comms::cast_assign(m_info.m_correlationDataLen) = correlationData.size(); + comms::cast_assign(info.m_correlationDataLen) = correlationData.size(); if (!correlationData.empty()) { - m_info.m_correlationData = &correlationData[0]; + info.m_correlationData = &correlationData[0]; } } if constexpr (Config::HasUserProps) { - if ((!propsHandler.m_userProps.empty()) && (qos < Qos::ExactlyOnceDelivery)) { + if (!propsHandler.m_userProps.empty()) { fillUserProps(propsHandler, m_userProps); - comms::cast_assign(m_info.m_userPropsCount) = m_userProps.size(); - m_info.m_userProps = &m_userProps[0]; + comms::cast_assign(info.m_userPropsCount) = m_userProps.size(); + info.m_userProps = &m_userProps[0]; } } if (propsHandler.m_contentType != nullptr) { auto& contentType = propsHandler.m_contentType->field_value().value(); if (!contentType.empty()) { - m_info.m_contentType = contentType.c_str(); + info.m_contentType = contentType.c_str(); } } @@ -280,24 +290,25 @@ void RecvOp::handle(PublishMsg& msg) m_subIds.push_back(id->field_value().value()); } - m_info.m_subIds = &m_subIds[0]; - comms::cast_assign(m_info.m_subIdsCount) = m_subIds.size(); + info.m_subIds = &m_subIds[0]; + comms::cast_assign(info.m_subIdsCount) = m_subIds.size(); } - comms::cast_assign(m_info.m_qos) = qos; + comms::cast_assign(info.m_qos) = qos; if (propsHandler.m_payloadFormatIndicator != nullptr) { - comms::cast_assign(m_info.m_format) = propsHandler.m_payloadFormatIndicator->field_value().value(); + comms::cast_assign(info.m_format) = propsHandler.m_payloadFormatIndicator->field_value().value(); } if (propsHandler.m_messageExpiryInterval != nullptr) { - comms::cast_assign(m_info.m_messageExpiryInterval) = propsHandler.m_messageExpiryInterval->field_value().value(); + comms::cast_assign(info.m_messageExpiryInterval) = propsHandler.m_messageExpiryInterval->field_value().value(); } - m_info.m_retained = msg.transportField_flags().field_retain().getBitValue_bit(); + info.m_retained = msg.transportField_flags().field_retain().getBitValue_bit(); if (qos == Qos::AtMostOnceDelivery) { - reportMsgInfoAndComplete(); + client().reportMsgInfo(info); + opComplete(); return; } @@ -308,66 +319,16 @@ void RecvOp::handle(PublishMsg& msg) return; } + client().reportMsgInfo(info); + if (qos == Qos::AtLeastOnceDelivery) { PubackMsg pubackMsg; pubackMsg.field_packetId().value() = msg.field_packetId().field().value(); sendMessage(pubackMsg); - reportMsgInfoAndComplete(); + opComplete(); return; } - comms::util::assign(m_topicStr, topicPtr->begin(), topicPtr->end()); - m_info.m_topic = m_topicStr.c_str(); - - comms::util::assign(m_data, data.begin(), data.end()); - if (!m_data.empty()) { - m_info.m_data = &m_data[0]; - } - - if (propsHandler.m_responseTopic != nullptr) { - auto& responseTopic = propsHandler.m_responseTopic->field_value().value(); - comms::util::assign(m_responseTopic, responseTopic.begin(), responseTopic.end()); - m_info.m_responseTopic = m_responseTopic.c_str(); - } - - if (propsHandler.m_correlationData != nullptr) { - auto& correlationData = propsHandler.m_correlationData->field_value().value(); - if (!correlationData.empty()) { - comms::util::assign(m_correlationData, correlationData.begin(), correlationData.end()); - m_info.m_correlationData = &m_correlationData[0]; - } - } - - if constexpr (Config::HasUserProps) { - if (!propsHandler.m_userProps.empty()) { - m_userPropsCpy.resize(propsHandler.m_userProps.size()); - m_userProps.resize(propsHandler.m_userProps.size()); - for (auto idx = 0U; idx < propsHandler.m_userProps.size(); ++idx) { - auto* srcFieldPtr = propsHandler.m_userProps[idx]; - auto& srcKey = srcFieldPtr->field_value().field_first().value(); - auto& srcValue = srcFieldPtr->field_value().field_second().value(); - auto& tgtStorage = m_userPropsCpy[idx]; - - comms::util::assign(tgtStorage.m_key, srcKey.begin(), srcKey.end()); - comms::util::assign(tgtStorage.m_value, srcValue.begin(), srcValue.end()); - - m_userProps[idx].m_key = tgtStorage.m_key.c_str(); - m_userProps[idx].m_value = tgtStorage.m_value.c_str(); - } - - comms::cast_assign(m_info.m_userPropsCount) = m_userProps.size(); - m_info.m_userProps = &m_userProps[0]; - } - } - - if (propsHandler.m_contentType != nullptr) { - auto& contentType = propsHandler.m_contentType->field_value().value(); - if (!contentType.empty()) { - comms::util::assign(m_contentType, contentType.begin(), contentType.end()); - m_info.m_contentType = m_contentType.c_str(); - } - } - m_packetId = msg.field_packetId().field().value(); PubrecMsg pubrecMsg; pubrecMsg.field_packetId().setValue(m_packetId); @@ -414,9 +375,7 @@ void RecvOp::handle(PubrelMsg& msg) return; } - fillUserProps(propsHandler, m_userProps); - comms::cast_assign(m_info.m_userPropsCount) = m_userProps.size(); - m_info.m_userProps = &m_userProps[0]; + // User properties in PUBREL are ignored } } } @@ -431,21 +390,14 @@ void RecvOp::handle(PubrelMsg& msg) PubcompMsg pubcompMsg; pubcompMsg.field_packetId().setValue(m_packetId); sendMessage(pubcompMsg); - reportMsgInfoAndComplete(); + opComplete(); } void RecvOp::reset() { m_responseTimer.cancel(); - m_topicStr.clear(); - m_data.clear(); - m_responseTopic.clear(); - m_correlationData.clear(); - m_userPropsCpy.clear(); m_userProps.clear(); - m_contentType.clear(); m_subIds.clear(); - m_info = CC_Mqtt5MessageInfo(); } void RecvOp::postReconnectionResume() @@ -460,7 +412,8 @@ Op::Type RecvOp::typeImpl() const void RecvOp::networkConnectivityChangedImpl() { - m_responseTimer.setSuspended(client().clientState().m_networkDisconnected); + m_responseTimer.setSuspended( + (!client().sessionState().m_connected) || client().clientState().m_networkDisconnected); } void RecvOp::restartResponseTimer() @@ -483,12 +436,6 @@ void RecvOp::recvTimeoutCb(void* data) asRecvOp(data)->responseTimeoutInternal(); } -void RecvOp::reportMsgInfoAndComplete() -{ - client().reportMsgInfo(m_info); - opComplete(); -} - } // namespace op } // namespace cc_mqtt5_client diff --git a/client/lib/src/op/RecvOp.h b/client/lib/src/op/RecvOp.h index 3ff05b1..745965a 100644 --- a/client/lib/src/op/RecvOp.h +++ b/client/lib/src/op/RecvOp.h @@ -45,10 +45,6 @@ class RecvOp final : public Op private: using UserPropKeyStorage = PublishMsg::Field_properties::ValueType::value_type::Field_userProperty::Field_value::Field_first::ValueType; using UserPropValueStorage = PublishMsg::Field_properties::ValueType::value_type::Field_userProperty::Field_value::Field_second::ValueType; - using DataStorage = PublishMsg::Field_payload::ValueType; - using ResponseTopicStorage = PublishMsg::Field_properties::ValueType::value_type::Field_responseTopic::Field_value::ValueType; - using CorrelationDataStorage = PublishMsg::Field_properties::ValueType::value_type::Field_correlationData::Field_value::ValueType; - using ContentTypeStorage = PublishMsg::Field_properties::ValueType::value_type::Field_contentType::Field_value::ValueType; struct UserPropInfo { @@ -60,20 +56,13 @@ class RecvOp final : public Op void restartResponseTimer(); void responseTimeoutInternal(); - void reportMsgInfoAndComplete(); static void recvTimeoutCb(void* data); TimerMgr::Timer m_responseTimer; TopicStr m_topicStr; - DataStorage m_data; - ResponseTopicStorage m_responseTopic; - CorrelationDataStorage m_correlationData; - UserPropsStorage m_userPropsCpy; UserPropsList m_userProps; - ContentTypeStorage m_contentType; SubIdsStorage m_subIds; - CC_Mqtt5MessageInfo m_info; unsigned m_packetId = 0U; static_assert(ExtConfig::RecvOpTimers == 1U); diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index 6edb61f..4384a04 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -649,7 +649,8 @@ void SendOp::terminateOpImpl(CC_Mqtt5AsyncOpStatus status) void SendOp::networkConnectivityChangedImpl() { - m_responseTimer.setSuspended(client().clientState().m_networkDisconnected); + m_responseTimer.setSuspended( + (!client().sessionState().m_connected) || client().clientState().m_networkDisconnected); } void SendOp::restartResponseTimer() diff --git a/client/lib/test/unit/UnitTestPublish.th b/client/lib/test/unit/UnitTestPublish.th index 7d4a5bd..92768a8 100644 --- a/client/lib/test/unit/UnitTestPublish.th +++ b/client/lib/test/unit/UnitTestPublish.th @@ -45,6 +45,7 @@ public: void test36(); void test37(); void test38(); + void test39(); private: virtual void setUp() override @@ -2857,4 +2858,94 @@ void UnitTestPublish::test38() auto& pubInfo = unitTestPublishResponseInfo(); TS_ASSERT_EQUALS(pubInfo.m_status, CC_Mqtt5AsyncOpStatus_Aborted); unitTestPopPublishResponseInfo(); +} + +void UnitTestPublish::test39() +{ + // Testing preserving of the PUBLISH (QoS1) operation after the broker disconnection (initiated by the broker). + // [MQTT-4.4.0-1] + + auto* client = unitTestAllocAndInitClient(); + + unitTestPerformBasicConnect(client, __FUNCTION__); + TS_ASSERT(unitTestIsConnected(client)); + + const std::string Topic1("some/topic1"); + const UnitTestData Data = {0x1, 0x2, 0x3}; + + auto config = CC_Mqtt5PublishBasicConfig(); + unitTestPublishInitConfigBasic(&config); + + config.m_topic = Topic1.c_str(); + config.m_data = &Data[0]; + config.m_dataLen = static_cast(Data.size()); + config.m_qos = CC_Mqtt5QoS_AtLeastOnceDelivery; + + auto* publish1 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish1, nullptr); + + auto ec = unitTestPublishConfigBasic(publish1, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish1); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + auto sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(publishMsg->field_packetId().doesExist()); + auto packetId1 = publishMsg->field_packetId().field().value(); + + unitTestTick(100); + UnitTestDisconnectMsg disconnectMsg; + unitTestReceiveMessage(disconnectMsg); + + TS_ASSERT(unitTestIsDisconnected()); + TS_ASSERT(unitTestHasDisconnectInfo()); + auto& disconnectInfo = unitTestDisconnectInfo(); + TS_ASSERT_EQUALS(disconnectInfo.m_reasonCode, CC_Mqtt5ReasonCode_NormalDisconnection); + unitTestPopDisconnectInfo(); + TS_ASSERT(unitTestCheckNoTicks()); + + TS_ASSERT(!unitTestIsPublishComplete()); + TS_ASSERT(unitTestCheckNoTicks()); + + ec = unitTestInit(client); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + TS_ASSERT(unitTestCheckNoTicks()); + + // Reconnection with attempt to restore the session + auto connectConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&connectConfig); + + connectConfig.m_clientId = __FUNCTION__; + connectConfig.m_cleanStart = false; + + auto connectRespConfig = UnitTestConnectResponseConfig(); + connectRespConfig.m_sessionPresent = true; + unitTestPerformConnect(client, &connectConfig, nullptr, nullptr, nullptr, &connectRespConfig); + + // Checking the PUBLISH is present + TS_ASSERT(!unitTestIsPublishComplete()); + TS_ASSERT(unitTestHasSentMessage()); + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(publishMsg->transportField_flags().field_dup().getBitValue_bit()); + TS_ASSERT(publishMsg->field_packetId().doesExist()); + TS_ASSERT_EQUALS(publishMsg->field_packetId().field().value(), packetId1); + + unitTestTick(1000); + UnitTestPubackMsg pubackMsg; + pubackMsg.field_packetId().value() = publishMsg->field_packetId().field().value(); + unitTestReceiveMessage(pubackMsg); + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); } \ No newline at end of file diff --git a/client/lib/test/unit/UnitTestReceive.th b/client/lib/test/unit/UnitTestReceive.th index 105c65d..f2eaab0 100644 --- a/client/lib/test/unit/UnitTestReceive.th +++ b/client/lib/test/unit/UnitTestReceive.th @@ -31,6 +31,7 @@ public: void test22(); void test23(); void test24(); + void test25(); private: virtual void setUp() override @@ -264,7 +265,24 @@ void UnitTestReceive::test3() publishMsg.doRefresh(); unitTestReceiveMessage(publishMsg); - TS_ASSERT(!unitTestHasMessageRecieved()); + TS_ASSERT(unitTestHasMessageRecieved()); + auto& msgInfo = unitTestReceivedMessageInfo(); + TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo.m_data, Data); + TS_ASSERT_EQUALS(msgInfo.m_responseTopic, ResponseTopic); + TS_ASSERT_EQUALS(msgInfo.m_correlationData, CorrelationData); + TS_ASSERT_EQUALS(msgInfo.m_userProps.size(), 1U); + TS_ASSERT_EQUALS(msgInfo.m_userProps[0].m_key, UserPropKey1); + TS_ASSERT_EQUALS(msgInfo.m_userProps[0].m_value, UserPropVal1); + TS_ASSERT_EQUALS(msgInfo.m_contentType, ContentType); + TS_ASSERT_EQUALS(msgInfo.m_subIds.size(), 1U); + TS_ASSERT_EQUALS(msgInfo.m_subIds[0], SubId); + TS_ASSERT_EQUALS(msgInfo.m_messageExpiryInterval, MessageExpiryInterval); + TS_ASSERT_EQUALS(msgInfo.m_qos, CC_Mqtt5QoS_ExactlyOnceDelivery); + TS_ASSERT_EQUALS(msgInfo.m_format, Format); + TS_ASSERT_EQUALS(msgInfo.m_retained, Retain); + unitTestPopReceivedMessageInfo(); + auto sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); @@ -307,26 +325,6 @@ void UnitTestReceive::test3() TS_ASSERT_EQUALS(pubcompMsg->field_packetId().value(), PacketId); TS_ASSERT(pubcompMsg->field_reasonCode().isMissing()); TS_ASSERT(pubcompMsg->field_properties().isMissing()); - - TS_ASSERT(unitTestHasMessageRecieved()); - auto& msgInfo = unitTestReceivedMessageInfo(); - TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); - TS_ASSERT_EQUALS(msgInfo.m_data, Data); - TS_ASSERT_EQUALS(msgInfo.m_responseTopic, ResponseTopic); - TS_ASSERT_EQUALS(msgInfo.m_correlationData, CorrelationData); - TS_ASSERT_EQUALS(msgInfo.m_userProps.size(), 2U); - TS_ASSERT_EQUALS(msgInfo.m_userProps[0].m_key, UserPropKey1); - TS_ASSERT_EQUALS(msgInfo.m_userProps[0].m_value, UserPropVal1); - TS_ASSERT_EQUALS(msgInfo.m_userProps[1].m_key, PubrelUserPropKey); - TS_ASSERT_EQUALS(msgInfo.m_userProps[1].m_value, PubrelUserPropVal); - TS_ASSERT_EQUALS(msgInfo.m_contentType, ContentType); - TS_ASSERT_EQUALS(msgInfo.m_subIds.size(), 1U); - TS_ASSERT_EQUALS(msgInfo.m_subIds[0], SubId); - TS_ASSERT_EQUALS(msgInfo.m_messageExpiryInterval, MessageExpiryInterval); - TS_ASSERT_EQUALS(msgInfo.m_qos, CC_Mqtt5QoS_ExactlyOnceDelivery); - TS_ASSERT_EQUALS(msgInfo.m_format, Format); - TS_ASSERT_EQUALS(msgInfo.m_retained, Retain); - unitTestPopReceivedMessageInfo(); } void UnitTestReceive::test4() @@ -355,7 +353,13 @@ void UnitTestReceive::test4() publishMsg.doRefresh(); unitTestReceiveMessage(publishMsg); - TS_ASSERT(!unitTestHasMessageRecieved()); + TS_ASSERT(unitTestHasMessageRecieved()); + auto& msgInfo = unitTestReceivedMessageInfo(); + TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo.m_data, Data); + TS_ASSERT_EQUALS(msgInfo.m_qos, CC_Mqtt5QoS_ExactlyOnceDelivery); + unitTestPopReceivedMessageInfo(); + auto sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); @@ -377,7 +381,6 @@ void UnitTestReceive::test4() TS_ASSERT(pubrecMsg2->field_reasonCode().doesExist()); TS_ASSERT_EQUALS(pubrecMsg2->field_reasonCode().field().value(), UnitTestPubrecMsg::Field_reasonCode::Field::ValueType::PacketIdInUse); - UnitTestPubrelMsg pubrelMsg; pubrelMsg.field_packetId().setValue(PacketId); unitTestReceiveMessage(pubrelMsg); @@ -390,13 +393,6 @@ void UnitTestReceive::test4() TS_ASSERT_EQUALS(pubcompMsg->field_packetId().value(), PacketId); TS_ASSERT(pubcompMsg->field_reasonCode().isMissing()); TS_ASSERT(pubcompMsg->field_properties().isMissing()); - - TS_ASSERT(unitTestHasMessageRecieved()); - auto& msgInfo = unitTestReceivedMessageInfo(); - TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); - TS_ASSERT_EQUALS(msgInfo.m_data, Data); - TS_ASSERT_EQUALS(msgInfo.m_qos, CC_Mqtt5QoS_ExactlyOnceDelivery); - unitTestPopReceivedMessageInfo(); } void UnitTestReceive::test5() @@ -423,7 +419,13 @@ void UnitTestReceive::test5() publishMsg.doRefresh(); unitTestReceiveMessage(publishMsg); - TS_ASSERT(!unitTestHasMessageRecieved()); + TS_ASSERT(unitTestHasMessageRecieved()); + auto& msgInfo = unitTestReceivedMessageInfo(); + TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo.m_data, Data); + TS_ASSERT_EQUALS(msgInfo.m_qos, CC_Mqtt5QoS_ExactlyOnceDelivery); + unitTestPopReceivedMessageInfo(); + auto sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); @@ -480,7 +482,9 @@ void UnitTestReceive::test6() publishMsg.doRefresh(); unitTestReceiveMessage(publishMsg); - TS_ASSERT(!unitTestHasMessageRecieved()); + TS_ASSERT(unitTestHasMessageRecieved()); + unitTestPopReceivedMessageInfo(); + auto sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); @@ -511,9 +515,6 @@ void UnitTestReceive::test6() TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubcomp); auto* pubcompMsg = dynamic_cast(sentMsg.get()); TS_ASSERT_DIFFERS(pubcompMsg, nullptr); - - TS_ASSERT(unitTestHasMessageRecieved()); - unitTestPopReceivedMessageInfo(); } void UnitTestReceive::test7() @@ -545,7 +546,9 @@ void UnitTestReceive::test7() publishMsg.doRefresh(); unitTestReceiveMessage(publishMsg); - TS_ASSERT(!unitTestHasMessageRecieved()); + TS_ASSERT(unitTestHasMessageRecieved()); + unitTestPopReceivedMessageInfo(); + auto sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); @@ -646,7 +649,9 @@ void UnitTestReceive::test9() publishMsg.doRefresh(); unitTestReceiveMessage(publishMsg); - TS_ASSERT(!unitTestHasMessageRecieved()); + TS_ASSERT(unitTestHasMessageRecieved()); + unitTestPopReceivedMessageInfo(); + auto sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); @@ -711,7 +716,9 @@ void UnitTestReceive::test10() publishMsg.doRefresh(); unitTestReceiveMessage(publishMsg); - TS_ASSERT(!unitTestHasMessageRecieved()); + TS_ASSERT(unitTestHasMessageRecieved()); + unitTestPopReceivedMessageInfo(); + auto sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); @@ -764,7 +771,13 @@ void UnitTestReceive::test11() publishMsg.doRefresh(); unitTestReceiveMessage(publishMsg); - TS_ASSERT(!unitTestHasMessageRecieved()); + TS_ASSERT(unitTestHasMessageRecieved()); + auto& msgInfo = unitTestReceivedMessageInfo(); + TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo.m_data, Data); + TS_ASSERT_EQUALS(msgInfo.m_qos, CC_Mqtt5QoS_ExactlyOnceDelivery); + unitTestPopReceivedMessageInfo(); + auto sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); @@ -781,6 +794,9 @@ void UnitTestReceive::test11() unitTestReceiveMessage(publishMsg); + // Duplicate message is not reported + TS_ASSERT(!unitTestHasMessageRecieved()); + sentMsg = unitTestGetSentMessage(); auto* pubrecMsg2 = dynamic_cast(sentMsg.get()); TS_ASSERT_DIFFERS(pubrecMsg2, nullptr); @@ -800,12 +816,7 @@ void UnitTestReceive::test11() TS_ASSERT(pubcompMsg->field_reasonCode().isMissing()); TS_ASSERT(pubcompMsg->field_properties().isMissing()); - TS_ASSERT(unitTestHasMessageRecieved()); - auto& msgInfo = unitTestReceivedMessageInfo(); - TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); - TS_ASSERT_EQUALS(msgInfo.m_data, Data); - TS_ASSERT_EQUALS(msgInfo.m_qos, CC_Mqtt5QoS_ExactlyOnceDelivery); - unitTestPopReceivedMessageInfo(); + TS_ASSERT(!unitTestHasMessageRecieved()); } @@ -834,7 +845,13 @@ void UnitTestReceive::test12() publishMsg.doRefresh(); unitTestReceiveMessage(publishMsg); - TS_ASSERT(!unitTestHasMessageRecieved()); + TS_ASSERT(unitTestHasMessageRecieved()); + auto& msgInfo = unitTestReceivedMessageInfo(); + TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo.m_data, Data); + TS_ASSERT_EQUALS(msgInfo.m_qos, CC_Mqtt5QoS_ExactlyOnceDelivery); + unitTestPopReceivedMessageInfo(); + auto sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); @@ -858,13 +875,6 @@ void UnitTestReceive::test12() TS_ASSERT_EQUALS(pubcompMsg->field_packetId().value(), PacketId); TS_ASSERT(pubcompMsg->field_reasonCode().isMissing()); TS_ASSERT(pubcompMsg->field_properties().isMissing()); - - TS_ASSERT(unitTestHasMessageRecieved()); - auto& msgInfo = unitTestReceivedMessageInfo(); - TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); - TS_ASSERT_EQUALS(msgInfo.m_data, Data); - TS_ASSERT_EQUALS(msgInfo.m_qos, CC_Mqtt5QoS_ExactlyOnceDelivery); - unitTestPopReceivedMessageInfo(); } void UnitTestReceive::test13() @@ -980,6 +990,11 @@ void UnitTestReceive::test14() publishMsg.doRefresh(); unitTestReceiveMessage(publishMsg); + TS_ASSERT(unitTestHasMessageRecieved()); + auto& msgInfo1 = unitTestReceivedMessageInfo(); + TS_ASSERT_EQUALS(msgInfo1.m_topic, Topic); + unitTestPopReceivedMessageInfo(); + TS_ASSERT(unitTestHasSentMessage()); auto sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); @@ -988,16 +1003,16 @@ void UnitTestReceive::test14() TS_ASSERT_DIFFERS(pubackMsg, nullptr); TS_ASSERT(pubackMsg->field_reasonCode().isMissing()); - TS_ASSERT(unitTestHasMessageRecieved()); - auto& msgInfo1 = unitTestReceivedMessageInfo(); - TS_ASSERT_EQUALS(msgInfo1.m_topic, Topic); - unitTestPopReceivedMessageInfo(); - unitTestTick(1000); publishMsg.transportField_flags().field_qos().value() = UnitTestPublishMsg::TransportField_flags::Field_qos::ValueType::ExactlyOnceDelivery; publishMsg.doRefresh(); unitTestReceiveMessage(publishMsg); + TS_ASSERT(unitTestHasMessageRecieved()); + auto& msgInfo3 = unitTestReceivedMessageInfo(); + TS_ASSERT_EQUALS(msgInfo3.m_topic, Topic); + unitTestPopReceivedMessageInfo(); + TS_ASSERT(unitTestHasSentMessage()); sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); @@ -1017,11 +1032,6 @@ void UnitTestReceive::test14() auto* pubcompMsg = dynamic_cast(sentMsg.get()); TS_ASSERT_DIFFERS(pubcompMsg, nullptr); TS_ASSERT(pubcompMsg->field_reasonCode().isMissing()); - - TS_ASSERT(unitTestHasMessageRecieved()); - auto& msgInfo3 = unitTestReceivedMessageInfo(); - TS_ASSERT_EQUALS(msgInfo3.m_topic, Topic); - unitTestPopReceivedMessageInfo(); } void UnitTestReceive::test15() @@ -1107,7 +1117,11 @@ void UnitTestReceive::test16() publishMsg.doRefresh(); unitTestReceiveMessage(publishMsg); - TS_ASSERT(!unitTestHasMessageRecieved()); + TS_ASSERT(unitTestHasMessageRecieved()); + auto& msgInfo3 = unitTestReceivedMessageInfo(); + TS_ASSERT_EQUALS(msgInfo3.m_topic, Topic); + unitTestPopReceivedMessageInfo(); + auto sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); @@ -1150,7 +1164,10 @@ void UnitTestReceive::test17() publishMsg.field_payload().value() = Data; publishMsg.doRefresh(); - unitTestReceiveMessage(publishMsg); + unitTestReceiveMessage(publishMsg); + + TS_ASSERT(unitTestHasMessageRecieved()); + unitTestPopReceivedMessageInfo(); TS_ASSERT(!unitTestHasMessageRecieved()); auto sentMsg = unitTestGetSentMessage(); @@ -1200,7 +1217,12 @@ void UnitTestReceive::test18() publishMsg.doRefresh(); unitTestReceiveMessage(publishMsg); - TS_ASSERT(!unitTestHasMessageRecieved()); + TS_ASSERT(unitTestHasMessageRecieved()); + auto& msgInfo = unitTestReceivedMessageInfo(); + TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo.m_data, Data); + unitTestPopReceivedMessageInfo(); + auto sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); @@ -1225,11 +1247,7 @@ void UnitTestReceive::test18() TS_ASSERT(pubcompMsg->field_reasonCode().isMissing()); TS_ASSERT(pubcompMsg->field_properties().isMissing()); - TS_ASSERT(unitTestHasMessageRecieved()); - auto& msgInfo = unitTestReceivedMessageInfo(); - TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); - TS_ASSERT_EQUALS(msgInfo.m_data, Data); - unitTestPopReceivedMessageInfo(); + TS_ASSERT(!unitTestHasMessageRecieved()); } void UnitTestReceive::test19() @@ -1258,6 +1276,13 @@ void UnitTestReceive::test19() publishMsg.doRefresh(); unitTestReceiveMessage(publishMsg); + TS_ASSERT(unitTestHasMessageRecieved()); + auto& msgInfo = unitTestReceivedMessageInfo(); + TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo.m_data, Data); + TS_ASSERT_EQUALS(msgInfo.m_qos, CC_Mqtt5QoS_AtLeastOnceDelivery); + unitTestPopReceivedMessageInfo(); + auto sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Puback); @@ -1265,30 +1290,23 @@ void UnitTestReceive::test19() TS_ASSERT_DIFFERS(pubackMsg, nullptr); TS_ASSERT_EQUALS(pubackMsg->field_packetId().value(), PacketId); - TS_ASSERT(unitTestHasMessageRecieved()); - auto& msgInfo = unitTestReceivedMessageInfo(); - TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); - TS_ASSERT_EQUALS(msgInfo.m_data, Data); - TS_ASSERT_EQUALS(msgInfo.m_qos, CC_Mqtt5QoS_AtLeastOnceDelivery); - unitTestPopReceivedMessageInfo(); - // Assume PUBACK is not received by the broker publishMsg.transportField_flags().field_dup().setBitValue_bit(true); unitTestReceiveMessage(publishMsg); + TS_ASSERT(unitTestHasMessageRecieved()); + auto& msgInfo2 = unitTestReceivedMessageInfo(); + TS_ASSERT_EQUALS(msgInfo2.m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo2.m_data, Data); + TS_ASSERT_EQUALS(msgInfo2.m_qos, CC_Mqtt5QoS_AtLeastOnceDelivery); + unitTestPopReceivedMessageInfo(); + sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Puback); pubackMsg = dynamic_cast(sentMsg.get()); TS_ASSERT_DIFFERS(pubackMsg, nullptr); TS_ASSERT_EQUALS(pubackMsg->field_packetId().value(), PacketId); - - TS_ASSERT(unitTestHasMessageRecieved()); - auto& msgInfo2 = unitTestReceivedMessageInfo(); - TS_ASSERT_EQUALS(msgInfo2.m_topic, Topic); - TS_ASSERT_EQUALS(msgInfo2.m_data, Data); - TS_ASSERT_EQUALS(msgInfo2.m_qos, CC_Mqtt5QoS_AtLeastOnceDelivery); - unitTestPopReceivedMessageInfo(); } void UnitTestReceive::test20() @@ -1329,7 +1347,9 @@ void UnitTestReceive::test20() publishMsg.field_topic().value() = "topic1"; unitTestReceiveMessage(publishMsg); - TS_ASSERT(!unitTestHasMessageRecieved()); + + TS_ASSERT(unitTestHasMessageRecieved()); + unitTestPopReceivedMessageInfo(); TS_ASSERT(unitTestHasSentMessage()); sentMsg = unitTestGetSentMessage(); @@ -1375,7 +1395,12 @@ void UnitTestReceive::test21() publishMsg.doRefresh(); unitTestReceiveMessage(publishMsg); - TS_ASSERT(!unitTestHasMessageRecieved()); + TS_ASSERT(unitTestHasMessageRecieved()); + auto& msgInfo = unitTestReceivedMessageInfo(); + TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo.m_data, Data); + unitTestPopReceivedMessageInfo(); + auto sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); @@ -1399,15 +1424,15 @@ void UnitTestReceive::test21() TS_ASSERT(pubcompMsg->field_reasonCode().isMissing()); TS_ASSERT(pubcompMsg->field_properties().isMissing()); + // Receive it again + unitTestReceiveMessage(publishMsg); + TS_ASSERT(unitTestHasMessageRecieved()); - auto& msgInfo = unitTestReceivedMessageInfo(); - TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); - TS_ASSERT_EQUALS(msgInfo.m_data, Data); + auto& msgInfo2 = unitTestReceivedMessageInfo(); + TS_ASSERT_EQUALS(msgInfo2.m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo2.m_data, Data); unitTestPopReceivedMessageInfo(); - // Receive it again - unitTestReceiveMessage(publishMsg); - TS_ASSERT(!unitTestHasMessageRecieved()); sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); @@ -1428,12 +1453,6 @@ void UnitTestReceive::test21() TS_ASSERT_EQUALS(pubcompMsg->field_packetId().value(), PacketId); TS_ASSERT(pubcompMsg->field_reasonCode().isMissing()); TS_ASSERT(pubcompMsg->field_properties().isMissing()); - - TS_ASSERT(unitTestHasMessageRecieved()); - auto& msgInfo2 = unitTestReceivedMessageInfo(); - TS_ASSERT_EQUALS(msgInfo2.m_topic, Topic); - TS_ASSERT_EQUALS(msgInfo2.m_data, Data); - unitTestPopReceivedMessageInfo(); } void UnitTestReceive::test22() @@ -1460,7 +1479,12 @@ void UnitTestReceive::test22() publishMsg.doRefresh(); unitTestReceiveMessage(publishMsg); - TS_ASSERT(!unitTestHasMessageRecieved()); + TS_ASSERT(unitTestHasMessageRecieved()); + auto& msgInfo = unitTestReceivedMessageInfo(); + TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo.m_data, Data); + unitTestPopReceivedMessageInfo(); + auto sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); @@ -1516,11 +1540,7 @@ void UnitTestReceive::test22() TS_ASSERT(pubcompMsg->field_reasonCode().isMissing()); TS_ASSERT(pubcompMsg->field_properties().isMissing()); - TS_ASSERT(unitTestHasMessageRecieved()); - auto& msgInfo = unitTestReceivedMessageInfo(); - TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); - TS_ASSERT_EQUALS(msgInfo.m_data, Data); - unitTestPopReceivedMessageInfo(); + TS_ASSERT(!unitTestHasMessageRecieved()); } void UnitTestReceive::test23() @@ -1547,7 +1567,12 @@ void UnitTestReceive::test23() publishMsg.doRefresh(); unitTestReceiveMessage(publishMsg); - TS_ASSERT(!unitTestHasMessageRecieved()); + TS_ASSERT(unitTestHasMessageRecieved()); + auto& msgInfo = unitTestReceivedMessageInfo(); + TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo.m_data, Data); + unitTestPopReceivedMessageInfo(); + auto sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); @@ -1593,11 +1618,7 @@ void UnitTestReceive::test23() TS_ASSERT(pubcompMsg->field_reasonCode().isMissing()); TS_ASSERT(pubcompMsg->field_properties().isMissing()); - TS_ASSERT(unitTestHasMessageRecieved()); - auto& msgInfo = unitTestReceivedMessageInfo(); - TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); - TS_ASSERT_EQUALS(msgInfo.m_data, Data); - unitTestPopReceivedMessageInfo(); + TS_ASSERT(!unitTestHasMessageRecieved()); } void UnitTestReceive::test24() @@ -1624,7 +1645,12 @@ void UnitTestReceive::test24() publishMsg.doRefresh(); unitTestReceiveMessage(publishMsg); - TS_ASSERT(!unitTestHasMessageRecieved()); + TS_ASSERT(unitTestHasMessageRecieved()); + auto& msgInfo = unitTestReceivedMessageInfo(); + TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo.m_data, Data); + unitTestPopReceivedMessageInfo(); + auto sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); @@ -1650,10 +1676,16 @@ void UnitTestReceive::test24() unitTestPerformBasicSubscribe(client, "#"); unitTestTick(1000); - // Resending the same publish without DUP, expected to be accepted (due to discard of the previous message) + // Resending the same publish without DUP, expected to be accepted (due to clean session) unitTestTick(100); unitTestReceiveMessage(publishMsg); + TS_ASSERT(unitTestHasMessageRecieved()); + auto& msgInfo2 = unitTestReceivedMessageInfo(); + TS_ASSERT_EQUALS(msgInfo2.m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo2.m_data, Data); + unitTestPopReceivedMessageInfo(); + sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); @@ -1674,11 +1706,97 @@ void UnitTestReceive::test24() TS_ASSERT_EQUALS(pubcompMsg->field_packetId().value(), PacketId); TS_ASSERT(pubcompMsg->field_reasonCode().isMissing()); TS_ASSERT(pubcompMsg->field_properties().isMissing()); +} + +void UnitTestReceive::test25() +{ + // Testing resuming reception of the PUBLISH (QoS2) after reconnection. + // [MQTT-4.4.0-1] + auto* client = unitTestAllocAndInitClient(); + + unitTestPerformBasicConnect(client, __FUNCTION__); + TS_ASSERT(unitTestIsConnected(client)); + + unitTestPerformBasicSubscribe(client, "#"); + unitTestTick(1000); + + const std::string Topic = "some/topic"; + const UnitTestData Data = {'h', 'e', 'l', 'l', 'o'}; + const unsigned PacketId = 10; + + UnitTestPublishMsg publishMsg; + publishMsg.transportField_flags().field_qos().value() = UnitTestPublishMsg::TransportField_flags::Field_qos::ValueType::ExactlyOnceDelivery; + publishMsg.field_packetId().field().setValue(PacketId); + publishMsg.field_topic().value() = Topic; + publishMsg.field_payload().value() = Data; + publishMsg.doRefresh(); + unitTestReceiveMessage(publishMsg); TS_ASSERT(unitTestHasMessageRecieved()); auto& msgInfo = unitTestReceivedMessageInfo(); TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); TS_ASSERT_EQUALS(msgInfo.m_data, Data); unitTestPopReceivedMessageInfo(); -} + auto sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); + auto* pubrecMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrecMsg, nullptr); + TS_ASSERT_EQUALS(pubrecMsg->field_packetId().value(), PacketId); + TS_ASSERT(pubrecMsg->field_reasonCode().isMissing()); + TS_ASSERT(pubrecMsg->field_properties().isMissing()); + + unitTestTick(100); + UnitTestDisconnectMsg disconnectMsg; + unitTestReceiveMessage(disconnectMsg); + + TS_ASSERT(unitTestIsDisconnected()); + TS_ASSERT(unitTestHasDisconnectInfo()); + auto& disconnectInfo = unitTestDisconnectInfo(); + TS_ASSERT_EQUALS(disconnectInfo.m_reasonCode, CC_Mqtt5ReasonCode_NormalDisconnection); + unitTestPopDisconnectInfo(); + TS_ASSERT(unitTestCheckNoTicks()); + + auto ec = unitTestInit(client); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + TS_ASSERT(unitTestCheckNoTicks()); + + // Reconnection with attempt to restore the session + auto connectConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&connectConfig); + + connectConfig.m_clientId = __FUNCTION__; + connectConfig.m_cleanStart = false; + + auto connectRespConfig = UnitTestConnectResponseConfig(); + connectRespConfig.m_sessionPresent = true; + unitTestPerformConnect(client, &connectConfig, nullptr, nullptr, nullptr, &connectRespConfig); + + unitTestTick(100); + publishMsg.transportField_flags().field_dup().setBitValue_bit(true); + unitTestReceiveMessage(publishMsg); + + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrec); + pubrecMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrecMsg, nullptr); + TS_ASSERT_EQUALS(pubrecMsg->field_packetId().value(), PacketId); + TS_ASSERT(pubrecMsg->field_reasonCode().isMissing()); + + UnitTestPubrelMsg pubrelMsg; + pubrelMsg.field_packetId().setValue(PacketId); + unitTestReceiveMessage(pubrelMsg); + + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubcomp); + auto* pubcompMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubcompMsg, nullptr); + TS_ASSERT_EQUALS(pubcompMsg->field_packetId().value(), PacketId); + TS_ASSERT(pubcompMsg->field_reasonCode().isMissing()); + TS_ASSERT(pubcompMsg->field_properties().isMissing()); + + TS_ASSERT(!unitTestHasMessageRecieved()); +} \ No newline at end of file From 4e4e91ecc28f5e4f57dbccc9410c78c1df765404 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sun, 31 Mar 2024 14:43:11 +1000 Subject: [PATCH 09/48] Getting rid of explicit "init" invocation requirement. --- client/afl_fuzz/AflFuzz.cpp.templ | 6 - client/app/common/AppClient.cpp | 6 - client/lib/doxygen/main.dox | 101 ++++++--------- client/lib/src/ClientImpl.cpp | 117 +++++++----------- client/lib/src/ClientImpl.h | 2 +- client/lib/templ/client.cpp.templ | 14 --- client/lib/templ/client.h.templ | 33 ++--- .../integration/IntegrationTestCommonBase.cpp | 5 - client/lib/test/unit/CMakeLists.txt | 1 - client/lib/test/unit/UnitTestBmBase.cpp | 2 - client/lib/test/unit/UnitTestBmConnect.th | 2 +- client/lib/test/unit/UnitTestBmPublish.th | 2 +- client/lib/test/unit/UnitTestBmReceive.th | 6 +- client/lib/test/unit/UnitTestClient.th | 96 -------------- client/lib/test/unit/UnitTestCommonBase.cpp | 26 +--- client/lib/test/unit/UnitTestCommonBase.h | 9 +- client/lib/test/unit/UnitTestConnect.th | 81 ++++++------ client/lib/test/unit/UnitTestDefaultBase.cpp | 2 - client/lib/test/unit/UnitTestDisconnect.th | 23 ++-- client/lib/test/unit/UnitTestPublish.th | 103 ++++++++------- client/lib/test/unit/UnitTestReauth.th | 16 +-- client/lib/test/unit/UnitTestReceive.th | 69 +++++------ client/lib/test/unit/UnitTestSubscribe.th | 28 ++--- client/lib/test/unit/UnitTestUnsubscribe.th | 20 +-- 24 files changed, 265 insertions(+), 505 deletions(-) delete mode 100644 client/lib/test/unit/UnitTestClient.th diff --git a/client/afl_fuzz/AflFuzz.cpp.templ b/client/afl_fuzz/AflFuzz.cpp.templ index bfaa1f9..43244fd 100644 --- a/client/afl_fuzz/AflFuzz.cpp.templ +++ b/client/afl_fuzz/AflFuzz.cpp.templ @@ -277,12 +277,6 @@ private: m_state.m_disconnected = false; m_state.m_reauthRequired = !m_opts.authMethod().empty(); m_state.m_publishCount = 0U; - - auto ec = cc_mqtt5_##NAME##client_init(m_client.get()); - assert(ec == CC_Mqtt5ErrorCode_Success); - if (ec != CC_Mqtt5ErrorCode_Success) { - exit(-1); - } } void doConnect() diff --git a/client/app/common/AppClient.cpp b/client/app/common/AppClient.cpp index 55594cd..bab5c14 100644 --- a/client/app/common/AppClient.cpp +++ b/client/app/common/AppClient.cpp @@ -141,12 +141,6 @@ bool AppClient::start(int argc, const char* argv[]) return true; } - auto ec = ::cc_mqtt5_client_init(m_client.get()); - if (ec != CC_Mqtt5ErrorCode_Success) { - logError() << "Failed to initialize client object." << std::endl; - return false; - } - if (!createSession()) { return false; } diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index 5779f39..fc04735 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -106,11 +106,11 @@ /// message from the broker the "info" parameter will report a disconnection reason as well /// as extra information reported by the broker. If the broker disconnection is due /// to other reasons, the "info" parameter will be @b NULL. See also @ref cc_mqtt5_client_unsolicited_disconnect -/// for details. +/// below for details. /// /// @subsection cc_mqtt5_client_callbacks_message Reporting Received Message /// The client application must assign a callback for the library to report -/// messages received from the broker. +/// application level messages received from the broker. /// @code /// void my_message_received_cb(void* data, const CC_Mqtt5MessageInfo* info) /// { @@ -141,8 +141,7 @@ /// @code /// void my_tick_program_cb(void* data, unsigned ms) /// { -/// ... -/// cc_mqtt5_client_tick(client, ms); +/// ... // program appropriate timer /// } /// /// cc_mqtt5_client_set_next_tick_program_callback(client, &my_tick_program_cb, data); @@ -150,14 +149,15 @@ /// It is allowed to invoke the @b cc_mqtt5_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_mqtt5_client_tick() is invoked, it is assumed that the previously requested tick -/// programming has been cancelled and the registered callback may be invoked again from within the +/// programming has been cancelled and the registered callback requesting to re-program +/// the timer may be invoked again from within the /// @b cc_mqtt5_client_tick(). /// /// See also the documentation of the @ref CC_Mqtt5NextTickProgramCb callback function definition. /// /// In case of callback approach for time measurement is chosen, another callback function -/// (in addition to the @b cc_mqtt5_client_set_next_tick_program_callback()) to -/// allow interruption of the previously programmed tick wait must also to be registered. +/// (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) /// { @@ -187,49 +187,6 @@ /// @endcode /// See also the documentation of the @ref CC_Mqtt5ErrorLogCb callback function definition. /// -/// @section cc_mqtt5_client_init Client (Re)Initialization -/// After all the necessary callbacks have been set, the application must -/// invoke the @b cc_mqtt5_client_init() function to allow all other subsequent operations. -/// The function checks that all the necessary callbacks have been set and reinitializes internal -/// data structures. -/// @code -/// CC_Mqtt5ErrorCode ec = cc_mqtt5_client_init(client); -/// if (ec != CC_Mqtt5ErrorCode_Success) { -/// ... /* Something is wrong, not all callbacks are set */ -/// } -/// @endcode -/// -/// Also when broker disconnection is @ref cc_mqtt5_client_callbacks_broker_disconnect "reported", -/// it is necessary to perform re-initialization of the library to be able to re-connect -/// to the broker and continue operation. -/// -/// @b IMPORTANT: According to the MQTT v5 specification, when broker disconnection is reported, the -/// client needs to close network connection. It is a responsibility of the application to do so. -/// -/// In other words, after the broker disconnection is reported the application is responsible to -/// perform the following steps: -/// @li Close the existing network connection. -/// @li Re-establish new network connection to the broker. -/// @li Re-initialize the client library by invoking @b cc_mqtt5_client_init() function. -/// -/// All previously registered callbacks configuration persists and doesn't need to be -/// performed between the re-initialization requests. -/// -/// @b NOTE that the @b cc_mqtt5_client_init() cannot be called from within a callback. -/// For example, if the broker disconnection is reported via @ref cc_mqtt5_client_callbacks_broker_disconnect "callback" -/// then the @ref cc_mqtt5_client_init "re-initialization" cannot be executed right away. -/// It needs to be postponed until the next event loop iteration. -/// -/// To check whether the client has gone through the initialization procedure or still -/// needs to be (re)-initialized, the @b cc_mqtt5_client_is_initialized() function -/// can be used. -/// @code -/// if (!cc_mqtt5_client_is_initialized(client)) { -/// CC_Mqtt5ErrorCode ec = cc_mqtt5_client_init(client); -/// ... -/// } -/// @endcode -/// /// @section cc_mqtt5_client_data Reporting Incoming Data /// It is the responsibility of the application to receive data from the broker /// and report it to the library. The report is performed using the @@ -310,6 +267,10 @@ /// printf("ERROR: Connect allocation failed with ec=%d\n", ec); /// } /// @endcode +/// @b NOTE that the @b cc_mqtt5_client_connect_prepare() cannot be called from within a callback. +/// For example, if the broker disconnection is reported via @ref cc_mqtt5_client_callbacks_broker_disconnect "callback" +/// then the @b cc_mqtt5_client_connect_prepare() cannot be invoked right away. +/// It needs to be postponed until the next event loop iteration. /// /// @subsection cc_mqtt5_client_connect_response_timeout Configuring "Connect" Response Timeout /// When created, the "connect" operation inherits the @ref cc_mqtt5_client_response_timeout @@ -549,8 +510,8 @@ /// When the callback reporting the connection status is invoked, it is responsibility /// of the application to check the @ref CC_Mqtt5ConnectResponse::m_reasonCode value. /// If it's @ref CC_Mqtt5ReasonCode_UnspecifiedError or greater, the application -/// is responsible to close the network connection and go through the -/// @ref cc_mqtt5_client_init "re-initialization" process. The same should be +/// is responsible to close the network connection and retry the "connect" operation after +/// the network connection to the broker is re-established. The same should be /// done when the "connect" operation is not properly completed, i.e. the /// reported status is @b NOT @ref CC_Mqtt5AsyncOpStatus_Complete. /// @@ -641,8 +602,8 @@ /// @b NOTE that the @b cc_mqtt5_client_disconnect_send() function doesn't receive /// any callback because there is no expected response to the @b DISCONNECT message /// from the broker. The disconnection effect is immediate. The application is -/// expected to terminate the network connection and go through -/// @ref cc_mqtt5_client_init "re-initialization" process if necessary. The +/// expected to terminate the network connection (while making sure that the +/// the requested data is actually sent). The /// handle returned by the @b cc_mqtt5_client_disconnect_prepare() must be discarded. /// /// In case there are other asynchronous operations that hasn't been completed yet, @@ -653,6 +614,10 @@ /// @ref cc_mqtt5_client_callbacks_broker_disconnect "registered unsolicited disconnection callback" /// is @b NOT invoked. /// +/// After the disconnection the application can re-establish network connection +/// to the broker and perform the @ref cc_mqtt5_client_connect "connect" operation +/// again. +/// /// @subsection cc_mqtt5_client_disconnect_cancel Cancel the "Disconnect" Operation. /// While the handle returned by the @b cc_mqtt5_client_disconnect_prepare() is still /// valid it is possible to cancel / discard the operation. @@ -1215,11 +1180,10 @@ /// It means that the allocation of the topic aliases is allowed only after the /// successful "connect" operation. /// -/// Also note that the topic aliases are not part of the session state. Every -/// @ref cc_mqtt5_client_init "re-initialization" (which is required for -/// @ref cc_mqtt5_client_connect "re-connection") all the previously allocated +/// Also note that the topic aliases are not part of the session state. For every +/// @ref cc_mqtt5_client_connect "re-connection" attempt all the previously allocated /// topic aliases get cleared in the internal data structures and they need to -/// be allocated again. +/// be allocated again after the successful connection. /// /// The last parameter passed to the @b cc_mqtt5_client_pub_topic_alias_alloc() function /// is used to specify how many times the topic alias is reported to the broker @@ -1540,9 +1504,7 @@ /// expected to go through the following steps: /// @li Close network connection. /// @li Re-establish network connection to the broker. -/// @li Go through the @ref cc_mqtt5_client_init "re-initialization" process. -/// Make sure to do it in the next iteration of the event loop and not from within the disconnection report callback. -/// @li Perform the @ref cc_mqtt5_client_connect "\"connect\"" operation. +/// @li Perform the @ref cc_mqtt5_client_connect "\"connect\"" operation from within a next event loop iteration. /// /// @section cc_mqtt5_client_network_disconnect Network Disconnection /// The MQTT v5 specification also tries to support intermittent network connection by @@ -1583,10 +1545,19 @@ /// bool disconnected = cc_mqtt5_client_is_network_disconnected(client); /// @endcode /// -/// Also @b note that invocation of the @b cc_mqtt5_client_init() function (see @ref cc_mqtt5_client_init) -/// will result in library "assuming" network is connected, i.e. -/// invocation of the @b cc_mqtt5_client_is_network_disconnected() will -/// return @b false. +/// Also @b note that after the broker disconnection report, attempt to re-connect +/// (invocation @b cc_mqtt5_client_connect_prepare()) will be rejected +/// with the @ref CC_Mqtt5ErrorCode_NetworkDisconnected error code until the network +/// re-connection reported to the library: +/// @code +/// cc_mqtt5_client_notify_network_disconnected(client, false); +/// CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_Success; +/// CC_Mqtt5ConnectHandle connect = cc_mqtt5_client_connect_prepare(client, &ec); +/// @endcode +/// +/// When new client is @ref cc_mqtt5_client_allocation "allocated", its inner state +/// assumes that the network is connected, i.e. invocation of the +/// @b cc_mqtt5_client_is_network_disconnected() will return @b false. /// /// @section cc_mqtt5_client_thread_safety Thread Safety /// In general the library is @b NOT thread safe. However, if each thread operates on a separate allocated diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 5dfca8f..9633339 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -54,42 +54,6 @@ ClientImpl::~ClientImpl() terminateOps(CC_Mqtt5AsyncOpStatus_Aborted, TerminateMode_AbortSendRecvOps); } -CC_Mqtt5ErrorCode ClientImpl::init() -{ - if (m_apiEnterCount > 0U) { - errorLog("Cannot (re)init from within callback"); - return CC_Mqtt5ErrorCode_RetryLater; - } - - auto guard = apiEnter(); - if ((m_sendOutputDataCb == nullptr) || - (m_brokerDisconnectReportCb == nullptr) || - (m_messageReceivedReportCb == nullptr)) { - errorLog("Hasn't set all must have callbacks"); - return CC_Mqtt5ErrorCode_BadParam; - } - - 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_Mqtt5ErrorCode_BadParam; - } - } - - terminateOps(CC_Mqtt5AsyncOpStatus_Aborted, TerminateMode_KeepSendRecvOps); - m_sessionState = SessionState(); - m_sessionState.m_initialized = true; - m_clientState.m_networkDisconnected = false; - return CC_Mqtt5ErrorCode_Success; -} void ClientImpl::tick(unsigned ms) { @@ -204,6 +168,20 @@ op::ConnectOp* ClientImpl::connectPrepare(CC_Mqtt5ErrorCode* ec) { op::ConnectOp* connectOp = nullptr; do { + if (!m_sessionState.m_initialized) { + if (m_apiEnterCount > 0U) { + errorLog("Cannot prepare connect from within callback"); + updateEc(ec, CC_Mqtt5ErrorCode_RetryLater); + break; + } + + auto initEc = initInternal(); + if (initEc != CC_Mqtt5ErrorCode_Success) { + updateEc(ec, initEc); + break; + } + } + if (!m_connectOps.empty()) { // Already allocated errorLog("Another connect operation is in progress."); @@ -211,12 +189,6 @@ op::ConnectOp* ClientImpl::connectPrepare(CC_Mqtt5ErrorCode* ec) break; } - if (!m_sessionState.m_initialized) { - errorLog("Client must be initialized to allow connect."); - updateEc(ec, CC_Mqtt5ErrorCode_NotIntitialized); - break; - } - if (m_sessionState.m_terminating) { errorLog("Session termination is in progress, cannot initiate connection."); updateEc(ec, CC_Mqtt5ErrorCode_Terminating); @@ -261,12 +233,6 @@ op::DisconnectOp* ClientImpl::disconnectPrepare(CC_Mqtt5ErrorCode* ec) { op::DisconnectOp* disconnectOp = nullptr; do { - if (!m_sessionState.m_initialized) { - errorLog("Client must be initialized to allow disconnect."); - updateEc(ec, CC_Mqtt5ErrorCode_NotIntitialized); - break; - } - if (!m_sessionState.m_connected) { errorLog("Client must be connected to allow disconnect."); updateEc(ec, CC_Mqtt5ErrorCode_NotConnected); @@ -317,12 +283,6 @@ op::SubscribeOp* ClientImpl::subscribePrepare(CC_Mqtt5ErrorCode* ec) { op::SubscribeOp* subOp = nullptr; do { - if (!m_sessionState.m_initialized) { - errorLog("Client must be initialized to allow subscription."); - updateEc(ec, CC_Mqtt5ErrorCode_NotIntitialized); - break; - } - if (!m_sessionState.m_connected) { errorLog("Client must be connected to allow subscription."); updateEc(ec, CC_Mqtt5ErrorCode_NotConnected); @@ -367,12 +327,6 @@ op::UnsubscribeOp* ClientImpl::unsubscribePrepare(CC_Mqtt5ErrorCode* ec) { op::UnsubscribeOp* unsubOp = nullptr; do { - if (!m_sessionState.m_initialized) { - errorLog("Client must be initialized to allow unsubscription."); - updateEc(ec, CC_Mqtt5ErrorCode_NotIntitialized); - break; - } - if (!m_sessionState.m_connected) { errorLog("Client must be connected to allow unsubscription."); updateEc(ec, CC_Mqtt5ErrorCode_NotConnected); @@ -417,12 +371,6 @@ op::SendOp* ClientImpl::publishPrepare(CC_Mqtt5ErrorCode* ec) { op::SendOp* sendOp = nullptr; do { - if (!m_sessionState.m_initialized) { - errorLog("Client must be initialized to allow publish."); - updateEc(ec, CC_Mqtt5ErrorCode_NotIntitialized); - break; - } - if (!m_sessionState.m_connected) { errorLog("Client must be connected to allow publish."); updateEc(ec, CC_Mqtt5ErrorCode_NotConnected); @@ -474,12 +422,6 @@ op::ReauthOp* ClientImpl::reauthPrepare(CC_Mqtt5ErrorCode* ec) break; } - if (!m_sessionState.m_initialized) { - errorLog("Client must be initialized to allow reauth."); - updateEc(ec, CC_Mqtt5ErrorCode_NotIntitialized); - break; - } - if (!m_sessionState.m_connected) { errorLog("Client must be connected to allow reauth."); updateEc(ec, CC_Mqtt5ErrorCode_NotConnected); @@ -1111,6 +1053,37 @@ void ClientImpl::sendDisconnectMsg(DisconnectMsg::Field_reasonCode::Field::Value sendMessage(disconnectMsg); } +CC_Mqtt5ErrorCode 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_Mqtt5ErrorCode_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_Mqtt5ErrorCode_NotIntitialized; + } + } + + terminateOps(CC_Mqtt5AsyncOpStatus_Aborted, TerminateMode_KeepSendRecvOps); + m_sessionState = SessionState(); + m_sessionState.m_initialized = true; + return CC_Mqtt5ErrorCode_Success; +} + void ClientImpl::opComplete_Connect(const op::Op* op) { eraseFromList(op, m_connectOps); diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index 55f2c22..7cd53fe 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -61,7 +61,6 @@ class ClientImpl final : public ProtMsgHandler return ApiEnterGuard(*this); } - CC_Mqtt5ErrorCode init(); void tick(unsigned ms); unsigned processData(const std::uint8_t* iter, unsigned len); void notifyNetworkDisconnected(bool disconnected); @@ -228,6 +227,7 @@ class ClientImpl final : public ProtMsgHandler void cleanOps(); void errorLogInternal(const char* msg); void sendDisconnectMsg(DisconnectMsg::Field_reasonCode::Field::ValueType reason); + CC_Mqtt5ErrorCode initInternal(); void opComplete_Connect(const op::Op* op); void opComplete_KeepAlive(const op::Op* op); diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index 8319a3f..ffd02f0 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -109,20 +109,6 @@ void cc_mqtt5_##NAME##client_free(CC_Mqtt5ClientHandle handle) getClientAllocator().free(clientFromHandle(handle)); } -CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_init(CC_Mqtt5ClientHandle handle) -{ - return clientFromHandle(handle)->init(); -} - -bool cc_mqtt5_##NAME##client_is_initialized(CC_Mqtt5ClientHandle handle) -{ - if (handle == nullptr) { - return false; - } - - return clientFromHandle(handle)->sessionState().m_initialized; -} - void cc_mqtt5_##NAME##client_tick(CC_Mqtt5ClientHandle handle, unsigned ms) { clientFromHandle(handle)->tick(ms); diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index 768e722..f6d9af6 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -42,21 +42,6 @@ CC_Mqtt5ClientHandle cc_mqtt5_##NAME##client_alloc(); /// @ingroup client void cc_mqtt5_##NAME##client_free(CC_Mqtt5ClientHandle handle); -/// @brief (Re)Initialize client. -/// @details Re-initializes all internal data structures into their default variables -/// and checks that all the required callbacks have been set. If there are old -/// outstanding operations they are all aborted. -/// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_alloc() function. -/// @pre Mustn't be called from within a callback, use next event loop iteration. -/// @ingroup client -CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_init(CC_Mqtt5ClientHandle handle); - -/// @brief Check that the client has been (re)initialized -/// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_alloc() function. -/// @return @b true if cc_mqtt5_##NAME##client_init() has been invoked before, @b false otherwise. -/// @ingroup client -bool cc_mqtt5_##NAME##client_is_initialized(CC_Mqtt5ClientHandle handle); - /// @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 @@ -95,8 +80,8 @@ void cc_mqtt5_##NAME##client_notify_network_disconnected(CC_Mqtt5ClientHandle ha /// @brief Check current network disconnected status /// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_alloc() function. /// @return @b true when disconnected, @b false when0 connected. -/// @note After invocation of the @ref cc_mqtt5_##NAME##client_init, this function will return @b false, i.e. -/// after (re)initializaiton network is assumend connected. +/// @note After invocation of the @ref cc_mqtt5_##NAME##client_alloc(), this function will return @b false, i.e. +/// after client allocation network is assumend connected. /// @ingroup client bool cc_mqtt5_##NAME##client_is_network_disconnected(CC_Mqtt5ClientHandle handle); @@ -186,11 +171,12 @@ bool cc_mqtt5_##NAME##client_get_verify_incoming_msg_subscribed(CC_Mqtt5ClientHa void cc_mqtt5_##NAME##client_init_user_prop(CC_Mqtt5UserProp* prop); /// @brief Prepare "connect" operation. +/// @details For successful operation the client needs to be in the "disconnected" state and +/// there are no other incomplete "connect" operation /// @param[in] handle Handle returned by @ref cc_mqtt5_##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. -/// @pre There were no other prepared or complete "connect" operation since last invocation of -/// @ref cc_mqtt5_##NAME##client_init(). +/// @pre The function can NOT be called from within a callback, use next event iteration. /// @post The "connect" operation is allocated, use either @ref cc_mqtt5_##NAME##client_connect_send() /// or @ref cc_mqtt5_##NAME##client_connect_cancel() to prevent memory leaks. /// @ingroup connect @@ -311,10 +297,11 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_connect_cancel(CC_Mqtt5ConnectHandle h bool cc_mqtt5_##NAME##client_is_connected(CC_Mqtt5ClientHandle handle); /// @brief Prepare "disconnect" operation. +/// @details For successful operation the client needs to be in the "connected" state and +/// there were no other prepared or complete "disconnect" operation since last "connect" operation. /// @param[in] handle Handle returned by @ref cc_mqtt5_##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. -/// @pre There were no other prepared or complete "disconnect" operation since last "connect" operation. /// @post The "disconnect" operation is allocated, use either @ref cc_mqtt5_##NAME##client_disconnect_send() /// or @ref cc_mqtt5_##NAME##client_disconnect_cancel() to prevent memory leaks. /// @ingroup disconnect @@ -356,6 +343,7 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_disconnect_send(CC_Mqtt5DisconnectHand CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_disconnect_cancel(CC_Mqtt5DisconnectHandle handle); /// @brief Prepare "subscribe" operation. +/// @details For successful operation the client needs to be in the "connected" state. /// @param[in] handle Handle returned by @ref cc_mqtt5_##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. @@ -432,6 +420,7 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_subscribe_send(CC_Mqtt5SubscribeHandle CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_subscribe_cancel(CC_Mqtt5SubscribeHandle handle); /// @brief Prepare "unsubscribe" operation. +/// @details For successful operation the client needs to be in the "connected" state. /// @param[in] handle Handle returned by @ref cc_mqtt5_##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. @@ -495,6 +484,7 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_unsubscribe_send(CC_Mqtt5UnsubscribeHa CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_unsubscribe_cancel(CC_Mqtt5UnsubscribeHandle handle); /// @brief Prepare "publish" operation. +/// @details For successful operation the client needs to be in the "connected" state. /// @param[in] handle Handle returned by @ref cc_mqtt5_##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. @@ -587,10 +577,11 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_send(CC_Mqtt5PublishHandle han CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_cancel(CC_Mqtt5PublishHandle handle); /// @brief Prepare "reauth" operation. +/// @details For successful operation the client needs to be in the "connected" state and +/// there is no other incomplete "reauth" operation. /// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_alloc() function. /// @param[out] ec Error code reporting result of the operation. Can be NULL. /// @return Handle of the "reauth" operation, will be NULL in case of failure. To analyze the reason failure use "ec" output parameter. -/// @pre There is no other incomplete "reauth" operation. /// @post The "reauth" operation is allocated, use either @ref cc_mqtt5_##NAME##client_reauth_send() /// or @ref cc_mqtt5_##NAME##client_reauth_cancel() to prevent memory leaks. /// @ingroup reauth diff --git a/client/lib/test/integration/IntegrationTestCommonBase.cpp b/client/lib/test/integration/IntegrationTestCommonBase.cpp index 4596367..66e4516 100644 --- a/client/lib/test/integration/IntegrationTestCommonBase.cpp +++ b/client/lib/test/integration/IntegrationTestCommonBase.cpp @@ -63,11 +63,6 @@ bool IntegrationTestCommonBase::integrationTestStart() ::cc_mqtt5_client_set_broker_disconnect_report_callback(m_client.get(), &IntegrationTestCommonBase::integrationTestBrokerDisconnectedCb, this); ::cc_mqtt5_client_set_message_received_report_callback(m_client.get(), &IntegrationTestCommonBase::integrationTestMessageReceivedCb, this); - auto ec = ::cc_mqtt5_client_init(m_client.get()); - if (ec != CC_Mqtt5ErrorCode_Success) { - errorLog(m_clientId) << "Failed to init: " << ec << std::endl; - } - boost::system::error_code ioEc; boost::asio::ip::tcp::resolver resolver(m_io); boost::asio::connect(m_socket, resolver.resolve(m_host, m_port), ioEc); diff --git a/client/lib/test/unit/CMakeLists.txt b/client/lib/test/unit/CMakeLists.txt index 357ad66..5fe61e7 100644 --- a/client/lib/test/unit/CMakeLists.txt +++ b/client/lib/test/unit/CMakeLists.txt @@ -44,7 +44,6 @@ if (TARGET cc::cc_mqtt5_client) $ ) - cc_mqtt5_client_add_unit_test(UnitTestClient ${DEFAULT_BASE_LIB_NAME}) cc_mqtt5_client_add_unit_test(UnitTestConnect ${DEFAULT_BASE_LIB_NAME}) cc_mqtt5_client_add_unit_test(UnitTestDisconnect ${DEFAULT_BASE_LIB_NAME}) cc_mqtt5_client_add_unit_test(UnitTestPublish ${DEFAULT_BASE_LIB_NAME}) diff --git a/client/lib/test/unit/UnitTestBmBase.cpp b/client/lib/test/unit/UnitTestBmBase.cpp index c12489d..69a8cf3 100644 --- a/client/lib/test/unit/UnitTestBmBase.cpp +++ b/client/lib/test/unit/UnitTestBmBase.cpp @@ -7,8 +7,6 @@ const UnitTestBmBase::LibFuncs& UnitTestBmBase::getFuncs() static LibFuncs funcs; funcs.m_alloc = &cc_mqtt5_bm_client_alloc; funcs.m_free = &cc_mqtt5_bm_client_free; - funcs.m_init = &cc_mqtt5_bm_client_init; - funcs.m_is_initialized = &cc_mqtt5_bm_client_is_initialized; funcs.m_tick = &cc_mqtt5_bm_client_tick; funcs.m_process_data = &cc_mqtt5_bm_client_process_data; funcs.m_notify_network_disconnected = &cc_mqtt5_bm_client_notify_network_disconnected; diff --git a/client/lib/test/unit/UnitTestBmConnect.th b/client/lib/test/unit/UnitTestBmConnect.th index a981f3b..db5c937 100644 --- a/client/lib/test/unit/UnitTestBmConnect.th +++ b/client/lib/test/unit/UnitTestBmConnect.th @@ -24,7 +24,7 @@ private: void UnitTestBmConnect::test1() { // Simple connect and ack - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); TS_ASSERT_DIFFERS(client, nullptr); TS_ASSERT(unitTestCheckNoTicks()); TS_ASSERT(!unitTestIsConnected(client)); diff --git a/client/lib/test/unit/UnitTestBmPublish.th b/client/lib/test/unit/UnitTestBmPublish.th index 48bdec5..33e13af 100644 --- a/client/lib/test/unit/UnitTestBmPublish.th +++ b/client/lib/test/unit/UnitTestBmPublish.th @@ -24,7 +24,7 @@ private: void UnitTestBmPublish::test1() { // Qos0 publish with properties - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformPubTopicAliasConnect(client, __FUNCTION__, 10); TS_ASSERT(unitTestIsConnected(client)); diff --git a/client/lib/test/unit/UnitTestBmReceive.th b/client/lib/test/unit/UnitTestBmReceive.th index 4c21eed..3173cb0 100644 --- a/client/lib/test/unit/UnitTestBmReceive.th +++ b/client/lib/test/unit/UnitTestBmReceive.th @@ -26,7 +26,7 @@ private: void UnitTestBmReceive::test1() { // Simple receive of Qos0 - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -54,7 +54,7 @@ void UnitTestBmReceive::test1() void UnitTestBmReceive::test2() { // Testing ignore of the user properties - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -94,7 +94,7 @@ void UnitTestBmReceive::test2() void UnitTestBmReceive::test3() { // Testing ignore of the too many properties - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); diff --git a/client/lib/test/unit/UnitTestClient.th b/client/lib/test/unit/UnitTestClient.th deleted file mode 100644 index f28f90c..0000000 --- a/client/lib/test/unit/UnitTestClient.th +++ /dev/null @@ -1,96 +0,0 @@ -#include "UnitTestDefaultBase.h" -#include "UnitTestPropsHandler.h" -#include "UnitTestProtocolDefs.h" - -#include "client.h" - -#include - -class UnitTestClient : public CxxTest::TestSuite, public UnitTestDefaultBase -{ -public: - void test1(); - void test2(); - - -private: - virtual void setUp() override - { - m_tickReqs.clear(); - unitTestSetUp(); - } - - virtual void tearDown() override - { - unitTestTearDown(); - } - - - static void brokerDisconnectedCb([[maybe_unused]] void* obj, [[maybe_unused]] const CC_Mqtt5DisconnectInfo* info) - { - } - - static void messageReceivedCb([[maybe_unused]] void* obj, [[maybe_unused]] const CC_Mqtt5MessageInfo* info) - { - } - - static void sendOutputDataCb([[maybe_unused]] void* obj, [[maybe_unused]] const unsigned char* buf, [[maybe_unused]] unsigned bufLen) - { - } - - static void programNextTickCb(void* obj, unsigned duration) - { - auto* realObj = reinterpret_cast(obj); - TS_ASSERT(realObj->m_tickReqs.empty()); - realObj->m_tickReqs.push_back(duration); - } - - static unsigned cancelNextTickWaitCb(void* obj) { - static_cast(obj); - TS_ASSERT(false); - return 0U; - } - - std::vector m_tickReqs; - -}; - -void UnitTestClient::test1() -{ - auto client = unitTestAlloc(); - TS_ASSERT(client); - - auto ec = unitTestInit(client.get()); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_BadParam); - - unitTestSetBrokerDisconnectReportCb(client.get(), &UnitTestClient::brokerDisconnectedCb, this); - ec = unitTestInit(client.get()); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_BadParam); - - unitTestSetSendOutputDataCb(client.get(), &UnitTestClient::sendOutputDataCb, this); - ec = unitTestInit(client.get()); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_BadParam); - - unitTestSetMessageReceivedReportCb(client.get(), &UnitTestClient::messageReceivedCb, this); - ec = unitTestInit(client.get()); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); -} - -void UnitTestClient::test2() -{ - auto client = unitTestAlloc(); - TS_ASSERT_DIFFERS(client.get(), nullptr); - - unitTestSetBrokerDisconnectReportCb(client.get(), &UnitTestClient::brokerDisconnectedCb, this); - unitTestSetMessageReceivedReportCb(client.get(), &UnitTestClient::messageReceivedCb, this); - unitTestSetSendOutputDataCb(client.get(), &UnitTestClient::sendOutputDataCb, this); - unitTestSetNextTickProgramCb(client.get(), &UnitTestClient::programNextTickCb, this); - - auto ec = unitTestInit(client.get()); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_BadParam); - - unitTestSetCancelNextTickWaitCb(client.get(), &UnitTestClient::cancelNextTickWaitCb, this); - ec = unitTestInit(client.get()); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); - -} diff --git a/client/lib/test/unit/UnitTestCommonBase.cpp b/client/lib/test/unit/UnitTestCommonBase.cpp index 9ec1705..d837c07 100644 --- a/client/lib/test/unit/UnitTestCommonBase.cpp +++ b/client/lib/test/unit/UnitTestCommonBase.cpp @@ -41,8 +41,6 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : { test_assert(m_funcs.m_alloc != nullptr); test_assert(m_funcs.m_free != nullptr); - test_assert(m_funcs.m_init != nullptr); - test_assert(m_funcs.m_is_initialized != nullptr); test_assert(m_funcs.m_tick != nullptr); test_assert(m_funcs.m_process_data != nullptr); test_assert(m_funcs.m_notify_network_disconnected != nullptr); @@ -234,7 +232,7 @@ UnitTestCommonBase::UnitTestMessageInfo& UnitTestCommonBase::UnitTestMessageInfo void UnitTestCommonBase::unitTestSetUp() { test_assert(!m_client); - unitTestClearState(); + unitTestClearState(false); } void UnitTestCommonBase::unitTestTearDown() @@ -242,11 +240,10 @@ void UnitTestCommonBase::unitTestTearDown() m_client.reset(); } -UnitTestCommonBase::UnitTestClientPtr::pointer UnitTestCommonBase::unitTestAllocAndInitClient(bool addLog) +UnitTestCommonBase::UnitTestClientPtr::pointer UnitTestCommonBase::unitTestAllocClient(bool addLog) { test_assert(!m_client); m_client.reset(m_funcs.m_alloc()); - test_assert(!m_funcs.m_is_initialized(m_client.get())); if (addLog) { m_funcs.m_set_error_log_callback(m_client.get(), &UnitTestCommonBase::unitTestErrorLogCb, nullptr); } @@ -255,9 +252,6 @@ UnitTestCommonBase::UnitTestClientPtr::pointer UnitTestCommonBase::unitTestAlloc unitTestSetSendOutputDataCb(m_client.get(), &UnitTestCommonBase::unitTestSendOutputDataCb, this); unitTestSetNextTickProgramCb(m_client.get(), &UnitTestCommonBase::unitTestProgramNextTickCb, this); unitTestSetCancelNextTickWaitCb(m_client.get(), &UnitTestCommonBase::unitTestCancelNextTickWaitCb, this); - [[maybe_unused]] auto ec = m_funcs.m_init(m_client.get()); - test_assert(ec == CC_Mqtt5ErrorCode_Success); - test_assert(m_funcs.m_is_initialized(m_client.get())); return m_client.get(); } @@ -948,22 +942,6 @@ UnitTestCommonBase::UnitTestClientPtr UnitTestCommonBase::unitTestAlloc() return UnitTestClientPtr(m_funcs.m_alloc(), UnitTestDeleter(m_funcs)); } -CC_Mqtt5ErrorCode UnitTestCommonBase::unitTestInit(CC_Mqtt5Client* client) -{ - test_assert(m_funcs.m_init != nullptr); - auto ec = m_funcs.m_init(client); - if (ec == CC_Mqtt5ErrorCode_Success) { - unitTestClearState(true); - } - return ec; -} - -bool UnitTestCommonBase::unitTestIsInitialized(CC_Mqtt5Client* client) const -{ - test_assert(m_funcs.m_is_initialized != nullptr); - return m_funcs.m_is_initialized(client); -} - void UnitTestCommonBase::unitTestNotifyNetworkDisconnected(CC_Mqtt5Client* client, bool disconnected) { m_funcs.m_notify_network_disconnected(client, disconnected); diff --git a/client/lib/test/unit/UnitTestCommonBase.h b/client/lib/test/unit/UnitTestCommonBase.h index 67d3646..b228bc1 100644 --- a/client/lib/test/unit/UnitTestCommonBase.h +++ b/client/lib/test/unit/UnitTestCommonBase.h @@ -21,7 +21,6 @@ class UnitTestCommonBase CC_Mqtt5ClientHandle (*m_alloc)() = nullptr; void (*m_free)(CC_Mqtt5ClientHandle) = nullptr; CC_Mqtt5ErrorCode (*m_init)(CC_Mqtt5ClientHandle) = nullptr; - bool (*m_is_initialized)(CC_Mqtt5ClientHandle) = nullptr; void (*m_tick)(CC_Mqtt5ClientHandle, unsigned) = nullptr; unsigned (*m_process_data)(CC_Mqtt5ClientHandle, const unsigned char*, unsigned) = nullptr; void (*m_notify_network_disconnected)(CC_Mqtt5ClientHandle, bool) = nullptr; @@ -346,7 +345,7 @@ class UnitTestCommonBase void unitTestSetUp(); void unitTestTearDown(); - UnitTestClientPtr::pointer unitTestAllocAndInitClient(bool addLog = false); + UnitTestClientPtr::pointer unitTestAllocClient(bool addLog = false); decltype(auto) unitTestSentData() { @@ -433,8 +432,6 @@ class UnitTestCommonBase void unitTestVerifyDisconnectSent(UnitTestDisconnectReason reason = UnitTestDisconnectReason::Success); UnitTestClientPtr unitTestAlloc(); - CC_Mqtt5ErrorCode unitTestInit(CC_Mqtt5Client* client); - bool unitTestIsInitialized(CC_Mqtt5Client* client) const; void unitTestNotifyNetworkDisconnected(CC_Mqtt5Client* client, bool disconnected); bool unitTestIsNetworkDisconnected(CC_Mqtt5Client* client); CC_Mqtt5ErrorCode unitTestSetDefaultResponseTimeout(CC_Mqtt5Client* client, unsigned ms); @@ -482,9 +479,9 @@ class UnitTestCommonBase void unitTestSetBrokerDisconnectReportCb(CC_Mqtt5ClientHandle handle, CC_Mqtt5BrokerDisconnectReportCb cb, void* data); void unitTestSetMessageReceivedReportCb(CC_Mqtt5ClientHandle handle, CC_Mqtt5MessageReceivedReportCb cb, void* data); -private: + void unitTestClearState(bool preserveTicks = true); - void unitTestClearState(bool preserveTicks = false); +private: static void unitTestErrorLogCb(void* obj, const char* msg); static void unitTestBrokerDisconnectedCb(void* obj, const CC_Mqtt5DisconnectInfo* info); diff --git a/client/lib/test/unit/UnitTestConnect.th b/client/lib/test/unit/UnitTestConnect.th index 1429d43..04fa133 100644 --- a/client/lib/test/unit/UnitTestConnect.th +++ b/client/lib/test/unit/UnitTestConnect.th @@ -62,7 +62,7 @@ void UnitTestConnect::test1() // [MQTT-3.1.2-13] // [MQTT-3.1.2-16] // [MQTT-3.1.2-18] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); TS_ASSERT_DIFFERS(client, nullptr); TS_ASSERT(unitTestCheckNoTicks()); TS_ASSERT(!unitTestIsConnected(client)); @@ -141,7 +141,7 @@ void UnitTestConnect::test2() // [MQTT-3.1.2-21] // [MQTT-3.2.2-9] // [MQTT-3.2.2-21] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); TS_ASSERT_DIFFERS(client, nullptr); TS_ASSERT(unitTestCheckNoTicks()); @@ -449,7 +449,7 @@ void UnitTestConnect::test3() // Connect with authentication // [MQTT-4.12.0-2] // [MQTT-4.12.0-3] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); TS_ASSERT_DIFFERS(client, nullptr); TS_ASSERT(unitTestCheckNoTicks()); @@ -632,7 +632,7 @@ void UnitTestConnect::test3() void UnitTestConnect::test4() { // Testing change of the Auth method, protocol error is expected - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); TS_ASSERT_DIFFERS(client, nullptr); TS_ASSERT(unitTestCheckNoTicks()); @@ -701,7 +701,7 @@ void UnitTestConnect::test5() // Keep alive test // [MQTT-3.1.2-20] // [MQTT-3.12.4-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto* connect = unitTestConnectPrepare(client, nullptr); TS_ASSERT_DIFFERS(connect, nullptr); @@ -777,7 +777,7 @@ void UnitTestConnect::test6() { // Testing inability to send any message before CONNECT. // [MQTT-3.1.0-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto ec = CC_Mqtt5ErrorCode_ValuesLimit; auto* disconnect = unitTestDisconnectPrepare(client, &ec); @@ -806,7 +806,7 @@ void UnitTestConnect::test7() // Testing attempt to send connect message second time // [MQTT-3.1.0-2] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); TS_ASSERT(!unitTestIsConnected(client)); const std::string ClientId(__FUNCTION__); @@ -824,7 +824,7 @@ void UnitTestConnect::test8() // Testing rejection of the non "clean start" on the first attempt to connect // [MQTT-3.1.2-4] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); TS_ASSERT(!unitTestIsConnected(client)); auto* connect = unitTestConnectPrepare(client, nullptr); @@ -844,7 +844,7 @@ void UnitTestConnect::test9() // Testing invalid will Qos // [MQTT-3.1.2-12] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); TS_ASSERT(!unitTestIsConnected(client)); auto* connect = unitTestConnectPrepare(client, nullptr); @@ -876,7 +876,7 @@ void UnitTestConnect::test10() // Testing network disconnection during connect // [MQTT-3.1.2-23] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); TS_ASSERT(!unitTestIsConnected(client)); auto* connect = unitTestConnectPrepare(client, nullptr); @@ -917,7 +917,7 @@ void UnitTestConnect::test11() // Delaying keep alive ping when network is disconnected // [MQTT-3.1.2-23] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); TS_ASSERT(!unitTestIsConnected(client)); const unsigned SessionExpiryInterval = 10; @@ -955,7 +955,7 @@ void UnitTestConnect::test12() // Testing rejection of "Reason String" in AUTH // [MQTT-3.1.2-29] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -1030,7 +1030,7 @@ void UnitTestConnect::test13() // Testing rejection of "User properties" in AUTH // [MQTT-3.1.2-29] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -1106,7 +1106,7 @@ void UnitTestConnect::test14() // Testing rejection of connection with "Client ID not valid" // [MQTT-3.1.3-8] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -1147,7 +1147,7 @@ void UnitTestConnect::test15() // Testing reception of DISCONNECT instead of CONNACK // [MQTT-3.1.4-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -1195,7 +1195,7 @@ void UnitTestConnect::test16() // Testing network disconnection after rejecting connect // [MQTT-3.1.4-2] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -1246,7 +1246,7 @@ void UnitTestConnect::test17() // Testing session taken over disconnection after successful connection. // [MQTT-3.1.4-3] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); @@ -1260,8 +1260,6 @@ void UnitTestConnect::test17() TS_ASSERT(unitTestHasDisconnectInfo()); auto& disconnectInfo = unitTestDisconnectInfo(); TS_ASSERT_EQUALS(disconnectInfo.m_reasonCode, CC_Mqtt5ReasonCode_SessionTakenOver); - - TS_ASSERT(!unitTestIsInitialized(client)); // Re-init is required. } void UnitTestConnect::test18() @@ -1269,7 +1267,7 @@ void UnitTestConnect::test18() // Testing reception of the session present when clean was requested // [MQTT-3.2.2-4] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -1310,7 +1308,7 @@ void UnitTestConnect::test19() // Testing cleared subscription when clean state is reported. // [MQTT-3.2.2-5] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); unitTestPerformBasicSubscribe(client, "#"); @@ -1334,8 +1332,7 @@ void UnitTestConnect::test19() TS_ASSERT(unitTestCheckNoTicks()); - auto ec = unitTestInit(client); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + unitTestClearState(); // Reconnecting requesting non clean start, while broker reports session is not present unitTestPerformBasicConnect(client, __FUNCTION__, false); @@ -1350,7 +1347,7 @@ void UnitTestConnect::test20() // Testing rejecting CONNACK with network disconnection // [MQTT-3.2.2-7] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -1365,8 +1362,6 @@ void UnitTestConnect::test20() TS_ASSERT(!unitTestIsNetworkDisconnected(client)); unitTestNotifyNetworkDisconnected(client, true); TS_ASSERT(unitTestIsNetworkDisconnected(client)); - [[maybe_unused]] auto ec = unitTestInit(client); - TS_ASSERT(!unitTestIsNetworkDisconnected(client)); } void UnitTestConnect::test21() @@ -1374,7 +1369,7 @@ void UnitTestConnect::test21() // Testing report of the max Qos by the broker // [MQTT-3.2.2-9] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -1392,7 +1387,7 @@ void UnitTestConnect::test22() // Testing rejection of connection with "QoS not supported" // [MQTT-3.2.2-12] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -1448,7 +1443,7 @@ void UnitTestConnect::test23() // Testing rejection of connection with "QoS not supported" // [MQTT-3.2.2-13] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -1506,7 +1501,7 @@ void UnitTestConnect::test24() // Require clean start for empty client id // [MQTT-3.2.2-16] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -1554,8 +1549,7 @@ void UnitTestConnect::test24() TS_ASSERT(unitTestCheckNoTicks()); - ec = unitTestInit(client); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + unitTestClearState(); basicConfig.m_cleanStart = false; @@ -1575,7 +1569,7 @@ void UnitTestConnect::test25() // Testing topic alias allocation // [MQTT-3.2.2-17] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto ec = unitTestPubTopicAliasAlloc(client, "topic1", 1); TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_NotConnected); @@ -1598,7 +1592,7 @@ void UnitTestConnect::test26() // Testing topic alias allocation when broker doesn't support aliases // [MQTT-3.2.2-18] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -1612,7 +1606,7 @@ void UnitTestConnect::test27() // Testing invalid flags for AUTH message. // [MQTT-3.15.1-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto* connect = unitTestConnectPrepare(client, nullptr); TS_ASSERT_DIFFERS(connect, nullptr); @@ -1676,7 +1670,7 @@ void UnitTestConnect::test28() // Testing bad reason code in AUTH message. // [MQTT-3.15.2-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto* connect = unitTestConnectPrepare(client, nullptr); TS_ASSERT_DIFFERS(connect, nullptr); @@ -1739,7 +1733,7 @@ void UnitTestConnect::test29() // Testing authentication rejection by broker // [MQTT-4.12.0-4] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); TS_ASSERT_DIFFERS(client, nullptr); TS_ASSERT(unitTestCheckNoTicks()); @@ -1837,7 +1831,7 @@ void UnitTestConnect::test30() // Testing wrong AuthMethod in CONNACK // [MQTT-4.12.0-5] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); TS_ASSERT_DIFFERS(client, nullptr); TS_ASSERT(unitTestCheckNoTicks()); @@ -1937,7 +1931,7 @@ void UnitTestConnect::test31() // Testing wrong AuthMethod in AUTH // [MQTT-4.12.0-5] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); TS_ASSERT_DIFFERS(client, nullptr); TS_ASSERT(unitTestCheckNoTicks()); @@ -2011,7 +2005,7 @@ void UnitTestConnect::test32() // Testing unexpected AUTH message from broker // [MQTT-4.12.0-5] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); TS_ASSERT_DIFFERS(client, nullptr); TS_ASSERT(unitTestCheckNoTicks()); @@ -2073,7 +2067,7 @@ void UnitTestConnect::test32() void UnitTestConnect::test33() { // Extra keep alive testing - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); TS_ASSERT_DIFFERS(client, nullptr); TS_ASSERT(unitTestCheckNoTicks()); @@ -2120,9 +2114,12 @@ void UnitTestConnect::test33() TS_ASSERT_EQUALS(tickReq->m_requested, nextTickMs); unitTestTick(6000); - unitTestInit(client); + auto config = CC_Mqtt5DisconnectConfig(); + unitTestDisconnectInitConfig(&config); + unitTestPerformDisconnect(client, &config); TS_ASSERT(unitTestCheckNoTicks()); + unitTestClearState(); unitTestPerformBasicConnect(client, __FUNCTION__); tickReq = unitTestTickReq(); diff --git a/client/lib/test/unit/UnitTestDefaultBase.cpp b/client/lib/test/unit/UnitTestDefaultBase.cpp index 7f0d5a9..1079910 100644 --- a/client/lib/test/unit/UnitTestDefaultBase.cpp +++ b/client/lib/test/unit/UnitTestDefaultBase.cpp @@ -7,8 +7,6 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() static LibFuncs funcs; funcs.m_alloc = &cc_mqtt5_client_alloc; funcs.m_free = &cc_mqtt5_client_free; - funcs.m_init = &cc_mqtt5_client_init; - funcs.m_is_initialized = &cc_mqtt5_client_is_initialized; funcs.m_tick = &cc_mqtt5_client_tick; funcs.m_process_data = &cc_mqtt5_client_process_data; funcs.m_notify_network_disconnected = &cc_mqtt5_client_notify_network_disconnected; diff --git a/client/lib/test/unit/UnitTestDisconnect.th b/client/lib/test/unit/UnitTestDisconnect.th index 60f0b21..59f72e1 100644 --- a/client/lib/test/unit/UnitTestDisconnect.th +++ b/client/lib/test/unit/UnitTestDisconnect.th @@ -27,7 +27,7 @@ private: void UnitTestDisconnect::test1() { // Simple disconnect from broker - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); TS_ASSERT_DIFFERS(client, nullptr); unitTestPerformBasicConnect(client, __FUNCTION__); @@ -46,7 +46,7 @@ void UnitTestDisconnect::test2() { // Bad reserved bits DISCONNECT from broker // [MQTT-3.14.1-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); TS_ASSERT_DIFFERS(client, nullptr); unitTestPerformBasicConnect(client, __FUNCTION__); @@ -68,7 +68,7 @@ void UnitTestDisconnect::test3() // If the Session Expiry Interval in the CONNECT packet was zero, then it is a Protocol Error to set a non- // zero Session Expiry Interval in the DISCONNECT packet sent by the Client - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); TS_ASSERT_DIFFERS(client, nullptr); unitTestPerformBasicConnect(client, __FUNCTION__); @@ -83,15 +83,14 @@ void UnitTestDisconnect::test3() TS_ASSERT_DIFFERS(disconnect, nullptr); auto ec = unitTestDisconnectConfig(disconnect, &config); TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_BadParam); - } void UnitTestDisconnect::test4() { - // Testing rejection any operation after disconnect + // Testing rejection any operation (except connect) after disconnect // [MQTT-3.14.4-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); TS_ASSERT_DIFFERS(client, nullptr); unitTestPerformBasicConnect(client, __FUNCTION__); @@ -104,23 +103,19 @@ void UnitTestDisconnect::test4() TS_ASSERT(!unitTestIsConnected(client)); CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_Success; - auto* connect = unitTestConnectPrepare(client, &ec); - TS_ASSERT_EQUALS(connect, nullptr); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_NotIntitialized); - auto* subscribe = unitTestSubscribePrepare(client, &ec); TS_ASSERT_EQUALS(subscribe, nullptr); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_NotIntitialized); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_NotConnected); auto* unsubscribe = unitTestUnsubscribePrepare(client, &ec); TS_ASSERT_EQUALS(unsubscribe, nullptr); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_NotIntitialized); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_NotConnected); auto* publish = unitTestPublishPrepare(client, &ec); TS_ASSERT_EQUALS(publish, nullptr); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_NotIntitialized); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_NotConnected); auto* disconnect = unitTestDisconnectPrepare(client, &ec); TS_ASSERT_EQUALS(disconnect, nullptr); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_NotIntitialized); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_NotConnected); } \ No newline at end of file diff --git a/client/lib/test/unit/UnitTestPublish.th b/client/lib/test/unit/UnitTestPublish.th index 92768a8..ca76de7 100644 --- a/client/lib/test/unit/UnitTestPublish.th +++ b/client/lib/test/unit/UnitTestPublish.th @@ -66,7 +66,7 @@ void UnitTestPublish::test1() // [MQTT-3.3.2-4] // [MQTT-3.3.2-5] // [MQTT-4.3.1-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -162,7 +162,7 @@ void UnitTestPublish::test2() { // Simple Qos1 publish // [MQTT-4.3.1-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -214,7 +214,7 @@ void UnitTestPublish::test2() void UnitTestPublish::test3() { // Simple Qos1 publish with response properties - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -314,7 +314,7 @@ void UnitTestPublish::test4() { // Timed out Qos1 publish // [MQTT-3.3.1-3] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -370,7 +370,7 @@ void UnitTestPublish::test5() // Simple Qos2 publish // [MQTT-4.3.3-2] // [MQTT-4.3.3-4] - auto* client = unitTestAllocAndInitClient(true); + auto* client = unitTestAllocClient(true); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); basicConfig.m_clientId = __FUNCTION__; @@ -507,7 +507,7 @@ void UnitTestPublish::test6() // Timed out Qos2 publish // [MQTT-3.3.1-3] // [MQTT-4.3.3-5] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -585,7 +585,7 @@ void UnitTestPublish::test7() { // Qos0 publish with topic id // [MQTT-3.3.2-8] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformPubTopicAliasConnect(client, __FUNCTION__, 10); TS_ASSERT(unitTestIsConnected(client)); @@ -693,7 +693,7 @@ void UnitTestPublish::test8() { // Qos1 publish with topic id // [MQTT-3.3.2-8] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformPubTopicAliasConnect(client, __FUNCTION__, 10); TS_ASSERT(unitTestIsConnected(client)); @@ -786,7 +786,7 @@ void UnitTestPublish::test9() { // Qos2 publish with topic id // [MQTT-3.3.2-8] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformPubTopicAliasConnect(client, __FUNCTION__, 10); TS_ASSERT(unitTestIsConnected(client)); @@ -910,7 +910,7 @@ void UnitTestPublish::test10() // [MQTT-4.7.2-1] // [MQTT-4.7.3-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -945,7 +945,7 @@ void UnitTestPublish::test11() // Testing invalid packet ID in PUBACK // [MQTT-2.2.1-5] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -1003,7 +1003,7 @@ void UnitTestPublish::test12() // Testing invalid packet ID in PUBREC // [MQTT-2.2.1-5] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -1075,7 +1075,7 @@ void UnitTestPublish::test13() // Testing invalid packet ID in PUBCOMP // [MQTT-2.2.1-5] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -1149,7 +1149,7 @@ void UnitTestPublish::test14() // Suspending Qos1 publish operation when network is disconnected // [MQTT-3.1.2-23] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); const unsigned SessionExpiryInterval = 10; unitTestPerformSessionExpiryConnect(client, __FUNCTION__, SessionExpiryInterval); @@ -1218,7 +1218,7 @@ void UnitTestPublish::test15() // Suspending Qos2 publish operation when network is disconnected // [MQTT-3.1.2-23] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); const unsigned SessionExpiryInterval = 10; unitTestPerformSessionExpiryConnect(client, __FUNCTION__, SessionExpiryInterval); @@ -1300,7 +1300,7 @@ void UnitTestPublish::test16() // Testing rejection of "Reason String" in PUBACK // [MQTT-3.1.2-29] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -1370,7 +1370,7 @@ void UnitTestPublish::test17() // Testing rejection of "User Properties" in PUBACK // [MQTT-3.1.2-29] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -1443,7 +1443,7 @@ void UnitTestPublish::test18() // Testing rejection of "Reason String" in PUBREC // [MQTT-3.1.2-29] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -1513,7 +1513,7 @@ void UnitTestPublish::test19() // Testing rejection of "User Properties" in PUBREL // [MQTT-3.1.2-29] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -1586,7 +1586,7 @@ void UnitTestPublish::test20() // Testing rejection of "Reason String" in PUBCOMP // [MQTT-3.1.2-29] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -1668,7 +1668,7 @@ void UnitTestPublish::test21() // Testing rejection of "User Properties" in PUBCOMP // [MQTT-3.1.2-29] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -1753,7 +1753,7 @@ void UnitTestPublish::test22() // [MQTT-3.2.2-11] // [MQTT-4.9.0-2] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -1803,7 +1803,7 @@ void UnitTestPublish::test23() // Testing rejection of publish with retain when it's unavailable // [MQTT-3.2.2-14] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -1851,7 +1851,7 @@ void UnitTestPublish::test24() // Testing rejection of publish exceeding max packet size // [MQTT-3.2.2-15] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -1905,7 +1905,7 @@ void UnitTestPublish::test25() // Testing rejection of invalid QoS value // [MQTT-3.3.1-4] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); @@ -1934,7 +1934,7 @@ void UnitTestPublish::test26() // Qos0 publish with topic alias with network disconnection // [MQTT-3.3.2-7] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -2060,7 +2060,7 @@ void UnitTestPublish::test27() // Testing inability to use wildcards in "Response Topic" // [MQTT-3.3.2-14] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -2089,7 +2089,7 @@ void UnitTestPublish::test28() // Testing incoming PUBREC instead of PUBACK // [MQTT-3.3.4-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -2142,7 +2142,7 @@ void UnitTestPublish::test29() // Testing incoming PUBACK instead of PUBREC // [MQTT-3.3.4-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -2195,7 +2195,7 @@ void UnitTestPublish::test30() // Testing limit of the outgoing high Qos publishes // [MQTT-3.3.4-7] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -2268,7 +2268,7 @@ void UnitTestPublish::test31() // Testing rejection of the Qos1 publish by the broker // [MQTT-4.4.0-2] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -2321,7 +2321,7 @@ void UnitTestPublish::test32() // Testing rejection of the Qos2 publish by the broker // [MQTT-4.4.0-2] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -2374,7 +2374,7 @@ void UnitTestPublish::test33() // Testing re-publish of not acked Qos1 messages in the same order. // [MQTT-4.6.0-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -2459,7 +2459,7 @@ void UnitTestPublish::test34() // Testing re-publish of not acked Qos1 and Qos2 messages in the same order. // [MQTT-4.6.0-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -2545,7 +2545,7 @@ void UnitTestPublish::test35() // Testing preserving of the PUBLISH (QoS1) operation after the broker disconnection. // [MQTT-4.4.0-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -2584,9 +2584,8 @@ void UnitTestPublish::test35() TS_ASSERT(!unitTestIsPublishComplete()); TS_ASSERT(unitTestCheckNoTicks()); - ec = unitTestInit(client); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); - TS_ASSERT(unitTestCheckNoTicks()); + unitTestClearState(); + unitTestNotifyNetworkDisconnected(client, false); // Reconnection with attempt to restore the session auto connectConfig = CC_Mqtt5ConnectBasicConfig(); @@ -2627,7 +2626,7 @@ void UnitTestPublish::test36() // Testing preserving of the PUBLISH (QoS2) operation after the broker disconnection. // [MQTT-4.4.0-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -2666,9 +2665,8 @@ void UnitTestPublish::test36() TS_ASSERT(!unitTestIsPublishComplete()); TS_ASSERT(unitTestCheckNoTicks()); - ec = unitTestInit(client); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); - TS_ASSERT(unitTestCheckNoTicks()); + unitTestClearState(); + unitTestNotifyNetworkDisconnected(client, false); // Reconnection with attempt to restore the session auto connectConfig = CC_Mqtt5ConnectBasicConfig(); @@ -2721,7 +2719,7 @@ void UnitTestPublish::test37() // Testing preserving of the PUBLISH (QoS2) operation after the broker disconnection and after reception of PUBREC. // [MQTT-4.4.0-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -2772,9 +2770,8 @@ void UnitTestPublish::test37() TS_ASSERT(!unitTestIsPublishComplete()); TS_ASSERT(unitTestCheckNoTicks()); - ec = unitTestInit(client); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); - TS_ASSERT(unitTestCheckNoTicks()); + unitTestClearState(); + unitTestNotifyNetworkDisconnected(client, false); // Reconnection with attempt to restore the session auto connectConfig = CC_Mqtt5ConnectBasicConfig(); @@ -2813,7 +2810,7 @@ void UnitTestPublish::test38() // Testing preserving of the PUBLISH (QoS1) operation after the broker disconnection and termination upon reconnect // [MQTT-4.6.0-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -2848,8 +2845,8 @@ void UnitTestPublish::test38() TS_ASSERT(!unitTestIsPublishComplete()); TS_ASSERT(unitTestCheckNoTicks()); - ec = unitTestInit(client); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + unitTestClearState(); + unitTestNotifyNetworkDisconnected(client, false); // Reconnection with attempt to restore the session, but the clean session is reported unitTestPerformBasicConnect(client, __FUNCTION__, true); @@ -2865,7 +2862,7 @@ void UnitTestPublish::test39() // Testing preserving of the PUBLISH (QoS1) operation after the broker disconnection (initiated by the broker). // [MQTT-4.4.0-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -2912,10 +2909,8 @@ void UnitTestPublish::test39() TS_ASSERT(!unitTestIsPublishComplete()); TS_ASSERT(unitTestCheckNoTicks()); - ec = unitTestInit(client); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); - TS_ASSERT(unitTestCheckNoTicks()); - + unitTestClearState(); + // Reconnection with attempt to restore the session auto connectConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&connectConfig); diff --git a/client/lib/test/unit/UnitTestReauth.th b/client/lib/test/unit/UnitTestReauth.th index 9b24c98..0348f14 100644 --- a/client/lib/test/unit/UnitTestReauth.th +++ b/client/lib/test/unit/UnitTestReauth.th @@ -32,7 +32,7 @@ void UnitTestReauth::test1() { // Simple reauth from client // [MQTT-4.12.1-1] - auto* client = unitTestAllocAndInitClient(true); + auto* client = unitTestAllocClient(true); TS_ASSERT_DIFFERS(client, nullptr); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); @@ -200,7 +200,7 @@ void UnitTestReauth::test2() // Testing invalid reason code from broker // [MQTT-3.15.2-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); TS_ASSERT_DIFFERS(client, nullptr); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); @@ -275,7 +275,7 @@ void UnitTestReauth::test3() // Testing big reason string in AUTH. // [MQTT-3.15.2-2] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -358,7 +358,7 @@ void UnitTestReauth::test4() // Testing big user properties strings in AUTH. // [MQTT-3.15.2-3] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -441,7 +441,7 @@ void UnitTestReauth::test5() // Testing inability to perfrom reauth operation when connect didn't have auth // [MQTT-4.12.0-7] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); TS_ASSERT_DIFFERS(client, nullptr); unitTestPerformBasicConnect(client, __FUNCTION__); @@ -456,7 +456,7 @@ void UnitTestReauth::test6() { // Testing AUTH rejection by the client. // [MQTT-4.12.1-2] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); TS_ASSERT_DIFFERS(client, nullptr); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); @@ -536,7 +536,7 @@ void UnitTestReauth::test7() { // Testing AUTH rejection by the broker. // [MQTT-4.12.1-2] - auto* client = unitTestAllocAndInitClient(true); + auto* client = unitTestAllocClient(true); TS_ASSERT_DIFFERS(client, nullptr); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); @@ -593,7 +593,7 @@ void UnitTestReauth::test7() void UnitTestReauth::test8() { // Simple reauth from client when broker responds without reason code. - auto* client = unitTestAllocAndInitClient(true); + auto* client = unitTestAllocClient(true); TS_ASSERT_DIFFERS(client, nullptr); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); diff --git a/client/lib/test/unit/UnitTestReceive.th b/client/lib/test/unit/UnitTestReceive.th index f2eaab0..73ef613 100644 --- a/client/lib/test/unit/UnitTestReceive.th +++ b/client/lib/test/unit/UnitTestReceive.th @@ -50,7 +50,7 @@ void UnitTestReceive::test1() // Simple receive of Qos0 // [MQTT-3.3.2-4] // [MQTT-3.3.2-6] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -136,7 +136,7 @@ void UnitTestReceive::test2() { // Simple receive of Qos1 // [MQTT-4.3.2-4] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -180,7 +180,7 @@ void UnitTestReceive::test3() // [MQTT-3.3.2-6] // [MQTT-4.3.3-8] // [MQTT-4.3.3-11] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -332,7 +332,7 @@ void UnitTestReceive::test4() // Testing rejection of the allocated packet Id in Qos2 publish. // [MQTT-2.2.1-4] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -400,7 +400,7 @@ void UnitTestReceive::test5() // Testing invalid packet ID in PUBREL // [MQTT-2.2.1-5] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -458,7 +458,7 @@ void UnitTestReceive::test6() // Testing network disconnection during reception of Qos2 message // [MQTT-3.1.2-23] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); const unsigned ResponseTimeout = 2000; unitTestSetDefaultResponseTimeout(client, ResponseTimeout); @@ -522,7 +522,7 @@ void UnitTestReceive::test7() // Testing network disconnection during reception of Qos2 message // [MQTT-3.1.2-23] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); const unsigned ResponseTimeout = 2000; unitTestSetDefaultResponseTimeout(client, ResponseTimeout); @@ -574,7 +574,7 @@ void UnitTestReceive::test8() // Rejecting packet bigger than configured. // [MQTT-3.1.2-24] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); TS_ASSERT(!unitTestIsConnected(client)); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); @@ -618,7 +618,7 @@ void UnitTestReceive::test9() // Testing rejection of "Reason String" in PUBREL // [MQTT-3.1.2-29] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -685,7 +685,7 @@ void UnitTestReceive::test10() // Testing rejection of "User Properties" in PUBREL // [MQTT-3.1.2-29] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -750,7 +750,7 @@ void UnitTestReceive::test11() // [MQTT-3.3.1-1] // [MQTT-4.3.3-10] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -825,7 +825,7 @@ void UnitTestReceive::test12() // Testing reception of the DUP bit set right away (first PUBLISH was missed) // [MQTT-3.3.1-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -882,7 +882,7 @@ void UnitTestReceive::test13() // Testing reception of the unsolicited message // [MQTT-3.3.2-3] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -941,7 +941,7 @@ void UnitTestReceive::test14() // Testing reception of topic aliases // [MQTT-3.3.2-10] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -1039,7 +1039,7 @@ void UnitTestReceive::test15() // Testing reception of invalid topic alias // [MQTT-3.3.2-10] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -1087,7 +1087,7 @@ void UnitTestReceive::test16() // Testing exceeding "Receive Maximum" value by the broker // [MQTT-3.3.4-9] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -1145,7 +1145,7 @@ void UnitTestReceive::test17() // Testing invalid PUBREL // [MQTT-3.6.1-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -1194,7 +1194,7 @@ void UnitTestReceive::test18() // Finish reception of the Qos2 message after unsubscribe // [MQTT-3.10.4-3] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -1255,7 +1255,7 @@ void UnitTestReceive::test19() // Testing double receive of the same Qos1 message // [MQTT-4.3.2-5] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -1314,7 +1314,7 @@ void UnitTestReceive::test20() // Testing PUBLISH after rejecting PUBREC // [MQTT-4.3.3-9] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -1366,7 +1366,7 @@ void UnitTestReceive::test21() // Double reception of the same Qos2 message after ack // [MQTT-4.3.3-12] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -1459,7 +1459,7 @@ void UnitTestReceive::test22() { // Testing resuming reception of the PUBLISH (QoS2) after reconnection. // [MQTT-4.4.0-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -1500,9 +1500,8 @@ void UnitTestReceive::test22() TS_ASSERT(!unitTestIsPublishComplete()); TS_ASSERT(unitTestCheckNoTicks()); - auto ec = unitTestInit(client); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); - TS_ASSERT(unitTestCheckNoTicks()); + unitTestClearState(); + unitTestNotifyNetworkDisconnected(client, false); // Reconnection with attempt to restore the session auto connectConfig = CC_Mqtt5ConnectBasicConfig(); @@ -1547,7 +1546,7 @@ void UnitTestReceive::test23() { // Testing resuming reception of the PUBLISH (QoS2) after reconnection, assuming PUBREC was received by the broker // [MQTT-4.4.0-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -1588,9 +1587,8 @@ void UnitTestReceive::test23() TS_ASSERT(!unitTestIsPublishComplete()); TS_ASSERT(unitTestCheckNoTicks()); - auto ec = unitTestInit(client); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); - TS_ASSERT(unitTestCheckNoTicks()); + unitTestClearState(); + unitTestNotifyNetworkDisconnected(client, false); // Reconnection with attempt to restore the session auto connectConfig = CC_Mqtt5ConnectBasicConfig(); @@ -1625,7 +1623,7 @@ void UnitTestReceive::test24() { // Testing discarding reseption of the PUBLISH (QoS2) after reconnection without session present. // [MQTT-4.4.0-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -1666,9 +1664,8 @@ void UnitTestReceive::test24() TS_ASSERT(!unitTestIsPublishComplete()); TS_ASSERT(unitTestCheckNoTicks()); - auto ec = unitTestInit(client); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); - TS_ASSERT(unitTestCheckNoTicks()); + unitTestClearState(); + unitTestNotifyNetworkDisconnected(client, false); // Reconnection with attempt to restore the session unitTestPerformBasicConnect(client, __FUNCTION__, false); @@ -1712,7 +1709,7 @@ void UnitTestReceive::test25() { // Testing resuming reception of the PUBLISH (QoS2) after reconnection. // [MQTT-4.4.0-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -1758,9 +1755,7 @@ void UnitTestReceive::test25() unitTestPopDisconnectInfo(); TS_ASSERT(unitTestCheckNoTicks()); - auto ec = unitTestInit(client); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); - TS_ASSERT(unitTestCheckNoTicks()); + unitTestClearState(); // Reconnection with attempt to restore the session auto connectConfig = CC_Mqtt5ConnectBasicConfig(); diff --git a/client/lib/test/unit/UnitTestSubscribe.th b/client/lib/test/unit/UnitTestSubscribe.th index 5530e00..927e3d0 100644 --- a/client/lib/test/unit/UnitTestSubscribe.th +++ b/client/lib/test/unit/UnitTestSubscribe.th @@ -39,7 +39,7 @@ void UnitTestSubscribe::test1() { // Simple subscribe and ack // [MQTT-3.8.4-5] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -128,7 +128,7 @@ void UnitTestSubscribe::test1() void UnitTestSubscribe::test2() { // Parallel subscribe - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -248,7 +248,7 @@ void UnitTestSubscribe::test3() // [MQTT-4.7.3-1] // [MQTT-4.8.2-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -319,7 +319,7 @@ void UnitTestSubscribe::test4() // Testing invalid suback; // [MQTT-2.2.1-6] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -365,7 +365,7 @@ void UnitTestSubscribe::test5() // Suspending subscribe operation when network is disconnected // [MQTT-3.1.2-23] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); const unsigned SessionExpiryInterval = 10; unitTestPerformSessionExpiryConnect(client, __FUNCTION__, SessionExpiryInterval); @@ -431,7 +431,7 @@ void UnitTestSubscribe::test6() { // Testing properties in SUBACK - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -507,7 +507,7 @@ void UnitTestSubscribe::test7() // Testing rejection of "Reason String" in SUBACK // [MQTT-3.1.2-29] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -562,7 +562,7 @@ void UnitTestSubscribe::test8() // Testing rejection of "User properties" in SUBACK // [MQTT-3.1.2-29] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -617,7 +617,7 @@ void UnitTestSubscribe::test9() { // Testing wildcard subscriptions disabled - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -657,7 +657,7 @@ void UnitTestSubscribe::test10() { // Testing subscription IDs disabled - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -692,7 +692,7 @@ void UnitTestSubscribe::test11() { // Testing shared subscriptions disabled - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -722,7 +722,7 @@ void UnitTestSubscribe::test12() // Testing rejection of the "No Local" bit set for shared subscriptions // [MQTT-3.8.3-4] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -743,7 +743,7 @@ void UnitTestSubscribe::test13() { // Simple SUBACK with wrong number of reason codes // [MQTT-3.8.4-6] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -813,7 +813,7 @@ void UnitTestSubscribe::test14() // Testing Qos2 subscription when broker doesn't support Qos2 publish. // [MQTT-3.8.4-6] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); diff --git a/client/lib/test/unit/UnitTestUnsubscribe.th b/client/lib/test/unit/UnitTestUnsubscribe.th index 794a1b6..25e1c3a 100644 --- a/client/lib/test/unit/UnitTestUnsubscribe.th +++ b/client/lib/test/unit/UnitTestUnsubscribe.th @@ -34,7 +34,7 @@ void UnitTestUnsubscribe::test1() { // Simple unsubscribe and ack // [MQTT-3.11.3-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -100,7 +100,7 @@ void UnitTestUnsubscribe::test1() void UnitTestUnsubscribe::test2() { // Parallel unsubscribe - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -196,7 +196,7 @@ void UnitTestUnsubscribe::test2() void UnitTestUnsubscribe::test3() { // Invalid unsubscribe topics - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -251,7 +251,7 @@ void UnitTestUnsubscribe::test4() // Testing invalid unsuback; // [MQTT-2.2.1-6] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -302,7 +302,7 @@ void UnitTestUnsubscribe::test5() // Suspending unsubscribe operation when network is disconnected // [MQTT-3.1.2-23] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); const unsigned SessionExpiryInterval = 10; unitTestPerformSessionExpiryConnect(client, __FUNCTION__, SessionExpiryInterval); @@ -372,7 +372,7 @@ void UnitTestUnsubscribe::test6() { // Testing properties in UNSUBACK - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); @@ -450,7 +450,7 @@ void UnitTestUnsubscribe::test7() // Testing rejection of "Reason String" in UNSUBACK // [MQTT-3.1.2-29] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -506,7 +506,7 @@ void UnitTestUnsubscribe::test8() // Testing rejection of "User properties" in UNSUBACK // [MQTT-3.1.2-29] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -563,7 +563,7 @@ void UnitTestUnsubscribe::test9() // Testing rejection of unsubscribe attempt for the topic that hasn't been subscribed before // [MQTT-3.10.4-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); @@ -585,7 +585,7 @@ void UnitTestUnsubscribe::test10() // Testing unexpected number of reason codes in UNSUBACK // [MQTT-3.11.3-1] - auto* client = unitTestAllocAndInitClient(); + auto* client = unitTestAllocClient(); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); From 7394ad7ef1d953140a607106d733f64789b9ea0a Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sun, 31 Mar 2024 17:54:59 +1000 Subject: [PATCH 10/48] Added ops wrapper functions to the API. --- client/lib/doxygen/main.dox | 415 +++++++++++++++++++++--------- client/lib/src/ClientImpl.cpp | 1 + client/lib/templ/client.cpp.templ | 274 ++++++++++++++++++++ client/lib/templ/client.h.templ | 204 +++++++++++++++ 4 files changed, 771 insertions(+), 123 deletions(-) diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index fc04735..e16b7f6 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -1,6 +1,6 @@ /// @mainpage MQTT5 Client Library /// @tableofcontents -/// @section cc_mqtt5_client_overview Overview +/// @section doc_cc_mqtt5_client_overview Overview /// The MQTT5 Client Library from the CommsChampion Ecosystem /// provides simple, asynchronous, non-blocking, /// and easy to use interface to operate MQTT v5 client. The library doesn't @@ -13,7 +13,7 @@ /// The library allows the application to have a full control over the raw data for /// any extra analysis and/or manipulation, such as encryption. /// -/// @section cc_mqtt5_client_version Version of the Library +/// @section doc_cc_mqtt5_client_version Version of the Library /// The version is of the library applicable to this documentation is defined in /// the @ref common.h "cc_mqtt5_client/common.h" file using the following defines: /// @li @ref CC_MQTT5_CLIENT_MAJOR_VERSION @@ -26,7 +26,7 @@ /// #include "cc_mqtt5_client/client.h" /// @endcode /// -/// @section cc_mqtt5_client_allocation Client Allocation +/// @section doc_cc_mqtt5_client_allocation Client Allocation /// The library supports multiple independent MQTT v5 client sessions. The /// allocation of data structures relevant to a single client is performed /// using cc_mqtt5_client_alloc() function. @@ -61,10 +61,10 @@ /// @b IMPORTANT: The function @b cc_mqtt5_client_free() must @b NOT /// be called from within a callback. Use next event loop iteration. /// -/// @section cc_mqtt5_client_callbacks "Must Have" Callbacks Registration +/// @section doc_cc_mqtt5_client_callbacks "Must Have" Callbacks Registration /// In order to properly function the library requires setting several callbacks. /// -/// @subsection cc_mqtt5_client_callbacks_send_data Sending Data To Broker +/// @subsection doc_cc_mqtt5_client_callbacks_send_data Sending Data To Broker /// To client application must assign a callback for the library to be able to send /// binary data out to the connected broker. /// @code @@ -86,7 +86,7 @@ /// 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 cc_mqtt5_client_callbacks_broker_disconnect Reporting Unsolicited Broker Disconnection +/// @subsection doc_cc_mqtt5_client_callbacks_broker_disconnect Reporting Unsolicited Broker Disconnection /// The client application must assign a callback for the library to report /// discovered broker disconnection. /// @code @@ -105,10 +105,10 @@ /// When the broker disconnection is due to reception of the **DISCONNECT** /// message from the broker the "info" parameter will report a disconnection reason as well /// as extra information reported by the broker. If the broker disconnection is due -/// to other reasons, the "info" parameter will be @b NULL. See also @ref cc_mqtt5_client_unsolicited_disconnect +/// to other reasons, the "info" parameter will be @b NULL. See also @ref doc_cc_mqtt5_client_unsolicited_disconnect /// below for details. /// -/// @subsection cc_mqtt5_client_callbacks_message Reporting Received Message +/// @subsection doc_cc_mqtt5_client_callbacks_message Reporting Received Message /// The client application must assign a callback for the library to report /// application level messages received from the broker. /// @code @@ -121,7 +121,7 @@ /// @endcode /// See also the documentation of the @ref CC_Mqtt5MessageReceivedReportCb callback function definition. /// -/// @section cc_mqtt5_client_time Time Measurement +/// @section doc_cc_mqtt5_client_time Time Measurement /// For the correct operation of the MQTT v5 client side of the protocol, the library /// requires an ability to measure time. This responsibility is delegated to the /// application. @@ -173,7 +173,7 @@ /// 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 cc_mqtt5_client_log Error Logging +/// @section doc_cc_mqtt5_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. @@ -187,7 +187,7 @@ /// @endcode /// See also the documentation of the @ref CC_Mqtt5ErrorLogCb callback function definition. /// -/// @section cc_mqtt5_client_data Reporting Incoming Data +/// @section doc_cc_mqtt5_client_data Reporting Incoming Data /// It is the responsibility of the application to receive data from the broker /// and report it to the library. The report is performed using the /// @b cc_mqtt5_client_process_data() function. @@ -209,7 +209,7 @@ /// such as reporting received message, sending new data out, as well as canceling /// the old and programming new tick timeout. /// -/// @section cc_mqtt5_client_concepts Operating Concepts +/// @section doc_cc_mqtt5_client_concepts Operating Concepts /// The library abstracts away multiple MQTT v5 protocol based "operations". Every such operation /// has multiple stages: /// @li @b prepare - The operation is "allocated" and relevant handle is returned. @@ -241,7 +241,7 @@ /// the prepared operation using the @b cancel request. After performing the @b cancel /// stage the allocated handle is no longer valid. /// -/// @section cc_mqtt5_client_response_timeout Default Response Timeout +/// @section doc_cc_mqtt5_client_response_timeout Default Response Timeout /// After sending any operation request to the broker, the client library has to allow /// some time for the broker to process the request. If it takes too much time, the /// client must report that operation has failed via the set callback. By default the client @@ -256,10 +256,10 @@ /// } /// @endcode /// -/// @section cc_mqtt5_client_connect Connecting to Broker +/// @section doc_cc_mqtt5_client_connect Connecting to Broker /// To connect to broker use @ref connect "connect" operation. /// -/// @subsection cc_mqtt5_client_connect_prepare Preparing "Connect" Operation. +/// @subsection doc_cc_mqtt5_client_connect_prepare Preparing "Connect" Operation. /// @code /// CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_Success; /// CC_Mqtt5ConnectHandle connect = cc_mqtt5_client_connect_prepare(client, &ec); @@ -268,12 +268,12 @@ /// } /// @endcode /// @b NOTE that the @b cc_mqtt5_client_connect_prepare() cannot be called from within a callback. -/// For example, if the broker disconnection is reported via @ref cc_mqtt5_client_callbacks_broker_disconnect "callback" +/// For example, if the broker disconnection is reported via @ref doc_cc_mqtt5_client_callbacks_broker_disconnect "callback" /// then the @b cc_mqtt5_client_connect_prepare() cannot be invoked right away. /// It needs to be postponed until the next event loop iteration. /// -/// @subsection cc_mqtt5_client_connect_response_timeout Configuring "Connect" Response Timeout -/// When created, the "connect" operation inherits the @ref cc_mqtt5_client_response_timeout +/// @subsection doc_cc_mqtt5_client_connect_response_timeout Configuring "Connect" Response Timeout +/// When created, the "connect" operation inherits the @ref doc_cc_mqtt5_client_response_timeout /// configuration. It can be changed for the allocated operation using the /// @b cc_mqtt5_client_connect_set_response_timeout() function. /// @code @@ -284,7 +284,7 @@ /// @endcode /// To retrieve the configured response timeout use the @b cc_mqtt5_client_connect_get_response_timeout() function. /// -/// @subsection cc_mqtt5_client_connect_basic Basic Configuration of "Connect" Operation +/// @subsection doc_cc_mqtt5_client_connect_basic Basic Configuration of "Connect" Operation /// The "basic" configuration means no extra MQTT v5 properties assigned to the message. /// @code /// CC_Mqtt5ConnectBasicConfig basicConfig; @@ -314,7 +314,7 @@ /// /// See also documentation of the @ref CC_Mqtt5ConnectBasicConfig structure. /// -/// @subsection cc_mqtt5_client_connect_will Will Configuration +/// @subsection doc_cc_mqtt5_client_connect_will Will Configuration /// To perform will configuration use the @b cc_mqtt5_client_connect_config_will() function. /// @code /// CC_Mqtt5ConnectWillConfig willConfig; @@ -357,7 +357,7 @@ /// @endcode /// See also documentation of the @ref CC_Mqtt5UserProp structure. /// -/// @subsection cc_mqtt5_client_connect_extra Extra Properties Configuration +/// @subsection doc_cc_mqtt5_client_connect_extra Extra Properties Configuration /// To add extra MQTT v5 specific properties to the connection request use /// @b cc_mqtt5_client_connect_config_extra() function. /// @code @@ -384,7 +384,7 @@ /// it may result in setting multiple properties of the same type, which /// is the "Protocol Error" according to the MQTT v5 specification. /// -/// @subsection cc_mqtt5_client_connect_auth Extended Authentication Handshake Configuration +/// @subsection doc_cc_mqtt5_client_connect_auth Extended Authentication Handshake Configuration /// In case the extended authentication handshake is needed use @b cc_mqtt5_client_connect_config_auth() function. /// @code /// CC_Mqtt5AuthErrorCode my_auth_handshake_cb(void* data, const CC_Mqtt5AuthInfo* authInfoIn, CC_Mqtt5AuthInfo* authInfoOut) @@ -425,9 +425,9 @@ /// of the @ref CC_Mqtt5AuthErrorCode_Continue. /// /// When client side fails the authentication, the @b DISCONNECT message is -/// sent to the broker and "connect" operation callback (see @ref cc_mqtt5_client_connect_send below) +/// sent to the broker and "connect" operation callback (see @ref doc_cc_mqtt5_client_connect_send below) /// will be invoked with @ref CC_Mqtt5AsyncOpStatus_Aborted as status report. The -/// broker disconnection report @ref cc_mqtt5_client_callbacks_broker_disconnect "callback" +/// broker disconnection report @ref doc_cc_mqtt5_client_callbacks_broker_disconnect "callback" /// will @b NOT be invoked, because the connection hasn't really happened yet. /// /// @b IMPORTANT: The @b cc_mqtt5_client_connect_config_auth() function @@ -435,7 +435,7 @@ /// it may result in setting multiple properties of the same type, which /// is the "Protocol Error" according to the MQTT v5 specification. /// -/// @subsection cc_mqtt5_client_connect_user_prop Adding "User Properties" +/// @subsection doc_cc_mqtt5_client_connect_user_prop Adding "User Properties" /// The MQTT v5 specification allows attaching any number of the "User Properties" to /// the @b CONNECT message. The library allows such assignment using multiple invocations of /// the @b cc_mqtt5_client_connect_add_user_prop() function. @@ -451,7 +451,7 @@ /// @endcode /// See also documentation of the @ref CC_Mqtt5UserProp structure. /// -/// @subsection cc_mqtt5_client_connect_send Sending Connection Request +/// @subsection doc_cc_mqtt5_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 broker. To initiate sending /// use the @b cc_mqtt5_client_connect_send() function. @@ -483,7 +483,7 @@ /// @b cc_mqtt5_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_Mqtt5ErrorCode_Success was returned). -/// The valid handle can be used to @ref cc_mqtt5_client_connect_cancel "cancel" +/// The valid handle can be used to @ref doc_cc_mqtt5_client_connect_cancel "cancel" /// the operation before the completion callback is invoked. /// /// When the "connect" operation completion callback is invoked the reported @@ -493,7 +493,7 @@ /// @b NOTE that only single "connect" operation is allowed at a time, any attempt to /// prepare a new one via @b cc_mqtt5_client_connect_prepare() will be rejected /// until the "connect" operation completion callback is invoked or -/// the operation is @ref cc_mqtt5_client_connect_cancel "cancelled". +/// the operation is @ref doc_cc_mqtt5_client_connect_cancel "cancelled". /// /// @b IMPORTANT: The @ref CC_Mqtt5ConnectResponse "response" information from /// the broker may report that some features on the broker side are disabled. @@ -515,7 +515,7 @@ /// done when the "connect" operation is not properly completed, i.e. the /// reported status is @b NOT @ref CC_Mqtt5AsyncOpStatus_Complete. /// -/// @subsection cc_mqtt5_client_connect_cancel Cancel the "Connect" Operation. +/// @subsection doc_cc_mqtt5_client_connect_cancel Cancel the "Connect" Operation. /// While the handle returned by the @b cc_mqtt5_client_connect_prepare() is still /// valid it is possible to cancel / discard the operation. /// @code @@ -529,19 +529,47 @@ /// called before the @b cc_mqtt5_client_connect_cancel(), the operation is /// cancelled @b without callback invocation. /// -/// @subsection cc_mqtt5_client_connect_check Check The Library Remains Connected +/// @subsection doc_cc_mqtt5_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 several wrapper functions that can be used: +/// @li @b cc_mqtt5_client_connect_simple() +/// @li @b cc_mqtt5_client_connect_full() +/// +/// For example: +/// @code +/// CC_Mqtt5ConnectBasicConfig basicConfig; +/// +/// // Assign default values to the "basicConfig" +/// cc_mqtt5_client_connect_init_config_basic(&basicConfig); +/// +/// // Update the values if needed: +/// basicConfig.m_clientId = "some_client"; +/// basicConfig.m_cleanStart = true; +/// +/// ec = cc_mqtt5_client_connect_simple(client, basicConfig, &my_connect_complete_cb, data); +/// if (ec != CC_Mqtt5ErrorCode_Success) { +/// printf("ERROR: Failed to send connect request with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// Note that the wrapper functions do NOT expose the handle returned by the +/// @b cc_mqtt5_client_connect_prepare(). It means that it's not possible to +/// cancel the "connect" operation before its completion. +/// +/// @subsection doc_cc_mqtt5_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 broker. /// @code /// bool isConnected = cc_mqtt5_client_is_connected(client); /// @endcode /// -/// @section cc_mqtt5_client_disconnect Disconnecting From Broker +/// @section doc_cc_mqtt5_client_disconnect Disconnecting From Broker /// To intentionally disconnect from broker use @ref disconnect "disconnect" operation. The /// unsolicited disconnection from the broker is described in ref -/// @ref cc_mqtt5_client_unsolicited_disconnect section below. +/// @ref doc_cc_mqtt5_client_unsolicited_disconnect section below. /// -/// @subsection cc_mqtt5_client_disconnect_prepare Preparing "Disconnect" Operation. +/// @subsection doc_cc_mqtt5_client_disconnect_prepare Preparing "Disconnect" Operation. /// @code /// CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_Success; /// CC_Mqtt5DisconnectHandle disconnect = cc_mqtt5_client_disconnect_prepare(client, &ec); @@ -550,7 +578,7 @@ /// } /// @endcode /// -/// @subsection cc_mqtt5_client_disconnect_config Configuration of "Disconnect" Operation +/// @subsection doc_cc_mqtt5_client_disconnect_config Configuration of "Disconnect" Operation /// The configuration of the "disconnect" operation is performed using a single /// @b cc_mqtt5_client_disconnect_config() function. /// @code @@ -570,7 +598,7 @@ /// } /// @endcode /// -/// @subsection cc_mqtt5_client_disconnect_user_prop Adding "User Properties" +/// @subsection doc_cc_mqtt5_client_disconnect_user_prop Adding "User Properties" /// The MQTT v5 specification allows attaching any number of the "User Properties" to /// the @b DISCONNECT message. The library allows such assignment using multiple invocations of /// the @b cc_mqtt5_client_disconnect_add_user_prop() function. @@ -586,7 +614,7 @@ /// @endcode /// See also documentation of the @ref CC_Mqtt5UserProp structure. /// -/// @subsection cc_mqtt5_client_disconnect_send Sending Disconnection Request +/// @subsection doc_cc_mqtt5_client_disconnect_send Sending Disconnection Request /// When the necessary configuration is performed for the allocated "disconnect" /// operation it can be sent to the broker. To initiate sending /// use the @b cc_mqtt5_client_disconnect_send() function. @@ -611,14 +639,14 @@ /// status. /// /// @b IMPORTANT: In case of sending the explicit disconnection request the -/// @ref cc_mqtt5_client_callbacks_broker_disconnect "registered unsolicited disconnection callback" +/// @ref doc_cc_mqtt5_client_callbacks_broker_disconnect "registered unsolicited disconnection callback" /// is @b NOT invoked. /// /// After the disconnection the application can re-establish network connection -/// to the broker and perform the @ref cc_mqtt5_client_connect "connect" operation +/// to the broker and perform the @ref doc_cc_mqtt5_client_connect "connect" operation /// again. /// -/// @subsection cc_mqtt5_client_disconnect_cancel Cancel the "Disconnect" Operation. +/// @subsection doc_cc_mqtt5_client_disconnect_cancel Cancel the "Disconnect" Operation. /// While the handle returned by the @b cc_mqtt5_client_disconnect_prepare() is still /// valid it is possible to cancel / discard the operation. /// @code @@ -629,11 +657,34 @@ /// } /// @endcode /// -/// @section cc_mqtt5_client_subscribe Subscribing to Receive Messages +/// @subsection doc_cc_mqtt5_client_disconnect_simplify Simplifying the "Disonnect" Operation Preparation. +/// In many use cases the "disconnect" 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_mqtt5_client_disconnect() +/// +/// For example: +/// @code +/// CC_Mqtt5DisconnectConfig config; +/// +/// // Assign default values to the "config" +/// cc_mqtt5_client_disconnect_init_config(&config); +/// +/// // Update the values if needed: +/// config.m_reasonCode = CC_Mqtt5ReasonCode_DisconnectWithWill; +/// +/// ec = cc_mqtt5_client_disconnect(client, config); +/// if (ec != CC_Mqtt5ErrorCode_Success) { +/// printf("ERROR: Failed to send disconnect request with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// +/// @section doc_cc_mqtt5_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 cc_mqtt5_client_subscribe_prepare Preparing "Subscribe" Operation. +/// @subsection doc_cc_mqtt5_client_subscribe_prepare Preparing "Subscribe" Operation. /// @code /// CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_Success; /// CC_Mqtt5SubscribeHandle subscribe = cc_mqtt5_client_subscribe_prepare(client, &ec); @@ -642,8 +693,8 @@ /// } /// @endcode /// -/// @subsection cc_mqtt5_client_subscribe_response_timeout Configuring "Subscribe" Response Timeout -/// When created, the "subscribe" operation inherits the @ref cc_mqtt5_client_response_timeout +/// @subsection doc_cc_mqtt5_client_subscribe_response_timeout Configuring "Subscribe" Response Timeout +/// When created, the "subscribe" operation inherits the @ref doc_cc_mqtt5_client_response_timeout /// configuration. It can be changed for the allocated operation using the /// @b cc_mqtt5_client_subscribe_set_response_timeout() function. /// @code @@ -654,7 +705,7 @@ /// @endcode /// To retrieve the configured response timeout use the @b cc_mqtt5_client_subscribe_get_response_timeout() function. /// -/// @subsection cc_mqtt5_client_subscribe_topic Topic Configuration +/// @subsection doc_cc_mqtt5_client_subscribe_topic Topic Configuration /// Single @b SUBSCRIBE message can carry multiple topic subscriptions. Use /// separate @b cc_mqtt5_client_subscribe_config_topic() function invocation to configure each /// such subscription. @@ -699,7 +750,7 @@ /// To retrieve the current configuration use @b cc_mqtt5_client_get_verify_outgoing_topic_enabled() /// function. /// -/// @subsection cc_mqtt5_client_subscribe_extra Extra Properties Configuration +/// @subsection doc_cc_mqtt5_client_subscribe_extra Extra Properties Configuration /// To add extra MQTT v5 specific properties to the subscription request use /// @b cc_mqtt5_client_subscribe_config_extra() function. /// @code @@ -737,7 +788,7 @@ /// reported value for easier dispatching of the received message to appropriate /// handling functionality. /// -/// @subsection cc_mqtt5_client_subscribe_user_prop Adding "User Properties" +/// @subsection doc_cc_mqtt5_client_subscribe_user_prop Adding "User Properties" /// The MQTT v5 specification allows attaching any number of the "User Properties" to /// the @b SUBSCRIBE message. The library allows such assignment using multiple invocations of /// the @b cc_mqtt5_client_subscribe_add_user_prop() function. @@ -753,7 +804,7 @@ /// @endcode /// See also documentation of the @ref CC_Mqtt5UserProp structure. /// -/// @subsection cc_mqtt5_client_subscribe_send Sending Subscription Request +/// @subsection doc_cc_mqtt5_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_mqtt5_client_subscribe_send() function. @@ -785,7 +836,7 @@ /// @b cc_mqtt5_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_Mqtt5ErrorCode_Success was returned). -/// The valid handle can be used to @ref cc_mqtt5_client_subscribe_cancel "cancel" +/// The valid handle can be used to @ref doc_cc_mqtt5_client_subscribe_cancel "cancel" /// the operation before the completion callback is invoked. /// /// Note that the callback function receives the "subscribe" operation handle as @@ -798,7 +849,7 @@ /// "response" information is present if and only if the "status" is /// @ref CC_Mqtt5AsyncOpStatus_Complete. /// -/// @subsection cc_mqtt5_client_subscribe_cancel Cancel the "Subscribe" Operation. +/// @subsection doc_cc_mqtt5_client_subscribe_cancel Cancel the "Subscribe" Operation. /// While the handle returned by the @b cc_mqtt5_client_subscribe_prepare() is still /// valid it is possible to cancel / discard the operation. /// @code @@ -812,11 +863,41 @@ /// called before the @b cc_mqtt5_client_subscribe_cancel(), the operation is /// cancelled @b without callback invocation. /// -/// @section cc_mqtt5_client_unsubscribe Unsubscribing from Message Reception +/// @subsection doc_cc_mqtt5_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 several wrapper functions that can be used: +/// @li @b cc_mqtt5_client_subscribe_simple() +/// @li @b cc_mqtt5_client_subscribe_full() +/// +/// For example: +/// @code +/// CC_Mqtt5SubscribeTopicConfig topicConfig; +/// +/// // Assign default values to the configuration +/// cc_mqtt5_client_subscribe_init_config_topic(&topicConfig); +/// +/// // Update values if needed +/// topicConfig.m_topic = "some/topic"; +/// topicConfig.m_noLocal = true; +/// +/// ec = cc_mqtt5_client_subscribe_simple(client, topicConfig, &my_subscribe_complete_cb, data); +/// if (ec != CC_Mqtt5ErrorCode_Success) { +/// printf("ERROR: Failed to send subscribe request with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// Note that the wrapper functions do NOT expose the handle returned by the +/// @b cc_mqtt5_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_mqtt5_client_unsubscribe Unsubscribing from Message Reception /// To unsubscribe from receiving incoming messages use @ref unsubscribe "unsubscribe" operation. /// The application can issue multiple "unsubscribe" operations in parallel. /// -/// @subsection cc_mqtt5_client_unsubscribe_prepare Preparing "Unsubscribe" Operation. +/// @subsection doc_cc_mqtt5_client_unsubscribe_prepare Preparing "Unsubscribe" Operation. /// @code /// CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_Success; /// CC_Mqtt5UnsubscribeHandle unsubscribe = cc_mqtt5_client_unsubscribe_prepare(client, &ec); @@ -825,8 +906,8 @@ /// } /// @endcode /// -/// @subsection cc_mqtt5_client_unsubscribe_response_timeout Configuring "Unsubscribe" Response Timeout -/// When created, the "unsubscribe" operation inherits the @ref cc_mqtt5_client_response_timeout +/// @subsection doc_cc_mqtt5_client_unsubscribe_response_timeout Configuring "Unsubscribe" Response Timeout +/// When created, the "unsubscribe" operation inherits the @ref doc_cc_mqtt5_client_response_timeout /// configuration. It can be changed for the allocated operation using the /// @b cc_mqtt5_client_unsubscribe_set_response_timeout() function. /// @code @@ -837,7 +918,7 @@ /// @endcode /// To retrieve the configured response timeout use the @b cc_mqtt5_client_unsubscribe_get_response_timeout() function. /// -/// @subsection cc_mqtt5_client_unsubscribe_topic Topic Configuration +/// @subsection doc_cc_mqtt5_client_unsubscribe_topic Topic Configuration /// Single @b UNSUBSCRIBE message can carry multiple topic unsubscriptions. Use /// @b cc_mqtt5_client_unsubscribe_config_topic() function to configure each /// such unsubscription. @@ -889,7 +970,7 @@ /// To retrieve the current configuration use @b cc_mqtt5_client_get_verify_outgoing_topic_enabled() /// function. /// -/// @subsection cc_mqtt5_client_unsubscribe_user_prop Adding "User Properties" +/// @subsection doc_cc_mqtt5_client_unsubscribe_user_prop Adding "User Properties" /// The MQTT v5 specification allows attaching any number of the "User Properties" to /// the @b UNSUBSCRIBE message. The library allows such assignment using multiple invocations of /// the @b cc_mqtt5_client_unsubscribe_add_user_prop() function. @@ -905,7 +986,7 @@ /// @endcode /// See also documentation of the @ref CC_Mqtt5UserProp structure. /// -/// @subsection cc_mqtt5_client_unsubscribe_send Sending Unsubscription Request +/// @subsection doc_cc_mqtt5_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_mqtt5_client_unsubscribe_send() function. @@ -937,7 +1018,7 @@ /// @b cc_mqtt5_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_Mqtt5ErrorCode_Success was returned). -/// The valid handle can be used to @ref cc_mqtt5_client_unsubscribe_cancel "cancel" +/// The valid handle can be used to @ref doc_cc_mqtt5_client_unsubscribe_cancel "cancel" /// the operation before the completion callback is invoked. /// /// Note that the callback function receives the "unsubscribe" operation handle as @@ -950,7 +1031,7 @@ /// "response" information is present if and only if the "status" is /// @ref CC_Mqtt5AsyncOpStatus_Complete. /// -/// @subsection cc_mqtt5_client_unsubscribe_cancel Cancel the "Unsubscribe" Operation. +/// @subsection doc_cc_mqtt5_client_unsubscribe_cancel Cancel the "Unsubscribe" Operation. /// While the handle returned by the @b cc_mqtt5_client_unsubscribe_prepare() is still /// valid it is possible to cancel / discard the operation. /// @code @@ -964,10 +1045,39 @@ /// called before the @b cc_mqtt5_client_unsubscribe_cancel(), the operation is /// cancelled @b without callback invocation. /// -/// @section cc_mqtt5_client_publish Publishing Messages +/// @subsection doc_cc_mqtt5_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 several wrapper functions that can be used: +/// @li @b cc_mqtt5_client_unsubscribe_simple() +/// @li @b cc_mqtt5_client_unsubscribe_full() +/// +/// For example: +/// @code +/// CC_Mqtt5UnsubscribeTopicConfig topicConfig; +/// +/// // Assign default values to the configuration +/// cc_mqtt5_client_unsubscribe_init_config_topic(&topicConfig); +/// +/// // Update values +/// topicConfig.m_topic = "some/topic"; +/// +/// ec = cc_mqtt5_client_unsubscribe_simple(client, topicConfig, &my_unsubscribe_complete_cb, data); +/// if (ec != CC_Mqtt5ErrorCode_Success) { +/// printf("ERROR: Failed to send unsubscribe request with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// Note that the wrapper functions do NOT expose the handle returned by the +/// @b cc_mqtt5_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_mqtt5_client_publish Publishing Messages /// To publish messages to the broker use @ref publish "publish" operation. /// -/// @subsection cc_mqtt5_client_publish_prepare Preparing "Publish" Operation. +/// @subsection doc_cc_mqtt5_client_publish_prepare Preparing "Publish" Operation. /// @code /// CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_Success; /// CC_Mqtt5PublishHandle publish = cc_mqtt5_client_publish_prepare(client, &ec); @@ -976,11 +1086,11 @@ /// } /// @endcode /// -/// @subsection cc_mqtt5_client_publish_response_timeout Configuring "Publish" Response Timeout +/// @subsection doc_cc_mqtt5_client_publish_response_timeout Configuring "Publish" Response Timeout /// When publishing messages with QoS value @ref CC_Mqtt5QoS_AtLeastOnceDelivery or above, the /// response from the broker is expected. /// -/// When created, the "publish" operation inherits the @ref cc_mqtt5_client_response_timeout +/// When created, the "publish" operation inherits the @ref doc_cc_mqtt5_client_response_timeout /// configuration. It can be changed for the allocated operation using the /// @b cc_mqtt5_client_publish_set_response_timeout() function. /// @code @@ -991,7 +1101,7 @@ /// @endcode /// To retrieve the configured response timeout use the @b cc_mqtt5_client_publish_get_response_timeout() function. /// -/// @subsection cc_mqtt5_client_publish_resend Configuring "Publish" Re-Send Attempts +/// @subsection doc_cc_mqtt5_client_publish_resend Configuring "Publish" Re-Send Attempts /// The MQTT v5 specification has a mechanism of insured delivery of the published /// message to the broker. In the case of not 100% reliable connection the messages /// can get lost and needs to be re-sent. The default amount of re-sends is @b 2, i.e. @@ -1009,9 +1119,9 @@ /// @endcode /// To retrieve the configured resend attempts number use the @b cc_mqtt5_client_publish_get_resend_attempts() function. /// -/// @subsection cc_mqtt5_client_publish_basic Basic Configuration of "Publish" Operation +/// @subsection doc_cc_mqtt5_client_publish_basic Basic Configuration of "Publish" Operation /// The "basic" configuration means no extra MQTT v5 properties assigned to the message, with -/// one small exception of using topic alias (covered @ref cc_mqtt5_client_publish_alias "below" as a separate subject). +/// one small exception of using topic alias (covered @ref doc_cc_mqtt5_client_publish_alias "below" as a separate subject). /// @code /// CC_Mqtt5PublishBasicConfig basicConfig; /// @@ -1052,7 +1162,7 @@ /// Also @b note that the same function controls the verification of the /// "subscribe", "unsubscribe", and "publish" filter / topic formats. /// -/// @subsection cc_mqtt5_client_publish_extra Extra Properties Configuration +/// @subsection doc_cc_mqtt5_client_publish_extra Extra Properties Configuration /// To add extra MQTT v5 specific properties to the publish request use /// @b cc_mqtt5_client_publish_config_extra() function. /// @code @@ -1074,7 +1184,7 @@ /// @endcode /// See also documentation of the @ref CC_Mqtt5PublishExtraConfig structure. /// -/// @subsection cc_mqtt5_client_publish_user_prop Adding "User Properties" +/// @subsection doc_cc_mqtt5_client_publish_user_prop Adding "User Properties" /// The MQTT v5 specification allows attaching any number of the "User Properties" to /// the @b PUBLISH message. The library allows such assignment using multiple invocations of /// the @b cc_mqtt5_client_publish_add_user_prop() function. @@ -1090,7 +1200,7 @@ /// @endcode /// See also documentation of the @ref CC_Mqtt5UserProp structure. /// -/// @subsection cc_mqtt5_client_publish_send Sending Publish Request +/// @subsection doc_cc_mqtt5_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 broker. To initiate sending /// use the @b cc_mqtt5_client_publish_send() function. @@ -1124,7 +1234,7 @@ /// publish operation is silent. /// /// When QoS value is @ref CC_Mqtt5QoS_AtMostOnceDelivery (see -/// the @ref cc_mqtt5_client_publish_basic "basic" configuration), there +/// the @ref doc_cc_mqtt5_client_publish_basic "basic" configuration), there /// is no broker response to wait for and the callback is invoked right away /// after sending the serialized data. /// @@ -1133,7 +1243,7 @@ /// @b cc_mqtt5_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_Mqtt5ErrorCode_Success was returned). -/// The valid handle can be used to @ref cc_mqtt5_client_publish_cancel "cancel" +/// The valid handle can be used to @ref doc_cc_mqtt5_client_publish_cancel "cancel" /// the operation before the completion callback is invoked. /// /// Note that the callback function receives the "publish" operation handle as @@ -1152,7 +1262,7 @@ /// If it's @ref CC_Mqtt5ReasonCode_UnspecifiedError or greater means the "publish" /// operation wasn't successful. /// -/// @subsection cc_mqtt5_client_publish_cancel Cancel the "Publish" Operation. +/// @subsection doc_cc_mqtt5_client_publish_cancel Cancel the "Publish" Operation. /// While the handle returned by the @b cc_mqtt5_client_publish_prepare() is still /// valid it is possible to cancel / discard the operation. /// @code @@ -1166,7 +1276,38 @@ /// called before the @b cc_mqtt5_client_publish_cancel(), the operation is /// cancelled @b without callback invocation. /// -/// @subsection cc_mqtt5_client_publish_alias Using Topic Alias +/// @subsection doc_cc_mqtt5_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 several wrapper functions that can be used: +/// @li @b cc_mqtt5_client_publish_simple() +/// @li @b cc_mqtt5_client_publish_full() +/// +/// For example: +/// @code +/// CC_Mqtt5PublishBasicConfig basicConfig; +/// +/// // Assign default values to the configuration +/// cc_mqtt5_client_publish_init_config_basic(&basicConfig); +/// +/// // Update values +/// basicConfig.m_topic = "some/topic"; +/// basicConfig.m_data = ...; +/// ... +/// +/// ec = cc_mqtt5_client_publish_simple(client, basicConfig, &my_publish_complete_cb, data); +/// if (ec != CC_Mqtt5ErrorCode_Success) { +/// printf("ERROR: Failed to send publish request with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// Note that the wrapper functions do NOT expose the handle returned by the +/// @b cc_mqtt5_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. +/// +/// @subsection doc_cc_mqtt5_client_publish_alias Using Topic Alias /// The MQTT v5 specification allows usage of the numeric topic aliases to /// be able to reduce amount of bytes per publish. In order for the library to start /// using topic aliases, they need to be allocated (registered) using the @@ -1181,7 +1322,7 @@ /// successful "connect" operation. /// /// Also note that the topic aliases are not part of the session state. For every -/// @ref cc_mqtt5_client_connect "re-connection" attempt all the previously allocated +/// @ref doc_cc_mqtt5_client_connect "re-connection" attempt all the previously allocated /// topic aliases get cleared in the internal data structures and they need to /// be allocated again after the successful connection. /// @@ -1228,7 +1369,7 @@ /// bool allocated = cc_mqtt5_client_pub_topic_alias_is_allocated(client, "some_topic"); /// @endcode /// -/// Once the topic alias is successfully allocated, the @ref cc_mqtt5_client_publish_basic "basic" +/// Once the topic alias is successfully allocated, the @ref doc_cc_mqtt5_client_publish_basic "basic" /// configuration of the "publish" operation allows control of whether and how to use /// topic alias via the @ref CC_Mqtt5PublishBasicConfig::m_topicAliasPref data member. /// @@ -1242,11 +1383,11 @@ /// @li @ref CC_Mqtt5TopicAliasPreference_ForceTopicWithAlias - Forces sending both topic /// alias and topic string even if it is safe to send topic alias only. /// -/// @section cc_mqtt5_client_receive Receiving Messages +/// @section doc_cc_mqtt5_client_receive Receiving Messages /// Right after the successful "connect" operation, the library starts expecting -/// the arrival of the new messages and reports it via the @ref cc_mqtt5_client_callbacks_message +/// the arrival of the new messages and reports it via the @ref doc_cc_mqtt5_client_callbacks_message /// "registered callback". By default the library monitors the topics the client -/// application @ref cc_mqtt5_client_subscribe "subscribed" to and does not +/// application @ref doc_cc_mqtt5_client_subscribe "subscribed" to and does not /// report "rogue" messages from the broker. /// /// @b Note that only topics themselves @@ -1272,14 +1413,14 @@ /// To retrieve the current configuration use the @b cc_mqtt5_client_get_verify_incoming_topic_enabled() function. /// /// To insure the required in-order reception of the messages, the -/// @ref cc_mqtt5_client_callbacks_message "message report callback" is invoked immediately on +/// @ref doc_cc_mqtt5_client_callbacks_message "message report callback" is invoked immediately on /// reception of the @b PUBLISH message. Just like it is described in section 4.3 of the /// MQTT v5 specification. /// /// The @b QoS2 publish operation initiated by the broker requires exchange of multiple messages /// between the broker and the client. When the library responds with the @b PUBREC /// message, the broker is expected to send @b PUBREL back. The library uses the -/// @ref cc_mqtt5_client_response_timeout configuration to measure the time frame +/// @ref doc_cc_mqtt5_client_response_timeout configuration to measure the time frame /// during which it allows the reception of the corresponding @b PUBREL message. If the latter doesn't /// arrive in time, the inner state of the message reception gets discarded resulting in /// the future rejection of the relevant @b PUBREL from the broker (if such arrives). @@ -1292,15 +1433,15 @@ /// to either drop its message delivery state (best case scenario) or /// treat it as protocol error and disconnect the client. /// -/// With all said above it might be necessary to increase the @ref cc_mqtt5_client_response_timeout +/// With all said above it might be necessary to increase the @ref doc_cc_mqtt5_client_response_timeout /// "response timeout" for slow networks. /// -/// @section cc_mqtt5_client_reauth Re-Authenticating +/// @section doc_cc_mqtt5_client_reauth Re-Authenticating /// The MQTT v5 specification allows performing re-authentication after the successful -/// @ref cc_mqtt5_client_connect "\"connect\"" operation using the same authentication method. +/// @ref doc_cc_mqtt5_client_connect "\"connect\"" operation using the same authentication method. /// To do so use the @ref reauth "reauth" operation. /// -/// @subsection cc_mqtt5_client_reauth_prepare Preparing "Rauth" Operation. +/// @subsection doc_cc_mqtt5_client_reauth_prepare Preparing "Rauth" Operation. /// @code /// CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_Success; /// CC_Mqtt5ReauthHandle reauth = cc_mqtt5_client_reauth_prepare(client, &ec); @@ -1309,8 +1450,8 @@ /// } /// @endcode /// -/// @subsection cc_mqtt5_client_reauth_response_timeout Configuring "Reauth" Response Timeout -/// When created, the "reauth" operation inherits the @ref cc_mqtt5_client_response_timeout +/// @subsection doc_cc_mqtt5_client_reauth_response_timeout Configuring "Reauth" Response Timeout +/// When created, the "reauth" operation inherits the @ref doc_cc_mqtt5_client_response_timeout /// configuration. It can be changed for the allocated operation using the /// @b cc_mqtt5_client_reauth_set_response_timeout() function. /// @code @@ -1321,9 +1462,9 @@ /// @endcode /// To retrieve the configured response timeout use the @b cc_mqtt5_client_reauth_get_response_timeout() function. /// -/// @subsection cc_mqtt5_client_reauth_auth (Re)Authentication Handshake Configuration +/// @subsection doc_cc_mqtt5_client_reauth_auth (Re)Authentication Handshake Configuration /// To configure the re-authentication handshake use the @b cc_mqtt5_client_reauth_config_auth() function. -/// It is very similar to the @ref cc_mqtt5_client_connect_auth of the "connect" operation. +/// It is very similar to the @ref doc_cc_mqtt5_client_connect_auth of the "connect" operation. /// @code /// CC_Mqtt5AuthErrorCode my_auth_handshake_cb(void* data, const CC_Mqtt5AuthInfo* authInfoIn, CC_Mqtt5AuthInfo* authInfoOut) /// { @@ -1365,12 +1506,12 @@ /// In case the application fails the re-authentication handshake by returning the /// @ref CC_Mqtt5AuthErrorCode_Disconnect value library preforms the following steps: /// @li Sends @b DISCONNECT message to the broker. -/// @li Invokes the operation completion callback (see @ref cc_mqtt5_client_reauth_send below) +/// @li Invokes the operation completion callback (see @ref doc_cc_mqtt5_client_reauth_send below) /// with the @ref CC_Mqtt5AsyncOpStatus_Aborted as status value report. /// @li Invokes the callbacks of all other incomplete operations with the /// the @ref CC_Mqtt5AsyncOpStatus_Aborted as status value report. -/// @li Invokes the @ref cc_mqtt5_client_callbacks_broker_disconnect "disconnection report callback" -/// to report disconnection from the broker. See also @ref cc_mqtt5_client_unsolicited_disconnect +/// @li Invokes the @ref doc_cc_mqtt5_client_callbacks_broker_disconnect "disconnection report callback" +/// to report disconnection from the broker. See also @ref doc_cc_mqtt5_client_unsolicited_disconnect /// section below for details on how to proceed after the callback invocation. /// /// @b IMPORTANT: The @b cc_mqtt5_client_reauth_config_auth() function @@ -1378,7 +1519,7 @@ /// it may result in setting multiple properties of the same type, which /// is the "Protocol Error" according to the MQTT v5 specification. /// -/// @subsection cc_mqtt5_client_reauth_send Sending Re-Authentication Request +/// @subsection doc_cc_mqtt5_client_reauth_send Sending Re-Authentication Request /// When all the necessary configurations are performed for the allocated "reauth" /// operation it can actually be sent to the broker. To initiate sending /// use the @b cc_mqtt5_client_reauth_send() function. @@ -1410,7 +1551,7 @@ /// @b cc_mqtt5_client_reauth_send() invocation /// regardless of the returned error code. However, the handle remains valid until /// the callback is called (in case the @ref CC_Mqtt5ErrorCode_Success was returned). -/// The valid handle can be used to @ref cc_mqtt5_client_reauth_cancel "cancel" +/// The valid handle can be used to @ref doc_cc_mqtt5_client_reauth_cancel "cancel" /// the operation before the completion callback is invoked. /// /// When the "reauth" operation completion callback is invoked the reported @@ -1420,9 +1561,9 @@ /// @b NOTE that only single "reauth" operation is allowed at a time, any attempt to /// prepare a new one via @b cc_mqtt5_client_reauth_prepare() will be rejected /// until the "reauth" operation completion callback is invoked or -/// the operation is @ref cc_mqtt5_client_reauth_cancel "cancelled". +/// the operation is @ref doc_cc_mqtt5_client_reauth_cancel "cancelled". /// -/// @subsection cc_mqtt5_client_reauth_cancel Cancel the "Reauth" Operation. +/// @subsection doc_cc_mqtt5_client_reauth_cancel Cancel the "Reauth" Operation. /// While the handle returned by the @b cc_mqtt5_client_reauth_prepare() is still /// valid it is possible to cancel / discard the operation. /// @code @@ -1438,13 +1579,41 @@ /// /// In case the re-authentication is rejected by the broker, the latter is /// expected to send the @b DISCONNECT message resulting in the -/// @ref cc_mqtt5_client_unsolicited_disconnect. In such case the completion +/// @ref doc_cc_mqtt5_client_unsolicited_disconnect. In such case the completion /// callback will be invoked with the @ref CC_Mqtt5AsyncOpStatus_BrokerDisconnected status. /// -/// @section cc_mqtt5_client_unsolicited_disconnect Unsolicited Broker Disconnection +/// @subsection doc_cc_mqtt5_client_reauth_simplify Simplifying the "Reauth" Operation Preparation. +/// In many use cases the "reauth" 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_mqtt5_client_reauth_() +/// +/// For example: +/// @code +/// CC_Mqtt5AuthConfig authConfig; +/// +/// // Initialize the configuration structure to the default values +/// cc_mqtt5_client_reauth_init_config_auth(&authConfig); +/// +/// // Assign all the necessary values +/// authConfig.m_authMethod = "some_method" +/// authConfig.m_authData = ...; +/// ... +/// +/// ec = cc_mqtt5_client_reauth(client, authConfig, &my_reauth_complete_cb, data); +/// if (ec != CC_Mqtt5ErrorCode_Success) { +/// printf("ERROR: Failed to send publish request with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// Note that the wrapper functions do NOT expose the handle returned by the +/// @b cc_mqtt5_client_reauth_prepare(). It means that it's not possible to +/// cancel the "reauth" operation before its completion. +/// +/// @section doc_cc_mqtt5_client_unsolicited_disconnect Unsolicited Broker Disconnection /// When broker disconnection is detected all the incomplete asynchronous operations -/// (except @ref cc_mqtt5_client_publish "publish") will be terminated with the an appropriate -/// @ref CC_Mqtt5AsyncOpStatus "status" report. The incomplete @ref cc_mqtt5_client_publish "publish" +/// (except @ref doc_cc_mqtt5_client_publish "publish") will be terminated with the an appropriate +/// @ref CC_Mqtt5AsyncOpStatus "status" report. The incomplete @ref doc_cc_mqtt5_client_publish "publish" /// operations will be preserved due to the following spec clause: /// @code /// When a Client reconnects with Clean Start set to 0 and a session is present, both the Client and Server @@ -1452,63 +1621,63 @@ /// original Packet Identifiers. This is the only circumstance where a Client or Server is REQUIRED to resend /// messages. Clients and Servers MUST NOT resend messages at any other time [MQTT-4.4.0-1]. /// @endcode -/// The incomplete @ref cc_mqtt5_client_publish "publish" operation as well as reception of the +/// The incomplete @ref doc_cc_mqtt5_client_publish "publish" operation as well as reception of the /// incoming message need to be preserved between the sessions. In case the broker reports /// a new (clean) session after the reconnection, the callbacks of all the incomplete -/// @ref cc_mqtt5_client_publish "publish" operations will be invoked with the +/// @ref doc_cc_mqtt5_client_publish "publish" operations will be invoked with the /// @ref CC_Mqtt5AsyncOpStatus_Aborted status. /// /// The unsolicited broker disconnection can happen in one of the following scenarios: /// -/// @subsection cc_mqtt5_client_unsolicited_disconnect_msg Receiving DISCONNECT message +/// @subsection doc_cc_mqtt5_client_unsolicited_disconnect_msg Receiving DISCONNECT message /// The broker can initiate disconnection any time by simply sending @b DISCONNECT message. /// In such case the library responds in the following way: /// @li Terminates and invokes the callbacks of previously initiated but incomplete operations -/// (except @ref cc_mqtt5_client_publish "publish") passing +/// (except @ref doc_cc_mqtt5_client_publish "publish") passing /// the @ref CC_Mqtt5AsyncOpStatus_BrokerDisconnected as their status report. -/// @li Invokes the @ref cc_mqtt5_client_callbacks_broker_disconnect "disconnection report callback" +/// @li Invokes the @ref doc_cc_mqtt5_client_callbacks_broker_disconnect "disconnection report callback" /// registered using the @b cc_mqtt5_client_set_broker_disconnect_report_callback() function. /// The information passed in the @b DISCONNECT message is used to populate reported /// disconnection information. /// -/// @subsection cc_mqtt5_client_unsolicited_disconnect_keep_alive Keep Alive Timeout +/// @subsection doc_cc_mqtt5_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 PING message. /// In such case the library responds in the following way: /// @li Terminates and invokes the callbacks of previously initiated but incomplete operations -/// (except @ref cc_mqtt5_client_publish "publish") passing +/// (except @ref doc_cc_mqtt5_client_publish "publish") passing /// the @ref CC_Mqtt5AsyncOpStatus_BrokerDisconnected as their status report. -/// @li Invokes the @ref cc_mqtt5_client_callbacks_broker_disconnect "disconnection report callback" +/// @li Invokes the @ref doc_cc_mqtt5_client_callbacks_broker_disconnect "disconnection report callback" /// registered using the @b cc_mqtt5_client_set_broker_disconnect_report_callback() function /// without any disconnection information. /// -/// @subsection cc_mqtt5_client_unsolicited_disconnect_protocol_error Detecting Protocol Error +/// @subsection doc_cc_mqtt5_client_unsolicited_disconnect_protocol_error Detecting Protocol Error /// In case the broker doesn't fully comply with the MQTT v5 specification or there is /// some unexpected data corruption the library responds in the following way: /// @li Sends @b DISCONNECT message to the broker reporting error. /// @li Terminates and invokes the callback of the operation that detected the protocol error with /// the @ref CC_Mqtt5AsyncOpStatus_ProtocolError status report. /// @li Terminates and invokes the callbacks of all other previously initiated but incomplete operations -/// (except @ref cc_mqtt5_client_publish "publish") passing +/// (except @ref doc_cc_mqtt5_client_publish "publish") passing /// the @ref CC_Mqtt5AsyncOpStatus_BrokerDisconnected as their status report. -/// @li Invokes the @ref cc_mqtt5_client_callbacks_broker_disconnect "disconnection report callback" +/// @li Invokes the @ref doc_cc_mqtt5_client_callbacks_broker_disconnect "disconnection report callback" /// registered using the @b cc_mqtt5_client_set_broker_disconnect_report_callback() function /// without any disconnection information. /// /// With all said above it means that if application receives @ref CC_Mqtt5AsyncOpStatus_ProtocolError or /// @ref CC_Mqtt5AsyncOpStatus_BrokerDisconnected status in the operation callback, then -/// the application is expected to wait for the @ref cc_mqtt5_client_callbacks_broker_disconnect "disconnection report callback" +/// the application is expected to wait for the @ref doc_cc_mqtt5_client_callbacks_broker_disconnect "disconnection report callback" /// which will follow. /// -/// When the @ref cc_mqtt5_client_callbacks_broker_disconnect "disconnection report callback" is called the application is +/// When the @ref doc_cc_mqtt5_client_callbacks_broker_disconnect "disconnection report callback" is called the application is /// expected to go through the following steps: /// @li Close network connection. /// @li Re-establish network connection to the broker. -/// @li Perform the @ref cc_mqtt5_client_connect "\"connect\"" operation from within a next event loop iteration. +/// @li Perform the @ref doc_cc_mqtt5_client_connect "\"connect\"" operation from within a next event loop iteration. /// -/// @section cc_mqtt5_client_network_disconnect Network Disconnection +/// @section doc_cc_mqtt5_client_network_disconnect Network Disconnection /// The MQTT v5 specification also tries to support intermittent network connection by -/// setting the "Session Expiry Interval" property during the @ref cc_mqtt5_client_connect "\"connect\"" +/// setting the "Session Expiry Interval" property during the @ref doc_cc_mqtt5_client_connect "\"connect\"" /// operation (see @ref CC_Mqtt5ConnectExtraConfig::m_sessionExpiryInterval). Such /// network disconnection is usually detected by the failing @b read or @b write /// operations on I/O socket. When the application detects such network disconnection, it @@ -1532,12 +1701,12 @@ /// functioning as usual (as if network disconnection hasn't happened). /// /// However, if the network re-connection is not reported until the "Session Expiry Interval" is over, -/// then the library will proceed to the @ref cc_mqtt5_client_unsolicited_disconnect logic. +/// then the library will proceed to the @ref doc_cc_mqtt5_client_unsolicited_disconnect logic. /// /// @b Note that if the "Session Expiry Interval" was not configured during the -/// @ref cc_mqtt5_client_connect "\"connect\"" operation, invocation of the +/// @ref doc_cc_mqtt5_client_connect "\"connect\"" operation, invocation of the /// @b cc_mqtt5_client_notify_network_disconnected() will result in the -/// @ref cc_mqtt5_client_unsolicited_disconnect report right away. +/// @ref doc_cc_mqtt5_client_unsolicited_disconnect report right away. /// /// Inquiry about current network disconnection status can be done using the /// @b cc_mqtt5_client_is_network_disconnected() function. @@ -1555,20 +1724,20 @@ /// CC_Mqtt5ConnectHandle connect = cc_mqtt5_client_connect_prepare(client, &ec); /// @endcode /// -/// When new client is @ref cc_mqtt5_client_allocation "allocated", its inner state +/// When new client is @ref doc_cc_mqtt5_client_allocation "allocated", its inner state /// assumes that the network is connected, i.e. invocation of the /// @b cc_mqtt5_client_is_network_disconnected() will return @b false. /// -/// @section cc_mqtt5_client_thread_safety Thread Safety +/// @section doc_cc_mqtt5_client_thread_safety Thread Safety /// In general the library is @b NOT thread safe. However, if each thread operates on a separate allocated -/// @ref cc_mqtt5_client_allocation "client", then the locking is required only -/// for the @ref cc_mqtt5_client_allocation "client allocation logic". +/// @ref doc_cc_mqtt5_client_allocation "client", then the locking is required only +/// for the @ref doc_cc_mqtt5_client_allocation "client allocation logic". /// -/// If multiple threads work with the same @ref cc_mqtt5_client_allocation "client" +/// If multiple threads work with the same @ref doc_cc_mqtt5_client_allocation "client" /// object (such as performing "publish" operations, or handling incoming messages in parallel), /// then explicit locking logic needs to be implemented by the application itself. /// -/// @section cc_mqtt5_client_debug_build Debug Build +/// @section doc_cc_mqtt5_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 diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 9633339..1a886cd 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/util/ScopeGuard.h" #include #include diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index ffd02f0..3e02120 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -9,6 +9,8 @@ #include "ClientAllocator.h" #include "ExtConfig.h" +#include "comms/util/ScopeGuard.h" + struct CC_Mqtt5Client {}; struct CC_Mqtt5Connect {}; struct CC_Mqtt5Disconnect {}; @@ -413,6 +415,77 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_connect_cancel(CC_Mqtt5ConnectHandle h return connectOpFromHandle(handle)->cancel(); } +CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_connect_simple( + CC_Mqtt5ClientHandle handle, + const CC_Mqtt5ConnectBasicConfig* basicConfig, + CC_Mqtt5ConnectCompleteCb cb, + void* cbData) +{ + return + cc_mqtt5_##NAME##client_connect_full( + handle, + basicConfig, + nullptr, // willConfig + nullptr, // extraConfig, + nullptr, // authConfig + cb, + cbData); +} + +CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_connect_full( + CC_Mqtt5ClientHandle handle, + const CC_Mqtt5ConnectBasicConfig* basicConfig, + const CC_Mqtt5ConnectWillConfig* willConfig, + const CC_Mqtt5ConnectExtraConfig* extraConfig, + const CC_Mqtt5AuthConfig* authConfig, + CC_Mqtt5ConnectCompleteCb cb, + void* cbData) +{ + auto ec = CC_Mqtt5ErrorCode_Success; + auto connect = cc_mqtt5_##NAME##client_connect_prepare(handle, &ec); + if (connect == nullptr) { + return ec; + } + + auto cancelOnExitGuard = + comms::util::makeScopeGuard( + [connect]() + { + [[maybe_unused]] auto ecTmp = cc_mqtt5_##NAME##client_connect_cancel(connect); + }); + + if (basicConfig != nullptr) { + ec = cc_mqtt5_##NAME##client_connect_config_basic(connect, basicConfig); + if (ec != CC_Mqtt5ErrorCode_Success) { + return ec; + } + } + + if (willConfig != nullptr) { + ec = cc_mqtt5_##NAME##client_connect_config_will(connect, willConfig); + if (ec != CC_Mqtt5ErrorCode_Success) { + return ec; + } + } + + if (extraConfig != nullptr) { + ec = cc_mqtt5_##NAME##client_connect_config_extra(connect, extraConfig); + if (ec != CC_Mqtt5ErrorCode_Success) { + return ec; + } + } + + if (authConfig != nullptr) { + ec = cc_mqtt5_##NAME##client_connect_config_auth(connect, authConfig); + if (ec != CC_Mqtt5ErrorCode_Success) { + return ec; + } + } + + cancelOnExitGuard.release(); + return cc_mqtt5_##NAME##client_connect_send(connect, cb, cbData); +} + bool cc_mqtt5_##NAME##client_is_connected(CC_Mqtt5ClientHandle handle) { if (handle == nullptr) { @@ -475,6 +548,34 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_disconnect_cancel(CC_Mqtt5DisconnectHa return disconnectOpFromHandle(handle)->cancel(); } +CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_disconnect( + CC_Mqtt5ClientHandle handle, + const CC_Mqtt5DisconnectConfig* config) +{ + auto ec = CC_Mqtt5ErrorCode_Success; + auto disconnect = cc_mqtt5_##NAME##client_disconnect_prepare(handle, &ec); + if (disconnect == nullptr) { + return ec; + } + + auto cancelOnExitGuard = + comms::util::makeScopeGuard( + [disconnect]() + { + [[maybe_unused]] auto ecTmp = cc_mqtt5_##NAME##client_disconnect_cancel(disconnect); + }); + + if (config != nullptr) { + ec = cc_mqtt5_##NAME##client_disconnect_config(disconnect, config); + if (ec != CC_Mqtt5ErrorCode_Success) { + return ec; + } + } + + cancelOnExitGuard.release(); + return cc_mqtt5_##NAME##client_disconnect_send(disconnect); +} + CC_Mqtt5SubscribeHandle cc_mqtt5_##NAME##client_subscribe_prepare(CC_Mqtt5ClientHandle handle, CC_Mqtt5ErrorCode* ec) { if (handle == nullptr) { @@ -562,6 +663,63 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_subscribe_cancel(CC_Mqtt5SubscribeHand return subscribeOpFromHandle(handle)->cancel(); } +CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_subscribe_simple( + CC_Mqtt5ClientHandle handle, + const CC_Mqtt5SubscribeTopicConfig* topicConfig, + CC_Mqtt5SubscribeCompleteCb cb, + void* cbData) +{ + return + cc_mqtt5_##NAME##client_subscribe_full( + handle, + topicConfig, + 1U, + nullptr, + cb, + cbData); +} + +CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_subscribe_full( + CC_Mqtt5ClientHandle handle, + const CC_Mqtt5SubscribeTopicConfig* topicConfigs, + unsigned topicConfigsCount, + const CC_Mqtt5SubscribeExtraConfig* extraConfig, + CC_Mqtt5SubscribeCompleteCb cb, + void* cbData) +{ + auto ec = CC_Mqtt5ErrorCode_Success; + auto subscribe = cc_mqtt5_##NAME##client_subscribe_prepare(handle, &ec); + if (subscribe == nullptr) { + return ec; + } + + auto cancelOnExitGuard = + comms::util::makeScopeGuard( + [subscribe]() + { + [[maybe_unused]] auto ecTmp = cc_mqtt5_##NAME##client_subscribe_cancel(subscribe); + }); + + COMMS_ASSERT((topicConfigsCount == 0U) || (topicConfigs != nullptr)); + for (auto idx = 0U; idx < topicConfigsCount; ++idx) { + auto& config = topicConfigs[idx]; + ec = cc_mqtt5_##NAME##client_subscribe_config_topic(subscribe, &config); + if (ec != CC_Mqtt5ErrorCode_Success) { + return ec; + } + } + + if (extraConfig != nullptr) { + ec = cc_mqtt5_##NAME##client_subscribe_config_extra(subscribe, extraConfig); + if (ec != CC_Mqtt5ErrorCode_Success) { + return ec; + } + } + + cancelOnExitGuard.release(); + return cc_mqtt5_##NAME##client_subscribe_send(subscribe, cb, cbData); +} + CC_Mqtt5UnsubscribeHandle cc_mqtt5_##NAME##client_unsubscribe_prepare(CC_Mqtt5ClientHandle handle, CC_Mqtt5ErrorCode* ec) { if (handle == nullptr) { @@ -634,6 +792,54 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_unsubscribe_cancel(CC_Mqtt5Unsubscribe return unsubscribeOpFromHandle(handle)->cancel(); } +CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_unsubscribe_simple( + CC_Mqtt5ClientHandle handle, + const CC_Mqtt5UnsubscribeTopicConfig* topicConfig, + CC_Mqtt5UnsubscribeCompleteCb cb, + void* cbData) +{ + return + cc_mqtt5_##NAME##client_unsubscribe_full( + handle, + topicConfig, + 1U, + cb, + cbData); +} + +CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_unsubscribe_full( + CC_Mqtt5ClientHandle handle, + const CC_Mqtt5UnsubscribeTopicConfig* topicConfigs, + unsigned topicConfigsCount, + CC_Mqtt5UnsubscribeCompleteCb cb, + void* cbData) +{ + auto ec = CC_Mqtt5ErrorCode_Success; + auto unsubscribe = cc_mqtt5_##NAME##client_unsubscribe_prepare(handle, &ec); + if (unsubscribe == nullptr) { + return ec; + } + + auto cancelOnExitGuard = + comms::util::makeScopeGuard( + [unsubscribe]() + { + [[maybe_unused]] auto ecTmp = cc_mqtt5_##NAME##client_unsubscribe_cancel(unsubscribe); + }); + + COMMS_ASSERT((topicConfigsCount == 0U) || (topicConfigs != nullptr)); + for (auto idx = 0U; idx < topicConfigsCount; ++idx) { + auto& config = topicConfigs[idx]; + ec = cc_mqtt5_##NAME##client_unsubscribe_config_topic(unsubscribe, &config); + if (ec != CC_Mqtt5ErrorCode_Success) { + return ec; + } + } + + cancelOnExitGuard.release(); + return cc_mqtt5_##NAME##client_unsubscribe_send(unsubscribe, cb, cbData); +} + CC_Mqtt5PublishHandle cc_mqtt5_##NAME##client_publish_prepare(CC_Mqtt5ClientHandle handle, CC_Mqtt5ErrorCode* ec) { if (handle == nullptr) { @@ -738,6 +944,44 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_cancel(CC_Mqtt5PublishHandle h return sendOpFromHandle(handle)->cancel(); } +CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_full( + CC_Mqtt5ClientHandle handle, + const CC_Mqtt5PublishBasicConfig* basicConfig, + const CC_Mqtt5PublishExtraConfig* extraConfig, + CC_Mqtt5PublishCompleteCb cb, + void* cbData) +{ + auto ec = CC_Mqtt5ErrorCode_Success; + auto publish = cc_mqtt5_##NAME##client_publish_prepare(handle, &ec); + if (publish == nullptr) { + return ec; + } + + auto cancelOnExitGuard = + comms::util::makeScopeGuard( + [publish]() + { + [[maybe_unused]] auto ecTmp = cc_mqtt5_##NAME##client_publish_cancel(publish); + }); + + if (basicConfig != nullptr) { + ec = cc_mqtt5_##NAME##client_publish_config_basic(publish, basicConfig); + if (ec != CC_Mqtt5ErrorCode_Success) { + return ec; + } + } + + if (extraConfig != nullptr) { + ec = cc_mqtt5_##NAME##client_publish_config_extra(publish, extraConfig); + if (ec != CC_Mqtt5ErrorCode_Success) { + return ec; + } + } + + cancelOnExitGuard.release(); + return cc_mqtt5_##NAME##client_publish_send(publish, cb, cbData); +} + CC_Mqtt5ReauthHandle cc_mqtt5_##NAME##client_reauth_prepare(CC_Mqtt5ClientHandle handle, CC_Mqtt5ErrorCode* ec) { if (handle == nullptr) { @@ -810,6 +1054,36 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_reauth_cancel(CC_Mqtt5ReauthHandle han return reauthOpFromHandle(handle)->cancel(); } +CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_reauth( + CC_Mqtt5ClientHandle handle, + const CC_Mqtt5AuthConfig* config, + CC_Mqtt5ReauthCompleteCb cb, + void* cbData) +{ + auto ec = CC_Mqtt5ErrorCode_Success; + auto reauth = cc_mqtt5_##NAME##client_reauth_prepare(handle, &ec); + if (reauth == nullptr) { + return ec; + } + + auto cancelOnExitGuard = + comms::util::makeScopeGuard( + [reauth]() + { + [[maybe_unused]] auto ecTmp = cc_mqtt5_##NAME##client_reauth_cancel(reauth); + }); + + if (config != nullptr) { + ec = cc_mqtt5_##NAME##client_reauth_config_auth(reauth, config); + if (ec != CC_Mqtt5ErrorCode_Success) { + return ec; + } + } + + cancelOnExitGuard.release(); + return cc_mqtt5_##NAME##client_reauth_send(reauth, cb, cbData); +} + // --------------------- Callbacks --------------------- void cc_mqtt5_##NAME##client_set_next_tick_program_callback( diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index f6d9af6..936c9dd 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -291,6 +291,53 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_connect_send(CC_Mqtt5ConnectHandle han /// @ingroup connect CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_connect_cancel(CC_Mqtt5ConnectHandle handle); +/// @brief Prepare, configure, and send "connect" request in one go (simple version) +/// @details Abstracts away sequence of the following functions invocation: +/// @li @ref cc_mqtt5_##NAME##client_connect_prepare() +/// @li @ref cc_mqtt5_##NAME##client_connect_config_basic() +/// @li @ref cc_mqtt5_##NAME##client_connect_send() +/// +/// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_alloc() function. +/// @param[in] basicConfig Basic 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_Mqtt5ErrorCode cc_mqtt5_##NAME##client_connect_simple( + CC_Mqtt5ClientHandle handle, + const CC_Mqtt5ConnectBasicConfig* basicConfig, + CC_Mqtt5ConnectCompleteCb cb, + void* cbData); + +/// @brief Prepare, configure, and send "connect" request in one go (full version) +/// @details Abstracts away sequence of the following functions invocation: +/// @li @ref cc_mqtt5_##NAME##client_connect_prepare() +/// @li @ref cc_mqtt5_##NAME##client_connect_config_basic() +/// @li @ref cc_mqtt5_##NAME##client_connect_config_will() +/// @li @ref cc_mqtt5_##NAME##client_connect_config_extra() +/// @li @ref cc_mqtt5_##NAME##client_connect_config_auth() +/// @li @ref cc_mqtt5_##NAME##client_connect_send() +/// +/// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_alloc() function. +/// @param[in] basicConfig Basic configuration. Can be NULL. +/// @param[in] willConfig Will configuration. Can be NULL. +/// @param[in] extraConfig Extra configuration. Can be NULL. +/// @param[in] authConfig Auth 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_Mqtt5ErrorCode cc_mqtt5_##NAME##client_connect_full( + CC_Mqtt5ClientHandle handle, + const CC_Mqtt5ConnectBasicConfig* basicConfig, + const CC_Mqtt5ConnectWillConfig* willConfig, + const CC_Mqtt5ConnectExtraConfig* extraConfig, + const CC_Mqtt5AuthConfig* authConfig, + CC_Mqtt5ConnectCompleteCb cb, + void* cbData); + /// @brief Check the inner state of the library of whether it's connected to the broker. /// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_alloc() function. /// @ingroup connect @@ -342,6 +389,20 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_disconnect_send(CC_Mqtt5DisconnectHand /// @ingroup disconnect CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_disconnect_cancel(CC_Mqtt5DisconnectHandle handle); +/// @brief Prepare, configure, and send "disconnect" request in one go. +/// @details Abstracts away sequence of the following functions invocation: +/// @li @ref cc_mqtt5_##NAME##client_disconnect_prepare() +/// @li @ref cc_mqtt5_##NAME##client_disconnect_config() +/// @li @ref cc_mqtt5_##NAME##client_disconnect_send() +/// +/// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_alloc() function. +/// @param[in] config Configuration. Can be NULL. +/// @return Result code of the call. +/// @ingroup disconnect +CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_disconnect( + CC_Mqtt5ClientHandle handle, + const CC_Mqtt5DisconnectConfig* config); + /// @brief Prepare "subscribe" operation. /// @details For successful operation the client needs to be in the "connected" state. /// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_alloc() function. @@ -419,6 +480,49 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_subscribe_send(CC_Mqtt5SubscribeHandle /// @ingroup subscribe CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_subscribe_cancel(CC_Mqtt5SubscribeHandle handle); +/// @brief Prepare, configure, and send "subscribe" request in one go (simple version) +/// @details Abstracts away sequence of the following functions invocation: +/// @li @ref cc_mqtt5_##NAME##client_subscribe_prepare() +/// @li @ref cc_mqtt5_##NAME##client_subscribe_config_topic() +/// @li @ref cc_mqtt5_##NAME##client_subscribe_send() +/// +/// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_alloc() function. +/// @param[in] topicConfig Topic configuration. Can be NULL. +/// @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_Mqtt5ErrorCode cc_mqtt5_##NAME##client_subscribe_simple( + CC_Mqtt5ClientHandle handle, + const CC_Mqtt5SubscribeTopicConfig* topicConfig, + CC_Mqtt5SubscribeCompleteCb cb, + void* cbData); + +/// @brief Prepare, configure, and send "subscribe" request in one go (full version) +/// @details Abstracts away sequence of the following functions invocation: +/// @li @ref cc_mqtt5_##NAME##client_subscribe_prepare() +/// @li @ref cc_mqtt5_##NAME##client_subscribe_config_topic() (multiple times) +/// @li @ref cc_mqtt5_##NAME##client_subscribe_config_extra() +/// @li @ref cc_mqtt5_##NAME##client_subscribe_send() +/// +/// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_alloc() function. +/// @param[in] topicConfigs Pointer to array of the topic configurations. +/// @param[in] topicConfigsCount Amount of the topic configurations in the array. +/// @param[in] extraConfig Extra configuration. Can be NULL. +/// @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_Mqtt5ErrorCode cc_mqtt5_##NAME##client_subscribe_full( + CC_Mqtt5ClientHandle handle, + const CC_Mqtt5SubscribeTopicConfig* topicConfigs, + unsigned topicConfigsCount, + const CC_Mqtt5SubscribeExtraConfig* extraConfig, + CC_Mqtt5SubscribeCompleteCb cb, + void* cbData); + /// @brief Prepare "unsubscribe" operation. /// @details For successful operation the client needs to be in the "connected" state. /// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_alloc() function. @@ -483,6 +587,46 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_unsubscribe_send(CC_Mqtt5UnsubscribeHa /// @ingroup unsubscribe CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_unsubscribe_cancel(CC_Mqtt5UnsubscribeHandle handle); +/// @brief Prepare, configure, and send "unsubscribe" request in one go (simple version) +/// @details Abstracts away sequence of the following functions invocation: +/// @li @ref cc_mqtt5_##NAME##client_unsubscribe_prepare() +/// @li @ref cc_mqtt5_##NAME##client_unsubscribe_config_topic() +/// @li @ref cc_mqtt5_##NAME##client_unsubscribe_send() +/// +/// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_alloc() function. +/// @param[in] topicConfig Topic configuration. Can be NULL. +/// @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_Mqtt5ErrorCode cc_mqtt5_##NAME##client_unsubscribe_simple( + CC_Mqtt5ClientHandle handle, + const CC_Mqtt5UnsubscribeTopicConfig* topicConfig, + CC_Mqtt5UnsubscribeCompleteCb cb, + void* cbData); + +/// @brief Prepare, configure, and send "unsubscribe" request in one go (full version) +/// @details Abstracts away sequence of the following functions invocation: +/// @li @ref cc_mqtt5_##NAME##client_unsubscribe_prepare() +/// @li @ref cc_mqtt5_##NAME##client_unsubscribe_config_topic() (multiple times) +/// @li @ref cc_mqtt5_##NAME##client_unsubscribe_send() +/// +/// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_alloc() function. +/// @param[in] topicConfigs Pointer to array of the topic configurations. +/// @param[in] topicConfigsCount Amount of the topic configurations in the array. +/// @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_Mqtt5ErrorCode cc_mqtt5_##NAME##client_unsubscribe_full( + CC_Mqtt5ClientHandle handle, + const CC_Mqtt5UnsubscribeTopicConfig* topicConfigs, + unsigned topicConfigsCount, + CC_Mqtt5UnsubscribeCompleteCb cb, + void* cbData); + /// @brief Prepare "publish" operation. /// @details For successful operation the client needs to be in the "connected" state. /// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_alloc() function. @@ -576,6 +720,47 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_send(CC_Mqtt5PublishHandle han /// @ingroup publish CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_cancel(CC_Mqtt5PublishHandle handle); +/// @brief Prepare, configure, and send "publish" request in one go (simple version) +/// @details Abstracts away sequence of the following functions invocation: +/// @li @ref cc_mqtt5_##NAME##client_publish_prepare() +/// @li @ref cc_mqtt5_##NAME##client_publish_config_basic() +/// @li @ref cc_mqtt5_##NAME##client_publish_send() +/// +/// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_alloc() function. +/// @param[in] basicConfig Basic 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_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_simple( + CC_Mqtt5ClientHandle handle, + const CC_Mqtt5PublishBasicConfig* basicConfig, + CC_Mqtt5PublishCompleteCb cb, + void* cbData); + +/// @brief Prepare, configure, and send "publish" request in one go (full version) +/// @details Abstracts away sequence of the following functions invocation: +/// @li @ref cc_mqtt5_##NAME##client_publish_prepare() +/// @li @ref cc_mqtt5_##NAME##client_publish_config_basic() +/// @li @ref cc_mqtt5_##NAME##client_publish_config_extra() +/// @li @ref cc_mqtt5_##NAME##client_publish_send() +/// +/// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_alloc() function. +/// @param[in] basicConfig Basic configuration. +/// @param[in] extraConfig Extra configuration. Can be NULL. +/// @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_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_full( + CC_Mqtt5ClientHandle handle, + const CC_Mqtt5PublishBasicConfig* basicConfig, + const CC_Mqtt5PublishExtraConfig* extraConfig, + CC_Mqtt5PublishCompleteCb cb, + void* cbData); + /// @brief Prepare "reauth" operation. /// @details For successful operation the client needs to be in the "connected" state and /// there is no other incomplete "reauth" operation. @@ -642,6 +827,25 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_reauth_send(CC_Mqtt5ReauthHandle handl /// @ingroup reauth CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_reauth_cancel(CC_Mqtt5ReauthHandle handle); +/// @brief Prepare, configure, and send "reauth" request in one go +/// @details Abstracts away sequence of the following functions invocation: +/// @li @ref cc_mqtt5_##NAME##client_reauth_prepare() +/// @li @ref cc_mqtt5_##NAME##client_reauth_config_auth() +/// @li @ref cc_mqtt5_##NAME##client_reauth_send() +/// +/// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_alloc() function. +/// @param[in] config Auth configuration. +/// @param[in] cb Callback to be invoked when "reauth" 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 reauth +CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_reauth( + CC_Mqtt5ClientHandle handle, + const CC_Mqtt5AuthConfig* config, + CC_Mqtt5ReauthCompleteCb cb, + void* cbData); + // --------------------- Callbacks --------------------- /// @brief Set callback to call when time measurement is required. From a355236730bc64e50a2ec6a3850661c55b6bbd1d Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sun, 31 Mar 2024 18:34:23 +1000 Subject: [PATCH 11/48] Using ops wrappers in unittesting. --- client/lib/templ/client.cpp.templ | 9 +++ client/lib/test/unit/UnitTestBmBase.cpp | 10 ++++ client/lib/test/unit/UnitTestCommonBase.cpp | 59 ++++++-------------- client/lib/test/unit/UnitTestCommonBase.h | 10 ++++ client/lib/test/unit/UnitTestDefaultBase.cpp | 10 ++++ 5 files changed, 57 insertions(+), 41 deletions(-) diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index 3e02120..570a52a 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -944,6 +944,15 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_cancel(CC_Mqtt5PublishHandle h return sendOpFromHandle(handle)->cancel(); } +CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_simple( + CC_Mqtt5ClientHandle handle, + const CC_Mqtt5PublishBasicConfig* basicConfig, + CC_Mqtt5PublishCompleteCb cb, + void* cbData) +{ + return cc_mqtt5_##NAME##client_publish_full(handle, basicConfig, nullptr, cb, cbData); +} + CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_full( CC_Mqtt5ClientHandle handle, const CC_Mqtt5PublishBasicConfig* basicConfig, diff --git a/client/lib/test/unit/UnitTestBmBase.cpp b/client/lib/test/unit/UnitTestBmBase.cpp index 69a8cf3..77b0819 100644 --- a/client/lib/test/unit/UnitTestBmBase.cpp +++ b/client/lib/test/unit/UnitTestBmBase.cpp @@ -40,6 +40,8 @@ const UnitTestBmBase::LibFuncs& UnitTestBmBase::getFuncs() funcs.m_connect_add_will_user_prop = &cc_mqtt5_bm_client_connect_add_will_user_prop; funcs.m_connect_send = &cc_mqtt5_bm_client_connect_send; funcs.m_connect_cancel = &cc_mqtt5_bm_client_connect_cancel; + funcs.m_connect_simple = &cc_mqtt5_bm_client_connect_simple; + funcs.m_connect_full = &cc_mqtt5_bm_client_connect_full; funcs.m_is_connected = &cc_mqtt5_bm_client_is_connected; funcs.m_disconnect_prepare = &cc_mqtt5_bm_client_disconnect_prepare; funcs.m_disconnect_init_config = &cc_mqtt5_bm_client_disconnect_init_config; @@ -47,6 +49,7 @@ const UnitTestBmBase::LibFuncs& UnitTestBmBase::getFuncs() funcs.m_disconnect_add_user_prop = &cc_mqtt5_bm_client_disconnect_add_user_prop; funcs.m_disconnect_send = &cc_mqtt5_bm_client_disconnect_send; funcs.m_disconnect_cancel = &cc_mqtt5_bm_client_disconnect_cancel; + funcs.m_disconnect = &cc_mqtt5_bm_client_disconnect; funcs.m_subscribe_prepare = &cc_mqtt5_bm_client_subscribe_prepare; funcs.m_subscribe_set_response_timeout = &cc_mqtt5_bm_client_subscribe_set_response_timeout; funcs.m_subscribe_get_response_timeout = &cc_mqtt5_bm_client_subscribe_get_response_timeout; @@ -57,6 +60,8 @@ const UnitTestBmBase::LibFuncs& UnitTestBmBase::getFuncs() funcs.m_subscribe_add_user_prop = &cc_mqtt5_bm_client_subscribe_add_user_prop; funcs.m_subscribe_send = &cc_mqtt5_bm_client_subscribe_send; funcs.m_subscribe_cancel = &cc_mqtt5_bm_client_subscribe_cancel; + funcs.m_subscribe_simple = &cc_mqtt5_bm_client_subscribe_simple; + funcs.m_subscribe_full = &cc_mqtt5_bm_client_subscribe_full; funcs.m_unsubscribe_prepare = &cc_mqtt5_bm_client_unsubscribe_prepare; funcs.m_unsubscribe_set_response_timeout = &cc_mqtt5_bm_client_unsubscribe_set_response_timeout; funcs.m_unsubscribe_get_response_timeout = &cc_mqtt5_bm_client_unsubscribe_get_response_timeout; @@ -65,6 +70,8 @@ const UnitTestBmBase::LibFuncs& UnitTestBmBase::getFuncs() funcs.m_unsubscribe_add_user_prop = &cc_mqtt5_bm_client_unsubscribe_add_user_prop; funcs.m_unsubscribe_send = &cc_mqtt5_bm_client_unsubscribe_send; funcs.m_unsubscribe_cancel = &cc_mqtt5_bm_client_unsubscribe_cancel; + funcs.m_unsubscribe_simple = &cc_mqtt5_bm_client_unsubscribe_simple; + funcs.m_unsubscribe_full = &cc_mqtt5_bm_client_unsubscribe_full; funcs.m_publish_prepare = &cc_mqtt5_bm_client_publish_prepare; funcs.m_publish_init_config_basic = &cc_mqtt5_bm_client_publish_init_config_basic; funcs.m_publish_init_config_extra = &cc_mqtt5_bm_client_publish_init_config_extra; @@ -77,6 +84,8 @@ const UnitTestBmBase::LibFuncs& UnitTestBmBase::getFuncs() funcs.m_publish_add_user_prop = &cc_mqtt5_bm_client_publish_add_user_prop; funcs.m_publish_send = &cc_mqtt5_bm_client_publish_send; funcs.m_publish_cancel = &cc_mqtt5_bm_client_publish_cancel; + funcs.m_publish_simple = &cc_mqtt5_bm_client_publish_simple; + funcs.m_publish_full = &cc_mqtt5_bm_client_publish_full; funcs.m_reauth_prepare = &cc_mqtt5_bm_client_reauth_prepare; funcs.m_reauth_init_config_auth = &cc_mqtt5_bm_client_reauth_init_config_auth; funcs.m_reauth_set_response_timeout = &cc_mqtt5_bm_client_reauth_set_response_timeout; @@ -85,6 +94,7 @@ const UnitTestBmBase::LibFuncs& UnitTestBmBase::getFuncs() funcs.m_reauth_add_user_prop = &cc_mqtt5_bm_client_reauth_add_user_prop; funcs.m_reauth_send = &cc_mqtt5_bm_client_reauth_send; funcs.m_reauth_cancel = &cc_mqtt5_bm_client_reauth_cancel; + funcs.m_reauth = &cc_mqtt5_bm_client_reauth; funcs.m_set_next_tick_program_callback = &cc_mqtt5_bm_client_set_next_tick_program_callback; funcs.m_set_cancel_next_tick_wait_callback = &cc_mqtt5_bm_client_set_cancel_next_tick_wait_callback; funcs.m_set_send_output_data_callback = &cc_mqtt5_bm_client_set_send_output_data_callback; diff --git a/client/lib/test/unit/UnitTestCommonBase.cpp b/client/lib/test/unit/UnitTestCommonBase.cpp index d837c07..cb5464c 100644 --- a/client/lib/test/unit/UnitTestCommonBase.cpp +++ b/client/lib/test/unit/UnitTestCommonBase.cpp @@ -74,6 +74,8 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_connect_add_will_user_prop != nullptr); test_assert(m_funcs.m_connect_send != nullptr); test_assert(m_funcs.m_connect_cancel != nullptr); + test_assert(m_funcs.m_connect_simple != nullptr); + test_assert(m_funcs.m_connect_full != nullptr); test_assert(m_funcs.m_is_connected != nullptr); test_assert(m_funcs.m_disconnect_prepare != nullptr); test_assert(m_funcs.m_disconnect_init_config != nullptr); @@ -81,6 +83,7 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_disconnect_add_user_prop != 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_subscribe_prepare != nullptr); test_assert(m_funcs.m_subscribe_set_response_timeout != nullptr); test_assert(m_funcs.m_subscribe_get_response_timeout != nullptr); @@ -91,6 +94,8 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_subscribe_add_user_prop != nullptr); test_assert(m_funcs.m_subscribe_send != nullptr); test_assert(m_funcs.m_subscribe_cancel != nullptr); + test_assert(m_funcs.m_subscribe_simple != nullptr); + test_assert(m_funcs.m_subscribe_full != nullptr); test_assert(m_funcs.m_unsubscribe_prepare != nullptr); test_assert(m_funcs.m_unsubscribe_set_response_timeout != nullptr); test_assert(m_funcs.m_unsubscribe_get_response_timeout != nullptr); @@ -99,6 +104,8 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_unsubscribe_add_user_prop != nullptr); test_assert(m_funcs.m_unsubscribe_send != nullptr); test_assert(m_funcs.m_unsubscribe_cancel != nullptr); + test_assert(m_funcs.m_unsubscribe_simple != nullptr); + test_assert(m_funcs.m_unsubscribe_full != nullptr); test_assert(m_funcs.m_publish_prepare != nullptr); test_assert(m_funcs.m_publish_init_config_basic != nullptr); test_assert(m_funcs.m_publish_init_config_extra != nullptr); @@ -111,6 +118,8 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_publish_add_user_prop != nullptr); test_assert(m_funcs.m_publish_send != nullptr); test_assert(m_funcs.m_publish_cancel != nullptr); + test_assert(m_funcs.m_publish_simple != nullptr); + test_assert(m_funcs.m_publish_full != nullptr); test_assert(m_funcs.m_reauth_prepare != nullptr); test_assert(m_funcs.m_reauth_init_config_auth != nullptr); test_assert(m_funcs.m_reauth_set_response_timeout != nullptr); @@ -119,6 +128,7 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_reauth_add_user_prop != nullptr); test_assert(m_funcs.m_reauth_send != nullptr); test_assert(m_funcs.m_reauth_cancel != nullptr); + test_assert(m_funcs.m_reauth != 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); @@ -570,34 +580,13 @@ void UnitTestCommonBase::unitTestPerformConnect( CC_Mqtt5AuthConfig* authConfig, const UnitTestConnectResponseConfig* responseConfig) { - auto* connect = unitTestConnectPrepare(client, nullptr); - test_assert(connect != nullptr); - - auto ec = CC_Mqtt5ErrorCode_Success; - if (basicConfig != nullptr) { - ec = unitTestConnectConfigBasic(connect, basicConfig); - test_assert(ec == CC_Mqtt5ErrorCode_Success); - } - - if (willConfig != nullptr) { - ec = unitTestConnectConfigWill(connect, willConfig); - test_assert(ec == CC_Mqtt5ErrorCode_Success); - } - - if (extraConfig != nullptr) { - ec = unitTestConnectConfigExtra(connect, extraConfig); - test_assert(ec == CC_Mqtt5ErrorCode_Success); - } - if (authConfig != nullptr) { test_assert(authConfig->m_authCb == nullptr); authConfig->m_authCb = &UnitTestCommonBase::unitTestAuthCb; authConfig->m_authCbData = this; - ec = unitTestConnectConfigAuth(connect, authConfig); - test_assert(ec == CC_Mqtt5ErrorCode_Success); - } + } - ec = unitTestSendConnect(connect); + [[maybe_unused]] auto ec = m_funcs.m_connect_full(client, basicConfig, willConfig, extraConfig, authConfig, &UnitTestCommonBase::unitTestConnectCompleteCb, this); test_assert(ec == CC_Mqtt5ErrorCode_Success); auto sentMsg = unitTestGetSentMessage(); @@ -842,14 +831,7 @@ void UnitTestCommonBase::unitTestPerformDisconnect( CC_Mqtt5Client* client, const CC_Mqtt5DisconnectConfig* config) { - auto* disconnect = unitTestDisconnectPrepare(client, nullptr); - test_assert(disconnect != nullptr); - if (config != nullptr) { - [[maybe_unused]] auto ec = unitTestDisconnectConfig(disconnect, config); - test_assert(ec == CC_Mqtt5ErrorCode_Success); - } - - [[maybe_unused]] auto ec = m_funcs.m_disconnect_send(disconnect); + [[maybe_unused]] auto ec = m_funcs.m_disconnect(client, config); test_assert(ec == CC_Mqtt5ErrorCode_Success); auto reason = UnitTestDisconnectReason::Success; @@ -873,20 +855,15 @@ void UnitTestCommonBase::unitTestPerformBasicSubscribe(CC_Mqtt5Client* client, c unitTestSubscribeInitConfigTopic(&config); config.m_topic = topic; - auto subscribe = unitTestSubscribePrepare(client, nullptr); - test_assert(subscribe != nullptr); - - [[maybe_unused]] auto ec = unitTestSubscribeConfigTopic(subscribe, &config); - test_assert(ec == CC_Mqtt5ErrorCode_Success); + auto extra = CC_Mqtt5SubscribeExtraConfig(); + const CC_Mqtt5SubscribeExtraConfig* extraPtr = nullptr; if (subId > 0U) { - auto extra = CC_Mqtt5SubscribeExtraConfig(); extra.m_subId = subId; - ec = unitTestSubscribeConfigExtra(subscribe, &extra); - test_assert(ec == CC_Mqtt5ErrorCode_Success); - } + extraPtr = &extra; + } - ec = unitTestSendSubscribe(subscribe); + [[maybe_unused]] auto ec = m_funcs.m_subscribe_full(client, &config, 1U, extraPtr, &UnitTestCommonBase::unitTestSubscribeCompleteCb, this); test_assert(ec == CC_Mqtt5ErrorCode_Success); test_assert(!unitTestIsSubscribeComplete()); diff --git a/client/lib/test/unit/UnitTestCommonBase.h b/client/lib/test/unit/UnitTestCommonBase.h index b228bc1..2bb2546 100644 --- a/client/lib/test/unit/UnitTestCommonBase.h +++ b/client/lib/test/unit/UnitTestCommonBase.h @@ -54,6 +54,8 @@ class UnitTestCommonBase CC_Mqtt5ErrorCode (*m_connect_add_will_user_prop)(CC_Mqtt5ConnectHandle, const CC_Mqtt5UserProp*) = nullptr; CC_Mqtt5ErrorCode (*m_connect_send)(CC_Mqtt5ConnectHandle, CC_Mqtt5ConnectCompleteCb, void*) = nullptr; CC_Mqtt5ErrorCode (*m_connect_cancel)(CC_Mqtt5ConnectHandle) = nullptr; + CC_Mqtt5ErrorCode (*m_connect_simple)(CC_Mqtt5ClientHandle handle, const CC_Mqtt5ConnectBasicConfig*, CC_Mqtt5ConnectCompleteCb, void*) = nullptr; + CC_Mqtt5ErrorCode (*m_connect_full)(CC_Mqtt5ClientHandle handle, const CC_Mqtt5ConnectBasicConfig*, const CC_Mqtt5ConnectWillConfig*, const CC_Mqtt5ConnectExtraConfig*, const CC_Mqtt5AuthConfig*, CC_Mqtt5ConnectCompleteCb, void*) = nullptr; bool (*m_is_connected)(CC_Mqtt5ClientHandle) = nullptr; CC_Mqtt5DisconnectHandle (*m_disconnect_prepare)(CC_Mqtt5ClientHandle, CC_Mqtt5ErrorCode*) = nullptr; void (*m_disconnect_init_config)(CC_Mqtt5DisconnectConfig*) = nullptr; @@ -61,6 +63,7 @@ class UnitTestCommonBase CC_Mqtt5ErrorCode (*m_disconnect_add_user_prop)(CC_Mqtt5DisconnectHandle, const CC_Mqtt5UserProp*) = nullptr; CC_Mqtt5ErrorCode (*m_disconnect_send)(CC_Mqtt5DisconnectHandle) = nullptr; CC_Mqtt5ErrorCode (*m_disconnect_cancel)(CC_Mqtt5DisconnectHandle) = nullptr; + CC_Mqtt5ErrorCode (*m_disconnect)(CC_Mqtt5ClientHandle, const CC_Mqtt5DisconnectConfig*) = nullptr; CC_Mqtt5SubscribeHandle (*m_subscribe_prepare)(CC_Mqtt5ClientHandle, CC_Mqtt5ErrorCode*) = nullptr; CC_Mqtt5ErrorCode (*m_subscribe_set_response_timeout)(CC_Mqtt5SubscribeHandle, unsigned) = nullptr; unsigned (*m_subscribe_get_response_timeout)(CC_Mqtt5SubscribeHandle) = nullptr; @@ -71,6 +74,8 @@ class UnitTestCommonBase CC_Mqtt5ErrorCode (*m_subscribe_add_user_prop)(CC_Mqtt5SubscribeHandle, const CC_Mqtt5UserProp*) = nullptr; CC_Mqtt5ErrorCode (*m_subscribe_send)(CC_Mqtt5SubscribeHandle, CC_Mqtt5SubscribeCompleteCb, void*) = nullptr; CC_Mqtt5ErrorCode (*m_subscribe_cancel)(CC_Mqtt5SubscribeHandle) = nullptr; + CC_Mqtt5ErrorCode (*m_subscribe_simple)(CC_Mqtt5ClientHandle, const CC_Mqtt5SubscribeTopicConfig*, CC_Mqtt5SubscribeCompleteCb, void*) = nullptr; + CC_Mqtt5ErrorCode (*m_subscribe_full)(CC_Mqtt5ClientHandle, const CC_Mqtt5SubscribeTopicConfig*, unsigned, const CC_Mqtt5SubscribeExtraConfig*, CC_Mqtt5SubscribeCompleteCb, void*) = nullptr; CC_Mqtt5UnsubscribeHandle (*m_unsubscribe_prepare)(CC_Mqtt5ClientHandle, CC_Mqtt5ErrorCode*) = nullptr; CC_Mqtt5ErrorCode (*m_unsubscribe_set_response_timeout)(CC_Mqtt5UnsubscribeHandle, unsigned) = nullptr; unsigned (*m_unsubscribe_get_response_timeout)(CC_Mqtt5UnsubscribeHandle) = nullptr; @@ -79,6 +84,8 @@ class UnitTestCommonBase CC_Mqtt5ErrorCode (*m_unsubscribe_add_user_prop)(CC_Mqtt5UnsubscribeHandle, const CC_Mqtt5UserProp*) = nullptr; CC_Mqtt5ErrorCode (*m_unsubscribe_send)(CC_Mqtt5UnsubscribeHandle, CC_Mqtt5UnsubscribeCompleteCb, void*) = nullptr; CC_Mqtt5ErrorCode (*m_unsubscribe_cancel)(CC_Mqtt5UnsubscribeHandle) = nullptr; + CC_Mqtt5ErrorCode (*m_unsubscribe_simple)(CC_Mqtt5ClientHandle, const CC_Mqtt5UnsubscribeTopicConfig*, CC_Mqtt5UnsubscribeCompleteCb, void*) = nullptr; + CC_Mqtt5ErrorCode (*m_unsubscribe_full)(CC_Mqtt5ClientHandle, const CC_Mqtt5UnsubscribeTopicConfig*, unsigned, CC_Mqtt5UnsubscribeCompleteCb, void*) = nullptr; CC_Mqtt5PublishHandle (*m_publish_prepare)(CC_Mqtt5ClientHandle, CC_Mqtt5ErrorCode*) = nullptr; void (*m_publish_init_config_basic)(CC_Mqtt5PublishBasicConfig*) = nullptr; void (*m_publish_init_config_extra)(CC_Mqtt5PublishExtraConfig*) = nullptr; @@ -91,6 +98,8 @@ class UnitTestCommonBase CC_Mqtt5ErrorCode (*m_publish_add_user_prop)(CC_Mqtt5PublishHandle, const CC_Mqtt5UserProp*) = nullptr; CC_Mqtt5ErrorCode (*m_publish_send)(CC_Mqtt5PublishHandle, CC_Mqtt5PublishCompleteCb, void*) = nullptr; CC_Mqtt5ErrorCode (*m_publish_cancel)(CC_Mqtt5PublishHandle) = nullptr; + CC_Mqtt5ErrorCode (*m_publish_simple)(CC_Mqtt5ClientHandle, const CC_Mqtt5PublishBasicConfig*, CC_Mqtt5PublishCompleteCb, void*) = nullptr; + CC_Mqtt5ErrorCode (*m_publish_full)(CC_Mqtt5ClientHandle, const CC_Mqtt5PublishBasicConfig*, const CC_Mqtt5PublishExtraConfig*, CC_Mqtt5PublishCompleteCb, void*) = nullptr; CC_Mqtt5ReauthHandle (*m_reauth_prepare)(CC_Mqtt5ClientHandle, CC_Mqtt5ErrorCode*) = nullptr; void (*m_reauth_init_config_auth)(CC_Mqtt5AuthConfig*) = nullptr; CC_Mqtt5ErrorCode (*m_reauth_set_response_timeout)(CC_Mqtt5ReauthHandle, unsigned) = nullptr; @@ -99,6 +108,7 @@ class UnitTestCommonBase CC_Mqtt5ErrorCode (*m_reauth_add_user_prop)(CC_Mqtt5ReauthHandle, const CC_Mqtt5UserProp*) = nullptr; CC_Mqtt5ErrorCode (*m_reauth_send)(CC_Mqtt5ReauthHandle, CC_Mqtt5ReauthCompleteCb, void*) = nullptr; CC_Mqtt5ErrorCode (*m_reauth_cancel)(CC_Mqtt5ReauthHandle) = nullptr; + CC_Mqtt5ErrorCode (*m_reauth)(CC_Mqtt5ClientHandle, const CC_Mqtt5AuthConfig*, CC_Mqtt5ReauthCompleteCb, void*) = nullptr; void (*m_set_next_tick_program_callback)(CC_Mqtt5ClientHandle, CC_Mqtt5NextTickProgramCb, void*) = nullptr; void (*m_set_cancel_next_tick_wait_callback)(CC_Mqtt5ClientHandle, CC_Mqtt5CancelNextTickWaitCb, void*) = nullptr; void (*m_set_send_output_data_callback)(CC_Mqtt5ClientHandle, CC_Mqtt5SendOutputDataCb, void*) = nullptr; diff --git a/client/lib/test/unit/UnitTestDefaultBase.cpp b/client/lib/test/unit/UnitTestDefaultBase.cpp index 1079910..f299298 100644 --- a/client/lib/test/unit/UnitTestDefaultBase.cpp +++ b/client/lib/test/unit/UnitTestDefaultBase.cpp @@ -40,6 +40,8 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_connect_add_will_user_prop = &cc_mqtt5_client_connect_add_will_user_prop; funcs.m_connect_send = &cc_mqtt5_client_connect_send; funcs.m_connect_cancel = &cc_mqtt5_client_connect_cancel; + funcs.m_connect_simple = &cc_mqtt5_client_connect_simple; + funcs.m_connect_full = &cc_mqtt5_client_connect_full; funcs.m_is_connected = &cc_mqtt5_client_is_connected; funcs.m_disconnect_prepare = &cc_mqtt5_client_disconnect_prepare; funcs.m_disconnect_init_config = &cc_mqtt5_client_disconnect_init_config; @@ -47,6 +49,7 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_disconnect_add_user_prop = &cc_mqtt5_client_disconnect_add_user_prop; funcs.m_disconnect_send = &cc_mqtt5_client_disconnect_send; funcs.m_disconnect_cancel = &cc_mqtt5_client_disconnect_cancel; + funcs.m_disconnect = &cc_mqtt5_client_disconnect; funcs.m_subscribe_prepare = &cc_mqtt5_client_subscribe_prepare; funcs.m_subscribe_set_response_timeout = &cc_mqtt5_client_subscribe_set_response_timeout; funcs.m_subscribe_get_response_timeout = &cc_mqtt5_client_subscribe_get_response_timeout; @@ -57,6 +60,8 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_subscribe_add_user_prop = &cc_mqtt5_client_subscribe_add_user_prop; funcs.m_subscribe_send = &cc_mqtt5_client_subscribe_send; funcs.m_subscribe_cancel = &cc_mqtt5_client_subscribe_cancel; + funcs.m_subscribe_simple = &cc_mqtt5_client_subscribe_simple; + funcs.m_subscribe_full = &cc_mqtt5_client_subscribe_full; funcs.m_unsubscribe_prepare = &cc_mqtt5_client_unsubscribe_prepare; funcs.m_unsubscribe_set_response_timeout = &cc_mqtt5_client_unsubscribe_set_response_timeout; funcs.m_unsubscribe_get_response_timeout = &cc_mqtt5_client_unsubscribe_get_response_timeout; @@ -65,6 +70,8 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_unsubscribe_add_user_prop = &cc_mqtt5_client_unsubscribe_add_user_prop; funcs.m_unsubscribe_send = &cc_mqtt5_client_unsubscribe_send; funcs.m_unsubscribe_cancel = &cc_mqtt5_client_unsubscribe_cancel; + funcs.m_unsubscribe_simple = &cc_mqtt5_client_unsubscribe_simple; + funcs.m_unsubscribe_full = &cc_mqtt5_client_unsubscribe_full; funcs.m_publish_prepare = &cc_mqtt5_client_publish_prepare; funcs.m_publish_init_config_basic = &cc_mqtt5_client_publish_init_config_basic; funcs.m_publish_init_config_extra = &cc_mqtt5_client_publish_init_config_extra; @@ -77,6 +84,8 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_publish_add_user_prop = &cc_mqtt5_client_publish_add_user_prop; funcs.m_publish_send = &cc_mqtt5_client_publish_send; funcs.m_publish_cancel = &cc_mqtt5_client_publish_cancel; + funcs.m_publish_simple = &cc_mqtt5_client_publish_simple; + funcs.m_publish_full = &cc_mqtt5_client_publish_full; funcs.m_reauth_prepare = &cc_mqtt5_client_reauth_prepare; funcs.m_reauth_init_config_auth = &cc_mqtt5_client_reauth_init_config_auth; funcs.m_reauth_set_response_timeout = &cc_mqtt5_client_reauth_set_response_timeout; @@ -85,6 +94,7 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_reauth_add_user_prop = &cc_mqtt5_client_reauth_add_user_prop; funcs.m_reauth_send = &cc_mqtt5_client_reauth_send; funcs.m_reauth_cancel = &cc_mqtt5_client_reauth_cancel; + funcs.m_reauth = &cc_mqtt5_client_reauth; funcs.m_set_next_tick_program_callback = &cc_mqtt5_client_set_next_tick_program_callback; funcs.m_set_cancel_next_tick_wait_callback = &cc_mqtt5_client_set_cancel_next_tick_wait_callback; funcs.m_set_send_output_data_callback = &cc_mqtt5_client_set_send_output_data_callback; From 87cb03621cde844193339479b294c4667657adc0 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 2 Apr 2024 08:53:44 +1000 Subject: [PATCH 12/48] Updated spec_compliance (MQTT-4.4.0-1). --- client/lib/spec_compliance.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/lib/spec_compliance.md b/client/lib/spec_compliance.md index 7fe1673..f23096e 100644 --- a/client/lib/spec_compliance.md +++ b/client/lib/spec_compliance.md @@ -743,8 +743,8 @@ MUST resend any unacknowledged PUBLISH packets (where QoS > 0) and PUBREL packets using their original Packet Identifiers. This is the only circumstance where a Client or Server is REQUIRED to resend messages. Clients and Servers MUST NOT resend messages at any other time. - * Not applicable to current client implementation. It doesn't allow connect without previous disconnection - where all outstanding requests get aborted. + * Publish tested in UnitTestPublish::test[35 - 39] + * Receive tested in UnitTestReceive::test[22 - 25] - [MQTT-4.4.0-2]: If PUBACK or PUBREC is received containing a Reason Code of 0x80 or greater the corresponding PUBLISH packet is treated as acknowledged, and MUST NOT be retransmitted * Tested in UnitTestPublish::test31 and UnitTestPublish::test32. From 2a3b3a5f873bb2c3d9603fe7815720ba441ce498 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 2 Apr 2024 08:57:14 +1000 Subject: [PATCH 13/48] Replacing newly added assert() invocations with COMMS_ASSERT(). --- client/lib/src/ClientImpl.cpp | 4 ++-- client/lib/src/op/RecvOp.cpp | 2 +- client/lib/src/op/SendOp.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 1a886cd..773c6fd 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -921,12 +921,12 @@ void ClientImpl::brokerDisconnected( terminateOps(status, TerminateMode_KeepSendRecvOps); for (auto& op : m_recvOps) { - assert(op); + COMMS_ASSERT(op); op->networkConnectivityChanged(); } for (auto& op : m_sendOps) { - assert(op); + COMMS_ASSERT(op); op->networkConnectivityChanged(); } diff --git a/client/lib/src/op/RecvOp.cpp b/client/lib/src/op/RecvOp.cpp index 2d5f85e..c5f3d83 100644 --- a/client/lib/src/op/RecvOp.cpp +++ b/client/lib/src/op/RecvOp.cpp @@ -111,7 +111,7 @@ void RecvOp::handle(PublishMsg& msg) } // If dispatched to this op, duplicate has been detected - assert(msg.transportField_flags().field_dup().getBitValue_bit()); + COMMS_ASSERT(msg.transportField_flags().field_dup().getBitValue_bit()); PubrecMsg pubrecMsg; pubrecMsg.field_packetId().setValue(m_packetId); sendMessage(pubrecMsg); diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index 4384a04..55ce7b6 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -630,7 +630,7 @@ CC_Mqtt5ErrorCode SendOp::cancel() void SendOp::postReconnectionResend() { - assert(m_sendAttempts > 0U); + COMMS_ASSERT(m_sendAttempts > 0U); --m_sendAttempts; m_responseTimer.cancel(); responseTimeoutInternal(); // Emulating timeout will resend the message again with DUP flag (if needed). From b4cf391db6ea33035880162750d4ed73dee99b6f Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 2 Apr 2024 09:10:29 +1000 Subject: [PATCH 14/48] Cosmetic improvement to the "send" operation. --- client/lib/src/op/SendOp.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index 55ce7b6..9283f70 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -197,10 +197,12 @@ void SendOp::handle(PubrecMsg& msg) } } + // Protocol wise it's all correct, no need to terminate any more + terminateOnExit.release(); + if ((msg.field_reasonCode().doesExist()) && (msg.field_reasonCode().field().value() >= PubrecMsg::Field_reasonCode::Field::ValueType::UnspecifiedError)) { comms::cast_assign(response.m_reasonCode) = msg.field_reasonCode().field().value(); - terminateOnExit.release(); status = CC_Mqtt5AsyncOpStatus_Complete; return; } @@ -216,13 +218,11 @@ void SendOp::handle(PubrecMsg& msg) auto result = client().sendMessage(pubrelMsg); if (result != CC_Mqtt5ErrorCode_Success) { errorLog("Failed to resend PUBREL message."); - terminateOnExit.release(); status = CC_Mqtt5AsyncOpStatus_InternalError; return; } m_reasonCode = response.m_reasonCode; - terminateOnExit.release(); completeOpOnExit.release(); ++m_sendAttempts; restartResponseTimer(); From 40225b9768eb24f36586c2f3800c4a2719867f80 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 2 Apr 2024 13:16:10 +1000 Subject: [PATCH 15/48] Keeping the "publish" operation paused when the "Receive Maximum" set by the broker is reached. --- client/lib/include/cc_mqtt5_client/common.h | 2 +- client/lib/src/ClientImpl.cpp | 52 +- client/lib/src/op/SendOp.cpp | 85 ++- client/lib/src/op/SendOp.h | 9 + client/lib/test/unit/UnitTestCommonBase.cpp | 12 +- client/lib/test/unit/UnitTestCommonBase.h | 3 +- client/lib/test/unit/UnitTestPublish.th | 676 +++++++++++++++++++- 7 files changed, 809 insertions(+), 30 deletions(-) diff --git a/client/lib/include/cc_mqtt5_client/common.h b/client/lib/include/cc_mqtt5_client/common.h index 35fb62c..1143b14 100644 --- a/client/lib/include/cc_mqtt5_client/common.h +++ b/client/lib/include/cc_mqtt5_client/common.h @@ -319,7 +319,7 @@ typedef struct const CC_Mqtt5UserProp* m_userProps; ///< Pointer to array containing "User Properties", NULL if none unsigned m_userPropsCount; ///< Amount of "User Properties" in the array unsigned m_sessionExpiryInterval; ///< "Session Expiry Interval" property, 0 if not reported. - unsigned m_highQosSendLimit; ///< "Receive Maximum" property, 0 if not reported. + unsigned m_highQosSendLimit; ///< "Receive Maximum" property. unsigned m_maxPacketSize; ///< "Maximum Packet Size" property, 0 if not reported. unsigned m_topicAliasMax; ///< "Topic Alias Maximum" property, 0 if not reported. CC_Mqtt5QoS m_maxQos; ///< "Maximum QoS" property, diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 773c6fd..fe747e2 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -403,6 +403,11 @@ op::SendOp* ClientImpl::publishPrepare(CC_Mqtt5ErrorCode* ec) break; } + COMMS_ASSERT(0U < m_sessionState.m_highQosSendLimit); + if (m_sessionState.m_highQosSendLimit <= m_sendOps.size()) { + ptr->pause(); + } + m_ops.push_back(ptr.get()); m_sendOps.push_back(std::move(ptr)); sendOp = m_sendOps.back().get(); @@ -889,7 +894,36 @@ void ClientImpl::brokerConnected(bool sessionPresent) for (auto& recvOpPtr : m_recvOps) { recvOpPtr->postReconnectionResume(); - } + } + + COMMS_ASSERT(0U < m_sessionState.m_highQosSendLimit); + auto resumeUntilIdx = std::min(std::size_t(m_sessionState.m_highQosSendLimit), 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; + } + + // Resume in-order of creation + while (resumeFromIdx < std::min(std::size_t(m_sessionState.m_highQosSendLimit), m_sendOps.size())) { + auto& sendOpPtr = m_sendOps[resumeFromIdx]; + COMMS_ASSERT(sendOpPtr); + if (!sendOpPtr->isPaused()) { + ++resumeFromIdx; + continue; + } + + sendOpPtr->resume(); + // Do not increament resumeFromIdx right away + // There can be QoS0 ops that can get completed right away + // The QoS0 completion can also trigger the resume of others + // Increment in the next iteration + } break; } @@ -1118,6 +1152,22 @@ void ClientImpl::opComplete_Recv(const op::Op* op) void ClientImpl::opComplete_Send(const op::Op* op) { eraseFromList(op, m_sendOps); + if (m_sessionState.m_terminating) { + return; + } + + COMMS_ASSERT(0U < m_sessionState.m_highQosSendLimit); + if (m_sendOps.size() < m_sessionState.m_highQosSendLimit) { + return; + } + + auto& opToUnpausePtr = m_sendOps[m_sessionState.m_highQosSendLimit - 1U]; + if (!opToUnpausePtr->isPaused()) { + // Some operations that has been paused can be cancelled, hence not influencing any resume. + return; + } + + opToUnpausePtr->resume(); } void ClientImpl::opComplete_Reauth(const op::Op* op) diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index 9283f70..203a4ae 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -331,11 +331,6 @@ CC_Mqtt5ErrorCode SendOp::configBasic(const CC_Mqtt5PublishBasicConfig& config) return CC_Mqtt5ErrorCode_BadParam; } - if ((config.m_qos > CC_Mqtt5QoS_AtMostOnceDelivery) && (state.m_highQosSendLimit < client().sendsCount())) { - errorLog("Exceeding number of allowed high QoS publishes."); - return CC_Mqtt5ErrorCode_Busy; - } - if (config.m_retain && (!state.m_retainAvailable)) { errorLog("Retain is not supported by the broker"); return CC_Mqtt5ErrorCode_BadParam; @@ -603,23 +598,19 @@ CC_Mqtt5ErrorCode SendOp::send(CC_Mqtt5PublishCompleteCb cb, void* cbData) m_pubMsg.doRefresh(); // Update packetId presence - m_sendAttempts = 0U; - auto result = client().sendMessage(m_pubMsg); - if (result != CC_Mqtt5ErrorCode_Success) { - return result; - } - - ++m_sendAttempts; - - if (m_pubMsg.transportField_flags().field_qos().value() == PublishMsg::TransportField_flags::Field_qos::ValueType::AtMostOnceDelivery) { - reportPubComplete(CC_Mqtt5AsyncOpStatus_Complete); + if (m_paused) { + completeOnExit.release(); // don't complete op yet return CC_Mqtt5ErrorCode_Success; } - completeOnExit.release(); // don't complete op yet auto guard = client().apiEnter(); - restartResponseTimer(); - return CC_Mqtt5ErrorCode_Success; + auto sendResult = doSendInternal(); + if (sendResult != CC_Mqtt5ErrorCode_Success) { + return sendResult; + } + + completeOnExit.release(); // don't complete op in this context + return sendResult; } CC_Mqtt5ErrorCode SendOp::cancel() @@ -630,10 +621,41 @@ CC_Mqtt5ErrorCode SendOp::cancel() void SendOp::postReconnectionResend() { + if (m_paused) { + return; + } + COMMS_ASSERT(m_sendAttempts > 0U); --m_sendAttempts; m_responseTimer.cancel(); - responseTimeoutInternal(); // Emulating timeout will resend the message again with DUP flag (if needed). + resendDupMsg(); +} + +void SendOp::pause() +{ + COMMS_ASSERT(!m_paused); + m_paused = true; +} + +void SendOp::resume() +{ + COMMS_ASSERT(m_paused); + m_paused = false; + auto ec = doSendInternal(); + if (ec == CC_Mqtt5ErrorCode_Success) { + return; + } + + auto cbStatus = CC_Mqtt5AsyncOpStatus_InternalError; + if (ec == CC_Mqtt5ErrorCode_BadParam) { + cbStatus = CC_Mqtt5AsyncOpStatus_BadParam; + } + else if (ec == CC_Mqtt5ErrorCode_BufferOverflow) { + cbStatus = CC_Mqtt5AsyncOpStatus_OutOfMemory; + } + + reportPubComplete(cbStatus); + opComplete(); } Op::Type SendOp::typeImpl() const @@ -663,6 +685,11 @@ void SendOp::responseTimeoutInternal() { COMMS_ASSERT(!m_responseTimer.isActive()); errorLog("Timeout on publish acknowledgement from broker."); + resendDupMsg(); +} + +void SendOp::resendDupMsg() +{ if (m_totalSendAttempts <= m_sendAttempts) { errorLog("Exhauses all attempts to publish message, discarding publish."); reportPubComplete(CC_Mqtt5AsyncOpStatus_Timeout); @@ -733,6 +760,26 @@ void SendOp::confirmRegisteredAlias() } } +CC_Mqtt5ErrorCode SendOp::doSendInternal() +{ + m_sendAttempts = 0U; + auto result = client().sendMessage(m_pubMsg); + if (result != CC_Mqtt5ErrorCode_Success) { + return result; + } + + ++m_sendAttempts; + + if (m_pubMsg.transportField_flags().field_qos().value() == PublishMsg::TransportField_flags::Field_qos::ValueType::AtMostOnceDelivery) { + reportPubComplete(CC_Mqtt5AsyncOpStatus_Complete); + opComplete(); + return CC_Mqtt5ErrorCode_Success; + } + + restartResponseTimer(); + return CC_Mqtt5ErrorCode_Success; +} + void SendOp::recvTimeoutCb(void* data) { asSendOp(data)->responseTimeoutInternal(); diff --git a/client/lib/src/op/SendOp.h b/client/lib/src/op/SendOp.h index bd47edc..22cb2e8 100644 --- a/client/lib/src/op/SendOp.h +++ b/client/lib/src/op/SendOp.h @@ -60,6 +60,12 @@ class SendOp final : public Op CC_Mqtt5ErrorCode send(CC_Mqtt5PublishCompleteCb cb, void* cbData); CC_Mqtt5ErrorCode cancel(); void postReconnectionResend(); + void pause(); + void resume(); + bool isPaused() const + { + return m_paused; + } protected: virtual Type typeImpl() const override; @@ -69,8 +75,10 @@ class SendOp final : public Op private: void restartResponseTimer(); void responseTimeoutInternal(); + void resendDupMsg(); void reportPubComplete(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response = nullptr); void confirmRegisteredAlias(); + CC_Mqtt5ErrorCode doSendInternal(); static void recvTimeoutCb(void* data); @@ -84,6 +92,7 @@ class SendOp final : public Op bool m_acked = false; bool m_registeredAlias = false; bool m_topicConfigured = false; + bool m_paused = false; static constexpr unsigned DefaultSendAttempts = 2U; static_assert(ExtConfig::SendOpTimers == 1U); diff --git a/client/lib/test/unit/UnitTestCommonBase.cpp b/client/lib/test/unit/UnitTestCommonBase.cpp index cb5464c..f0fd403 100644 --- a/client/lib/test/unit/UnitTestCommonBase.cpp +++ b/client/lib/test/unit/UnitTestCommonBase.cpp @@ -9,6 +9,7 @@ namespace { #define test_assert(cond_) \ + assert(cond_); \ if (!(cond_)) { \ std::cerr << "\nAssertion failure (" << #cond_ << ") in " << __FILE__ << ":" << __LINE__ << std::endl; \ std::exit(1); \ @@ -320,10 +321,12 @@ CC_Mqtt5ErrorCode UnitTestCommonBase::unitTestSendUnsubscribe(CC_Mqtt5Unsubscrib return result; } -CC_Mqtt5ErrorCode UnitTestCommonBase::unitTestSendPublish(CC_Mqtt5PublishHandle& publish) +CC_Mqtt5ErrorCode UnitTestCommonBase::unitTestSendPublish(CC_Mqtt5PublishHandle& publish, bool clearHandle) { auto result = m_funcs.m_publish_send(publish, &UnitTestCommonBase::unitTestPublishCompleteCb, this); - publish = nullptr; + if (clearHandle) { + publish = nullptr; + } return result; } @@ -1109,6 +1112,11 @@ CC_Mqtt5ErrorCode UnitTestCommonBase::unitTestPublishAddUserProp(CC_Mqtt5Publish return m_funcs.m_publish_add_user_prop(handle, prop); } +CC_Mqtt5ErrorCode UnitTestCommonBase::unitTestPublishCancel(CC_Mqtt5PublishHandle handle) +{ + return m_funcs.m_publish_cancel(handle); +} + CC_Mqtt5ReauthHandle UnitTestCommonBase::unitTestReauthPrepare(CC_Mqtt5Client* client, CC_Mqtt5ErrorCode* ec) { return m_funcs.m_reauth_prepare(client, ec); diff --git a/client/lib/test/unit/UnitTestCommonBase.h b/client/lib/test/unit/UnitTestCommonBase.h index 2bb2546..1cd2379 100644 --- a/client/lib/test/unit/UnitTestCommonBase.h +++ b/client/lib/test/unit/UnitTestCommonBase.h @@ -368,7 +368,7 @@ class UnitTestCommonBase CC_Mqtt5ErrorCode unitTestSendConnect(CC_Mqtt5ConnectHandle& connect); CC_Mqtt5ErrorCode unitTestSendSubscribe(CC_Mqtt5SubscribeHandle& subscribe); CC_Mqtt5ErrorCode unitTestSendUnsubscribe(CC_Mqtt5UnsubscribeHandle& unsubscribe); - CC_Mqtt5ErrorCode unitTestSendPublish(CC_Mqtt5PublishHandle& publish); + CC_Mqtt5ErrorCode unitTestSendPublish(CC_Mqtt5PublishHandle& publish, bool clearHandle = true); CC_Mqtt5ErrorCode unitTestSendReauth(CC_Mqtt5ReauthHandle& reauth); UniTestsMsgPtr unitTestGetSentMessage(); bool unitTestHasSentMessage() const; @@ -480,6 +480,7 @@ class UnitTestCommonBase CC_Mqtt5ErrorCode unitTestPublishConfigBasic(CC_Mqtt5PublishHandle handle, const CC_Mqtt5PublishBasicConfig* config); CC_Mqtt5ErrorCode unitTestPublishConfigExtra(CC_Mqtt5PublishHandle handle, const CC_Mqtt5PublishExtraConfig* config); CC_Mqtt5ErrorCode unitTestPublishAddUserProp(CC_Mqtt5PublishHandle handle, const CC_Mqtt5UserProp* prop); + CC_Mqtt5ErrorCode unitTestPublishCancel(CC_Mqtt5PublishHandle handle); CC_Mqtt5ReauthHandle unitTestReauthPrepare(CC_Mqtt5Client* client, CC_Mqtt5ErrorCode* ec); void unitTestReauthInitConfigAuth(CC_Mqtt5AuthConfig* config); CC_Mqtt5ErrorCode unitTestReauthAddUserProp(CC_Mqtt5ReauthHandle handle, const CC_Mqtt5UserProp* prop); diff --git a/client/lib/test/unit/UnitTestPublish.th b/client/lib/test/unit/UnitTestPublish.th index ca76de7..9f54e6d 100644 --- a/client/lib/test/unit/UnitTestPublish.th +++ b/client/lib/test/unit/UnitTestPublish.th @@ -46,6 +46,10 @@ public: void test37(); void test38(); void test39(); + void test40(); + void test41(); + void test42(); + void test43(); private: virtual void setUp() override @@ -2233,6 +2237,8 @@ void UnitTestPublish::test30() TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); auto* publishMsg = dynamic_cast(sentMsg.get()); TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(publishMsg->field_packetId().doesExist()); + auto packetId1 = publishMsg->field_packetId().field().value(); TS_ASSERT(!unitTestIsPublishComplete()); @@ -2246,21 +2252,74 @@ void UnitTestPublish::test30() ec = unitTestSendPublish(publish2); TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + TS_ASSERT(!unitTestHasSentMessage()); // the sent message sending is delayed + + auto* publish3 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish3, nullptr); + + config.m_qos = CC_Mqtt5QoS_ExactlyOnceDelivery; + ec = unitTestPublishConfigBasic(publish3, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish3); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + + TS_ASSERT(!unitTestHasSentMessage()); // the sent message sending is delayed + + unitTestTick(1000); + UnitTestPubackMsg pubackMsg; + pubackMsg.field_packetId().setValue(packetId1); + unitTestReceiveMessage(pubackMsg); + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); + + TS_ASSERT(unitTestHasSentMessage()); sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); publishMsg = dynamic_cast(sentMsg.get()); TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(publishMsg->field_packetId().isMissing()); // QoS0 sent TS_ASSERT(unitTestIsPublishComplete()); - unitTestPopPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); - auto* publish3 = unitTestPublishPrepare(client, nullptr); - TS_ASSERT_DIFFERS(publish3, nullptr); + // QoS2 is also sent right away + TS_ASSERT(unitTestHasSentMessage()); + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(publishMsg->field_packetId().doesExist()); + auto packetId2 = publishMsg->field_packetId().field().value(); + TS_ASSERT(!unitTestIsPublishComplete()); - config.m_qos = CC_Mqtt5QoS_ExactlyOnceDelivery; - ec = unitTestPublishConfigBasic(publish3, &config); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Busy); + unitTestTick(1000); + UnitTestPubrecMsg pubrecMsg; + pubrecMsg.field_packetId().setValue(packetId2); + unitTestReceiveMessage(pubrecMsg); + + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrel); + auto* pubrelMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrelMsg, nullptr); + TS_ASSERT_EQUALS(pubrelMsg->field_packetId().value(), packetId2); + TS_ASSERT(!unitTestIsPublishComplete()); + + unitTestTick(1000); + UnitTestPubcompMsg pubcompMsg; + pubcompMsg.field_packetId().setValue(packetId2); + unitTestReceiveMessage(pubcompMsg); + TS_ASSERT(unitTestIsPublishComplete()); + TS_ASSERT_EQUALS(pubInfo.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); } void UnitTestPublish::test31() @@ -2943,4 +3002,609 @@ void UnitTestPublish::test39() auto& pubInfo = unitTestPublishResponseInfo(); TS_ASSERT_EQUALS(pubInfo.m_status, CC_Mqtt5AsyncOpStatus_Complete); unitTestPopPublishResponseInfo(); +} + +void UnitTestPublish::test40() +{ + // Testing limit of the outgoing high Qos publishes when paused publishes get cancelled + + auto* client = unitTestAllocClient(); + + auto basicConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&basicConfig); + basicConfig.m_clientId = __FUNCTION__; + basicConfig.m_cleanStart = true; + + UnitTestConnectResponseConfig responseConfig; + responseConfig.m_recvMaximum = 1; + + unitTestPerformConnect(client, &basicConfig, nullptr, nullptr, nullptr, &responseConfig); + TS_ASSERT(unitTestIsConnected(client)); + + const std::string Topic("some/topic"); + const UnitTestData Data = { 0x1, 0x2, 0x3, 0x4, 0x5}; + + auto config = CC_Mqtt5PublishBasicConfig(); + unitTestPublishInitConfigBasic(&config); + + config.m_topic = Topic.c_str(); + config.m_data = &Data[0]; + config.m_dataLen = static_cast(Data.size()); + config.m_qos = CC_Mqtt5QoS_AtLeastOnceDelivery; + + auto* publish1 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish1, nullptr); + + auto ec = unitTestPublishConfigBasic(publish1, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish1); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + auto sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(publishMsg->field_packetId().doesExist()); + auto packetId1 = publishMsg->field_packetId().field().value(); + + TS_ASSERT(!unitTestIsPublishComplete()); + + auto* publish2 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish2, nullptr); + + config.m_qos = CC_Mqtt5QoS_AtMostOnceDelivery; + ec = unitTestPublishConfigBasic(publish2, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish2); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + TS_ASSERT(!unitTestHasSentMessage()); // the sent message sending is delayed + + auto* publish3 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish3, nullptr); + + config.m_qos = CC_Mqtt5QoS_ExactlyOnceDelivery; + ec = unitTestPublishConfigBasic(publish3, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish3, false); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + TS_ASSERT(!unitTestHasSentMessage()); // the sent message sending is delayed + + TS_ASSERT_DIFFERS(publish3, nullptr); + ec = unitTestPublishCancel(publish3); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + TS_ASSERT(!unitTestIsPublishComplete()); // No callback is invoked + + TS_ASSERT(!unitTestHasSentMessage()); // No new message has been sent + + unitTestTick(1000); + UnitTestPubackMsg pubackMsg; + pubackMsg.field_packetId().setValue(packetId1); + unitTestReceiveMessage(pubackMsg); + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); + + TS_ASSERT(unitTestHasSentMessage()); + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(publishMsg->field_packetId().isMissing()); // QoS0 sent + + TS_ASSERT(unitTestIsPublishComplete()); + TS_ASSERT_EQUALS(pubInfo.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); + + TS_ASSERT(!unitTestHasSentMessage()); +} + +void UnitTestPublish::test41() +{ + // Testing limit of the outgoing high Qos publishes with broker disconnect + + auto* client = unitTestAllocClient(); + + auto basicConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&basicConfig); + basicConfig.m_clientId = __FUNCTION__; + basicConfig.m_cleanStart = true; + + UnitTestConnectResponseConfig responseConfig; + responseConfig.m_recvMaximum = 1; + + unitTestPerformConnect(client, &basicConfig, nullptr, nullptr, nullptr, &responseConfig); + TS_ASSERT(unitTestIsConnected(client)); + + const std::string Topic("some/topic"); + const UnitTestData Data = { 0x1, 0x2, 0x3, 0x4, 0x5}; + + auto config = CC_Mqtt5PublishBasicConfig(); + unitTestPublishInitConfigBasic(&config); + + config.m_topic = Topic.c_str(); + config.m_data = &Data[0]; + config.m_dataLen = static_cast(Data.size()); + config.m_qos = CC_Mqtt5QoS_AtLeastOnceDelivery; + + auto* publish1 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish1, nullptr); + + auto ec = unitTestPublishConfigBasic(publish1, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish1); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + auto sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(publishMsg->field_packetId().doesExist()); + auto packetId1 = publishMsg->field_packetId().field().value(); + + TS_ASSERT(!unitTestIsPublishComplete()); + + auto* publish2 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish2, nullptr); + + config.m_qos = CC_Mqtt5QoS_AtMostOnceDelivery; + ec = unitTestPublishConfigBasic(publish2, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish2); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + TS_ASSERT(!unitTestHasSentMessage()); // the sent message sending is delayed + + auto* publish3 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish3, nullptr); + + config.m_qos = CC_Mqtt5QoS_ExactlyOnceDelivery; + ec = unitTestPublishConfigBasic(publish3, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish3); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + unitTestTick(100); + UnitTestDisconnectMsg disconnectMsg; + unitTestReceiveMessage(disconnectMsg); + + TS_ASSERT(unitTestIsDisconnected()); + TS_ASSERT(unitTestHasDisconnectInfo()); + auto& disconnectInfo = unitTestDisconnectInfo(); + TS_ASSERT_EQUALS(disconnectInfo.m_reasonCode, CC_Mqtt5ReasonCode_NormalDisconnection); + unitTestPopDisconnectInfo(); + TS_ASSERT(unitTestCheckNoTicks()); + + TS_ASSERT(!unitTestIsPublishComplete()); + TS_ASSERT(unitTestCheckNoTicks()); + unitTestClearState(); + + // Reconnection with attempt to restore the session + auto connectConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&connectConfig); + + connectConfig.m_clientId = __FUNCTION__; + connectConfig.m_cleanStart = false; + + auto connectRespConfig = UnitTestConnectResponseConfig(); + connectRespConfig.m_sessionPresent = true; + connectRespConfig.m_recvMaximum = 1; + unitTestPerformConnect(client, &connectConfig, nullptr, nullptr, nullptr, &connectRespConfig); + + // Checking the PUBLISH is re-sent + TS_ASSERT(!unitTestIsPublishComplete()); + TS_ASSERT(unitTestHasSentMessage()); + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(publishMsg->transportField_flags().field_dup().getBitValue_bit()); + TS_ASSERT(publishMsg->field_packetId().doesExist()); + TS_ASSERT_EQUALS(publishMsg->field_packetId().field().value(), packetId1); + + TS_ASSERT(!unitTestHasSentMessage()); // the other messages sending is delayed + + unitTestTick(1000); + UnitTestPubackMsg pubackMsg; + pubackMsg.field_packetId().setValue(packetId1); + unitTestReceiveMessage(pubackMsg); + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo1 = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo1.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); + + TS_ASSERT(unitTestHasSentMessage()); + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(!publishMsg->transportField_flags().field_dup().getBitValue_bit()); + TS_ASSERT(publishMsg->field_packetId().isMissing()); // QoS0 sent + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo2 = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo2.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); + + // QoS2 is also sent right away + TS_ASSERT(unitTestHasSentMessage()); + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(!publishMsg->transportField_flags().field_dup().getBitValue_bit()); // NO DUP + TS_ASSERT(publishMsg->field_packetId().doesExist()); + auto packetId2 = publishMsg->field_packetId().field().value(); + TS_ASSERT(!unitTestIsPublishComplete()); + + unitTestTick(1000); + UnitTestPubrecMsg pubrecMsg; + pubrecMsg.field_packetId().setValue(packetId2); + unitTestReceiveMessage(pubrecMsg); + + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrel); + auto* pubrelMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrelMsg, nullptr); + TS_ASSERT_EQUALS(pubrelMsg->field_packetId().value(), packetId2); + TS_ASSERT(!unitTestIsPublishComplete()); + + unitTestTick(1000); + UnitTestPubcompMsg pubcompMsg; + pubcompMsg.field_packetId().setValue(packetId2); + unitTestReceiveMessage(pubcompMsg); + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo3 = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo3.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); +} + +void UnitTestPublish::test42() +{ + // Testing limit of the outgoing high Qos publishes with broker disconnect, recomment removes the limit + + auto* client = unitTestAllocClient(); + + auto basicConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&basicConfig); + basicConfig.m_clientId = __FUNCTION__; + basicConfig.m_cleanStart = true; + + UnitTestConnectResponseConfig responseConfig; + responseConfig.m_recvMaximum = 1; + + unitTestPerformConnect(client, &basicConfig, nullptr, nullptr, nullptr, &responseConfig); + TS_ASSERT(unitTestIsConnected(client)); + + const std::string Topic("some/topic"); + const UnitTestData Data = { 0x1, 0x2, 0x3, 0x4, 0x5}; + + auto config = CC_Mqtt5PublishBasicConfig(); + unitTestPublishInitConfigBasic(&config); + + config.m_topic = Topic.c_str(); + config.m_data = &Data[0]; + config.m_dataLen = static_cast(Data.size()); + config.m_qos = CC_Mqtt5QoS_AtLeastOnceDelivery; + + auto* publish1 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish1, nullptr); + + auto ec = unitTestPublishConfigBasic(publish1, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish1); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + auto sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(publishMsg->field_packetId().doesExist()); + auto packetId1 = publishMsg->field_packetId().field().value(); + + TS_ASSERT(!unitTestIsPublishComplete()); + + auto* publish2 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish2, nullptr); + + config.m_qos = CC_Mqtt5QoS_AtMostOnceDelivery; + ec = unitTestPublishConfigBasic(publish2, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish2); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + TS_ASSERT(!unitTestHasSentMessage()); // the sent message sending is delayed + + auto* publish3 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish3, nullptr); + + config.m_qos = CC_Mqtt5QoS_ExactlyOnceDelivery; + ec = unitTestPublishConfigBasic(publish3, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish3); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + unitTestTick(100); + UnitTestDisconnectMsg disconnectMsg; + unitTestReceiveMessage(disconnectMsg); + + TS_ASSERT(unitTestIsDisconnected()); + TS_ASSERT(unitTestHasDisconnectInfo()); + auto& disconnectInfo = unitTestDisconnectInfo(); + TS_ASSERT_EQUALS(disconnectInfo.m_reasonCode, CC_Mqtt5ReasonCode_NormalDisconnection); + unitTestPopDisconnectInfo(); + TS_ASSERT(unitTestCheckNoTicks()); + + TS_ASSERT(!unitTestIsPublishComplete()); + TS_ASSERT(unitTestCheckNoTicks()); + unitTestClearState(); + + // Reconnection with attempt to restore the session + auto connectConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&connectConfig); + + connectConfig.m_clientId = __FUNCTION__; + connectConfig.m_cleanStart = false; + + auto connectRespConfig = UnitTestConnectResponseConfig(); + connectRespConfig.m_sessionPresent = true; + unitTestPerformConnect(client, &connectConfig, nullptr, nullptr, nullptr, &connectRespConfig); + + // Checking the PUBLISH is re-sent + TS_ASSERT(unitTestHasSentMessage()); + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(publishMsg->transportField_flags().field_dup().getBitValue_bit()); + TS_ASSERT(publishMsg->field_packetId().doesExist()); + TS_ASSERT_EQUALS(publishMsg->field_packetId().field().value(), packetId1); + + // Checking other PUBLISH-es are resumed + TS_ASSERT(unitTestHasSentMessage()); + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(!publishMsg->transportField_flags().field_dup().getBitValue_bit()); + TS_ASSERT(publishMsg->field_packetId().isMissing()); // QoS0 sent + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo2 = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo2.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); + + // QoS2 is also sent right away + TS_ASSERT(unitTestHasSentMessage()); + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(!publishMsg->transportField_flags().field_dup().getBitValue_bit()); // NO DUP + TS_ASSERT(publishMsg->field_packetId().doesExist()); + auto packetId2 = publishMsg->field_packetId().field().value(); + TS_ASSERT(!unitTestIsPublishComplete()); + + unitTestTick(1000); + UnitTestPubackMsg pubackMsg; + pubackMsg.field_packetId().setValue(packetId1); + unitTestReceiveMessage(pubackMsg); + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo1 = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo1.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); + + UnitTestPubrecMsg pubrecMsg; + pubrecMsg.field_packetId().setValue(packetId2); + unitTestReceiveMessage(pubrecMsg); + + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrel); + auto* pubrelMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrelMsg, nullptr); + TS_ASSERT_EQUALS(pubrelMsg->field_packetId().value(), packetId2); + TS_ASSERT(!unitTestIsPublishComplete()); + + unitTestTick(1000); + UnitTestPubcompMsg pubcompMsg; + pubcompMsg.field_packetId().setValue(packetId2); + unitTestReceiveMessage(pubcompMsg); + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo3 = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo3.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); +} + +void UnitTestPublish::test43() +{ + // Testing limit of the outgoing high Qos publishes with broker disconnect, recomment increases the limit + + auto* client = unitTestAllocClient(); + + auto basicConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&basicConfig); + basicConfig.m_clientId = __FUNCTION__; + basicConfig.m_cleanStart = true; + + UnitTestConnectResponseConfig responseConfig; + responseConfig.m_recvMaximum = 1; + + unitTestPerformConnect(client, &basicConfig, nullptr, nullptr, nullptr, &responseConfig); + TS_ASSERT(unitTestIsConnected(client)); + + const std::string Topic("some/topic"); + const UnitTestData Data = { 0x1, 0x2, 0x3, 0x4, 0x5}; + + auto config = CC_Mqtt5PublishBasicConfig(); + unitTestPublishInitConfigBasic(&config); + + config.m_topic = Topic.c_str(); + config.m_data = &Data[0]; + config.m_dataLen = static_cast(Data.size()); + config.m_qos = CC_Mqtt5QoS_AtLeastOnceDelivery; + + auto* publish1 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish1, nullptr); + + auto ec = unitTestPublishConfigBasic(publish1, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish1); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + auto sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(publishMsg->field_packetId().doesExist()); + auto packetId1 = publishMsg->field_packetId().field().value(); + + TS_ASSERT(!unitTestIsPublishComplete()); + + auto* publish2 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish2, nullptr); + + config.m_qos = CC_Mqtt5QoS_AtMostOnceDelivery; + ec = unitTestPublishConfigBasic(publish2, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish2); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + TS_ASSERT(!unitTestHasSentMessage()); // the sent message sending is delayed + + auto* publish3 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish3, nullptr); + + config.m_qos = CC_Mqtt5QoS_ExactlyOnceDelivery; + ec = unitTestPublishConfigBasic(publish3, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish3); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + unitTestTick(100); + UnitTestDisconnectMsg disconnectMsg; + unitTestReceiveMessage(disconnectMsg); + + TS_ASSERT(unitTestIsDisconnected()); + TS_ASSERT(unitTestHasDisconnectInfo()); + auto& disconnectInfo = unitTestDisconnectInfo(); + TS_ASSERT_EQUALS(disconnectInfo.m_reasonCode, CC_Mqtt5ReasonCode_NormalDisconnection); + unitTestPopDisconnectInfo(); + TS_ASSERT(unitTestCheckNoTicks()); + + TS_ASSERT(!unitTestIsPublishComplete()); + TS_ASSERT(unitTestCheckNoTicks()); + unitTestClearState(); + + // Reconnection with attempt to restore the session + auto connectConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&connectConfig); + + connectConfig.m_clientId = __FUNCTION__; + connectConfig.m_cleanStart = false; + + auto connectRespConfig = UnitTestConnectResponseConfig(); + connectRespConfig.m_sessionPresent = true; + connectRespConfig.m_recvMaximum = 2; + unitTestPerformConnect(client, &connectConfig, nullptr, nullptr, nullptr, &connectRespConfig); + + // Checking the PUBLISH is re-sent + TS_ASSERT(unitTestHasSentMessage()); + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(publishMsg->transportField_flags().field_dup().getBitValue_bit()); + TS_ASSERT(publishMsg->field_packetId().doesExist()); + TS_ASSERT_EQUALS(publishMsg->field_packetId().field().value(), packetId1); + + // Checking other PUBLISH-es are resumed + TS_ASSERT(unitTestHasSentMessage()); + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(!publishMsg->transportField_flags().field_dup().getBitValue_bit()); + TS_ASSERT(publishMsg->field_packetId().isMissing()); // QoS0 sent + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo2 = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo2.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); + + // QoS2 is also sent right away + TS_ASSERT(unitTestHasSentMessage()); + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(!publishMsg->transportField_flags().field_dup().getBitValue_bit()); // NO DUP + TS_ASSERT(publishMsg->field_packetId().doesExist()); + auto packetId2 = publishMsg->field_packetId().field().value(); + TS_ASSERT(!unitTestIsPublishComplete()); + + unitTestTick(1000); + UnitTestPubackMsg pubackMsg; + pubackMsg.field_packetId().setValue(packetId1); + unitTestReceiveMessage(pubackMsg); + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo1 = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo1.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); + + UnitTestPubrecMsg pubrecMsg; + pubrecMsg.field_packetId().setValue(packetId2); + unitTestReceiveMessage(pubrecMsg); + + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrel); + auto* pubrelMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(pubrelMsg, nullptr); + TS_ASSERT_EQUALS(pubrelMsg->field_packetId().value(), packetId2); + TS_ASSERT(!unitTestIsPublishComplete()); + + unitTestTick(1000); + UnitTestPubcompMsg pubcompMsg; + pubcompMsg.field_packetId().setValue(packetId2); + unitTestReceiveMessage(pubcompMsg); + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo3 = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo3.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); } \ No newline at end of file From e672080761ee12759eada1fd7fa763549b9eaf32 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 2 Apr 2024 13:30:19 +1000 Subject: [PATCH 16/48] Support for check whether publish was initiated. --- client/lib/src/op/SendOp.cpp | 1 + client/lib/src/op/SendOp.h | 6 ++++++ client/lib/templ/client.cpp.templ | 9 +++++++++ client/lib/templ/client.h.templ | 9 +++++++++ client/lib/test/unit/UnitTestBmBase.cpp | 1 + client/lib/test/unit/UnitTestCommonBase.cpp | 6 ++++++ client/lib/test/unit/UnitTestCommonBase.h | 2 ++ client/lib/test/unit/UnitTestDefaultBase.cpp | 1 + client/lib/test/unit/UnitTestPublish.th | 2 ++ 9 files changed, 37 insertions(+) diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index 203a4ae..6d1e054 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -768,6 +768,7 @@ CC_Mqtt5ErrorCode SendOp::doSendInternal() return result; } + m_sent = true; ++m_sendAttempts; if (m_pubMsg.transportField_flags().field_qos().value() == PublishMsg::TransportField_flags::Field_qos::ValueType::AtMostOnceDelivery) { diff --git a/client/lib/src/op/SendOp.h b/client/lib/src/op/SendOp.h index 22cb2e8..cfd4973 100644 --- a/client/lib/src/op/SendOp.h +++ b/client/lib/src/op/SendOp.h @@ -67,6 +67,11 @@ class SendOp final : public Op return m_paused; } + bool isSent() const + { + return m_sent; + } + protected: virtual Type typeImpl() const override; virtual void terminateOpImpl(CC_Mqtt5AsyncOpStatus status) override; @@ -89,6 +94,7 @@ class SendOp final : public Op unsigned m_totalSendAttempts = DefaultSendAttempts; unsigned m_sendAttempts = 0U; CC_Mqtt5ReasonCode m_reasonCode = CC_Mqtt5ReasonCode_Success; + bool m_sent = false; bool m_acked = false; bool m_registeredAlias = false; bool m_topicConfigured = false; diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index 570a52a..6e4eea6 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -935,6 +935,15 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_send(CC_Mqtt5PublishHandle han return sendOpFromHandle(handle)->send(cb, cbData); } +bool cc_mqtt5_##NAME##client_publish_was_initiated(CC_Mqtt5PublishHandle handle) +{ + if (handle == nullptr) { + return false; + } + + return sendOpFromHandle(handle)->isSent(); +} + CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_cancel(CC_Mqtt5PublishHandle handle) { if (handle == nullptr) { diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index 936c9dd..f255970 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -711,6 +711,15 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_add_user_prop(CC_Mqtt5PublishH /// @ingroup publish CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_send(CC_Mqtt5PublishHandle handle, CC_Mqtt5PublishCompleteCb cb, void* cbData); +/// @brief Check whether the "publish" operation was actually initiated (PUBLISH was sent) +/// @details In case the the amount of outgoing publish messages exceeds the "Receive Maximum" limit +/// set by the broker, the requested "publish" operation can be paused. This API +/// call checks whether the @b PUBLISH message was already sent to the broker. +/// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_publish_prepare() function. +/// @return @b true in case @b PUBLISH message has already been sent. +/// @ingroup publish +bool cc_mqtt5_##NAME##client_publish_was_initiated(CC_Mqtt5PublishHandle handle); + /// @brief Cancel the allocated "publish" operation /// @details In case the @ref cc_mqtt5_##NAME##client_publish_send() function was successfully called before, /// the operation is cancelled @b without callback invocation. diff --git a/client/lib/test/unit/UnitTestBmBase.cpp b/client/lib/test/unit/UnitTestBmBase.cpp index 77b0819..f804266 100644 --- a/client/lib/test/unit/UnitTestBmBase.cpp +++ b/client/lib/test/unit/UnitTestBmBase.cpp @@ -84,6 +84,7 @@ const UnitTestBmBase::LibFuncs& UnitTestBmBase::getFuncs() funcs.m_publish_add_user_prop = &cc_mqtt5_bm_client_publish_add_user_prop; funcs.m_publish_send = &cc_mqtt5_bm_client_publish_send; funcs.m_publish_cancel = &cc_mqtt5_bm_client_publish_cancel; + funcs.m_publish_was_initiated = &cc_mqtt5_bm_client_publish_was_initiated; funcs.m_publish_simple = &cc_mqtt5_bm_client_publish_simple; funcs.m_publish_full = &cc_mqtt5_bm_client_publish_full; funcs.m_reauth_prepare = &cc_mqtt5_bm_client_reauth_prepare; diff --git a/client/lib/test/unit/UnitTestCommonBase.cpp b/client/lib/test/unit/UnitTestCommonBase.cpp index f0fd403..144ce31 100644 --- a/client/lib/test/unit/UnitTestCommonBase.cpp +++ b/client/lib/test/unit/UnitTestCommonBase.cpp @@ -119,6 +119,7 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_publish_add_user_prop != nullptr); test_assert(m_funcs.m_publish_send != nullptr); test_assert(m_funcs.m_publish_cancel != nullptr); + test_assert(m_funcs.m_publish_was_initiated != nullptr); test_assert(m_funcs.m_publish_simple != nullptr); test_assert(m_funcs.m_publish_full != nullptr); test_assert(m_funcs.m_reauth_prepare != nullptr); @@ -1117,6 +1118,11 @@ CC_Mqtt5ErrorCode UnitTestCommonBase::unitTestPublishCancel(CC_Mqtt5PublishHandl return m_funcs.m_publish_cancel(handle); } +bool UnitTestCommonBase::unitTestPublishWasInitiated(CC_Mqtt5PublishHandle handle) +{ + return m_funcs.m_publish_was_initiated(handle); +} + CC_Mqtt5ReauthHandle UnitTestCommonBase::unitTestReauthPrepare(CC_Mqtt5Client* client, CC_Mqtt5ErrorCode* ec) { return m_funcs.m_reauth_prepare(client, ec); diff --git a/client/lib/test/unit/UnitTestCommonBase.h b/client/lib/test/unit/UnitTestCommonBase.h index 1cd2379..6042834 100644 --- a/client/lib/test/unit/UnitTestCommonBase.h +++ b/client/lib/test/unit/UnitTestCommonBase.h @@ -98,6 +98,7 @@ class UnitTestCommonBase CC_Mqtt5ErrorCode (*m_publish_add_user_prop)(CC_Mqtt5PublishHandle, const CC_Mqtt5UserProp*) = nullptr; CC_Mqtt5ErrorCode (*m_publish_send)(CC_Mqtt5PublishHandle, CC_Mqtt5PublishCompleteCb, void*) = nullptr; CC_Mqtt5ErrorCode (*m_publish_cancel)(CC_Mqtt5PublishHandle) = nullptr; + bool (*m_publish_was_initiated)(CC_Mqtt5PublishHandle) = nullptr; CC_Mqtt5ErrorCode (*m_publish_simple)(CC_Mqtt5ClientHandle, const CC_Mqtt5PublishBasicConfig*, CC_Mqtt5PublishCompleteCb, void*) = nullptr; CC_Mqtt5ErrorCode (*m_publish_full)(CC_Mqtt5ClientHandle, const CC_Mqtt5PublishBasicConfig*, const CC_Mqtt5PublishExtraConfig*, CC_Mqtt5PublishCompleteCb, void*) = nullptr; CC_Mqtt5ReauthHandle (*m_reauth_prepare)(CC_Mqtt5ClientHandle, CC_Mqtt5ErrorCode*) = nullptr; @@ -481,6 +482,7 @@ class UnitTestCommonBase CC_Mqtt5ErrorCode unitTestPublishConfigExtra(CC_Mqtt5PublishHandle handle, const CC_Mqtt5PublishExtraConfig* config); CC_Mqtt5ErrorCode unitTestPublishAddUserProp(CC_Mqtt5PublishHandle handle, const CC_Mqtt5UserProp* prop); CC_Mqtt5ErrorCode unitTestPublishCancel(CC_Mqtt5PublishHandle handle); + bool unitTestPublishWasInitiated(CC_Mqtt5PublishHandle handle); CC_Mqtt5ReauthHandle unitTestReauthPrepare(CC_Mqtt5Client* client, CC_Mqtt5ErrorCode* ec); void unitTestReauthInitConfigAuth(CC_Mqtt5AuthConfig* config); CC_Mqtt5ErrorCode unitTestReauthAddUserProp(CC_Mqtt5ReauthHandle handle, const CC_Mqtt5UserProp* prop); diff --git a/client/lib/test/unit/UnitTestDefaultBase.cpp b/client/lib/test/unit/UnitTestDefaultBase.cpp index f299298..0248c28 100644 --- a/client/lib/test/unit/UnitTestDefaultBase.cpp +++ b/client/lib/test/unit/UnitTestDefaultBase.cpp @@ -84,6 +84,7 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_publish_add_user_prop = &cc_mqtt5_client_publish_add_user_prop; funcs.m_publish_send = &cc_mqtt5_client_publish_send; funcs.m_publish_cancel = &cc_mqtt5_client_publish_cancel; + funcs.m_publish_was_initiated = &cc_mqtt5_client_publish_was_initiated; funcs.m_publish_simple = &cc_mqtt5_client_publish_simple; funcs.m_publish_full = &cc_mqtt5_client_publish_full; funcs.m_reauth_prepare = &cc_mqtt5_client_reauth_prepare; diff --git a/client/lib/test/unit/UnitTestPublish.th b/client/lib/test/unit/UnitTestPublish.th index 9f54e6d..0c1dad0 100644 --- a/client/lib/test/unit/UnitTestPublish.th +++ b/client/lib/test/unit/UnitTestPublish.th @@ -3500,6 +3500,7 @@ void UnitTestPublish::test43() TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); TS_ASSERT(!unitTestHasSentMessage()); // the sent message sending is delayed + TS_ASSERT(!unitTestPublishWasInitiated(publish2)); auto* publish3 = unitTestPublishPrepare(client, nullptr); TS_ASSERT_DIFFERS(publish3, nullptr); @@ -3510,6 +3511,7 @@ void UnitTestPublish::test43() ec = unitTestSendPublish(publish3); TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + TS_ASSERT(!unitTestPublishWasInitiated(publish3)); unitTestTick(100); UnitTestDisconnectMsg disconnectMsg; From e7249db7f747d27688d8f47d6590265fed0c3e04 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 2 Apr 2024 16:57:41 +1000 Subject: [PATCH 17/48] Allowing publish of QoS0 messages in case "Receive Maximum" is reached and there are no other preceding paused publishes. --- client/lib/src/ClientImpl.cpp | 74 +++++--- client/lib/src/ClientImpl.h | 12 ++ client/lib/src/ClientState.h | 1 + client/lib/src/op/Op.h | 5 + client/lib/src/op/SendOp.cpp | 85 ++++++--- client/lib/src/op/SendOp.h | 5 +- client/lib/test/unit/UnitTestPublish.th | 238 ++++++++++++++++-------- 7 files changed, 289 insertions(+), 131 deletions(-) diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index fe747e2..30f51cc 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -403,11 +403,6 @@ op::SendOp* ClientImpl::publishPrepare(CC_Mqtt5ErrorCode* ec) break; } - COMMS_ASSERT(0U < m_sessionState.m_highQosSendLimit); - if (m_sessionState.m_highQosSendLimit <= m_sendOps.size()) { - ptr->pause(); - } - m_ops.push_back(ptr.get()); m_sendOps.push_back(std::move(ptr)); sendOp = m_sendOps.back().get(); @@ -909,22 +904,9 @@ void ClientImpl::brokerConnected(bool sessionPresent) resumeFromIdx = idx; } - // Resume in-order of creation - while (resumeFromIdx < std::min(std::size_t(m_sessionState.m_highQosSendLimit), m_sendOps.size())) { - auto& sendOpPtr = m_sendOps[resumeFromIdx]; - COMMS_ASSERT(sendOpPtr); - if (!sendOpPtr->isPaused()) { - ++resumeFromIdx; - continue; - } - - sendOpPtr->resume(); - // Do not increament resumeFromIdx right away - // There can be QoS0 ops that can get completed right away - // The QoS0 completion can also trigger the resume of others - // Increment in the next iteration + if (resumeFromIdx < resumeUntilIdx) { + resumeSendOpsSince(static_cast(resumeFromIdx)); } - break; } @@ -976,6 +958,32 @@ void ClientImpl::reportMsgInfo(const CC_Mqtt5MessageInfo& info) 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(); +} + void ClientImpl::doApiEnter() { ++m_apiEnterCount; @@ -1119,6 +1127,23 @@ CC_Mqtt5ErrorCode ClientImpl::initInternal() return CC_Mqtt5ErrorCode_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 + } +} + void ClientImpl::opComplete_Connect(const op::Op* op) { eraseFromList(op, m_connectOps); @@ -1161,13 +1186,8 @@ void ClientImpl::opComplete_Send(const op::Op* op) return; } - auto& opToUnpausePtr = m_sendOps[m_sessionState.m_highQosSendLimit - 1U]; - if (!opToUnpausePtr->isPaused()) { - // Some operations that has been paused can be cancelled, hence not influencing any resume. - return; - } - - opToUnpausePtr->resume(); + auto idx = m_sessionState.m_highQosSendLimit - 1U; + resumeSendOpsSince(idx); } void ClientImpl::opComplete_Reauth(const op::Op* op) diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index 7cd53fe..f1d4cab 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -141,6 +141,7 @@ class ClientImpl final : public ProtMsgHandler CC_Mqtt5AsyncOpStatus status = CC_Mqtt5AsyncOpStatus_BrokerDisconnected, const CC_Mqtt5DisconnectInfo* info = nullptr); void reportMsgInfo(const CC_Mqtt5MessageInfo& info); + bool hasPausedSendsBefore(const op::SendOp* sendOp) const; TimerMgr& timerMgr() { @@ -157,11 +158,21 @@ class ClientImpl final : public ProtMsgHandler 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; @@ -228,6 +239,7 @@ class ClientImpl final : public ProtMsgHandler void errorLogInternal(const char* msg); void sendDisconnectMsg(DisconnectMsg::Field_reasonCode::Field::ValueType reason); CC_Mqtt5ErrorCode initInternal(); + void resumeSendOpsSince(unsigned idx); void opComplete_Connect(const op::Op* op); void opComplete_KeepAlive(const op::Op* op); diff --git a/client/lib/src/ClientState.h b/client/lib/src/ClientState.h index 9656057..fa6ddee 100644 --- a/client/lib/src/ClientState.h +++ b/client/lib/src/ClientState.h @@ -28,6 +28,7 @@ struct ClientState PacketIdsList m_allocatedPacketIds; std::uint16_t m_lastPacketId = 0U; + unsigned m_inFlightSends = 0U; bool m_firstConnect = true; bool m_networkDisconnected = false; diff --git a/client/lib/src/op/Op.h b/client/lib/src/op/Op.h index 25cfd1c..7b868b6 100644 --- a/client/lib/src/op/Op.h +++ b/client/lib/src/op/Op.h @@ -88,6 +88,11 @@ class Op : public ProtMsgHandler return m_client; } + const ClientImpl& client() const + { + return m_client; + } + static void sendDisconnectWithReason(ClientImpl& client, DisconnectReason reason); void sendDisconnectWithReason(DisconnectReason reason); static void terminationWithReasonStatic(ClientImpl& client, DisconnectReason reason); diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index 6d1e054..c013675 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -46,6 +46,9 @@ void SendOp::handle(PubackMsg& msg) m_responseTimer.cancel(); + COMMS_ASSERT(m_sent); + COMMS_ASSERT(0U < client().clientState().m_inFlightSends); + auto terminateOnExit = comms::util::makeScopeGuard( [&cl = client()]() @@ -67,7 +70,7 @@ void SendOp::handle(PubackMsg& msg) responsePtr = nullptr; } reportPubComplete(status, responsePtr); - opComplete(); + opCompleteInternal(); }); if (m_pubMsg.transportField_flags().field_qos().value() != PublishMsg::TransportField_flags::Field_qos::ValueType::AtLeastOnceDelivery) { @@ -127,6 +130,9 @@ void SendOp::handle(PubrecMsg& msg) return; } + COMMS_ASSERT(m_sent); + COMMS_ASSERT(0U < client().clientState().m_inFlightSends); + m_responseTimer.cancel(); auto terminateOnExit = @@ -150,7 +156,7 @@ void SendOp::handle(PubrecMsg& msg) responsePtr = nullptr; } reportPubComplete(status, responsePtr); - opComplete(); + opCompleteInternal(); }); if (m_pubMsg.transportField_flags().field_qos().value() != PublishMsg::TransportField_flags::Field_qos::ValueType::ExactlyOnceDelivery) { @@ -257,7 +263,7 @@ void SendOp::handle(PubcompMsg& msg) responsePtr = nullptr; } reportPubComplete(status, responsePtr); - opComplete(); + opCompleteInternal(); }); if (m_pubMsg.transportField_flags().field_qos().value() != PublishMsg::TransportField_flags::Field_qos::ValueType::ExactlyOnceDelivery) { @@ -575,7 +581,7 @@ CC_Mqtt5ErrorCode SendOp::send(CC_Mqtt5PublishCompleteCb cb, void* cbData) comms::util::makeScopeGuard( [this]() { - opComplete(); + opCompleteInternal(); }); if (!m_responseTimer.isValid()) { @@ -598,7 +604,10 @@ CC_Mqtt5ErrorCode SendOp::send(CC_Mqtt5PublishCompleteCb cb, void* cbData) m_pubMsg.doRefresh(); // Update packetId presence - if (m_paused) { + if (!canSend()) { + COMMS_ASSERT(!m_paused); + m_paused = true; + completeOnExit.release(); // don't complete op yet return CC_Mqtt5ErrorCode_Success; } @@ -615,7 +624,7 @@ CC_Mqtt5ErrorCode SendOp::send(CC_Mqtt5PublishCompleteCb cb, void* cbData) CC_Mqtt5ErrorCode SendOp::cancel() { - opComplete(); + opCompleteInternal(); return CC_Mqtt5ErrorCode_Success; } @@ -631,19 +640,20 @@ void SendOp::postReconnectionResend() resendDupMsg(); } -void SendOp::pause() +bool SendOp::resume() { - COMMS_ASSERT(!m_paused); - m_paused = true; -} + if (!m_paused) { + return false; + } + + if (!canSend()) { + return false; + } -void SendOp::resume() -{ - COMMS_ASSERT(m_paused); m_paused = false; auto ec = doSendInternal(); if (ec == CC_Mqtt5ErrorCode_Success) { - return; + return true; } auto cbStatus = CC_Mqtt5AsyncOpStatus_InternalError; @@ -655,7 +665,8 @@ void SendOp::resume() } reportPubComplete(cbStatus); - opComplete(); + opCompleteInternal(); + return true; } Op::Type SendOp::typeImpl() const @@ -666,7 +677,7 @@ Op::Type SendOp::typeImpl() const void SendOp::terminateOpImpl(CC_Mqtt5AsyncOpStatus status) { reportPubComplete(status); - opComplete(); + opCompleteInternal(); } void SendOp::networkConnectivityChangedImpl() @@ -693,17 +704,18 @@ void SendOp::resendDupMsg() if (m_totalSendAttempts <= m_sendAttempts) { errorLog("Exhauses all attempts to publish message, discarding publish."); reportPubComplete(CC_Mqtt5AsyncOpStatus_Timeout); - opComplete(); + opCompleteInternal(); return; } + COMMS_ASSERT(m_sent); if (!m_acked) { m_pubMsg.transportField_flags().field_dup().setBitValue_bit(true); auto result = client().sendMessage(m_pubMsg); if (result != CC_Mqtt5ErrorCode_Success) { errorLog("Failed to resend PUBLISH message."); reportPubComplete(CC_Mqtt5AsyncOpStatus_InternalError); - opComplete(); + opCompleteInternal(); return; } @@ -719,7 +731,7 @@ void SendOp::resendDupMsg() if (result != CC_Mqtt5ErrorCode_Success) { errorLog("Failed to resend PUBREL message."); reportPubComplete(CC_Mqtt5AsyncOpStatus_InternalError); - opComplete(); + opCompleteInternal(); return; } @@ -768,12 +780,17 @@ CC_Mqtt5ErrorCode SendOp::doSendInternal() return result; } - m_sent = true; + if (!m_sent) { + m_sent = true; + ++client().clientState().m_inFlightSends; + } + + COMMS_ASSERT(0U < client().clientState().m_inFlightSends); ++m_sendAttempts; if (m_pubMsg.transportField_flags().field_qos().value() == PublishMsg::TransportField_flags::Field_qos::ValueType::AtMostOnceDelivery) { reportPubComplete(CC_Mqtt5AsyncOpStatus_Complete); - opComplete(); + opCompleteInternal(); return CC_Mqtt5ErrorCode_Success; } @@ -781,6 +798,32 @@ CC_Mqtt5ErrorCode SendOp::doSendInternal() return CC_Mqtt5ErrorCode_Success; } +bool SendOp::canSend() const +{ + if (client().clientState().m_inFlightSends < client().sessionState().m_highQosSendLimit) { + return true; + } + + using Qos = PublishMsg::TransportField_flags::Field_qos::ValueType; + if ((m_pubMsg.transportField_flags().field_qos().value() == Qos::AtMostOnceDelivery) && + (client().clientState().m_inFlightSends == client().sessionState().m_highQosSendLimit) && + (!client().hasPausedSendsBefore(this))) { + return true; + } + + return false; +} + +void SendOp::opCompleteInternal() +{ + if (m_sent) { + COMMS_ASSERT(0U < client().clientState().m_inFlightSends); + --client().clientState().m_inFlightSends; + } + + opComplete(); +} + void SendOp::recvTimeoutCb(void* data) { asSendOp(data)->responseTimeoutInternal(); diff --git a/client/lib/src/op/SendOp.h b/client/lib/src/op/SendOp.h index cfd4973..6e21540 100644 --- a/client/lib/src/op/SendOp.h +++ b/client/lib/src/op/SendOp.h @@ -60,8 +60,7 @@ class SendOp final : public Op CC_Mqtt5ErrorCode send(CC_Mqtt5PublishCompleteCb cb, void* cbData); CC_Mqtt5ErrorCode cancel(); void postReconnectionResend(); - void pause(); - void resume(); + bool resume(); bool isPaused() const { return m_paused; @@ -84,6 +83,8 @@ class SendOp final : public Op void reportPubComplete(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response = nullptr); void confirmRegisteredAlias(); CC_Mqtt5ErrorCode doSendInternal(); + bool canSend() const; + void opCompleteInternal(); static void recvTimeoutCb(void* data); diff --git a/client/lib/test/unit/UnitTestPublish.th b/client/lib/test/unit/UnitTestPublish.th index 0c1dad0..5e1cb6f 100644 --- a/client/lib/test/unit/UnitTestPublish.th +++ b/client/lib/test/unit/UnitTestPublish.th @@ -2252,7 +2252,19 @@ void UnitTestPublish::test30() ec = unitTestSendPublish(publish2); TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); - TS_ASSERT(!unitTestHasSentMessage()); // the sent message sending is delayed + // QoS0 message is sent right away even when exceeding the "Receive Maximum" set by broker + TS_ASSERT(unitTestHasSentMessage()); + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(publishMsg->field_packetId().isMissing()); // QoS0 sent + + TS_ASSERT(unitTestIsPublishComplete()); + auto* pubInfo = &unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo->m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); auto* publish3 = unitTestPublishPrepare(client, nullptr); TS_ASSERT_DIFFERS(publish3, nullptr); @@ -2265,6 +2277,19 @@ void UnitTestPublish::test30() TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + TS_ASSERT(!unitTestHasSentMessage()); // the sent message sending is delayed + + // Resend the QoS0 message + auto* publish4 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish4, nullptr); + + config.m_qos = CC_Mqtt5QoS_AtMostOnceDelivery; + ec = unitTestPublishConfigBasic(publish4, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish4); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + TS_ASSERT(!unitTestHasSentMessage()); // the sent message sending is delayed unitTestTick(1000); @@ -2273,32 +2298,35 @@ void UnitTestPublish::test30() unitTestReceiveMessage(pubackMsg); TS_ASSERT(unitTestIsPublishComplete()); - auto& pubInfo = unitTestPublishResponseInfo(); - TS_ASSERT_EQUALS(pubInfo.m_status, CC_Mqtt5AsyncOpStatus_Complete); + pubInfo = &unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo->m_status, CC_Mqtt5AsyncOpStatus_Complete); unitTestPopPublishResponseInfo(); + // QoS2 is sent right away TS_ASSERT(unitTestHasSentMessage()); sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); publishMsg = dynamic_cast(sentMsg.get()); TS_ASSERT_DIFFERS(publishMsg, nullptr); - TS_ASSERT(publishMsg->field_packetId().isMissing()); // QoS0 sent - - TS_ASSERT(unitTestIsPublishComplete()); - TS_ASSERT_EQUALS(pubInfo.m_status, CC_Mqtt5AsyncOpStatus_Complete); - unitTestPopPublishResponseInfo(); + TS_ASSERT(publishMsg->field_packetId().doesExist()); + auto packetId2 = publishMsg->field_packetId().field().value(); - // QoS2 is also sent right away + // QoS0 is also sent right away TS_ASSERT(unitTestHasSentMessage()); sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); publishMsg = dynamic_cast(sentMsg.get()); TS_ASSERT_DIFFERS(publishMsg, nullptr); - TS_ASSERT(publishMsg->field_packetId().doesExist()); - auto packetId2 = publishMsg->field_packetId().field().value(); - TS_ASSERT(!unitTestIsPublishComplete()); + TS_ASSERT(publishMsg->field_packetId().isMissing()); // QoS0 sent + + TS_ASSERT(unitTestIsPublishComplete()); + pubInfo = &unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo->m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); + + TS_ASSERT(!unitTestIsPublishComplete()); // QoS2 is not complete yet unitTestTick(1000); UnitTestPubrecMsg pubrecMsg; @@ -2318,8 +2346,9 @@ void UnitTestPublish::test30() pubcompMsg.field_packetId().setValue(packetId2); unitTestReceiveMessage(pubcompMsg); TS_ASSERT(unitTestIsPublishComplete()); - TS_ASSERT_EQUALS(pubInfo.m_status, CC_Mqtt5AsyncOpStatus_Complete); - unitTestPopPublishResponseInfo(); + pubInfo = &unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo->m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); } void UnitTestPublish::test31() @@ -3054,7 +3083,7 @@ void UnitTestPublish::test40() auto* publish2 = unitTestPublishPrepare(client, nullptr); TS_ASSERT_DIFFERS(publish2, nullptr); - config.m_qos = CC_Mqtt5QoS_AtMostOnceDelivery; + config.m_qos = CC_Mqtt5QoS_AtLeastOnceDelivery; ec = unitTestPublishConfigBasic(publish2, &config); TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); @@ -3098,7 +3127,13 @@ void UnitTestPublish::test40() TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); publishMsg = dynamic_cast(sentMsg.get()); TS_ASSERT_DIFFERS(publishMsg, nullptr); - TS_ASSERT(publishMsg->field_packetId().isMissing()); // QoS0 sent + TS_ASSERT(publishMsg->field_packetId().doesExist()); // QoS1 sent + auto packetId2 = publishMsg->field_packetId().field().value(); + + unitTestTick(1000); + UnitTestPubackMsg pubackMsg2; + pubackMsg2.field_packetId().setValue(packetId2); + unitTestReceiveMessage(pubackMsg2); TS_ASSERT(unitTestIsPublishComplete()); TS_ASSERT_EQUALS(pubInfo.m_status, CC_Mqtt5AsyncOpStatus_Complete); @@ -3111,7 +3146,7 @@ void UnitTestPublish::test41() { // Testing limit of the outgoing high Qos publishes with broker disconnect - auto* client = unitTestAllocClient(); + auto* client = unitTestAllocClient(true); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -3157,25 +3192,25 @@ void UnitTestPublish::test41() auto* publish2 = unitTestPublishPrepare(client, nullptr); TS_ASSERT_DIFFERS(publish2, nullptr); - config.m_qos = CC_Mqtt5QoS_AtMostOnceDelivery; + config.m_qos = CC_Mqtt5QoS_ExactlyOnceDelivery; ec = unitTestPublishConfigBasic(publish2, &config); TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); - + ec = unitTestSendPublish(publish2); TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); - TS_ASSERT(!unitTestHasSentMessage()); // the sent message sending is delayed - auto* publish3 = unitTestPublishPrepare(client, nullptr); TS_ASSERT_DIFFERS(publish3, nullptr); - config.m_qos = CC_Mqtt5QoS_ExactlyOnceDelivery; + config.m_qos = CC_Mqtt5QoS_AtMostOnceDelivery; ec = unitTestPublishConfigBasic(publish3, &config); TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); - + ec = unitTestSendPublish(publish3); TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + TS_ASSERT(!unitTestHasSentMessage()); // the sent message sending is delayed + unitTestTick(100); UnitTestDisconnectMsg disconnectMsg; unitTestReceiveMessage(disconnectMsg); @@ -3191,7 +3226,7 @@ void UnitTestPublish::test41() TS_ASSERT(unitTestCheckNoTicks()); unitTestClearState(); - // Reconnection with attempt to restore the session + // Reconnection with attempt to restore the session auto connectConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&connectConfig); @@ -3227,30 +3262,32 @@ void UnitTestPublish::test41() TS_ASSERT_EQUALS(pubInfo1.m_status, CC_Mqtt5AsyncOpStatus_Complete); unitTestPopPublishResponseInfo(); + // QoS2 is sent right away TS_ASSERT(unitTestHasSentMessage()); sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); publishMsg = dynamic_cast(sentMsg.get()); TS_ASSERT_DIFFERS(publishMsg, nullptr); - TS_ASSERT(!publishMsg->transportField_flags().field_dup().getBitValue_bit()); - TS_ASSERT(publishMsg->field_packetId().isMissing()); // QoS0 sent - - TS_ASSERT(unitTestIsPublishComplete()); - auto& pubInfo2 = unitTestPublishResponseInfo(); - TS_ASSERT_EQUALS(pubInfo2.m_status, CC_Mqtt5AsyncOpStatus_Complete); - unitTestPopPublishResponseInfo(); + TS_ASSERT(!publishMsg->transportField_flags().field_dup().getBitValue_bit()); // NO DUP + TS_ASSERT(publishMsg->field_packetId().doesExist()); + auto packetId2 = publishMsg->field_packetId().field().value(); - // QoS2 is also sent right away + // OoS0 is alos sent right away TS_ASSERT(unitTestHasSentMessage()); sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); publishMsg = dynamic_cast(sentMsg.get()); TS_ASSERT_DIFFERS(publishMsg, nullptr); - TS_ASSERT(!publishMsg->transportField_flags().field_dup().getBitValue_bit()); // NO DUP - TS_ASSERT(publishMsg->field_packetId().doesExist()); - auto packetId2 = publishMsg->field_packetId().field().value(); + TS_ASSERT(!publishMsg->transportField_flags().field_dup().getBitValue_bit()); + TS_ASSERT(publishMsg->field_packetId().isMissing()); // QoS0 sent + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo3 = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo3.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); + TS_ASSERT(!unitTestIsPublishComplete()); unitTestTick(1000); @@ -3271,14 +3308,14 @@ void UnitTestPublish::test41() pubcompMsg.field_packetId().setValue(packetId2); unitTestReceiveMessage(pubcompMsg); TS_ASSERT(unitTestIsPublishComplete()); - auto& pubInfo3 = unitTestPublishResponseInfo(); - TS_ASSERT_EQUALS(pubInfo3.m_status, CC_Mqtt5AsyncOpStatus_Complete); + auto& pubInfo2 = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo2.m_status, CC_Mqtt5AsyncOpStatus_Complete); unitTestPopPublishResponseInfo(); } void UnitTestPublish::test42() { - // Testing limit of the outgoing high Qos publishes with broker disconnect, recomment removes the limit + // Testing limit of the outgoing high Qos publishes with broker disconnect, broker removes the limit auto* client = unitTestAllocClient(); @@ -3326,25 +3363,25 @@ void UnitTestPublish::test42() auto* publish2 = unitTestPublishPrepare(client, nullptr); TS_ASSERT_DIFFERS(publish2, nullptr); - config.m_qos = CC_Mqtt5QoS_AtMostOnceDelivery; + config.m_qos = CC_Mqtt5QoS_ExactlyOnceDelivery; ec = unitTestPublishConfigBasic(publish2, &config); TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); - + ec = unitTestSendPublish(publish2); TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); - TS_ASSERT(!unitTestHasSentMessage()); // the sent message sending is delayed - auto* publish3 = unitTestPublishPrepare(client, nullptr); TS_ASSERT_DIFFERS(publish3, nullptr); - config.m_qos = CC_Mqtt5QoS_ExactlyOnceDelivery; + config.m_qos = CC_Mqtt5QoS_AtMostOnceDelivery; ec = unitTestPublishConfigBasic(publish3, &config); TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); - + ec = unitTestSendPublish(publish3); TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + TS_ASSERT(!unitTestHasSentMessage()); // the sent message sending is delayed + unitTestTick(100); UnitTestDisconnectMsg disconnectMsg; unitTestReceiveMessage(disconnectMsg); @@ -3383,31 +3420,31 @@ void UnitTestPublish::test42() TS_ASSERT_EQUALS(publishMsg->field_packetId().field().value(), packetId1); // Checking other PUBLISH-es are resumed + + // QoS2 is also sent right away TS_ASSERT(unitTestHasSentMessage()); sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); publishMsg = dynamic_cast(sentMsg.get()); TS_ASSERT_DIFFERS(publishMsg, nullptr); - TS_ASSERT(!publishMsg->transportField_flags().field_dup().getBitValue_bit()); - TS_ASSERT(publishMsg->field_packetId().isMissing()); // QoS0 sent - - TS_ASSERT(unitTestIsPublishComplete()); - auto& pubInfo2 = unitTestPublishResponseInfo(); - TS_ASSERT_EQUALS(pubInfo2.m_status, CC_Mqtt5AsyncOpStatus_Complete); - unitTestPopPublishResponseInfo(); + TS_ASSERT(!publishMsg->transportField_flags().field_dup().getBitValue_bit()); // NO DUP + TS_ASSERT(publishMsg->field_packetId().doesExist()); + auto packetId2 = publishMsg->field_packetId().field().value(); - // QoS2 is also sent right away TS_ASSERT(unitTestHasSentMessage()); sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); publishMsg = dynamic_cast(sentMsg.get()); TS_ASSERT_DIFFERS(publishMsg, nullptr); - TS_ASSERT(!publishMsg->transportField_flags().field_dup().getBitValue_bit()); // NO DUP - TS_ASSERT(publishMsg->field_packetId().doesExist()); - auto packetId2 = publishMsg->field_packetId().field().value(); - TS_ASSERT(!unitTestIsPublishComplete()); + TS_ASSERT(!publishMsg->transportField_flags().field_dup().getBitValue_bit()); + TS_ASSERT(publishMsg->field_packetId().isMissing()); // QoS0 sent + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo3 = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo3.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); unitTestTick(1000); UnitTestPubackMsg pubackMsg; @@ -3437,16 +3474,16 @@ void UnitTestPublish::test42() unitTestReceiveMessage(pubcompMsg); TS_ASSERT(unitTestIsPublishComplete()); - auto& pubInfo3 = unitTestPublishResponseInfo(); - TS_ASSERT_EQUALS(pubInfo3.m_status, CC_Mqtt5AsyncOpStatus_Complete); + auto& pubInfo2 = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo2.m_status, CC_Mqtt5AsyncOpStatus_Complete); unitTestPopPublishResponseInfo(); } void UnitTestPublish::test43() { - // Testing limit of the outgoing high Qos publishes with broker disconnect, recomment increases the limit + // Testing limit of the outgoing high Qos publishes with broker disconnect, broker increases the limit - auto* client = unitTestAllocClient(); + auto* client = unitTestAllocClient(true); auto basicConfig = CC_Mqtt5ConnectBasicConfig(); unitTestConnectInitConfigBasic(&basicConfig); @@ -3492,27 +3529,40 @@ void UnitTestPublish::test43() auto* publish2 = unitTestPublishPrepare(client, nullptr); TS_ASSERT_DIFFERS(publish2, nullptr); - config.m_qos = CC_Mqtt5QoS_AtMostOnceDelivery; + config.m_qos = CC_Mqtt5QoS_ExactlyOnceDelivery; ec = unitTestPublishConfigBasic(publish2, &config); TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); - + ec = unitTestSendPublish(publish2); TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); - - TS_ASSERT(!unitTestHasSentMessage()); // the sent message sending is delayed TS_ASSERT(!unitTestPublishWasInitiated(publish2)); auto* publish3 = unitTestPublishPrepare(client, nullptr); TS_ASSERT_DIFFERS(publish3, nullptr); - config.m_qos = CC_Mqtt5QoS_ExactlyOnceDelivery; + config.m_qos = CC_Mqtt5QoS_AtMostOnceDelivery; ec = unitTestPublishConfigBasic(publish3, &config); TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); - + ec = unitTestSendPublish(publish3); TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + TS_ASSERT(!unitTestHasSentMessage()); // the sent message sending is delayed TS_ASSERT(!unitTestPublishWasInitiated(publish3)); + auto* publish4 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish4, nullptr); + + config.m_qos = CC_Mqtt5QoS_AtLeastOnceDelivery; + ec = unitTestPublishConfigBasic(publish4, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish4); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + TS_ASSERT(!unitTestHasSentMessage()); // the sent message sending is delayed + TS_ASSERT(!unitTestPublishWasInitiated(publish4)); + unitTestTick(100); UnitTestDisconnectMsg disconnectMsg; unitTestReceiveMessage(disconnectMsg); @@ -3552,31 +3602,36 @@ void UnitTestPublish::test43() TS_ASSERT_EQUALS(publishMsg->field_packetId().field().value(), packetId1); // Checking other PUBLISH-es are resumed + + // QoS2 is sent right away TS_ASSERT(unitTestHasSentMessage()); sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); publishMsg = dynamic_cast(sentMsg.get()); TS_ASSERT_DIFFERS(publishMsg, nullptr); - TS_ASSERT(!publishMsg->transportField_flags().field_dup().getBitValue_bit()); - TS_ASSERT(publishMsg->field_packetId().isMissing()); // QoS0 sent - - TS_ASSERT(unitTestIsPublishComplete()); - auto& pubInfo2 = unitTestPublishResponseInfo(); - TS_ASSERT_EQUALS(pubInfo2.m_status, CC_Mqtt5AsyncOpStatus_Complete); - unitTestPopPublishResponseInfo(); - - // QoS2 is also sent right away + TS_ASSERT(!publishMsg->transportField_flags().field_dup().getBitValue_bit()); // NO DUP + TS_ASSERT_EQUALS(static_cast(publishMsg->transportField_flags().field_qos().value()), CC_Mqtt5QoS_ExactlyOnceDelivery); + TS_ASSERT(publishMsg->field_packetId().doesExist()); + auto packetId2 = publishMsg->field_packetId().field().value(); + + // QoS0 is also sent TS_ASSERT(unitTestHasSentMessage()); sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); publishMsg = dynamic_cast(sentMsg.get()); TS_ASSERT_DIFFERS(publishMsg, nullptr); - TS_ASSERT(!publishMsg->transportField_flags().field_dup().getBitValue_bit()); // NO DUP - TS_ASSERT(publishMsg->field_packetId().doesExist()); - auto packetId2 = publishMsg->field_packetId().field().value(); - TS_ASSERT(!unitTestIsPublishComplete()); + TS_ASSERT(!publishMsg->transportField_flags().field_dup().getBitValue_bit()); + TS_ASSERT(publishMsg->field_packetId().isMissing()); // QoS0 sent + + // QoS0 completion + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo3 = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo3.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); + + TS_ASSERT(!unitTestHasSentMessage()); // No more messages unitTestTick(1000); UnitTestPubackMsg pubackMsg; @@ -3588,6 +3643,18 @@ void UnitTestPublish::test43() TS_ASSERT_EQUALS(pubInfo1.m_status, CC_Mqtt5AsyncOpStatus_Complete); unitTestPopPublishResponseInfo(); + // Last QoS1 message is sent + TS_ASSERT(unitTestHasSentMessage()); + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(!publishMsg->transportField_flags().field_dup().getBitValue_bit()); + TS_ASSERT(publishMsg->field_packetId().doesExist()); + auto packetId4 = publishMsg->field_packetId().field().value(); + TS_ASSERT(!unitTestIsPublishComplete()); + UnitTestPubrecMsg pubrecMsg; pubrecMsg.field_packetId().setValue(packetId2); unitTestReceiveMessage(pubrecMsg); @@ -3600,13 +3667,22 @@ void UnitTestPublish::test43() TS_ASSERT_EQUALS(pubrelMsg->field_packetId().value(), packetId2); TS_ASSERT(!unitTestIsPublishComplete()); + UnitTestPubackMsg pubrecMsg4; + pubrecMsg4.field_packetId().setValue(packetId4); + unitTestReceiveMessage(pubrecMsg4); + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo4 = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo4.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); + unitTestTick(1000); UnitTestPubcompMsg pubcompMsg; pubcompMsg.field_packetId().setValue(packetId2); unitTestReceiveMessage(pubcompMsg); TS_ASSERT(unitTestIsPublishComplete()); - auto& pubInfo3 = unitTestPublishResponseInfo(); - TS_ASSERT_EQUALS(pubInfo3.m_status, CC_Mqtt5AsyncOpStatus_Complete); + auto& pubInfo2 = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo2.m_status, CC_Mqtt5AsyncOpStatus_Complete); unitTestPopPublishResponseInfo(); } \ No newline at end of file From dc4a3fe6d6d27cf53c459544ccc807ed5b8e5951 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 2 Apr 2024 17:47:11 +1000 Subject: [PATCH 18/48] Limit allocation and preparation of one op at at time until send/cancel. --- client/app/common/AppClient.cpp | 3 +- client/lib/doxygen/main.dox | 14 +++ client/lib/include/cc_mqtt5_client/common.h | 31 +++--- client/lib/src/ClientImpl.cpp | 108 ++++++++++++++------ client/lib/src/ClientImpl.h | 2 + client/lib/src/SessionState.h | 2 +- client/lib/src/op/ConnectOp.cpp | 6 ++ client/lib/src/op/DisconnectOp.cpp | 2 + client/lib/src/op/ReauthOp.cpp | 7 ++ client/lib/src/op/SendOp.cpp | 7 ++ client/lib/src/op/SubscribeOp.cpp | 7 ++ client/lib/src/op/UnsubscribeOp.cpp | 7 ++ client/lib/test/unit/UnitTestUnsubscribe.th | 2 +- 13 files changed, 150 insertions(+), 48 deletions(-) diff --git a/client/app/common/AppClient.cpp b/client/app/common/AppClient.cpp index bab5c14..6c8c724 100644 --- a/client/app/common/AppClient.cpp +++ b/client/app/common/AppClient.cpp @@ -164,9 +164,10 @@ std::string AppClient::toString(CC_Mqtt5ErrorCode val) "CC_Mqtt5ErrorCode_BufferOverflow", "CC_Mqtt5ErrorCode_NotSupported", "CC_Mqtt5ErrorCode_RetryLater", - "CC_Mqtt5ErrorCode_Terminating", + "CC_Mqtt5ErrorCode_Disconnecting", "CC_Mqtt5ErrorCode_NetworkDisconnected", "CC_Mqtt5ErrorCode_NotAuthenticated", + "CC_Mqtt5ErrorCode_PreparationLocked", }; static constexpr std::size_t MapSize = std::extent::value; static_assert(MapSize == CC_Mqtt5ErrorCode_ValuesLimit); diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index e16b7f6..37fb003 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -241,6 +241,20 @@ /// 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 prepared. For example: +/// @code +/// CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_Success; +/// CC_Mqtt5ConnectHandle connect = cc_mqtt5_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_Mqtt5SubscribeHandle subscribe = cc_mqtt5_client_subscribe_prepare(...); +/// assert(subscribe == NULL); +/// @endcode +/// /// @section doc_cc_mqtt5_client_response_timeout Default Response Timeout /// After sending any operation request to the broker, the client library has to allow /// some time for the broker to process the request. If it takes too much time, the diff --git a/client/lib/include/cc_mqtt5_client/common.h b/client/lib/include/cc_mqtt5_client/common.h index 1143b14..7ba54a3 100644 --- a/client/lib/include/cc_mqtt5_client/common.h +++ b/client/lib/include/cc_mqtt5_client/common.h @@ -63,21 +63,22 @@ typedef enum /// @ingroup global typedef enum { - CC_Mqtt5ErrorCode_Success, ///< The requested function executed successfully. - CC_Mqtt5ErrorCode_InternalError, ///< Internal library error, please submit bug report - CC_Mqtt5ErrorCode_NotIntitialized, ///< The allocated client hasn't been initialized. - CC_Mqtt5ErrorCode_Busy, ///< The client library is in the middle of previous operation(s), cannot start a new one. - CC_Mqtt5ErrorCode_NotConnected, ///< The client library is not connected to the broker. Returned by operations that require connection to the broker. - CC_Mqtt5ErrorCode_AlreadyConnected, ///< The client library is already connected to the broker, cannot perform connection operation. - CC_Mqtt5ErrorCode_BadParam, ///< Bad parameter is passed to the function. - CC_Mqtt5ErrorCode_InsufficientConfig, ///< The required configuration hasn't been performed. - CC_Mqtt5ErrorCode_OutOfMemory, ///< Memory allocation failed. - CC_Mqtt5ErrorCode_BufferOverflow, ///< Output buffer is too short - CC_Mqtt5ErrorCode_NotSupported, ///< Feature is not supported - CC_Mqtt5ErrorCode_RetryLater, ///< Retry in next event loop iteration. - CC_Mqtt5ErrorCode_Terminating, ///< The client is in "terminating" state, (re)init is required. - CC_Mqtt5ErrorCode_NetworkDisconnected, ///< When network is disconnected issueing new ops is not accepted - CC_Mqtt5ErrorCode_NotAuthenticated, ///< The client not authenticated. + CC_Mqtt5ErrorCode_Success = 0, ///< The requested function executed successfully. + CC_Mqtt5ErrorCode_InternalError = 1, ///< Internal library error, please submit bug report + CC_Mqtt5ErrorCode_NotIntitialized = 2, ///< The allocated client hasn't been initialized. + CC_Mqtt5ErrorCode_Busy = 3, ///< The client library is in the middle of previous operation(s), cannot start a new one. + CC_Mqtt5ErrorCode_NotConnected = 4, ///< The client library is not connected to the broker. Returned by operations that require connection to the broker. + CC_Mqtt5ErrorCode_AlreadyConnected = 5, ///< The client library is already connected to the broker, cannot perform connection operation. + CC_Mqtt5ErrorCode_BadParam = 6, ///< Bad parameter is passed to the function. + CC_Mqtt5ErrorCode_InsufficientConfig = 7, ///< The required configuration hasn't been performed. + CC_Mqtt5ErrorCode_OutOfMemory = 8, ///< Memory allocation failed. + CC_Mqtt5ErrorCode_BufferOverflow = 9, ///< Output buffer is too short + CC_Mqtt5ErrorCode_NotSupported = 10, ///< Feature is not supported + CC_Mqtt5ErrorCode_RetryLater = 11, ///< Retry in next event loop iteration. + CC_Mqtt5ErrorCode_Disconnecting = 12, ///< The client is in "disconnecting" state, (re)connect is required in the next iteration loop. + CC_Mqtt5ErrorCode_NetworkDisconnected = 13, ///< When network is disconnected issueing new ops is not accepted + CC_Mqtt5ErrorCode_NotAuthenticated = 14, ///< The client not authenticated. + CC_Mqtt5ErrorCode_PreparationLocked = 15, ///< Another operation is being prepared, cannot create a new one without performing "send" or "cancel". CC_Mqtt5ErrorCode_ValuesLimit ///< Limit for the values } CC_Mqtt5ErrorCode; diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 30f51cc..572c16a 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -190,9 +190,9 @@ op::ConnectOp* ClientImpl::connectPrepare(CC_Mqtt5ErrorCode* ec) break; } - if (m_sessionState.m_terminating) { - errorLog("Session termination is in progress, cannot initiate connection."); - updateEc(ec, CC_Mqtt5ErrorCode_Terminating); + if (m_sessionState.m_disconnecting) { + errorLog("Session disconnection is in progress, cannot initiate connection."); + updateEc(ec, CC_Mqtt5ErrorCode_Disconnecting); break; } @@ -214,6 +214,12 @@ op::ConnectOp* ClientImpl::connectPrepare(CC_Mqtt5ErrorCode* ec) break; } + if (m_preparationLocked) { + errorLog("Another operation is being prepared, cannot prepare \"connect\" without \"send\" or \"cancel\" of the previous."); + updateEc(ec, CC_Mqtt5ErrorCode_PreparationLocked); + break; + } + auto ptr = m_connectOpAlloc.alloc(*this); if (!ptr) { errorLog("Cannot allocate new connect operation."); @@ -221,6 +227,7 @@ op::ConnectOp* ClientImpl::connectPrepare(CC_Mqtt5ErrorCode* ec) break; } + m_preparationLocked = true; m_ops.push_back(ptr.get()); m_connectOps.push_back(std::move(ptr)); connectOp = m_connectOps.back().get(); @@ -246,9 +253,9 @@ op::DisconnectOp* ClientImpl::disconnectPrepare(CC_Mqtt5ErrorCode* ec) break; } - if (m_sessionState.m_terminating) { - errorLog("Session termination is in progress, cannot initiate disconnection."); - updateEc(ec, CC_Mqtt5ErrorCode_Terminating); + if (m_sessionState.m_disconnecting) { + errorLog("Session disconnection is in progress, cannot initiate disconnection."); + updateEc(ec, CC_Mqtt5ErrorCode_Disconnecting); break; } @@ -262,7 +269,13 @@ op::DisconnectOp* ClientImpl::disconnectPrepare(CC_Mqtt5ErrorCode* ec) errorLog("Cannot start disconnect operation, retry in next event loop iteration."); updateEc(ec, CC_Mqtt5ErrorCode_RetryLater); break; - } + } + + if (m_preparationLocked) { + errorLog("Another operation is being prepared, cannot prepare \"disconnect\" without \"send\" or \"cancel\" of the previous."); + updateEc(ec, CC_Mqtt5ErrorCode_PreparationLocked); + break; + } auto ptr = m_disconnectOpsAlloc.alloc(*this); if (!ptr) { @@ -271,6 +284,7 @@ op::DisconnectOp* ClientImpl::disconnectPrepare(CC_Mqtt5ErrorCode* ec) break; } + m_preparationLocked = true; m_ops.push_back(ptr.get()); m_disconnectOps.push_back(std::move(ptr)); disconnectOp = m_disconnectOps.back().get(); @@ -290,9 +304,9 @@ op::SubscribeOp* ClientImpl::subscribePrepare(CC_Mqtt5ErrorCode* ec) break; } - if (m_sessionState.m_terminating) { - errorLog("Session termination is in progress, cannot initiate subscription."); - updateEc(ec, CC_Mqtt5ErrorCode_Terminating); + if (m_sessionState.m_disconnecting) { + errorLog("Session disconnection is in progress, cannot initiate subscription."); + updateEc(ec, CC_Mqtt5ErrorCode_Disconnecting); break; } @@ -306,7 +320,13 @@ op::SubscribeOp* ClientImpl::subscribePrepare(CC_Mqtt5ErrorCode* ec) errorLog("Cannot start subscribe operation, retry in next event loop iteration."); updateEc(ec, CC_Mqtt5ErrorCode_RetryLater); break; - } + } + + if (m_preparationLocked) { + errorLog("Another operation is being prepared, cannot prepare \"subscribe\" without \"send\" or \"cancel\" of the previous."); + updateEc(ec, CC_Mqtt5ErrorCode_PreparationLocked); + break; + } auto ptr = m_subscribeOpsAlloc.alloc(*this); if (!ptr) { @@ -315,6 +335,7 @@ op::SubscribeOp* ClientImpl::subscribePrepare(CC_Mqtt5ErrorCode* ec) break; } + m_preparationLocked = true; m_ops.push_back(ptr.get()); m_subscribeOps.push_back(std::move(ptr)); subOp = m_subscribeOps.back().get(); @@ -334,9 +355,9 @@ op::UnsubscribeOp* ClientImpl::unsubscribePrepare(CC_Mqtt5ErrorCode* ec) break; } - if (m_sessionState.m_terminating) { - errorLog("Session termination is in progress, cannot initiate unsubscription."); - updateEc(ec, CC_Mqtt5ErrorCode_Terminating); + if (m_sessionState.m_disconnecting) { + errorLog("Session disconnection is in progress, cannot initiate unsubscription."); + updateEc(ec, CC_Mqtt5ErrorCode_Disconnecting); break; } @@ -350,7 +371,13 @@ op::UnsubscribeOp* ClientImpl::unsubscribePrepare(CC_Mqtt5ErrorCode* ec) errorLog("Cannot start subscribe operation, retry in next event loop iteration."); updateEc(ec, CC_Mqtt5ErrorCode_RetryLater); break; - } + } + + if (m_preparationLocked) { + errorLog("Another operation is being prepared, cannot prepare \"unsubscribe\" without \"send\" or \"cancel\" of the previous."); + updateEc(ec, CC_Mqtt5ErrorCode_PreparationLocked); + break; + } auto ptr = m_unsubscribeOpsAlloc.alloc(*this); if (!ptr) { @@ -359,6 +386,7 @@ op::UnsubscribeOp* ClientImpl::unsubscribePrepare(CC_Mqtt5ErrorCode* ec) break; } + m_preparationLocked = true; m_ops.push_back(ptr.get()); m_unsubscribeOps.push_back(std::move(ptr)); unsubOp = m_unsubscribeOps.back().get(); @@ -378,9 +406,9 @@ op::SendOp* ClientImpl::publishPrepare(CC_Mqtt5ErrorCode* ec) break; } - if (m_sessionState.m_terminating) { - errorLog("Session termination is in progress, cannot initiate publish."); - updateEc(ec, CC_Mqtt5ErrorCode_Terminating); + if (m_sessionState.m_disconnecting) { + errorLog("Session disconnection is in progress, cannot initiate publish."); + updateEc(ec, CC_Mqtt5ErrorCode_Disconnecting); break; } @@ -403,6 +431,13 @@ op::SendOp* ClientImpl::publishPrepare(CC_Mqtt5ErrorCode* ec) break; } + if (m_preparationLocked) { + errorLog("Another operation is being prepared, cannot prepare \"unsubscribe\" without \"send\" or \"cancel\" of the previous."); + updateEc(ec, CC_Mqtt5ErrorCode_PreparationLocked); + break; + } + + m_preparationLocked = true; m_ops.push_back(ptr.get()); m_sendOps.push_back(std::move(ptr)); sendOp = m_sendOps.back().get(); @@ -429,9 +464,9 @@ op::ReauthOp* ClientImpl::reauthPrepare(CC_Mqtt5ErrorCode* ec) break; } - if (m_sessionState.m_terminating) { - errorLog("Session termination is in progress, cannot initiate reauth."); - updateEc(ec, CC_Mqtt5ErrorCode_Terminating); + if (m_sessionState.m_disconnecting) { + errorLog("Session disconnection is in progress, cannot initiate reauth."); + updateEc(ec, CC_Mqtt5ErrorCode_Disconnecting); break; } @@ -451,7 +486,13 @@ op::ReauthOp* ClientImpl::reauthPrepare(CC_Mqtt5ErrorCode* ec) errorLog("Cannot start reauth operation, retry in next event loop iteration."); updateEc(ec, CC_Mqtt5ErrorCode_RetryLater); break; - } + } + + if (m_preparationLocked) { + errorLog("Another operation is being prepared, cannot prepare \"reauth\" without \"send\" or \"cancel\" of the previous."); + updateEc(ec, CC_Mqtt5ErrorCode_PreparationLocked); + break; + } auto ptr = m_reauthOpsAlloc.alloc(*this); if (!ptr) { @@ -460,6 +501,7 @@ op::ReauthOp* ClientImpl::reauthPrepare(CC_Mqtt5ErrorCode* ec) break; } + m_preparationLocked = true; m_ops.push_back(ptr.get()); m_reauthOps.push_back(std::move(ptr)); reauthOp = m_reauthOps.back().get(); @@ -482,9 +524,9 @@ CC_Mqtt5ErrorCode ClientImpl::allocPubTopicAlias(const char* topic, unsigned qos return CC_Mqtt5ErrorCode_NotConnected; } - if (m_sessionState.m_terminating) { - errorLog("Session termination is in progress, cannot allocate topic alias."); - return CC_Mqtt5ErrorCode_Terminating; + if (m_sessionState.m_disconnecting) { + errorLog("Session disconnection is in progress, cannot allocate topic alias."); + return CC_Mqtt5ErrorCode_Disconnecting; } if (m_sessionState.m_maxSendTopicAlias <= m_sessionState.m_sendTopicAliases.size()) { @@ -606,7 +648,7 @@ bool ClientImpl::pubTopicAliasIsAllocated(const char* topic) const void ClientImpl::handle(PublishMsg& msg) { - if (m_sessionState.m_terminating) { + if (m_sessionState.m_disconnecting) { return; } @@ -776,7 +818,7 @@ void ClientImpl::handle(PubcompMsg& msg) void ClientImpl::handle(ProtMessage& msg) { - if (m_sessionState.m_terminating) { + if (m_sessionState.m_disconnecting) { return; } @@ -798,7 +840,7 @@ void ClientImpl::handle(ProtMessage& msg) // After message dispatching the whole session may be in terminating state // Don't continue iteration - if (m_sessionState.m_terminating) { + if (m_sessionState.m_disconnecting) { break; } } @@ -984,6 +1026,12 @@ bool ClientImpl::hasPausedSendsBefore(const op::SendOp* sendOp) const return prevSendOpPtr->isPaused(); } +void ClientImpl::allowNextPrepare() +{ + COMMS_ASSERT(m_preparationLocked); + m_preparationLocked = false; +} + void ClientImpl::doApiEnter() { ++m_apiEnterCount; @@ -1040,7 +1088,7 @@ void ClientImpl::createKeepAliveOpIfNeeded() void ClientImpl::terminateOps(CC_Mqtt5AsyncOpStatus status, TerminateMode mode) { - m_sessionState.m_terminating = true; + m_sessionState.m_disconnecting = true; for (auto* op : m_ops) { if (op == nullptr) { continue; @@ -1177,7 +1225,7 @@ void ClientImpl::opComplete_Recv(const op::Op* op) void ClientImpl::opComplete_Send(const op::Op* op) { eraseFromList(op, m_sendOps); - if (m_sessionState.m_terminating) { + if (m_sessionState.m_disconnecting) { return; } diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index f1d4cab..6517c5d 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -142,6 +142,7 @@ class ClientImpl final : public ProtMsgHandler const CC_Mqtt5DisconnectInfo* info = nullptr); void reportMsgInfo(const CC_Mqtt5MessageInfo& info); bool hasPausedSendsBefore(const op::SendOp* sendOp) const; + void allowNextPrepare(); TimerMgr& timerMgr() { @@ -308,6 +309,7 @@ class ClientImpl final : public ProtMsgHandler OpPtrsList m_ops; bool m_opsDeleted = false; + bool m_preparationLocked = false; }; } // namespace cc_mqtt5_client diff --git a/client/lib/src/SessionState.h b/client/lib/src/SessionState.h index 28784f4..3f71d73 100644 --- a/client/lib/src/SessionState.h +++ b/client/lib/src/SessionState.h @@ -43,7 +43,7 @@ struct SessionState bool m_problemInfoAllowed = false; bool m_initialized = false; bool m_connected = false; - bool m_terminating = false; + bool m_disconnecting = false; bool m_wildcardSubAvailable = false; bool m_subIdsAvailable = false; bool m_retainAvailable = false; diff --git a/client/lib/src/op/ConnectOp.cpp b/client/lib/src/op/ConnectOp.cpp index db36bff..fc6e6cc 100644 --- a/client/lib/src/op/ConnectOp.cpp +++ b/client/lib/src/op/ConnectOp.cpp @@ -463,6 +463,7 @@ CC_Mqtt5ErrorCode ConnectOp::addWillUserProp(const CC_Mqtt5UserProp& prop) CC_Mqtt5ErrorCode ConnectOp::send(CC_Mqtt5ConnectCompleteCb cb, void* cbData) { + client().allowNextPrepare(); auto completeOnError = comms::util::makeScopeGuard( [this]() @@ -502,6 +503,11 @@ CC_Mqtt5ErrorCode ConnectOp::send(CC_Mqtt5ConnectCompleteCb cb, void* cbData) CC_Mqtt5ErrorCode ConnectOp::cancel() { + if (m_cb == nullptr) { + // hasn't been sent yet + client().allowNextPrepare(); + } + opComplete(); return CC_Mqtt5ErrorCode_Success; } diff --git a/client/lib/src/op/DisconnectOp.cpp b/client/lib/src/op/DisconnectOp.cpp index 0de1a08..ea4ac15 100644 --- a/client/lib/src/op/DisconnectOp.cpp +++ b/client/lib/src/op/DisconnectOp.cpp @@ -74,6 +74,7 @@ CC_Mqtt5ErrorCode DisconnectOp::addUserProp(const CC_Mqtt5UserProp& prop) CC_Mqtt5ErrorCode DisconnectOp::send() { + client().allowNextPrepare(); if ((m_disconnectMsg.field_reasonCode().field().value() != DisconnectReason::Success) || (!m_disconnectMsg.field_properties().field().value().empty())) { m_disconnectMsg.field_reasonCode().setExists(); @@ -90,6 +91,7 @@ CC_Mqtt5ErrorCode DisconnectOp::send() CC_Mqtt5ErrorCode DisconnectOp::cancel() { + client().allowNextPrepare(); opComplete(); return CC_Mqtt5ErrorCode_Success; } diff --git a/client/lib/src/op/ReauthOp.cpp b/client/lib/src/op/ReauthOp.cpp index 853d3f1..372aac7 100644 --- a/client/lib/src/op/ReauthOp.cpp +++ b/client/lib/src/op/ReauthOp.cpp @@ -111,6 +111,8 @@ CC_Mqtt5ErrorCode ReauthOp::addUserProp(const CC_Mqtt5UserProp& prop) CC_Mqtt5ErrorCode ReauthOp::send(CC_Mqtt5ReauthCompleteCb cb, void* cbData) { + client().allowNextPrepare(); + auto completeOnError = comms::util::makeScopeGuard( [this]() @@ -149,6 +151,11 @@ CC_Mqtt5ErrorCode ReauthOp::send(CC_Mqtt5ReauthCompleteCb cb, void* cbData) CC_Mqtt5ErrorCode ReauthOp::cancel() { + if (m_cb == nullptr) { + // hasn't been sent yet + client().allowNextPrepare(); + } + opComplete(); return CC_Mqtt5ErrorCode_Success; } diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index c013675..278ce81 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -577,6 +577,8 @@ unsigned SendOp::getResendAttempts() const CC_Mqtt5ErrorCode SendOp::send(CC_Mqtt5PublishCompleteCb cb, void* cbData) { + client().allowNextPrepare(); + auto completeOnExit = comms::util::makeScopeGuard( [this]() @@ -624,6 +626,11 @@ CC_Mqtt5ErrorCode SendOp::send(CC_Mqtt5PublishCompleteCb cb, void* cbData) CC_Mqtt5ErrorCode SendOp::cancel() { + if (m_cb == nullptr) { + // hasn't been sent yet + client().allowNextPrepare(); + } + opCompleteInternal(); return CC_Mqtt5ErrorCode_Success; } diff --git a/client/lib/src/op/SubscribeOp.cpp b/client/lib/src/op/SubscribeOp.cpp index 39ee150..3202aee 100644 --- a/client/lib/src/op/SubscribeOp.cpp +++ b/client/lib/src/op/SubscribeOp.cpp @@ -138,6 +138,8 @@ CC_Mqtt5ErrorCode SubscribeOp::addUserProp(const CC_Mqtt5UserProp& prop) CC_Mqtt5ErrorCode SubscribeOp::send(CC_Mqtt5SubscribeCompleteCb cb, void* cbData) { + client().allowNextPrepare(); + auto completeOnError = comms::util::makeScopeGuard( [this]() @@ -177,6 +179,11 @@ CC_Mqtt5ErrorCode SubscribeOp::send(CC_Mqtt5SubscribeCompleteCb cb, void* cbData CC_Mqtt5ErrorCode SubscribeOp::cancel() { + if (m_cb == nullptr) { + // hasn't been sent yet + client().allowNextPrepare(); + } + opComplete(); return CC_Mqtt5ErrorCode_Success; } diff --git a/client/lib/src/op/UnsubscribeOp.cpp b/client/lib/src/op/UnsubscribeOp.cpp index fc62f53..0ae7351 100644 --- a/client/lib/src/op/UnsubscribeOp.cpp +++ b/client/lib/src/op/UnsubscribeOp.cpp @@ -112,6 +112,8 @@ CC_Mqtt5ErrorCode UnsubscribeOp::addUserProp(const CC_Mqtt5UserProp& prop) CC_Mqtt5ErrorCode UnsubscribeOp::send(CC_Mqtt5UnsubscribeCompleteCb cb, void* cbData) { + client().allowNextPrepare(); + auto completeOnError = comms::util::makeScopeGuard( [this]() @@ -151,6 +153,11 @@ CC_Mqtt5ErrorCode UnsubscribeOp::send(CC_Mqtt5UnsubscribeCompleteCb cb, void* cb CC_Mqtt5ErrorCode UnsubscribeOp::cancel() { + if (m_cb == nullptr) { + // hasn't been sent yet + client().allowNextPrepare(); + } + opComplete(); return CC_Mqtt5ErrorCode_Success; } diff --git a/client/lib/test/unit/UnitTestUnsubscribe.th b/client/lib/test/unit/UnitTestUnsubscribe.th index 25e1c3a..b9bae96 100644 --- a/client/lib/test/unit/UnitTestUnsubscribe.th +++ b/client/lib/test/unit/UnitTestUnsubscribe.th @@ -34,7 +34,7 @@ void UnitTestUnsubscribe::test1() { // Simple unsubscribe and ack // [MQTT-3.11.3-1] - auto* client = unitTestAllocClient(); + auto* client = unitTestAllocClient(true); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); From 5740f3b51047ccd53bb04f9025dce52dbef20bbe Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 5 Apr 2024 10:28:50 +1000 Subject: [PATCH 19/48] Supporting retrieval count of the incomplete publishes. --- client/lib/src/op/SendOp.cpp | 41 ++++++++++---------- client/lib/src/op/SendOp.h | 2 +- client/lib/templ/client.cpp.templ | 9 +++++ client/lib/templ/client.h.templ | 6 +++ client/lib/test/unit/UnitTestBmBase.cpp | 1 + client/lib/test/unit/UnitTestCommonBase.cpp | 6 +++ client/lib/test/unit/UnitTestCommonBase.h | 2 + client/lib/test/unit/UnitTestDefaultBase.cpp | 1 + client/lib/test/unit/UnitTestPublish.th | 3 ++ 9 files changed, 49 insertions(+), 22 deletions(-) diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index 278ce81..b845e1a 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -69,8 +69,7 @@ void SendOp::handle(PubackMsg& msg) if (status != CC_Mqtt5AsyncOpStatus_Complete) { responsePtr = nullptr; } - reportPubComplete(status, responsePtr); - opCompleteInternal(); + completeWithCb(status, responsePtr); }); if (m_pubMsg.transportField_flags().field_qos().value() != PublishMsg::TransportField_flags::Field_qos::ValueType::AtLeastOnceDelivery) { @@ -155,8 +154,8 @@ void SendOp::handle(PubrecMsg& msg) if (status != CC_Mqtt5AsyncOpStatus_Complete) { responsePtr = nullptr; } - reportPubComplete(status, responsePtr); - opCompleteInternal(); + + completeWithCb(status, responsePtr); }); if (m_pubMsg.transportField_flags().field_qos().value() != PublishMsg::TransportField_flags::Field_qos::ValueType::ExactlyOnceDelivery) { @@ -262,8 +261,8 @@ void SendOp::handle(PubcompMsg& msg) if (status != CC_Mqtt5AsyncOpStatus_Complete) { responsePtr = nullptr; } - reportPubComplete(status, responsePtr); - opCompleteInternal(); + + completeWithCb(status, responsePtr); }); if (m_pubMsg.transportField_flags().field_qos().value() != PublishMsg::TransportField_flags::Field_qos::ValueType::ExactlyOnceDelivery) { @@ -671,8 +670,7 @@ bool SendOp::resume() cbStatus = CC_Mqtt5AsyncOpStatus_OutOfMemory; } - reportPubComplete(cbStatus); - opCompleteInternal(); + completeWithCb(cbStatus); return true; } @@ -683,8 +681,7 @@ Op::Type SendOp::typeImpl() const void SendOp::terminateOpImpl(CC_Mqtt5AsyncOpStatus status) { - reportPubComplete(status); - opCompleteInternal(); + completeWithCb(status); } void SendOp::networkConnectivityChangedImpl() @@ -710,8 +707,7 @@ void SendOp::resendDupMsg() { if (m_totalSendAttempts <= m_sendAttempts) { errorLog("Exhauses all attempts to publish message, discarding publish."); - reportPubComplete(CC_Mqtt5AsyncOpStatus_Timeout); - opCompleteInternal(); + completeWithCb(CC_Mqtt5AsyncOpStatus_Timeout); return; } @@ -721,8 +717,7 @@ void SendOp::resendDupMsg() auto result = client().sendMessage(m_pubMsg); if (result != CC_Mqtt5ErrorCode_Success) { errorLog("Failed to resend PUBLISH message."); - reportPubComplete(CC_Mqtt5AsyncOpStatus_InternalError); - opCompleteInternal(); + completeWithCb(CC_Mqtt5AsyncOpStatus_InternalError); return; } @@ -737,8 +732,7 @@ void SendOp::resendDupMsg() auto result = client().sendMessage(pubrelMsg); if (result != CC_Mqtt5ErrorCode_Success) { errorLog("Failed to resend PUBREL message."); - reportPubComplete(CC_Mqtt5AsyncOpStatus_InternalError); - opCompleteInternal(); + completeWithCb(CC_Mqtt5AsyncOpStatus_InternalError); return; } @@ -746,13 +740,19 @@ void SendOp::resendDupMsg() restartResponseTimer(); } -void SendOp::reportPubComplete(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response) +void SendOp::completeWithCb(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response) { - if (m_cb == nullptr) { + auto cb = m_cb; + auto cbData = m_cbData; + auto handle = toHandle(); + + opCompleteInternal(); // No member access after this point + + if (cb == nullptr) { return; } - m_cb(m_cbData, toHandle(), status, response); + cb(cbData, handle, status, response); } void SendOp::confirmRegisteredAlias() @@ -796,8 +796,7 @@ CC_Mqtt5ErrorCode SendOp::doSendInternal() ++m_sendAttempts; if (m_pubMsg.transportField_flags().field_qos().value() == PublishMsg::TransportField_flags::Field_qos::ValueType::AtMostOnceDelivery) { - reportPubComplete(CC_Mqtt5AsyncOpStatus_Complete); - opCompleteInternal(); + completeWithCb(CC_Mqtt5AsyncOpStatus_Complete); return CC_Mqtt5ErrorCode_Success; } diff --git a/client/lib/src/op/SendOp.h b/client/lib/src/op/SendOp.h index 6e21540..6e9fe92 100644 --- a/client/lib/src/op/SendOp.h +++ b/client/lib/src/op/SendOp.h @@ -80,7 +80,7 @@ class SendOp final : public Op void restartResponseTimer(); void responseTimeoutInternal(); void resendDupMsg(); - void reportPubComplete(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response = nullptr); + void completeWithCb(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response = nullptr); void confirmRegisteredAlias(); CC_Mqtt5ErrorCode doSendInternal(); bool canSend() const; diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index 6e4eea6..4d7ed6c 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -852,6 +852,15 @@ CC_Mqtt5PublishHandle cc_mqtt5_##NAME##client_publish_prepare(CC_Mqtt5ClientHand return handleFromSendOp(clientFromHandle(handle)->publishPrepare(ec)); } +unsigned cc_mqtt5_##NAME##client_publish_count(CC_Mqtt5ClientHandle handle) +{ + if (handle == nullptr) { + return 0U; + } + + return static_cast(clientFromHandle(handle)->sendsCount()); +} + void cc_mqtt5_##NAME##client_publish_init_config_basic(CC_Mqtt5PublishBasicConfig* config) { *config = CC_Mqtt5PublishBasicConfig(); diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index f255970..43f257d 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -637,6 +637,12 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_unsubscribe_full( /// @ingroup publish CC_Mqtt5PublishHandle cc_mqtt5_##NAME##client_publish_prepare(CC_Mqtt5ClientHandle handle, CC_Mqtt5ErrorCode* ec); +/// @brief Get amount incomplete "publish" operations +/// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_alloc() function. +/// @return Amount of publish operations that have been prepared before, and are still incomplete. +/// @ingroup publish +unsigned cc_mqtt5_##NAME##client_publish_count(CC_Mqtt5ClientHandle handle); + /// @brief Intialize the @ref CC_Mqtt5PublishBasicConfig configuration structure. /// @param[out] config Configuration structure. Must not be NULL. /// @ingroup publish diff --git a/client/lib/test/unit/UnitTestBmBase.cpp b/client/lib/test/unit/UnitTestBmBase.cpp index f804266..6ba1b03 100644 --- a/client/lib/test/unit/UnitTestBmBase.cpp +++ b/client/lib/test/unit/UnitTestBmBase.cpp @@ -73,6 +73,7 @@ const UnitTestBmBase::LibFuncs& UnitTestBmBase::getFuncs() funcs.m_unsubscribe_simple = &cc_mqtt5_bm_client_unsubscribe_simple; funcs.m_unsubscribe_full = &cc_mqtt5_bm_client_unsubscribe_full; funcs.m_publish_prepare = &cc_mqtt5_bm_client_publish_prepare; + funcs.m_publish_count = &cc_mqtt5_bm_client_publish_count; funcs.m_publish_init_config_basic = &cc_mqtt5_bm_client_publish_init_config_basic; funcs.m_publish_init_config_extra = &cc_mqtt5_bm_client_publish_init_config_extra; funcs.m_publish_set_response_timeout = &cc_mqtt5_bm_client_publish_set_response_timeout; diff --git a/client/lib/test/unit/UnitTestCommonBase.cpp b/client/lib/test/unit/UnitTestCommonBase.cpp index 144ce31..a81f432 100644 --- a/client/lib/test/unit/UnitTestCommonBase.cpp +++ b/client/lib/test/unit/UnitTestCommonBase.cpp @@ -108,6 +108,7 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_unsubscribe_simple != nullptr); test_assert(m_funcs.m_unsubscribe_full != nullptr); test_assert(m_funcs.m_publish_prepare != nullptr); + test_assert(m_funcs.m_publish_count != nullptr); test_assert(m_funcs.m_publish_init_config_basic != nullptr); test_assert(m_funcs.m_publish_init_config_extra != nullptr); test_assert(m_funcs.m_publish_set_response_timeout != nullptr); @@ -1083,6 +1084,11 @@ CC_Mqtt5PublishHandle UnitTestCommonBase::unitTestPublishPrepare(CC_Mqtt5Client* return m_funcs.m_publish_prepare(client, ec); } +unsigned UnitTestCommonBase::unitTestPublishCount(CC_Mqtt5Client* client) +{ + return m_funcs.m_publish_count(client); +} + void UnitTestCommonBase::unitTestPublishInitConfigBasic(CC_Mqtt5PublishBasicConfig* config) { return m_funcs.m_publish_init_config_basic(config); diff --git a/client/lib/test/unit/UnitTestCommonBase.h b/client/lib/test/unit/UnitTestCommonBase.h index 6042834..fcc107d 100644 --- a/client/lib/test/unit/UnitTestCommonBase.h +++ b/client/lib/test/unit/UnitTestCommonBase.h @@ -87,6 +87,7 @@ class UnitTestCommonBase CC_Mqtt5ErrorCode (*m_unsubscribe_simple)(CC_Mqtt5ClientHandle, const CC_Mqtt5UnsubscribeTopicConfig*, CC_Mqtt5UnsubscribeCompleteCb, void*) = nullptr; CC_Mqtt5ErrorCode (*m_unsubscribe_full)(CC_Mqtt5ClientHandle, const CC_Mqtt5UnsubscribeTopicConfig*, unsigned, CC_Mqtt5UnsubscribeCompleteCb, void*) = nullptr; CC_Mqtt5PublishHandle (*m_publish_prepare)(CC_Mqtt5ClientHandle, CC_Mqtt5ErrorCode*) = nullptr; + unsigned (*m_publish_count)(CC_Mqtt5ClientHandle) = nullptr; void (*m_publish_init_config_basic)(CC_Mqtt5PublishBasicConfig*) = nullptr; void (*m_publish_init_config_extra)(CC_Mqtt5PublishExtraConfig*) = nullptr; CC_Mqtt5ErrorCode (*m_publish_set_response_timeout)(CC_Mqtt5PublishHandle, unsigned) = nullptr; @@ -475,6 +476,7 @@ class UnitTestCommonBase CC_Mqtt5ErrorCode unitTestUnsubscribeConfigTopic(CC_Mqtt5UnsubscribeHandle handle, const CC_Mqtt5UnsubscribeTopicConfig* config); CC_Mqtt5ErrorCode unitTestUnsubscribeAddUserProp(CC_Mqtt5UnsubscribeHandle handle, const CC_Mqtt5UserProp* prop); CC_Mqtt5PublishHandle unitTestPublishPrepare(CC_Mqtt5Client* client, CC_Mqtt5ErrorCode* ec); + unsigned unitTestPublishCount(CC_Mqtt5Client* client); void unitTestPublishInitConfigBasic(CC_Mqtt5PublishBasicConfig* config); void unitTestPublishInitConfigExtra(CC_Mqtt5PublishExtraConfig* config); CC_Mqtt5ErrorCode unitTestPublishSetResponseTimeout(CC_Mqtt5PublishHandle handle, unsigned ms); diff --git a/client/lib/test/unit/UnitTestDefaultBase.cpp b/client/lib/test/unit/UnitTestDefaultBase.cpp index 0248c28..442968b 100644 --- a/client/lib/test/unit/UnitTestDefaultBase.cpp +++ b/client/lib/test/unit/UnitTestDefaultBase.cpp @@ -73,6 +73,7 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_unsubscribe_simple = &cc_mqtt5_client_unsubscribe_simple; funcs.m_unsubscribe_full = &cc_mqtt5_client_unsubscribe_full; funcs.m_publish_prepare = &cc_mqtt5_client_publish_prepare; + funcs.m_publish_count = &cc_mqtt5_client_publish_count; funcs.m_publish_init_config_basic = &cc_mqtt5_client_publish_init_config_basic; funcs.m_publish_init_config_extra = &cc_mqtt5_client_publish_init_config_extra; funcs.m_publish_set_response_timeout = &cc_mqtt5_client_publish_set_response_timeout; diff --git a/client/lib/test/unit/UnitTestPublish.th b/client/lib/test/unit/UnitTestPublish.th index 5e1cb6f..46ea9f8 100644 --- a/client/lib/test/unit/UnitTestPublish.th +++ b/client/lib/test/unit/UnitTestPublish.th @@ -76,6 +76,7 @@ void UnitTestPublish::test1() auto* publish = unitTestPublishPrepare(client, nullptr); TS_ASSERT_DIFFERS(publish, nullptr); + TS_ASSERT_EQUALS(unitTestPublishCount(client), 1U); const std::string Topic("some/topic"); const UnitTestData Data = { 0x1, 0x2, 0x3, 0x4, 0x5}; @@ -160,6 +161,8 @@ void UnitTestPublish::test1() TS_ASSERT_EQUALS(propsHandler.m_userProps.size(), 1U); TS_ASSERT_EQUALS(propsHandler.m_userProps[0]->field_value().field_first().value(), PubUserPropKey1); TS_ASSERT_EQUALS(propsHandler.m_userProps[0]->field_value().field_second().value(), PubUserPropVal1); + + TS_ASSERT_EQUALS(unitTestPublishCount(client), 0U); } void UnitTestPublish::test2() From 3cad6f1419b7c093c3c561617b1ab89baa3bca69 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 5 Apr 2024 11:03:32 +1000 Subject: [PATCH 20/48] Allow out of order publish of the QoS0 messages. --- client/lib/src/op/SendOp.cpp | 11 +- client/lib/src/op/SendOp.h | 12 ++ client/lib/templ/client.cpp.templ | 24 +++- client/lib/templ/client.h.templ | 24 +++- client/lib/test/unit/UnitTestBmBase.cpp | 2 + client/lib/test/unit/UnitTestCommonBase.cpp | 12 ++ client/lib/test/unit/UnitTestCommonBase.h | 4 + client/lib/test/unit/UnitTestDefaultBase.cpp | 2 + client/lib/test/unit/UnitTestPublish.th | 144 +++++++++++++++++++ 9 files changed, 225 insertions(+), 10 deletions(-) diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index b845e1a..0ebc25b 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -811,8 +811,15 @@ bool SendOp::canSend() const } using Qos = PublishMsg::TransportField_flags::Field_qos::ValueType; - if ((m_pubMsg.transportField_flags().field_qos().value() == Qos::AtMostOnceDelivery) && - (client().clientState().m_inFlightSends == client().sessionState().m_highQosSendLimit) && + if (m_pubMsg.transportField_flags().field_qos().value() > Qos::AtMostOnceDelivery) { + return false; + } + + if (m_outOfOrderAllowed) { + return true; + } + + if ((client().clientState().m_inFlightSends == client().sessionState().m_highQosSendLimit) && (!client().hasPausedSendsBefore(this))) { return true; } diff --git a/client/lib/src/op/SendOp.h b/client/lib/src/op/SendOp.h index 6e9fe92..5c0fb8b 100644 --- a/client/lib/src/op/SendOp.h +++ b/client/lib/src/op/SendOp.h @@ -71,6 +71,17 @@ class SendOp final : public Op return m_sent; } + CC_Mqtt5ErrorCode setOutOfOrderAllowed(bool allowed) + { + m_outOfOrderAllowed = allowed; + return CC_Mqtt5ErrorCode_Success; + } + + bool getOutOfOrderAllowed() const + { + return m_outOfOrderAllowed; + } + protected: virtual Type typeImpl() const override; virtual void terminateOpImpl(CC_Mqtt5AsyncOpStatus status) override; @@ -95,6 +106,7 @@ class SendOp final : public Op unsigned m_totalSendAttempts = DefaultSendAttempts; unsigned m_sendAttempts = 0U; CC_Mqtt5ReasonCode m_reasonCode = CC_Mqtt5ReasonCode_Success; + bool m_outOfOrderAllowed = false; bool m_sent = false; bool m_acked = false; bool m_registeredAlias = false; diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index 4d7ed6c..77ec81f 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -944,6 +944,15 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_send(CC_Mqtt5PublishHandle han return sendOpFromHandle(handle)->send(cb, cbData); } +CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_cancel(CC_Mqtt5PublishHandle handle) +{ + if (handle == nullptr) { + return CC_Mqtt5ErrorCode_BadParam; + } + + return sendOpFromHandle(handle)->cancel(); +} + bool cc_mqtt5_##NAME##client_publish_was_initiated(CC_Mqtt5PublishHandle handle) { if (handle == nullptr) { @@ -953,13 +962,22 @@ bool cc_mqtt5_##NAME##client_publish_was_initiated(CC_Mqtt5PublishHandle handle) return sendOpFromHandle(handle)->isSent(); } -CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_cancel(CC_Mqtt5PublishHandle handle) +CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_set_out_of_order_allowed(CC_Mqtt5PublishHandle handle, bool allowed) { if (handle == nullptr) { return CC_Mqtt5ErrorCode_BadParam; - } + } - return sendOpFromHandle(handle)->cancel(); + return sendOpFromHandle(handle)->setOutOfOrderAllowed(allowed); +} + +bool cc_mqtt5_##NAME##client_publish_get_out_of_order_allowed(CC_Mqtt5PublishHandle handle) +{ + if (handle == nullptr) { + return false; + } + + return sendOpFromHandle(handle)->getOutOfOrderAllowed(); } CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_simple( diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index 43f257d..2a7581e 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -717,6 +717,15 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_add_user_prop(CC_Mqtt5PublishH /// @ingroup publish CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_send(CC_Mqtt5PublishHandle handle, CC_Mqtt5PublishCompleteCb cb, void* cbData); +/// @brief Cancel the allocated "publish" operation +/// @details In case the @ref cc_mqtt5_##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_mqtt5_##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_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_cancel(CC_Mqtt5PublishHandle handle); + /// @brief Check whether the "publish" operation was actually initiated (PUBLISH was sent) /// @details In case the the amount of outgoing publish messages exceeds the "Receive Maximum" limit /// set by the broker, the requested "publish" operation can be paused. This API @@ -726,14 +735,19 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_send(CC_Mqtt5PublishHandle han /// @ingroup publish bool cc_mqtt5_##NAME##client_publish_was_initiated(CC_Mqtt5PublishHandle handle); -/// @brief Cancel the allocated "publish" operation -/// @details In case the @ref cc_mqtt5_##NAME##client_publish_send() function was successfully called before, -/// the operation is cancelled @b without callback invocation. +/// @brief Configure whether sending the prepared @b PUBLISH message can be sent out-of-order. +/// @details Applicable only to the "publish" operations with QoS0. /// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_publish_prepare() function. +/// @param[in] allowed Out-of-order allowed value /// @return Result code of the call. -/// @post The handle of the "publish" operation is no longer valid and must be discarded. /// @ingroup publish -CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_cancel(CC_Mqtt5PublishHandle handle); +CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_set_out_of_order_allowed(CC_Mqtt5PublishHandle handle, bool allowed); + +/// @brief Get current configuration whether the sending the @b PUBLISH message out of order is allowed. +/// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_publish_prepare() function. +/// @return @b true in case out of order send is allowed, @b false otherwise +/// @ingroup publish +bool cc_mqtt5_##NAME##client_publish_get_out_of_order_allowed(CC_Mqtt5PublishHandle handle); /// @brief Prepare, configure, and send "publish" request in one go (simple version) /// @details Abstracts away sequence of the following functions invocation: diff --git a/client/lib/test/unit/UnitTestBmBase.cpp b/client/lib/test/unit/UnitTestBmBase.cpp index 6ba1b03..68618ce 100644 --- a/client/lib/test/unit/UnitTestBmBase.cpp +++ b/client/lib/test/unit/UnitTestBmBase.cpp @@ -86,6 +86,8 @@ const UnitTestBmBase::LibFuncs& UnitTestBmBase::getFuncs() funcs.m_publish_send = &cc_mqtt5_bm_client_publish_send; funcs.m_publish_cancel = &cc_mqtt5_bm_client_publish_cancel; funcs.m_publish_was_initiated = &cc_mqtt5_bm_client_publish_was_initiated; + funcs.m_publish_set_out_of_order_allowed = &cc_mqtt5_bm_client_publish_set_out_of_order_allowed; + funcs.m_publish_get_out_of_order_allowed = &cc_mqtt5_bm_client_publish_get_out_of_order_allowed; funcs.m_publish_simple = &cc_mqtt5_bm_client_publish_simple; funcs.m_publish_full = &cc_mqtt5_bm_client_publish_full; funcs.m_reauth_prepare = &cc_mqtt5_bm_client_reauth_prepare; diff --git a/client/lib/test/unit/UnitTestCommonBase.cpp b/client/lib/test/unit/UnitTestCommonBase.cpp index a81f432..65446c5 100644 --- a/client/lib/test/unit/UnitTestCommonBase.cpp +++ b/client/lib/test/unit/UnitTestCommonBase.cpp @@ -121,6 +121,8 @@ 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_was_initiated != nullptr); + test_assert(m_funcs.m_publish_set_out_of_order_allowed != nullptr); + test_assert(m_funcs.m_publish_get_out_of_order_allowed != nullptr); test_assert(m_funcs.m_publish_simple != nullptr); test_assert(m_funcs.m_publish_full != nullptr); test_assert(m_funcs.m_reauth_prepare != nullptr); @@ -1129,6 +1131,16 @@ bool UnitTestCommonBase::unitTestPublishWasInitiated(CC_Mqtt5PublishHandle handl return m_funcs.m_publish_was_initiated(handle); } +CC_Mqtt5ErrorCode UnitTestCommonBase::unitTestPublishSetOutOfOrderAllowed(CC_Mqtt5PublishHandle handle, bool allowed) +{ + return m_funcs.m_publish_set_out_of_order_allowed(handle, allowed); +} + +bool UnitTestCommonBase::unitTestPublishGetOutOfOrderAllowed(CC_Mqtt5PublishHandle handle) +{ + return m_funcs.m_publish_get_out_of_order_allowed(handle); +} + CC_Mqtt5ReauthHandle UnitTestCommonBase::unitTestReauthPrepare(CC_Mqtt5Client* client, CC_Mqtt5ErrorCode* ec) { return m_funcs.m_reauth_prepare(client, ec); diff --git a/client/lib/test/unit/UnitTestCommonBase.h b/client/lib/test/unit/UnitTestCommonBase.h index fcc107d..f359e46 100644 --- a/client/lib/test/unit/UnitTestCommonBase.h +++ b/client/lib/test/unit/UnitTestCommonBase.h @@ -100,6 +100,8 @@ class UnitTestCommonBase CC_Mqtt5ErrorCode (*m_publish_send)(CC_Mqtt5PublishHandle, CC_Mqtt5PublishCompleteCb, void*) = nullptr; CC_Mqtt5ErrorCode (*m_publish_cancel)(CC_Mqtt5PublishHandle) = nullptr; bool (*m_publish_was_initiated)(CC_Mqtt5PublishHandle) = nullptr; + CC_Mqtt5ErrorCode (*m_publish_set_out_of_order_allowed)(CC_Mqtt5PublishHandle, bool) = nullptr; + bool (*m_publish_get_out_of_order_allowed)(CC_Mqtt5PublishHandle) = nullptr; CC_Mqtt5ErrorCode (*m_publish_simple)(CC_Mqtt5ClientHandle, const CC_Mqtt5PublishBasicConfig*, CC_Mqtt5PublishCompleteCb, void*) = nullptr; CC_Mqtt5ErrorCode (*m_publish_full)(CC_Mqtt5ClientHandle, const CC_Mqtt5PublishBasicConfig*, const CC_Mqtt5PublishExtraConfig*, CC_Mqtt5PublishCompleteCb, void*) = nullptr; CC_Mqtt5ReauthHandle (*m_reauth_prepare)(CC_Mqtt5ClientHandle, CC_Mqtt5ErrorCode*) = nullptr; @@ -485,6 +487,8 @@ class UnitTestCommonBase CC_Mqtt5ErrorCode unitTestPublishAddUserProp(CC_Mqtt5PublishHandle handle, const CC_Mqtt5UserProp* prop); CC_Mqtt5ErrorCode unitTestPublishCancel(CC_Mqtt5PublishHandle handle); bool unitTestPublishWasInitiated(CC_Mqtt5PublishHandle handle); + CC_Mqtt5ErrorCode unitTestPublishSetOutOfOrderAllowed(CC_Mqtt5PublishHandle handle, bool allowed); + bool unitTestPublishGetOutOfOrderAllowed(CC_Mqtt5PublishHandle handle); CC_Mqtt5ReauthHandle unitTestReauthPrepare(CC_Mqtt5Client* client, CC_Mqtt5ErrorCode* ec); void unitTestReauthInitConfigAuth(CC_Mqtt5AuthConfig* config); CC_Mqtt5ErrorCode unitTestReauthAddUserProp(CC_Mqtt5ReauthHandle handle, const CC_Mqtt5UserProp* prop); diff --git a/client/lib/test/unit/UnitTestDefaultBase.cpp b/client/lib/test/unit/UnitTestDefaultBase.cpp index 442968b..e898ebe 100644 --- a/client/lib/test/unit/UnitTestDefaultBase.cpp +++ b/client/lib/test/unit/UnitTestDefaultBase.cpp @@ -86,6 +86,8 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_publish_send = &cc_mqtt5_client_publish_send; funcs.m_publish_cancel = &cc_mqtt5_client_publish_cancel; funcs.m_publish_was_initiated = &cc_mqtt5_client_publish_was_initiated; + funcs.m_publish_set_out_of_order_allowed = &cc_mqtt5_client_publish_set_out_of_order_allowed; + funcs.m_publish_get_out_of_order_allowed = &cc_mqtt5_client_publish_get_out_of_order_allowed; funcs.m_publish_simple = &cc_mqtt5_client_publish_simple; funcs.m_publish_full = &cc_mqtt5_client_publish_full; funcs.m_reauth_prepare = &cc_mqtt5_client_reauth_prepare; diff --git a/client/lib/test/unit/UnitTestPublish.th b/client/lib/test/unit/UnitTestPublish.th index 46ea9f8..b78e27f 100644 --- a/client/lib/test/unit/UnitTestPublish.th +++ b/client/lib/test/unit/UnitTestPublish.th @@ -50,6 +50,7 @@ public: void test41(); void test42(); void test43(); + void test44(); private: virtual void setUp() override @@ -3684,6 +3685,149 @@ void UnitTestPublish::test43() pubcompMsg.field_packetId().setValue(packetId2); unitTestReceiveMessage(pubcompMsg); + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo2 = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo2.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); +} + +void UnitTestPublish::test44() +{ + // Testing out of order publishes of the QoS0 messages + + auto* client = unitTestAllocClient(true); + + auto basicConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&basicConfig); + basicConfig.m_clientId = __FUNCTION__; + basicConfig.m_cleanStart = true; + + UnitTestConnectResponseConfig responseConfig; + responseConfig.m_recvMaximum = 1; + + unitTestPerformConnect(client, &basicConfig, nullptr, nullptr, nullptr, &responseConfig); + TS_ASSERT(unitTestIsConnected(client)); + + const std::string Topic("some/topic"); + const UnitTestData Data = { 0x1, 0x2, 0x3, 0x4, 0x5}; + + auto config = CC_Mqtt5PublishBasicConfig(); + unitTestPublishInitConfigBasic(&config); + + config.m_topic = Topic.c_str(); + config.m_data = &Data[0]; + config.m_dataLen = static_cast(Data.size()); + config.m_qos = CC_Mqtt5QoS_AtLeastOnceDelivery; + + auto* publish1 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish1, nullptr); + TS_ASSERT(!unitTestPublishGetOutOfOrderAllowed(publish1)); + + auto ec = unitTestPublishConfigBasic(publish1, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish1); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + auto sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + TS_ASSERT(publishMsg->field_packetId().doesExist()); + auto packetId1 = publishMsg->field_packetId().field().value(); + + TS_ASSERT(!unitTestIsPublishComplete()); + + auto* publish2 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish2, nullptr); + + ec = unitTestPublishConfigBasic(publish2, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestPublishSetOutOfOrderAllowed(publish2, true); // Don't have an effect + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish2); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + TS_ASSERT(!unitTestPublishWasInitiated(publish2)); + TS_ASSERT(!unitTestHasSentMessage()); + + auto* publish3 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish3, nullptr); + + config.m_qos = CC_Mqtt5QoS_AtMostOnceDelivery; + ec = unitTestPublishConfigBasic(publish3, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish3); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + TS_ASSERT(!unitTestHasSentMessage()); // the sent message sending is delayed + TS_ASSERT(!unitTestPublishWasInitiated(publish3)); + + auto* publish4 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish4, nullptr); + + ec = unitTestPublishConfigBasic(publish4, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestPublishSetOutOfOrderAllowed(publish4, true); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish4); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + TS_ASSERT(unitTestHasSentMessage()); // the sent message sending is delayed + + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + auto* publishMsg4 = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg4, nullptr); + TS_ASSERT(publishMsg4->field_packetId().isMissing()); + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo4 = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo4.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); + + unitTestTick(1000); + UnitTestPubackMsg pubackMsg1; + pubackMsg1.field_packetId().setValue(packetId1); + unitTestReceiveMessage(pubackMsg1); + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo1 = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo1.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); + + TS_ASSERT(unitTestHasSentMessage()); + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + auto* publishMsg2 = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg2, nullptr); + TS_ASSERT(publishMsg2->field_packetId().doesExist()); + auto packetId2 = publishMsg2->field_packetId().field().value(); + + // Qos0 is also sent right away + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + auto* publishMsg3 = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg3, nullptr); + TS_ASSERT(publishMsg3->field_packetId().isMissing()); + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo3 = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo3.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); + + UnitTestPubackMsg pubackMsg2; + pubackMsg2.field_packetId().setValue(packetId2); + unitTestReceiveMessage(pubackMsg2); + TS_ASSERT(unitTestIsPublishComplete()); auto& pubInfo2 = unitTestPublishResponseInfo(); TS_ASSERT_EQUALS(pubInfo2.m_status, CC_Mqtt5AsyncOpStatus_Complete); From b842e548c9b69dcedb0d4bb4b05ed3c4226555dd Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 5 Apr 2024 13:37:57 +1000 Subject: [PATCH 21/48] Documenting exceeding "Receive Maximum" of publishes. --- client/lib/doxygen/main.dox | 65 ++++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index 37fb003..19e2ae5 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -243,7 +243,7 @@ /// /// 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 prepared. For example: +/// any other other operation can be @b prepared. For example: /// @code /// CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_Success; /// CC_Mqtt5ConnectHandle connect = cc_mqtt5_client_connect_prepare(...); @@ -1143,7 +1143,7 @@ /// cc_mqtt5_client_publish_init_config_basic(&basicConfig); /// /// // Update the required values -/// basicConfig.m_topic = "some_topic"; +/// basicConfig.m_topic = "some/topic"; /// basicConfig.m_data = &some_buf[0]; /// basicConfig.m_dataLen = ...; /// basicConfig.m_qos = ...; @@ -1290,6 +1290,61 @@ /// called before the @b cc_mqtt5_client_publish_cancel(), the operation is /// cancelled @b without callback invocation. /// +/// @subsection doc_cc_mqtt5_client_publish_recv_max Exceeding "Receive Maximum" Set by the Broker +/// When the @ref doc_cc_mqtt5_client_connect "connect" operation is complete, the +/// "Receive Maximum" property set by the broker is reported via the +/// @ref CC_Mqtt5ConnectResponse::m_highQosSendLimit value. The application can +/// monitor amount of incomplete "publish" operations using the +/// @b cc_mqtt5_client_publish_count() function and decide whether to issue +/// any new publishes. +/// @code +/// unsigned count = cc_mqtt5_client_publish_count(client); +/// if (count >= max_allowed) { +/// ... +/// } +/// @endcode +/// However, when the "Receive Maximum" limit is reached and a new @b QoS1 +/// or @b QoS2 publish is issued by the application, its send will be paused until one of the +/// previous "publish" operations is complete. It is possible to inquire whether +/// the operation was initiated (@b PUBLISH message is sent) via the +/// @b cc_mqtt5_client_publish_was_initiated() function. If the function returns +/// @b false, the "publish" operation can be safely @ref doc_cc_mqtt5_client_publish_cancel "cancelled" +/// without any possible side effects. +/// @code +/// if (!cc_mqtt5_client_publish_was_initiated(publish)) { +/// cc_mqtt5_client_publish_cancel(publish); +/// } +/// @endcode +/// When it comes to publishing the @b QoS0 messages after the "Receive Maximum" limit +/// is reached the library checks whether there are other previous "publish" operations +/// which are @b paused. If the requested @b QoS0 "publish" is the only one to exceed +/// the "Receive Maximum" limit, it is sent right away because @b QoS0 messages +/// do not require acknowledgement and are not influenced by the "Receive Maximum" limit. +/// However, when there are previously issued @b QoS1 / @b QoS2 publish messages +/// which are paused (due to exceeding the "Receive Maximum" limit), the issued +/// @b QoS0 "publish" operation is also paused until all other previously issued "publish"-es +/// send their first @b PUBLISH message. It is done to ensure in-order send of +/// the messages regardless of their QoS value. +/// +/// The MQTT v5 specification allows out-of-order reception of the messages with different +/// QoS values. The library provides means to allow out-of-order sending of the @b QoS0 +/// messages, send of which could have been delayed in the default operation mode described above. +/// To allow the immediate send of the @b QoS0 message (even if there other paused publishes +/// that haven't sent their @b PUBLISH message yet) use @b cc_mqtt5_client_publish_set_out_of_order_allowed() +/// function to override the default behaviour. +/// @code +/// ec = cc_mqtt5_client_publish_set_out_of_order_allowed(publish, true); +/// if (ec != CC_Mqtt5ErrorCode_Success) { +/// printf("ERROR: Out of order configuration failed with ec=%d\n", ec); +/// ... +/// } +/// @endcode +/// Note that the out-of-order configuration set by the @b cc_mqtt5_client_publish_set_out_of_order_allowed() +/// function applicable only to @b QoS0 messages and doesn't have any effect for +/// the @b QoS1 and @b QoS2 ones. +/// +/// To retrieve the current configuration use @b cc_mqtt5_client_publish_get_out_of_order_allowed(). +/// /// @subsection doc_cc_mqtt5_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, @@ -1327,7 +1382,7 @@ /// using topic aliases, they need to be allocated (registered) using the /// @b cc_mqtt5_client_pub_topic_alias_alloc() function. /// @code -/// CC_Mqtt5ErrorCode ec = cc_mqtt5_client_pub_topic_alias_alloc(client, "some_topic", 2 /* qos0RegsCount */); +/// CC_Mqtt5ErrorCode ec = cc_mqtt5_client_pub_topic_alias_alloc(client, "some/topic", 2 /* qos0RegsCount */); /// @endcode /// Note that the broker reports maximum amount of allowed topic aliases /// in its response to the "connect" request via the @ref CC_Mqtt5ConnectResponse::m_topicAliasMax @@ -1368,7 +1423,7 @@ /// To stop using topic alias for the specific topic, use @b cc_mqtt5_client_pub_topic_alias_free() /// function to free the allocated alias. /// @code -/// CC_Mqtt5ErrorCode ec = cc_mqtt5_client_pub_topic_alias_free(client, "some_topic"); +/// CC_Mqtt5ErrorCode ec = cc_mqtt5_client_pub_topic_alias_free(client, "some/topic"); /// @endcode /// /// To inquire how many topic aliases have already been allocated, use the @@ -1380,7 +1435,7 @@ /// To check whether a topic alias is allocated for specific topic use /// the @b cc_mqtt5_client_pub_topic_alias_is_allocated() function. /// @code -/// bool allocated = cc_mqtt5_client_pub_topic_alias_is_allocated(client, "some_topic"); +/// bool allocated = cc_mqtt5_client_pub_topic_alias_is_allocated(client, "some/topic"); /// @endcode /// /// Once the topic alias is successfully allocated, the @ref doc_cc_mqtt5_client_publish_basic "basic" From 88d640d9f026e3093fd0d76fe765c0725d09bf90 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 5 Apr 2024 17:18:08 +1000 Subject: [PATCH 22/48] Preserving "Session State" only when "Session Expiry Interval" is set. --- client/afl_fuzz/AflFuzz.cpp.templ | 4 +- client/app/common/AppClient.cpp | 5 +- client/app/common/Session.cpp | 8 +- client/app/common/Session.h | 4 +- client/app/common/TcpSession.cpp | 28 +-- client/app/common/TcpSession.h | 1 - client/lib/doxygen/main.dox | 74 +++---- client/lib/spec_compliance.md | 4 +- client/lib/src/ClientImpl.cpp | 85 +++++--- client/lib/src/ClientImpl.h | 7 +- client/lib/src/ExtConfig.h | 5 +- client/lib/src/op/ConnectOp.cpp | 2 +- client/lib/src/op/ConnectOp.h | 2 +- client/lib/src/op/KeepAliveOp.cpp | 41 +--- client/lib/src/op/KeepAliveOp.h | 6 +- client/lib/src/op/Op.cpp | 2 +- client/lib/src/op/Op.h | 6 +- client/lib/src/op/RecvOp.cpp | 4 +- client/lib/src/op/RecvOp.h | 2 +- client/lib/src/op/SendOp.cpp | 2 +- client/lib/src/op/SendOp.h | 2 +- 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 | 4 +- client/lib/templ/client.h.templ | 9 +- client/lib/test/unit/UnitTestCommonBase.cpp | 9 +- client/lib/test/unit/UnitTestCommonBase.h | 5 +- client/lib/test/unit/UnitTestConnect.th | 34 +--- client/lib/test/unit/UnitTestPublish.th | 202 +++++++++++--------- client/lib/test/unit/UnitTestReceive.th | 55 +++--- client/lib/test/unit/UnitTestSubscribe.th | 24 +-- client/lib/test/unit/UnitTestUnsubscribe.th | 25 +-- 34 files changed, 297 insertions(+), 376 deletions(-) diff --git a/client/afl_fuzz/AflFuzz.cpp.templ b/client/afl_fuzz/AflFuzz.cpp.templ index 43244fd..8ed6ad1 100644 --- a/client/afl_fuzz/AflFuzz.cpp.templ +++ b/client/afl_fuzz/AflFuzz.cpp.templ @@ -160,7 +160,7 @@ public: if (!cc_mqtt5_##NAME##client_is_network_disconnected(m_client.get())) { infoLog() << "Insufficient data in buffer (" << dataLen << "), reporting network disconnected..." << std::endl; - cc_mqtt5_##NAME##client_notify_network_disconnected(m_client.get(), true); + cc_mqtt5_##NAME##client_notify_network_disconnected(m_client.get()); continue; } @@ -192,7 +192,7 @@ public: if ((consumed == 0U) && noMoreRead) { if (!cc_mqtt5_##NAME##client_is_network_disconnected(m_client.get())) { infoLog() << "No data consumed and no more input, reporting network disconnected..." << std::endl; - cc_mqtt5_##NAME##client_notify_network_disconnected(m_client.get(), true); + cc_mqtt5_##NAME##client_notify_network_disconnected(m_client.get()); continue; } diff --git a/client/app/common/AppClient.cpp b/client/app/common/AppClient.cpp index 6c8c724..73f411d 100644 --- a/client/app/common/AppClient.cpp +++ b/client/app/common/AppClient.cpp @@ -750,10 +750,11 @@ bool AppClient::createSession() }); m_session->setNetworkDisconnectedReportCb( - [this](bool disconnected) + [this]() { assert(m_client); - ::cc_mqtt5_client_notify_network_disconnected(m_client.get(), disconnected); + ::cc_mqtt5_client_notify_network_disconnected(m_client.get()); + brokerDisconnectedImpl(nullptr); } ); diff --git a/client/app/common/Session.cpp b/client/app/common/Session.cpp index fdb29ec..2d7ac11 100644 --- a/client/app/common/Session.cpp +++ b/client/app/common/Session.cpp @@ -50,14 +50,16 @@ unsigned Session::reportData(const std::uint8_t* buf, std::size_t bufLen) return m_dataReportCb(buf, bufLen); } -void Session::reportNetworkDisconnected(bool disconnected) +void Session::reportNetworkDisconnected() { - if (disconnected == m_networkDisconnected) { + if (m_networkDisconnected) { return; } + + m_networkDisconnected = true; assert(m_networkDisconnectedReportCb); - m_networkDisconnectedReportCb(disconnected); + m_networkDisconnectedReportCb(); } } // namespace cc_mqtt5_client_app diff --git a/client/app/common/Session.h b/client/app/common/Session.h index 3a5b7f4..81fb78b 100644 --- a/client/app/common/Session.h +++ b/client/app/common/Session.h @@ -43,7 +43,7 @@ class Session m_dataReportCb = std::forward(func); } - using NetworkDisconnectedReportCb = std::function; + using NetworkDisconnectedReportCb = std::function; template void setNetworkDisconnectedReportCb(TFunc&& func) { @@ -67,7 +67,7 @@ class Session static std::ostream& logError(); unsigned reportData(const std::uint8_t* buf, std::size_t bufLen); - void reportNetworkDisconnected(bool disconnected); + void reportNetworkDisconnected(); virtual bool startImpl() = 0; virtual void sendDataImpl(const std::uint8_t* buf, std::size_t bufLen) = 0; diff --git a/client/app/common/TcpSession.cpp b/client/app/common/TcpSession.cpp index 4deb8f2..f0fd939 100644 --- a/client/app/common/TcpSession.cpp +++ b/client/app/common/TcpSession.cpp @@ -60,7 +60,7 @@ void TcpSession::sendDataImpl(const std::uint8_t* buf, std::size_t bufLen) return; } while (false); - reportNetworkDisconnected(true); + reportNetworkDisconnected(); } void TcpSession::doRead() @@ -75,13 +75,10 @@ void TcpSession::doRead() if (ec) { logError() << "Failed to read data: " << ec.message(); - reportNetworkDisconnected(true); - doReadLater(); + reportNetworkDisconnected(); return; } - reportNetworkDisconnected(false); - auto buf = &m_inData[0]; auto bufLen = bytesCount; if (!m_buf.empty()) { @@ -112,25 +109,4 @@ void TcpSession::doRead() ); } -void TcpSession::doReadLater() -{ - m_readTimer.expires_after(std::chrono::seconds(1U)); - m_readTimer.async_wait( - [this](const boost::system::error_code& ec) - { - if (ec == boost::asio::error::operation_aborted) { - return; - } - - if (ec) { - logError() << "Timer error: " << ec.message(); - io().stop(); - return; - } - - doRead(); - } - ); -} - } // namespace cc_mqtt5_client_app diff --git a/client/app/common/TcpSession.h b/client/app/common/TcpSession.h index 821cc4f..5bcf61b 100644 --- a/client/app/common/TcpSession.h +++ b/client/app/common/TcpSession.h @@ -40,7 +40,6 @@ class TcpSession final : public Session } void doRead(); - void doReadLater(); Socket m_socket; Timer m_readTimer; diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index 19e2ae5..801b059 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -1683,18 +1683,14 @@ /// When broker disconnection is detected all the incomplete asynchronous operations /// (except @ref doc_cc_mqtt5_client_publish "publish") will be terminated with the an appropriate /// @ref CC_Mqtt5AsyncOpStatus "status" report. The incomplete @ref doc_cc_mqtt5_client_publish "publish" -/// operations will be preserved due to the following spec clause: +/// operations may be preserved (depending on "Session Expiry Interval" configuration +/// during the @ref doc_cc_mqtt5_client_connect "connect" operation) due to the following spec clause: /// @code /// When a Client reconnects with Clean Start set to 0 and a session is present, both the Client and Server /// MUST resend any unacknowledged PUBLISH packets (where QoS > 0) and PUBREL packets using their /// original Packet Identifiers. This is the only circumstance where a Client or Server is REQUIRED to resend /// messages. Clients and Servers MUST NOT resend messages at any other time [MQTT-4.4.0-1]. /// @endcode -/// The incomplete @ref doc_cc_mqtt5_client_publish "publish" operation as well as reception of the -/// incoming message need to be preserved between the sessions. In case the broker reports -/// a new (clean) session after the reconnection, the callbacks of all the incomplete -/// @ref doc_cc_mqtt5_client_publish "publish" operations will be invoked with the -/// @ref CC_Mqtt5AsyncOpStatus_Aborted status. /// /// The unsolicited broker disconnection can happen in one of the following scenarios: /// @@ -1744,58 +1740,48 @@ /// @li Re-establish network connection to the broker. /// @li Perform the @ref doc_cc_mqtt5_client_connect "\"connect\"" operation from within a next event loop iteration. /// +/// In case the "Session Expiry Interval" was set during the @ref doc_cc_mqtt5_client_connect "connect" +/// operation, the library may still require timer measurement, depending on +/// whether there are incomplete @ref doc_cc_mqtt5_client_publish "publish" operations +/// and/or incomplete @ref doc_cc_mqtt5_client_receive "message reception" that +/// need to be preserved for some time until the session expires or client re-connects +/// to the broker. In case the "Session Expiry Interval" is not set or when the +/// session is actually expires (based on the reported @ref doc_cc_mqtt5_client_time "ticks"), then all the +/// incomplete @ref doc_cc_mqtt5_client_publish "publish" operations will also get terminated +/// with the @ref CC_Mqtt5AsyncOpStatus_BrokerDisconnected status and the +/// incomplete @ref doc_cc_mqtt5_client_receive "message reception" will be discarded. +/// +/// In case the client re-connects to the broker before the "Session Expiry Interval" expires +/// and the broker reports "clean session", then all the incomplete @ref doc_cc_mqtt5_client_publish "publish" operations +/// will be terminated with the @ref CC_Mqtt5AsyncOpStatus_Aborted status. +/// /// @section doc_cc_mqtt5_client_network_disconnect Network Disconnection -/// The MQTT v5 specification also tries to support intermittent network connection by -/// setting the "Session Expiry Interval" property during the @ref doc_cc_mqtt5_client_connect "\"connect\"" -/// operation (see @ref CC_Mqtt5ConnectExtraConfig::m_sessionExpiryInterval). Such +/// In addition to the @ref doc_cc_mqtt5_client_unsolicited_disconnect the network +/// connection can be suddenly terminated. Such /// network disconnection is usually detected by the failing @b read or @b write /// operations on I/O socket. When the application detects such network disconnection, it /// is expected to report it to the library using @b cc_mqtt5_client_notify_network_disconnected() /// function. /// @code /// // Report network disconnected -/// cc_mqtt5_client_notify_network_disconnected(client, true); -/// @endcode -/// When network disconnection is reported some internal timers such as waiting for -/// QoS1/QoS2 publish responses get suspended for the period of configured "Session Expiry Interval". -/// -/// If the application manages to re-establish connection by repeating failing @b read / @b write -/// operations for several times until they succeed, then the application is expected to report -/// the network re-connection by invoking the same @b cc_mqtt5_client_notify_network_disconnected() function. -/// @code -/// // Report network reconnected -/// cc_mqtt5_client_notify_network_disconnected(client, false); +/// cc_mqtt5_client_notify_network_disconnected(client); /// @endcode -/// After such network re-connection is reported, the library will resume its timers and continue -/// functioning as usual (as if network disconnection hasn't happened). -/// -/// However, if the network re-connection is not reported until the "Session Expiry Interval" is over, -/// then the library will proceed to the @ref doc_cc_mqtt5_client_unsolicited_disconnect logic. -/// -/// @b Note that if the "Session Expiry Interval" was not configured during the -/// @ref doc_cc_mqtt5_client_connect "\"connect\"" operation, invocation of the -/// @b cc_mqtt5_client_notify_network_disconnected() will result in the -/// @ref doc_cc_mqtt5_client_unsolicited_disconnect report right away. +/// When the network disconnection is reported, the similar workflow to one described +/// in the @ref doc_cc_mqtt5_client_unsolicited_disconnect section above is +/// performed, @b excluding the invocation of the +/// @ref doc_cc_mqtt5_client_callbacks_broker_disconnect "broker disconnection report callback". +/// When the incomplete operations get terminated the @ref CC_Mqtt5AsyncOpStatus_BrokerDisconnected status +/// is reported. /// /// Inquiry about current network disconnection status can be done using the /// @b cc_mqtt5_client_is_network_disconnected() function. /// @code /// bool disconnected = cc_mqtt5_client_is_network_disconnected(client); /// @endcode -/// -/// Also @b note that after the broker disconnection report, attempt to re-connect -/// (invocation @b cc_mqtt5_client_connect_prepare()) will be rejected -/// with the @ref CC_Mqtt5ErrorCode_NetworkDisconnected error code until the network -/// re-connection reported to the library: -/// @code -/// cc_mqtt5_client_notify_network_disconnected(client, false); -/// CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_Success; -/// CC_Mqtt5ConnectHandle connect = cc_mqtt5_client_connect_prepare(client, &ec); -/// @endcode -/// -/// When new client is @ref doc_cc_mqtt5_client_allocation "allocated", its inner state -/// assumes that the network is connected, i.e. invocation of the -/// @b cc_mqtt5_client_is_network_disconnected() will return @b false. +/// Note that when the new "connect" op is @ref doc_cc_mqtt5_client_connect_prepare "prepared", +/// the library will assume that the network connection is re-established and +/// the subsequent call to the @b cc_mqtt5_client_is_network_disconnected() function +/// will return @b false. /// /// @section doc_cc_mqtt5_client_thread_safety Thread Safety /// In general the library is @b NOT thread safe. However, if each thread operates on a separate allocated diff --git a/client/lib/spec_compliance.md b/client/lib/spec_compliance.md index f23096e..80186b5 100644 --- a/client/lib/spec_compliance.md +++ b/client/lib/spec_compliance.md @@ -130,8 +130,8 @@ Session Expiry Interval is greater than 0 * Implemented by allowing notification of the network disconnection. * When network is disconnected during connection attempt, the operation is immediately aborted. Tested in UnitTestConnect::test10. - * Suspending keep alive ping for session expiry period is tested in UnitTestConnect::test11. - * Suspending subscribe operation for session expiry period is tested in UnitTestSubscribe::test5. + * Canceling keep alive is tested in UnitTestConnect::test11. + * Canceling subscribe operation for session expiry period is tested in UnitTestSubscribe::test5. * Suspending unsubscribe operation for session expiry period is tested in UnitTestUnsubscribe::test5. * Suspending publish operation for session expiry period is tested in UnitTestPublish::test14 and UnitTestPublish::test15. * Suspending message reception for session expiry period is tested in UnitTestReceive::test6 and UnitTestReceive::test7. diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 572c16a..ece0356 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -49,6 +49,12 @@ void updateEc(CC_Mqtt5ErrorCode* ec, CC_Mqtt5ErrorCode val) } // namespace +ClientImpl::ClientImpl() : + m_sessionExpiryTimer(m_timerMgr.allocTimer()) +{ + COMMS_ASSERT(m_sessionExpiryTimer.isValid()); +} + ClientImpl::~ClientImpl() { COMMS_ASSERT(m_apiEnterCount == 0U); @@ -142,22 +148,11 @@ unsigned ClientImpl::processData(const std::uint8_t* iter, unsigned len) return consumed; } -void ClientImpl::notifyNetworkDisconnected(bool disconnected) +void ClientImpl::notifyNetworkDisconnected() { auto guard = apiEnter(); - m_clientState.m_networkDisconnected = disconnected; - if (disconnected) { - for (auto& aliasInfo : m_sessionState.m_sendTopicAliases) { - aliasInfo.m_lowQosRegRemCount = aliasInfo.m_lowQosRegCountRequest; - aliasInfo.m_highQosRegRemCount = TopicAliasInfo::DefaultHighQosRegRemCount; - } - } - - for (auto* op : m_ops) { - if (op != nullptr) { - op->networkConnectivityChanged(); - } - } + m_clientState.m_networkDisconnected = true; + brokerDisconnected(false); } bool ClientImpl::isNetworkDisconnected() const @@ -169,6 +164,8 @@ op::ConnectOp* ClientImpl::connectPrepare(CC_Mqtt5ErrorCode* ec) { op::ConnectOp* connectOp = nullptr; do { + m_clientState.m_networkDisconnected = false; + if (!m_sessionState.m_initialized) { if (m_apiEnterCount > 0U) { errorLog("Cannot prepare connect from within callback"); @@ -202,12 +199,6 @@ op::ConnectOp* ClientImpl::connectPrepare(CC_Mqtt5ErrorCode* ec) break; } - if (m_clientState.m_networkDisconnected) { - errorLog("Network is disconnected."); - updateEc(ec, CC_Mqtt5ErrorCode_NetworkDisconnected); - break; - } - if (m_ops.max_size() <= m_ops.size()) { errorLog("Cannot start connect operation, retry in next event loop iteration."); updateEc(ec, CC_Mqtt5ErrorCode_RetryLater); @@ -920,6 +911,8 @@ void ClientImpl::doApiGuard() void ClientImpl::brokerConnected(bool sessionPresent) { + m_sessionExpiryTimer.cancel(); + m_clientState.m_firstConnect = false; m_sessionState.m_connected = true; @@ -976,16 +969,28 @@ void ClientImpl::brokerDisconnected( m_sessionState.m_initialized = false; // Require re-initialization m_sessionState.m_connected = false; - terminateOps(status, TerminateMode_KeepSendRecvOps); + bool preserveSendRecv = + (m_sessionState.m_sessionExpiryIntervalMs > 0U) && + ((!m_recvOps.empty()) || (!m_sendOps.empty())); - for (auto& op : m_recvOps) { - COMMS_ASSERT(op); - op->networkConnectivityChanged(); - } + auto termMode = TerminateMode_AbortSendRecvOps; + if (preserveSendRecv) { + termMode = TerminateMode_KeepSendRecvOps; + } - for (auto& op : m_sendOps) { - COMMS_ASSERT(op); - op->networkConnectivityChanged(); + terminateOps(status, termMode); + + if (preserveSendRecv) { + for (auto* op : m_ops) { + if (op != nullptr) { + op->connectivityChanged(); + } + } + + const auto NeverExpires = (static_cast(CC_MQTT5_SESSION_NEVER_EXPIRES) * 1000U); + if (m_sessionState.m_sessionExpiryIntervalMs != NeverExpires) { + m_sessionExpiryTimer.wait(m_sessionState.m_sessionExpiryIntervalMs, &ClientImpl::sessionExpiryTimeoutCb, this); + } } if (reportDisconnection) { @@ -1192,6 +1197,25 @@ void ClientImpl::resumeSendOpsSince(unsigned idx) } } +void ClientImpl::sessionExpiryTimeoutInternal() +{ + COMMS_ASSERT(m_apiEnterCount > 0U); + COMMS_ASSERT(!m_sessionState.m_connected); + for (auto* op : m_ops) { + if (op == nullptr) { + continue; + } + + auto opType = op->type(); + + if ((opType != op::Op::Type_Recv) && (opType != op::Op::Type_Send)) { + continue; + } + + op->terminateOp(CC_Mqtt5AsyncOpStatus_BrokerDisconnected); + } +} + void ClientImpl::opComplete_Connect(const op::Op* op) { eraseFromList(op, m_connectOps); @@ -1243,4 +1267,9 @@ void ClientImpl::opComplete_Reauth(const op::Op* op) eraseFromList(op, m_reauthOps); } +void ClientImpl::sessionExpiryTimeoutCb(void* data) +{ + reinterpret_cast(data)->sessionExpiryTimeoutInternal(); +} + } // namespace cc_mqtt5_client diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index 6517c5d..11ef36b 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -54,6 +54,7 @@ class ClientImpl final : public ProtMsgHandler ClientImpl& m_client; }; + ClientImpl(); ~ClientImpl(); ApiEnterGuard apiEnter() @@ -63,7 +64,7 @@ class ClientImpl final : public ProtMsgHandler void tick(unsigned ms); unsigned processData(const std::uint8_t* iter, unsigned len); - void notifyNetworkDisconnected(bool disconnected); + void notifyNetworkDisconnected(); bool isNetworkDisconnected() const; op::ConnectOp* connectPrepare(CC_Mqtt5ErrorCode* ec); @@ -241,6 +242,7 @@ class ClientImpl final : public ProtMsgHandler void sendDisconnectMsg(DisconnectMsg::Field_reasonCode::Field::ValueType reason); CC_Mqtt5ErrorCode initInternal(); void resumeSendOpsSince(unsigned idx); + void sessionExpiryTimeoutInternal(); void opComplete_Connect(const op::Op* op); void opComplete_KeepAlive(const op::Op* op); @@ -251,6 +253,8 @@ class ClientImpl final : public ProtMsgHandler void opComplete_Send(const op::Op* op); void opComplete_Reauth(const op::Op* op); + static void sessionExpiryTimeoutCb(void* data); + friend class ApiEnterGuard; CC_Mqtt5NextTickProgramCb m_nextTickProgramCb = nullptr; @@ -308,6 +312,7 @@ class ClientImpl final : public ProtMsgHandler ReauthOpsList m_reauthOps; OpPtrsList m_ops; + TimerMgr::Timer m_sessionExpiryTimer; bool m_opsDeleted = false; bool m_preparationLocked = false; }; diff --git a/client/lib/src/ExtConfig.h b/client/lib/src/ExtConfig.h index bfca495..de0e605 100644 --- a/client/lib/src/ExtConfig.h +++ b/client/lib/src/ExtConfig.h @@ -18,8 +18,10 @@ struct ExtConfig : public Config { static constexpr unsigned ConnectOpsLimit = HasDynMemAlloc ? 0 : 1U; static constexpr unsigned KeepAliveOpsLimit = HasDynMemAlloc ? 0 : 1U; + static constexpr unsigned ClientTimersLimit = HasDynMemAlloc ? 0 : 1U; + static constexpr unsigned ClientTimers = 1U; static constexpr unsigned ConnectOpTimers = 1U; - static constexpr unsigned KeepAliveOpTimers = 4U; + static constexpr unsigned KeepAliveOpTimers = 3U; static constexpr unsigned DisconnectOpsLimit = HasDynMemAlloc ? 0 : 1U; static constexpr unsigned DisconnectOpTimers = 0U; static constexpr unsigned SubscribeOpTimers = 1U; @@ -31,6 +33,7 @@ struct ExtConfig : public Config static constexpr unsigned ReauthOpsLimit = HasDynMemAlloc ? 0 : 1U; static constexpr unsigned ReauthOpTimers = 1U; static constexpr unsigned TimersLimit = + (ClientTimersLimit * ClientTimers) + (ConnectOpsLimit * ConnectOpTimers) + (KeepAliveOpsLimit * KeepAliveOpTimers) + (DisconnectOpsLimit * DisconnectOpTimers) + diff --git a/client/lib/src/op/ConnectOp.cpp b/client/lib/src/op/ConnectOp.cpp index fc6e6cc..f42a79c 100644 --- a/client/lib/src/op/ConnectOp.cpp +++ b/client/lib/src/op/ConnectOp.cpp @@ -967,7 +967,7 @@ void ConnectOp::terminateOpImpl(CC_Mqtt5AsyncOpStatus status) completeOpInternal(status); } -void ConnectOp::networkConnectivityChangedImpl() +void ConnectOp::connectivityChangedImpl() { if (client().clientState().m_networkDisconnected) { completeOpInternal(CC_Mqtt5AsyncOpStatus_BrokerDisconnected); diff --git a/client/lib/src/op/ConnectOp.h b/client/lib/src/op/ConnectOp.h index e8ff0b3..2662a6c 100644 --- a/client/lib/src/op/ConnectOp.h +++ b/client/lib/src/op/ConnectOp.h @@ -43,7 +43,7 @@ class ConnectOp final : public Op protected: virtual Type typeImpl() const override; virtual void terminateOpImpl(CC_Mqtt5AsyncOpStatus status) override; - virtual void networkConnectivityChangedImpl() override; + virtual void connectivityChangedImpl() override; private: using AuthMethodStorageType = ConnectMsg::Field_properties::ValueType::value_type::Field_authMethod::Field_value::ValueType; diff --git a/client/lib/src/op/KeepAliveOp.cpp b/client/lib/src/op/KeepAliveOp.cpp index f604157..9725f24 100644 --- a/client/lib/src/op/KeepAliveOp.cpp +++ b/client/lib/src/op/KeepAliveOp.cpp @@ -28,13 +28,11 @@ KeepAliveOp::KeepAliveOp(ClientImpl& client) : Base(client), m_pingTimer(client.timerMgr().allocTimer()), m_recvTimer(client.timerMgr().allocTimer()), - m_respTimer(client.timerMgr().allocTimer()), - m_sessionExpiryTimer(client.timerMgr().allocTimer()) + m_respTimer(client.timerMgr().allocTimer()) { COMMS_ASSERT(m_pingTimer.isValid()); COMMS_ASSERT(m_recvTimer.isValid()); COMMS_ASSERT(m_respTimer.isValid()); - COMMS_ASSERT(m_sessionExpiryTimer.isValid()); restartPingTimer(); } @@ -108,32 +106,6 @@ Op::Type KeepAliveOp::typeImpl() const return Type_KeepAlive; } -void KeepAliveOp::networkConnectivityChangedImpl() -{ - bool networkDisconnected = client().clientState().m_networkDisconnected; - m_pingTimer.setSuspended(networkDisconnected); - m_recvTimer.setSuspended(networkDisconnected); - m_respTimer.setSuspended(networkDisconnected); - - if (!networkDisconnected) { - m_sessionExpiryTimer.cancel(); - return; - } - - auto sessionExpiryInterval = client().sessionState().m_sessionExpiryIntervalMs; - if (sessionExpiryInterval == 0U) { - sessionExpiryTimeoutInternal(); - return; - } - - if (sessionExpiryInterval == (static_cast(CC_MQTT5_SESSION_NEVER_EXPIRES) * 1000U)) { - // Session never expires - return; - } - - m_sessionExpiryTimer.wait(sessionExpiryInterval, &KeepAliveOp::sessionExpiryTimeoutCb, this); -} - void KeepAliveOp::restartPingTimer() { auto& state = client().sessionState(); @@ -174,12 +146,6 @@ void KeepAliveOp::pingTimeoutInternal() client().brokerDisconnected(true); } -void KeepAliveOp::sessionExpiryTimeoutInternal() -{ - COMMS_ASSERT(!m_sessionExpiryTimer.isActive()); - client().brokerDisconnected(true); -} - void KeepAliveOp::sendPingCb(void* data) { asKeepAliveOp(data)->sendPing(); @@ -195,11 +161,6 @@ void KeepAliveOp::pingTimeoutCb(void* data) asKeepAliveOp(data)->pingTimeoutInternal(); } -void KeepAliveOp::sessionExpiryTimeoutCb(void* data) -{ - asKeepAliveOp(data)->sessionExpiryTimeoutInternal(); -} - } // namespace op } // namespace cc_mqtt5_client diff --git a/client/lib/src/op/KeepAliveOp.h b/client/lib/src/op/KeepAliveOp.h index bfc44a2..d478280 100644 --- a/client/lib/src/op/KeepAliveOp.h +++ b/client/lib/src/op/KeepAliveOp.h @@ -34,26 +34,22 @@ class KeepAliveOp final : public Op protected: virtual Type typeImpl() const override; - virtual void networkConnectivityChangedImpl() override; private: void restartPingTimer(); void restartRecvTimer(); void sendPing(); void pingTimeoutInternal(); - void sessionExpiryTimeoutInternal(); static void sendPingCb(void* data); static void recvTimeoutCb(void* data); static void pingTimeoutCb(void* data); - static void sessionExpiryTimeoutCb(void* data); TimerMgr::Timer m_pingTimer; TimerMgr::Timer m_recvTimer; TimerMgr::Timer m_respTimer; - TimerMgr::Timer m_sessionExpiryTimer; - static_assert(ExtConfig::KeepAliveOpTimers == 4U); + static_assert(ExtConfig::KeepAliveOpTimers == 3U); }; } // namespace op diff --git a/client/lib/src/op/Op.cpp b/client/lib/src/op/Op.cpp index 3dc97d8..8ad16bc 100644 --- a/client/lib/src/op/Op.cpp +++ b/client/lib/src/op/Op.cpp @@ -48,7 +48,7 @@ void Op::terminateOpImpl([[maybe_unused]] CC_Mqtt5AsyncOpStatus status) opComplete(); } -void Op::networkConnectivityChangedImpl() +void Op::connectivityChangedImpl() { // Do nothing by default } diff --git a/client/lib/src/op/Op.h b/client/lib/src/op/Op.h index 7b868b6..8fcd6e1 100644 --- a/client/lib/src/op/Op.h +++ b/client/lib/src/op/Op.h @@ -62,9 +62,9 @@ class Op : public ProtMsgHandler m_responseTimeoutMs = ms; } - void networkConnectivityChanged() + void connectivityChanged() { - networkConnectivityChangedImpl(); + connectivityChangedImpl(); } protected: @@ -75,7 +75,7 @@ class Op : public ProtMsgHandler virtual Type typeImpl() const = 0; virtual void terminateOpImpl(CC_Mqtt5AsyncOpStatus status); - virtual void networkConnectivityChangedImpl(); + virtual void connectivityChangedImpl(); void sendMessage(const ProtMessage& msg); void opComplete(); diff --git a/client/lib/src/op/RecvOp.cpp b/client/lib/src/op/RecvOp.cpp index c5f3d83..7b65969 100644 --- a/client/lib/src/op/RecvOp.cpp +++ b/client/lib/src/op/RecvOp.cpp @@ -402,7 +402,7 @@ void RecvOp::reset() void RecvOp::postReconnectionResume() { - networkConnectivityChangedImpl(); + connectivityChangedImpl(); } Op::Type RecvOp::typeImpl() const @@ -410,7 +410,7 @@ Op::Type RecvOp::typeImpl() const return Type_Recv; } -void RecvOp::networkConnectivityChangedImpl() +void RecvOp::connectivityChangedImpl() { m_responseTimer.setSuspended( (!client().sessionState().m_connected) || client().clientState().m_networkDisconnected); diff --git a/client/lib/src/op/RecvOp.h b/client/lib/src/op/RecvOp.h index 745965a..deef2bb 100644 --- a/client/lib/src/op/RecvOp.h +++ b/client/lib/src/op/RecvOp.h @@ -40,7 +40,7 @@ class RecvOp final : public Op protected: virtual Type typeImpl() const override; - virtual void networkConnectivityChangedImpl() override; + virtual void connectivityChangedImpl() override; private: using UserPropKeyStorage = PublishMsg::Field_properties::ValueType::value_type::Field_userProperty::Field_value::Field_first::ValueType; diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index 0ebc25b..a277374 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -684,7 +684,7 @@ void SendOp::terminateOpImpl(CC_Mqtt5AsyncOpStatus status) completeWithCb(status); } -void SendOp::networkConnectivityChangedImpl() +void SendOp::connectivityChangedImpl() { m_responseTimer.setSuspended( (!client().sessionState().m_connected) || client().clientState().m_networkDisconnected); diff --git a/client/lib/src/op/SendOp.h b/client/lib/src/op/SendOp.h index 5c0fb8b..dbbf16c 100644 --- a/client/lib/src/op/SendOp.h +++ b/client/lib/src/op/SendOp.h @@ -85,7 +85,7 @@ class SendOp final : public Op protected: virtual Type typeImpl() const override; virtual void terminateOpImpl(CC_Mqtt5AsyncOpStatus status) override; - virtual void networkConnectivityChangedImpl() override; + virtual void connectivityChangedImpl() override; private: void restartResponseTimer(); diff --git a/client/lib/src/op/SubscribeOp.cpp b/client/lib/src/op/SubscribeOp.cpp index 3202aee..52b1419 100644 --- a/client/lib/src/op/SubscribeOp.cpp +++ b/client/lib/src/op/SubscribeOp.cpp @@ -329,11 +329,6 @@ void SubscribeOp::terminateOpImpl(CC_Mqtt5AsyncOpStatus status) completeOpInternal(status); } -void SubscribeOp::networkConnectivityChangedImpl() -{ - m_timer.setSuspended(client().clientState().m_networkDisconnected); -} - void SubscribeOp::completeOpInternal(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response) { auto cb = m_cb; diff --git a/client/lib/src/op/SubscribeOp.h b/client/lib/src/op/SubscribeOp.h index 9609f8a..28aa682 100644 --- a/client/lib/src/op/SubscribeOp.h +++ b/client/lib/src/op/SubscribeOp.h @@ -51,7 +51,6 @@ class SubscribeOp final : public Op protected: virtual Type typeImpl() const override; virtual void terminateOpImpl(CC_Mqtt5AsyncOpStatus status) override; - virtual void networkConnectivityChangedImpl() override; private: void completeOpInternal(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response = nullptr); diff --git a/client/lib/src/op/UnsubscribeOp.cpp b/client/lib/src/op/UnsubscribeOp.cpp index 0ae7351..6fc15fa 100644 --- a/client/lib/src/op/UnsubscribeOp.cpp +++ b/client/lib/src/op/UnsubscribeOp.cpp @@ -296,11 +296,6 @@ void UnsubscribeOp::terminateOpImpl(CC_Mqtt5AsyncOpStatus status) completeOpInternal(status); } -void UnsubscribeOp::networkConnectivityChangedImpl() -{ - m_timer.setSuspended(client().clientState().m_networkDisconnected); -} - void UnsubscribeOp::completeOpInternal(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5UnsubscribeResponse* response) { auto cb = m_cb; diff --git a/client/lib/src/op/UnsubscribeOp.h b/client/lib/src/op/UnsubscribeOp.h index d3cc0b5..fb8972c 100644 --- a/client/lib/src/op/UnsubscribeOp.h +++ b/client/lib/src/op/UnsubscribeOp.h @@ -50,7 +50,6 @@ class UnsubscribeOp final : public Op protected: virtual Type typeImpl() const override; virtual void terminateOpImpl(CC_Mqtt5AsyncOpStatus status) override; - virtual void networkConnectivityChangedImpl() override; private: void completeOpInternal(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5UnsubscribeResponse* response = nullptr); diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index 77ec81f..c6493c4 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -122,10 +122,10 @@ unsigned cc_mqtt5_##NAME##client_process_data(CC_Mqtt5ClientHandle handle, const return clientFromHandle(handle)->processData(buf, bufLen); } -void cc_mqtt5_##NAME##client_notify_network_disconnected(CC_Mqtt5ClientHandle handle, bool disconnected) +void cc_mqtt5_##NAME##client_notify_network_disconnected(CC_Mqtt5ClientHandle handle) { COMMS_ASSERT(handle != nullptr); - clientFromHandle(handle)->notifyNetworkDisconnected(disconnected); + clientFromHandle(handle)->notifyNetworkDisconnected(); } bool cc_mqtt5_##NAME##client_is_network_disconnected(CC_Mqtt5ClientHandle handle) diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index 2a7581e..31e8dd1 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -71,11 +71,12 @@ void cc_mqtt5_##NAME##client_tick(CC_Mqtt5ClientHandle handle, unsigned ms); /// @ingroup client unsigned cc_mqtt5_##NAME##client_process_data(CC_Mqtt5ClientHandle handle, const unsigned char* buf, unsigned bufLen); -/// @brief Report network disconnected status +/// @brief Report network disconnected +/// @details To notify the client that the network is connected again use +/// @ref cc_mqtt5_##NAME##client_connect_prepare() /// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_alloc() function. -/// @param[in] disconnected Flag for network disconnection: @b true when disconnected, @b false when (re)connected. /// @ingroup client -void cc_mqtt5_##NAME##client_notify_network_disconnected(CC_Mqtt5ClientHandle handle, bool disconnected); +void cc_mqtt5_##NAME##client_notify_network_disconnected(CC_Mqtt5ClientHandle handle); /// @brief Check current network disconnected status /// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_alloc() function. @@ -177,6 +178,8 @@ void cc_mqtt5_##NAME##client_init_user_prop(CC_Mqtt5UserProp* prop); /// @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. /// @pre The function can NOT be called from within a callback, use next event iteration. +/// @post The network assumed to be in the "connected" state, use @ref cc_mqtt5_##NAME##client_is_network_disconnected() +/// to notify about the network disconnection. /// @post The "connect" operation is allocated, use either @ref cc_mqtt5_##NAME##client_connect_send() /// or @ref cc_mqtt5_##NAME##client_connect_cancel() to prevent memory leaks. /// @ingroup connect diff --git a/client/lib/test/unit/UnitTestCommonBase.cpp b/client/lib/test/unit/UnitTestCommonBase.cpp index 65446c5..95297a0 100644 --- a/client/lib/test/unit/UnitTestCommonBase.cpp +++ b/client/lib/test/unit/UnitTestCommonBase.cpp @@ -926,9 +926,9 @@ UnitTestCommonBase::UnitTestClientPtr UnitTestCommonBase::unitTestAlloc() return UnitTestClientPtr(m_funcs.m_alloc(), UnitTestDeleter(m_funcs)); } -void UnitTestCommonBase::unitTestNotifyNetworkDisconnected(CC_Mqtt5Client* client, bool disconnected) +void UnitTestCommonBase::unitTestNotifyNetworkDisconnected(CC_Mqtt5Client* client) { - m_funcs.m_notify_network_disconnected(client, disconnected); + m_funcs.m_notify_network_disconnected(client); } bool UnitTestCommonBase::unitTestIsNetworkDisconnected(CC_Mqtt5Client* client) @@ -946,6 +946,11 @@ CC_Mqtt5ErrorCode UnitTestCommonBase::unitTestPubTopicAliasAlloc(CC_Mqtt5Client* return m_funcs.m_pub_topic_alias_alloc(client, topic, qos0RegsCount); } +unsigned UnitTestCommonBase::unitTestPubTopicAliasCount(CC_Mqtt5Client* client) +{ + return m_funcs.m_pub_topic_alias_count(client); +} + CC_Mqtt5ConnectHandle UnitTestCommonBase::unitTestConnectPrepare(CC_Mqtt5Client* client, CC_Mqtt5ErrorCode* ec) { return m_funcs.m_connect_prepare(client, ec); diff --git a/client/lib/test/unit/UnitTestCommonBase.h b/client/lib/test/unit/UnitTestCommonBase.h index f359e46..a454da9 100644 --- a/client/lib/test/unit/UnitTestCommonBase.h +++ b/client/lib/test/unit/UnitTestCommonBase.h @@ -23,7 +23,7 @@ class UnitTestCommonBase CC_Mqtt5ErrorCode (*m_init)(CC_Mqtt5ClientHandle) = nullptr; void (*m_tick)(CC_Mqtt5ClientHandle, unsigned) = nullptr; unsigned (*m_process_data)(CC_Mqtt5ClientHandle, const unsigned char*, unsigned) = nullptr; - void (*m_notify_network_disconnected)(CC_Mqtt5ClientHandle, bool) = nullptr; + void (*m_notify_network_disconnected)(CC_Mqtt5ClientHandle) = nullptr; bool (*m_is_network_disconnected)(CC_Mqtt5ClientHandle) = nullptr; CC_Mqtt5ErrorCode (*m_set_default_response_timeout)(CC_Mqtt5ClientHandle, unsigned) = nullptr; unsigned (*m_get_default_response_timeout)(CC_Mqtt5ClientHandle) = nullptr; @@ -446,10 +446,11 @@ class UnitTestCommonBase void unitTestVerifyDisconnectSent(UnitTestDisconnectReason reason = UnitTestDisconnectReason::Success); UnitTestClientPtr unitTestAlloc(); - void unitTestNotifyNetworkDisconnected(CC_Mqtt5Client* client, bool disconnected); + void unitTestNotifyNetworkDisconnected(CC_Mqtt5Client* client); bool unitTestIsNetworkDisconnected(CC_Mqtt5Client* client); CC_Mqtt5ErrorCode unitTestSetDefaultResponseTimeout(CC_Mqtt5Client* client, unsigned ms); CC_Mqtt5ErrorCode unitTestPubTopicAliasAlloc(CC_Mqtt5Client* client, const char* topic, unsigned char qos0RegsCount); + unsigned unitTestPubTopicAliasCount(CC_Mqtt5Client* client); CC_Mqtt5ConnectHandle unitTestConnectPrepare(CC_Mqtt5Client* client, CC_Mqtt5ErrorCode* ec); void unitTestConnectInitConfigBasic(CC_Mqtt5ConnectBasicConfig* config); void unitTestConnectInitConfigWill(CC_Mqtt5ConnectWillConfig* config); diff --git a/client/lib/test/unit/UnitTestConnect.th b/client/lib/test/unit/UnitTestConnect.th index 04fa133..ea95742 100644 --- a/client/lib/test/unit/UnitTestConnect.th +++ b/client/lib/test/unit/UnitTestConnect.th @@ -899,17 +899,12 @@ void UnitTestConnect::test10() TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Connect); unitTestTick(1000); - unitTestNotifyNetworkDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client); TS_ASSERT(unitTestIsConnectComplete()); auto& connackInfo = unitTestConnectResponseInfo(); TS_ASSERT_EQUALS(connackInfo.m_status, CC_Mqtt5AsyncOpStatus_BrokerDisconnected); TS_ASSERT(!unitTestIsConnected(client)); - - // New connection attempt when network is disconnected - connect = unitTestConnectPrepare(client, &ec); - TS_ASSERT_EQUALS(connect, nullptr); - TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_NetworkDisconnected); } void UnitTestConnect::test11() @@ -928,26 +923,9 @@ void UnitTestConnect::test11() unitTestTick(10000); // 10 seconds - unitTestNotifyNetworkDisconnected(client, true); - - tickReq = unitTestTickReq(); - TS_ASSERT_EQUALS(tickReq->m_requested, SessionExpiryInterval * 1000); // Expecting to measure session expiry interval - - unitTestTick(5000); // 5 seconds - unitTestNotifyNetworkDisconnected(client, false); - - tickReq = unitTestTickReq(); - TS_ASSERT_EQUALS(tickReq->m_requested, 50000); // Remaining period of the keep alive - - unitTestTick(45000); // 45 seconds - unitTestNotifyNetworkDisconnected(client, true); - - tickReq = unitTestTickReq(); - TS_ASSERT_EQUALS(tickReq->m_requested, SessionExpiryInterval * 1000); // Expecting to measure session expiry interval - - TS_ASSERT(!unitTestIsDisconnected()); - unitTestTick(); // Session expiry timeout - TS_ASSERT(unitTestIsDisconnected()); + unitTestNotifyNetworkDisconnected(client); + TS_ASSERT(!unitTestIsDisconnected()); // Broker disconnection is not notified + TS_ASSERT(unitTestCheckNoTicks()); // No publish or receive, not need to measure session expiry } void UnitTestConnect::test12() @@ -1238,7 +1216,7 @@ void UnitTestConnect::test16() TS_ASSERT(!unitTestIsConnected(client)); TS_ASSERT(unitTestCheckNoTicks()); - unitTestNotifyNetworkDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client); } void UnitTestConnect::test17() @@ -1360,7 +1338,7 @@ void UnitTestConnect::test20() unitTestPerformConnect(client, &basicConfig, nullptr, nullptr, nullptr, &responseConfig); TS_ASSERT(!unitTestIsConnected(client)); TS_ASSERT(!unitTestIsNetworkDisconnected(client)); - unitTestNotifyNetworkDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client); TS_ASSERT(unitTestIsNetworkDisconnected(client)); } diff --git a/client/lib/test/unit/UnitTestPublish.th b/client/lib/test/unit/UnitTestPublish.th index b78e27f..fb49282 100644 --- a/client/lib/test/unit/UnitTestPublish.th +++ b/client/lib/test/unit/UnitTestPublish.th @@ -1157,7 +1157,7 @@ void UnitTestPublish::test14() // Suspending Qos1 publish operation when network is disconnected // [MQTT-3.1.2-23] - auto* client = unitTestAllocClient(); + auto* client = unitTestAllocClient(true); const unsigned SessionExpiryInterval = 10; unitTestPerformSessionExpiryConnect(client, __FUNCTION__, SessionExpiryInterval); @@ -1194,31 +1194,18 @@ void UnitTestPublish::test14() TS_ASSERT_EQUALS(tickReq->m_requested, ResponseTimeout); unitTestTick(1000); - unitTestNotifyNetworkDisconnected(client, true); - - TS_ASSERT(!unitTestIsPublishComplete()); - tickReq = unitTestTickReq(); - TS_ASSERT_EQUALS(tickReq->m_requested, SessionExpiryInterval * 1000); // Expecting to measure session expiry interval - - unitTestTick(5000); - unitTestNotifyNetworkDisconnected(client, false); - TS_ASSERT(!unitTestIsPublishComplete()); - - tickReq = unitTestTickReq(); - TS_ASSERT_EQUALS(tickReq->m_requested, ResponseTimeout - 1000); // Resuming PUBACK timer - - unitTestNotifyNetworkDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client); + TS_ASSERT(!unitTestIsDisconnected()); TS_ASSERT(!unitTestIsPublishComplete()); tickReq = unitTestTickReq(); TS_ASSERT_EQUALS(tickReq->m_requested, SessionExpiryInterval * 1000); // Expecting to measure session expiry interval unitTestTick(); // Session expiry - TS_ASSERT(!unitTestIsPublishComplete()); // Stored until reconnect - // auto& responseInfo = unitTestPublishResponseInfo(); - // TS_ASSERT_EQUALS(responseInfo.m_status, CC_Mqtt5AsyncOpStatus_BrokerDisconnected); - // unitTestPopPublishResponseInfo(); - // TS_ASSERT(unitTestIsDisconnected()); + TS_ASSERT(unitTestIsPublishComplete()); // Stored until reconnect + auto& responseInfo = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(responseInfo.m_status, CC_Mqtt5AsyncOpStatus_BrokerDisconnected); + unitTestPopPublishResponseInfo(); } void UnitTestPublish::test15() @@ -1263,44 +1250,17 @@ void UnitTestPublish::test15() TS_ASSERT_EQUALS(tickReq->m_requested, ResponseTimeout); unitTestTick(1000); - unitTestNotifyNetworkDisconnected(client, true); - - TS_ASSERT(!unitTestIsPublishComplete()); - tickReq = unitTestTickReq(); - TS_ASSERT_EQUALS(tickReq->m_requested, SessionExpiryInterval * 1000); // Expecting to measure session expiry interval - - unitTestTick(5000); - unitTestNotifyNetworkDisconnected(client, false); - TS_ASSERT(!unitTestIsPublishComplete()); - - tickReq = unitTestTickReq(); - TS_ASSERT_EQUALS(tickReq->m_requested, ResponseTimeout - 1000); // Resuming PUBREC timer - - UnitTestPubrecMsg pubrecMsg; - pubrecMsg.field_packetId().value() = publishMsg->field_packetId().field().value(); - unitTestReceiveMessage(pubrecMsg); - TS_ASSERT(!unitTestIsPublishComplete()); - - sentMsg = unitTestGetSentMessage(); - TS_ASSERT(sentMsg); - TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Pubrel); - auto* pubrelMsg = dynamic_cast(sentMsg.get()); - TS_ASSERT_DIFFERS(pubrelMsg, nullptr); - TS_ASSERT_EQUALS(pubrelMsg->field_packetId().value(), pubrecMsg.field_packetId().value()); - - unitTestTick(1000); - unitTestNotifyNetworkDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client); TS_ASSERT(!unitTestIsPublishComplete()); tickReq = unitTestTickReq(); TS_ASSERT_EQUALS(tickReq->m_requested, SessionExpiryInterval * 1000); // Expecting to measure session expiry interval unitTestTick(); // Session expiry - TS_ASSERT(!unitTestIsPublishComplete()); // Stored in session - // auto& responseInfo = unitTestPublishResponseInfo(); - // TS_ASSERT_EQUALS(responseInfo.m_status, CC_Mqtt5AsyncOpStatus_BrokerDisconnected); - // unitTestPopPublishResponseInfo(); - // TS_ASSERT(unitTestIsDisconnected()); + TS_ASSERT(unitTestIsPublishComplete()); // Stored in session + auto& responseInfo = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(responseInfo.m_status, CC_Mqtt5AsyncOpStatus_BrokerDisconnected); + unitTestPopPublishResponseInfo(); } void UnitTestPublish::test16() @@ -2031,8 +1991,19 @@ void UnitTestPublish::test26() } while (false); unitTestTick(1000); - unitTestNotifyNetworkDisconnected(client, true); - unitTestNotifyNetworkDisconnected(client, false); + unitTestNotifyNetworkDisconnected(client); + + // Reconnecting + basicConfig.m_cleanStart = false; + responseConfig.m_sessionPresent = true; + + unitTestPerformConnect(client, &basicConfig, nullptr, &extraConfig, nullptr, &responseConfig); + TS_ASSERT(unitTestIsConnected(client)); + + TS_ASSERT_EQUALS(unitTestPubTopicAliasCount(client), 0); + + ec = unitTestPubTopicAliasAlloc(client, Topic.c_str(), 1U); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); do { auto* publish = unitTestPublishPrepare(client, nullptr); @@ -2639,8 +2610,17 @@ void UnitTestPublish::test35() auto* client = unitTestAllocClient(); - unitTestPerformBasicConnect(client, __FUNCTION__); - TS_ASSERT(unitTestIsConnected(client)); + auto basicConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&basicConfig); + basicConfig.m_clientId = __FUNCTION__; + basicConfig.m_cleanStart = true; + + auto extraConfig = CC_Mqtt5ConnectExtraConfig(); + unitTestConnectInitConfigExtra(&extraConfig); + extraConfig.m_sessionExpiryInterval = 10; + + unitTestPerformConnect(client, &basicConfig, nullptr, &extraConfig); + TS_ASSERT(unitTestIsConnected(client)); const std::string Topic1("some/topic1"); const UnitTestData Data = {0x1, 0x2, 0x3}; @@ -2671,13 +2651,15 @@ void UnitTestPublish::test35() auto packetId1 = publishMsg->field_packetId().field().value(); unitTestTick(1000); - unitTestNotifyNetworkDisconnected(client, true); - TS_ASSERT(unitTestIsDisconnected()); + unitTestNotifyNetworkDisconnected(client); + TS_ASSERT(!unitTestIsDisconnected()); TS_ASSERT(!unitTestIsPublishComplete()); - TS_ASSERT(unitTestCheckNoTicks()); + TS_ASSERT(!unitTestCheckNoTicks()); + + auto* tickReq = unitTestTickReq(); + TS_ASSERT_EQUALS(tickReq->m_requested, extraConfig.m_sessionExpiryInterval * 1000U); unitTestClearState(); - unitTestNotifyNetworkDisconnected(client, false); // Reconnection with attempt to restore the session auto connectConfig = CC_Mqtt5ConnectBasicConfig(); @@ -2720,8 +2702,17 @@ void UnitTestPublish::test36() auto* client = unitTestAllocClient(); - unitTestPerformBasicConnect(client, __FUNCTION__); - TS_ASSERT(unitTestIsConnected(client)); + auto basicConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&basicConfig); + basicConfig.m_clientId = __FUNCTION__; + basicConfig.m_cleanStart = true; + + auto extraConfig = CC_Mqtt5ConnectExtraConfig(); + unitTestConnectInitConfigExtra(&extraConfig); + extraConfig.m_sessionExpiryInterval = 10; + + unitTestPerformConnect(client, &basicConfig, nullptr, &extraConfig); + TS_ASSERT(unitTestIsConnected(client)); const std::string Topic1("some/topic1"); const UnitTestData Data = {0x1, 0x2, 0x3}; @@ -2752,13 +2743,12 @@ void UnitTestPublish::test36() auto packetId1 = publishMsg->field_packetId().field().value(); unitTestTick(100); - unitTestNotifyNetworkDisconnected(client, true); - TS_ASSERT(unitTestIsDisconnected()); + unitTestNotifyNetworkDisconnected(client); + TS_ASSERT(!unitTestIsDisconnected()); TS_ASSERT(!unitTestIsPublishComplete()); - TS_ASSERT(unitTestCheckNoTicks()); + TS_ASSERT(!unitTestCheckNoTicks()); unitTestClearState(); - unitTestNotifyNetworkDisconnected(client, false); // Reconnection with attempt to restore the session auto connectConfig = CC_Mqtt5ConnectBasicConfig(); @@ -2813,8 +2803,17 @@ void UnitTestPublish::test37() auto* client = unitTestAllocClient(); - unitTestPerformBasicConnect(client, __FUNCTION__); - TS_ASSERT(unitTestIsConnected(client)); + auto basicConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&basicConfig); + basicConfig.m_clientId = __FUNCTION__; + basicConfig.m_cleanStart = true; + + auto extraConfig = CC_Mqtt5ConnectExtraConfig(); + unitTestConnectInitConfigExtra(&extraConfig); + extraConfig.m_sessionExpiryInterval = 10; + + unitTestPerformConnect(client, &basicConfig, nullptr, &extraConfig); + TS_ASSERT(unitTestIsConnected(client)); const std::string Topic1("some/topic1"); const UnitTestData Data = {0x1, 0x2, 0x3}; @@ -2857,13 +2856,12 @@ void UnitTestPublish::test37() TS_ASSERT_EQUALS(pubrelMsg->field_packetId().value(), pubrecMsg.field_packetId().value()); unitTestTick(1000); - unitTestNotifyNetworkDisconnected(client, true); - TS_ASSERT(unitTestIsDisconnected()); + unitTestNotifyNetworkDisconnected(client); + TS_ASSERT(!unitTestIsDisconnected()); TS_ASSERT(!unitTestIsPublishComplete()); - TS_ASSERT(unitTestCheckNoTicks()); + TS_ASSERT(!unitTestCheckNoTicks()); unitTestClearState(); - unitTestNotifyNetworkDisconnected(client, false); // Reconnection with attempt to restore the session auto connectConfig = CC_Mqtt5ConnectBasicConfig(); @@ -2904,8 +2902,17 @@ void UnitTestPublish::test38() auto* client = unitTestAllocClient(); - unitTestPerformBasicConnect(client, __FUNCTION__); - TS_ASSERT(unitTestIsConnected(client)); + auto basicConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&basicConfig); + basicConfig.m_clientId = __FUNCTION__; + basicConfig.m_cleanStart = true; + + auto extraConfig = CC_Mqtt5ConnectExtraConfig(); + unitTestConnectInitConfigExtra(&extraConfig); + extraConfig.m_sessionExpiryInterval = 10; + + unitTestPerformConnect(client, &basicConfig, nullptr, &extraConfig); + TS_ASSERT(unitTestIsConnected(client)); const std::string Topic1("some/topic1"); const UnitTestData Data = {0x1, 0x2, 0x3}; @@ -2932,13 +2939,12 @@ void UnitTestPublish::test38() TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); unitTestTick(1000); - unitTestNotifyNetworkDisconnected(client, true); - TS_ASSERT(unitTestIsDisconnected()); + unitTestNotifyNetworkDisconnected(client); + TS_ASSERT(!unitTestIsDisconnected()); TS_ASSERT(!unitTestIsPublishComplete()); - TS_ASSERT(unitTestCheckNoTicks()); + TS_ASSERT(!unitTestCheckNoTicks()); unitTestClearState(); - unitTestNotifyNetworkDisconnected(client, false); // Reconnection with attempt to restore the session, but the clean session is reported unitTestPerformBasicConnect(client, __FUNCTION__, true); @@ -2956,7 +2962,8 @@ void UnitTestPublish::test39() auto* client = unitTestAllocClient(); - unitTestPerformBasicConnect(client, __FUNCTION__); + const unsigned SessionExpiryInterval = 10; + unitTestPerformSessionExpiryConnect(client, __FUNCTION__, SessionExpiryInterval); TS_ASSERT(unitTestIsConnected(client)); const std::string Topic1("some/topic1"); @@ -2996,10 +3003,8 @@ void UnitTestPublish::test39() auto& disconnectInfo = unitTestDisconnectInfo(); TS_ASSERT_EQUALS(disconnectInfo.m_reasonCode, CC_Mqtt5ReasonCode_NormalDisconnection); unitTestPopDisconnectInfo(); - TS_ASSERT(unitTestCheckNoTicks()); - TS_ASSERT(!unitTestIsPublishComplete()); - TS_ASSERT(unitTestCheckNoTicks()); + TS_ASSERT(!unitTestCheckNoTicks()); // Has session expiry timeout unitTestClearState(); @@ -3157,10 +3162,14 @@ void UnitTestPublish::test41() basicConfig.m_clientId = __FUNCTION__; basicConfig.m_cleanStart = true; + auto extraConfig = CC_Mqtt5ConnectExtraConfig(); + unitTestConnectInitConfigExtra(&extraConfig); + extraConfig.m_sessionExpiryInterval = 10; + UnitTestConnectResponseConfig responseConfig; responseConfig.m_recvMaximum = 1; - unitTestPerformConnect(client, &basicConfig, nullptr, nullptr, nullptr, &responseConfig); + unitTestPerformConnect(client, &basicConfig, nullptr, &extraConfig, nullptr, &responseConfig); TS_ASSERT(unitTestIsConnected(client)); const std::string Topic("some/topic"); @@ -3224,10 +3233,13 @@ void UnitTestPublish::test41() auto& disconnectInfo = unitTestDisconnectInfo(); TS_ASSERT_EQUALS(disconnectInfo.m_reasonCode, CC_Mqtt5ReasonCode_NormalDisconnection); unitTestPopDisconnectInfo(); - TS_ASSERT(unitTestCheckNoTicks()); + + TS_ASSERT(!unitTestCheckNoTicks()); // has session expiry timer + auto* tickReq = unitTestTickReq(); + TS_ASSERT_EQUALS(tickReq->m_requested, extraConfig.m_sessionExpiryInterval * 1000U); TS_ASSERT(!unitTestIsPublishComplete()); - TS_ASSERT(unitTestCheckNoTicks()); + unitTestClearState(); // Reconnection with attempt to restore the session @@ -3328,10 +3340,14 @@ void UnitTestPublish::test42() basicConfig.m_clientId = __FUNCTION__; basicConfig.m_cleanStart = true; + auto extraConfig = CC_Mqtt5ConnectExtraConfig(); + unitTestConnectInitConfigExtra(&extraConfig); + extraConfig.m_sessionExpiryInterval = 10; + UnitTestConnectResponseConfig responseConfig; responseConfig.m_recvMaximum = 1; - unitTestPerformConnect(client, &basicConfig, nullptr, nullptr, nullptr, &responseConfig); + unitTestPerformConnect(client, &basicConfig, nullptr, &extraConfig, nullptr, &responseConfig); TS_ASSERT(unitTestIsConnected(client)); const std::string Topic("some/topic"); @@ -3395,10 +3411,10 @@ void UnitTestPublish::test42() auto& disconnectInfo = unitTestDisconnectInfo(); TS_ASSERT_EQUALS(disconnectInfo.m_reasonCode, CC_Mqtt5ReasonCode_NormalDisconnection); unitTestPopDisconnectInfo(); - TS_ASSERT(unitTestCheckNoTicks()); TS_ASSERT(!unitTestIsPublishComplete()); - TS_ASSERT(unitTestCheckNoTicks()); + TS_ASSERT(!unitTestCheckNoTicks()); // Has session expiry calculation + unitTestClearState(); // Reconnection with attempt to restore the session @@ -3494,10 +3510,14 @@ void UnitTestPublish::test43() basicConfig.m_clientId = __FUNCTION__; basicConfig.m_cleanStart = true; + auto extraConfig = CC_Mqtt5ConnectExtraConfig(); + unitTestConnectInitConfigExtra(&extraConfig); + extraConfig.m_sessionExpiryInterval = 10; + UnitTestConnectResponseConfig responseConfig; responseConfig.m_recvMaximum = 1; - unitTestPerformConnect(client, &basicConfig, nullptr, nullptr, nullptr, &responseConfig); + unitTestPerformConnect(client, &basicConfig, nullptr, &extraConfig, nullptr, &responseConfig); TS_ASSERT(unitTestIsConnected(client)); const std::string Topic("some/topic"); @@ -3576,10 +3596,10 @@ void UnitTestPublish::test43() auto& disconnectInfo = unitTestDisconnectInfo(); TS_ASSERT_EQUALS(disconnectInfo.m_reasonCode, CC_Mqtt5ReasonCode_NormalDisconnection); unitTestPopDisconnectInfo(); - TS_ASSERT(unitTestCheckNoTicks()); TS_ASSERT(!unitTestIsPublishComplete()); - TS_ASSERT(unitTestCheckNoTicks()); + TS_ASSERT(!unitTestCheckNoTicks()); // has session expiry + unitTestClearState(); // Reconnection with attempt to restore the session diff --git a/client/lib/test/unit/UnitTestReceive.th b/client/lib/test/unit/UnitTestReceive.th index 73ef613..baa7a11 100644 --- a/client/lib/test/unit/UnitTestReceive.th +++ b/client/lib/test/unit/UnitTestReceive.th @@ -458,7 +458,7 @@ void UnitTestReceive::test6() // Testing network disconnection during reception of Qos2 message // [MQTT-3.1.2-23] - auto* client = unitTestAllocClient(); + auto* client = unitTestAllocClient(true); const unsigned ResponseTimeout = 2000; unitTestSetDefaultResponseTimeout(client, ResponseTimeout); @@ -495,16 +495,20 @@ void UnitTestReceive::test6() TS_ASSERT_EQUALS(tickReq->m_requested, ResponseTimeout); unitTestTick(1000); - unitTestNotifyNetworkDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client); tickReq = unitTestTickReq(); TS_ASSERT_EQUALS(tickReq->m_requested, SessionExpiryInterval * 1000); // Expecting to measure session expiry interval unitTestTick(5000); - unitTestNotifyNetworkDisconnected(client, false); - tickReq = unitTestTickReq(); - TS_ASSERT_EQUALS(tickReq->m_requested, ResponseTimeout - 1000); + auto basicConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&basicConfig); + basicConfig.m_clientId = __FUNCTION__; + + auto connectRespConfig = UnitTestConnectResponseConfig(); + connectRespConfig.m_sessionPresent = true; + unitTestPerformConnect(client, &basicConfig, nullptr, nullptr, nullptr, &connectRespConfig); // Sending PUBREL with invalid packet id; UnitTestPubrelMsg pubrelMsg; @@ -559,14 +563,13 @@ void UnitTestReceive::test7() TS_ASSERT_EQUALS(tickReq->m_requested, ResponseTimeout); unitTestTick(1000); - unitTestNotifyNetworkDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client); tickReq = unitTestTickReq(); TS_ASSERT_EQUALS(tickReq->m_requested, SessionExpiryInterval * 1000); // Expecting to measure session expiry interval unitTestTick(); // Session expiry TS_ASSERT(!unitTestHasMessageRecieved()); - TS_ASSERT(unitTestIsDisconnected()); } void UnitTestReceive::test8() @@ -1461,7 +1464,8 @@ void UnitTestReceive::test22() // [MQTT-4.4.0-1] auto* client = unitTestAllocClient(); - unitTestPerformBasicConnect(client, __FUNCTION__); + const unsigned SessionExpiryInterval = 10; + unitTestPerformSessionExpiryConnect(client, __FUNCTION__, SessionExpiryInterval); TS_ASSERT(unitTestIsConnected(client)); unitTestPerformBasicSubscribe(client, "#"); @@ -1495,13 +1499,11 @@ void UnitTestReceive::test22() TS_ASSERT(pubrecMsg->field_properties().isMissing()); unitTestTick(100); - unitTestNotifyNetworkDisconnected(client, true); - TS_ASSERT(unitTestIsDisconnected()); - TS_ASSERT(!unitTestIsPublishComplete()); - TS_ASSERT(unitTestCheckNoTicks()); + unitTestNotifyNetworkDisconnected(client); + TS_ASSERT(!unitTestIsDisconnected()); + TS_ASSERT(!unitTestCheckNoTicks()); // has session expiry unitTestClearState(); - unitTestNotifyNetworkDisconnected(client, false); // Reconnection with attempt to restore the session auto connectConfig = CC_Mqtt5ConnectBasicConfig(); @@ -1548,7 +1550,8 @@ void UnitTestReceive::test23() // [MQTT-4.4.0-1] auto* client = unitTestAllocClient(); - unitTestPerformBasicConnect(client, __FUNCTION__); + const unsigned SessionExpiryInterval = 10; + unitTestPerformSessionExpiryConnect(client, __FUNCTION__, SessionExpiryInterval); TS_ASSERT(unitTestIsConnected(client)); unitTestPerformBasicSubscribe(client, "#"); @@ -1582,13 +1585,12 @@ void UnitTestReceive::test23() TS_ASSERT(pubrecMsg->field_properties().isMissing()); unitTestTick(100); - unitTestNotifyNetworkDisconnected(client, true); - TS_ASSERT(unitTestIsDisconnected()); + unitTestNotifyNetworkDisconnected(client); + TS_ASSERT(!unitTestIsDisconnected()); TS_ASSERT(!unitTestIsPublishComplete()); - TS_ASSERT(unitTestCheckNoTicks()); + TS_ASSERT(!unitTestCheckNoTicks()); // session expiry tick unitTestClearState(); - unitTestNotifyNetworkDisconnected(client, false); // Reconnection with attempt to restore the session auto connectConfig = CC_Mqtt5ConnectBasicConfig(); @@ -1625,7 +1627,8 @@ void UnitTestReceive::test24() // [MQTT-4.4.0-1] auto* client = unitTestAllocClient(); - unitTestPerformBasicConnect(client, __FUNCTION__); + const unsigned SessionExpiryInterval = 10; + unitTestPerformSessionExpiryConnect(client, __FUNCTION__, SessionExpiryInterval); TS_ASSERT(unitTestIsConnected(client)); unitTestPerformBasicSubscribe(client, "#"); @@ -1659,13 +1662,11 @@ void UnitTestReceive::test24() TS_ASSERT(pubrecMsg->field_properties().isMissing()); unitTestTick(100); - unitTestNotifyNetworkDisconnected(client, true); - TS_ASSERT(unitTestIsDisconnected()); - TS_ASSERT(!unitTestIsPublishComplete()); - TS_ASSERT(unitTestCheckNoTicks()); + unitTestNotifyNetworkDisconnected(client); + TS_ASSERT(!unitTestIsDisconnected()); + TS_ASSERT(!unitTestCheckNoTicks()); // session expiry tick unitTestClearState(); - unitTestNotifyNetworkDisconnected(client, false); // Reconnection with attempt to restore the session unitTestPerformBasicConnect(client, __FUNCTION__, false); @@ -1711,7 +1712,8 @@ void UnitTestReceive::test25() // [MQTT-4.4.0-1] auto* client = unitTestAllocClient(); - unitTestPerformBasicConnect(client, __FUNCTION__); + const unsigned SessionExpiryInterval = 10; + unitTestPerformSessionExpiryConnect(client, __FUNCTION__, SessionExpiryInterval); TS_ASSERT(unitTestIsConnected(client)); unitTestPerformBasicSubscribe(client, "#"); @@ -1734,6 +1736,7 @@ void UnitTestReceive::test25() TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); TS_ASSERT_EQUALS(msgInfo.m_data, Data); unitTestPopReceivedMessageInfo(); + TS_ASSERT(!unitTestHasMessageRecieved()); auto sentMsg = unitTestGetSentMessage(); TS_ASSERT(sentMsg); @@ -1753,7 +1756,7 @@ void UnitTestReceive::test25() auto& disconnectInfo = unitTestDisconnectInfo(); TS_ASSERT_EQUALS(disconnectInfo.m_reasonCode, CC_Mqtt5ReasonCode_NormalDisconnection); unitTestPopDisconnectInfo(); - TS_ASSERT(unitTestCheckNoTicks()); + TS_ASSERT(!unitTestCheckNoTicks()); // session expiry tick unitTestClearState(); diff --git a/client/lib/test/unit/UnitTestSubscribe.th b/client/lib/test/unit/UnitTestSubscribe.th index 927e3d0..0589721 100644 --- a/client/lib/test/unit/UnitTestSubscribe.th +++ b/client/lib/test/unit/UnitTestSubscribe.th @@ -362,7 +362,7 @@ void UnitTestSubscribe::test4() void UnitTestSubscribe::test5() { - // Suspending subscribe operation when network is disconnected + // Canceling subscribe operation when network is disconnected // [MQTT-3.1.2-23] auto* client = unitTestAllocClient(); @@ -400,31 +400,13 @@ void UnitTestSubscribe::test5() TS_ASSERT_EQUALS(tickReq->m_requested, ResponseTimeout); unitTestTick(1000); - unitTestNotifyNetworkDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client); + TS_ASSERT(!unitTestIsDisconnected()); - TS_ASSERT(!unitTestIsSubscribeComplete()); - tickReq = unitTestTickReq(); - TS_ASSERT_EQUALS(tickReq->m_requested, SessionExpiryInterval * 1000); // Expecting to measure session expiry interval - - unitTestTick(5000); - unitTestNotifyNetworkDisconnected(client, false); - TS_ASSERT(!unitTestIsSubscribeComplete()); - - tickReq = unitTestTickReq(); - TS_ASSERT_EQUALS(tickReq->m_requested, ResponseTimeout - 1000); // Resuming SUBACK timer - - unitTestNotifyNetworkDisconnected(client, true); - - TS_ASSERT(!unitTestIsSubscribeComplete()); - tickReq = unitTestTickReq(); - TS_ASSERT_EQUALS(tickReq->m_requested, SessionExpiryInterval * 1000); // Expecting to measure session expiry interval - - unitTestTick(); // Session expiry TS_ASSERT(unitTestIsSubscribeComplete()); auto& subackInfo = unitTestSubscribeResponseInfo(); TS_ASSERT_EQUALS(subackInfo.m_status, CC_Mqtt5AsyncOpStatus_BrokerDisconnected); unitTestPopSubscribeResponseInfo(); - TS_ASSERT(unitTestIsDisconnected()); } void UnitTestSubscribe::test6() diff --git a/client/lib/test/unit/UnitTestUnsubscribe.th b/client/lib/test/unit/UnitTestUnsubscribe.th index b9bae96..de66e35 100644 --- a/client/lib/test/unit/UnitTestUnsubscribe.th +++ b/client/lib/test/unit/UnitTestUnsubscribe.th @@ -299,7 +299,7 @@ void UnitTestUnsubscribe::test4() void UnitTestUnsubscribe::test5() { - // Suspending unsubscribe operation when network is disconnected + // Canceling unsubscribe operation when network is disconnected // [MQTT-3.1.2-23] auto* client = unitTestAllocClient(); @@ -341,31 +341,14 @@ void UnitTestUnsubscribe::test5() TS_ASSERT_EQUALS(tickReq->m_requested, ResponseTimeout); unitTestTick(1000); - unitTestNotifyNetworkDisconnected(client, true); + unitTestNotifyNetworkDisconnected(client); + TS_ASSERT(!unitTestIsDisconnected()); - TS_ASSERT(!unitTestIsUnsubscribeComplete()); - tickReq = unitTestTickReq(); - TS_ASSERT_EQUALS(tickReq->m_requested, SessionExpiryInterval * 1000); // Expecting to measure session expiry interval - - unitTestTick(5000); - unitTestNotifyNetworkDisconnected(client, false); - TS_ASSERT(!unitTestIsUnsubscribeComplete()); - - tickReq = unitTestTickReq(); - TS_ASSERT_EQUALS(tickReq->m_requested, ResponseTimeout - 1000); // Resuming UNSUBACK timer - - unitTestNotifyNetworkDisconnected(client, true); - - TS_ASSERT(!unitTestIsUnsubscribeComplete()); - tickReq = unitTestTickReq(); - TS_ASSERT_EQUALS(tickReq->m_requested, SessionExpiryInterval * 1000); // Expecting to measure session expiry interval - - unitTestTick(); // Session expiry TS_ASSERT(unitTestIsUnsubscribeComplete()); auto& unsubackInfo = unitTestUnsubscribeResponseInfo(); TS_ASSERT_EQUALS(unsubackInfo.m_status, CC_Mqtt5AsyncOpStatus_BrokerDisconnected); unitTestPopUnsubscribeResponseInfo(); - TS_ASSERT(unitTestIsDisconnected()); + } void UnitTestUnsubscribe::test6() From f0dd3c1cb3ac7cfa63b8119a1eafc2b3d517da3b Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sat, 6 Apr 2024 11:38:13 +1000 Subject: [PATCH 23/48] Not using topic alias on re-connect resend. --- client/lib/spec_compliance.md | 2 +- client/lib/src/ClientImpl.cpp | 30 ++--- client/lib/src/ClientState.h | 1 + client/lib/src/SessionState.h | 1 - client/lib/src/TopicAliasDefs.h | 3 - client/lib/src/op/ConnectOp.cpp | 3 +- client/lib/src/op/RecvOp.cpp | 1 + client/lib/src/op/SendOp.cpp | 77 ++++++++++--- client/lib/test/unit/UnitTestPublish.th | 144 ++++++++++++++++++++++++ 9 files changed, 224 insertions(+), 38 deletions(-) diff --git a/client/lib/spec_compliance.md b/client/lib/spec_compliance.md index 80186b5..e488ed6 100644 --- a/client/lib/spec_compliance.md +++ b/client/lib/spec_compliance.md @@ -132,7 +132,7 @@ * When network is disconnected during connection attempt, the operation is immediately aborted. Tested in UnitTestConnect::test10. * Canceling keep alive is tested in UnitTestConnect::test11. * Canceling subscribe operation for session expiry period is tested in UnitTestSubscribe::test5. - * Suspending unsubscribe operation for session expiry period is tested in UnitTestUnsubscribe::test5. + * Canceling unsubscribe operation for session expiry period is tested in UnitTestUnsubscribe::test5. * Suspending publish operation for session expiry period is tested in UnitTestPublish::test14 and UnitTestPublish::test15. * Suspending message reception for session expiry period is tested in UnitTestReceive::test6 and UnitTestReceive::test7. - [MQTT-3.1.2-24]: The Server MUST NOT send packets exceeding Maximum Packet Size to the Client diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index ece0356..8cabc24 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -520,12 +520,12 @@ CC_Mqtt5ErrorCode ClientImpl::allocPubTopicAlias(const char* topic, unsigned qos return CC_Mqtt5ErrorCode_Disconnecting; } - if (m_sessionState.m_maxSendTopicAlias <= m_sessionState.m_sendTopicAliases.size()) { + if (m_sessionState.m_maxSendTopicAlias <= m_clientState.m_sendTopicAliases.size()) { errorLog("Broker doesn't support usage of any more aliases."); return CC_Mqtt5ErrorCode_BadParam; } - if (m_sessionState.m_sendTopicAliases.max_size() <= m_sessionState.m_sendTopicAliases.size()) { + if (m_clientState.m_sendTopicAliases.max_size() <= m_clientState.m_sendTopicAliases.size()) { errorLog("Amount of topic aliases has reached their maximum allowed memory."); return CC_Mqtt5ErrorCode_OutOfMemory; } @@ -537,17 +537,15 @@ CC_Mqtt5ErrorCode ClientImpl::allocPubTopicAlias(const char* topic, unsigned qos auto iter = std::lower_bound( - m_sessionState.m_sendTopicAliases.begin(), m_sessionState.m_sendTopicAliases.end(), topic, + m_clientState.m_sendTopicAliases.begin(), m_clientState.m_sendTopicAliases.end(), topic, [](auto& info, const char* topicParam) { return info.m_topic < topicParam; }); - if ((iter != m_sessionState.m_sendTopicAliases.end()) && + if ((iter != m_clientState.m_sendTopicAliases.end()) && (iter->m_topic == topic)) { - comms::cast_assign(iter->m_lowQosRegCountRequest) = qos0RegsCount; - comms::cast_assign(iter->m_lowQosRegRemCount) = qos0RegsCount; - iter->m_highQosRegRemCount = TopicAliasInfo::DefaultHighQosRegRemCount; + comms::cast_assign(iter->m_lowQosRegRemCount) = std::max(qos0RegsCount, 1U); return CC_Mqtt5ErrorCode_Success; } @@ -564,10 +562,10 @@ CC_Mqtt5ErrorCode ClientImpl::allocPubTopicAlias(const char* topic, unsigned qos COMMS_ASSERT(alias > 0U); COMMS_ASSERT(alias <= m_sessionState.m_maxSendTopicAlias); - auto infoIter = m_sessionState.m_sendTopicAliases.insert(iter, TopicAliasInfo()); + auto infoIter = m_clientState.m_sendTopicAliases.insert(iter, TopicAliasInfo()); infoIter->m_topic = topic; infoIter->m_alias = alias; - comms::cast_assign(infoIter->m_lowQosRegRemCount) = qos0RegsCount; + comms::cast_assign(infoIter->m_lowQosRegRemCount) = std::max(qos0RegsCount, 1U); return CC_Mqtt5ErrorCode_Success; } else { @@ -585,19 +583,19 @@ CC_Mqtt5ErrorCode ClientImpl::freePubTopicAlias(const char* topic) auto iter = std::lower_bound( - m_sessionState.m_sendTopicAliases.begin(), m_sessionState.m_sendTopicAliases.end(), topic, + m_clientState.m_sendTopicAliases.begin(), m_clientState.m_sendTopicAliases.end(), topic, [](auto& info, const char* topicParam) { return info.m_topic < topicParam; }); - if ((iter == m_sessionState.m_sendTopicAliases.end()) || (iter->m_topic != topic)) { + if ((iter == m_clientState.m_sendTopicAliases.end()) || (iter->m_topic != topic)) { errorLog("Alias for provided topic hasn't been allocated before."); return CC_Mqtt5ErrorCode_BadParam; } m_sessionState.m_sendTopicFreeAliases.push_back(iter->m_alias); - m_sessionState.m_sendTopicAliases.erase(iter); + m_clientState.m_sendTopicAliases.erase(iter); return CC_Mqtt5ErrorCode_Success; } else { @@ -608,7 +606,7 @@ CC_Mqtt5ErrorCode ClientImpl::freePubTopicAlias(const char* topic) unsigned ClientImpl::pubTopicAliasCount() const { if constexpr (Config::HasTopicAliases) { - return static_cast(m_sessionState.m_sendTopicAliases.size()); + return static_cast(m_clientState.m_sendTopicAliases.size()); } else { return 0U; @@ -624,13 +622,13 @@ bool ClientImpl::pubTopicAliasIsAllocated(const char* topic) const auto iter = std::lower_bound( - m_sessionState.m_sendTopicAliases.begin(), m_sessionState.m_sendTopicAliases.end(), topic, + m_clientState.m_sendTopicAliases.begin(), m_clientState.m_sendTopicAliases.end(), topic, [](auto& info, const char* topicParam) { return info.m_topic < topicParam; }); - return ((iter != m_sessionState.m_sendTopicAliases.end()) && (iter->m_topic == topic)); + return ((iter != m_clientState.m_sendTopicAliases.end()) && (iter->m_topic == topic)); } else { return false; @@ -957,6 +955,8 @@ void ClientImpl::brokerConnected(bool sessionPresent) } } while (false); + m_clientState.m_sendTopicAliases.clear(); + createKeepAliveOpIfNeeded(); } diff --git a/client/lib/src/ClientState.h b/client/lib/src/ClientState.h index fa6ddee..f881d32 100644 --- a/client/lib/src/ClientState.h +++ b/client/lib/src/ClientState.h @@ -26,6 +26,7 @@ struct ClientState static constexpr unsigned DefaultKeepAlive = 60; static constexpr unsigned DefaultTopicAliasMax = 10; + SendTopicsMap m_sendTopicAliases; PacketIdsList m_allocatedPacketIds; std::uint16_t m_lastPacketId = 0U; unsigned m_inFlightSends = 0U; diff --git a/client/lib/src/SessionState.h b/client/lib/src/SessionState.h index 3f71d73..b3463cd 100644 --- a/client/lib/src/SessionState.h +++ b/client/lib/src/SessionState.h @@ -27,7 +27,6 @@ struct SessionState static constexpr unsigned DefaultTopicAliasMax = 10; RecvTopicsMap m_recvTopicAliases; - SendTopicsMap m_sendTopicAliases; SendTopicsFreeAliasList m_sendTopicFreeAliases; AuthMethodStorageType m_authMethod; std::uint64_t m_sessionExpiryIntervalMs = 0U; diff --git a/client/lib/src/TopicAliasDefs.h b/client/lib/src/TopicAliasDefs.h index 92944f1..b7415fb 100644 --- a/client/lib/src/TopicAliasDefs.h +++ b/client/lib/src/TopicAliasDefs.h @@ -21,13 +21,10 @@ using RecvTopicsMap = ObjListType; diff --git a/client/lib/src/op/ConnectOp.cpp b/client/lib/src/op/ConnectOp.cpp index f42a79c..eaefb2f 100644 --- a/client/lib/src/op/ConnectOp.cpp +++ b/client/lib/src/op/ConnectOp.cpp @@ -695,7 +695,8 @@ void ConnectOp::handle(ConnackMsg& msg) reuseState = ReuseState(); } - state.m_sendTopicAliases.resize(std::min(state.m_sendTopicAliases.size(), std::size_t(response.m_topicAliasMax))); + auto& clientState = client().clientState(); + clientState.m_sendTopicAliases.resize(std::min(clientState.m_sendTopicAliases.size(), std::size_t(response.m_topicAliasMax))); state.m_keepAliveMs = keepAlive * 1000U; state.m_highQosSendLimit = response.m_highQosSendLimit; diff --git a/client/lib/src/op/RecvOp.cpp b/client/lib/src/op/RecvOp.cpp index 7b65969..6349f38 100644 --- a/client/lib/src/op/RecvOp.cpp +++ b/client/lib/src/op/RecvOp.cpp @@ -403,6 +403,7 @@ void RecvOp::reset() void RecvOp::postReconnectionResume() { connectivityChangedImpl(); + restartResponseTimer(); } Op::Type RecvOp::typeImpl() const diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index a277374..0dd8c1d 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -349,15 +349,16 @@ CC_Mqtt5ErrorCode SendOp::configBasic(const CC_Mqtt5PublishBasicConfig& config) break; } + auto& clientState = client().clientState(); auto iter = std::lower_bound( - state.m_sendTopicAliases.begin(), state.m_sendTopicAliases.end(), config.m_topic, + clientState.m_sendTopicAliases.begin(), clientState.m_sendTopicAliases.end(), config.m_topic, [](auto& info, const char* topicParam) { return info.m_topic < topicParam; }); - bool found = ((iter != state.m_sendTopicAliases.end()) && (iter->m_topic == config.m_topic)); + bool found = ((iter != clientState.m_sendTopicAliases.end()) && (iter->m_topic == config.m_topic)); if (!found) { if ((config.m_topicAliasPref == CC_Mqtt5TopicAliasPreference_ForceTopicWithAlias) || (config.m_topicAliasPref == CC_Mqtt5TopicAliasPreference_ForceAliasOnly)) { @@ -380,21 +381,17 @@ CC_Mqtt5ErrorCode SendOp::configBasic(const CC_Mqtt5PublishBasicConfig& config) break; } - if (iter->m_highQosRegRemCount == 0U) { - // The message was acked + if (iter->m_lowQosRegRemCount == 0U) { mustAssignTopic = false; break; } - if (config.m_qos > CC_Mqtt5QoS_AtMostOnceDelivery) { - break; - } - if (iter->m_lowQosRegRemCount == 0U) { - mustAssignTopic = false; - break; - } + // if (config.m_qos > CC_Mqtt5QoS_AtMostOnceDelivery) { + // break; + // } + --iter->m_lowQosRegRemCount; } else { @@ -640,6 +637,54 @@ void SendOp::postReconnectionResend() return; } + if constexpr (Config::HasTopicAliases) { + do { + auto& propsVec = m_pubMsg.field_properties().value(); + auto iter = + std::find_if( + propsVec.begin(), propsVec.end(), + [](auto& prop) { + return (prop.currentField() == PublishMsg::Field_properties::ValueType::value_type::FieldIdx_topicAlias); + }); + + if (iter == propsVec.end()) { + COMMS_ASSERT(!m_pubMsg.field_topic().value().empty()); + break; + } + + if (m_acked) { + // The message won't be sent, no need to update it. + propsVec.erase(iter); + break; + } + + if (m_pubMsg.field_topic().value().empty()) { + auto& topicAliasField = iter->accessField_topicAlias(); + auto topicAliasValue = topicAliasField.field_value().value(); + + auto& aliases = client().clientState().m_sendTopicAliases; + auto infoIter = + std::find_if( + aliases.begin(), aliases.end(), + [topicAliasValue](auto& info) + { + return info.m_alias == topicAliasValue; + }); + + COMMS_ASSERT(infoIter != aliases.end()); + if (infoIter == aliases.end()) { + errorLog("Broker reduced its allowed topic aliases (less likely) or it's internal error (most likely)."); + completeWithCb(CC_Mqtt5AsyncOpStatus_InternalError); + return; + } + + m_pubMsg.field_topic().value() = infoIter->m_topic.c_str(); + } + + propsVec.erase(iter); + } while (false); + } + COMMS_ASSERT(m_sendAttempts > 0U); --m_sendAttempts; m_responseTimer.cancel(); @@ -759,24 +804,22 @@ void SendOp::confirmRegisteredAlias() { COMMS_ASSERT(!m_pubMsg.field_topic().value().empty()); COMMS_ASSERT(m_registeredAlias); - auto& state = client().sessionState(); + auto& clientState = client().clientState(); auto& topic = m_pubMsg.field_topic().value(); auto iter = std::lower_bound( - state.m_sendTopicAliases.begin(), state.m_sendTopicAliases.end(), topic, + clientState.m_sendTopicAliases.begin(), clientState.m_sendTopicAliases.end(), topic, [](auto& info, auto& topicParam) { return info.m_topic < topicParam; }); - if (iter == state.m_sendTopicAliases.end()) { + if (iter == clientState.m_sendTopicAliases.end()) { errorLog("Topic alias freed before it is acknowledged"); return; } - if (iter->m_highQosRegRemCount > 0U) { - --(iter->m_highQosRegRemCount); - } + iter->m_lowQosRegRemCount = 0U; } CC_Mqtt5ErrorCode SendOp::doSendInternal() diff --git a/client/lib/test/unit/UnitTestPublish.th b/client/lib/test/unit/UnitTestPublish.th index fb49282..9e6c10a 100644 --- a/client/lib/test/unit/UnitTestPublish.th +++ b/client/lib/test/unit/UnitTestPublish.th @@ -51,6 +51,7 @@ public: void test42(); void test43(); void test44(); + void test45(); private: virtual void setUp() override @@ -3852,4 +3853,147 @@ void UnitTestPublish::test44() auto& pubInfo2 = unitTestPublishResponseInfo(); TS_ASSERT_EQUALS(pubInfo2.m_status, CC_Mqtt5AsyncOpStatus_Complete); unitTestPopPublishResponseInfo(); +} + +void UnitTestPublish::test45() +{ + // Testing resend in the resumed session when topic alias was in use before disconnection. + + auto* client = unitTestAllocClient(true); + + auto basicConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&basicConfig); + basicConfig.m_clientId = __FUNCTION__; + basicConfig.m_cleanStart = true; + + auto extraConfig = CC_Mqtt5ConnectExtraConfig(); + unitTestConnectInitConfigExtra(&extraConfig); + extraConfig.m_sessionExpiryInterval = 10; + + UnitTestConnectResponseConfig responseConfig; + responseConfig.m_topicAliasMax = 10; + + unitTestPerformConnect(client, &basicConfig, nullptr, &extraConfig, nullptr, &responseConfig); + TS_ASSERT(unitTestIsConnected(client)); + + const std::string Topic("some/topic"); + const UnitTestData Data = { 0x1, 0x2, 0x3, 0x4, 0x5}; + + auto ec = unitTestPubTopicAliasAlloc(client, Topic.c_str(), 1U); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + auto config = CC_Mqtt5PublishBasicConfig(); + unitTestPublishInitConfigBasic(&config); + + config.m_topic = Topic.c_str(); + config.m_data = &Data[0]; + config.m_dataLen = static_cast(Data.size()); + config.m_qos = CC_Mqtt5QoS_AtMostOnceDelivery; + + auto* publish1 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish1, nullptr); + + ec = unitTestPublishConfigBasic(publish1, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish1); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + auto sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + auto* publishMsg1 = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg1, nullptr); + TS_ASSERT(publishMsg1->field_packetId().isMissing()); + TS_ASSERT_EQUALS(publishMsg1->field_topic().value(), Topic); + + UnitTestPropsHandler propsHandler1; + for (auto& p : publishMsg1->field_properties().value()) { + p.currentFieldExec(propsHandler1); + } + + TS_ASSERT_DIFFERS(propsHandler1.m_topicAlias, nullptr); + auto alias1 = propsHandler1.m_topicAlias->field_value().value(); + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubInfo1 = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubInfo1.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); + + auto* publish2 = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish2, nullptr); + + config.m_qos = CC_Mqtt5QoS_AtLeastOnceDelivery; + ec = unitTestPublishConfigBasic(publish2, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish2, false); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + TS_ASSERT(unitTestPublishWasInitiated(publish2)); + TS_ASSERT(unitTestHasSentMessage()); + + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + auto* publishMsg2 = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg2, nullptr); + TS_ASSERT(publishMsg2->field_topic().value().empty()); // Topic alias is in use + TS_ASSERT(publishMsg2->field_packetId().doesExist()); + auto packetId2 = publishMsg2->field_packetId().field().value(); + + UnitTestPropsHandler propsHandler2; + for (auto& p : publishMsg2->field_properties().value()) { + p.currentFieldExec(propsHandler2); + } + + TS_ASSERT_DIFFERS(propsHandler2.m_topicAlias, nullptr); + auto alias2 = propsHandler2.m_topicAlias->field_value().value(); + TS_ASSERT_EQUALS(alias1, alias2); + + unitTestTick(100); + UnitTestDisconnectMsg disconnectMsg; + unitTestReceiveMessage(disconnectMsg); + + TS_ASSERT(unitTestIsDisconnected()); + TS_ASSERT(unitTestHasDisconnectInfo()); + auto& disconnectInfo = unitTestDisconnectInfo(); + TS_ASSERT_EQUALS(disconnectInfo.m_reasonCode, CC_Mqtt5ReasonCode_NormalDisconnection); + unitTestPopDisconnectInfo(); + TS_ASSERT(!unitTestIsPublishComplete()); + TS_ASSERT(!unitTestCheckNoTicks()); // Has session expiry timeout + + unitTestClearState(); + + // Reconnection with attempt to restore the session + auto connectConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&connectConfig); + + connectConfig.m_clientId = __FUNCTION__; + connectConfig.m_cleanStart = false; + + auto connectRespConfig = UnitTestConnectResponseConfig(); + connectRespConfig.m_sessionPresent = true; + connectRespConfig.m_topicAliasMax = 10; + unitTestPerformConnect(client, &connectConfig, nullptr, nullptr, nullptr, &connectRespConfig); + + // Checking the PUBLISH is present + TS_ASSERT(!unitTestIsPublishComplete()); + TS_ASSERT(unitTestHasSentMessage()); + sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + auto publishMsg3 = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg3, nullptr); + TS_ASSERT(publishMsg3->transportField_flags().field_dup().getBitValue_bit()); + TS_ASSERT(publishMsg3->field_packetId().doesExist()); + TS_ASSERT_EQUALS(publishMsg3->field_packetId().field().value(), packetId2); + TS_ASSERT(!publishMsg3->field_topic().value().empty()); + + UnitTestPropsHandler propsHandler3; + for (auto& p : publishMsg3->field_properties().value()) { + p.currentFieldExec(propsHandler3); + } + + // The topic alias should not be used in resend + TS_ASSERT_EQUALS(propsHandler3.m_topicAlias, nullptr); } \ No newline at end of file From 764b964afa368765aa0e93b7a0c5bf105153ad17 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sat, 6 Apr 2024 11:54:38 +1000 Subject: [PATCH 24/48] A bit of API cleanup. --- client/lib/src/ClientImpl.cpp | 5 ----- client/lib/src/ClientImpl.h | 16 ++++++++++------ client/lib/src/op/Op.cpp | 5 ----- client/lib/src/op/Op.h | 1 - 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 8cabc24..ca3f984 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -902,11 +902,6 @@ void ClientImpl::opComplete(const op::Op* op) (this->*func)(op); } -void ClientImpl::doApiGuard() -{ - auto guard = apiEnter(); -} - void ClientImpl::brokerConnected(bool sessionPresent) { m_sessionExpiryTimer.cancel(); diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index 11ef36b..c42cba1 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -62,6 +62,7 @@ class ClientImpl final : public ProtMsgHandler return ApiEnterGuard(*this); } + // -------------------- API Calls ----------------------------- void tick(unsigned ms); unsigned processData(const std::uint8_t* iter, unsigned len); void notifyNetworkDisconnected(); @@ -79,6 +80,11 @@ class ClientImpl final : public ProtMsgHandler unsigned pubTopicAliasCount() const; bool pubTopicAliasIsAllocated(const char* topic) const; + std::size_t sendsCount() const + { + return m_sendOps.size(); + } + void setNextTickProgramCallback(CC_Mqtt5NextTickProgramCb cb, void* data) { if (cb != nullptr) { @@ -125,6 +131,8 @@ class ClientImpl final : public ProtMsgHandler m_errorLogData = data; } + // -------------------- Message Handling ----------------------------- + using Base::handle; virtual void handle(PublishMsg& msg) override; virtual void handle(PubackMsg& msg) override; @@ -133,9 +141,10 @@ class ClientImpl final : public ProtMsgHandler virtual void handle(PubcompMsg& msg) override; virtual void handle(ProtMessage& msg) override; + // -------------------- Ops Access API ----------------------------- + CC_Mqtt5ErrorCode sendMessage(const ProtMessage& msg); void opComplete(const op::Op* op); - void doApiGuard(); void brokerConnected(bool sessionPresent); void brokerDisconnected( bool reportDisconnection, @@ -187,11 +196,6 @@ class ClientImpl final : public ProtMsgHandler } } - std::size_t sendsCount() const - { - return m_sendOps.size(); - } - std::size_t recvsCount() const { return m_recvOps.size(); diff --git a/client/lib/src/op/Op.cpp b/client/lib/src/op/Op.cpp index 8ad16bc..9214757 100644 --- a/client/lib/src/op/Op.cpp +++ b/client/lib/src/op/Op.cpp @@ -58,11 +58,6 @@ void Op::opComplete() m_client.opComplete(this); } -void Op::doApiGuard() -{ - m_client.doApiGuard(); -} - std::uint16_t Op::allocPacketId() { static constexpr auto MaxPacketId = std::numeric_limits::max(); diff --git a/client/lib/src/op/Op.h b/client/lib/src/op/Op.h index 8fcd6e1..8bb83cf 100644 --- a/client/lib/src/op/Op.h +++ b/client/lib/src/op/Op.h @@ -79,7 +79,6 @@ class Op : public ProtMsgHandler void sendMessage(const ProtMessage& msg); void opComplete(); - void doApiGuard(); std::uint16_t allocPacketId(); void releasePacketId(std::uint16_t id); From ac6e0d028d0ce1b47511dc67d8f5a7712005103b Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sat, 6 Apr 2024 13:08:57 +1000 Subject: [PATCH 25/48] Allowing non-clean first connect when subscription verification is disabled. --- client/lib/doxygen/main.dox | 13 ++++++-- client/lib/src/op/ConnectOp.cpp | 6 ++-- client/lib/test/unit/UnitTestCommonBase.cpp | 5 +++ client/lib/test/unit/UnitTestCommonBase.h | 1 + client/lib/test/unit/UnitTestReceive.th | 35 +++++++++++++++++++++ 5 files changed, 55 insertions(+), 5 deletions(-) diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index 801b059..55245cd 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -319,11 +319,18 @@ /// @endcode /// /// **IMPORTANT**: MQTT v5 specification allows reconnection to the broker while -/// requesting previous session restoration (via "clean start" bit). To prevent +/// requesting previous session restoration (via "clean start" bit). By default, +/// the client library verifies that the message received from the broker was +/// actually subscribed to before reporting the message to the application +/// (see @ref doc_cc_mqtt5_client_receive for details). To prevent /// potential errors of the client and broker inner states being out of sync, the /// @b first "connect" operation requires setting the @ref CC_Mqtt5ConnectBasicConfig::m_cleanStart -/// value to @b true. Otherwise the @ref CC_Mqtt5ErrorCode_BadParam error code -/// will be returned. Any subsequent reconnection attempts will allow +/// 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_mqtt5_client_receive section below). In case the subscription +/// verification is still enabled and the @ref CC_Mqtt5ConnectBasicConfig::m_cleanStart +/// value is @b NOT set to @b true, the function rejects the configuration +/// with the @ref CC_Mqtt5ErrorCode_BadParam error code. Any subsequent reconnection attempts will allow /// setting the value to @b false. /// /// See also documentation of the @ref CC_Mqtt5ConnectBasicConfig structure. diff --git a/client/lib/src/op/ConnectOp.cpp b/client/lib/src/op/ConnectOp.cpp index eaefb2f..3a1dc5e 100644 --- a/client/lib/src/op/ConnectOp.cpp +++ b/client/lib/src/op/ConnectOp.cpp @@ -45,7 +45,7 @@ CC_Mqtt5ErrorCode ConnectOp::configBasic(const CC_Mqtt5ConnectBasicConfig& confi return CC_Mqtt5ErrorCode_BadParam; } - if ((!config.m_cleanStart) && (client().clientState().m_firstConnect)) { + if ((!config.m_cleanStart) && client().clientState().m_firstConnect && client().configState().m_verifySubFilter) { errorLog("Clean start flag needs to be set on the first connection attempt"); return CC_Mqtt5ErrorCode_BadParam; } @@ -481,7 +481,9 @@ CC_Mqtt5ErrorCode ConnectOp::send(CC_Mqtt5ConnectCompleteCb cb, void* cbData) return CC_Mqtt5ErrorCode_InternalError; } - if ((!m_connectMsg.field_flags().field_low().getBitValue_cleanStart()) && (client().clientState().m_firstConnect)) { + if ((!m_connectMsg.field_flags().field_low().getBitValue_cleanStart()) && + (client().clientState().m_firstConnect) && + (client().configState().m_verifySubFilter)) { errorLog("Clean start flag needs to be set on the first connection attempt, perform configuration first."); return CC_Mqtt5ErrorCode_InsufficientConfig; } diff --git a/client/lib/test/unit/UnitTestCommonBase.cpp b/client/lib/test/unit/UnitTestCommonBase.cpp index 95297a0..fc93ad8 100644 --- a/client/lib/test/unit/UnitTestCommonBase.cpp +++ b/client/lib/test/unit/UnitTestCommonBase.cpp @@ -951,6 +951,11 @@ unsigned UnitTestCommonBase::unitTestPubTopicAliasCount(CC_Mqtt5Client* client) return m_funcs.m_pub_topic_alias_count(client); } +void UnitTestCommonBase::unitTestSetVerifyIncomingMsgSubscribed(CC_Mqtt5Client* client, bool enabled) +{ + m_funcs.m_set_verify_incoming_msg_subscribed(client, enabled); +} + CC_Mqtt5ConnectHandle UnitTestCommonBase::unitTestConnectPrepare(CC_Mqtt5Client* client, CC_Mqtt5ErrorCode* ec) { return m_funcs.m_connect_prepare(client, ec); diff --git a/client/lib/test/unit/UnitTestCommonBase.h b/client/lib/test/unit/UnitTestCommonBase.h index a454da9..b470b22 100644 --- a/client/lib/test/unit/UnitTestCommonBase.h +++ b/client/lib/test/unit/UnitTestCommonBase.h @@ -451,6 +451,7 @@ class UnitTestCommonBase CC_Mqtt5ErrorCode unitTestSetDefaultResponseTimeout(CC_Mqtt5Client* client, unsigned ms); CC_Mqtt5ErrorCode unitTestPubTopicAliasAlloc(CC_Mqtt5Client* client, const char* topic, unsigned char qos0RegsCount); unsigned unitTestPubTopicAliasCount(CC_Mqtt5Client* client); + void unitTestSetVerifyIncomingMsgSubscribed(CC_Mqtt5Client* client, bool enabled); CC_Mqtt5ConnectHandle unitTestConnectPrepare(CC_Mqtt5Client* client, CC_Mqtt5ErrorCode* ec); void unitTestConnectInitConfigBasic(CC_Mqtt5ConnectBasicConfig* config); void unitTestConnectInitConfigWill(CC_Mqtt5ConnectWillConfig* config); diff --git a/client/lib/test/unit/UnitTestReceive.th b/client/lib/test/unit/UnitTestReceive.th index baa7a11..5c19595 100644 --- a/client/lib/test/unit/UnitTestReceive.th +++ b/client/lib/test/unit/UnitTestReceive.th @@ -32,6 +32,7 @@ public: void test23(); void test24(); void test25(); + void test26(); private: virtual void setUp() override @@ -1796,5 +1797,39 @@ void UnitTestReceive::test25() TS_ASSERT(pubcompMsg->field_reasonCode().isMissing()); TS_ASSERT(pubcompMsg->field_properties().isMissing()); + TS_ASSERT(!unitTestHasMessageRecieved()); +} + +void UnitTestReceive::test26() +{ + // Testing allowing non-clean first connect when subscription verification is disabled + auto* client = unitTestAllocClient(true); + + unitTestSetVerifyIncomingMsgSubscribed(client, false); + + auto basicConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&basicConfig); + basicConfig.m_clientId = __FUNCTION__; + basicConfig.m_cleanStart = false; + + auto connectRespConfig = UnitTestConnectResponseConfig(); + connectRespConfig.m_sessionPresent = true; + unitTestPerformConnect(client, &basicConfig, nullptr, nullptr, nullptr, &connectRespConfig); + + const std::string Topic = "some/topic"; + const UnitTestData Data = {'h', 'e', 'l', 'l', 'o'}; + + UnitTestPublishMsg publishMsg; + publishMsg.transportField_flags().field_qos().value() = UnitTestPublishMsg::TransportField_flags::Field_qos::ValueType::AtMostOnceDelivery; + publishMsg.field_topic().value() = Topic; + publishMsg.field_payload().value() = Data; + publishMsg.doRefresh(); + unitTestReceiveMessage(publishMsg); + + TS_ASSERT(unitTestHasMessageRecieved()); + auto& msgInfo = unitTestReceivedMessageInfo(); + TS_ASSERT_EQUALS(msgInfo.m_topic, Topic); + TS_ASSERT_EQUALS(msgInfo.m_data, Data); + unitTestPopReceivedMessageInfo(); TS_ASSERT(!unitTestHasMessageRecieved()); } \ No newline at end of file From f568a8f0386cb5e4c268ab26572dd7ad0bf0198f Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sat, 6 Apr 2024 13:23:12 +1000 Subject: [PATCH 26/48] Testing network disconnection report after the DISCONNECT message reception. --- client/lib/doxygen/main.dox | 5 ++++ client/lib/src/ClientImpl.cpp | 6 ++++- client/lib/test/unit/UnitTestDisconnect.th | 27 +++++++++++++++++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index 55245cd..fb73267 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -1780,6 +1780,11 @@ /// When the incomplete operations get terminated the @ref CC_Mqtt5AsyncOpStatus_BrokerDisconnected status /// is reported. /// +/// When the @ref doc_cc_mqtt5_client_unsolicited_disconnect is detected prior to +/// the detection of the network disconnection itself and the broker disconnection +/// report callback is invoked, there is no need to report the network disconnection +/// to the library. +/// /// Inquiry about current network disconnection status can be done using the /// @b cc_mqtt5_client_is_network_disconnected() function. /// @code diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index ca3f984..22dc275 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -152,6 +152,10 @@ 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(false); } @@ -973,6 +977,7 @@ void ClientImpl::brokerDisconnected( termMode = TerminateMode_KeepSendRecvOps; } + m_sessionState.m_disconnecting = true; terminateOps(status, termMode); if (preserveSendRecv) { @@ -1088,7 +1093,6 @@ void ClientImpl::createKeepAliveOpIfNeeded() void ClientImpl::terminateOps(CC_Mqtt5AsyncOpStatus status, TerminateMode mode) { - m_sessionState.m_disconnecting = true; for (auto* op : m_ops) { if (op == nullptr) { continue; diff --git a/client/lib/test/unit/UnitTestDisconnect.th b/client/lib/test/unit/UnitTestDisconnect.th index 59f72e1..75c1dab 100644 --- a/client/lib/test/unit/UnitTestDisconnect.th +++ b/client/lib/test/unit/UnitTestDisconnect.th @@ -11,6 +11,7 @@ public: void test2(); void test3(); void test4(); + void test5(); private: virtual void setUp() override @@ -90,7 +91,7 @@ void UnitTestDisconnect::test4() // Testing rejection any operation (except connect) after disconnect // [MQTT-3.14.4-1] - auto* client = unitTestAllocClient(); + auto* client = unitTestAllocClient(); TS_ASSERT_DIFFERS(client, nullptr); unitTestPerformBasicConnect(client, __FUNCTION__); @@ -118,4 +119,28 @@ void UnitTestDisconnect::test4() auto* disconnect = unitTestDisconnectPrepare(client, &ec); TS_ASSERT_EQUALS(disconnect, nullptr); TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_NotConnected); +} + +void UnitTestDisconnect::test5() +{ + // Testing DISCONNECT message and network disconnection that follows + + auto* client = unitTestAllocClient(); + TS_ASSERT_DIFFERS(client, nullptr); + + unitTestPerformBasicConnect(client, __FUNCTION__); + TS_ASSERT(unitTestIsConnected(client)); + + unitTestTick(100); + UnitTestDisconnectMsg disconnectMsg; + unitTestReceiveMessage(disconnectMsg); + + TS_ASSERT(unitTestIsDisconnected()); + TS_ASSERT(unitTestHasDisconnectInfo()); + auto& disconnectInfo = unitTestDisconnectInfo(); + TS_ASSERT_EQUALS(disconnectInfo.m_reasonCode, CC_Mqtt5ReasonCode_NormalDisconnection); + unitTestPopDisconnectInfo(); + TS_ASSERT(unitTestCheckNoTicks()); + + unitTestNotifyNetworkDisconnected(client); } \ No newline at end of file From 886f2ddcf25cec642e0ec422b18542af746196f2 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sat, 6 Apr 2024 17:36:40 +1000 Subject: [PATCH 27/48] Supporting "Max Qos" value in custom configuration. --- .appveyor.yml | 2 +- .github/workflows/actions_build.yml | 4 +- ...Config.cmake => BareMetalTestConfig.cmake} | 0 .../lib/script/DefineDefaultConfigVars.cmake | 3 +- client/lib/script/Qos0TestConfig.cmake | 5 + client/lib/script/Qos1TestConfig.cmake | 5 + client/lib/script/WriteConfigHeader.cmake | 1 + client/lib/src/ClientImpl.cpp | 68 +++++--- client/lib/src/ClientImpl.h | 7 + client/lib/src/ProtocolDefs.h | 41 ++++- client/lib/src/op/ConnectOp.cpp | 3 +- client/lib/src/op/Op.h | 6 + client/lib/src/op/RecvOp.cpp | 150 +++++++++++------- client/lib/src/op/RecvOp.h | 7 +- client/lib/src/op/SendOp.cpp | 10 +- client/lib/src/op/SendOp.h | 8 +- client/lib/src/op/SubscribeOp.cpp | 18 ++- client/lib/templ/Config.h.templ | 6 + client/lib/test/unit/CMakeLists.txt | 34 ++++ client/lib/test/unit/UnitTestCommonBase.cpp | 56 ++++--- client/lib/test/unit/UnitTestCommonBase.h | 6 + client/lib/test/unit/UnitTestQos0Base.cpp | 109 +++++++++++++ client/lib/test/unit/UnitTestQos0Base.h | 16 ++ client/lib/test/unit/UnitTestQos0Publish.th | 65 ++++++++ client/lib/test/unit/UnitTestQos0Receive.th | 85 ++++++++++ client/lib/test/unit/UnitTestQos0Subscribe.th | 73 +++++++++ client/lib/test/unit/UnitTestQos1Base.cpp | 109 +++++++++++++ client/lib/test/unit/UnitTestQos1Base.h | 16 ++ client/lib/test/unit/UnitTestQos1Publish.th | 77 +++++++++ client/lib/test/unit/UnitTestQos1Receive.th | 53 +++++++ client/lib/test/unit/UnitTestQos1Subscribe.th | 69 ++++++++ client/lib/test/unit/UnitTestReceive.th | 2 +- client/lib/test/unit/UnitTestSubscribe.th | 2 +- doc/custom_client_build.md | 2 +- script/full_build.sh | 3 +- 35 files changed, 996 insertions(+), 125 deletions(-) rename client/lib/script/{BareMetalConfig.cmake => BareMetalTestConfig.cmake} (100%) create mode 100644 client/lib/script/Qos0TestConfig.cmake create mode 100644 client/lib/script/Qos1TestConfig.cmake create mode 100644 client/lib/test/unit/UnitTestQos0Base.cpp create mode 100644 client/lib/test/unit/UnitTestQos0Base.h create mode 100644 client/lib/test/unit/UnitTestQos0Publish.th create mode 100644 client/lib/test/unit/UnitTestQos0Receive.th create mode 100644 client/lib/test/unit/UnitTestQos0Subscribe.th create mode 100644 client/lib/test/unit/UnitTestQos1Base.cpp create mode 100644 client/lib/test/unit/UnitTestQos1Base.h create mode 100644 client/lib/test/unit/UnitTestQos1Publish.th create mode 100644 client/lib/test/unit/UnitTestQos1Receive.th create mode 100644 client/lib/test/unit/UnitTestQos1Subscribe.th diff --git a/.appveyor.yml b/.appveyor.yml index 89cbeb7..aaeb150 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -51,7 +51,7 @@ build_script: - cmake .. -DCMAKE_BUILD_TYPE=%CONFIGURATION% -G "%CMAKE_GENERATOR%" %PLATFORM_PARAM% -DBOOST_ROOT="%BOOST_DIR%" ^ -DBoost_USE_STATIC_LIBS=ON -DCMAKE_CXX_STANDARD=%CPP_STD% -DCMAKE_INSTALL_PREFIX=install ^ -DCMAKE_PREFIX_PATH="%COMMON_INSTALL_DIR%" -DCC_MQTT5_BUILD_UNIT_TESTS=ON -DCC_MQTT5_CLIENT_AFL_FUZZ=ON ^ - -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES=%APPVEYOR_BUILD_FOLDER%/client/lib/script/BareMetalConfig.cmake + -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES=%APPVEYOR_BUILD_FOLDER%/client/lib/script/BareMetalTestConfig.cmake - cmake --build . --config %CONFIGURATION% --target install --parallel %NUMBER_OF_PROCESSORS% diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index 8c3a43c..7b3abe9 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -52,7 +52,7 @@ jobs: -DCMAKE_PREFIX_PATH=${{runner.workspace}}/build/install -DCMAKE_CXX_STANDARD=${{matrix.cpp}} \ -DCC_MQTT5_WITH_DEFAULT_SANITIZERS=ON \ -DCC_MQTT5_BUILD_UNIT_TESTS=ON -DCC_MQTT5_BUILD_INTEGRATION_TESTS=ON \ - -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES=$GITHUB_WORKSPACE/client/lib/script/BareMetalConfig.cmake \ + -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES=$GITHUB_WORKSPACE/client/lib/script/BareMetalTestConfig.cmake \ -DCC_MQTT5_CLIENT_AFL_FUZZ=ON env: CC: gcc-${{matrix.cc_ver}} @@ -122,7 +122,7 @@ jobs: -DCMAKE_PREFIX_PATH=${{runner.workspace}}/build/install -DCMAKE_CXX_STANDARD=${{matrix.cpp}} \ -DCC_MQTT5_WITH_DEFAULT_SANITIZERS=ON \ -DCC_MQTT5_BUILD_UNIT_TESTS=ON -DCC_MQTT5_BUILD_INTEGRATION_TESTS=ON \ - -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES=$GITHUB_WORKSPACE/client/lib/script/BareMetalConfig.cmake \ + -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES=$GITHUB_WORKSPACE/client/lib/script/BareMetalTestConfig.cmake \ -DCC_MQTT5_CLIENT_AFL_FUZZ=ON env: CC: clang-${{matrix.cc_ver}} diff --git a/client/lib/script/BareMetalConfig.cmake b/client/lib/script/BareMetalTestConfig.cmake similarity index 100% rename from client/lib/script/BareMetalConfig.cmake rename to client/lib/script/BareMetalTestConfig.cmake diff --git a/client/lib/script/DefineDefaultConfigVars.cmake b/client/lib/script/DefineDefaultConfigVars.cmake index 05851a8..d97c771 100644 --- a/client/lib/script/DefineDefaultConfigVars.cmake +++ b/client/lib/script/DefineDefaultConfigVars.cmake @@ -34,4 +34,5 @@ set_default_var_value(CC_MQTT5_CLIENT_ASYNC_UNSUBS_LIMIT 0) set_default_var_value(CC_MQTT5_CLIENT_HAS_ERROR_LOG TRUE) set_default_var_value(CC_MQTT5_CLIENT_HAS_TOPIC_FORMAT_VERIFICATION TRUE) set_default_var_value(CC_MQTT5_CLIENT_HAS_SUB_TOPIC_VERIFICATION TRUE) -set_default_var_value(CC_MQTT5_CLIENT_SUB_FILTERS_LIMIT 0) \ No newline at end of file +set_default_var_value(CC_MQTT5_CLIENT_SUB_FILTERS_LIMIT 0) +set_default_var_value(CC_MQTT5_CLIENT_MAX_QOS 2) \ No newline at end of file diff --git a/client/lib/script/Qos0TestConfig.cmake b/client/lib/script/Qos0TestConfig.cmake new file mode 100644 index 0000000..bbd5026 --- /dev/null +++ b/client/lib/script/Qos0TestConfig.cmake @@ -0,0 +1,5 @@ +# Name of the client API +set (CC_MQTT5_CLIENT_CUSTOM_NAME "qos0") + +# Limit to QoS0 +set (CC_MQTT5_CLIENT_MAX_QOS 0) \ No newline at end of file diff --git a/client/lib/script/Qos1TestConfig.cmake b/client/lib/script/Qos1TestConfig.cmake new file mode 100644 index 0000000..adeb483 --- /dev/null +++ b/client/lib/script/Qos1TestConfig.cmake @@ -0,0 +1,5 @@ +# Name of the client API +set (CC_MQTT5_CLIENT_CUSTOM_NAME "qos1") + +# Limit to QoS1 +set (CC_MQTT5_CLIENT_MAX_QOS 1) \ No newline at end of file diff --git a/client/lib/script/WriteConfigHeader.cmake b/client/lib/script/WriteConfigHeader.cmake index 95c7902..e112983 100644 --- a/client/lib/script/WriteConfigHeader.cmake +++ b/client/lib/script/WriteConfigHeader.cmake @@ -63,6 +63,7 @@ replace_in_text (CC_MQTT5_CLIENT_HAS_ERROR_LOG_CPP) replace_in_text (CC_MQTT5_CLIENT_HAS_TOPIC_FORMAT_VERIFICATION_CPP) replace_in_text (CC_MQTT5_CLIENT_HAS_SUB_TOPIC_VERIFICATION_CPP) replace_in_text (CC_MQTT5_CLIENT_SUB_FILTERS_LIMIT) +replace_in_text (CC_MQTT5_CLIENT_MAX_QOS) file (WRITE "${OUT_FILE}.tmp" "${text}") diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 22dc275..9fc26b9 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -668,38 +668,47 @@ void ClientImpl::handle(PublishMsg& msg) }; using Qos = PublishMsg::TransportField_flags::Field_qos::ValueType; - if ((msg.transportField_flags().field_qos().value() == Qos::AtMostOnceDelivery) || - (msg.transportField_flags().field_qos().value() == Qos::AtLeastOnceDelivery)) { + auto qos = msg.transportField_flags().field_qos().value(); + if ((qos == Qos::AtMostOnceDelivery) || + (qos == Qos::AtLeastOnceDelivery)) { createRecvOp(); break; } - 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; - } + 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; + } - if (!msg.transportField_flags().field_dup().getBitValue_bit()) { PubrecMsg pubrecMsg; pubrecMsg.field_packetId().setValue(msg.field_packetId().field().value()); - pubrecMsg.field_reasonCode().setExists(); - pubrecMsg.field_properties().setExists(); - pubrecMsg.field_reasonCode().field().value() = PubackMsg::Field_reasonCode::Field::ValueType::PacketIdInUse; + + if (!msg.transportField_flags().field_dup().getBitValue_bit()) { + pubrecMsg.field_reasonCode().setExists(); + pubrecMsg.field_properties().setExists(); + pubrecMsg.field_reasonCode().field().value() = PubrecMsg::Field_reasonCode::Field::ValueType::PacketIdInUse; + } + else { + // Duplicate detected, just re-confirming + (*iter)->resetTimer(); + } + sendMessage(pubrecMsg); return; } - - // Duplicate attempt to deliver - (*iter)->reset(); - msg.dispatch(**iter); + else { + createRecvOp(); + break; + } } while (false); if (disconnectSent) { @@ -708,8 +717,11 @@ void ClientImpl::handle(PublishMsg& msg) } } +#if CC_MQTT5_CLIENT_MAX_QOS >= 1 void ClientImpl::handle(PubackMsg& msg) { + static_assert(Config::MaxQos >= 1); + for (auto& opPtr : m_keepAliveOps) { msg.dispatch(*opPtr); } @@ -729,9 +741,12 @@ void ClientImpl::handle(PubackMsg& msg) msg.dispatch(**iter); } +#endif // #if CC_MQTT5_CLIENT_MAX_QOS >= 1 +#if CC_MQTT5_CLIENT_MAX_QOS >= 2 void ClientImpl::handle(PubrecMsg& msg) { + static_assert(Config::MaxQos >= 2); for (auto& opPtr : m_keepAliveOps) { msg.dispatch(*opPtr); } @@ -750,7 +765,7 @@ void ClientImpl::handle(PubrecMsg& msg) pubrelMsg.field_packetId().setValue(msg.field_packetId().value()); pubrelMsg.field_reasonCode().setExists(); pubrelMsg.field_properties().setExists(); - pubrelMsg.field_reasonCode().field().value() = PubackMsg::Field_reasonCode::Field::ValueType::PacketIdNotFound; + pubrelMsg.field_reasonCode().field().value() = PubrecMsg::Field_reasonCode::Field::ValueType::PacketIdNotFound; sendMessage(pubrelMsg); return; } @@ -760,6 +775,7 @@ void ClientImpl::handle(PubrecMsg& msg) void ClientImpl::handle(PubrelMsg& msg) { + static_assert(Config::MaxQos >= 2); for (auto& opPtr : m_keepAliveOps) { msg.dispatch(*opPtr); } @@ -769,6 +785,7 @@ void ClientImpl::handle(PubrelMsg& msg) m_recvOps.begin(), m_recvOps.end(), [&msg](auto& opPtr) { + COMMS_ASSERT(opPtr); return opPtr->packetId() == msg.field_packetId().value(); }); @@ -778,12 +795,11 @@ void ClientImpl::handle(PubrelMsg& msg) pubcompMsg.field_packetId().setValue(msg.field_packetId().value()); pubcompMsg.field_reasonCode().setExists(); pubcompMsg.field_properties().setExists(); - pubcompMsg.field_reasonCode().field().value() = PubackMsg::Field_reasonCode::Field::ValueType::PacketIdNotFound; + pubcompMsg.field_reasonCode().field().value() = PubcompMsg::Field_reasonCode::Field::ValueType::PacketIdNotFound; sendMessage(pubcompMsg); return; } - msg.dispatch(**iter); } @@ -809,6 +825,8 @@ void ClientImpl::handle(PubcompMsg& msg) msg.dispatch(**iter); } +#endif // #if CC_MQTT5_CLIENT_MAX_QOS >= 2 + void ClientImpl::handle(ProtMessage& msg) { if (m_sessionState.m_disconnecting) { diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index c42cba1..8021d7f 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -135,10 +135,17 @@ class ClientImpl final : public ProtMsgHandler using Base::handle; virtual void handle(PublishMsg& msg) override; + +#if CC_MQTT5_CLIENT_MAX_QOS >= 1 virtual void handle(PubackMsg& msg) override; +#endif // #if CC_MQTT5_CLIENT_MAX_QOS >= 1 + +#if CC_MQTT5_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_MQTT5_CLIENT_MAX_QOS >= 2 + virtual void handle(ProtMessage& msg) override; // -------------------- Ops Access API ----------------------------- diff --git a/client/lib/src/ProtocolDefs.h b/client/lib/src/ProtocolDefs.h index a78d1e5..15e7308 100644 --- a/client/lib/src/ProtocolDefs.h +++ b/client/lib/src/ProtocolDefs.h @@ -7,6 +7,7 @@ #pragma once +#include "Config.h" #include "ProtocolOptions.h" #include "cc_mqtt5/Message.h" @@ -37,10 +38,46 @@ using ProtMessage = cc_mqtt5::Message< CC_MQTT5_ALIASES_FOR_ALL_MESSAGES(, Msg, ProtMessage, ProtocolOptions) -using ProtFrame = cc_mqtt5::frame::Frame, ProtocolOptions>; +template +using Qos1ClientInputMessages = + std::tuple< + cc_mqtt5::message::Connack, + cc_mqtt5::message::Publish, + cc_mqtt5::message::Puback, + cc_mqtt5::message::Suback, + cc_mqtt5::message::Unsuback, + cc_mqtt5::message::Pingresp, + cc_mqtt5::message::Disconnect, + cc_mqtt5::message::Auth + >; + +template +using Qos0ClientInputMessages = + std::tuple< + cc_mqtt5::message::Connack, + cc_mqtt5::message::Publish, + cc_mqtt5::message::Suback, + cc_mqtt5::message::Unsuback, + cc_mqtt5::message::Pingresp, + cc_mqtt5::message::Disconnect, + cc_mqtt5::message::Auth + >; + +using ProtInputMessages = + std::conditional_t< + 2 <= Config::MaxQos, + cc_mqtt5::input::ClientInputMessages, + std::conditional_t< + 1 == Config::MaxQos, + Qos1ClientInputMessages, + Qos0ClientInputMessages + > + >; + +using ProtFrame = cc_mqtt5::frame::Frame; using ProtMsgPtr = ProtFrame::MsgPtr; -class ProtMsgHandler : public comms::GenericHandler > +class ProtMsgHandler : public comms::GenericHandler { protected: ProtMsgHandler() = default; diff --git a/client/lib/src/op/ConnectOp.cpp b/client/lib/src/op/ConnectOp.cpp index 3a1dc5e..28843c8 100644 --- a/client/lib/src/op/ConnectOp.cpp +++ b/client/lib/src/op/ConnectOp.cpp @@ -122,7 +122,8 @@ CC_Mqtt5ErrorCode ConnectOp::configWill(const CC_Mqtt5ConnectWillConfig& config) return CC_Mqtt5ErrorCode_BadParam; } - if ((config.m_qos < CC_Mqtt5QoS_AtMostOnceDelivery) || (config.m_qos > CC_Mqtt5QoS_ExactlyOnceDelivery)) { + if ((config.m_qos < CC_Mqtt5QoS_AtMostOnceDelivery) || + (static_cast(Config::MaxQos) < config.m_qos)) { errorLog("Invalid will QoS value in configuration."); return CC_Mqtt5ErrorCode_BadParam; } diff --git a/client/lib/src/op/Op.h b/client/lib/src/op/Op.h index 8bb83cf..db7159a 100644 --- a/client/lib/src/op/Op.h +++ b/client/lib/src/op/Op.h @@ -67,6 +67,12 @@ class Op : public ProtMsgHandler connectivityChangedImpl(); } + inline + static bool verifyQosValid(PublishMsg::TransportField_flags::Field_qos::ValueType qos) + { + return (qos <= static_cast(Config::MaxQos)); + } + protected: using UserPropsList = ObjListType; using DisconnectReason = DisconnectMsg::Field_reasonCode::Field::ValueType; diff --git a/client/lib/src/op/RecvOp.cpp b/client/lib/src/op/RecvOp.cpp index 6349f38..9b8f735 100644 --- a/client/lib/src/op/RecvOp.cpp +++ b/client/lib/src/op/RecvOp.cpp @@ -96,36 +96,40 @@ void RecvOp::handle(PublishMsg& msg) using Qos = PublishMsg::TransportField_flags::Field_qos::ValueType; auto qos = msg.transportField_flags().field_qos().value(); - if (qos > Qos::ExactlyOnceDelivery) { + if (!verifyQosValid(qos)) { terminationWithReason(DisconnectReason::QosNotSupported); return; } - if ((qos == Qos::ExactlyOnceDelivery) && - (m_packetId != 0U) && - (msg.field_packetId().doesExist())) { - - if (msg.field_packetId().field().value() != m_packetId) { - // Applicable to other RecvOp being handled in parallel + if constexpr (Config::MaxQos >= 2) { + if ((qos == Qos::ExactlyOnceDelivery) && + (m_packetId != 0U) && + (msg.field_packetId().doesExist())) { + + if (msg.field_packetId().field().value() != m_packetId) { + // Applicable to other RecvOp being handled in parallel + return; + } + + // If dispatched to this op, duplicate has been detected + COMMS_ASSERT(msg.transportField_flags().field_dup().getBitValue_bit()); + PubrecMsg pubrecMsg; + pubrecMsg.field_packetId().setValue(m_packetId); + sendMessage(pubrecMsg); + restartResponseTimer(); return; } - - // If dispatched to this op, duplicate has been detected - COMMS_ASSERT(msg.transportField_flags().field_dup().getBitValue_bit()); - PubrecMsg pubrecMsg; - pubrecMsg.field_packetId().setValue(m_packetId); - sendMessage(pubrecMsg); - restartResponseTimer(); - return; } auto& sessionState = client().sessionState(); - if ((qos > Qos::AtMostOnceDelivery) && - (0U < sessionState.m_highQosRecvLimit) && - (sessionState.m_highQosRecvLimit < client().recvsCount())) { - terminationWithReason(DisconnectReason::ReceiveMaxExceeded); - return; + if constexpr (Config::MaxQos >= 1) { + if ((qos > Qos::AtMostOnceDelivery) && + (0U < sessionState.m_highQosRecvLimit) && + (sessionState.m_highQosRecvLimit < client().recvsCount())) { + terminationWithReason(DisconnectReason::ReceiveMaxExceeded); + return; + } } auto completeNotAuthorized = @@ -146,14 +150,18 @@ void RecvOp::handle(PublishMsg& msg) break; } - if (qos == Qos::AtLeastOnceDelivery) { - PubackMsg pubackMsg; - sendNotAuthorized(pubackMsg); - break; + if constexpr (Config::MaxQos >= 1) { + if (qos == Qos::AtLeastOnceDelivery) { + PubackMsg pubackMsg; + sendNotAuthorized(pubackMsg); + break; + } } - PubrecMsg pubrecMsg; - sendNotAuthorized(pubrecMsg); + if constexpr (Config::MaxQos >= 2) { + PubrecMsg pubrecMsg; + sendNotAuthorized(pubrecMsg); + } break; } while (false); @@ -173,6 +181,9 @@ void RecvOp::handle(PublishMsg& msg) return; } + UserPropsList userProps; + SubIdsStorage subIds; + PropsHandler propsHandler; for (auto& p : msg.field_properties().value()) { p.currentFieldExec(propsHandler); @@ -271,9 +282,9 @@ void RecvOp::handle(PublishMsg& msg) if constexpr (Config::HasUserProps) { if (!propsHandler.m_userProps.empty()) { - fillUserProps(propsHandler, m_userProps); - comms::cast_assign(info.m_userPropsCount) = m_userProps.size(); - info.m_userProps = &m_userProps[0]; + fillUserProps(propsHandler, userProps); + comms::cast_assign(info.m_userPropsCount) = userProps.size(); + info.m_userProps = &userProps[0]; } } @@ -285,13 +296,13 @@ void RecvOp::handle(PublishMsg& msg) } if (!propsHandler.m_subscriptionIds.empty()) { - m_subIds.reserve(propsHandler.m_subscriptionIds.size()); + subIds.reserve(propsHandler.m_subscriptionIds.size()); for (auto* id : propsHandler.m_subscriptionIds) { - m_subIds.push_back(id->field_value().value()); + subIds.push_back(id->field_value().value()); } - info.m_subIds = &m_subIds[0]; - comms::cast_assign(info.m_subIdsCount) = m_subIds.size(); + info.m_subIds = &subIds[0]; + comms::cast_assign(info.m_subIdsCount) = subIds.size(); } comms::cast_assign(info.m_qos) = qos; @@ -321,23 +332,29 @@ void RecvOp::handle(PublishMsg& msg) client().reportMsgInfo(info); - if (qos == Qos::AtLeastOnceDelivery) { - PubackMsg pubackMsg; - pubackMsg.field_packetId().value() = msg.field_packetId().field().value(); - sendMessage(pubackMsg); - opComplete(); - return; - } + if constexpr (Config::MaxQos >= 1) { + if (qos == Qos::AtLeastOnceDelivery) { + PubackMsg pubackMsg; + pubackMsg.field_packetId().value() = msg.field_packetId().field().value(); + sendMessage(pubackMsg); + opComplete(); + return; + } + } - m_packetId = msg.field_packetId().field().value(); - PubrecMsg pubrecMsg; - pubrecMsg.field_packetId().setValue(m_packetId); - sendMessage(pubrecMsg); - restartResponseTimer(); + if constexpr (Config::MaxQos >= 2) { + m_packetId = msg.field_packetId().field().value(); + PubrecMsg pubrecMsg; + pubrecMsg.field_packetId().setValue(m_packetId); + sendMessage(pubrecMsg); + restartResponseTimer(); + } } +#if CC_MQTT5_CLIENT_MAX_QOS >= 2 void RecvOp::handle(PubrelMsg& msg) { + static_assert(Config::MaxQos >= 2); if (msg.field_packetId().value() != m_packetId) { return; } @@ -392,18 +409,21 @@ void RecvOp::handle(PubrelMsg& msg) sendMessage(pubcompMsg); opComplete(); } +#endif // #if CC_MQTT5_CLIENT_MAX_QOS >= 2 -void RecvOp::reset() +void RecvOp::resetTimer() { - m_responseTimer.cancel(); - m_userProps.clear(); - m_subIds.clear(); + if constexpr (Config::MaxQos >= 2) { + m_responseTimer.cancel(); + } } void RecvOp::postReconnectionResume() { - connectivityChangedImpl(); - restartResponseTimer(); + if constexpr (Config::MaxQos >= 2) { + connectivityChangedImpl(); + restartResponseTimer(); + } } Op::Type RecvOp::typeImpl() const @@ -413,28 +433,36 @@ Op::Type RecvOp::typeImpl() const void RecvOp::connectivityChangedImpl() { - m_responseTimer.setSuspended( - (!client().sessionState().m_connected) || client().clientState().m_networkDisconnected); + if constexpr (Config::MaxQos >= 2) { + m_responseTimer.setSuspended( + (!client().sessionState().m_connected) || client().clientState().m_networkDisconnected); + } } void RecvOp::restartResponseTimer() { - auto& state = client().configState(); - m_responseTimer.wait(state.m_responseTimeoutMs, &RecvOp::recvTimeoutCb, this); + if constexpr (Config::MaxQos >= 2) { + auto& state = client().configState(); + m_responseTimer.wait(state.m_responseTimeoutMs, &RecvOp::recvTimeoutCb, this); + } } void RecvOp::responseTimeoutInternal() { - // When there is no response from broker, just terminate the reception. - // The retry will be initiated by the broker. - COMMS_ASSERT(!m_responseTimer.isActive()); - errorLog("Timeout on PUBREL reception from broker."); - opComplete(); + if constexpr (Config::MaxQos >= 2) { + // When there is no response from broker, just terminate the reception. + // The retry will be initiated by the broker. + COMMS_ASSERT(!m_responseTimer.isActive()); + errorLog("Timeout on PUBREL reception from broker."); + opComplete(); + } } void RecvOp::recvTimeoutCb(void* data) { - asRecvOp(data)->responseTimeoutInternal(); + if constexpr (Config::MaxQos >= 2) { + asRecvOp(data)->responseTimeoutInternal(); + } } } // namespace op diff --git a/client/lib/src/op/RecvOp.h b/client/lib/src/op/RecvOp.h index deef2bb..ff67a4e 100644 --- a/client/lib/src/op/RecvOp.h +++ b/client/lib/src/op/RecvOp.h @@ -28,14 +28,17 @@ class RecvOp final : public Op using Base::handle; void handle(PublishMsg& msg) override; + +#if CC_MQTT5_CLIENT_MAX_QOS >= 2 void handle(PubrelMsg& msg) override; +#endif // #if CC_MQTT5_CLIENT_MAX_QOS >= 2 unsigned packetId() const { return m_packetId; } - void reset(); + void resetTimer(); void postReconnectionResume(); protected: @@ -61,8 +64,6 @@ class RecvOp final : public Op TimerMgr::Timer m_responseTimer; TopicStr m_topicStr; - UserPropsList m_userProps; - SubIdsStorage m_subIds; unsigned m_packetId = 0U; static_assert(ExtConfig::RecvOpTimers == 1U); diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index 0dd8c1d..e6935f4 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -38,8 +38,10 @@ SendOp::~SendOp() releasePacketId(m_pubMsg.field_packetId().field().value()); } +#if CC_MQTT5_CLIENT_MAX_QOS >= 1 void SendOp::handle(PubackMsg& msg) { + static_assert(Config::MaxQos >= 1); if (m_pubMsg.field_packetId().field().value() != msg.field_packetId().value()) { return; } @@ -122,9 +124,12 @@ void SendOp::handle(PubackMsg& msg) terminateOnExit.release(); status = CC_Mqtt5AsyncOpStatus_Complete; } +#endif // #if CC_MQTT5_CLIENT_MAX_QOS >= 1 +#if CC_MQTT5_CLIENT_MAX_QOS >= 2 void SendOp::handle(PubrecMsg& msg) { + static_assert(Config::MaxQos >= 2); if (m_pubMsg.field_packetId().field().value() != msg.field_packetId().value()) { return; } @@ -235,6 +240,7 @@ void SendOp::handle(PubrecMsg& msg) void SendOp::handle(PubcompMsg& msg) { + static_assert(Config::MaxQos >= 2); if (m_pubMsg.field_packetId().field().value() != msg.field_packetId().value()) { return; } @@ -316,6 +322,7 @@ void SendOp::handle(PubcompMsg& msg) terminateOnExit.release(); status = CC_Mqtt5AsyncOpStatus_Complete; } +#endif // #if CC_MQTT5_CLIENT_MAX_QOS >= 2 CC_Mqtt5ErrorCode SendOp::configBasic(const CC_Mqtt5PublishBasicConfig& config) { @@ -331,7 +338,8 @@ CC_Mqtt5ErrorCode SendOp::configBasic(const CC_Mqtt5PublishBasicConfig& config) auto& state = client().sessionState(); - if (config.m_qos > state.m_pubMaxQos) { + if ((config.m_qos > state.m_pubMaxQos) || + (config.m_qos > static_cast(Config::MaxQos))) { errorLog("QoS value is too high in publish."); return CC_Mqtt5ErrorCode_BadParam; } diff --git a/client/lib/src/op/SendOp.h b/client/lib/src/op/SendOp.h index dbbf16c..3b377bb 100644 --- a/client/lib/src/op/SendOp.h +++ b/client/lib/src/op/SendOp.h @@ -28,9 +28,15 @@ class SendOp final : public Op ~SendOp(); using Base::handle; + +#if CC_MQTT5_CLIENT_MAX_QOS >= 1 virtual void handle(PubackMsg& msg) override; +#endif // #if CC_MQTT5_CLIENT_MAX_QOS >= 1 + +#if CC_MQTT5_CLIENT_MAX_QOS >= 2 virtual void handle(PubrecMsg& msg) override; virtual void handle(PubcompMsg& msg) override; +#endif // #if CC_MQTT5_CLIENT_MAX_QOS >= 2 CC_Mqtt5PublishHandle toHandle() { @@ -105,7 +111,7 @@ class SendOp final : public Op void* m_cbData = nullptr; unsigned m_totalSendAttempts = DefaultSendAttempts; unsigned m_sendAttempts = 0U; - CC_Mqtt5ReasonCode m_reasonCode = CC_Mqtt5ReasonCode_Success; + [[maybe_unused]] CC_Mqtt5ReasonCode m_reasonCode = CC_Mqtt5ReasonCode_Success; bool m_outOfOrderAllowed = false; bool m_sent = false; bool m_acked = false; diff --git a/client/lib/src/op/SubscribeOp.cpp b/client/lib/src/op/SubscribeOp.cpp index 52b1419..4e026f5 100644 --- a/client/lib/src/op/SubscribeOp.cpp +++ b/client/lib/src/op/SubscribeOp.cpp @@ -72,7 +72,7 @@ CC_Mqtt5ErrorCode SubscribeOp::configTopic(const CC_Mqtt5SubscribeTopicConfig& c return CC_Mqtt5ErrorCode_BadParam; } - if (CC_Mqtt5QoS_ValuesLimit <= config.m_maxQos) { + if (static_cast(Config::MaxQos) < config.m_maxQos) { errorLog("Bad subscription qos value."); return CC_Mqtt5ErrorCode_BadParam; } @@ -241,7 +241,8 @@ void SubscribeOp::handle(SubackMsg& msg) return; } - reasonCodes.reserve(std::min(reasonCodesVec.size(), reasonCodesVec.max_size())); + auto maxReasonCodesCount = std::min(reasonCodesVec.size(), reasonCodes.max_size()); + reasonCodes.reserve(maxReasonCodesCount); for (auto idx = 0U; idx < reasonCodesVec.size(); ++idx) { auto rc = msg.field_list().value()[idx]; if (reasonCodes.max_size() <= idx) { @@ -251,7 +252,18 @@ void SubscribeOp::handle(SubackMsg& msg) return; } - reasonCodes.push_back(static_cast(rc.value())); + auto rcCasted = static_cast(rc.value()); + + if ((CC_Mqtt5ReasonCode_GrantedQos0 <= rcCasted) && (rcCasted <= CC_Mqtt5ReasonCode_GrantedQos2)) { + auto ackQos = static_cast(rcCasted) - static_cast(CC_Mqtt5ReasonCode_GrantedQos0); + auto reqQos = static_cast(topicsVec[idx].field_options().field_qos().value()); + if (reqQos < ackQos) { + errorLog("Granted QoS in SUBACK is greater than requested"); + return; // protocol error will be reported + } + } + + reasonCodes.push_back(rcCasted); if constexpr (Config::HasSubTopicVerification) { if (!client().configState().m_verifySubFilter) { diff --git a/client/lib/templ/Config.h.templ b/client/lib/templ/Config.h.templ index 09e5f24..e159101 100644 --- a/client/lib/templ/Config.h.templ +++ b/client/lib/templ/Config.h.templ @@ -23,6 +23,7 @@ struct Config static constexpr bool HasTopicFormatVerification = ##CC_MQTT5_CLIENT_HAS_TOPIC_FORMAT_VERIFICATION_CPP##; static constexpr bool HasSubTopicVerification = ##CC_MQTT5_CLIENT_HAS_SUB_TOPIC_VERIFICATION_CPP##; static constexpr unsigned SubFiltersLimit = ##CC_MQTT5_CLIENT_SUB_FILTERS_LIMIT##; + static constexpr unsigned MaxQos = ##CC_MQTT5_CLIENT_MAX_QOS##; static_assert(HasDynMemAlloc || (ClientAllocLimit > 0U), "Must use CC_MQTT5_CLIENT_ALLOC_LIMIT in configuration to limit number of clients"); static_assert(HasDynMemAlloc || (StringFieldFixedLen > 0U), "Must use CC_MQTT5_CLIENT_STRING_FIELD_FIXED_LEN in configuration to limit string field length"); @@ -37,6 +38,11 @@ struct Config static_assert(HasDynMemAlloc || (!HasSubTopicVerification) || (SubFiltersLimit > 0U), "Must use CC_MQTT5_CLIENT_SUB_FILTERS_LIMIT in configuration to limit amount of subscribe filters"); static_assert(HasUserProps || (UserPropsLimit == 0), "CC_MQTT5_CLIENT_USER_PROPS_LIMIT is expected to be 0 when user props are disabled"); + static_assert(MaxQos <= 2, "Not supported QoS value"); }; } // namespace cc_mqtt5_client + +#ifndef CC_MQTT5_CLIENT_MAX_QOS +#define CC_MQTT5_CLIENT_MAX_QOS ##CC_MQTT5_CLIENT_MAX_QOS## +#endif \ No newline at end of file diff --git a/client/lib/test/unit/CMakeLists.txt b/client/lib/test/unit/CMakeLists.txt index 5fe61e7..ec42f36 100644 --- a/client/lib/test/unit/CMakeLists.txt +++ b/client/lib/test/unit/CMakeLists.txt @@ -70,3 +70,37 @@ if (TARGET cc::cc_mqtt5_bm_client) cc_mqtt5_client_add_unit_test(UnitTestBmPublish ${BM_BASE_LIB_NAME}) cc_mqtt5_client_add_unit_test(UnitTestBmReceive ${BM_BASE_LIB_NAME}) endif () + +if (TARGET cc::cc_mqtt5_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_mqtt5_qos1_client) + target_include_directories( + ${QOS1_BASE_LIB_NAME} INTERFACE + $ + ) + + cc_mqtt5_client_add_unit_test(UnitTestQos1Publish ${QOS1_BASE_LIB_NAME}) + cc_mqtt5_client_add_unit_test(UnitTestQos1Receive ${QOS1_BASE_LIB_NAME}) + cc_mqtt5_client_add_unit_test(UnitTestQos1Subscribe ${QOS1_BASE_LIB_NAME}) +endif () + +if (TARGET cc::cc_mqtt5_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_mqtt5_qos0_client) + target_include_directories( + ${QOS0_BASE_LIB_NAME} INTERFACE + $ + ) + + cc_mqtt5_client_add_unit_test(UnitTestQos0Publish ${QOS0_BASE_LIB_NAME}) + cc_mqtt5_client_add_unit_test(UnitTestQos0Receive ${QOS0_BASE_LIB_NAME}) + cc_mqtt5_client_add_unit_test(UnitTestQos0Subscribe ${QOS0_BASE_LIB_NAME}) +endif () diff --git a/client/lib/test/unit/UnitTestCommonBase.cpp b/client/lib/test/unit/UnitTestCommonBase.cpp index fc93ad8..c06b0ac 100644 --- a/client/lib/test/unit/UnitTestCommonBase.cpp +++ b/client/lib/test/unit/UnitTestCommonBase.cpp @@ -856,21 +856,13 @@ void UnitTestCommonBase::unitTestPerformBasicDisconnect(CC_Mqtt5Client* client, unitTestPerformDisconnect(client, &config); } -void UnitTestCommonBase::unitTestPerformBasicSubscribe(CC_Mqtt5Client* client, const char* topic, unsigned subId) +void UnitTestCommonBase::unitTestPerformSubscribe( + CC_Mqtt5Client* client, + CC_Mqtt5SubscribeTopicConfig* topicConfigs, + unsigned topicConfigsCount, + const CC_Mqtt5SubscribeExtraConfig* extraConfig) { - auto config = CC_Mqtt5SubscribeTopicConfig(); - unitTestSubscribeInitConfigTopic(&config); - config.m_topic = topic; - - auto extra = CC_Mqtt5SubscribeExtraConfig(); - const CC_Mqtt5SubscribeExtraConfig* extraPtr = nullptr; - - if (subId > 0U) { - extra.m_subId = subId; - extraPtr = &extra; - } - - [[maybe_unused]] auto ec = m_funcs.m_subscribe_full(client, &config, 1U, extraPtr, &UnitTestCommonBase::unitTestSubscribeCompleteCb, this); + auto ec = m_funcs.m_subscribe_full(client, topicConfigs, topicConfigsCount, extraConfig, &UnitTestCommonBase::unitTestSubscribeCompleteCb, this); test_assert(ec == CC_Mqtt5ErrorCode_Success); test_assert(!unitTestIsSubscribeComplete()); @@ -879,31 +871,50 @@ void UnitTestCommonBase::unitTestPerformBasicSubscribe(CC_Mqtt5Client* client, c test_assert(sentMsg->getId() == cc_mqtt5::MsgId_Subscribe); [[maybe_unused]] auto* subscribeMsg = dynamic_cast(sentMsg.get()); test_assert(subscribeMsg != nullptr); - if (subId > 0U) { + if ((extraConfig != nullptr) && (extraConfig->m_subId > 0U)) { UnitTestPropsHandler propsHandler; for (auto& p : subscribeMsg->field_properties().value()) { p.currentFieldExec(propsHandler); } assert (!propsHandler.m_subscriptionIds.empty()); - test_assert(propsHandler.m_subscriptionIds.front()->field_value().value() == subId); + test_assert(propsHandler.m_subscriptionIds.front()->field_value().value() == extraConfig->m_subId); } unitTestTick(1000); UnitTestSubackMsg subackMsg; subackMsg.field_packetId().value() = subscribeMsg->field_packetId().value(); - subackMsg.field_list().value().resize(1); - subackMsg.field_list().value()[0].setValue(CC_Mqtt5ReasonCode_GrantedQos2); + subackMsg.field_list().value().resize(topicConfigsCount); + for (auto idx = 0U; idx < topicConfigsCount; ++idx) { + subackMsg.field_list().value()[0].setValue(static_cast(CC_Mqtt5ReasonCode_GrantedQos0) + topicConfigs[idx].m_maxQos); + } + unitTestReceiveMessage(subackMsg); test_assert(unitTestIsSubscribeComplete()); [[maybe_unused]] auto& subackInfo = unitTestSubscribeResponseInfo(); test_assert(subackInfo.m_status == CC_Mqtt5AsyncOpStatus_Complete); - test_assert(subackInfo.m_response.m_reasonCodes.size() == 1U); - test_assert(subackInfo.m_response.m_reasonCodes[0] == CC_Mqtt5ReasonCode_GrantedQos2); + test_assert(subackInfo.m_response.m_reasonCodes.size() == topicConfigsCount); unitTestPopSubscribeResponseInfo(); } +void UnitTestCommonBase::unitTestPerformBasicSubscribe(CC_Mqtt5Client* client, const char* topic, unsigned subId) +{ + auto config = CC_Mqtt5SubscribeTopicConfig(); + unitTestSubscribeInitConfigTopic(&config); + config.m_topic = topic; + + auto extra = CC_Mqtt5SubscribeExtraConfig(); + const CC_Mqtt5SubscribeExtraConfig* extraPtr = nullptr; + + if (subId > 0U) { + extra.m_subId = subId; + extraPtr = &extra; + } + + unitTestPerformSubscribe(client, &config, 1U, extraPtr); +} + void UnitTestCommonBase::unitTestVerifyDisconnectSent(UnitTestDisconnectReason reason) { test_assert(unitTestHasSentMessage()); @@ -1066,6 +1077,11 @@ CC_Mqtt5ErrorCode UnitTestCommonBase::unitTestSubscribeAddUserProp(CC_Mqtt5Subsc return m_funcs.m_subscribe_add_user_prop(handle, prop); } +CC_Mqtt5ErrorCode UnitTestCommonBase::unitTestSubscribeSimple(CC_Mqtt5Client* client, CC_Mqtt5SubscribeTopicConfig* config) +{ + return m_funcs.m_subscribe_simple(client, config, &UnitTestCommonBase::unitTestSubscribeCompleteCb, this); +} + CC_Mqtt5UnsubscribeHandle UnitTestCommonBase::unitTestUnsubscribePrepare(CC_Mqtt5Client* client, CC_Mqtt5ErrorCode* ec) { return m_funcs.m_unsubscribe_prepare(client, ec); diff --git a/client/lib/test/unit/UnitTestCommonBase.h b/client/lib/test/unit/UnitTestCommonBase.h index b470b22..d2c33a0 100644 --- a/client/lib/test/unit/UnitTestCommonBase.h +++ b/client/lib/test/unit/UnitTestCommonBase.h @@ -440,6 +440,11 @@ class UnitTestCommonBase void unitTestPerformBasicDisconnect(CC_Mqtt5Client* client, CC_Mqtt5ReasonCode reasonCode = CC_Mqtt5ReasonCode_NormalDisconnection); + void unitTestPerformSubscribe( + CC_Mqtt5Client* client, + CC_Mqtt5SubscribeTopicConfig* topicConfigs, + unsigned topicConfigsCount = 1U, + const CC_Mqtt5SubscribeExtraConfig* extraConfig = nullptr); void unitTestPerformBasicSubscribe(CC_Mqtt5Client* client, const char* topic, unsigned subId = 0U); using UnitTestDisconnectReason = UnitTestDisconnectMsg::Field_reasonCode::Field::ValueType; @@ -474,6 +479,7 @@ class UnitTestCommonBase CC_Mqtt5ErrorCode unitTestSubscribeConfigTopic(CC_Mqtt5SubscribeHandle handle, const CC_Mqtt5SubscribeTopicConfig* config); CC_Mqtt5ErrorCode unitTestSubscribeConfigExtra(CC_Mqtt5SubscribeHandle handle, const CC_Mqtt5SubscribeExtraConfig* config); CC_Mqtt5ErrorCode unitTestSubscribeAddUserProp(CC_Mqtt5SubscribeHandle handle, const CC_Mqtt5UserProp* prop); + CC_Mqtt5ErrorCode unitTestSubscribeSimple(CC_Mqtt5Client* client, CC_Mqtt5SubscribeTopicConfig* config); CC_Mqtt5UnsubscribeHandle unitTestUnsubscribePrepare(CC_Mqtt5Client* client, CC_Mqtt5ErrorCode* ec); CC_Mqtt5ErrorCode unitTestUnsubscribeSetResponseTimeout(CC_Mqtt5UnsubscribeHandle handle, unsigned ms); void unitTestUnsubscribeInitConfigTopic(CC_Mqtt5UnsubscribeTopicConfig* config); diff --git a/client/lib/test/unit/UnitTestQos0Base.cpp b/client/lib/test/unit/UnitTestQos0Base.cpp new file mode 100644 index 0000000..426958a --- /dev/null +++ b/client/lib/test/unit/UnitTestQos0Base.cpp @@ -0,0 +1,109 @@ +#include "UnitTestQos0Base.h" + +#include "qos0_client.h" + +const UnitTestQos0Base::LibFuncs& UnitTestQos0Base::getFuncs() +{ + static LibFuncs funcs; + funcs.m_alloc = &cc_mqtt5_qos0_client_alloc; + funcs.m_free = &cc_mqtt5_qos0_client_free; + funcs.m_tick = &cc_mqtt5_qos0_client_tick; + funcs.m_process_data = &cc_mqtt5_qos0_client_process_data; + funcs.m_notify_network_disconnected = &cc_mqtt5_qos0_client_notify_network_disconnected; + funcs.m_is_network_disconnected = &cc_mqtt5_qos0_client_is_network_disconnected; + funcs.m_set_default_response_timeout = &cc_mqtt5_qos0_client_set_default_response_timeout; + funcs.m_get_default_response_timeout = &cc_mqtt5_qos0_client_get_default_response_timeout; + funcs.m_pub_topic_alias_alloc = &cc_mqtt5_qos0_client_pub_topic_alias_alloc; + funcs.m_pub_topic_alias_free = &cc_mqtt5_qos0_client_pub_topic_alias_free; + funcs.m_pub_topic_alias_count = &cc_mqtt5_qos0_client_pub_topic_alias_count; + funcs.m_pub_topic_alias_is_allocated = &cc_mqtt5_qos0_client_pub_topic_alias_is_allocated; + funcs.m_set_verify_outgoing_topic_enabled = &cc_mqtt5_qos0_client_set_verify_outgoing_topic_enabled; + funcs.m_get_verify_outgoing_topic_enabled = &cc_mqtt5_qos0_client_get_verify_outgoing_topic_enabled; + funcs.m_set_verify_incoming_topic_enabled = &cc_mqtt5_qos0_client_set_verify_incoming_topic_enabled; + funcs.m_get_verify_incoming_topic_enabled = &cc_mqtt5_qos0_client_get_verify_incoming_topic_enabled; + funcs.m_set_verify_incoming_msg_subscribed = &cc_mqtt5_qos0_client_set_verify_incoming_msg_subscribed; + funcs.m_get_verify_incoming_msg_subscribed = &cc_mqtt5_qos0_client_get_verify_incoming_msg_subscribed; + funcs.m_init_user_prop = &cc_mqtt5_qos0_client_init_user_prop; + funcs.m_connect_prepare = &cc_mqtt5_qos0_client_connect_prepare; + funcs.m_connect_init_config_basic = &cc_mqtt5_qos0_client_connect_init_config_basic; + funcs.m_connect_init_config_will = &cc_mqtt5_qos0_client_connect_init_config_will; + funcs.m_connect_init_config_extra = &cc_mqtt5_qos0_client_connect_init_config_extra; + funcs.m_connect_init_config_auth = &cc_mqtt5_qos0_client_connect_init_config_auth; + funcs.m_connect_init_auth_info = &cc_mqtt5_qos0_client_connect_init_auth_info; + funcs.m_connect_set_response_timeout = &cc_mqtt5_qos0_client_connect_set_response_timeout; + funcs.m_connect_get_response_timeout = &cc_mqtt5_qos0_client_connect_get_response_timeout; + funcs.m_connect_config_basic = &cc_mqtt5_qos0_client_connect_config_basic; + funcs.m_connect_config_will = &cc_mqtt5_qos0_client_connect_config_will; + funcs.m_connect_config_extra = &cc_mqtt5_qos0_client_connect_config_extra; + funcs.m_connect_config_auth = &cc_mqtt5_qos0_client_connect_config_auth; + funcs.m_connect_add_user_prop = &cc_mqtt5_qos0_client_connect_add_user_prop; + funcs.m_connect_add_will_user_prop = &cc_mqtt5_qos0_client_connect_add_will_user_prop; + funcs.m_connect_send = &cc_mqtt5_qos0_client_connect_send; + funcs.m_connect_cancel = &cc_mqtt5_qos0_client_connect_cancel; + funcs.m_connect_simple = &cc_mqtt5_qos0_client_connect_simple; + funcs.m_connect_full = &cc_mqtt5_qos0_client_connect_full; + funcs.m_is_connected = &cc_mqtt5_qos0_client_is_connected; + funcs.m_disconnect_prepare = &cc_mqtt5_qos0_client_disconnect_prepare; + funcs.m_disconnect_init_config = &cc_mqtt5_qos0_client_disconnect_init_config; + funcs.m_disconnect_config = &cc_mqtt5_qos0_client_disconnect_config; + funcs.m_disconnect_add_user_prop = &cc_mqtt5_qos0_client_disconnect_add_user_prop; + funcs.m_disconnect_send = &cc_mqtt5_qos0_client_disconnect_send; + funcs.m_disconnect_cancel = &cc_mqtt5_qos0_client_disconnect_cancel; + funcs.m_disconnect = &cc_mqtt5_qos0_client_disconnect; + funcs.m_subscribe_prepare = &cc_mqtt5_qos0_client_subscribe_prepare; + funcs.m_subscribe_set_response_timeout = &cc_mqtt5_qos0_client_subscribe_set_response_timeout; + funcs.m_subscribe_get_response_timeout = &cc_mqtt5_qos0_client_subscribe_get_response_timeout; + funcs.m_subscribe_init_config_topic = &cc_mqtt5_qos0_client_subscribe_init_config_topic; + funcs.m_subscribe_init_config_extra = &cc_mqtt5_qos0_client_subscribe_init_config_extra; + funcs.m_subscribe_config_topic = &cc_mqtt5_qos0_client_subscribe_config_topic; + funcs.m_subscribe_config_extra = &cc_mqtt5_qos0_client_subscribe_config_extra; + funcs.m_subscribe_add_user_prop = &cc_mqtt5_qos0_client_subscribe_add_user_prop; + funcs.m_subscribe_send = &cc_mqtt5_qos0_client_subscribe_send; + funcs.m_subscribe_cancel = &cc_mqtt5_qos0_client_subscribe_cancel; + funcs.m_subscribe_simple = &cc_mqtt5_qos0_client_subscribe_simple; + funcs.m_subscribe_full = &cc_mqtt5_qos0_client_subscribe_full; + funcs.m_unsubscribe_prepare = &cc_mqtt5_qos0_client_unsubscribe_prepare; + funcs.m_unsubscribe_set_response_timeout = &cc_mqtt5_qos0_client_unsubscribe_set_response_timeout; + funcs.m_unsubscribe_get_response_timeout = &cc_mqtt5_qos0_client_unsubscribe_get_response_timeout; + funcs.m_unsubscribe_init_config_topic = &cc_mqtt5_qos0_client_unsubscribe_init_config_topic; + funcs.m_unsubscribe_config_topic = &cc_mqtt5_qos0_client_unsubscribe_config_topic; + funcs.m_unsubscribe_add_user_prop = &cc_mqtt5_qos0_client_unsubscribe_add_user_prop; + funcs.m_unsubscribe_send = &cc_mqtt5_qos0_client_unsubscribe_send; + funcs.m_unsubscribe_cancel = &cc_mqtt5_qos0_client_unsubscribe_cancel; + funcs.m_unsubscribe_simple = &cc_mqtt5_qos0_client_unsubscribe_simple; + funcs.m_unsubscribe_full = &cc_mqtt5_qos0_client_unsubscribe_full; + funcs.m_publish_prepare = &cc_mqtt5_qos0_client_publish_prepare; + funcs.m_publish_count = &cc_mqtt5_qos0_client_publish_count; + funcs.m_publish_init_config_basic = &cc_mqtt5_qos0_client_publish_init_config_basic; + funcs.m_publish_init_config_extra = &cc_mqtt5_qos0_client_publish_init_config_extra; + funcs.m_publish_set_response_timeout = &cc_mqtt5_qos0_client_publish_set_response_timeout; + funcs.m_publish_get_response_timeout = &cc_mqtt5_qos0_client_publish_get_response_timeout; + funcs.m_publish_set_resend_attempts = &cc_mqtt5_qos0_client_publish_set_resend_attempts; + funcs.m_publish_get_resend_attempts = &cc_mqtt5_qos0_client_publish_get_resend_attempts; + funcs.m_publish_config_basic = &cc_mqtt5_qos0_client_publish_config_basic; + funcs.m_publish_config_extra = &cc_mqtt5_qos0_client_publish_config_extra; + funcs.m_publish_add_user_prop = &cc_mqtt5_qos0_client_publish_add_user_prop; + funcs.m_publish_send = &cc_mqtt5_qos0_client_publish_send; + funcs.m_publish_cancel = &cc_mqtt5_qos0_client_publish_cancel; + funcs.m_publish_was_initiated = &cc_mqtt5_qos0_client_publish_was_initiated; + funcs.m_publish_set_out_of_order_allowed = &cc_mqtt5_qos0_client_publish_set_out_of_order_allowed; + funcs.m_publish_get_out_of_order_allowed = &cc_mqtt5_qos0_client_publish_get_out_of_order_allowed; + funcs.m_publish_simple = &cc_mqtt5_qos0_client_publish_simple; + funcs.m_publish_full = &cc_mqtt5_qos0_client_publish_full; + funcs.m_reauth_prepare = &cc_mqtt5_qos0_client_reauth_prepare; + funcs.m_reauth_init_config_auth = &cc_mqtt5_qos0_client_reauth_init_config_auth; + funcs.m_reauth_set_response_timeout = &cc_mqtt5_qos0_client_reauth_set_response_timeout; + funcs.m_reauth_get_response_timeout = &cc_mqtt5_qos0_client_reauth_get_response_timeout; + funcs.m_reauth_config_auth = &cc_mqtt5_qos0_client_reauth_config_auth; + funcs.m_reauth_add_user_prop = &cc_mqtt5_qos0_client_reauth_add_user_prop; + funcs.m_reauth_send = &cc_mqtt5_qos0_client_reauth_send; + funcs.m_reauth_cancel = &cc_mqtt5_qos0_client_reauth_cancel; + funcs.m_reauth = &cc_mqtt5_qos0_client_reauth; + funcs.m_set_next_tick_program_callback = &cc_mqtt5_qos0_client_set_next_tick_program_callback; + funcs.m_set_cancel_next_tick_wait_callback = &cc_mqtt5_qos0_client_set_cancel_next_tick_wait_callback; + funcs.m_set_send_output_data_callback = &cc_mqtt5_qos0_client_set_send_output_data_callback; + funcs.m_set_broker_disconnect_report_callback = &cc_mqtt5_qos0_client_set_broker_disconnect_report_callback; + funcs.m_set_message_received_report_callback = &cc_mqtt5_qos0_client_set_message_received_report_callback; + funcs.m_set_error_log_callback = &cc_mqtt5_qos0_client_set_error_log_callback; + return funcs; +} \ No newline at end of file diff --git a/client/lib/test/unit/UnitTestQos0Base.h b/client/lib/test/unit/UnitTestQos0Base.h new file mode 100644 index 0000000..490ccb1 --- /dev/null +++ b/client/lib/test/unit/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/unit/UnitTestQos0Publish.th b/client/lib/test/unit/UnitTestQos0Publish.th new file mode 100644 index 0000000..9575a76 --- /dev/null +++ b/client/lib/test/unit/UnitTestQos0Publish.th @@ -0,0 +1,65 @@ +#include "UnitTestQos0Base.h" +#include "UnitTestPropsHandler.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 Qos2 configuration + auto* client = unitTestAllocClient(); + unitTestPerformBasicConnect(client, __FUNCTION__); + TS_ASSERT(unitTestIsConnected(client)); + + auto* publish = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish, nullptr); + TS_ASSERT_EQUALS(unitTestPublishCount(client), 1U); + + const std::string Topic("some/topic"); + const UnitTestData Data = { 0x1, 0x2, 0x3, 0x4, 0x5}; + + auto config = CC_Mqtt5PublishBasicConfig(); + unitTestPublishInitConfigBasic(&config); + + config.m_topic = Topic.c_str(); + config.m_data = &Data[0]; + config.m_dataLen = static_cast(Data.size()); + config.m_qos = CC_Mqtt5QoS_ExactlyOnceDelivery; + + auto ec = unitTestPublishConfigBasic(publish, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_BadParam); + + config.m_qos = CC_Mqtt5QoS_AtLeastOnceDelivery; + ec = unitTestPublishConfigBasic(publish, &config); + ec = unitTestPublishConfigBasic(publish, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_BadParam); + + config.m_qos = CC_Mqtt5QoS_AtMostOnceDelivery; + ec = unitTestPublishConfigBasic(publish, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubackInfo = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubackInfo.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); +} diff --git a/client/lib/test/unit/UnitTestQos0Receive.th b/client/lib/test/unit/UnitTestQos0Receive.th new file mode 100644 index 0000000..d580c56 --- /dev/null +++ b/client/lib/test/unit/UnitTestQos0Receive.th @@ -0,0 +1,85 @@ +#include "UnitTestQos0Base.h" +#include "UnitTestPropsHandler.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(); + } +}; + +void UnitTestQos0Receive::test1() +{ + // Rejection QoS2 reception + auto* client = unitTestAllocClient(); + unitTestPerformBasicConnect(client, __FUNCTION__); + TS_ASSERT(unitTestIsConnected(client)); + + auto subscribeConfig = CC_Mqtt5SubscribeTopicConfig(); + unitTestSubscribeInitConfigTopic(&subscribeConfig); + subscribeConfig.m_topic = "#"; + subscribeConfig.m_maxQos = CC_Mqtt5QoS_AtMostOnceDelivery; + + unitTestPerformSubscribe(client, &subscribeConfig); + + const std::string Topic = "some/topic"; + const UnitTestData Data = {'h', 'e', 'l', 'l', 'o'}; + const unsigned PacketId = 10; + + UnitTestPublishMsg publishMsg; + publishMsg.transportField_flags().field_qos().value() = UnitTestPublishMsg::TransportField_flags::Field_qos::ValueType::ExactlyOnceDelivery; + publishMsg.field_packetId().field().setValue(PacketId); + publishMsg.field_topic().value() = Topic; + publishMsg.field_payload().value() = Data; + + publishMsg.doRefresh(); + unitTestReceiveMessage(publishMsg); + + unitTestVerifyDisconnectSent(UnitTestDisconnectReason::QosNotSupported); + TS_ASSERT(unitTestIsDisconnected()); +} + +void UnitTestQos0Receive::test2() +{ + // Rejection QoS1 reception + auto* client = unitTestAllocClient(); + unitTestPerformBasicConnect(client, __FUNCTION__); + TS_ASSERT(unitTestIsConnected(client)); + + auto subscribeConfig = CC_Mqtt5SubscribeTopicConfig(); + unitTestSubscribeInitConfigTopic(&subscribeConfig); + subscribeConfig.m_topic = "#"; + subscribeConfig.m_maxQos = CC_Mqtt5QoS_AtMostOnceDelivery; + + unitTestPerformSubscribe(client, &subscribeConfig); + + const std::string Topic = "some/topic"; + const UnitTestData Data = {'h', 'e', 'l', 'l', 'o'}; + const unsigned PacketId = 10; + + UnitTestPublishMsg publishMsg; + publishMsg.transportField_flags().field_qos().value() = UnitTestPublishMsg::TransportField_flags::Field_qos::ValueType::AtLeastOnceDelivery; + publishMsg.field_packetId().field().setValue(PacketId); + publishMsg.field_topic().value() = Topic; + publishMsg.field_payload().value() = Data; + + publishMsg.doRefresh(); + unitTestReceiveMessage(publishMsg); + + unitTestVerifyDisconnectSent(UnitTestDisconnectReason::QosNotSupported); + TS_ASSERT(unitTestIsDisconnected()); +} \ No newline at end of file diff --git a/client/lib/test/unit/UnitTestQos0Subscribe.th b/client/lib/test/unit/UnitTestQos0Subscribe.th new file mode 100644 index 0000000..70b540b --- /dev/null +++ b/client/lib/test/unit/UnitTestQos0Subscribe.th @@ -0,0 +1,73 @@ +#include "UnitTestQos0Base.h" +#include "UnitTestPropsHandler.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/2 subscription attempt + auto* client = unitTestAllocClient(); + unitTestPerformBasicConnect(client, __FUNCTION__); + TS_ASSERT(unitTestIsConnected(client)); + + auto subscribeConfig = CC_Mqtt5SubscribeTopicConfig(); + unitTestSubscribeInitConfigTopic(&subscribeConfig); + subscribeConfig.m_topic = "#"; + subscribeConfig.m_maxQos = CC_Mqtt5QoS_ExactlyOnceDelivery; + + auto* subscribe = unitTestSubscribePrepare(client, nullptr); + TS_ASSERT_DIFFERS(subscribe, nullptr); + + auto ec = unitTestSubscribeConfigTopic(subscribe, &subscribeConfig); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_BadParam); + + subscribeConfig.m_maxQos = CC_Mqtt5QoS_AtLeastOnceDelivery; + ec = unitTestSubscribeConfigTopic(subscribe, &subscribeConfig); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_BadParam); + + subscribeConfig.m_maxQos = CC_Mqtt5QoS_AtMostOnceDelivery; + ec = unitTestSubscribeConfigTopic(subscribe, &subscribeConfig); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendSubscribe(subscribe); + TS_ASSERT(!unitTestIsSubscribeComplete()); + + auto sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Subscribe); + auto* subscribeMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(subscribeMsg, nullptr); + + UnitTestSubackMsg subackMsg; + subackMsg.field_packetId().value() = subscribeMsg->field_packetId().value(); + subackMsg.field_list().value().resize(1); + subackMsg.field_list().value()[0].setValue(CC_Mqtt5ReasonCode_GrantedQos1); + + unitTestReceiveMessage(subackMsg); + TS_ASSERT(unitTestIsSubscribeComplete()); + + auto& subackInfo = unitTestSubscribeResponseInfo(); + TS_ASSERT_EQUALS(subackInfo.m_status, CC_Mqtt5AsyncOpStatus_ProtocolError); + unitTestPopSubscribeResponseInfo(); + + unitTestVerifyDisconnectSent(UnitTestDisconnectReason::ProtocolError); + TS_ASSERT(unitTestIsDisconnected()); +} \ No newline at end of file diff --git a/client/lib/test/unit/UnitTestQos1Base.cpp b/client/lib/test/unit/UnitTestQos1Base.cpp new file mode 100644 index 0000000..fb4d1e2 --- /dev/null +++ b/client/lib/test/unit/UnitTestQos1Base.cpp @@ -0,0 +1,109 @@ +#include "UnitTestQos1Base.h" + +#include "qos1_client.h" + +const UnitTestQos1Base::LibFuncs& UnitTestQos1Base::getFuncs() +{ + static LibFuncs funcs; + funcs.m_alloc = &cc_mqtt5_qos1_client_alloc; + funcs.m_free = &cc_mqtt5_qos1_client_free; + funcs.m_tick = &cc_mqtt5_qos1_client_tick; + funcs.m_process_data = &cc_mqtt5_qos1_client_process_data; + funcs.m_notify_network_disconnected = &cc_mqtt5_qos1_client_notify_network_disconnected; + funcs.m_is_network_disconnected = &cc_mqtt5_qos1_client_is_network_disconnected; + funcs.m_set_default_response_timeout = &cc_mqtt5_qos1_client_set_default_response_timeout; + funcs.m_get_default_response_timeout = &cc_mqtt5_qos1_client_get_default_response_timeout; + funcs.m_pub_topic_alias_alloc = &cc_mqtt5_qos1_client_pub_topic_alias_alloc; + funcs.m_pub_topic_alias_free = &cc_mqtt5_qos1_client_pub_topic_alias_free; + funcs.m_pub_topic_alias_count = &cc_mqtt5_qos1_client_pub_topic_alias_count; + funcs.m_pub_topic_alias_is_allocated = &cc_mqtt5_qos1_client_pub_topic_alias_is_allocated; + funcs.m_set_verify_outgoing_topic_enabled = &cc_mqtt5_qos1_client_set_verify_outgoing_topic_enabled; + funcs.m_get_verify_outgoing_topic_enabled = &cc_mqtt5_qos1_client_get_verify_outgoing_topic_enabled; + funcs.m_set_verify_incoming_topic_enabled = &cc_mqtt5_qos1_client_set_verify_incoming_topic_enabled; + funcs.m_get_verify_incoming_topic_enabled = &cc_mqtt5_qos1_client_get_verify_incoming_topic_enabled; + funcs.m_set_verify_incoming_msg_subscribed = &cc_mqtt5_qos1_client_set_verify_incoming_msg_subscribed; + funcs.m_get_verify_incoming_msg_subscribed = &cc_mqtt5_qos1_client_get_verify_incoming_msg_subscribed; + funcs.m_init_user_prop = &cc_mqtt5_qos1_client_init_user_prop; + funcs.m_connect_prepare = &cc_mqtt5_qos1_client_connect_prepare; + funcs.m_connect_init_config_basic = &cc_mqtt5_qos1_client_connect_init_config_basic; + funcs.m_connect_init_config_will = &cc_mqtt5_qos1_client_connect_init_config_will; + funcs.m_connect_init_config_extra = &cc_mqtt5_qos1_client_connect_init_config_extra; + funcs.m_connect_init_config_auth = &cc_mqtt5_qos1_client_connect_init_config_auth; + funcs.m_connect_init_auth_info = &cc_mqtt5_qos1_client_connect_init_auth_info; + funcs.m_connect_set_response_timeout = &cc_mqtt5_qos1_client_connect_set_response_timeout; + funcs.m_connect_get_response_timeout = &cc_mqtt5_qos1_client_connect_get_response_timeout; + funcs.m_connect_config_basic = &cc_mqtt5_qos1_client_connect_config_basic; + funcs.m_connect_config_will = &cc_mqtt5_qos1_client_connect_config_will; + funcs.m_connect_config_extra = &cc_mqtt5_qos1_client_connect_config_extra; + funcs.m_connect_config_auth = &cc_mqtt5_qos1_client_connect_config_auth; + funcs.m_connect_add_user_prop = &cc_mqtt5_qos1_client_connect_add_user_prop; + funcs.m_connect_add_will_user_prop = &cc_mqtt5_qos1_client_connect_add_will_user_prop; + funcs.m_connect_send = &cc_mqtt5_qos1_client_connect_send; + funcs.m_connect_cancel = &cc_mqtt5_qos1_client_connect_cancel; + funcs.m_connect_simple = &cc_mqtt5_qos1_client_connect_simple; + funcs.m_connect_full = &cc_mqtt5_qos1_client_connect_full; + funcs.m_is_connected = &cc_mqtt5_qos1_client_is_connected; + funcs.m_disconnect_prepare = &cc_mqtt5_qos1_client_disconnect_prepare; + funcs.m_disconnect_init_config = &cc_mqtt5_qos1_client_disconnect_init_config; + funcs.m_disconnect_config = &cc_mqtt5_qos1_client_disconnect_config; + funcs.m_disconnect_add_user_prop = &cc_mqtt5_qos1_client_disconnect_add_user_prop; + funcs.m_disconnect_send = &cc_mqtt5_qos1_client_disconnect_send; + funcs.m_disconnect_cancel = &cc_mqtt5_qos1_client_disconnect_cancel; + funcs.m_disconnect = &cc_mqtt5_qos1_client_disconnect; + funcs.m_subscribe_prepare = &cc_mqtt5_qos1_client_subscribe_prepare; + funcs.m_subscribe_set_response_timeout = &cc_mqtt5_qos1_client_subscribe_set_response_timeout; + funcs.m_subscribe_get_response_timeout = &cc_mqtt5_qos1_client_subscribe_get_response_timeout; + funcs.m_subscribe_init_config_topic = &cc_mqtt5_qos1_client_subscribe_init_config_topic; + funcs.m_subscribe_init_config_extra = &cc_mqtt5_qos1_client_subscribe_init_config_extra; + funcs.m_subscribe_config_topic = &cc_mqtt5_qos1_client_subscribe_config_topic; + funcs.m_subscribe_config_extra = &cc_mqtt5_qos1_client_subscribe_config_extra; + funcs.m_subscribe_add_user_prop = &cc_mqtt5_qos1_client_subscribe_add_user_prop; + funcs.m_subscribe_send = &cc_mqtt5_qos1_client_subscribe_send; + funcs.m_subscribe_cancel = &cc_mqtt5_qos1_client_subscribe_cancel; + funcs.m_subscribe_simple = &cc_mqtt5_qos1_client_subscribe_simple; + funcs.m_subscribe_full = &cc_mqtt5_qos1_client_subscribe_full; + funcs.m_unsubscribe_prepare = &cc_mqtt5_qos1_client_unsubscribe_prepare; + funcs.m_unsubscribe_set_response_timeout = &cc_mqtt5_qos1_client_unsubscribe_set_response_timeout; + funcs.m_unsubscribe_get_response_timeout = &cc_mqtt5_qos1_client_unsubscribe_get_response_timeout; + funcs.m_unsubscribe_init_config_topic = &cc_mqtt5_qos1_client_unsubscribe_init_config_topic; + funcs.m_unsubscribe_config_topic = &cc_mqtt5_qos1_client_unsubscribe_config_topic; + funcs.m_unsubscribe_add_user_prop = &cc_mqtt5_qos1_client_unsubscribe_add_user_prop; + funcs.m_unsubscribe_send = &cc_mqtt5_qos1_client_unsubscribe_send; + funcs.m_unsubscribe_cancel = &cc_mqtt5_qos1_client_unsubscribe_cancel; + funcs.m_unsubscribe_simple = &cc_mqtt5_qos1_client_unsubscribe_simple; + funcs.m_unsubscribe_full = &cc_mqtt5_qos1_client_unsubscribe_full; + funcs.m_publish_prepare = &cc_mqtt5_qos1_client_publish_prepare; + funcs.m_publish_count = &cc_mqtt5_qos1_client_publish_count; + funcs.m_publish_init_config_basic = &cc_mqtt5_qos1_client_publish_init_config_basic; + funcs.m_publish_init_config_extra = &cc_mqtt5_qos1_client_publish_init_config_extra; + funcs.m_publish_set_response_timeout = &cc_mqtt5_qos1_client_publish_set_response_timeout; + funcs.m_publish_get_response_timeout = &cc_mqtt5_qos1_client_publish_get_response_timeout; + funcs.m_publish_set_resend_attempts = &cc_mqtt5_qos1_client_publish_set_resend_attempts; + funcs.m_publish_get_resend_attempts = &cc_mqtt5_qos1_client_publish_get_resend_attempts; + funcs.m_publish_config_basic = &cc_mqtt5_qos1_client_publish_config_basic; + funcs.m_publish_config_extra = &cc_mqtt5_qos1_client_publish_config_extra; + funcs.m_publish_add_user_prop = &cc_mqtt5_qos1_client_publish_add_user_prop; + funcs.m_publish_send = &cc_mqtt5_qos1_client_publish_send; + funcs.m_publish_cancel = &cc_mqtt5_qos1_client_publish_cancel; + funcs.m_publish_was_initiated = &cc_mqtt5_qos1_client_publish_was_initiated; + funcs.m_publish_set_out_of_order_allowed = &cc_mqtt5_qos1_client_publish_set_out_of_order_allowed; + funcs.m_publish_get_out_of_order_allowed = &cc_mqtt5_qos1_client_publish_get_out_of_order_allowed; + funcs.m_publish_simple = &cc_mqtt5_qos1_client_publish_simple; + funcs.m_publish_full = &cc_mqtt5_qos1_client_publish_full; + funcs.m_reauth_prepare = &cc_mqtt5_qos1_client_reauth_prepare; + funcs.m_reauth_init_config_auth = &cc_mqtt5_qos1_client_reauth_init_config_auth; + funcs.m_reauth_set_response_timeout = &cc_mqtt5_qos1_client_reauth_set_response_timeout; + funcs.m_reauth_get_response_timeout = &cc_mqtt5_qos1_client_reauth_get_response_timeout; + funcs.m_reauth_config_auth = &cc_mqtt5_qos1_client_reauth_config_auth; + funcs.m_reauth_add_user_prop = &cc_mqtt5_qos1_client_reauth_add_user_prop; + funcs.m_reauth_send = &cc_mqtt5_qos1_client_reauth_send; + funcs.m_reauth_cancel = &cc_mqtt5_qos1_client_reauth_cancel; + funcs.m_reauth = &cc_mqtt5_qos1_client_reauth; + funcs.m_set_next_tick_program_callback = &cc_mqtt5_qos1_client_set_next_tick_program_callback; + funcs.m_set_cancel_next_tick_wait_callback = &cc_mqtt5_qos1_client_set_cancel_next_tick_wait_callback; + funcs.m_set_send_output_data_callback = &cc_mqtt5_qos1_client_set_send_output_data_callback; + funcs.m_set_broker_disconnect_report_callback = &cc_mqtt5_qos1_client_set_broker_disconnect_report_callback; + funcs.m_set_message_received_report_callback = &cc_mqtt5_qos1_client_set_message_received_report_callback; + funcs.m_set_error_log_callback = &cc_mqtt5_qos1_client_set_error_log_callback; + return funcs; +} \ No newline at end of file diff --git a/client/lib/test/unit/UnitTestQos1Base.h b/client/lib/test/unit/UnitTestQos1Base.h new file mode 100644 index 0000000..a074cf0 --- /dev/null +++ b/client/lib/test/unit/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/client/lib/test/unit/UnitTestQos1Publish.th b/client/lib/test/unit/UnitTestQos1Publish.th new file mode 100644 index 0000000..813be81 --- /dev/null +++ b/client/lib/test/unit/UnitTestQos1Publish.th @@ -0,0 +1,77 @@ +#include "UnitTestQos1Base.h" +#include "UnitTestPropsHandler.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* client = unitTestAllocClient(); + unitTestPerformBasicConnect(client, __FUNCTION__); + TS_ASSERT(unitTestIsConnected(client)); + + auto* publish = unitTestPublishPrepare(client, nullptr); + TS_ASSERT_DIFFERS(publish, nullptr); + TS_ASSERT_EQUALS(unitTestPublishCount(client), 1U); + + const std::string Topic("some/topic"); + const UnitTestData Data = { 0x1, 0x2, 0x3, 0x4, 0x5}; + + auto config = CC_Mqtt5PublishBasicConfig(); + unitTestPublishInitConfigBasic(&config); + + config.m_topic = Topic.c_str(); + config.m_data = &Data[0]; + config.m_dataLen = static_cast(Data.size()); + config.m_qos = CC_Mqtt5QoS_ExactlyOnceDelivery; + + auto ec = unitTestPublishConfigBasic(publish, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_BadParam); + + config.m_qos = CC_Mqtt5QoS_AtLeastOnceDelivery; + ec = unitTestPublishConfigBasic(publish, &config); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendPublish(publish); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + TS_ASSERT(!unitTestIsPublishComplete()); + + auto sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Publish); + auto* publishMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(publishMsg, nullptr); + + TS_ASSERT_EQUALS(static_cast(publishMsg->transportField_flags().field_qos().value()), CC_Mqtt5QoS_AtLeastOnceDelivery); + TS_ASSERT_EQUALS(publishMsg->field_topic().value(), Topic); + TS_ASSERT(publishMsg->field_packetId().doesExist()); + TS_ASSERT_EQUALS(publishMsg->field_payload().value(), Data); + + unitTestTick(1000); + UnitTestPubackMsg pubackMsg; + pubackMsg.field_packetId().value() = publishMsg->field_packetId().field().value(); + unitTestReceiveMessage(pubackMsg); + + TS_ASSERT(unitTestIsPublishComplete()); + auto& pubackInfo = unitTestPublishResponseInfo(); + TS_ASSERT_EQUALS(pubackInfo.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopPublishResponseInfo(); +} diff --git a/client/lib/test/unit/UnitTestQos1Receive.th b/client/lib/test/unit/UnitTestQos1Receive.th new file mode 100644 index 0000000..5969df0 --- /dev/null +++ b/client/lib/test/unit/UnitTestQos1Receive.th @@ -0,0 +1,53 @@ +#include "UnitTestQos1Base.h" +#include "UnitTestPropsHandler.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(); + } +}; + +void UnitTestQos1Receive::test1() +{ + // Rejection QoS2 reception + auto* client = unitTestAllocClient(); + unitTestPerformBasicConnect(client, __FUNCTION__); + TS_ASSERT(unitTestIsConnected(client)); + + auto subscribeConfig = CC_Mqtt5SubscribeTopicConfig(); + unitTestSubscribeInitConfigTopic(&subscribeConfig); + subscribeConfig.m_topic = "#"; + subscribeConfig.m_maxQos = CC_Mqtt5QoS_AtLeastOnceDelivery; + + unitTestPerformSubscribe(client, &subscribeConfig); + + const std::string Topic = "some/topic"; + const UnitTestData Data = {'h', 'e', 'l', 'l', 'o'}; + const unsigned PacketId = 10; + + UnitTestPublishMsg publishMsg; + publishMsg.transportField_flags().field_qos().value() = UnitTestPublishMsg::TransportField_flags::Field_qos::ValueType::ExactlyOnceDelivery; + publishMsg.field_packetId().field().setValue(PacketId); + publishMsg.field_topic().value() = Topic; + publishMsg.field_payload().value() = Data; + + publishMsg.doRefresh(); + unitTestReceiveMessage(publishMsg); + + unitTestVerifyDisconnectSent(UnitTestDisconnectReason::QosNotSupported); + TS_ASSERT(unitTestIsDisconnected()); +} \ No newline at end of file diff --git a/client/lib/test/unit/UnitTestQos1Subscribe.th b/client/lib/test/unit/UnitTestQos1Subscribe.th new file mode 100644 index 0000000..29c75f7 --- /dev/null +++ b/client/lib/test/unit/UnitTestQos1Subscribe.th @@ -0,0 +1,69 @@ +#include "UnitTestQos1Base.h" +#include "UnitTestPropsHandler.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* client = unitTestAllocClient(); + unitTestPerformBasicConnect(client, __FUNCTION__); + TS_ASSERT(unitTestIsConnected(client)); + + auto subscribeConfig = CC_Mqtt5SubscribeTopicConfig(); + unitTestSubscribeInitConfigTopic(&subscribeConfig); + subscribeConfig.m_topic = "#"; + subscribeConfig.m_maxQos = CC_Mqtt5QoS_ExactlyOnceDelivery; + + auto* subscribe = unitTestSubscribePrepare(client, nullptr); + TS_ASSERT_DIFFERS(subscribe, nullptr); + + auto ec = unitTestSubscribeConfigTopic(subscribe, &subscribeConfig); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_BadParam); + + subscribeConfig.m_maxQos = CC_Mqtt5QoS_AtLeastOnceDelivery; + ec = unitTestSubscribeConfigTopic(subscribe, &subscribeConfig); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendSubscribe(subscribe); + TS_ASSERT(!unitTestIsSubscribeComplete()); + + auto sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Subscribe); + auto* subscribeMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(subscribeMsg, nullptr); + + UnitTestSubackMsg subackMsg; + subackMsg.field_packetId().value() = subscribeMsg->field_packetId().value(); + subackMsg.field_list().value().resize(1); + subackMsg.field_list().value()[0].setValue(CC_Mqtt5ReasonCode_GrantedQos2); + + unitTestReceiveMessage(subackMsg); + TS_ASSERT(unitTestIsSubscribeComplete()); + + auto& subackInfo = unitTestSubscribeResponseInfo(); + TS_ASSERT_EQUALS(subackInfo.m_status, CC_Mqtt5AsyncOpStatus_ProtocolError); + unitTestPopSubscribeResponseInfo(); + + unitTestVerifyDisconnectSent(UnitTestDisconnectReason::ProtocolError); + TS_ASSERT(unitTestIsDisconnected()); +} \ No newline at end of file diff --git a/client/lib/test/unit/UnitTestReceive.th b/client/lib/test/unit/UnitTestReceive.th index 5c19595..628633b 100644 --- a/client/lib/test/unit/UnitTestReceive.th +++ b/client/lib/test/unit/UnitTestReceive.th @@ -754,7 +754,7 @@ void UnitTestReceive::test11() // [MQTT-3.3.1-1] // [MQTT-4.3.3-10] - auto* client = unitTestAllocClient(); + auto* client = unitTestAllocClient(true); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); diff --git a/client/lib/test/unit/UnitTestSubscribe.th b/client/lib/test/unit/UnitTestSubscribe.th index 0589721..79d994a 100644 --- a/client/lib/test/unit/UnitTestSubscribe.th +++ b/client/lib/test/unit/UnitTestSubscribe.th @@ -128,7 +128,7 @@ void UnitTestSubscribe::test1() void UnitTestSubscribe::test2() { // Parallel subscribe - auto* client = unitTestAllocClient(); + auto* client = unitTestAllocClient(true); unitTestPerformBasicConnect(client, __FUNCTION__); TS_ASSERT(unitTestIsConnected(client)); diff --git a/doc/custom_client_build.md b/doc/custom_client_build.md index ad4ff54..63107da 100644 --- a/doc/custom_client_build.md +++ b/doc/custom_client_build.md @@ -446,7 +446,7 @@ 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/BareMetalConfig.cmake): +like [this](../client/lib/script/BareMetalTestConfig.cmake): Setting "bm" as a custom client name results in having a static library called `cc_mqtt5_bm_client`. All the API functions are defined in `cc_mqtt5_client/bm_client.h` header file: diff --git a/script/full_build.sh b/script/full_build.sh index 3a375a4..3258184 100755 --- a/script/full_build.sh +++ b/script/full_build.sh @@ -26,7 +26,8 @@ cmake .. -DCMAKE_INSTALL_PREFIX=${COMMON_INSTALL_DIR} -DCMAKE_BUILD_TYPE=${BUILD -DCC_MQTT5_WITH_DEFAULT_SANITIZERS=ON \ -DCC_MQTT5_BUILD_UNIT_TESTS=ON -DCC_MQTT5_BUILD_INTEGRATION_TESTS=ON \ -DCC_MQTT5_CLIENT_AFL_FUZZ=ON \ - -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES="${ROOT_DIR}/client/lib/script/BareMetalConfig.cmake" "$@" + -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES="${ROOT_DIR}/client/lib/script/BareMetalTestConfig.cmake;${ROOT_DIR}/client/lib/script/Qos1TestConfig.cmake;${ROOT_DIR}/client/lib/script/Qos0TestConfig.cmake" \ + "$@" procs=$(nproc) if [ -n "${procs}" ]; then From 0fa32488c449c0ac49521cfbb392182f21d182eb Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sun, 7 Apr 2024 10:19:53 +1000 Subject: [PATCH 28/48] Fixing gcc build. --- client/afl_fuzz/AflFuzz.cpp.templ | 12 ++++++------ client/lib/src/op/RecvOp.h | 1 - client/lib/src/op/SendOp.cpp | 1 + client/lib/src/op/SendOp.h | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/client/afl_fuzz/AflFuzz.cpp.templ b/client/afl_fuzz/AflFuzz.cpp.templ index 8ed6ad1..3732a18 100644 --- a/client/afl_fuzz/AflFuzz.cpp.templ +++ b/client/afl_fuzz/AflFuzz.cpp.templ @@ -161,6 +161,7 @@ public: if (!cc_mqtt5_##NAME##client_is_network_disconnected(m_client.get())) { infoLog() << "Insufficient data in buffer (" << dataLen << "), reporting network disconnected..." << std::endl; cc_mqtt5_##NAME##client_notify_network_disconnected(m_client.get()); + brokerDisconnectReportCb(this, nullptr); continue; } @@ -277,6 +278,7 @@ private: m_state.m_disconnected = false; m_state.m_reauthRequired = !m_opts.authMethod().empty(); m_state.m_publishCount = 0U; + m_state.m_connected = false; } void doConnect() @@ -544,6 +546,7 @@ private: m_state.m_disconnected = true; m_state.m_reinitRequired = true; + m_state.m_connected = false; CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_ValuesLimit; auto handle = cc_mqtt5_##NAME##client_disconnect_prepare(m_client.get(), &ec); @@ -568,7 +571,7 @@ private: if (ec != CC_Mqtt5ErrorCode_Success) { errorLog() << "Failed to send disconnect\n"; return; - } + } } bool createGeneratorIfNeeded() @@ -633,11 +636,8 @@ private: auto& state = asThis(data)->m_state; state.m_disconnected = true; state.m_connected = false; - - if (!cc_mqtt5_##NAME##client_is_network_disconnected(asThis(data)->m_client.get())) { - state.m_reinitRequired = true; - state.m_connectRequired = true; - } + state.m_reinitRequired = true; + state.m_connectRequired = true; } static void messageReceivedReportCb(void* data, const CC_Mqtt5MessageInfo* info) diff --git a/client/lib/src/op/RecvOp.h b/client/lib/src/op/RecvOp.h index ff67a4e..a9129f7 100644 --- a/client/lib/src/op/RecvOp.h +++ b/client/lib/src/op/RecvOp.h @@ -63,7 +63,6 @@ class RecvOp final : public Op static void recvTimeoutCb(void* data); TimerMgr::Timer m_responseTimer; - TopicStr m_topicStr; unsigned m_packetId = 0U; static_assert(ExtConfig::RecvOpTimers == 1U); diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index e6935f4..e50b9e0 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -31,6 +31,7 @@ SendOp::SendOp(ClientImpl& client) : m_responseTimer(client.timerMgr().allocTimer()) { COMMS_ASSERT(m_responseTimer.isValid()); + static_cast(m_reasonCode); } SendOp::~SendOp() diff --git a/client/lib/src/op/SendOp.h b/client/lib/src/op/SendOp.h index 3b377bb..2451b07 100644 --- a/client/lib/src/op/SendOp.h +++ b/client/lib/src/op/SendOp.h @@ -111,7 +111,7 @@ class SendOp final : public Op void* m_cbData = nullptr; unsigned m_totalSendAttempts = DefaultSendAttempts; unsigned m_sendAttempts = 0U; - [[maybe_unused]] CC_Mqtt5ReasonCode m_reasonCode = CC_Mqtt5ReasonCode_Success; + CC_Mqtt5ReasonCode m_reasonCode = CC_Mqtt5ReasonCode_Success; bool m_outOfOrderAllowed = false; bool m_sent = false; bool m_acked = false; From b79ec07b9e6140ca0fcd9f691b12f7c577bd98ee Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sun, 7 Apr 2024 10:20:01 +1000 Subject: [PATCH 29/48] Attempt to test windows build on github actions. --- .github/workflows/actions_build.yml | 69 +++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index 7b3abe9..ede784e 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -3,8 +3,8 @@ name: Github Actions Build on: [push] env: - COMMS_BRANCH: v5.2.2 - CC_MQTT5_BRANCH: v2.6 + COMMS_TAG: v5.2.2 + CC_MQTT5_TAG: v2.6 jobs: build_gcc_ubuntu_22_04: @@ -19,7 +19,7 @@ jobs: - cc_ver: 12 cpp: 20 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Prepare Install run: sudo apt-get update --fix-missing @@ -41,8 +41,8 @@ jobs: COMMON_INSTALL_DIR: ${{runner.workspace}}/build/install COMMON_BUILD_TYPE: ${{matrix.type}} COMMON_CXX_STANDARD: ${{matrix.cpp}} - COMMS_TAG: ${{env.COMMS_BRANCH}} - CC_MQTT5_TAG: ${{env.CC_MQTT5_BRANCH}} + COMMS_TAG: ${{env.COMMS_TAG}} + CC_MQTT5_TAG: ${{env.CC_MQTT5_TAG}} - name: Configure CMake shell: bash @@ -89,7 +89,7 @@ jobs: steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Prepare Install run: sudo apt-get update --fix-missing @@ -111,8 +111,8 @@ jobs: COMMON_INSTALL_DIR: ${{runner.workspace}}/build/install COMMON_BUILD_TYPE: ${{matrix.type}} COMMON_CXX_STANDARD: ${{matrix.cpp}} - COMMS_TAG: ${{env.COMMS_BRANCH}} - CC_MQTT5_TAG: ${{env.CC_MQTT5_BRANCH}} + COMMS_TAG: ${{env.COMMS_TAG}} + CC_MQTT5_TAG: ${{env.CC_MQTT5_TAG}} - name: Configure CMake shell: bash @@ -138,4 +138,55 @@ jobs: working-directory: ${{runner.workspace}}/build shell: bash run: ctest -V - + + build_msvc_2019: + runs-on: windows-2019 + strategy: + fail-fast: false + matrix: + type: [Debug, Release, MinSizeRel] + arch: [Win32, x64] + cpp: [17] + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Prepare externals + shell: pwsh + run: $GITHUB_WORKSPACE/script/prepare_externals.bat + env: + BUILD_DIR: ${{runner.workspace}}/build + GENERATOR: Visual Studio 16 2019 + PLATFORM: ${{matrix.arch}} + 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_MQTT5_TAG: ${{env.CC_MQTT5_TAG}} + + - name: Configure CMake + shell: pwsh + working-directory: ${{runner.workspace}}/build + run: | + cmake $GITHUB_WORKSPACE -G "Visual Studio 16 2019" -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_MQTT5_WITH_DEFAULT_SANITIZERS=ON \ + -DCC_MQTT5_BUILD_UNIT_TESTS=ON -DCC_MQTT5_BUILD_INTEGRATION_TESTS=ON \ + -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES="$GITHUB_WORKSPACE/client/lib/script/BareMetalTestConfig.cmake;$GITHUB_WORKSPACE/client/lib/script/Qos0TestConfig.cmake;$GITHUB_WORKSPACE/client/lib/script/Qos1TestConfig.cmake" \ + -DCC_MQTT5_CLIENT_AFL_FUZZ=ON + + - name: Build Target + working-directory: ${{runner.workspace}}/build + shell: pwsh + run: cmake --build . --config ${{matrix.type}} --target install + env: + VERBOSE: 1 + - name: Testing + working-directory: ${{runner.workspace}}/build + shell: pwsh + run: ctest -V + From f3fb83adc02680dda8e53bc12e7f35a2026f4070 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sun, 7 Apr 2024 10:31:56 +1000 Subject: [PATCH 30/48] Attempt to fix windows build configuration for github actions --- .github/workflows/actions_build.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index ede784e..9310646 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -8,6 +8,7 @@ env: jobs: build_gcc_ubuntu_22_04: + if: false runs-on: ubuntu-22.04 strategy: fail-fast: false @@ -52,7 +53,7 @@ jobs: -DCMAKE_PREFIX_PATH=${{runner.workspace}}/build/install -DCMAKE_CXX_STANDARD=${{matrix.cpp}} \ -DCC_MQTT5_WITH_DEFAULT_SANITIZERS=ON \ -DCC_MQTT5_BUILD_UNIT_TESTS=ON -DCC_MQTT5_BUILD_INTEGRATION_TESTS=ON \ - -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES=$GITHUB_WORKSPACE/client/lib/script/BareMetalTestConfig.cmake \ + -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES="$GITHUB_WORKSPACE/client/lib/script/BareMetalTestConfig.cmake;$GITHUB_WORKSPACE/client/lib/script/Qos0TestConfig.cmake;$GITHUB_WORKSPACE/client/lib/script/Qos1TestConfig.cmake" \ -DCC_MQTT5_CLIENT_AFL_FUZZ=ON env: CC: gcc-${{matrix.cc_ver}} @@ -72,6 +73,7 @@ jobs: build_clang_ubuntu_22_04: + if: false runs-on: ubuntu-22.04 strategy: fail-fast: false @@ -122,7 +124,7 @@ jobs: -DCMAKE_PREFIX_PATH=${{runner.workspace}}/build/install -DCMAKE_CXX_STANDARD=${{matrix.cpp}} \ -DCC_MQTT5_WITH_DEFAULT_SANITIZERS=ON \ -DCC_MQTT5_BUILD_UNIT_TESTS=ON -DCC_MQTT5_BUILD_INTEGRATION_TESTS=ON \ - -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES=$GITHUB_WORKSPACE/client/lib/script/BareMetalTestConfig.cmake \ + -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES="$GITHUB_WORKSPACE/client/lib/script/BareMetalTestConfig.cmake;$GITHUB_WORKSPACE/client/lib/script/Qos0TestConfig.cmake;$GITHUB_WORKSPACE/client/lib/script/Qos1TestConfig.cmake" \ -DCC_MQTT5_CLIENT_AFL_FUZZ=ON env: CC: clang-${{matrix.cc_ver}} @@ -156,7 +158,7 @@ jobs: - name: Prepare externals shell: pwsh - run: $GITHUB_WORKSPACE/script/prepare_externals.bat + run: $GITHUB_WORKSPACE\script\prepare_externals.bat env: BUILD_DIR: ${{runner.workspace}}/build GENERATOR: Visual Studio 16 2019 @@ -174,10 +176,9 @@ jobs: run: | cmake $GITHUB_WORKSPACE -G "Visual Studio 16 2019" -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_MQTT5_WITH_DEFAULT_SANITIZERS=ON \ -DCC_MQTT5_BUILD_UNIT_TESTS=ON -DCC_MQTT5_BUILD_INTEGRATION_TESTS=ON \ -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES="$GITHUB_WORKSPACE/client/lib/script/BareMetalTestConfig.cmake;$GITHUB_WORKSPACE/client/lib/script/Qos0TestConfig.cmake;$GITHUB_WORKSPACE/client/lib/script/Qos1TestConfig.cmake" \ - -DCC_MQTT5_CLIENT_AFL_FUZZ=ON + -DCC_MQTT5_CLIENT_APPS=OFF -DCC_MQTT5_CLIENT_AFL_FUZZ=OFF - name: Build Target working-directory: ${{runner.workspace}}/build From 10901fdda2319860845b7b73ff89311efa7a2190 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sun, 7 Apr 2024 10:49:00 +1000 Subject: [PATCH 31/48] Another attempt to fix msvc build configuration on github actions. --- .github/workflows/actions_build.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index 9310646..1a2d92d 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -157,8 +157,8 @@ jobs: run: cmake -E make_directory ${{runner.workspace}}/build - name: Prepare externals - shell: pwsh - run: $GITHUB_WORKSPACE\script\prepare_externals.bat + shell: cmd + run: %GITHUB_WORKSPACE%\script\prepare_externals.bat env: BUILD_DIR: ${{runner.workspace}}/build GENERATOR: Visual Studio 16 2019 @@ -171,23 +171,23 @@ jobs: CC_MQTT5_TAG: ${{env.CC_MQTT5_TAG}} - name: Configure CMake - shell: pwsh + shell: cmd working-directory: ${{runner.workspace}}/build run: | - cmake $GITHUB_WORKSPACE -G "Visual Studio 16 2019" -A ${{matrix.arch}} -DCMAKE_BUILD_TYPE=${{matrix.type}} -DCMAKE_INSTALL_PREFIX=install \ + cmake %GITHUB_WORKSPACE% -G "Visual Studio 16 2019" -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_MQTT5_BUILD_UNIT_TESTS=ON -DCC_MQTT5_BUILD_INTEGRATION_TESTS=ON \ - -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES="$GITHUB_WORKSPACE/client/lib/script/BareMetalTestConfig.cmake;$GITHUB_WORKSPACE/client/lib/script/Qos0TestConfig.cmake;$GITHUB_WORKSPACE/client/lib/script/Qos1TestConfig.cmake" \ + -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES="%GITHUB_WORKSPACE%/client/lib/script/BareMetalTestConfig.cmake;%GITHUB_WORKSPACE%/client/lib/script/Qos0TestConfig.cmake;%GITHUB_WORKSPACE%/client/lib/script/Qos1TestConfig.cmake" \ -DCC_MQTT5_CLIENT_APPS=OFF -DCC_MQTT5_CLIENT_AFL_FUZZ=OFF - name: Build Target working-directory: ${{runner.workspace}}/build - shell: pwsh + shell: cmd run: cmake --build . --config ${{matrix.type}} --target install env: VERBOSE: 1 - name: Testing working-directory: ${{runner.workspace}}/build - shell: pwsh + shell: cmd run: ctest -V From 7ed240c93b3d52f37b3600adf37807c0b79f8892 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sun, 7 Apr 2024 10:54:29 +1000 Subject: [PATCH 32/48] Testing github actions windows configuration. --- .github/workflows/actions_build.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index 1a2d92d..a999b2f 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -154,11 +154,18 @@ jobs: - uses: actions/checkout@v4 - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Check GITHUB_WORKSPACE + shell: cmd + run: | + echo github.workspace=${{ github.workspace }} + echo GITHUB_WORKSPACE=%GITHUB_WORKSPACE% - name: Prepare externals shell: cmd - run: %GITHUB_WORKSPACE%\script\prepare_externals.bat + run: | + %GITHUB_WORKSPACE%\script\prepare_externals.bat env: BUILD_DIR: ${{runner.workspace}}/build GENERATOR: Visual Studio 16 2019 From 442b75e6bdde9c49c197a98b7fab0c0f452525c3 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sun, 7 Apr 2024 10:56:20 +1000 Subject: [PATCH 33/48] Another test of windows runner configuration. --- .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 a999b2f..e496ecc 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -168,7 +168,7 @@ jobs: %GITHUB_WORKSPACE%\script\prepare_externals.bat env: BUILD_DIR: ${{runner.workspace}}/build - GENERATOR: Visual Studio 16 2019 + GENERATOR: "Visual Studio 16 2019" PLATFORM: ${{matrix.arch}} EXTERNALS_DIR: ${{runner.workspace}}/externals COMMON_INSTALL_DIR: ${{runner.workspace}}/build/install From 5d8e5c0db317616ef009bbd78873c48a517403ae Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sun, 7 Apr 2024 11:03:44 +1000 Subject: [PATCH 34/48] Testing another github actions build. --- .github/workflows/actions_build.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index e496ecc..97d19b7 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -168,8 +168,6 @@ jobs: %GITHUB_WORKSPACE%\script\prepare_externals.bat env: BUILD_DIR: ${{runner.workspace}}/build - GENERATOR: "Visual Studio 16 2019" - PLATFORM: ${{matrix.arch}} EXTERNALS_DIR: ${{runner.workspace}}/externals COMMON_INSTALL_DIR: ${{runner.workspace}}/build/install COMMON_BUILD_TYPE: ${{matrix.type}} From ffe5aae54655570332b0c089235014b6a4343a0b Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sun, 7 Apr 2024 11:07:11 +1000 Subject: [PATCH 35/48] Using default windows generator in github actions. --- .github/workflows/actions_build.yml | 1 + script/prepare_externals.bat | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index 97d19b7..afc42e3 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -168,6 +168,7 @@ jobs: %GITHUB_WORKSPACE%\script\prepare_externals.bat env: BUILD_DIR: ${{runner.workspace}}/build + PLATFORM: ${{matrix.arch}} EXTERNALS_DIR: ${{runner.workspace}}/externals COMMON_INSTALL_DIR: ${{runner.workspace}}/build/install COMMON_BUILD_TYPE: ${{matrix.type}} diff --git a/script/prepare_externals.bat b/script/prepare_externals.bat index 8653dd9..dbb84a6 100755 --- a/script/prepare_externals.bat +++ b/script/prepare_externals.bat @@ -15,7 +15,7 @@ rem ----------------------------------------------------- if [%BUILD_DIR%] == [] echo "BUILD_DIR hasn't been specified" & exit /b 1 -if [%GENERATOR%] == [] set GENERATOR=NMake Makefiles +if NOT [%GENERATOR%] == [] set GENERATOR_PARAM=-G %GENERATOR% if NOT [%PLATFORM%] == [] set PLATFORM_PARAM=-A %PLATFORM% @@ -63,7 +63,7 @@ if %errorlevel% neq 0 exit /b %errorlevel% echo "Building COMMS library..." mkdir "%COMMS_BUILD_DIR%" cd %COMMS_BUILD_DIR% -cmake -G %GENERATOR% %PLATFORM_PARAM% -S %COMMS_SRC_DIR% -B %COMMS_BUILD_DIR% ^ +cmake %GENERATOR_PARAM% %PLATFORM_PARAM% -S %COMMS_SRC_DIR% -B %COMMS_BUILD_DIR% ^ -DCMAKE_INSTALL_PREFIX=%COMMS_INSTALL_DIR% -DCMAKE_BUILD_TYPE=%COMMON_BUILD_TYPE% ^ -DCMAKE_CXX_STANDARD=%COMMON_CXX_STANDARD% if %errorlevel% neq 0 exit /b %errorlevel% @@ -89,7 +89,7 @@ if %errorlevel% neq 0 exit /b %errorlevel% echo "Building cc.mqtt5.generated library..." mkdir "%CC_MQTT5_BUILD_DIR%" cd %CC_MQTT5_BUILD_DIR% -cmake -G %GENERATOR% %PLATFORM_PARAM% -S %CC_MQTT5_SRC_DIR% -B %CC_MQTT5_BUILD_DIR% ^ +cmake %GENERATOR_PARAM% %PLATFORM_PARAM% -S %CC_MQTT5_SRC_DIR% -B %CC_MQTT5_BUILD_DIR% ^ -DCMAKE_INSTALL_PREFIX=%CC_MQTT5_INSTALL_DIR% -DCMAKE_BUILD_TYPE=%COMMON_BUILD_TYPE% ^ -DCMAKE_CXX_STANDARD=%COMMON_CXX_STANDARD% -DOPT_REQUIRE_COMMS_LIB=OFF if %errorlevel% neq 0 exit /b %errorlevel% From a8757ab6003bd540c742572998b196976faa41d2 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sun, 7 Apr 2024 11:09:42 +1000 Subject: [PATCH 36/48] Fixing windows configuration in github actions. --- .github/workflows/actions_build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index afc42e3..f92ed3c 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -180,10 +180,10 @@ jobs: shell: cmd working-directory: ${{runner.workspace}}/build run: | - cmake %GITHUB_WORKSPACE% -G "Visual Studio 16 2019" -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_MQTT5_BUILD_UNIT_TESTS=ON -DCC_MQTT5_BUILD_INTEGRATION_TESTS=ON \ - -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES="%GITHUB_WORKSPACE%/client/lib/script/BareMetalTestConfig.cmake;%GITHUB_WORKSPACE%/client/lib/script/Qos0TestConfig.cmake;%GITHUB_WORKSPACE%/client/lib/script/Qos1TestConfig.cmake" \ + cmake %GITHUB_WORKSPACE% -G "Visual Studio 16 2019" -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_MQTT5_BUILD_UNIT_TESTS=ON -DCC_MQTT5_BUILD_INTEGRATION_TESTS=ON ^ + -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES="%GITHUB_WORKSPACE%/client/lib/script/BareMetalTestConfig.cmake;%GITHUB_WORKSPACE%/client/lib/script/Qos0TestConfig.cmake;%GITHUB_WORKSPACE%/client/lib/script/Qos1TestConfig.cmake" ^ -DCC_MQTT5_CLIENT_APPS=OFF -DCC_MQTT5_CLIENT_AFL_FUZZ=OFF - name: Build Target From a58208625561bc1f67993c250a4628bd302d9c41 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sun, 7 Apr 2024 11:14:00 +1000 Subject: [PATCH 37/48] Attempt to install boost in github actions windows runner. --- .github/workflows/actions_build.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index f92ed3c..01ea514 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -156,11 +156,10 @@ jobs: - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build - - name: Check GITHUB_WORKSPACE + - name: Install Boost shell: cmd run: | - echo github.workspace=${{ github.workspace }} - echo GITHUB_WORKSPACE=%GITHUB_WORKSPACE% + choco install boost-msvc-14.1 - name: Prepare externals shell: cmd @@ -181,10 +180,10 @@ jobs: working-directory: ${{runner.workspace}}/build run: | cmake %GITHUB_WORKSPACE% -G "Visual Studio 16 2019" -A ${{matrix.arch}} -DCMAKE_BUILD_TYPE=${{matrix.type}} -DCMAKE_INSTALL_PREFIX=install ^ - -DCMAKE_PREFIX_PATH=${{runner.workspace}}/build/install -DCMAKE_CXX_STANDARD=${{matrix.cpp}} ^ + -DCMAKE_PREFIX_PATH=${{runner.workspace}}/build/install -DCMAKE_CXX_STANDARD=${{matrix.cpp}} -DBoost_USE_STATIC_LIBS=ON ^ -DCC_MQTT5_BUILD_UNIT_TESTS=ON -DCC_MQTT5_BUILD_INTEGRATION_TESTS=ON ^ -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES="%GITHUB_WORKSPACE%/client/lib/script/BareMetalTestConfig.cmake;%GITHUB_WORKSPACE%/client/lib/script/Qos0TestConfig.cmake;%GITHUB_WORKSPACE%/client/lib/script/Qos1TestConfig.cmake" ^ - -DCC_MQTT5_CLIENT_APPS=OFF -DCC_MQTT5_CLIENT_AFL_FUZZ=OFF + -DCC_MQTT5_CLIENT_AFL_FUZZ=ON - name: Build Target working-directory: ${{runner.workspace}}/build From e86fc681cc881d525deb5c5505e8cd0aeb8084d4 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sun, 7 Apr 2024 11:47:57 +1000 Subject: [PATCH 38/48] Using "develop" branch of comms in the 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 01ea514..99a5a8c 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -3,7 +3,7 @@ name: Github Actions Build on: [push] env: - COMMS_TAG: v5.2.2 + COMMS_TAG: develop CC_MQTT5_TAG: v2.6 jobs: @@ -181,7 +181,7 @@ jobs: run: | cmake %GITHUB_WORKSPACE% -G "Visual Studio 16 2019" -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_MQTT5_BUILD_UNIT_TESTS=ON -DCC_MQTT5_BUILD_INTEGRATION_TESTS=ON ^ + -DCC_MQTT5_BUILD_UNIT_TESTS=ON ^ -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES="%GITHUB_WORKSPACE%/client/lib/script/BareMetalTestConfig.cmake;%GITHUB_WORKSPACE%/client/lib/script/Qos0TestConfig.cmake;%GITHUB_WORKSPACE%/client/lib/script/Qos1TestConfig.cmake" ^ -DCC_MQTT5_CLIENT_AFL_FUZZ=ON From cfcc0d0020a027e5aaa372ff3c185e7d20301d00 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sun, 7 Apr 2024 11:56:14 +1000 Subject: [PATCH 39/48] Installing boost for MSVC 2019 in windows runner of the 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 99a5a8c..9f849af 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -159,7 +159,7 @@ jobs: - name: Install Boost shell: cmd run: | - choco install boost-msvc-14.1 + choco install boost-msvc-14.2 - name: Prepare externals shell: cmd @@ -179,7 +179,7 @@ jobs: shell: cmd working-directory: ${{runner.workspace}}/build run: | - cmake %GITHUB_WORKSPACE% -G "Visual Studio 16 2019" -A ${{matrix.arch}} -DCMAKE_BUILD_TYPE=${{matrix.type}} -DCMAKE_INSTALL_PREFIX=install ^ + 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_MQTT5_BUILD_UNIT_TESTS=ON ^ -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES="%GITHUB_WORKSPACE%/client/lib/script/BareMetalTestConfig.cmake;%GITHUB_WORKSPACE%/client/lib/script/Qos0TestConfig.cmake;%GITHUB_WORKSPACE%/client/lib/script/Qos1TestConfig.cmake" ^ From 16ec84a80f41a40f6225b4a3bdcd2042811b6e9b Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sun, 7 Apr 2024 12:57:46 +1000 Subject: [PATCH 40/48] Added MSVC 2022 build to github actions. --- .github/workflows/actions_build.yml | 57 ++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index 9f849af..7423b8e 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -8,7 +8,6 @@ env: jobs: build_gcc_ubuntu_22_04: - if: false runs-on: ubuntu-22.04 strategy: fail-fast: false @@ -73,7 +72,6 @@ jobs: build_clang_ubuntu_22_04: - if: false runs-on: ubuntu-22.04 strategy: fail-fast: false @@ -196,3 +194,58 @@ jobs: shell: cmd run: ctest -V + build_msvc_2022: + runs-on: windows-2022 + strategy: + fail-fast: false + matrix: + type: [Debug, Release, MinSizeRel] + arch: [Win32, x64] + cpp: [17, 20] + + steps: + - uses: actions/checkout@v4 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Install Boost + shell: cmd + run: | + choco install boost-msvc-14.3 + + - name: Prepare externals + shell: cmd + run: | + %GITHUB_WORKSPACE%\script\prepare_externals.bat + env: + BUILD_DIR: ${{runner.workspace}}/build + PLATFORM: ${{matrix.arch}} + 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_MQTT5_TAG: ${{env.CC_MQTT5_TAG}} + + - name: Configure CMake + shell: cmd + working-directory: ${{runner.workspace}}/build + 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}} -DBoost_USE_STATIC_LIBS=ON ^ + -DCC_MQTT5_BUILD_UNIT_TESTS=ON ^ + -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES="%GITHUB_WORKSPACE%/client/lib/script/BareMetalTestConfig.cmake;%GITHUB_WORKSPACE%/client/lib/script/Qos0TestConfig.cmake;%GITHUB_WORKSPACE%/client/lib/script/Qos1TestConfig.cmake" ^ + -DCC_MQTT5_CLIENT_AFL_FUZZ=ON + + - name: Build Target + working-directory: ${{runner.workspace}}/build + shell: cmd + run: cmake --build . --config ${{matrix.type}} --target install + env: + VERBOSE: 1 + - name: Testing + working-directory: ${{runner.workspace}}/build + shell: cmd + run: ctest -V + From d11917d76dc0401d4c6186f13dc8f855cc8faf5f Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sun, 7 Apr 2024 16:07:17 +1000 Subject: [PATCH 41/48] Not using boost for msvc2022 compilation in 32 bit mode on github actions. --- .github/workflows/actions_build.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index 7423b8e..fec7813 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -210,6 +210,7 @@ jobs: run: cmake -E make_directory ${{runner.workspace}}/build - name: Install Boost + if: ${{ matrix.arch == x64 } shell: cmd run: | choco install boost-msvc-14.3 @@ -236,7 +237,9 @@ jobs: -DCMAKE_PREFIX_PATH=${{runner.workspace}}/build/install -DCMAKE_CXX_STANDARD=${{matrix.cpp}} -DBoost_USE_STATIC_LIBS=ON ^ -DCC_MQTT5_BUILD_UNIT_TESTS=ON ^ -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES="%GITHUB_WORKSPACE%/client/lib/script/BareMetalTestConfig.cmake;%GITHUB_WORKSPACE%/client/lib/script/Qos0TestConfig.cmake;%GITHUB_WORKSPACE%/client/lib/script/Qos1TestConfig.cmake" ^ - -DCC_MQTT5_CLIENT_AFL_FUZZ=ON + -DCC_MQTT5_CLIENT_APPS=${{env.HAS_BOOST}} -DCC_MQTT5_CLIENT_AFL_FUZZ=${{env.HAS_BOOST}} + env: + HAS_BOOST: "${{ matrix.arch == x64 && 'ON' || 'OFF' }}" - name: Build Target working-directory: ${{runner.workspace}}/build From f7bbb688825620ac280f38651379f0ffd03121e1 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sun, 7 Apr 2024 16:14:30 +1000 Subject: [PATCH 42/48] Attempt to fix github actions configuration. --- .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 fec7813..84e3526 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -210,7 +210,7 @@ jobs: run: cmake -E make_directory ${{runner.workspace}}/build - name: Install Boost - if: ${{ matrix.arch == x64 } + if: matrix.arch == x64 shell: cmd run: | choco install boost-msvc-14.3 From a290b706ef1dd45dcb0b52fe5133eb63db733eba Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sun, 7 Apr 2024 16:17:27 +1000 Subject: [PATCH 43/48] Attempt to fix github actions configuration. --- .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 84e3526..bf88a07 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -239,7 +239,7 @@ jobs: -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES="%GITHUB_WORKSPACE%/client/lib/script/BareMetalTestConfig.cmake;%GITHUB_WORKSPACE%/client/lib/script/Qos0TestConfig.cmake;%GITHUB_WORKSPACE%/client/lib/script/Qos1TestConfig.cmake" ^ -DCC_MQTT5_CLIENT_APPS=${{env.HAS_BOOST}} -DCC_MQTT5_CLIENT_AFL_FUZZ=${{env.HAS_BOOST}} env: - HAS_BOOST: "${{ matrix.arch == x64 && 'ON' || 'OFF' }}" + HAS_BOOST: "${{ matrix.arch == 'x64' && 'ON' || 'OFF' }}" - name: Build Target working-directory: ${{runner.workspace}}/build From 656a104e957d907483ae4d40134324f5d778de2a Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sun, 7 Apr 2024 16:18:14 +1000 Subject: [PATCH 44/48] Another attempt to fix github action configuration. --- .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 bf88a07..d09e2fe 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -210,7 +210,7 @@ jobs: run: cmake -E make_directory ${{runner.workspace}}/build - name: Install Boost - if: matrix.arch == x64 + if: matrix.arch == 'x64' shell: cmd run: | choco install boost-msvc-14.3 From 4e702d771ec08b7a745b7c97ef073a29109e2b47 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Mon, 8 Apr 2024 07:43:36 +1000 Subject: [PATCH 45/48] Updating next release version to be v0.4. --- client/lib/include/cc_mqtt5_client/common.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/lib/include/cc_mqtt5_client/common.h b/client/lib/include/cc_mqtt5_client/common.h index 7ba54a3..eb78688 100644 --- a/client/lib/include/cc_mqtt5_client/common.h +++ b/client/lib/include/cc_mqtt5_client/common.h @@ -21,11 +21,11 @@ extern "C" { /// @brief Minor verion of the library /// @ingroup global -#define CC_MQTT5_CLIENT_MINOR_VERSION 3U +#define CC_MQTT5_CLIENT_MINOR_VERSION 4U /// @brief Patch level of the library /// @ingroup global -#define CC_MQTT5_CLIENT_PATCH_VERSION 2U +#define CC_MQTT5_CLIENT_PATCH_VERSION 0U /// @brief Macro to create numeric version as single unsigned number /// @ingroup global From ad6decf596d4c21d65c61a4da2f61e1743e39c1e Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sat, 13 Apr 2024 10:36:54 +1000 Subject: [PATCH 46/48] Improvements to prepare_externals.bat. --- script/prepare_externals.bat | 59 +++++++++++++++++------------------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/script/prepare_externals.bat b/script/prepare_externals.bat index dbb84a6..8abdf9d 100755 --- a/script/prepare_externals.bat +++ b/script/prepare_externals.bat @@ -44,22 +44,20 @@ if NOT [%COMMON_INSTALL_DIR%] == [] set CC_MQTT5_INSTALL_DIR=%COMMON_INSTALL_DIR rem ---------------------------------------------------- mkdir "%EXTERNALS_DIR%" -if exist %COMMS_SRC_DIR%/.git goto comms_update -echo "Cloning COMMS library..." -git clone -b %COMMS_TAG% %COMMS_REPO% %COMMS_SRC_DIR% -if %errorlevel% neq 0 exit /b %errorlevel% -goto comms_build - -:comms_update -echo "Updating COMMS library..." -cd "%COMMS_SRC_DIR%" -git fetch --all -git checkout . -git checkout %COMMS_TAG% -git pull --all -if %errorlevel% neq 0 exit /b %errorlevel% +if exist %COMMS_SRC_DIR%/.git ( + echo "Updating COMMS library..." + cd "%COMMS_SRC_DIR%" + git fetch --all + git checkout . + git checkout %COMMS_TAG% + git pull --all + if %errorlevel% neq 0 exit /b %errorlevel% +) else ( + echo "Cloning COMMS library..." + git clone -b %COMMS_TAG% %COMMS_REPO% %COMMS_SRC_DIR% + if %errorlevel% neq 0 exit /b %errorlevel% +) -:comms_build echo "Building COMMS library..." mkdir "%COMMS_BUILD_DIR%" cd %COMMS_BUILD_DIR% @@ -70,22 +68,22 @@ if %errorlevel% neq 0 exit /b %errorlevel% cmake --build %COMMS_BUILD_DIR% --config %COMMON_BUILD_TYPE% --target install if %errorlevel% neq 0 exit /b %errorlevel% -if exist %CC_MQTT5_SRC_DIR%/.git goto mqtt5_update -echo "Cloning cc.mqtt5.generated library..." -git clone -b %CC_MQTT5_TAG% %CC_MQTT5_REPO% %CC_MQTT5_SRC_DIR% -if %errorlevel% neq 0 exit /b %errorlevel% -goto mqtt5_build - -:mqtt5_update -echo "Updating cc.mqtt5.generated library..." -cd "%CC_MQTT5_SRC_DIR%" -git fetch --all -git checkout . -git checkout %CC_MQTT5_TAG% -git pull --all -if %errorlevel% neq 0 exit /b %errorlevel% +rem ---------------------------------------------------- + +if exist %CC_MQTT5_SRC_DIR%/.git ( + echo "Updating cc.mqtt5.generated library..." + cd "%CC_MQTT5_SRC_DIR%" + git fetch --all + git checkout . + git checkout %CC_MQTT5_TAG% + git pull --all + if %errorlevel% neq 0 exit /b %errorlevel% +) else ( + echo "Cloning cc.mqtt5.generated library..." + git clone -b %CC_MQTT5_TAG% %CC_MQTT5_REPO% %CC_MQTT5_SRC_DIR% + if %errorlevel% neq 0 exit /b %errorlevel% +) -:mqtt5_build echo "Building cc.mqtt5.generated library..." mkdir "%CC_MQTT5_BUILD_DIR%" cd %CC_MQTT5_BUILD_DIR% @@ -95,4 +93,3 @@ cmake %GENERATOR_PARAM% %PLATFORM_PARAM% -S %CC_MQTT5_SRC_DIR% -B %CC_MQTT5_BUIL if %errorlevel% neq 0 exit /b %errorlevel% cmake --build %CC_MQTT5_BUILD_DIR% --config %COMMON_BUILD_TYPE% --target install if %errorlevel% neq 0 exit /b %errorlevel% - From d8b0e9eecfe27723c0ffef01f48c22aacfb2f4bc Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sat, 13 Apr 2024 10:38:16 +1000 Subject: [PATCH 47/48] Requiring v2.7 of the cc.mqtt5.libs. --- .appveyor.yml | 4 ++-- .github/workflows/actions_build.yml | 4 ++-- client/lib/src/ProtocolDefs.h | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index aaeb150..9a31dde 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -19,8 +19,8 @@ configuration: - Release environment: - COMMS_BRANCH: v5.2.2 - CC_MQTT5_BRANCH: v2.6 + COMMS_BRANCH: v5.2.3 + CC_MQTT5_BRANCH: v2.7 matrix: - CPP_STD: 17 - CPP_STD: 20 diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index d09e2fe..c5a1462 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -3,8 +3,8 @@ name: Github Actions Build on: [push] env: - COMMS_TAG: develop - CC_MQTT5_TAG: v2.6 + COMMS_TAG: v5.2.3 + CC_MQTT5_TAG: v2.7 jobs: build_gcc_ubuntu_22_04: diff --git a/client/lib/src/ProtocolDefs.h b/client/lib/src/ProtocolDefs.h index 15e7308..31d63ae 100644 --- a/client/lib/src/ProtocolDefs.h +++ b/client/lib/src/ProtocolDefs.h @@ -20,7 +20,7 @@ #include -static_assert(CC_MQTT5_VERSION <= COMMS_MAKE_VERSION(2, 6, 0), +static_assert(CC_MQTT5_VERSION <= COMMS_MAKE_VERSION(2, 7, 0), "The version of the cc_mqtt5 library is too low."); namespace cc_mqtt5_client From ef87d75d15988948ddd62be360e77b37b527a0bf Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sat, 13 Apr 2024 10:55:16 +1000 Subject: [PATCH 48/48] Added an ability to disable retained messages in cc_mqtt5_client_sub. --- client/app/common/ProgramOptions.cpp | 6 ++++++ client/app/common/ProgramOptions.h | 1 + client/app/sub/Sub.cpp | 5 +++++ client/lib/include/cc_mqtt5_client/common.h | 2 +- client/lib/test/unit/UnitTestSubscribe.th | 4 ++-- 5 files changed, 15 insertions(+), 3 deletions(-) diff --git a/client/app/common/ProgramOptions.cpp b/client/app/common/ProgramOptions.cpp index bbcd301..44bed0d 100644 --- a/client/app/common/ProgramOptions.cpp +++ b/client/app/common/ProgramOptions.cpp @@ -109,6 +109,7 @@ void ProgramOptions::addSubscribe() ("sub-topic,t", po::value(), "Subscribe topic filter. Can be used multiple times.") ("sub-qos,q", po::value(), "Subscribe max QoS: 0, 1, or 2. Defaults to 2. Can be used multiple times " "(for each topic filter correspondingly).") + ("sub-no-retained", "Disable reception of the retained messages") ("sub-binary", "Force binary output of the received message data") ("sub-id", po::value()->default_value(0), "\"Subscription Identifier\" property.") ("sub-user-prop", po::value(), "Add \"User Property\" in \"key=value\" format to the SUBSCRIBE request.") @@ -332,6 +333,11 @@ ProgramOptions::UnsignedsList ProgramOptions::subQoses() const return result; } +bool ProgramOptions::subNoRetained() const +{ + return m_vm.count("sub-no-retained") > 0U; +} + bool ProgramOptions::subBinary() const { return m_vm.count("sub-binary") > 0U; diff --git a/client/app/common/ProgramOptions.h b/client/app/common/ProgramOptions.h index 09dbcde..88cba58 100644 --- a/client/app/common/ProgramOptions.h +++ b/client/app/common/ProgramOptions.h @@ -89,6 +89,7 @@ class ProgramOptions // Subscribe Options StringsList subTopics() const; UnsignedsList subQoses() const; + bool subNoRetained() const; bool subBinary() const; unsigned subId() const; StringsList subUserProps() const; diff --git a/client/app/sub/Sub.cpp b/client/app/sub/Sub.cpp index 8605c2a..bfc3e5a 100644 --- a/client/app/sub/Sub.cpp +++ b/client/app/sub/Sub.cpp @@ -36,6 +36,7 @@ void Sub::brokerConnectedImpl() { auto topics = opts().subTopics(); auto qoses = opts().subQoses(); + bool noRetained = opts().subNoRetained(); auto ec = CC_Mqtt5ErrorCode_InternalError; auto* subscribe = ::cc_mqtt5_client_subscribe_prepare(client(), &ec); @@ -54,6 +55,10 @@ void Sub::brokerConnectedImpl() topicConfig.m_maxQos = static_cast(qoses[idx]); } + if (noRetained) { + topicConfig.m_retainHandling = CC_Mqtt5RetainHandling_DoNotSend; + } + ec = ::cc_mqtt5_client_subscribe_config_topic(subscribe, &topicConfig); if (ec != CC_Mqtt5ErrorCode_Success) { logError() << "Failed to configure topic \"" << topics[idx] << "\" with ec=" << toString(ec) << std::endl; diff --git a/client/lib/include/cc_mqtt5_client/common.h b/client/lib/include/cc_mqtt5_client/common.h index eb78688..13a700b 100644 --- a/client/lib/include/cc_mqtt5_client/common.h +++ b/client/lib/include/cc_mqtt5_client/common.h @@ -173,7 +173,7 @@ typedef enum { CC_Mqtt5RetainHandling_Send = 0, ///< Send retained messages at the time of the subscribe CC_Mqtt5RetainHandling_SendIfDoesNotExist = 1, ///< Send retained messages at subscribe only if the subscription does not currently exist - CC_Mqtt5AuthErrorCode_DoNotSend = 2, ///< Do not send retained messages at the time of the subscribe + CC_Mqtt5RetainHandling_DoNotSend = 2, ///< Do not send retained messages at the time of the subscribe CC_Mqtt5RetainHandling_ValuesLimit ///< Limit for the values } CC_Mqtt5RetainHandling; diff --git a/client/lib/test/unit/UnitTestSubscribe.th b/client/lib/test/unit/UnitTestSubscribe.th index 79d994a..7460bea 100644 --- a/client/lib/test/unit/UnitTestSubscribe.th +++ b/client/lib/test/unit/UnitTestSubscribe.th @@ -68,7 +68,7 @@ void UnitTestSubscribe::test1() const std::string SubTopic2 = "/sub/topic/2"; const CC_Mqtt5QoS SubQos2 = CC_Mqtt5QoS_ExactlyOnceDelivery; - const CC_Mqtt5RetainHandling SubRetainHandling2 = CC_Mqtt5AuthErrorCode_DoNotSend; + const CC_Mqtt5RetainHandling SubRetainHandling2 = CC_Mqtt5RetainHandling_DoNotSend; const bool SubNoLocal2 = true; const bool SubRetainAsPublish2 = false; auto subscribeConfig2 = CC_Mqtt5SubscribeTopicConfig(); @@ -194,7 +194,7 @@ void UnitTestSubscribe::test2() const std::string SubTopic2 = "/sub/topic/2"; const CC_Mqtt5QoS SubQos2 = CC_Mqtt5QoS_ExactlyOnceDelivery; - const CC_Mqtt5RetainHandling SubRetainHandling2 = CC_Mqtt5AuthErrorCode_DoNotSend; + const CC_Mqtt5RetainHandling SubRetainHandling2 = CC_Mqtt5RetainHandling_DoNotSend; const bool SubNoLocal2 = true; const bool SubRetainAsPublish2 = false; auto subscribeConfig2 = CC_Mqtt5SubscribeTopicConfig();