From 72582d697234d545f2f49768dfaa997f35d35952 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 5 Jul 2024 09:01:06 +1000 Subject: [PATCH] Added "disconnect" operation to the new client. --- client/lib/CMakeLists.txt | 1 + client/lib/include/cc_mqttsn_client/common.h | 17 ++ client/lib/src/ClientImpl.cpp | 114 ++++++------ client/lib/src/ClientImpl.h | 14 +- client/lib/src/ExtConfig.h | 8 +- client/lib/src/op/DisconnectOp.cpp | 159 ++++++++++++++++ client/lib/src/op/DisconnectOp.h | 58 ++++++ client/lib/src/op/Op.h | 2 +- client/lib/templ/client.cpp.templ | 75 ++++++++ client/lib/templ/client.h.templ | 72 ++++++++ client/lib/test/CMakeLists.txt | 2 +- client/lib/test/UnitTestCommonBase.cpp | 130 +++++++++++++ client/lib/test/UnitTestCommonBase.h | 31 ++++ client/lib/test/UnitTestDefaultBase.cpp | 8 + client/lib/test/UnitTestDisconnect.th | 184 +++++++++++++++++++ 15 files changed, 810 insertions(+), 65 deletions(-) create mode 100644 client/lib/src/op/DisconnectOp.cpp create mode 100644 client/lib/src/op/DisconnectOp.h create mode 100644 client/lib/test/UnitTestDisconnect.th diff --git a/client/lib/CMakeLists.txt b/client/lib/CMakeLists.txt index 88e6653..ee1cdb0 100644 --- a/client/lib/CMakeLists.txt +++ b/client/lib/CMakeLists.txt @@ -166,6 +166,7 @@ function (gen_lib_mqttsn_client config_file) message (STATUS "Defining library ${lib_name}") set (src src/op/ConnectOp.cpp + src/op/DisconnectOp.cpp src/op/KeepAliveOp.cpp src/op/Op.cpp src/op/SearchOp.cpp diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index d154ac2..ee6ee27 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -140,6 +140,15 @@ struct CC_MqttsnConnect; /// @ingroup "connect". typedef struct CC_MqttsnConnect* CC_MqttsnConnectHandle; +/// @brief Declaration of the hidden structure used to define @ref CC_MqttsnDisconnectHandle +/// @ingroup disconnect +struct CC_MqttsnDisconnect; + +/// @brief Handle for "disconnect" operation. +/// @details Returned by @b cc_mqttsn_client_disconnect_prepare() function. +/// @ingroup "disconnect". +typedef struct CC_MqttsnDisconnect* CC_MqttsnDisconnectHandle; + /// @brief Type used to hold Topic ID value. typedef unsigned short CC_MqttsnTopicId; @@ -288,6 +297,14 @@ typedef void (*CC_MqttsnSearchCompleteCb)(void* data, CC_MqttsnAsyncOpStatus sta /// @ingroup connect typedef void (*CC_MqttsnConnectCompleteCb)(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info); +/// @brief Callback used to report completion of the disconnect operation. +/// @param[in] data Pointer to user data object, passed as the last parameter to +/// the request call. +/// @param[in] status Status of the "disconnect" operation. +/// @post The data members of the reported response can NOT be accessed after the function returns. +/// @ingroup disconnect +typedef void (*CC_MqttsnDisconnectCompleteCb)(void* data, CC_MqttsnAsyncOpStatus status); + #ifdef __cplusplus } diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index cb2bb1e..dc7b1bf 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -169,6 +169,13 @@ op::ConnectOp* ClientImpl::connectPrepare(CC_MqttsnErrorCode* ec) break; } + if (!m_disconnectOps.empty()) { + // Already allocated + errorLog("Another disconnect operation is in progress."); + updateEc(ec, CC_MqttsnErrorCode_Busy); + break; + } + if (m_sessionState.m_disconnecting) { errorLog("Session disconnection is in progress, cannot initiate connection."); updateEc(ec, CC_MqttsnErrorCode_Disconnecting); @@ -210,62 +217,63 @@ op::ConnectOp* ClientImpl::connectPrepare(CC_MqttsnErrorCode* ec) return op; } -// op::DisconnectOp* ClientImpl::disconnectPrepare(CC_MqttsnErrorCode* ec) -// { -// op::DisconnectOp* disconnectOp = nullptr; -// do { -// if (!m_sessionState.m_connected) { -// errorLog("Client must be connected to allow disconnect."); -// updateEc(ec, CC_MqttsnErrorCode_NotConnected); -// break; -// } +op::DisconnectOp* ClientImpl::disconnectPrepare(CC_MqttsnErrorCode* ec) +{ + op::DisconnectOp* op = nullptr; + do { + if (!m_sessionState.m_connected) { + errorLog("Client must be connected to allow disconnect."); + updateEc(ec, CC_MqttsnErrorCode_NotConnected); + break; + } -// if (!m_disconnectOps.empty()) { -// errorLog("Another disconnect operation is in progress."); -// updateEc(ec, CC_MqttsnErrorCode_Busy); -// break; -// } + if (!m_disconnectOps.empty()) { + errorLog("Another disconnect operation is in progress."); + updateEc(ec, CC_MqttsnErrorCode_Busy); + break; + } -// if (m_sessionState.m_disconnecting) { -// errorLog("Session disconnection is in progress, cannot initiate disconnection."); -// updateEc(ec, CC_MqttsnErrorCode_Disconnecting); -// break; -// } + if (!m_connectOps.empty()) { + // Already allocated + errorLog("Another connect operation is in progress."); + updateEc(ec, CC_MqttsnErrorCode_Busy); + break; + } -// if (m_clientState.m_networkDisconnected) { -// errorLog("Network is disconnected."); -// updateEc(ec, CC_MqttsnErrorCode_NetworkDisconnected); -// break; -// } + if (m_sessionState.m_disconnecting) { + errorLog("Session disconnection is in progress, cannot initiate disconnection."); + updateEc(ec, CC_MqttsnErrorCode_Disconnecting); + break; + } -// if (m_ops.max_size() <= m_ops.size()) { -// errorLog("Cannot start disconnect operation, retry in next event loop iteration."); -// updateEc(ec, CC_MqttsnErrorCode_RetryLater); -// break; -// } + if (m_ops.max_size() <= m_ops.size()) { + errorLog("Cannot start disconnect operation, retry in next event loop iteration."); + updateEc(ec, CC_MqttsnErrorCode_RetryLater); + break; + } -// if (m_preparationLocked) { -// errorLog("Another operation is being prepared, cannot prepare \"disconnect\" without \"send\" or \"cancel\" of the previous."); -// updateEc(ec, CC_MqttsnErrorCode_PreparationLocked); -// break; -// } + if (m_preparationLocked) { + errorLog("Another operation is being prepared, cannot prepare \"disconnect\" without \"send\" or \"cancel\" of the previous."); + updateEc(ec, CC_MqttsnErrorCode_PreparationLocked); + break; + } -// auto ptr = m_disconnectOpsAlloc.alloc(*this); -// if (!ptr) { -// errorLog("Cannot allocate new disconnect operation."); -// updateEc(ec, CC_MqttsnErrorCode_OutOfMemory); -// break; -// } + auto ptr = m_disconnectOpsAlloc.alloc(*this); + if (!ptr) { + errorLog("Cannot allocate new disconnect operation."); + updateEc(ec, CC_MqttsnErrorCode_OutOfMemory); + break; + } -// m_preparationLocked = true; -// m_ops.push_back(ptr.get()); -// m_disconnectOps.push_back(std::move(ptr)); -// disconnectOp = m_disconnectOps.back().get(); -// updateEc(ec, CC_MqttsnErrorCode_Success); -// } while (false); + m_preparationLocked = true; + m_ops.push_back(ptr.get()); + m_disconnectOps.push_back(std::move(ptr)); + op = m_disconnectOps.back().get(); + updateEc(ec, CC_MqttsnErrorCode_Success); + } while (false); -// return disconnectOp; -// } + return op; +} // op::SubscribeOp* ClientImpl::subscribePrepare(CC_MqttsnErrorCode* ec) // { @@ -829,7 +837,7 @@ void ClientImpl::opComplete(const op::Op* op) /* Type_Search */ &ClientImpl::opComplete_Search, /* Type_Connect */ &ClientImpl::opComplete_Connect, /* Type_KeepAlive */ &ClientImpl::opComplete_KeepAlive, - // /* Type_Disconnect */ &ClientImpl::opComplete_Disconnect, + /* Type_Disconnect */ &ClientImpl::opComplete_Disconnect, // /* Type_Subscribe */ &ClientImpl::opComplete_Subscribe, // /* Type_Unsubscribe */ &ClientImpl::opComplete_Unsubscribe, // /* Type_Recv */ &ClientImpl::opComplete_Recv, @@ -1172,10 +1180,10 @@ void ClientImpl::opComplete_KeepAlive(const op::Op* op) eraseFromList(op, m_keepAliveOps); } -// void ClientImpl::opComplete_Disconnect(const op::Op* op) -// { -// eraseFromList(op, m_disconnectOps); -// } +void ClientImpl::opComplete_Disconnect(const op::Op* op) +{ + eraseFromList(op, m_disconnectOps); +} // void ClientImpl::opComplete_Subscribe(const op::Op* op) // { diff --git a/client/lib/src/ClientImpl.h b/client/lib/src/ClientImpl.h index 192e49c..38fea9b 100644 --- a/client/lib/src/ClientImpl.h +++ b/client/lib/src/ClientImpl.h @@ -18,7 +18,7 @@ #include "TimerMgr.h" #include "op/ConnectOp.h" -// #include "op/DisconnectOp.h" +#include "op/DisconnectOp.h" #include "op/KeepAliveOp.h" #include "op/Op.h" // #include "op/RecvOp.h" @@ -70,7 +70,7 @@ class ClientImpl final : public ProtMsgHandler op::SearchOp* searchPrepare(CC_MqttsnErrorCode* ec); op::ConnectOp* connectPrepare(CC_MqttsnErrorCode* ec); - // op::DisconnectOp* disconnectPrepare(CC_MqttsnErrorCode* ec); + op::DisconnectOp* disconnectPrepare(CC_MqttsnErrorCode* ec); // op::SubscribeOp* subscribePrepare(CC_MqttsnErrorCode* ec); // op::UnsubscribeOp* unsubscribePrepare(CC_MqttsnErrorCode* ec); // op::SendOp* publishPrepare(CC_MqttsnErrorCode* ec); @@ -240,8 +240,8 @@ class ClientImpl final : public ProtMsgHandler using KeepAliveOpAlloc = ObjAllocator; using KeepAliveOpsList = ObjListType; - // using DisconnectOpAlloc = ObjAllocator; - // using DisconnectOpsList = ObjListType; + using DisconnectOpAlloc = ObjAllocator; + using DisconnectOpsList = ObjListType; // using SubscribeOpAlloc = ObjAllocator; // using SubscribeOpsList = ObjListType; @@ -282,7 +282,7 @@ class ClientImpl final : public ProtMsgHandler void opComplete_Search(const op::Op* op); void opComplete_Connect(const op::Op* op); void opComplete_KeepAlive(const op::Op* op); - // void opComplete_Disconnect(const op::Op* op); + void opComplete_Disconnect(const op::Op* op); // void opComplete_Subscribe(const op::Op* op); // void opComplete_Unsubscribe(const op::Op* op); // void opComplete_Recv(const op::Op* op); @@ -346,8 +346,8 @@ class ClientImpl final : public ProtMsgHandler KeepAliveOpAlloc m_keepAliveOpsAlloc; KeepAliveOpsList m_keepAliveOps; - // DisconnectOpAlloc m_disconnectOpsAlloc; - // DisconnectOpsList m_disconnectOps; + DisconnectOpAlloc m_disconnectOpsAlloc; + DisconnectOpsList m_disconnectOps; // SubscribeOpAlloc m_subscribeOpsAlloc; // SubscribeOpsList m_subscribeOps; diff --git a/client/lib/src/ExtConfig.h b/client/lib/src/ExtConfig.h index 4a1aa1e..a45259b 100644 --- a/client/lib/src/ExtConfig.h +++ b/client/lib/src/ExtConfig.h @@ -24,7 +24,7 @@ struct ExtConfig : public Config static constexpr unsigned ConnectOpTimers = 1U; static constexpr unsigned KeepAliveOpTimers = 3U; static constexpr unsigned DisconnectOpsLimit = HasDynMemAlloc ? 0 : 1U; - static constexpr unsigned DisconnectOpTimers = 0U; + static constexpr unsigned DisconnectOpTimers = 1U; static constexpr unsigned SubscribeOpTimers = 1U; static constexpr unsigned UnsubscribeOpTimers = 1U; static constexpr unsigned RecvOpsLimit = MaxQos < 2 ? 1U : (ReceiveMaxLimit == 0U ? 0U : ReceiveMaxLimit + 1U); @@ -34,8 +34,8 @@ struct ExtConfig : public Config static constexpr bool HasOpsLimit = (SearchOpsLimit > 0U) && (ConnectOpsLimit > 0U) && - (KeepAliveOpsLimit > 0U) /* && - (DisconnectOpsLimit > 0U) && + (KeepAliveOpsLimit > 0U) && + (DisconnectOpsLimit > 0U) /* && (SubscribeOpsLimit > 0U) && (UnsubscribeOpsLimit > 0U) && (RecvOpsLimit > 0U) && @@ -44,6 +44,7 @@ struct ExtConfig : public Config (DiscoveryTimers) + (SearchOpsLimit * SearchOpTimers) + (ConnectOpsLimit * ConnectOpTimers) + + (DisconnectOpsLimit * DisconnectOpTimers) + (KeepAliveOpsLimit * KeepAliveOpTimers) + (DisconnectOpsLimit * DisconnectOpTimers) + (SubscribeOpsLimit * SubscribeOpTimers) + @@ -73,6 +74,7 @@ struct ExtConfig : public Config static_assert(HasDynMemAlloc || (TimersLimit > 0U)); static_assert(HasDynMemAlloc || (ConnectOpsLimit > 0U)); static_assert(HasDynMemAlloc || (KeepAliveOpsLimit > 0U)); + static_assert(HasDynMemAlloc || (DisconnectOpsLimit > 0U)); // static_assert(HasDynMemAlloc || (RecvOpsLimit > 0U)); // static_assert(HasDynMemAlloc || (SendOpsLimit > 0U)); static_assert(HasDynMemAlloc || (OpsLimit > 0U)); diff --git a/client/lib/src/op/DisconnectOp.cpp b/client/lib/src/op/DisconnectOp.cpp new file mode 100644 index 0000000..4c6766c --- /dev/null +++ b/client/lib/src/op/DisconnectOp.cpp @@ -0,0 +1,159 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "op/DisconnectOp.h" +#include "ClientImpl.h" + +#include "comms/util/assign.h" +#include "comms/util/ScopeGuard.h" +#include "comms/units.h" + +#include +#include + +namespace cc_mqttsn_client +{ + +namespace op +{ + +namespace +{ + +inline DisconnectOp* asDisconnectOp(void* data) +{ + return reinterpret_cast(data); +} + +} // namespace + + +DisconnectOp::DisconnectOp(ClientImpl& client) : + Base(client), + m_timer(client.timerMgr().allocTimer()) +{ +} + +CC_MqttsnErrorCode DisconnectOp::send(CC_MqttsnDisconnectCompleteCb cb, void* cbData) +{ + client().allowNextPrepare(); + auto completeOnError = + comms::util::makeScopeGuard( + [this]() + { + opComplete(); + }); + + if (cb == nullptr) { + errorLog("Disconnect completion callback is not provided."); + return CC_MqttsnErrorCode_BadParam; + } + + if (!m_timer.isValid()) { + errorLog("The library cannot allocate required number of timers."); + return CC_MqttsnErrorCode_InternalError; + } + + auto guard = client().apiEnter(); + m_cb = cb; + m_cbData = cbData; + + auto ec = sendInternal(); + if (ec != CC_MqttsnErrorCode_Success) { + return ec; + } + + completeOnError.release(); + return CC_MqttsnErrorCode_Success; +} + +CC_MqttsnErrorCode DisconnectOp::cancel() +{ + if (m_cb == nullptr) { + // hasn't been sent yet + client().allowNextPrepare(); + } + + opComplete(); + return CC_MqttsnErrorCode_Success; +} + +void DisconnectOp::handle([[maybe_unused]] DisconnectMsg& msg) +{ + m_timer.cancel(); + + auto& cl = client(); + auto onExit = + comms::util::makeScopeGuard( + [&cl]() + { + cl.gatewayDisconnected(); + }); + + completeOpInternal(CC_MqttsnAsyncOpStatus_Complete); +} + +Op::Type DisconnectOp::typeImpl() const +{ + return Type_Disconnect; +} + +void DisconnectOp::terminateOpImpl(CC_MqttsnAsyncOpStatus status) +{ + completeOpInternal(status); +} + +void DisconnectOp::completeOpInternal(CC_MqttsnAsyncOpStatus status) +{ + auto cb = m_cb; + auto* cbData = m_cbData; + opComplete(); // mustn't access data members after destruction + if (cb != nullptr) { + cb(cbData, status); + } +} + +void DisconnectOp::restartTimer() +{ + m_timer.wait(getRetryPeriod(), &DisconnectOp::opTimeoutCb, this); +} + +CC_MqttsnErrorCode DisconnectOp::sendInternal() +{ + auto ec = sendMessage(m_disconnectMsg); + if (ec == CC_MqttsnErrorCode_Success) { + restartTimer(); + } + + return ec; +} + +void DisconnectOp::timeoutInternal() +{ + if (getRetryCount() == 0U) { + errorLog("All retries of the disconnect operation have been exhausted."); + completeOpInternal(CC_MqttsnAsyncOpStatus_Timeout); + return; + } + + auto ec = sendInternal(); + if (ec != CC_MqttsnErrorCode_Success) { + completeOpInternal(translateErrorCodeToAsyncOpStatus(ec)); + return; + } + + decRetryCount(); +} + +void DisconnectOp::opTimeoutCb(void* data) +{ + asDisconnectOp(data)->timeoutInternal(); +} + +} // namespace op + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/op/DisconnectOp.h b/client/lib/src/op/DisconnectOp.h new file mode 100644 index 0000000..9b8b497 --- /dev/null +++ b/client/lib/src/op/DisconnectOp.h @@ -0,0 +1,58 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "op/Op.h" +#include "ExtConfig.h" +#include "ProtocolDefs.h" + +#include "TimerMgr.h" + +namespace cc_mqttsn_client +{ + +namespace op +{ + +class DisconnectOp final : public Op +{ + using Base = Op; +public: + explicit DisconnectOp(ClientImpl& client); + + CC_MqttsnErrorCode send(CC_MqttsnDisconnectCompleteCb cb, void* cbData); + CC_MqttsnErrorCode cancel(); + + using Base::handle; + void handle(DisconnectMsg& msg) override; + +protected: + virtual Type typeImpl() const override; + virtual void terminateOpImpl(CC_MqttsnAsyncOpStatus status) override; + +private: + + void completeOpInternal(CC_MqttsnAsyncOpStatus status); + void restartTimer(); + CC_MqttsnErrorCode sendInternal(); + void timeoutInternal(); + + static void opTimeoutCb(void* data); + + DisconnectMsg m_disconnectMsg; + TimerMgr::Timer m_timer; + CC_MqttsnDisconnectCompleteCb m_cb = nullptr; + void* m_cbData = nullptr; + + static_assert(ExtConfig::DisconnectOpTimers == 1U); +}; + +} // namespace op + + +} // namespace cc_mqttsn_client diff --git a/client/lib/src/op/Op.h b/client/lib/src/op/Op.h index 858a22f..c473fe9 100644 --- a/client/lib/src/op/Op.h +++ b/client/lib/src/op/Op.h @@ -31,7 +31,7 @@ class Op : public ProtMsgHandler Type_Search, Type_Connect, Type_KeepAlive, - // Type_Disconnect, + Type_Disconnect, // Type_Subscribe, // Type_Unsubscribe, // Type_Recv, diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index 78595df..849ea7c 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -17,6 +17,7 @@ struct CC_MqttsnClient {}; struct CC_MqttsnSearch {}; struct CC_MqttsnConnect {}; +struct CC_MqttsnDisconnect {}; namespace { @@ -59,6 +60,16 @@ inline CC_MqttsnConnectHandle handleFromConnectOp(cc_mqttsn_client::op::ConnectO return reinterpret_cast(op); } +inline cc_mqttsn_client::op::DisconnectOp* disconnectOpFromHandle(CC_MqttsnDisconnectHandle handle) +{ + return reinterpret_cast(handle); +} + +inline CC_MqttsnDisconnectHandle handleFromDisconnectOp(cc_mqttsn_client::op::DisconnectOp* op) +{ + return reinterpret_cast(op); +} + } // namespace CC_MqttsnClientHandle cc_mqttsn_##NAME##client_alloc() @@ -554,6 +565,70 @@ bool cc_mqttsn_##NAME##client_is_connected(CC_MqttsnClientHandle client) return clientFromHandle(client)->sessionState().m_connected; } +CC_MqttsnDisconnectHandle cc_mqttsn_##NAME##client_disconnect_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec) +{ + COMMS_ASSERT(client != nullptr); + return handleFromDisconnectOp(clientFromHandle(client)->disconnectPrepare(ec)); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect_set_retry_period(CC_MqttsnDisconnectHandle handle, unsigned ms) +{ + COMMS_ASSERT(handle != nullptr); + if (ms == 0U) { + disconnectOpFromHandle(handle)->client().errorLog("The retry period must be greater than 0"); + return CC_MqttsnErrorCode_BadParam; + } + + COMMS_ASSERT(handle != nullptr); + disconnectOpFromHandle(handle)->setRetryPeriod(ms); + return CC_MqttsnErrorCode_Success; +} + +unsigned cc_mqttsn_##NAME##client_disconnect_get_retry_period(CC_MqttsnDisconnectHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return disconnectOpFromHandle(handle)->getRetryPeriod(); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect_set_retry_count(CC_MqttsnDisconnectHandle handle, unsigned count) +{ + COMMS_ASSERT(handle != nullptr); + disconnectOpFromHandle(handle)->setRetryCount(count); + return CC_MqttsnErrorCode_Success; +} + +unsigned cc_mqttsn_##NAME##client_disconnect_get_retry_count(CC_MqttsnDisconnectHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return disconnectOpFromHandle(handle)->getRetryCount(); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect_send(CC_MqttsnDisconnectHandle handle, CC_MqttsnDisconnectCompleteCb cb, void* cbData) +{ + COMMS_ASSERT(handle != nullptr); + return disconnectOpFromHandle(handle)->send(cb, cbData); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect_cancel(CC_MqttsnDisconnectHandle handle) +{ + COMMS_ASSERT(handle != nullptr); + return disconnectOpFromHandle(handle)->cancel(); +} + +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect( + CC_MqttsnClientHandle client, + CC_MqttsnDisconnectCompleteCb cb, + void* cbData) +{ + auto ec = CC_MqttsnErrorCode_Success; + auto disconnect = cc_mqttsn_##NAME##client_disconnect_prepare(client, &ec); + if (disconnect == nullptr) { + return ec; + } + + return cc_mqttsn_##NAME##client_disconnect_send(disconnect, cb, cbData); +} + // --------------------- Callbacks --------------------- void cc_mqttsn_##NAME##client_set_next_tick_program_callback( diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index 1a70f27..c5432e9 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -391,6 +391,78 @@ CC_MqttsnErrorCode cc_mqttsn_##NAME##client_connect( /// @ingroup connect bool cc_mqttsn_##NAME##client_is_connected(CC_MqttsnClientHandle client); +/// @brief Prepare "disconnect" operation. +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[out] ec Error code reporting result of the operation. Can be NULL. +/// @return Handle of the "disconnect" operation, will be NULL in case of failure. To analyze the reason failure use "ec" output parameter. +/// @post The "disconnect" operation is allocated, use either @ref cc_mqttsn_##NAME##client_disconnect_send() +/// or @ref cc_mqttsn_##NAME##client_disconnect_cancel() to prevent memory leaks. +/// @ingroup disconnect +CC_MqttsnDisconnectHandle cc_mqttsn_##NAME##client_disconnect_prepare(CC_MqttsnClientHandle client, CC_MqttsnErrorCode* ec); + +/// @brief Configure the retry period for the "disconnect" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_disconnect_prepare() function. +/// @param[in] ms Retry period in @b milliseconds. +/// @return Result code of the call. +/// @ingroup disconnect +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect_set_retry_period(CC_MqttsnDisconnectHandle handle, unsigned ms); + +/// @brief Retrieve the configured retry period for the "disconnect" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_disconnect_prepare() function. +/// @return Retry period duration in @b milliseconds. +/// @ingroup disconnect +unsigned cc_mqttsn_##NAME##client_disconnect_get_retry_period(CC_MqttsnDisconnectHandle handle); + +/// @brief Configure the retry count for the "disconnect" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_disconnect_prepare() function. +/// @param[in] count Number of retries. +/// @return Result code of the call. +/// @ingroup disconnect +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect_set_retry_count(CC_MqttsnDisconnectHandle handle, unsigned count); + +/// @brief Retrieve the configured retry count for the "disconnect" operation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_disconnect_prepare() function. +/// @return Number of retries. +/// @ingroup disconnect +unsigned cc_mqttsn_##NAME##client_disconnect_get_retry_count(CC_MqttsnDisconnectHandle handle); + +/// @brief Send the "disconnect" operation +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_disconnect_prepare() function. +/// @param[in] cb Callback to be invoked when "disconnect" operation is complete. +/// @param[in] cbData Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +/// @return Result code of the call. +/// @post The handle of the "disconnect" operation can be discarded. +/// @post The provided callback will be invoked when the "disconnect" operation is complete if and only if +/// the function returns @ref CC_MqttsnErrorCode_Success. +/// @ingroup disconnect +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect_send(CC_MqttsnDisconnectHandle handle, CC_MqttsnDisconnectCompleteCb cb, void* cbData); + +/// @brief Cancel the allocated "disconnect" operation +/// @details In case the @ref cc_mqttsn_##NAME##client_disconnect_send() function was successfully called before, +/// the operation is cancelled @b without callback invocation. +/// @param[in] handle Handle returned by @ref cc_mqttsn_##NAME##client_disconnect_prepare() function. +/// @return Result code of the call. +/// @post The handle of the "disconnect" operation is no longer valid and must be discarded. +/// @ingroup disconnect +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect_cancel(CC_MqttsnDisconnectHandle handle); + +/// @brief Prepare and send "disconnect" request in one go +/// @details Abstracts away sequence of the following functions invocation: +/// @li @ref cc_mqttsn_##NAME##client_disconnect_prepare() +/// @li @ref cc_mqttsn_##NAME##client_disconnect_send() +/// +/// @param[in] client Handle returned by @ref cc_mqttsn_##NAME##client_alloc() function. +/// @param[in] cb Callback to be invoked when "disconnect" operation is complete. +/// @param[in] cbData Pointer to any user data structure. It will passed as one +/// of the parameters in callback invocation. May be NULL. +/// @return Result code of the call. +/// @ingroup disconnect +CC_MqttsnErrorCode cc_mqttsn_##NAME##client_disconnect( + CC_MqttsnClientHandle client, + CC_MqttsnDisconnectCompleteCb cb, + void* cbData); + // --------------------- Callbacks --------------------- /// @brief Set callback to call when time measurement is required. diff --git a/client/lib/test/CMakeLists.txt b/client/lib/test/CMakeLists.txt index 2c593cb..622af98 100644 --- a/client/lib/test/CMakeLists.txt +++ b/client/lib/test/CMakeLists.txt @@ -41,7 +41,7 @@ if (TARGET cc::cc_mqttsn_client) cc_mqttsn_client_add_unit_test(UnitTestGwDiscover ${DEFAULT_BASE_LIB_NAME}) cc_mqttsn_client_add_unit_test(UnitTestConnect ${DEFAULT_BASE_LIB_NAME}) -# cc_mqttsn_client_add_unit_test(UnitTestDisconnect ${DEFAULT_BASE_LIB_NAME}) + cc_mqttsn_client_add_unit_test(UnitTestDisconnect ${DEFAULT_BASE_LIB_NAME}) # cc_mqttsn_client_add_unit_test(UnitTestPublish ${DEFAULT_BASE_LIB_NAME}) # cc_mqttsn_client_add_unit_test(UnitTestReceive ${DEFAULT_BASE_LIB_NAME}) # cc_mqttsn_client_add_unit_test(UnitTestSubscribe ${DEFAULT_BASE_LIB_NAME}) diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index 5ab04c2..f5ccfd9 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -67,6 +67,14 @@ UnitTestCommonBase::UnitTestCommonBase(const LibFuncs& funcs) : test_assert(m_funcs.m_connect_cancel != nullptr); test_assert(m_funcs.m_connect != nullptr); test_assert(m_funcs.m_is_connected != nullptr); + test_assert(m_funcs.m_disconnect_prepare != nullptr); + test_assert(m_funcs.m_disconnect_set_retry_period != nullptr); + test_assert(m_funcs.m_disconnect_get_retry_period != nullptr); + test_assert(m_funcs.m_disconnect_set_retry_count != nullptr); + test_assert(m_funcs.m_disconnect_get_retry_count != nullptr); + test_assert(m_funcs.m_disconnect_send != nullptr); + test_assert(m_funcs.m_disconnect_cancel != nullptr); + test_assert(m_funcs.m_disconnect != nullptr); test_assert(m_funcs.m_set_next_tick_program_callback != nullptr); test_assert(m_funcs.m_set_cancel_next_tick_wait_callback != nullptr); @@ -382,6 +390,112 @@ CC_MqttsnErrorCode UnitTestCommonBase::unitTestConnectSend(CC_MqttsnConnectHandl return m_funcs.m_connect_send(connect, &UnitTestCommonBase::unitTestConnectCompleteCb, this); } +void UnitTestCommonBase::unitTestDoConnect(CC_MqttsnClient* client, const CC_MqttsnConnectConfig* config, const CC_MqttsnWillConfig* willConfig) +{ + auto ec = m_funcs.m_connect(client, config, willConfig, &UnitTestCommonBase::unitTestConnectCompleteCb, this); + test_assert(ec == CC_MqttsnErrorCode_Success); + + { + test_assert(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* connectMsg = dynamic_cast(sentMsg.get()); + test_assert(connectMsg != nullptr); + if (config != nullptr) { + test_assert(connectMsg->field_clientId().value() == config->m_clientId); + test_assert(connectMsg->field_duration().value() == config->m_duration); + test_assert(connectMsg->field_flags().field_mid().getBitValue_CleanSession() == config->m_cleanSession); + } + test_assert(!unitTestHasOutputData()); + } + + test_assert(!unitTestHasConnectCompleteReport()); + test_assert(unitTestHasTickReq()); + unitTestTick(client, 100); + + if (willConfig != nullptr) { + UnitTestWilltopicreqMsg willtopicreqMsg; + unitTestClientInputMessage(client, willtopicreqMsg); + + { + test_assert(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willtopicMsg = dynamic_cast(sentMsg.get()); + test_assert(willtopicMsg != nullptr); + test_assert(willtopicMsg->field_flags().doesExist()); + test_assert(static_cast(willtopicMsg->field_flags().field().field_qos().getValue()) == willConfig->m_qos); + test_assert(willtopicMsg->field_flags().field().field_mid().getBitValue_Retain() == willConfig->m_retain); + test_assert((willConfig->m_topic == nullptr) || (willtopicMsg->field_willTopic().value() == willConfig->m_topic)); + test_assert(!unitTestHasOutputData()); + } + + test_assert(!unitTestHasConnectCompleteReport()); + test_assert(unitTestHasTickReq()); + unitTestTick(client, 100); + + UnitTestWillmsgreqMsg willmsgreqMsg; + unitTestClientInputMessage(client, willmsgreqMsg); + + { + test_assert(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* willmsgMsg = dynamic_cast(sentMsg.get()); + test_assert(willmsgMsg != nullptr); + test_assert((willConfig->m_data == nullptr) || (willmsgMsg->field_willMsg().value() == UnitTestData(willConfig->m_data, willConfig->m_data + willConfig->m_dataLen))); + } + + test_assert(!unitTestHasConnectCompleteReport()); + test_assert(!apiIsConnected(client)); + test_assert(unitTestHasTickReq()); + unitTestTick(client, 100); + } + + UnitTestConnackMsg connackMsg; + connackMsg.field_returnCode().setValue(CC_MqttsnReturnCode_Accepted); + unitTestClientInputMessage(client, connackMsg); + + test_assert(unitTestHasConnectCompleteReport()); + auto* connectReport = unitTestConnectCompleteReport(); + test_assert(connectReport->m_status== CC_MqttsnAsyncOpStatus_Complete) + test_assert(connectReport->m_info.m_returnCode == CC_MqttsnReturnCode_Accepted) + unitTestPopConnectCompleteReport(); + test_assert(apiIsConnected(client)); +} + +void UnitTestCommonBase::unitTestDoConnectBasic(CC_MqttsnClient* client, const std::string& clientId, bool cleanSession) +{ + CC_MqttsnConnectConfig config; + apiConnectInitConfig(&config); + config.m_clientId = clientId.c_str(); + config.m_cleanSession = cleanSession; + unitTestDoConnect(client, &config, nullptr); +} + +bool UnitTestCommonBase::unitTestHasDisconnectCompleteReport() const +{ + return !m_data.m_disconnectCompleteReports.empty(); +} + +const UnitTestCommonBase::UnitTestDisconnectCompleteReport* UnitTestCommonBase::unitTestDisconnectCompleteReport(bool mustExist) const +{ + if (!unitTestHasDisconnectCompleteReport()) { + test_assert(!mustExist); + return nullptr; + } + + return &m_data.m_disconnectCompleteReports.front(); +} + +void UnitTestCommonBase::unitTestPopDisconnectCompleteReport() +{ + test_assert(unitTestHasDisconnectCompleteReport()); + m_data.m_disconnectCompleteReports.pop_front(); +} + +CC_MqttsnErrorCode UnitTestCommonBase::unitTestDisconnectSend(CC_MqttsnDisconnectHandle disconnect) +{ + return m_funcs.m_disconnect_send(disconnect, &UnitTestCommonBase::unitTestDisconnectCompleteCb, this); +} + void UnitTestCommonBase::apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen) { m_funcs.m_process_data(client, buf, bufLen); @@ -452,6 +566,16 @@ bool UnitTestCommonBase::apiIsConnected(CC_MqttsnClient* client) return m_funcs.m_is_connected(client); } +CC_MqttsnDisconnectHandle UnitTestCommonBase::apiDisconnectPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec) +{ + return m_funcs.m_disconnect_prepare(client, ec); +} + +CC_MqttsnErrorCode UnitTestCommonBase::apiDisconnectSetRetryCount(CC_MqttsnDisconnectHandle disconnect, unsigned count) +{ + return m_funcs.m_disconnect_set_retry_count(disconnect, count); +} + void UnitTestCommonBase::unitTestTickProgramCb(void* data, unsigned duration) { auto* thisPtr = asThis(data); @@ -537,4 +661,10 @@ void UnitTestCommonBase::unitTestConnectCompleteCb(void* data, CC_MqttsnAsyncOpS { auto* thisPtr = asThis(data); thisPtr->m_data.m_connectCompleteReports.emplace_back(status, info); +} + +void UnitTestCommonBase::unitTestDisconnectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status) +{ + auto* thisPtr = asThis(data); + thisPtr->m_data.m_disconnectCompleteReports.emplace_back(status); } \ No newline at end of file diff --git a/client/lib/test/UnitTestCommonBase.h b/client/lib/test/UnitTestCommonBase.h index 19d23a2..48a55b2 100644 --- a/client/lib/test/UnitTestCommonBase.h +++ b/client/lib/test/UnitTestCommonBase.h @@ -57,6 +57,14 @@ class UnitTestCommonBase CC_MqttsnErrorCode (*m_connect_cancel)(CC_MqttsnConnectHandle) = nullptr; CC_MqttsnErrorCode (*m_connect)(CC_MqttsnClientHandle, const CC_MqttsnConnectConfig*, const CC_MqttsnWillConfig*, CC_MqttsnConnectCompleteCb, void*) = nullptr; bool (*m_is_connected)(CC_MqttsnClientHandle) = nullptr; + CC_MqttsnDisconnectHandle (*m_disconnect_prepare)(CC_MqttsnClientHandle, CC_MqttsnErrorCode*) = nullptr; + CC_MqttsnErrorCode (*m_disconnect_set_retry_period)(CC_MqttsnDisconnectHandle, unsigned ms) = nullptr; + unsigned (*m_disconnect_get_retry_period)(CC_MqttsnDisconnectHandle) = nullptr; + CC_MqttsnErrorCode (*m_disconnect_set_retry_count)(CC_MqttsnDisconnectHandle, unsigned count) = nullptr; + unsigned (*m_disconnect_get_retry_count)(CC_MqttsnDisconnectHandle) = nullptr; + CC_MqttsnErrorCode (*m_disconnect_send)(CC_MqttsnDisconnectHandle, CC_MqttsnDisconnectCompleteCb, void*) = nullptr; + CC_MqttsnErrorCode (*m_disconnect_cancel)(CC_MqttsnDisconnectHandle) = nullptr; + CC_MqttsnErrorCode (*m_disconnect)(CC_MqttsnClientHandle, CC_MqttsnDisconnectCompleteCb, void*) = nullptr; void (*m_set_next_tick_program_callback)(CC_MqttsnClientHandle, CC_MqttsnNextTickProgramCb, void*) = nullptr; void (*m_set_cancel_next_tick_wait_callback)(CC_MqttsnClientHandle, CC_MqttsnCancelNextTickWaitCb, void*) = nullptr; @@ -161,6 +169,15 @@ class UnitTestCommonBase using UnitTestConnectCompleteReportList = std::list; + struct UnitTestDisconnectCompleteReport + { + CC_MqttsnAsyncOpStatus m_status = CC_MqttsnAsyncOpStatus_ValuesLimit; + + UnitTestDisconnectCompleteReport(CC_MqttsnAsyncOpStatus status) : m_status(status) {}; + }; + + using UnitTestDisconnectCompleteReportList = std::list; + using UnitTestClientPtr = std::unique_ptr; void unitTestSetUp(); @@ -197,6 +214,14 @@ class UnitTestCommonBase void unitTestPopConnectCompleteReport(); CC_MqttsnErrorCode unitTestConnectSend(CC_MqttsnConnectHandle connect); + void unitTestDoConnect(CC_MqttsnClient* client, const CC_MqttsnConnectConfig* config, const CC_MqttsnWillConfig* willConfig); + void unitTestDoConnectBasic(CC_MqttsnClient* client, const std::string& clientId = std::string(), bool cleanSession = true); + + bool unitTestHasDisconnectCompleteReport() const; + const UnitTestDisconnectCompleteReport* unitTestDisconnectCompleteReport(bool mustExist = true) const; + void unitTestPopDisconnectCompleteReport(); + + CC_MqttsnErrorCode unitTestDisconnectSend(CC_MqttsnDisconnectHandle disconnect); void apiProcessData(CC_MqttsnClient* client, const unsigned char* buf, unsigned bufLen); CC_MqttsnErrorCode apiSetDefaultRetryPeriod(CC_MqttsnClient* client, unsigned value); @@ -205,6 +230,7 @@ class UnitTestCommonBase CC_MqttsnErrorCode apiSearchSetRetryPeriod(CC_MqttsnSearchHandle search, unsigned value); CC_MqttsnErrorCode apiSearchSetRetryCount(CC_MqttsnSearchHandle search, unsigned value); CC_MqttsnErrorCode apiSearchSetBroadcastRadius(CC_MqttsnSearchHandle search, unsigned value); + CC_MqttsnConnectHandle apiConnectPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec = nullptr); CC_MqttsnErrorCode apiConnectSetRetryCount(CC_MqttsnConnectHandle connect, unsigned count); void apiConnectInitConfig(CC_MqttsnConnectConfig* config); @@ -213,6 +239,9 @@ class UnitTestCommonBase CC_MqttsnErrorCode apiConnectConfigWill(CC_MqttsnConnectHandle connect, const CC_MqttsnWillConfig* config); bool apiIsConnected(CC_MqttsnClient* client); + CC_MqttsnDisconnectHandle apiDisconnectPrepare(CC_MqttsnClient* client, CC_MqttsnErrorCode* ec = nullptr); + CC_MqttsnErrorCode apiDisconnectSetRetryCount(CC_MqttsnDisconnectHandle disconnect, unsigned count); + protected: explicit UnitTestCommonBase(const LibFuncs& funcs); @@ -225,6 +254,7 @@ class UnitTestCommonBase UnitTestSearchCompleteReportsList m_searchCompleteReports; UnitTestSearchCompleteCbList m_searchCompleteCallbacks; UnitTestConnectCompleteReportList m_connectCompleteReports; + UnitTestDisconnectCompleteReportList m_disconnectCompleteReports; }; static void unitTestTickProgramCb(void* data, unsigned duration); @@ -237,6 +267,7 @@ class UnitTestCommonBase static void unitTestErrorLogCb(void* data, const char* msg); static void unitTestSearchCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnGatewayInfo* info); static void unitTestConnectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info); + static void unitTestDisconnectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status); LibFuncs m_funcs; ClientData m_data; diff --git a/client/lib/test/UnitTestDefaultBase.cpp b/client/lib/test/UnitTestDefaultBase.cpp index 612915f..403be6a 100644 --- a/client/lib/test/UnitTestDefaultBase.cpp +++ b/client/lib/test/UnitTestDefaultBase.cpp @@ -48,6 +48,14 @@ const UnitTestDefaultBase::LibFuncs& UnitTestDefaultBase::getFuncs() funcs.m_connect_cancel = &cc_mqttsn_client_connect_cancel; funcs.m_connect = &cc_mqttsn_client_connect; funcs.m_is_connected = &cc_mqttsn_client_is_connected; + funcs.m_disconnect_prepare = &cc_mqttsn_client_disconnect_prepare; + funcs.m_disconnect_set_retry_period = &cc_mqttsn_client_disconnect_set_retry_period; + funcs.m_disconnect_get_retry_period = &cc_mqttsn_client_disconnect_get_retry_period; + funcs.m_disconnect_set_retry_count = &cc_mqttsn_client_disconnect_set_retry_count; + funcs.m_disconnect_get_retry_count = &cc_mqttsn_client_disconnect_get_retry_count; + funcs.m_disconnect_send = &cc_mqttsn_client_disconnect_send; + funcs.m_disconnect_cancel = &cc_mqttsn_client_disconnect_cancel; + funcs.m_disconnect = &cc_mqttsn_client_disconnect; funcs.m_set_next_tick_program_callback = &cc_mqttsn_client_set_next_tick_program_callback; funcs.m_set_cancel_next_tick_wait_callback = &cc_mqttsn_client_set_cancel_next_tick_wait_callback; diff --git a/client/lib/test/UnitTestDisconnect.th b/client/lib/test/UnitTestDisconnect.th new file mode 100644 index 0000000..957a896 --- /dev/null +++ b/client/lib/test/UnitTestDisconnect.th @@ -0,0 +1,184 @@ +#include "UnitTestDefaultBase.h" +#include "UnitTestProtocolDefs.h" + +#include "comms/units.h" + +#include + +class UnitTestDisconnect : public CxxTest::TestSuite, public UnitTestDefaultBase +{ +public: + void test1(); + void test2(); + void test3(); + +private: + virtual void setUp() override + { + unitTestSetUp(); + } + + virtual void tearDown() override + { + unitTestTearDown(); + } +}; + +void UnitTestDisconnect::test1() +{ + // Testing basic disconnect + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + auto disconnect = apiDisconnectPrepare(client); + TS_ASSERT_DIFFERS(disconnect, nullptr); + + unitTestDisconnectSend(disconnect); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* disconnectMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(disconnectMsg, nullptr); + TS_ASSERT(disconnectMsg->field_duration().isMissing()); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestDisconnectMsg disconnectMsg; + unitTestClientInputMessage(client, disconnectMsg); + } + + TS_ASSERT(unitTestHasDisconnectCompleteReport()); + auto* disconnectReport = unitTestDisconnectCompleteReport(); + TS_ASSERT_EQUALS(disconnectReport->m_status, CC_MqttsnAsyncOpStatus_Complete) + unitTestPopDisconnectCompleteReport(); + + TS_ASSERT(!unitTestHasTickReq()); // No more keep alive + TS_ASSERT(!apiIsConnected(client)); +} + +void UnitTestDisconnect::test2() +{ + // Testing timeout disconnect + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + auto disconnect = apiDisconnectPrepare(client); + TS_ASSERT_DIFFERS(disconnect, nullptr); + auto ec = apiDisconnectSetRetryCount(disconnect, 1U); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestDisconnectSend(disconnect); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* disconnectMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(disconnectMsg, nullptr); + TS_ASSERT(disconnectMsg->field_duration().isMissing()); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* disconnectMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(disconnectMsg, nullptr); + TS_ASSERT(disconnectMsg->field_duration().isMissing()); + TS_ASSERT(!unitTestHasOutputData()); + } + + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + + TS_ASSERT(unitTestHasDisconnectCompleteReport()); + auto* disconnectReport = unitTestDisconnectCompleteReport(); + TS_ASSERT_EQUALS(disconnectReport->m_status, CC_MqttsnAsyncOpStatus_Timeout) + unitTestPopDisconnectCompleteReport(); + + TS_ASSERT(unitTestHasTickReq()); // Still hase keep alive + TS_ASSERT(apiIsConnected(client)); // Still connected +} + +void UnitTestDisconnect::test3() +{ + // Testing disconnect with retry + + auto clientPtr = unitTestAllocClient(); + auto* client = clientPtr.get(); + + const std::string ClientId("bla"); + unitTestDoConnectBasic(client, ClientId); + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 1000); + + auto disconnect = apiDisconnectPrepare(client); + TS_ASSERT_DIFFERS(disconnect, nullptr); + auto ec = apiDisconnectSetRetryCount(disconnect, 1U); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + ec = unitTestDisconnectSend(disconnect); + TS_ASSERT_EQUALS(ec, CC_MqttsnErrorCode_Success); + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* disconnectMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(disconnectMsg, nullptr); + TS_ASSERT(disconnectMsg->field_duration().isMissing()); + TS_ASSERT(!unitTestHasOutputData()); + } + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client); // timeout + + { + TS_ASSERT(unitTestHasOutputData()); + auto sentMsg = unitTestPopOutputMessage(); + auto* disconnectMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(disconnectMsg, nullptr); + TS_ASSERT(disconnectMsg->field_duration().isMissing()); + TS_ASSERT(!unitTestHasOutputData()); + } + + + TS_ASSERT(unitTestHasTickReq()); + unitTestTick(client, 100); + + { + UnitTestDisconnectMsg disconnectMsg; + unitTestClientInputMessage(client, disconnectMsg); + } + + TS_ASSERT(unitTestHasDisconnectCompleteReport()); + auto* disconnectReport = unitTestDisconnectCompleteReport(); + TS_ASSERT_EQUALS(disconnectReport->m_status, CC_MqttsnAsyncOpStatus_Complete) + unitTestPopDisconnectCompleteReport(); + + TS_ASSERT(!unitTestHasTickReq()); // No more keep alive + TS_ASSERT(!apiIsConnected(client)); +}