From e93eac789b1c1ead488886a6bfbd988cb360fd3c Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 14 Aug 2024 13:55:22 +1000 Subject: [PATCH] Added publish client application. --- client/app/CMakeLists.txt | 2 +- client/app/common/AppClient.cpp | 120 ++++++++++++++++--- client/app/common/AppClient.h | 12 +- client/app/common/ProgramOptions.cpp | 96 ++++++++++++++- client/app/common/ProgramOptions.h | 20 ++++ client/app/common/Session.cpp | 4 +- client/app/common/Session.h | 6 +- client/app/common/UdpSession.cpp | 7 +- client/app/pub/CMakeLists.txt | 13 ++ client/app/pub/Pub.cpp | 113 +++++++++++++++++ client/app/pub/Pub.h | 33 +++++ client/app/pub/main.cpp | 51 ++++++++ client/lib/include/cc_mqttsn_client/common.h | 4 +- client/lib/test/UnitTestCommonBase.cpp | 10 +- 14 files changed, 460 insertions(+), 31 deletions(-) create mode 100644 client/app/pub/CMakeLists.txt create mode 100644 client/app/pub/Pub.cpp create mode 100644 client/app/pub/Pub.h create mode 100644 client/app/pub/main.cpp diff --git a/client/app/CMakeLists.txt b/client/app/CMakeLists.txt index f022edb..f3fa8d3 100644 --- a/client/app/CMakeLists.txt +++ b/client/app/CMakeLists.txt @@ -20,5 +20,5 @@ set (COMMON_APPS_LIB "cc_mqttsn_client_apps_lib") add_subdirectory (common) add_subdirectory (gw_discover) -#add_subdirectory (pub) +add_subdirectory (pub) #add_subdirectory (sub) \ No newline at end of file diff --git a/client/app/common/AppClient.cpp b/client/app/common/AppClient.cpp index 68a1c88..01f90bf 100644 --- a/client/app/common/AppClient.cpp +++ b/client/app/common/AppClient.cpp @@ -86,6 +86,13 @@ bool AppClient::start(int argc, const char* argv[]) return false; } + if (m_opts.connectNoCleanSession()) { + auto ec = cc_mqttsn_client_set_verify_incoming_msg_subscribed(m_client.get(), false); + if (ec != CC_MqttsnErrorCode_Success) { + logError() << "Failed to disable incoming message subscribed verification" << std::endl; + } + } + return startImpl(); } @@ -187,6 +194,11 @@ std::ostream& AppClient::logError() return std::cerr << "ERROR: "; } +std::ostream& AppClient::logInfo() +{ + return std::cout << "INFO: "; +} + void AppClient::doTerminate(int result) { m_result = result; @@ -195,15 +207,6 @@ void AppClient::doTerminate(int result) void AppClient::doComplete() { - // if (m_opts.willTopic().empty()) { - // auto ec = ::cc_mqttsn_client_disconnect(m_client.get()); - // if (ec != CC_MqttsnErrorCode_Success) { - // logError() << "Failed to send disconnect with ec=" << toString(ec) << std::endl; - // doTerminate(); - // return; - // } - // } - boost::asio::post( m_io, [this]() @@ -212,6 +215,45 @@ void AppClient::doComplete() }); } +bool AppClient::doConnect() +{ + auto config = CC_MqttsnConnectConfig(); + cc_mqttsn_client_connect_init_config(&config); + + auto clientId = m_opts.connectClientId(); + if (!clientId.empty()) { + config.m_clientId = clientId.c_str(); + } + + config.m_duration = m_opts.connectKeepAlive(); + config.m_cleanSession = !m_opts.connectNoCleanSession(); + + auto willConfig = CC_MqttsnWillConfig(); + CC_MqttsnWillConfig* willConfigPtr = nullptr; + auto willTopic = m_opts.willTopic(); + auto willData = parseBinaryData(m_opts.willMessage()); + if (!willTopic.empty()) { + cc_mqttsn_client_connect_init_config_will(&willConfig); + willConfig.m_topic = willTopic.c_str(); + willConfig.m_data = willData.data(); + willConfig.m_dataLen = static_cast(willData.size()); + willConfig.m_qos = static_cast(m_opts.willQos()); + willConfigPtr = &willConfig; + } + + if (m_opts.verbose()) { + logInfo() << "Attempting connection" << std::endl; + } + + auto ec = cc_mqttsn_client_connect(m_client.get(), &config, willConfigPtr, &AppClient::connectCompleteCb, this); + if (ec != CC_MqttsnErrorCode_Success) { + logError() << "Failed to initiate connection to the gateway" << std::endl; + return false; + } + + return true; +} + bool AppClient::startImpl() { return true; @@ -222,6 +264,10 @@ void AppClient::messageReceivedImpl([[maybe_unused]] const CC_MqttsnMessageInfo* { } +void AppClient::connectCompleteImpl() +{ +} + std::vector AppClient::parseBinaryData(const std::string& val) { std::vector result; @@ -308,6 +354,28 @@ std::string AppClient::toString(CC_MqttsnAsyncOpStatus val) return Map[idx] + " (" + std::to_string(val) + ')'; } + +std::string AppClient::toString(CC_MqttsnReturnCode val) +{ + static const std::string Map[] = { + /* CC_MqttsnReturnCode_Accepted */ "Accepted", + /* CC_MqttsnReturnCode_Conjestion */ "Conjestion", + /* CC_MqttsnReturnCode_InvalidTopicId */ "Invalid Topic ID", + /* CC_MqttsnReturnCode_NotSupported */ "Not supported", + }; + + static constexpr std::size_t MapSize = std::extent::value; + static_assert(MapSize == CC_MqttsnReturnCode_ValuesLimit); + + auto idx = static_cast(val); + if (MapSize <= idx) { + assert(false); // Should not happen + return std::to_string(val); + } + + return Map[idx] + " (" + std::to_string(val) + ')'; +} + void AppClient::nextTickProgramInternal(unsigned duration) { m_lastWaitProgram = Clock::now(); @@ -354,15 +422,10 @@ bool AppClient::createSession() } m_session->setDataReportCb( - [this](const Addr& addr, const std::uint8_t* buf, std::size_t bufLen) + [this](const std::uint8_t* buf, std::size_t bufLen, const Addr& addr, CC_MqttsnDataOrigin origin) { assert(m_client); m_lastAddr = addr; - auto origin = CC_MqttsnDataOrigin_Any; - if (addr == m_gwAddr) { - origin = CC_MqttsnDataOrigin_ConnectedGw; - } - ::cc_mqttsn_client_process_data(m_client.get(), buf, static_cast(bufLen), origin); }); @@ -382,6 +445,28 @@ bool AppClient::createSession() return true; } +void AppClient::connectCompleteInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info) +{ + if (status != CC_MqttsnAsyncOpStatus_Complete) { + logError() << "Failed to connect with status: " << toString(status) << std::endl; + doTerminate(); + return; + } + + assert(info != nullptr); + if (info->m_returnCode != CC_MqttsnReturnCode_Accepted) { + logError() << "Connection rejected with return code: " << toString(info->m_returnCode) << std::endl; + doTerminate(); + return; + } + + if (m_opts.verbose()) { + logInfo() << "Connection established" << std::endl; + } + + connectCompleteImpl(); +} + void AppClient::sendDataCb(void* data, const unsigned char* buf, unsigned bufLen, unsigned broadcastRadius) { asThis(data)->sendDataInternal(buf, bufLen, broadcastRadius); @@ -407,4 +492,9 @@ unsigned AppClient::cancelNextTickWaitCb(void* data) return asThis(data)->cancelNextTickWaitInternal(); } +void AppClient::connectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info) +{ + return asThis(data)->connectCompleteInternal(status, info); +} + } // namespace cc_mqttsn_client_app diff --git a/client/app/common/AppClient.h b/client/app/common/AppClient.h index 2a27ffb..01e6b80 100644 --- a/client/app/common/AppClient.h +++ b/client/app/common/AppClient.h @@ -60,12 +60,15 @@ class AppClient } static std::ostream& logError(); + static std::ostream& logInfo(); void doTerminate(int result = 1); void doComplete(); + bool doConnect(); virtual bool startImpl(); virtual void messageReceivedImpl(const CC_MqttsnMessageInfo* info); + virtual void connectCompleteImpl(); static std::vector parseBinaryData(const std::string& val); @@ -74,12 +77,8 @@ class AppClient return m_lastAddr; } - void setGwAddr(const Addr& addr) - { - m_gwAddr = addr; - } - static std::string toString(CC_MqttsnAsyncOpStatus val); + static std::string toString(CC_MqttsnReturnCode val); private: using ClientPtr = std::unique_ptr; @@ -90,12 +89,14 @@ class AppClient unsigned cancelNextTickWaitInternal(); void sendDataInternal(const unsigned char* buf, unsigned bufLen, unsigned broadcastRadius); bool createSession(); + void connectCompleteInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info); static void sendDataCb(void* data, const unsigned char* buf, unsigned bufLen, unsigned broadcastRadius); static void messageReceivedCb(void* data, const CC_MqttsnMessageInfo* info); static void logMessageCb(void* data, const char* msg); static void nextTickProgramCb(void* data, unsigned duration); static unsigned cancelNextTickWaitCb(void* data); + static void connectCompleteCb(void* data, CC_MqttsnAsyncOpStatus status, const CC_MqttsnConnectInfo* info); boost::asio::io_context& m_io; int& m_result; @@ -105,7 +106,6 @@ class AppClient ClientPtr m_client; SessionPtr m_session; Addr m_lastAddr; - Addr m_gwAddr; }; } // namespace cc_mqttsn_client_app diff --git a/client/app/common/ProgramOptions.cpp b/client/app/common/ProgramOptions.cpp index a501f7a..b4437b1 100644 --- a/client/app/common/ProgramOptions.cpp +++ b/client/app/common/ProgramOptions.cpp @@ -18,6 +18,7 @@ namespace { constexpr std::uint16_t DefaultPort = 1883U; +constexpr unsigned DefaultKeepAlive = 60; } // namespace @@ -39,7 +40,7 @@ void ProgramOptions::addNetwork() opts.add_options() ("network-gateway,g", po::value()->default_value("127.0.0.1"), "Gateway address to connect to") ("network-broadcast,b", po::value()->default_value("255.255.255.255"), "Address to broadcast to") - ("network-port,p", po::value()->default_value(DefaultPort), "Network remove port") + ("network-port,p", po::value()->default_value(DefaultPort), "Network remote port") ("network-local-port,P", po::value()->default_value(0), "Network local port") ; @@ -57,6 +58,44 @@ void ProgramOptions::addDiscover() m_desc.add(opts); } +void ProgramOptions::addConnect() +{ + po::options_description opts("Connect Options"); + opts.add_options() + ("connect-client-id,c", po::value()->default_value(std::string()), "Client ID") + ("connect-keep-alive,k", po::value()->default_value(DefaultKeepAlive), "Protocol \"keep alive\" configuration") + ("connect-no-clean-session,l", "Do not force clean session upon connection") + ; + + m_desc.add(opts); +} + +void ProgramOptions::addWill() +{ + po::options_description opts("Will Options"); + opts.add_options() + ("will-topic", po::value()->default_value(std::string()), "Will topic, when not provided means no will") + ("will-message", po::value()->default_value(std::string()), "Will message data, use \"\\x\" prefix before hex value of each byte for binary string") + ("will-qos", po::value()->default_value(0U), "Will QoS value") + ; + + m_desc.add(opts); +} + +void ProgramOptions::addPublish() +{ + po::options_description opts("Publish Options"); + opts.add_options() + ("pub-topic,t", po::value()->default_value(std::string()), "Publish topic, must be empty when topic ID is not 0") + ("pub-topic-id,i", po::value()->default_value(0U), "Publish topic id, must be 0 when topic is specified") + ("pub-message,m", po::value()->default_value(std::string()), "Publish message data, use \"\\x\" prefix before hex value of each byte for binary string") + ("pub-qos,q", po::value()->default_value(0U), "Publish QoS value") + ("pub-retain", "Publish retained message") + ; + + m_desc.add(opts); +} + void ProgramOptions::printHelp() { std::cout << m_desc << std::endl; @@ -116,4 +155,59 @@ unsigned ProgramOptions::discoverTimeout() const return m_vm["discover-timeout"].as(); } +std::string ProgramOptions::connectClientId() const +{ + return m_vm["connect-client-id"].as(); +} + +unsigned ProgramOptions::connectKeepAlive() const +{ + return m_vm["connect-keep-alive"].as(); +} + +bool ProgramOptions::connectNoCleanSession() const +{ + return m_vm.count("connect-no-clean-session") > 0U; +} + +std::string ProgramOptions::willTopic() const +{ + return m_vm["will-topic"].as(); +} + +std::string ProgramOptions::willMessage() const +{ + return m_vm["will-message"].as(); +} + +unsigned ProgramOptions::willQos() const +{ + return m_vm["will-qos"].as(); +} + +std::string ProgramOptions::pubTopic() const +{ + return m_vm["pub-topic"].as(); +} + +std::uint16_t ProgramOptions::pubTopicId() const +{ + return m_vm["pub-topic-id"].as(); +} + +std::string ProgramOptions::pubMessage() const +{ + return m_vm["pub-message"].as(); +} + +unsigned ProgramOptions::pubQos() const +{ + return m_vm["pub-qos"].as(); +} + +bool ProgramOptions::pubRetain() const +{ + return m_vm.count("pub-retain") > 0U; +} + } // namespace cc_mqttsn_client_app diff --git a/client/app/common/ProgramOptions.h b/client/app/common/ProgramOptions.h index bd7079a..abd572c 100644 --- a/client/app/common/ProgramOptions.h +++ b/client/app/common/ProgramOptions.h @@ -30,6 +30,9 @@ class ProgramOptions void addCommon(); void addNetwork(); void addDiscover(); + void addConnect(); + void addWill(); + void addPublish(); void printHelp(); @@ -50,6 +53,23 @@ class ProgramOptions bool discoverExitOnFirst() const; unsigned discoverTimeout() const; + // Connect Options + std::string connectClientId() const; + unsigned connectKeepAlive() const; + bool connectNoCleanSession() const; + + // Will Options + std::string willTopic() const; + std::string willMessage() const; + unsigned willQos() const; + + // Publish Options + std::string pubTopic() const; + std::uint16_t pubTopicId() const; + std::string pubMessage() const; + unsigned pubQos() const; + bool pubRetain() const; + private: boost::program_options::variables_map m_vm; OptDesc m_desc; diff --git a/client/app/common/Session.cpp b/client/app/common/Session.cpp index 30f6424..aef0444 100644 --- a/client/app/common/Session.cpp +++ b/client/app/common/Session.cpp @@ -44,10 +44,10 @@ std::ostream& Session::logError() return std::cerr << "ERROR: "; } -void Session::reportData(const Addr& addr, const std::uint8_t* buf, std::size_t bufLen) +void Session::reportData(const std::uint8_t* buf, std::size_t bufLen, const Addr& addr, CC_MqttsnDataOrigin origin) { assert(m_dataReportCb); - m_dataReportCb(addr, buf, bufLen); + m_dataReportCb(buf, bufLen, addr, origin); } void Session::reportNetworkError() diff --git a/client/app/common/Session.h b/client/app/common/Session.h index 006982b..d093c7c 100644 --- a/client/app/common/Session.h +++ b/client/app/common/Session.h @@ -9,6 +9,8 @@ #include "ProgramOptions.h" +#include "cc_mqttsn_client/common.h" + #include #include @@ -38,7 +40,7 @@ class Session sendDataImpl(buf, bufLen, broadcastRadius); } - using DataReportCb = std::function; + using DataReportCb = std::function; template void setDataReportCb(TFunc&& func) { @@ -68,7 +70,7 @@ class Session static std::ostream& logError(); - void reportData(const Addr& addr, const std::uint8_t* buf, std::size_t bufLen); + void reportData(const std::uint8_t* buf, std::size_t bufLen, const Addr& addr, CC_MqttsnDataOrigin origin); void reportNetworkError(); virtual bool startImpl() = 0; diff --git a/client/app/common/UdpSession.cpp b/client/app/common/UdpSession.cpp index f563d51..0b5d33a 100644 --- a/client/app/common/UdpSession.cpp +++ b/client/app/common/UdpSession.cpp @@ -121,7 +121,12 @@ void UdpSession::doRead() auto remoteAddr = m_senderEndpoint.address().to_v4().to_bytes(); Addr addrToReport(remoteAddr.begin(), remoteAddr.end()); - reportData(addrToReport, m_inBuf.data(), bytesCount); + auto origin = CC_MqttsnDataOrigin_Any; + if (m_senderEndpoint == m_remoteEndpoint) { + origin = CC_MqttsnDataOrigin_ConnectedGw; + } + + reportData(m_inBuf.data(), bytesCount, addrToReport, origin); doRead(); }); } diff --git a/client/app/pub/CMakeLists.txt b/client/app/pub/CMakeLists.txt new file mode 100644 index 0000000..a454e3e --- /dev/null +++ b/client/app/pub/CMakeLists.txt @@ -0,0 +1,13 @@ +set (name "cc_mqttsn_client_pub") +set (src + main.cpp + Pub.cpp +) + +add_executable(${name} ${src}) +target_link_libraries(${name} ${COMMON_APPS_LIB}) + +install ( + TARGETS ${name} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) \ No newline at end of file diff --git a/client/app/pub/Pub.cpp b/client/app/pub/Pub.cpp new file mode 100644 index 0000000..bc4703f --- /dev/null +++ b/client/app/pub/Pub.cpp @@ -0,0 +1,113 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "Pub.h" + +#include +#include +#include +#include + +namespace cc_mqttsn_client_app +{ + +namespace +{ + +Pub* asThis(void* data) +{ + return reinterpret_cast(data); +} + +} // namespace + + +Pub::Pub(boost::asio::io_context& io, int& result) : + Base(io, result) +{ + opts().addCommon(); + opts().addNetwork(); + opts().addConnect(); + opts().addWill(); + opts().addPublish(); +} + +bool Pub::startImpl() +{ + auto topic = opts().pubTopic(); + auto topicId = opts().pubTopicId(); + if (topic.empty() && topicId == 0) { + logError() << "Neither topic nor topic ID are specified" << std::endl; + return false; + } + + if ((!topic.empty()) && (topicId != 0)) { + logError() << "Both topic topic topic ID are specified" << std::endl; + return false; + } + + return doConnect(); +} + +void Pub::connectCompleteImpl() +{ + auto config = CC_MqttsnPublishConfig(); + cc_mqttsn_client_publish_init_config(&config); + + auto topic = opts().pubTopic(); + auto data = parseBinaryData(opts().pubMessage()); + + if (!topic.empty()) { + config.m_topic = topic.c_str(); + } + + config.m_topicId = opts().pubTopicId(); + config.m_data = data.data(); + config.m_dataLen = static_cast(data.size()); + config.m_qos = static_cast(opts().pubQos()); + config.m_retain = opts().pubRetain(); + + auto ec = cc_mqttsn_client_publish(client(), &config, &Pub::publishCompleteCb, this); + if (ec == CC_MqttsnErrorCode_Success) { + return; + } + + logError() << "Failed to initiate publish operation with error code: " << toString(ec) << std::endl; + doTerminate(); +} + +void Pub::publishCompleteInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnPublishInfo* info) +{ + if (status != CC_MqttsnAsyncOpStatus_Complete) { + logError() << "Publish failed with status: " << toString(status) << std::endl; + doTerminate(); + return; + } + + if ((info != nullptr) && (info->m_returnCode != CC_MqttsnReturnCode_Accepted)) { + logError() << "Publish rejected with return code: " << toString(info->m_returnCode) << std::endl; + doTerminate(); + return; + } + + if (opts().verbose()) { + logInfo() << "Publish complete" << std::endl; + } + + doComplete(); +} + +void Pub::publishCompleteCb( + void* data, + [[maybe_unused]] CC_MqttsnPublishHandle handle, + CC_MqttsnAsyncOpStatus status, + const CC_MqttsnPublishInfo* info) +{ + asThis(data)->publishCompleteInternal(status, info); +} + +} // namespace cc_mqttsn_client_app diff --git a/client/app/pub/Pub.h b/client/app/pub/Pub.h new file mode 100644 index 0000000..6f11112 --- /dev/null +++ b/client/app/pub/Pub.h @@ -0,0 +1,33 @@ +// +// Copyright 2024 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "AppClient.h" +#include "ProgramOptions.h" + +#include + +namespace cc_mqttsn_client_app +{ + +class Pub : public AppClient +{ + using Base = AppClient; +public: + Pub(boost::asio::io_context& io, int& result); + +protected: + virtual bool startImpl() override; + virtual void connectCompleteImpl() override; + +private: + void publishCompleteInternal(CC_MqttsnAsyncOpStatus status, const CC_MqttsnPublishInfo* info); + static void publishCompleteCb(void* data, CC_MqttsnPublishHandle handle, CC_MqttsnAsyncOpStatus status, const CC_MqttsnPublishInfo* info); +}; + +} // namespace cc_mqttsn_client_app diff --git a/client/app/pub/main.cpp b/client/app/pub/main.cpp new file mode 100644 index 0000000..fa38a7d --- /dev/null +++ b/client/app/pub/main.cpp @@ -0,0 +1,51 @@ + +#include "Pub.h" + +#include + +#include +#include +#include + +int main(int argc, const char* argv[]) +{ + int result = 0U; + try { + boost::asio::io_context io; + + boost::asio::signal_set signals(io, SIGINT, SIGTERM); + signals.async_wait( + [&io, &result](const boost::system::error_code& ec, int sigNum) + { + if (ec == boost::asio::error::operation_aborted) { + return; + } + + if (ec) { + std::cerr << "ERROR: Unexpected error in signal handling: " << ec.message() << std::endl; + result = 150; + io.stop(); + return; + } + + std::cerr << "Terminated with signal " << sigNum << std::endl; + result = 100; + io.stop(); + }); + + cc_mqttsn_client_app::Pub app(io, result); + + if (!app.start(argc, argv)) { + return -1; + } + + io.run(); + } + catch (const std::exception& ec) + { + std::cerr << "ERROR: Unexpected exception: " << ec.what() << std::endl; + result = 200; + } + + return result; +} \ No newline at end of file diff --git a/client/lib/include/cc_mqttsn_client/common.h b/client/lib/include/cc_mqttsn_client/common.h index c593a48..62745fc 100644 --- a/client/lib/include/cc_mqttsn_client/common.h +++ b/client/lib/include/cc_mqttsn_client/common.h @@ -225,8 +225,8 @@ typedef struct const char* m_topic; ///< Topic the message was published with. May be NULL if message is reported with predefined topic ID. const unsigned char* m_data; ///< Pointer to reported message binary data. unsigned m_dataLen; ///< Number of bytes in reported message binary data. - CC_MqttsnQoS m_qos; ///< QoS level the message was received with. CC_MqttsnTopicId m_topicId; ///< Predefined topic ID. This data member is used only if topic field has value NULL. + CC_MqttsnQoS m_qos; ///< QoS level the message was received with. bool m_retained; ///< Retain flag of the message. } CC_MqttsnMessageInfo; @@ -296,9 +296,9 @@ typedef struct typedef struct { const char* m_topic; ///< Publish topic. - CC_MqttsnTopicId m_topicId; ///< Pre-defined topic ID, should be @b 0 when topic is not NULL. const unsigned char* m_data; ///< Publish data (message). unsigned m_dataLen; ///< Publish data (message) length. + CC_MqttsnTopicId m_topicId; ///< Pre-defined topic ID, should be @b 0 when topic is not NULL. CC_MqttsnQoS m_qos; ///< Publish message QoS. bool m_retain; ///< Publish message retain configuration. } CC_MqttsnPublishConfig; diff --git a/client/lib/test/UnitTestCommonBase.cpp b/client/lib/test/UnitTestCommonBase.cpp index 1344e32..a753b52 100644 --- a/client/lib/test/UnitTestCommonBase.cpp +++ b/client/lib/test/UnitTestCommonBase.cpp @@ -556,7 +556,15 @@ void UnitTestCommonBase::unitTestDoConnect(CC_MqttsnClient* client, const CC_Mqt auto* connectMsg = dynamic_cast(sentMsg.get()); test_assert(connectMsg != nullptr); if (config != nullptr) { - test_assert(connectMsg->field_clientId().value() == config->m_clientId); + if (config->m_clientId != nullptr) { + if (connectMsg->field_clientId().value() != config->m_clientId) { + std::cerr << "FAILURE: \"" << connectMsg->field_clientId().value().c_str() << "\" != \"" << config->m_clientId << "\"" << std::endl; + } + test_assert(connectMsg->field_clientId().value() == config->m_clientId); + } + else { + test_assert(connectMsg->field_clientId().value().empty()); + } test_assert(connectMsg->field_duration().value() == config->m_duration); test_assert(connectMsg->field_flags().field_mid().getBitValue_CleanSession() == config->m_cleanSession); }