From 10e071153246f5fd6dddd492c505b14a9aaa32e5 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Mon, 11 Dec 2023 08:36:24 +1000 Subject: [PATCH 01/35] Updated next release to be v0.1.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 25025bb..8040ed3 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 985abe09ec81b3592642880cc99e9ec71087123a Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Mon, 18 Dec 2023 09:14:42 +1000 Subject: [PATCH 02/35] Initial infrastructure for fuzzing. --- CMakeLists.txt | 3 + client/CMakeLists.txt | 3 +- client/afl_fuzz/CMakeLists.txt | 78 +++++++ client/afl_fuzz/main.cpp.templ | 192 ++++++++++++++++++ client/lib/CMakeLists.txt | 11 +- cmake/Compile.cmake | 7 + .../script => cmake}/ProcessTemplate.cmake | 0 7 files changed, 283 insertions(+), 11 deletions(-) create mode 100644 client/afl_fuzz/CMakeLists.txt create mode 100644 client/afl_fuzz/main.cpp.templ rename {client/lib/script => cmake}/ProcessTemplate.cmake (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 69d0510..d4244b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,7 @@ project ("cc_mqtt5_libs") # Options option (CC_MQTT5_CLIENT_DEFAULT_LIB "Build and install default variant of MQTT5 client library" ON) option (CC_MQTT5_CLIENT_APPS "Build and install client applications" ${CC_MQTT5_CLIENT_DEFAULT_LIB}) +option (CC_MQTT5_CLIENT_AFL_FUZZ "Build and install client AFL++ fuzzing application" OFF) option (CC_MQTT5_WARN_AS_ERR "Treat warning as error" ON) option (CC_MQTT5_USE_CCACHE "Use ccache on unix system" ON) option (CC_MQTT5_BUILD_UNIT_TESTS "Build unit tests" OFF) @@ -18,6 +19,8 @@ set(CMAKE_CXX_STANDARD 17 CACHE STRING "The C++ standard to use") ########################################################################## +cmake_policy(SET CMP0079 NEW) + set (CC_MQTT5_EXTERNALS_DIR "${PROJECT_SOURCE_DIR}/externals") # Dependencies diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index cdfef8a..018a875 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory (lib) -add_subdirectory (app) \ No newline at end of file +add_subdirectory (app) +add_subdirectory (afl_fuzz) \ No newline at end of file diff --git a/client/afl_fuzz/CMakeLists.txt b/client/afl_fuzz/CMakeLists.txt new file mode 100644 index 0000000..f69da71 --- /dev/null +++ b/client/afl_fuzz/CMakeLists.txt @@ -0,0 +1,78 @@ +if (NOT CC_MQTT5_CLIENT_AFL_FUZZ) + return () +endif () + +set (SRC_TEMPL ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp.templ) +set (TEMPL_PROCESS_SCRIPT ${PROJECT_SOURCE_DIR}/cmake/ProcessTemplate.cmake) + +###################################################################### + +function (gen_mqtt5_client_afl_fuzz config_file) + if (NOT "${config_file}" STREQUAL "") + include (${config_file}) + endif() + + set (name "${CC_MQTT5_CLIENT_CUSTOM_NAME}") + if ("${CC_MQTT5_CLIENT_CUSTOM_NAME}" STREQUAL "") + set (dir ${DEFAULT_CLIENT_DIR_NAME}) + set (lib_name "cc_mqtt5_client") + set (app_name "cc_mqtt5_client_afl_fuzz") + else () + set (dir "${CC_MQTT5_CLIENT_CUSTOM_NAME}") + set (name "${name}_") + set (lib_name "cc_mqtt5_${name}client") + set (app_name "cc_mqtt5_${name}client_afl_fuzz") + endif () + + execute_process( + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/${dir}) + + set (src_output ${CMAKE_CURRENT_BINARY_DIR}/${dir}/${name}main.cpp) + + add_custom_command( + OUTPUT "${src_output}" + COMMAND ${CMAKE_COMMAND} + -DIN_FILE="${SRC_TEMPL}" + -DOUT_FILE="${src_output}" + -DNAME="${name}" + -P ${TEMPL_PROCESS_SCRIPT} + DEPENDS ${SRC_TEMPL} ${TEMPL_PROCESS_SCRIPT} + ) + + set_source_files_properties( + ${src_output} + PROPERTIES GENERATED TRUE + ) + + set (src_tgt_name "${name}main.cpp.tgt") + add_custom_target( + ${src_tgt_name} + DEPENDS "${src_output}" ${SRC_TEMPL} ${TEMPL_PROCESS_SCRIPT} + ) + + set (src + ${src_output} + ) + + add_executable(${app_name} ${src}) + target_link_libraries(${app_name} PRIVATE cc::${lib_name}) + + add_dependencies(${app_name} ${src_tgt_name}) + + install ( + TARGETS ${app_name} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ) +endfunction() + +###################################################################### + +if (CC_MQTT5_CLIENT_DEFAULT_LIB) + gen_mqtt5_client_afl_fuzz("") +endif () + +if (NOT "${CC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES}" STREQUAL "") + foreach (custom_config ${CC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES}) + gen_mqtt5_client_afl_fuzz("${custom_config}") + endforeach () +endif () \ No newline at end of file diff --git a/client/afl_fuzz/main.cpp.templ b/client/afl_fuzz/main.cpp.templ new file mode 100644 index 0000000..e3f2f05 --- /dev/null +++ b/client/afl_fuzz/main.cpp.templ @@ -0,0 +1,192 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "##NAME##client.h" + + +namespace +{ + +const std::size_t BufSize = 1024000; +const unsigned IterCount = 10000; + +struct AflFuzzDeleter +{ + void operator()(CC_Mqtt5Client* ptr) + { + cc_mqtt5_##NAME##client_free(ptr); + } +}; + +using AflFuzzClientPtr = std::unique_ptr; + +struct State +{ + CC_Mqtt5ClientHandle m_client = nullptr; + std::vector m_inData; + unsigned m_nextTickDuration = 0U; + bool m_reinitRequired = true; + bool m_connected = false; + bool m_subscribed = false; + bool m_firstConnect = true; +}; + +State* asState(void* data) +{ + return reinterpret_cast(data); +} + +void nextTickProgramCb(void* data, unsigned duration) +{ + asState(data)->m_nextTickDuration = duration; +} + +unsigned cancelNextTickWaitCb(void* data) +{ + auto diff = std::min(asState(data)->m_nextTickDuration, 1U); + asState(data)->m_nextTickDuration -= diff; + return diff; +} + +void sendOutputDataCb([[maybe_unused]] void* data, [[maybe_unused]] const unsigned char* buf, unsigned bufLen) +{ + std::cout << "Sending " << bufLen << " bytes." << std::endl; +} + +void brokerDisconnectReportCb(void* data, [[maybe_unused]] const CC_Mqtt5DisconnectInfo* info) +{ + auto* state = asState(data); + state->m_connected = false; + state->m_reinitRequired = true; +} + +void messageReceivedReportCb([[maybe_unused]] void* data, [[maybe_unused]] const CC_Mqtt5MessageInfo* info) +{ + std::cout << "Message Received!!!\n" << std::endl; +} + +void connectCompleteCb(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5ConnectResponse* response) +{ + auto* state = asState(data); + + do { + if (status != CC_Mqtt5AsyncOpStatus_Complete) { + break; + } + + assert(response != nullptr); + if (CC_Mqtt5ReasonCode_UnspecifiedError <= response->m_reasonCode) { + break; + } + + state->m_connected = true; + state->m_firstConnect = false; + } while (false); + + state->m_connected = false; + state->m_reinitRequired = true; +} + +} // namespace + +int main([[maybe_unused]] int argc, [[maybe_unused]] const char* argv[]) +{ + + AflFuzzClientPtr client(cc_mqtt5_##NAME##client_alloc()); + State state; + state.m_client = client.get(); + + cc_mqtt5_##NAME##client_set_next_tick_program_callback(client.get(), &nextTickProgramCb, &state); + cc_mqtt5_##NAME##client_set_cancel_next_tick_wait_callback(client.get(), &cancelNextTickWaitCb, &state); + cc_mqtt5_##NAME##client_set_send_output_data_callback(client.get(), &sendOutputDataCb, &state); + cc_mqtt5_##NAME##client_set_broker_disconnect_report_callback(client.get(), &brokerDisconnectReportCb, &state); + cc_mqtt5_##NAME##client_set_message_received_report_callback(client.get(), &messageReceivedReportCb, &state); + + sync(); + + CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_Success; + std::uint8_t buf[BufSize] = {0}; + for (auto i = 0U; i < IterCount; ++i) { + if (state.m_reinitRequired) { + state.m_reinitRequired = false; + ec = cc_mqtt5_##NAME##client_init(client.get()); + assert(ec == CC_Mqtt5ErrorCode_Success); + if (ec != CC_Mqtt5ErrorCode_Success) { + exit(-1); + } + + state.m_inData.clear(); + } + + do { + if (!state.m_connected) { + auto connect = cc_mqtt5_##NAME##client_connect_prepare(client.get(), &ec); + assert(connect != nullptr); + assert(ec == CC_Mqtt5ErrorCode_Success); + if (connect == nullptr) { + exit(-1); + } + + CC_Mqtt5ConnectBasicConfig basicConfig; + cc_mqtt5_##NAME##client_connect_init_config_basic(&basicConfig); + basicConfig.m_cleanStart = state.m_firstConnect; + + ec = cc_mqtt5_##NAME##client_connect_config_basic(connect, &basicConfig); + assert(ec == CC_Mqtt5ErrorCode_Success); + if (ec != CC_Mqtt5ErrorCode_Success) { + exit(-1); + } + + // TODO: extra configuration + + ec = cc_mqtt5_##NAME##client_connect_send(connect, &connectCompleteCb, &state); + assert(ec == CC_Mqtt5ErrorCode_Success); + if (ec != CC_Mqtt5ErrorCode_Success) { + exit(-1); + } + } + } while (false); + + auto len = read(0, buf, BufSize); + if (len <= 0) { + break; + } + + auto dataPtr = &buf[0]; + auto dataLen = static_cast(len); + if (!state.m_inData.empty()) { + state.m_inData.insert(state.m_inData.end(), dataPtr, dataPtr + dataLen); + static const auto MaxLen = std::numeric_limits::max(); + + if (MaxLen < state.m_inData.size()) { + state.m_inData.erase(state.m_inData.begin(), state.m_inData.begin() + (state.m_inData.size() - MaxLen)); + } + + dataPtr = state.m_inData.data(); + dataLen = state.m_inData.size(); + } + + auto consumed = cc_mqtt5_##NAME##client_process_data(client.get(), dataPtr, static_cast(dataLen)); + if (dataLen <= consumed) { + state.m_inData.clear(); + continue; + } + + if (state.m_inData.empty()) { + state.m_inData.assign(dataPtr + consumed, dataPtr + dataLen); + continue; + } + + state.m_inData.erase(state.m_inData.begin(), state.m_inData.begin() + consumed); + } + + return 0U; +} \ No newline at end of file diff --git a/client/lib/CMakeLists.txt b/client/lib/CMakeLists.txt index 712f532..db3ecdf 100644 --- a/client/lib/CMakeLists.txt +++ b/client/lib/CMakeLists.txt @@ -10,7 +10,7 @@ set (HEADER_TEMPL ${CMAKE_CURRENT_SOURCE_DIR}/templ/client.h.templ) set (SRC_TEMPL ${CMAKE_CURRENT_SOURCE_DIR}/templ/client.cpp.templ) set (CONFIG_TEMPL ${CMAKE_CURRENT_SOURCE_DIR}/templ/Config.h.templ) set (PROT_OPTS_TEMPL ${CMAKE_CURRENT_SOURCE_DIR}/templ/ProtocolOptions.h.templ) -set (TEMPL_PROCESS_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/script/ProcessTemplate.cmake) +set (TEMPL_PROCESS_SCRIPT ${PROJECT_SOURCE_DIR}/cmake/ProcessTemplate.cmake) set (WRITE_CONFIG_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/script/WriteConfigHeader.cmake) set (WRITE_PROT_OPTS_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/script/WriteProtocolOptions.cmake) set (DEFAULT_CONFIG_VARS_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/script/DefineDefaultConfigVars.cmake) @@ -170,15 +170,6 @@ function (gen_lib_mqtt5_client config_file) ) add_dependencies(${lib_name} ${header_tgt_name} ${src_tgt_name} ${config_tgt_name} ${prot_opts_tgt_name}) - if (NOT "${extra_flags}" STREQUAL "") - string(REPLACE ";" " " extra_flags "${extra_flags}") - - set_target_properties( - ${lib_name} PROPERTIES - COMPILE_FLAGS ${extra_flags} - ) - endif () - install ( FILES ${header_output} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cc_mqtt5_client diff --git a/cmake/Compile.cmake b/cmake/Compile.cmake index 13f22d6..326f618 100644 --- a/cmake/Compile.cmake +++ b/cmake/Compile.cmake @@ -34,6 +34,13 @@ macro (cc_mqttsn_compile) ) endif () + if (CC_MQTT5_CLIENT_AFL_FUZZ AND + (CMAKE_COMPILER_IS_GNUCC OR ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang"))) + list (APPEND extra_flags_list + "-Wno-old-style-cast" + ) + endif() + string(REPLACE ";" " " extra_flags "${extra_flags_list}") set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${extra_flags}") endmacro() \ No newline at end of file diff --git a/client/lib/script/ProcessTemplate.cmake b/cmake/ProcessTemplate.cmake similarity index 100% rename from client/lib/script/ProcessTemplate.cmake rename to cmake/ProcessTemplate.cmake From 8e6c80b143813cd65da36222121cb41f5f06614a Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 19 Dec 2023 08:27:11 +1000 Subject: [PATCH 03/35] Saving work on fuzzing. --- client/afl_fuzz/main.cpp.templ | 91 ++++++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 3 deletions(-) diff --git a/client/afl_fuzz/main.cpp.templ b/client/afl_fuzz/main.cpp.templ index e3f2f05..95e51b8 100644 --- a/client/afl_fuzz/main.cpp.templ +++ b/client/afl_fuzz/main.cpp.templ @@ -34,7 +34,9 @@ struct State std::vector m_inData; unsigned m_nextTickDuration = 0U; bool m_reinitRequired = true; + bool m_connectRequired = true; bool m_connected = false; + bool m_subscribeRequired = true; bool m_subscribed = false; bool m_firstConnect = true; }; @@ -64,8 +66,9 @@ void sendOutputDataCb([[maybe_unused]] void* data, [[maybe_unused]] const unsign void brokerDisconnectReportCb(void* data, [[maybe_unused]] const CC_Mqtt5DisconnectInfo* info) { auto* state = asState(data); - state->m_connected = false; state->m_reinitRequired = true; + state->m_connectRequired = true; + state->m_connected = false; } void messageReceivedReportCb([[maybe_unused]] void* data, [[maybe_unused]] const CC_Mqtt5MessageInfo* info) @@ -73,6 +76,11 @@ void messageReceivedReportCb([[maybe_unused]] void* data, [[maybe_unused]] const std::cout << "Message Received!!!\n" << std::endl; } +void errorLogCb([[maybe_unused]] void* data, const char* msg) +{ + std::cerr << "ERROR: " << msg << std::endl; +} + void connectCompleteCb(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5ConnectResponse* response) { auto* state = asState(data); @@ -87,14 +95,50 @@ void connectCompleteCb(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5C break; } + if (!response->m_sessionPresent) { + state->m_subscribeRequired = false; + state->m_subscribed = false; + } + + state->m_connectRequired = false; state->m_connected = true; state->m_firstConnect = false; } while (false); + state->m_connectRequired = true; state->m_connected = false; state->m_reinitRequired = true; } +void subscribeCompleteCb(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response) +{ + auto* state = asState(data); + + do { + if (status != CC_Mqtt5AsyncOpStatus_Complete) { + break; + } + + assert(response != nullptr); + bool allOk = true; + for (auto idx = 0U; idx < response->m_reasonCodesCount; ++idx) { + if (CC_Mqtt5ReasonCode_UnspecifiedError <= response->m_reasonCodes[idx]) { + allOk = false; + break; + } + } + + if (!allOk) { + break; + } + + state->m_subscribed = true; + } while (false); + + state->m_subscribeRequired = true; + state->m_subscribed = false; +} + } // namespace int main([[maybe_unused]] int argc, [[maybe_unused]] const char* argv[]) @@ -109,6 +153,7 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] const char* argv[]) cc_mqtt5_##NAME##client_set_send_output_data_callback(client.get(), &sendOutputDataCb, &state); cc_mqtt5_##NAME##client_set_broker_disconnect_report_callback(client.get(), &brokerDisconnectReportCb, &state); cc_mqtt5_##NAME##client_set_message_received_report_callback(client.get(), &messageReceivedReportCb, &state); + cc_mqtt5_##NAME##client_set_error_log_callback(client.get(), &errorLogCb, nullptr); sync(); @@ -126,8 +171,12 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] const char* argv[]) state.m_inData.clear(); } + assert(cc_mqtt5_##NAME##client_is_initialized(client.get())); + do { - if (!state.m_connected) { + if (state.m_connectRequired) { + state.m_connected = false; + auto connect = cc_mqtt5_##NAME##client_connect_prepare(client.get(), &ec); assert(connect != nullptr); assert(ec == CC_Mqtt5ErrorCode_Success); @@ -151,8 +200,44 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] const char* argv[]) assert(ec == CC_Mqtt5ErrorCode_Success); if (ec != CC_Mqtt5ErrorCode_Success) { exit(-1); - } + } + + state.m_connectRequired = false; + break; } + + assert(cc_mqtt5_##NAME##client_is_connected(client.get())); + + if (state.m_subscribeRequired) { + state.m_subscribed = false; + + auto subscribe = cc_mqtt5_##NAME##client_subscribe_prepare(client.get(), &ec); + assert(subscribe != nullptr); + assert(ec == CC_Mqtt5ErrorCode_Success); + if (subscribe == nullptr) { + exit(-1); + } + + CC_Mqtt5SubscribeTopicConfig config; + cc_mqtt5_##NAME##client_subscribe_init_config_topic(&config); + config.m_topic = "#"; + + ec = cc_mqtt5_##NAME##client_subscribe_config_topic(subscribe, &config); + assert(ec == CC_Mqtt5ErrorCode_Success); + if (ec != CC_Mqtt5ErrorCode_Success) { + exit(-1); + } + + ec = cc_mqtt5_##NAME##client_subscribe_send(subscribe, &subscribeCompleteCb, &state); + assert(ec == CC_Mqtt5ErrorCode_Success); + if (ec != CC_Mqtt5ErrorCode_Success) { + exit(-1); + } + + state.m_subscribeRequired = false; + break; + } + } while (false); auto len = read(0, buf, BufSize); From 444afbcbe62a47ba23bd286c26f22b40f997c620 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 19 Dec 2023 09:26:06 +1000 Subject: [PATCH 04/35] Added "packet id" report for selected operations. --- client/lib/include/cc_mqtt5_client/common.h | 3 ++ client/lib/src/op/SendOp.cpp | 29 +++++++++++---- client/lib/src/op/SendOp.h | 1 + client/lib/src/op/SubscribeOp.cpp | 9 ++++- client/lib/src/op/SubscribeOp.h | 1 + client/lib/src/op/UnsubscribeOp.cpp | 10 ++++- client/lib/src/op/UnsubscribeOp.h | 1 + client/lib/templ/client.cpp.templ | 27 ++++++++++++++ client/lib/templ/client.h.templ | 39 ++++++++++++++++++++ client/lib/test/unit/UnitTestBmBase.cpp | 3 ++ client/lib/test/unit/UnitTestCommonBase.cpp | 18 +++++++++ client/lib/test/unit/UnitTestCommonBase.h | 6 +++ client/lib/test/unit/UnitTestDefaultBase.cpp | 3 ++ 13 files changed, 140 insertions(+), 10 deletions(-) diff --git a/client/lib/include/cc_mqtt5_client/common.h b/client/lib/include/cc_mqtt5_client/common.h index 8040ed3..ce70ddd 100644 --- a/client/lib/include/cc_mqtt5_client/common.h +++ b/client/lib/include/cc_mqtt5_client/common.h @@ -390,6 +390,7 @@ typedef struct const char* m_reasonStr; ///< "Reason String" property, can be NULL const CC_Mqtt5UserProp* m_userProps; ///< Pointer to "User Properties" array, can be NULL unsigned m_userPropsCount; ///< Amount of elements in the "User Properties" array. + unsigned m_packetId; ///< "Packet Identifier" of the "subscribe" operation. } CC_Mqtt5SubscribeResponse; /// @brief Topic filter configuration structure of the "unsubscribe" operation. @@ -409,6 +410,7 @@ typedef struct const char* m_reasonStr; ///< "Reason String" property, can be NULL const CC_Mqtt5UserProp* m_userProps; ///< Pointer to "User Properties" array, can be NULL unsigned m_userPropsCount; ///< Amount of elements in the "User Properties" array. + unsigned m_packetId; ///< "Packet Identifier" of the "unsubscribe" operation. } CC_Mqtt5UnsubscribeResponse; /// @brief Received message information @@ -466,6 +468,7 @@ typedef struct const char* m_reasonStr; ///< "Reason String" property, can be NULL. const CC_Mqtt5UserProp* m_userProps; ///< Pointer to array of "User Properties", can be NULL unsigned m_userPropsCount; ///< Number of elements in "User Properties" array. + unsigned m_packetId; ///< "Packet Identifier" of the "publish" operation. } CC_Mqtt5PublishResponse; /// @brief Callback used to request time measurement. diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index 0a7e18c..b50cd35 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -40,7 +40,8 @@ SendOp::~SendOp() void SendOp::handle(PubackMsg& msg) { - if (m_pubMsg.field_packetId().field().value() != msg.field_packetId().value()) { + auto packetId = msg.field_packetId().value(); + if (m_pubMsg.field_packetId().field().value() != packetId) { return; } @@ -58,6 +59,8 @@ void SendOp::handle(PubackMsg& msg) UserPropsList userProps; // Will be referenced in response auto response = CC_Mqtt5PublishResponse(); + response.m_packetId = packetId; + auto completeOpOnExit = comms::util::makeScopeGuard( [this, &status, &response]() @@ -123,7 +126,8 @@ void SendOp::handle(PubackMsg& msg) void SendOp::handle(PubrecMsg& msg) { - if (m_pubMsg.field_packetId().field().value() != msg.field_packetId().value()) { + auto packetId = msg.field_packetId().value(); + if (m_pubMsg.field_packetId().field().value() != packetId) { return; } @@ -141,6 +145,8 @@ void SendOp::handle(PubrecMsg& msg) UserPropsList userProps; // Will be referenced in response auto response = CC_Mqtt5PublishResponse(); + response.m_packetId = packetId; + auto completeOpOnExit = comms::util::makeScopeGuard( [this, &status, &response]() @@ -230,7 +236,8 @@ void SendOp::handle(PubrecMsg& msg) void SendOp::handle(PubcompMsg& msg) { - if (m_pubMsg.field_packetId().field().value() != msg.field_packetId().value()) { + auto packetId = msg.field_packetId().value(); + if (m_pubMsg.field_packetId().field().value() != packetId) { return; } @@ -248,6 +255,8 @@ void SendOp::handle(PubcompMsg& msg) UserPropsList userProps; // Will be referenced in response auto response = CC_Mqtt5PublishResponse(); + response.m_packetId = packetId; + auto completeOpOnExit = comms::util::makeScopeGuard( [this, &status, &response]() @@ -408,6 +417,10 @@ CC_Mqtt5ErrorCode SendOp::configBasic(const CC_Mqtt5PublishBasicConfig& config) m_pubMsg.transportField_flags().field_retain().setBitValue_bit(config.m_retain); m_pubMsg.transportField_flags().field_qos().setValue(config.m_qos); + + if (config.m_qos > CC_Mqtt5QoS_AtMostOnceDelivery) { + m_pubMsg.field_packetId().field().setValue(allocPacketId()); + } if (mustAssignTopic) { auto& topicStr = m_pubMsg.field_topic().value(); @@ -596,11 +609,6 @@ CC_Mqtt5ErrorCode SendOp::send(CC_Mqtt5PublishCompleteCb cb, void* cbData) m_cb = cb; m_cbData = cbData; - using Qos = PublishMsg::TransportField_flags::Field_qos::ValueType; - if (m_pubMsg.transportField_flags().field_qos().value() > Qos::AtMostOnceDelivery) { - m_pubMsg.field_packetId().field().setValue(allocPacketId()); - } - m_pubMsg.doRefresh(); // Update packetId presence m_sendAttempts = 0U; @@ -628,6 +636,11 @@ CC_Mqtt5ErrorCode SendOp::cancel() return CC_Mqtt5ErrorCode_Success; } +unsigned SendOp::getPacketId() const +{ + return m_pubMsg.field_packetId().field().getValue(); +} + 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 30f88ef..3cfa0f1 100644 --- a/client/lib/src/op/SendOp.h +++ b/client/lib/src/op/SendOp.h @@ -44,6 +44,7 @@ class SendOp final : public Op unsigned getResendAttempts() const; CC_Mqtt5ErrorCode send(CC_Mqtt5PublishCompleteCb cb, void* cbData); CC_Mqtt5ErrorCode cancel(); + unsigned getPacketId() const; protected: virtual Type typeImpl() const override; diff --git a/client/lib/src/op/SubscribeOp.cpp b/client/lib/src/op/SubscribeOp.cpp index 5238e0f..374d853 100644 --- a/client/lib/src/op/SubscribeOp.cpp +++ b/client/lib/src/op/SubscribeOp.cpp @@ -48,6 +48,7 @@ SubscribeOp::SubscribeOp(ClientImpl& client) : Base(client), m_timer(client.timerMgr().allocTimer()) { + m_subMsg.field_packetId().setValue(allocPacketId()); } SubscribeOp::~SubscribeOp() @@ -163,7 +164,6 @@ CC_Mqtt5ErrorCode SubscribeOp::send(CC_Mqtt5SubscribeCompleteCb cb, void* cbData m_cb = cb; m_cbData = cbData; - m_subMsg.field_packetId().setValue(allocPacketId()); auto result = client().sendMessage(m_subMsg); if (result != CC_Mqtt5ErrorCode_Success) { return result; @@ -181,6 +181,11 @@ CC_Mqtt5ErrorCode SubscribeOp::cancel() return CC_Mqtt5ErrorCode_Success; } +unsigned SubscribeOp::getPacketId() const +{ + return m_subMsg.field_packetId().getValue(); +} + void SubscribeOp::handle(SubackMsg& msg) { auto packetId = msg.field_packetId().value(); @@ -196,6 +201,8 @@ void SubscribeOp::handle(SubackMsg& msg) UserPropsList userProps; // Will be referenced in response auto response = CC_Mqtt5SubscribeResponse(); + response.m_packetId = packetId; + auto terminationReason = DisconnectReason::ProtocolError; auto terminateOnExit = comms::util::makeScopeGuard( diff --git a/client/lib/src/op/SubscribeOp.h b/client/lib/src/op/SubscribeOp.h index 8555839..7d61d32 100644 --- a/client/lib/src/op/SubscribeOp.h +++ b/client/lib/src/op/SubscribeOp.h @@ -29,6 +29,7 @@ class SubscribeOp final : public Op CC_Mqtt5ErrorCode addUserProp(const CC_Mqtt5UserProp& prop); CC_Mqtt5ErrorCode send(CC_Mqtt5SubscribeCompleteCb cb, void* cbData); CC_Mqtt5ErrorCode cancel(); + unsigned getPacketId() const; using Base::handle; virtual void handle(SubackMsg& msg) override; diff --git a/client/lib/src/op/UnsubscribeOp.cpp b/client/lib/src/op/UnsubscribeOp.cpp index b72fc29..2d9bbf8 100644 --- a/client/lib/src/op/UnsubscribeOp.cpp +++ b/client/lib/src/op/UnsubscribeOp.cpp @@ -48,6 +48,7 @@ UnsubscribeOp::UnsubscribeOp(ClientImpl& client) : Base(client), m_timer(client.timerMgr().allocTimer()) { + m_unsubMsg.field_packetId().setValue(allocPacketId()); } UnsubscribeOp::~UnsubscribeOp() @@ -137,7 +138,6 @@ CC_Mqtt5ErrorCode UnsubscribeOp::send(CC_Mqtt5UnsubscribeCompleteCb cb, void* cb m_cb = cb; m_cbData = cbData; - m_unsubMsg.field_packetId().setValue(allocPacketId()); auto result = client().sendMessage(m_unsubMsg); if (result != CC_Mqtt5ErrorCode_Success) { return result; @@ -155,8 +155,14 @@ CC_Mqtt5ErrorCode UnsubscribeOp::cancel() return CC_Mqtt5ErrorCode_Success; } +unsigned UnsubscribeOp::getPacketId() const +{ + return m_unsubMsg.field_packetId().getValue(); +} + void UnsubscribeOp::handle(UnsubackMsg& msg) { + auto packetId = msg.field_packetId().value(); if (msg.field_packetId().value() != m_unsubMsg.field_packetId().value()) { return; } @@ -169,6 +175,8 @@ void UnsubscribeOp::handle(UnsubackMsg& msg) UserPropsList userProps; // Will be referenced in response auto response = CC_Mqtt5UnsubscribeResponse(); + response.m_packetId = packetId; + auto terminationReason = DisconnectReason::ProtocolError; auto terminateOnExit = comms::util::makeScopeGuard( diff --git a/client/lib/src/op/UnsubscribeOp.h b/client/lib/src/op/UnsubscribeOp.h index be84988..69f05fc 100644 --- a/client/lib/src/op/UnsubscribeOp.h +++ b/client/lib/src/op/UnsubscribeOp.h @@ -28,6 +28,7 @@ class UnsubscribeOp final : public Op CC_Mqtt5ErrorCode addUserProp(const CC_Mqtt5UserProp& prop); CC_Mqtt5ErrorCode send(CC_Mqtt5UnsubscribeCompleteCb cb, void* cbData); CC_Mqtt5ErrorCode cancel(); + unsigned getPacketId() const; using Base::handle; virtual void handle(UnsubackMsg& msg) override; diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index 458b9cc..b8856e4 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -576,6 +576,15 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_subscribe_cancel(CC_Mqtt5SubscribeHand return subscribeOpFromHandle(handle)->cancel(); } +unsigned cc_mqtt5_##NAME##client_subscribe_get_packet_id(CC_Mqtt5SubscribeHandle handle) +{ + if (handle == nullptr) { + return 0U; + } + + return subscribeOpFromHandle(handle)->getPacketId(); +} + CC_Mqtt5UnsubscribeHandle cc_mqtt5_##NAME##client_unsubscribe_prepare(CC_Mqtt5ClientHandle handle, CC_Mqtt5ErrorCode* ec) { if (handle == nullptr) { @@ -648,6 +657,15 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_unsubscribe_cancel(CC_Mqtt5Unsubscribe return unsubscribeOpFromHandle(handle)->cancel(); } +unsigned cc_mqtt5_##NAME##client_unsubscribe_get_packet_id(CC_Mqtt5UnsubscribeHandle handle) +{ + if (handle == nullptr) { + return CC_Mqtt5ErrorCode_BadParam; + } + + return unsubscribeOpFromHandle(handle)->getPacketId(); +} + CC_Mqtt5PublishHandle cc_mqtt5_##NAME##client_publish_prepare(CC_Mqtt5ClientHandle handle, CC_Mqtt5ErrorCode* ec) { if (handle == nullptr) { @@ -752,6 +770,15 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_cancel(CC_Mqtt5PublishHandle h return sendOpFromHandle(handle)->cancel(); } +unsigned cc_mqtt5_##NAME##client_publish_get_packet_id(CC_Mqtt5PublishHandle handle) +{ + if (handle == nullptr) { + return 0U; + } + + return sendOpFromHandle(handle)->getPacketId(); +} + CC_Mqtt5ReauthHandle cc_mqtt5_##NAME##client_reauth_prepare(CC_Mqtt5ClientHandle handle, CC_Mqtt5ErrorCode* ec) { if (handle == nullptr) { diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index 762f267..32e3905 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -305,6 +305,7 @@ 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 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 @@ -431,6 +432,16 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_subscribe_send(CC_Mqtt5SubscribeHandle /// @ingroup subscribe CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_subscribe_cancel(CC_Mqtt5SubscribeHandle handle); +/// @brief Retrieve "Packet Identifier" of the "subscribe" operation. +/// @details The retrieved value can be used to differentiate between the "subscribe" operations +/// in case they use the same callback when calling the +/// @ref cc_mqtt5_##NAME##client_subscribe_send() function. +/// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_subscribe_prepare() function. +/// @pre The function can be called while the handle is valid, i.e. until the callback +/// of the operation completion is called or until the operation is cancelled. +/// @return "Packet Identifier" value +unsigned cc_mqtt5_##NAME##client_subscribe_get_packet_id(CC_Mqtt5SubscribeHandle handle); + /// @brief Prepare "unsubscribe" 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. @@ -494,6 +505,16 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_unsubscribe_send(CC_Mqtt5UnsubscribeHa /// @ingroup unsubscribe CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_unsubscribe_cancel(CC_Mqtt5UnsubscribeHandle handle); +/// @brief Retrieve "Packet Identifier" of the "unsubscribe" operation. +/// @details The retrieved value can be used to differentiate between the "unsubscribe" operations +/// in case they use the same callback when calling the +/// @ref cc_mqtt5_##NAME##client_unsubscribe_send() function. +/// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_unsubscribe_prepare() function. +/// @pre The function can be called while the handle is valid, i.e. until the callback +/// of the operation completion is called or until the operation is cancelled. +/// @return "Packet Identifier" value +unsigned cc_mqtt5_##NAME##client_unsubscribe_get_packet_id(CC_Mqtt5UnsubscribeHandle handle); + /// @brief Prepare "publish" 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. @@ -586,6 +607,24 @@ 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 Retrieve "Packet Identifier" of the "publish" operation. +/// @details The retrieved value can be used to differentiate between the "publish" operations +/// in case they use the same callback when calling the +/// @ref cc_mqtt5_##NAME##client_publish_send() function. +/// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_publish_prepare() function. +/// @pre The function is expected to be called after the @ref cc_mqtt5_##NAME##client_publish_config_basic() +/// which sets the QoS value. In case the QoS value is @ref CC_Mqtt5QoS_AtMostOnceDelivery the +/// "Packet Identifier" is not allocated and @b 0 is returned +/// @pre The function can be called while the handle is valid, i.e. until the callback +/// of the operation completion is called or until the operation is cancelled. +/// @note When QoS is @b 0 (@ref CC_Mqtt5QoS_AtMostOnceDelivery), the callback function is +//// invoked immediately from within the @ref cc_mqtt5_##NAME##client_publish_send() +/// invocation immediatelly invalidating the handle, i.e. in such case the +/// @ref cc_mqtt5_##NAME##client_publish_get_packet_id() cannot be called after the +/// @ref cc_mqtt5_##NAME##client_publish_send(). +/// @return "Packet Identifier" value when allocated, @b 0 otherwise. +unsigned cc_mqtt5_##NAME##client_publish_get_packet_id(CC_Mqtt5PublishHandle handle); + /// @brief Prepare "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. diff --git a/client/lib/test/unit/UnitTestBmBase.cpp b/client/lib/test/unit/UnitTestBmBase.cpp index c12489d..56b29f5 100644 --- a/client/lib/test/unit/UnitTestBmBase.cpp +++ b/client/lib/test/unit/UnitTestBmBase.cpp @@ -59,6 +59,7 @@ 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_get_packet_id = &cc_mqtt5_bm_client_subscribe_get_packet_id; 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; @@ -67,6 +68,7 @@ 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_get_packet_id = &cc_mqtt5_bm_client_unsubscribe_get_packet_id; 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; @@ -79,6 +81,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_get_packet_id = &cc_mqtt5_bm_client_publish_get_packet_id; 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; diff --git a/client/lib/test/unit/UnitTestCommonBase.cpp b/client/lib/test/unit/UnitTestCommonBase.cpp index 968238b..57886d1 100644 --- a/client/lib/test/unit/UnitTestCommonBase.cpp +++ b/client/lib/test/unit/UnitTestCommonBase.cpp @@ -93,6 +93,7 @@ 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_get_packet_id != 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); @@ -101,6 +102,7 @@ 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_get_packet_id != 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); @@ -113,6 +115,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_get_packet_id != 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); @@ -1101,6 +1104,11 @@ CC_Mqtt5ErrorCode UnitTestCommonBase::unitTestSubscribeAddUserProp(CC_Mqtt5Subsc return m_funcs.m_subscribe_add_user_prop(handle, prop); } +unsigned UnitTestCommonBase::unitTestSubscribeGetPacktId(CC_Mqtt5SubscribeHandle handle) +{ + return m_funcs.m_subscribe_get_packet_id(handle); +} + CC_Mqtt5UnsubscribeHandle UnitTestCommonBase::unitTestUnsubscribePrepare(CC_Mqtt5Client* client, CC_Mqtt5ErrorCode* ec) { return m_funcs.m_unsubscribe_prepare(client, ec); @@ -1126,6 +1134,11 @@ CC_Mqtt5ErrorCode UnitTestCommonBase::unitTestUnsubscribeAddUserProp(CC_Mqtt5Uns return m_funcs.m_unsubscribe_add_user_prop(handle, prop); } +unsigned UnitTestCommonBase::unitTestUnsubscribeGetPacktId(CC_Mqtt5UnsubscribeHandle handle) +{ + return m_funcs.m_unsubscribe_get_packet_id(handle); +} + CC_Mqtt5PublishHandle UnitTestCommonBase::unitTestPublishPrepare(CC_Mqtt5Client* client, CC_Mqtt5ErrorCode* ec) { return m_funcs.m_publish_prepare(client, ec); @@ -1161,6 +1174,11 @@ CC_Mqtt5ErrorCode UnitTestCommonBase::unitTestPublishAddUserProp(CC_Mqtt5Publish return m_funcs.m_publish_add_user_prop(handle, prop); } +unsigned UnitTestCommonBase::unitTestPublishGetPacktId(CC_Mqtt5PublishHandle handle) +{ + return m_funcs.m_publish_get_packet_id(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 9d8d2f9..d1c775e 100644 --- a/client/lib/test/unit/UnitTestCommonBase.h +++ b/client/lib/test/unit/UnitTestCommonBase.h @@ -72,6 +72,7 @@ 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; + unsigned (*m_subscribe_get_packet_id)(CC_Mqtt5SubscribeHandle) = 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; @@ -80,6 +81,7 @@ 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; + unsigned (*m_unsubscribe_get_packet_id)(CC_Mqtt5UnsubscribeHandle) = 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; @@ -92,6 +94,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; + unsigned (*m_publish_get_packet_id)(CC_Mqtt5PublishHandle) = 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; @@ -460,11 +463,13 @@ 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); + unsigned unitTestSubscribeGetPacktId(CC_Mqtt5SubscribeHandle handle); CC_Mqtt5UnsubscribeHandle unitTestUnsubscribePrepare(CC_Mqtt5Client* client, CC_Mqtt5ErrorCode* ec); CC_Mqtt5ErrorCode unitTestUnsubscribeSetResponseTimeout(CC_Mqtt5UnsubscribeHandle handle, unsigned ms); void unitTestUnsubscribeInitConfigTopic(CC_Mqtt5UnsubscribeTopicConfig* config); CC_Mqtt5ErrorCode unitTestUnsubscribeConfigTopic(CC_Mqtt5UnsubscribeHandle handle, const CC_Mqtt5UnsubscribeTopicConfig* config); CC_Mqtt5ErrorCode unitTestUnsubscribeAddUserProp(CC_Mqtt5UnsubscribeHandle handle, const CC_Mqtt5UserProp* prop); + unsigned unitTestUnsubscribeGetPacktId(CC_Mqtt5UnsubscribeHandle handle); CC_Mqtt5PublishHandle unitTestPublishPrepare(CC_Mqtt5Client* client, CC_Mqtt5ErrorCode* ec); void unitTestPublishInitConfigBasic(CC_Mqtt5PublishBasicConfig* config); void unitTestPublishInitConfigExtra(CC_Mqtt5PublishExtraConfig* config); @@ -472,6 +477,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); + unsigned unitTestPublishGetPacktId(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 7f0d5a9..2c0394e 100644 --- a/client/lib/test/unit/UnitTestDefaultBase.cpp +++ b/client/lib/test/unit/UnitTestDefaultBase.cpp @@ -59,6 +59,7 @@ 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_get_packet_id = &cc_mqtt5_client_subscribe_get_packet_id; 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; @@ -67,6 +68,7 @@ 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_get_packet_id = &cc_mqtt5_client_unsubscribe_get_packet_id; 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; @@ -79,6 +81,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_get_packet_id = &cc_mqtt5_client_publish_get_packet_id; 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; From 34b0bbbb07334702ef0e4b3a0b170c67964b9985 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 20 Dec 2023 07:43:38 +1000 Subject: [PATCH 05/35] Revert "Added "packet id" report for selected operations." This reverts commit 444afbcbe62a47ba23bd286c26f22b40f997c620. --- client/lib/include/cc_mqtt5_client/common.h | 3 -- client/lib/src/op/SendOp.cpp | 29 ++++----------- client/lib/src/op/SendOp.h | 1 - client/lib/src/op/SubscribeOp.cpp | 9 +---- client/lib/src/op/SubscribeOp.h | 1 - client/lib/src/op/UnsubscribeOp.cpp | 10 +---- client/lib/src/op/UnsubscribeOp.h | 1 - client/lib/templ/client.cpp.templ | 27 -------------- client/lib/templ/client.h.templ | 39 -------------------- client/lib/test/unit/UnitTestBmBase.cpp | 3 -- client/lib/test/unit/UnitTestCommonBase.cpp | 18 --------- client/lib/test/unit/UnitTestCommonBase.h | 6 --- client/lib/test/unit/UnitTestDefaultBase.cpp | 3 -- 13 files changed, 10 insertions(+), 140 deletions(-) diff --git a/client/lib/include/cc_mqtt5_client/common.h b/client/lib/include/cc_mqtt5_client/common.h index ce70ddd..8040ed3 100644 --- a/client/lib/include/cc_mqtt5_client/common.h +++ b/client/lib/include/cc_mqtt5_client/common.h @@ -390,7 +390,6 @@ typedef struct const char* m_reasonStr; ///< "Reason String" property, can be NULL const CC_Mqtt5UserProp* m_userProps; ///< Pointer to "User Properties" array, can be NULL unsigned m_userPropsCount; ///< Amount of elements in the "User Properties" array. - unsigned m_packetId; ///< "Packet Identifier" of the "subscribe" operation. } CC_Mqtt5SubscribeResponse; /// @brief Topic filter configuration structure of the "unsubscribe" operation. @@ -410,7 +409,6 @@ typedef struct const char* m_reasonStr; ///< "Reason String" property, can be NULL const CC_Mqtt5UserProp* m_userProps; ///< Pointer to "User Properties" array, can be NULL unsigned m_userPropsCount; ///< Amount of elements in the "User Properties" array. - unsigned m_packetId; ///< "Packet Identifier" of the "unsubscribe" operation. } CC_Mqtt5UnsubscribeResponse; /// @brief Received message information @@ -468,7 +466,6 @@ typedef struct const char* m_reasonStr; ///< "Reason String" property, can be NULL. const CC_Mqtt5UserProp* m_userProps; ///< Pointer to array of "User Properties", can be NULL unsigned m_userPropsCount; ///< Number of elements in "User Properties" array. - unsigned m_packetId; ///< "Packet Identifier" of the "publish" operation. } CC_Mqtt5PublishResponse; /// @brief Callback used to request time measurement. diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index b50cd35..0a7e18c 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -40,8 +40,7 @@ SendOp::~SendOp() void SendOp::handle(PubackMsg& msg) { - auto packetId = msg.field_packetId().value(); - if (m_pubMsg.field_packetId().field().value() != packetId) { + if (m_pubMsg.field_packetId().field().value() != msg.field_packetId().value()) { return; } @@ -59,8 +58,6 @@ void SendOp::handle(PubackMsg& msg) UserPropsList userProps; // Will be referenced in response auto response = CC_Mqtt5PublishResponse(); - response.m_packetId = packetId; - auto completeOpOnExit = comms::util::makeScopeGuard( [this, &status, &response]() @@ -126,8 +123,7 @@ void SendOp::handle(PubackMsg& msg) void SendOp::handle(PubrecMsg& msg) { - auto packetId = msg.field_packetId().value(); - if (m_pubMsg.field_packetId().field().value() != packetId) { + if (m_pubMsg.field_packetId().field().value() != msg.field_packetId().value()) { return; } @@ -145,8 +141,6 @@ void SendOp::handle(PubrecMsg& msg) UserPropsList userProps; // Will be referenced in response auto response = CC_Mqtt5PublishResponse(); - response.m_packetId = packetId; - auto completeOpOnExit = comms::util::makeScopeGuard( [this, &status, &response]() @@ -236,8 +230,7 @@ void SendOp::handle(PubrecMsg& msg) void SendOp::handle(PubcompMsg& msg) { - auto packetId = msg.field_packetId().value(); - if (m_pubMsg.field_packetId().field().value() != packetId) { + if (m_pubMsg.field_packetId().field().value() != msg.field_packetId().value()) { return; } @@ -255,8 +248,6 @@ void SendOp::handle(PubcompMsg& msg) UserPropsList userProps; // Will be referenced in response auto response = CC_Mqtt5PublishResponse(); - response.m_packetId = packetId; - auto completeOpOnExit = comms::util::makeScopeGuard( [this, &status, &response]() @@ -417,10 +408,6 @@ CC_Mqtt5ErrorCode SendOp::configBasic(const CC_Mqtt5PublishBasicConfig& config) m_pubMsg.transportField_flags().field_retain().setBitValue_bit(config.m_retain); m_pubMsg.transportField_flags().field_qos().setValue(config.m_qos); - - if (config.m_qos > CC_Mqtt5QoS_AtMostOnceDelivery) { - m_pubMsg.field_packetId().field().setValue(allocPacketId()); - } if (mustAssignTopic) { auto& topicStr = m_pubMsg.field_topic().value(); @@ -609,6 +596,11 @@ CC_Mqtt5ErrorCode SendOp::send(CC_Mqtt5PublishCompleteCb cb, void* cbData) m_cb = cb; m_cbData = cbData; + using Qos = PublishMsg::TransportField_flags::Field_qos::ValueType; + if (m_pubMsg.transportField_flags().field_qos().value() > Qos::AtMostOnceDelivery) { + m_pubMsg.field_packetId().field().setValue(allocPacketId()); + } + m_pubMsg.doRefresh(); // Update packetId presence m_sendAttempts = 0U; @@ -636,11 +628,6 @@ CC_Mqtt5ErrorCode SendOp::cancel() return CC_Mqtt5ErrorCode_Success; } -unsigned SendOp::getPacketId() const -{ - return m_pubMsg.field_packetId().field().getValue(); -} - 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 3cfa0f1..30f88ef 100644 --- a/client/lib/src/op/SendOp.h +++ b/client/lib/src/op/SendOp.h @@ -44,7 +44,6 @@ class SendOp final : public Op unsigned getResendAttempts() const; CC_Mqtt5ErrorCode send(CC_Mqtt5PublishCompleteCb cb, void* cbData); CC_Mqtt5ErrorCode cancel(); - unsigned getPacketId() const; protected: virtual Type typeImpl() const override; diff --git a/client/lib/src/op/SubscribeOp.cpp b/client/lib/src/op/SubscribeOp.cpp index 374d853..5238e0f 100644 --- a/client/lib/src/op/SubscribeOp.cpp +++ b/client/lib/src/op/SubscribeOp.cpp @@ -48,7 +48,6 @@ SubscribeOp::SubscribeOp(ClientImpl& client) : Base(client), m_timer(client.timerMgr().allocTimer()) { - m_subMsg.field_packetId().setValue(allocPacketId()); } SubscribeOp::~SubscribeOp() @@ -164,6 +163,7 @@ CC_Mqtt5ErrorCode SubscribeOp::send(CC_Mqtt5SubscribeCompleteCb cb, void* cbData m_cb = cb; m_cbData = cbData; + m_subMsg.field_packetId().setValue(allocPacketId()); auto result = client().sendMessage(m_subMsg); if (result != CC_Mqtt5ErrorCode_Success) { return result; @@ -181,11 +181,6 @@ CC_Mqtt5ErrorCode SubscribeOp::cancel() return CC_Mqtt5ErrorCode_Success; } -unsigned SubscribeOp::getPacketId() const -{ - return m_subMsg.field_packetId().getValue(); -} - void SubscribeOp::handle(SubackMsg& msg) { auto packetId = msg.field_packetId().value(); @@ -201,8 +196,6 @@ void SubscribeOp::handle(SubackMsg& msg) UserPropsList userProps; // Will be referenced in response auto response = CC_Mqtt5SubscribeResponse(); - response.m_packetId = packetId; - auto terminationReason = DisconnectReason::ProtocolError; auto terminateOnExit = comms::util::makeScopeGuard( diff --git a/client/lib/src/op/SubscribeOp.h b/client/lib/src/op/SubscribeOp.h index 7d61d32..8555839 100644 --- a/client/lib/src/op/SubscribeOp.h +++ b/client/lib/src/op/SubscribeOp.h @@ -29,7 +29,6 @@ class SubscribeOp final : public Op CC_Mqtt5ErrorCode addUserProp(const CC_Mqtt5UserProp& prop); CC_Mqtt5ErrorCode send(CC_Mqtt5SubscribeCompleteCb cb, void* cbData); CC_Mqtt5ErrorCode cancel(); - unsigned getPacketId() const; using Base::handle; virtual void handle(SubackMsg& msg) override; diff --git a/client/lib/src/op/UnsubscribeOp.cpp b/client/lib/src/op/UnsubscribeOp.cpp index 2d9bbf8..b72fc29 100644 --- a/client/lib/src/op/UnsubscribeOp.cpp +++ b/client/lib/src/op/UnsubscribeOp.cpp @@ -48,7 +48,6 @@ UnsubscribeOp::UnsubscribeOp(ClientImpl& client) : Base(client), m_timer(client.timerMgr().allocTimer()) { - m_unsubMsg.field_packetId().setValue(allocPacketId()); } UnsubscribeOp::~UnsubscribeOp() @@ -138,6 +137,7 @@ CC_Mqtt5ErrorCode UnsubscribeOp::send(CC_Mqtt5UnsubscribeCompleteCb cb, void* cb m_cb = cb; m_cbData = cbData; + m_unsubMsg.field_packetId().setValue(allocPacketId()); auto result = client().sendMessage(m_unsubMsg); if (result != CC_Mqtt5ErrorCode_Success) { return result; @@ -155,14 +155,8 @@ CC_Mqtt5ErrorCode UnsubscribeOp::cancel() return CC_Mqtt5ErrorCode_Success; } -unsigned UnsubscribeOp::getPacketId() const -{ - return m_unsubMsg.field_packetId().getValue(); -} - void UnsubscribeOp::handle(UnsubackMsg& msg) { - auto packetId = msg.field_packetId().value(); if (msg.field_packetId().value() != m_unsubMsg.field_packetId().value()) { return; } @@ -175,8 +169,6 @@ void UnsubscribeOp::handle(UnsubackMsg& msg) UserPropsList userProps; // Will be referenced in response auto response = CC_Mqtt5UnsubscribeResponse(); - response.m_packetId = packetId; - auto terminationReason = DisconnectReason::ProtocolError; auto terminateOnExit = comms::util::makeScopeGuard( diff --git a/client/lib/src/op/UnsubscribeOp.h b/client/lib/src/op/UnsubscribeOp.h index 69f05fc..be84988 100644 --- a/client/lib/src/op/UnsubscribeOp.h +++ b/client/lib/src/op/UnsubscribeOp.h @@ -28,7 +28,6 @@ class UnsubscribeOp final : public Op CC_Mqtt5ErrorCode addUserProp(const CC_Mqtt5UserProp& prop); CC_Mqtt5ErrorCode send(CC_Mqtt5UnsubscribeCompleteCb cb, void* cbData); CC_Mqtt5ErrorCode cancel(); - unsigned getPacketId() const; using Base::handle; virtual void handle(UnsubackMsg& msg) override; diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index b8856e4..458b9cc 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -576,15 +576,6 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_subscribe_cancel(CC_Mqtt5SubscribeHand return subscribeOpFromHandle(handle)->cancel(); } -unsigned cc_mqtt5_##NAME##client_subscribe_get_packet_id(CC_Mqtt5SubscribeHandle handle) -{ - if (handle == nullptr) { - return 0U; - } - - return subscribeOpFromHandle(handle)->getPacketId(); -} - CC_Mqtt5UnsubscribeHandle cc_mqtt5_##NAME##client_unsubscribe_prepare(CC_Mqtt5ClientHandle handle, CC_Mqtt5ErrorCode* ec) { if (handle == nullptr) { @@ -657,15 +648,6 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_unsubscribe_cancel(CC_Mqtt5Unsubscribe return unsubscribeOpFromHandle(handle)->cancel(); } -unsigned cc_mqtt5_##NAME##client_unsubscribe_get_packet_id(CC_Mqtt5UnsubscribeHandle handle) -{ - if (handle == nullptr) { - return CC_Mqtt5ErrorCode_BadParam; - } - - return unsubscribeOpFromHandle(handle)->getPacketId(); -} - CC_Mqtt5PublishHandle cc_mqtt5_##NAME##client_publish_prepare(CC_Mqtt5ClientHandle handle, CC_Mqtt5ErrorCode* ec) { if (handle == nullptr) { @@ -770,15 +752,6 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_publish_cancel(CC_Mqtt5PublishHandle h return sendOpFromHandle(handle)->cancel(); } -unsigned cc_mqtt5_##NAME##client_publish_get_packet_id(CC_Mqtt5PublishHandle handle) -{ - if (handle == nullptr) { - return 0U; - } - - return sendOpFromHandle(handle)->getPacketId(); -} - CC_Mqtt5ReauthHandle cc_mqtt5_##NAME##client_reauth_prepare(CC_Mqtt5ClientHandle handle, CC_Mqtt5ErrorCode* ec) { if (handle == nullptr) { diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index 32e3905..762f267 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -305,7 +305,6 @@ 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 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 @@ -432,16 +431,6 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_subscribe_send(CC_Mqtt5SubscribeHandle /// @ingroup subscribe CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_subscribe_cancel(CC_Mqtt5SubscribeHandle handle); -/// @brief Retrieve "Packet Identifier" of the "subscribe" operation. -/// @details The retrieved value can be used to differentiate between the "subscribe" operations -/// in case they use the same callback when calling the -/// @ref cc_mqtt5_##NAME##client_subscribe_send() function. -/// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_subscribe_prepare() function. -/// @pre The function can be called while the handle is valid, i.e. until the callback -/// of the operation completion is called or until the operation is cancelled. -/// @return "Packet Identifier" value -unsigned cc_mqtt5_##NAME##client_subscribe_get_packet_id(CC_Mqtt5SubscribeHandle handle); - /// @brief Prepare "unsubscribe" 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. @@ -505,16 +494,6 @@ CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_unsubscribe_send(CC_Mqtt5UnsubscribeHa /// @ingroup unsubscribe CC_Mqtt5ErrorCode cc_mqtt5_##NAME##client_unsubscribe_cancel(CC_Mqtt5UnsubscribeHandle handle); -/// @brief Retrieve "Packet Identifier" of the "unsubscribe" operation. -/// @details The retrieved value can be used to differentiate between the "unsubscribe" operations -/// in case they use the same callback when calling the -/// @ref cc_mqtt5_##NAME##client_unsubscribe_send() function. -/// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_unsubscribe_prepare() function. -/// @pre The function can be called while the handle is valid, i.e. until the callback -/// of the operation completion is called or until the operation is cancelled. -/// @return "Packet Identifier" value -unsigned cc_mqtt5_##NAME##client_unsubscribe_get_packet_id(CC_Mqtt5UnsubscribeHandle handle); - /// @brief Prepare "publish" 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. @@ -607,24 +586,6 @@ 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 Retrieve "Packet Identifier" of the "publish" operation. -/// @details The retrieved value can be used to differentiate between the "publish" operations -/// in case they use the same callback when calling the -/// @ref cc_mqtt5_##NAME##client_publish_send() function. -/// @param[in] handle Handle returned by @ref cc_mqtt5_##NAME##client_publish_prepare() function. -/// @pre The function is expected to be called after the @ref cc_mqtt5_##NAME##client_publish_config_basic() -/// which sets the QoS value. In case the QoS value is @ref CC_Mqtt5QoS_AtMostOnceDelivery the -/// "Packet Identifier" is not allocated and @b 0 is returned -/// @pre The function can be called while the handle is valid, i.e. until the callback -/// of the operation completion is called or until the operation is cancelled. -/// @note When QoS is @b 0 (@ref CC_Mqtt5QoS_AtMostOnceDelivery), the callback function is -//// invoked immediately from within the @ref cc_mqtt5_##NAME##client_publish_send() -/// invocation immediatelly invalidating the handle, i.e. in such case the -/// @ref cc_mqtt5_##NAME##client_publish_get_packet_id() cannot be called after the -/// @ref cc_mqtt5_##NAME##client_publish_send(). -/// @return "Packet Identifier" value when allocated, @b 0 otherwise. -unsigned cc_mqtt5_##NAME##client_publish_get_packet_id(CC_Mqtt5PublishHandle handle); - /// @brief Prepare "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. diff --git a/client/lib/test/unit/UnitTestBmBase.cpp b/client/lib/test/unit/UnitTestBmBase.cpp index 56b29f5..c12489d 100644 --- a/client/lib/test/unit/UnitTestBmBase.cpp +++ b/client/lib/test/unit/UnitTestBmBase.cpp @@ -59,7 +59,6 @@ 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_get_packet_id = &cc_mqtt5_bm_client_subscribe_get_packet_id; 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; @@ -68,7 +67,6 @@ 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_get_packet_id = &cc_mqtt5_bm_client_unsubscribe_get_packet_id; 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; @@ -81,7 +79,6 @@ 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_get_packet_id = &cc_mqtt5_bm_client_publish_get_packet_id; 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; diff --git a/client/lib/test/unit/UnitTestCommonBase.cpp b/client/lib/test/unit/UnitTestCommonBase.cpp index 57886d1..968238b 100644 --- a/client/lib/test/unit/UnitTestCommonBase.cpp +++ b/client/lib/test/unit/UnitTestCommonBase.cpp @@ -93,7 +93,6 @@ 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_get_packet_id != 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); @@ -102,7 +101,6 @@ 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_get_packet_id != 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); @@ -115,7 +113,6 @@ 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_get_packet_id != 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); @@ -1104,11 +1101,6 @@ CC_Mqtt5ErrorCode UnitTestCommonBase::unitTestSubscribeAddUserProp(CC_Mqtt5Subsc return m_funcs.m_subscribe_add_user_prop(handle, prop); } -unsigned UnitTestCommonBase::unitTestSubscribeGetPacktId(CC_Mqtt5SubscribeHandle handle) -{ - return m_funcs.m_subscribe_get_packet_id(handle); -} - CC_Mqtt5UnsubscribeHandle UnitTestCommonBase::unitTestUnsubscribePrepare(CC_Mqtt5Client* client, CC_Mqtt5ErrorCode* ec) { return m_funcs.m_unsubscribe_prepare(client, ec); @@ -1134,11 +1126,6 @@ CC_Mqtt5ErrorCode UnitTestCommonBase::unitTestUnsubscribeAddUserProp(CC_Mqtt5Uns return m_funcs.m_unsubscribe_add_user_prop(handle, prop); } -unsigned UnitTestCommonBase::unitTestUnsubscribeGetPacktId(CC_Mqtt5UnsubscribeHandle handle) -{ - return m_funcs.m_unsubscribe_get_packet_id(handle); -} - CC_Mqtt5PublishHandle UnitTestCommonBase::unitTestPublishPrepare(CC_Mqtt5Client* client, CC_Mqtt5ErrorCode* ec) { return m_funcs.m_publish_prepare(client, ec); @@ -1174,11 +1161,6 @@ CC_Mqtt5ErrorCode UnitTestCommonBase::unitTestPublishAddUserProp(CC_Mqtt5Publish return m_funcs.m_publish_add_user_prop(handle, prop); } -unsigned UnitTestCommonBase::unitTestPublishGetPacktId(CC_Mqtt5PublishHandle handle) -{ - return m_funcs.m_publish_get_packet_id(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 d1c775e..9d8d2f9 100644 --- a/client/lib/test/unit/UnitTestCommonBase.h +++ b/client/lib/test/unit/UnitTestCommonBase.h @@ -72,7 +72,6 @@ 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; - unsigned (*m_subscribe_get_packet_id)(CC_Mqtt5SubscribeHandle) = 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; @@ -81,7 +80,6 @@ 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; - unsigned (*m_unsubscribe_get_packet_id)(CC_Mqtt5UnsubscribeHandle) = 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; @@ -94,7 +92,6 @@ 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; - unsigned (*m_publish_get_packet_id)(CC_Mqtt5PublishHandle) = 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; @@ -463,13 +460,11 @@ 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); - unsigned unitTestSubscribeGetPacktId(CC_Mqtt5SubscribeHandle handle); CC_Mqtt5UnsubscribeHandle unitTestUnsubscribePrepare(CC_Mqtt5Client* client, CC_Mqtt5ErrorCode* ec); CC_Mqtt5ErrorCode unitTestUnsubscribeSetResponseTimeout(CC_Mqtt5UnsubscribeHandle handle, unsigned ms); void unitTestUnsubscribeInitConfigTopic(CC_Mqtt5UnsubscribeTopicConfig* config); CC_Mqtt5ErrorCode unitTestUnsubscribeConfigTopic(CC_Mqtt5UnsubscribeHandle handle, const CC_Mqtt5UnsubscribeTopicConfig* config); CC_Mqtt5ErrorCode unitTestUnsubscribeAddUserProp(CC_Mqtt5UnsubscribeHandle handle, const CC_Mqtt5UserProp* prop); - unsigned unitTestUnsubscribeGetPacktId(CC_Mqtt5UnsubscribeHandle handle); CC_Mqtt5PublishHandle unitTestPublishPrepare(CC_Mqtt5Client* client, CC_Mqtt5ErrorCode* ec); void unitTestPublishInitConfigBasic(CC_Mqtt5PublishBasicConfig* config); void unitTestPublishInitConfigExtra(CC_Mqtt5PublishExtraConfig* config); @@ -477,7 +472,6 @@ 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); - unsigned unitTestPublishGetPacktId(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 2c0394e..7f0d5a9 100644 --- a/client/lib/test/unit/UnitTestDefaultBase.cpp +++ b/client/lib/test/unit/UnitTestDefaultBase.cpp @@ -59,7 +59,6 @@ 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_get_packet_id = &cc_mqtt5_client_subscribe_get_packet_id; 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; @@ -68,7 +67,6 @@ 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_get_packet_id = &cc_mqtt5_client_unsubscribe_get_packet_id; 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; @@ -81,7 +79,6 @@ 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_get_packet_id = &cc_mqtt5_client_publish_get_packet_id; 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; From 4a27fb3920ca0d26f1e6c35d5c2d68beeae96f95 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 20 Dec 2023 08:59:23 +1000 Subject: [PATCH 06/35] Reporting operation handles in the "subscribe", "unsubscribe", and "publish" operations. --- client/afl_fuzz/main.cpp.templ | 2 +- client/app/pub/Pub.cpp | 6 +-- client/app/pub/Pub.h | 4 +- client/app/sub/Sub.cpp | 9 ++-- client/app/sub/Sub.h | 4 +- client/lib/doxygen/main.dox | 48 +++++++++++++++---- client/lib/include/cc_mqtt5_client/common.h | 18 +++++-- client/lib/src/op/SendOp.cpp | 2 +- client/lib/src/op/SendOp.h | 15 ++++++ client/lib/src/op/SubscribeOp.cpp | 3 +- client/lib/src/op/SubscribeOp.h | 15 ++++++ client/lib/src/op/UnsubscribeOp.cpp | 3 +- client/lib/src/op/UnsubscribeOp.h | 15 ++++++ client/lib/templ/client.cpp.templ | 8 ++-- .../IntegrationTestBasicPubSub.cpp | 17 +++---- .../integration/IntegrationTestCommonBase.cpp | 29 +++++++---- .../integration/IntegrationTestCommonBase.h | 16 +++---- client/lib/test/unit/UnitTestCommonBase.cpp | 6 +-- client/lib/test/unit/UnitTestCommonBase.h | 6 +-- 19 files changed, 163 insertions(+), 63 deletions(-) diff --git a/client/afl_fuzz/main.cpp.templ b/client/afl_fuzz/main.cpp.templ index 95e51b8..4f6488c 100644 --- a/client/afl_fuzz/main.cpp.templ +++ b/client/afl_fuzz/main.cpp.templ @@ -110,7 +110,7 @@ void connectCompleteCb(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5C state->m_reinitRequired = true; } -void subscribeCompleteCb(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response) +void subscribeCompleteCb(void* data, [[maybe_unused]] CC_Mqtt5SubscribeHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response) { auto* state = asState(data); diff --git a/client/app/pub/Pub.cpp b/client/app/pub/Pub.cpp index 5488620..d5b151e 100644 --- a/client/app/pub/Pub.cpp +++ b/client/app/pub/Pub.cpp @@ -112,7 +112,7 @@ void Pub::brokerConnectedImpl() } } -void Pub::publishCompleteInternal(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response) +void Pub::publishCompleteInternal([[maybe_unused]] CC_Mqtt5PublishHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response) { if (status != CC_Mqtt5AsyncOpStatus_Complete) { logError() << "Publish failed with status=" << toString(status) << std::endl; @@ -136,9 +136,9 @@ void Pub::publishCompleteInternal(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5Pu doTerminate(result); } -void Pub::publishCompleteCb(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response) +void Pub::publishCompleteCb(void* data, CC_Mqtt5PublishHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response) { - asThis(data)->publishCompleteInternal(status, response); + asThis(data)->publishCompleteInternal(handle, status, response); } diff --git a/client/app/pub/Pub.h b/client/app/pub/Pub.h index 92ea4ad..bf88596 100644 --- a/client/app/pub/Pub.h +++ b/client/app/pub/Pub.h @@ -24,9 +24,9 @@ class Pub : public AppClient protected: virtual void brokerConnectedImpl() override; private: - void publishCompleteInternal(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response); + void publishCompleteInternal(CC_Mqtt5PublishHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response); - static void publishCompleteCb(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response); + static void publishCompleteCb(void* data, CC_Mqtt5PublishHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response); }; } // namespace cc_mqtt5_client_app diff --git a/client/app/sub/Sub.cpp b/client/app/sub/Sub.cpp index 8b205f1..8605c2a 100644 --- a/client/app/sub/Sub.cpp +++ b/client/app/sub/Sub.cpp @@ -111,7 +111,10 @@ void Sub::messageReceivedImpl(const CC_Mqtt5MessageInfo* info) } } -void Sub::subscribeCompleteInternal(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response) +void Sub::subscribeCompleteInternal( + [[maybe_unused]] CC_Mqtt5SubscribeHandle handle, + CC_Mqtt5AsyncOpStatus status, + const CC_Mqtt5SubscribeResponse* response) { if (status != CC_Mqtt5AsyncOpStatus_Complete) { logError() << "Subscribe failed with status=" << toString(status) << std::endl; @@ -136,9 +139,9 @@ void Sub::subscribeCompleteInternal(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5 // Listening to the messages } -void Sub::subscribeCompleteCb(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response) +void Sub::subscribeCompleteCb(void* data, CC_Mqtt5SubscribeHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response) { - asThis(data)->subscribeCompleteInternal(status, response); + asThis(data)->subscribeCompleteInternal(handle, status, response); } diff --git a/client/app/sub/Sub.h b/client/app/sub/Sub.h index 1c45bc5..662d341 100644 --- a/client/app/sub/Sub.h +++ b/client/app/sub/Sub.h @@ -26,9 +26,9 @@ class Sub : public AppClient virtual void messageReceivedImpl(const CC_Mqtt5MessageInfo* info) override; private: - void subscribeCompleteInternal(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response); + void subscribeCompleteInternal(CC_Mqtt5SubscribeHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response); - static void subscribeCompleteCb(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response); + static void subscribeCompleteCb(void* data, CC_Mqtt5SubscribeHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response); }; } // namespace cc_mqtt5_client_app diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index 8d284db..6ba7973 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -509,9 +509,14 @@ /// the operation before the completion callback is invoked. /// /// When the "connect" operation completion callback is invoked the reported -/// response information is present if and only if the "status" is +/// "response" information is present if and only if the "status" is /// @ref CC_Mqtt5AsyncOpStatus_Complete. /// +/// @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". +/// /// @b IMPORTANT: The @ref CC_Mqtt5ConnectResponse "response" information from /// the broker may report that some features on the broker side are disabled. /// The library will reject any subsequent operation configuration which contradict @@ -771,7 +776,7 @@ /// operation it can actually be sent to the broker. To initiate sending /// use the @b cc_mqtt5_client_subscribe_send() function. /// @code -/// void my_subscribe_complete_cb(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response) +/// void my_subscribe_complete_cb(void* data, CC_Mqtt5SubscribeHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response) /// { /// if (status != CC_Mqtt5AsyncOpStatus_Complete) { /// printf("ERROR: The subscription operation has failed with status=%d\n", status); @@ -801,8 +806,14 @@ /// The valid handle can be used to @ref 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 +/// its second parameter. Although the handle is already invalid and cannot be +/// used in any other function, it allows the application to identify the +/// original "subscribe" request if multiple have been issued in parallel +/// and use the same callback function for all of them. +/// /// When the "subscribe" operation completion callback is invoked the reported -/// response information is present if and only if the "status" is +/// "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. @@ -914,7 +925,7 @@ /// operation it can actually be sent to the broker. To initiate sending /// use the @b cc_mqtt5_client_unsubscribe_send() function. /// @code -/// void my_unsubscribe_complete_cb(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5UnsubscribeResponse* response) +/// void my_unsubscribe_complete_cb(void* data, CC_Mqtt5UnsubscribeHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5UnsubscribeResponse* response) /// { /// if (status != CC_Mqtt5AsyncOpStatus_Complete) { /// printf("ERROR: The unsubscription operation has failed with status=%d\n", status); @@ -944,8 +955,14 @@ /// The valid handle can be used to @ref 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 +/// its second parameter. Although the handle is already invalid and cannot be +/// used in any other function, it allows the application to identify the +/// original "unsubscribe" request if multiple have been issued in parallel +/// and use the same callback function for all of them. +/// /// When the "unsubscribe" operation completion callback is invoked the reported -/// response information is present if and only if the "status" is +/// "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. @@ -1092,7 +1109,7 @@ /// operation it can actually be sent to the broker. To initiate sending /// use the @b cc_mqtt5_client_publish_send() function. /// @code -/// void my_publish_complete_cb(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response) +/// void my_publish_complete_cb(void* data, CC_Mqtt5PublishHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response) /// { /// if (status != CC_Mqtt5AsyncOpStatus_Complete) { /// printf("ERROR: The publish operation has failed with status=%d\n", status); @@ -1133,11 +1150,17 @@ /// The valid handle can be used to @ref 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 +/// its second parameter. Although the handle is already invalid and cannot be +/// used in any other function, it allows the application to identify the +/// original "publish" request if multiple have been issued in parallel +/// and use the same callback function for all of them. +/// /// When the "publish" operation completion callback is invoked the reported -/// response information is present if and only if the "status" is +/// "response" information is present if and only if the "status" is /// @ref CC_Mqtt5AsyncOpStatus_Complete. /// -/// When the callback reporting the connection status is invoked, it is responsibility +/// When the callback reporting the publish status is invoked, it is responsibility /// of the application to check the @ref CC_Mqtt5PublishResponse::m_reasonCode value. /// If it's @ref CC_Mqtt5ReasonCode_UnspecifiedError or greater means the "publish" /// operation wasn't successful. @@ -1387,9 +1410,14 @@ /// the operation before the completion callback is invoked. /// /// When the "reauth" operation completion callback is invoked the reported -/// response information is present if and only if the "status" is +/// "response" information is present if and only if the "status" is /// @ref CC_Mqtt5AsyncOpStatus_Complete. /// +/// @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". +/// /// @subsection 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. @@ -1451,7 +1479,7 @@ /// 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 -/// operations. When the application detects such network disconnection, it +/// 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 diff --git a/client/lib/include/cc_mqtt5_client/common.h b/client/lib/include/cc_mqtt5_client/common.h index 8040ed3..fcb3d8c 100644 --- a/client/lib/include/cc_mqtt5_client/common.h +++ b/client/lib/include/cc_mqtt5_client/common.h @@ -544,32 +544,44 @@ typedef CC_Mqtt5AuthErrorCode (*CC_Mqtt5AuthCb)(void* data, const CC_Mqtt5AuthIn /// @brief Callback used to report completion of the "subscribe" operation. /// @param[in] data Pointer to user data object passed as last parameter to the /// @b cc_mqtt5_client_subscribe_send(). +/// @param[in] handle Handle returned by @b cc_mqtt5_client_subscribe_prepare() function. When the +/// callback is invoked the handle is already invalid and cannot be used in any relevant +/// function invocation, but it allows end application to identify the original "subscribe" operation +/// and use the same callback function in parallel requests. /// @param[in] status Status of the "subscribe" operation. /// @param[in] response Response information from the broker. Not-NULL is reported if and onfly if /// the "status" is equal to @ref CC_Mqtt5AsyncOpStatus_Complete. /// @post The data members of the reported response can NOT be accessed after the function returns. /// @ingroup subscribe -typedef void (*CC_Mqtt5SubscribeCompleteCb)(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response); +typedef void (*CC_Mqtt5SubscribeCompleteCb)(void* data, CC_Mqtt5SubscribeHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response); /// @brief Callback used to report completion of the "unsubscribe" operation. /// @param[in] data Pointer to user data object passed as last parameter to the /// @b cc_mqtt5_client_unsubscribe_send(). +/// @param[in] handle Handle returned by @b cc_mqtt5_client_unsubscribe_prepare() function. When the +/// callback is invoked the handle is already invalid and cannot be used in any relevant +/// function invocation, but it allows end application to identify the original "unsubscribe" operation +/// and use the same callback function in parallel requests. /// @param[in] status Status of the "unsubscribe" operation. /// @param[in] response Response information from the broker. Not-NULL is reported if and onfly if /// the "status" is equal to @ref CC_Mqtt5AsyncOpStatus_Complete. /// @post The data members of the reported response can NOT be accessed after the function returns. /// @ingroup unsubscribe -typedef void (*CC_Mqtt5UnsubscribeCompleteCb)(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5UnsubscribeResponse* response); +typedef void (*CC_Mqtt5UnsubscribeCompleteCb)(void* data, CC_Mqtt5UnsubscribeHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5UnsubscribeResponse* response); /// @brief Callback used to report completion of the "publish" operation. /// @param[in] data Pointer to user data object passed as last parameter to the /// @b cc_mqtt5_client_publish_send(). +/// @param[in] handle Handle returned by @b cc_mqtt5_client_publish_prepare() function. When the +/// callback is invoked the handle is already invalid and cannot be used in any relevant +/// function invocation, but it allows end application to identify the original "publish" operation +/// and use the same callback function in parallel requests. /// @param[in] status Status of the "publish" operation. /// @param[in] response Response information from the broker. Not-NULL is reported if and onfly if /// the "status" is equal to @ref CC_Mqtt5AsyncOpStatus_Complete. /// @post The data members of the reported response can NOT be accessed after the function returns. /// @ingroup publish -typedef void (*CC_Mqtt5PublishCompleteCb)(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response); +typedef void (*CC_Mqtt5PublishCompleteCb)(void* data, CC_Mqtt5PublishHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response); /// @brief Callback used to report completion of the "reauth" operation. /// @param[in] data Pointer to user data object passed as last parameter to the diff --git a/client/lib/src/op/SendOp.cpp b/client/lib/src/op/SendOp.cpp index 0a7e18c..0e21f00 100644 --- a/client/lib/src/op/SendOp.cpp +++ b/client/lib/src/op/SendOp.cpp @@ -697,7 +697,7 @@ void SendOp::reportPubComplete(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5Publi return; } - m_cb(m_cbData, status, response); + m_cb(m_cbData, toHandle(), status, response); } void SendOp::confirmRegisteredAlias() diff --git a/client/lib/src/op/SendOp.h b/client/lib/src/op/SendOp.h index 30f88ef..e1ad6fe 100644 --- a/client/lib/src/op/SendOp.h +++ b/client/lib/src/op/SendOp.h @@ -32,6 +32,21 @@ class SendOp final : public Op virtual void handle(PubrecMsg& msg) override; virtual void handle(PubcompMsg& msg) override; + CC_Mqtt5PublishHandle toHandle() + { + return reinterpret_cast(this); + } + + static CC_Mqtt5PublishHandle asHandle(SendOp* obj) + { + return reinterpret_cast(obj); + } + + static SendOp* fromHandle(CC_Mqtt5PublishHandle handle) + { + return reinterpret_cast(handle); + } + unsigned packetId() const { return m_pubMsg.field_packetId().field().value(); diff --git a/client/lib/src/op/SubscribeOp.cpp b/client/lib/src/op/SubscribeOp.cpp index 5238e0f..14517e5 100644 --- a/client/lib/src/op/SubscribeOp.cpp +++ b/client/lib/src/op/SubscribeOp.cpp @@ -331,9 +331,10 @@ void SubscribeOp::completeOpInternal(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt { auto cb = m_cb; auto* cbData = m_cbData; + auto handle = toHandle(); opComplete(); // mustn't access data members after destruction if (cb != nullptr) { - cb(cbData, status, response); + cb(cbData, handle, status, response); } } diff --git a/client/lib/src/op/SubscribeOp.h b/client/lib/src/op/SubscribeOp.h index 8555839..9609f8a 100644 --- a/client/lib/src/op/SubscribeOp.h +++ b/client/lib/src/op/SubscribeOp.h @@ -30,6 +30,21 @@ class SubscribeOp final : public Op CC_Mqtt5ErrorCode send(CC_Mqtt5SubscribeCompleteCb cb, void* cbData); CC_Mqtt5ErrorCode cancel(); + CC_Mqtt5SubscribeHandle toHandle() + { + return reinterpret_cast(this); + } + + static CC_Mqtt5SubscribeHandle asHandle(SubscribeOp* obj) + { + return reinterpret_cast(obj); + } + + static SubscribeOp* fromHandle(CC_Mqtt5SubscribeHandle handle) + { + return reinterpret_cast(handle); + } + using Base::handle; virtual void handle(SubackMsg& msg) override; diff --git a/client/lib/src/op/UnsubscribeOp.cpp b/client/lib/src/op/UnsubscribeOp.cpp index b72fc29..88e1199 100644 --- a/client/lib/src/op/UnsubscribeOp.cpp +++ b/client/lib/src/op/UnsubscribeOp.cpp @@ -298,9 +298,10 @@ void UnsubscribeOp::completeOpInternal(CC_Mqtt5AsyncOpStatus status, const CC_Mq { auto cb = m_cb; auto* cbData = m_cbData; + auto handle = toHandle(); opComplete(); // mustn't access data members after destruction if (cb != nullptr) { - cb(cbData, status, response); + cb(cbData, handle, status, response); } } diff --git a/client/lib/src/op/UnsubscribeOp.h b/client/lib/src/op/UnsubscribeOp.h index be84988..d3cc0b5 100644 --- a/client/lib/src/op/UnsubscribeOp.h +++ b/client/lib/src/op/UnsubscribeOp.h @@ -29,6 +29,21 @@ class UnsubscribeOp final : public Op CC_Mqtt5ErrorCode send(CC_Mqtt5UnsubscribeCompleteCb cb, void* cbData); CC_Mqtt5ErrorCode cancel(); + CC_Mqtt5UnsubscribeHandle toHandle() + { + return reinterpret_cast(this); + } + + static CC_Mqtt5UnsubscribeHandle asHandle(UnsubscribeOp* obj) + { + return reinterpret_cast(obj); + } + + static UnsubscribeOp* fromHandle(CC_Mqtt5UnsubscribeHandle handle) + { + return reinterpret_cast(handle); + } + using Base::handle; virtual void handle(UnsubackMsg& msg) override; diff --git a/client/lib/templ/client.cpp.templ b/client/lib/templ/client.cpp.templ index 458b9cc..4a7fc79 100644 --- a/client/lib/templ/client.cpp.templ +++ b/client/lib/templ/client.cpp.templ @@ -58,22 +58,22 @@ inline CC_Mqtt5DisconnectHandle handleFromDisconnectOp(cc_mqtt5_client::op::Disc inline cc_mqtt5_client::op::SubscribeOp* subscribeOpFromHandle(CC_Mqtt5SubscribeHandle handle) { - return reinterpret_cast(handle); + return cc_mqtt5_client::op::SubscribeOp::fromHandle(handle); } inline CC_Mqtt5SubscribeHandle handleFromSubscribeOp(cc_mqtt5_client::op::SubscribeOp* op) { - return reinterpret_cast(op); + return cc_mqtt5_client::op::SubscribeOp::asHandle(op); } inline cc_mqtt5_client::op::UnsubscribeOp* unsubscribeOpFromHandle(CC_Mqtt5UnsubscribeHandle handle) { - return reinterpret_cast(handle); + return cc_mqtt5_client::op::UnsubscribeOp::fromHandle(handle); } inline CC_Mqtt5UnsubscribeHandle handleFromUnsubscribeOp(cc_mqtt5_client::op::UnsubscribeOp* op) { - return reinterpret_cast(op); + return cc_mqtt5_client::op::UnsubscribeOp::asHandle(op); } inline cc_mqtt5_client::op::SendOp* sendOpFromHandle(CC_Mqtt5PublishHandle handle) diff --git a/client/lib/test/integration/IntegrationTestBasicPubSub.cpp b/client/lib/test/integration/IntegrationTestBasicPubSub.cpp index 8f405e3..7066c42 100644 --- a/client/lib/test/integration/IntegrationTestBasicPubSub.cpp +++ b/client/lib/test/integration/IntegrationTestBasicPubSub.cpp @@ -93,11 +93,12 @@ class IntegrationTestBasicPubSub_Client1: public IntegrationTestCommonBase ++m_opCount; } - virtual void integrationTestSubscribeCompleteImpl(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response) override + virtual void integrationTestSubscribeCompleteImpl( + CC_Mqtt5SubscribeHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response) override { assert(m_opCount > 0U); --m_opCount; - if (!integrationTestVerifySubscribeSuccessful(status, response)) { + if (!integrationTestVerifySubscribeSuccessful(handle, status, response)) { failTestInternal(); return; } @@ -105,12 +106,12 @@ class IntegrationTestBasicPubSub_Client1: public IntegrationTestCommonBase // Wait for message } - virtual void integrationTestPublishCompleteImpl(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response) override + virtual void integrationTestPublishCompleteImpl(CC_Mqtt5PublishHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response) override { assert(m_opCount > 0U); --m_opCount; - if (!integrationTestVerifyPublishSuccessful(status, response)) { + if (!integrationTestVerifyPublishSuccessful(handle, status, response)) { failTestInternal(); return; } @@ -213,12 +214,12 @@ class IntegrationTestBasicPubSub_Client2: public IntegrationTestCommonBase ++m_opCount; } - virtual void integrationTestSubscribeCompleteImpl(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response) override + virtual void integrationTestSubscribeCompleteImpl(CC_Mqtt5SubscribeHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response) override { assert(m_opCount > 0U); --m_opCount; - if (!integrationTestVerifySubscribeSuccessful(status, response)) { + if (!integrationTestVerifySubscribeSuccessful(handle, status, response)) { failTestInternal(); return; } @@ -226,12 +227,12 @@ class IntegrationTestBasicPubSub_Client2: public IntegrationTestCommonBase doNextPublish(); } - virtual void integrationTestPublishCompleteImpl(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response) override + virtual void integrationTestPublishCompleteImpl(CC_Mqtt5PublishHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response) override { assert(m_opCount > 0U); --m_opCount; - if (!integrationTestVerifyPublishSuccessful(status, response)) { + if (!integrationTestVerifyPublishSuccessful(handle, status, response)) { failTestInternal(); return; } diff --git a/client/lib/test/integration/IntegrationTestCommonBase.cpp b/client/lib/test/integration/IntegrationTestCommonBase.cpp index bdf2a61..4596367 100644 --- a/client/lib/test/integration/IntegrationTestCommonBase.cpp +++ b/client/lib/test/integration/IntegrationTestCommonBase.cpp @@ -265,7 +265,11 @@ bool IntegrationTestCommonBase::integrationTestVerifyConnectSuccessful(CC_Mqtt5A return true; } -bool IntegrationTestCommonBase::integrationTestVerifySubscribeSuccessful(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response, unsigned reasonCodesCount) +bool IntegrationTestCommonBase::integrationTestVerifySubscribeSuccessful( + [[maybe_unused]] CC_Mqtt5SubscribeHandle handle, + CC_Mqtt5AsyncOpStatus status, + const CC_Mqtt5SubscribeResponse* response, + unsigned reasonCodesCount) { if (status != CC_Mqtt5AsyncOpStatus_Complete) { integrationTestErrorLog() << "Unexpected subscribe status: " << status << std::endl; @@ -293,7 +297,10 @@ bool IntegrationTestCommonBase::integrationTestVerifySubscribeSuccessful(CC_Mqtt return true; } -bool IntegrationTestCommonBase::integrationTestVerifyPublishSuccessful(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response) +bool IntegrationTestCommonBase::integrationTestVerifyPublishSuccessful( + [[maybe_unused]] CC_Mqtt5PublishHandle handle, + CC_Mqtt5AsyncOpStatus status, + const CC_Mqtt5PublishResponse* response) { if (status != CC_Mqtt5AsyncOpStatus_Complete) { integrationTestErrorLog() << "Unexpected publish status: " << status << std::endl; @@ -402,12 +409,14 @@ void IntegrationTestCommonBase::integrationTestConnectCompleteImpl( } void IntegrationTestCommonBase::integrationTestSubscribeCompleteImpl( + [[maybe_unused]] CC_Mqtt5SubscribeHandle handle, [[maybe_unused]] CC_Mqtt5AsyncOpStatus status, [[maybe_unused]] const CC_Mqtt5SubscribeResponse* response) { } void IntegrationTestCommonBase::integrationTestPublishCompleteImpl( + [[maybe_unused]] CC_Mqtt5PublishHandle handle, [[maybe_unused]] CC_Mqtt5AsyncOpStatus status, [[maybe_unused]] const CC_Mqtt5PublishResponse* response) { @@ -442,16 +451,16 @@ void IntegrationTestCommonBase::integrationTestConnectCompleteInternal(CC_Mqtt5A integrationTestConnectCompleteImpl(status, response); } -void IntegrationTestCommonBase::integrationTestSubscribeCompleteInternal(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response) +void IntegrationTestCommonBase::integrationTestSubscribeCompleteInternal(CC_Mqtt5SubscribeHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response) { integrationTestInfoLog() << "Subscribe complete with status=" << status << std::endl; - integrationTestSubscribeCompleteImpl(status, response); + integrationTestSubscribeCompleteImpl(handle, status, response); } -void IntegrationTestCommonBase::integrationTestPublishCompleteInternal(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response) +void IntegrationTestCommonBase::integrationTestPublishCompleteInternal(CC_Mqtt5PublishHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response) { integrationTestInfoLog() << "Publish complete with status=" << status << std::endl; - integrationTestPublishCompleteImpl(status, response); + integrationTestPublishCompleteImpl(handle, status, response); } void IntegrationTestCommonBase::integrationTestTickProgramCb(void* data, unsigned ms) @@ -520,13 +529,13 @@ void IntegrationTestCommonBase::integrationTestConnectCompleteCb(void* data, CC_ asObj(data)->integrationTestConnectCompleteInternal(status, response); } -void IntegrationTestCommonBase::integrationTestSubscribeCompleteCb(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response) +void IntegrationTestCommonBase::integrationTestSubscribeCompleteCb(void* data, CC_Mqtt5SubscribeHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response) { - asObj(data)->integrationTestSubscribeCompleteInternal(status, response); + asObj(data)->integrationTestSubscribeCompleteInternal(handle, status, response); } -void IntegrationTestCommonBase::integrationTestPublishCompleteCb(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response) +void IntegrationTestCommonBase::integrationTestPublishCompleteCb(void* data, CC_Mqtt5PublishHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response) { - asObj(data)->integrationTestPublishCompleteInternal(status, response); + asObj(data)->integrationTestPublishCompleteInternal(handle, status, response); } diff --git a/client/lib/test/integration/IntegrationTestCommonBase.h b/client/lib/test/integration/IntegrationTestCommonBase.h index df2ad32..2f7002d 100644 --- a/client/lib/test/integration/IntegrationTestCommonBase.h +++ b/client/lib/test/integration/IntegrationTestCommonBase.h @@ -53,8 +53,8 @@ class IntegrationTestCommonBase bool integrationTestStartBasicDisconnect(CC_Mqtt5ReasonCode reasonCode = CC_Mqtt5ReasonCode_NormalDisconnection); bool integrationTestVerifyConnectSuccessful(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5ConnectResponse* response); - bool integrationTestVerifySubscribeSuccessful(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response, unsigned reasonCodesCount = 1U); - bool integrationTestVerifyPublishSuccessful(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response); + bool integrationTestVerifySubscribeSuccessful(CC_Mqtt5SubscribeHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response, unsigned reasonCodesCount = 1U); + bool integrationTestVerifyPublishSuccessful(CC_Mqtt5PublishHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response); const std::string& integrationTestClientId() { @@ -68,8 +68,8 @@ class IntegrationTestCommonBase virtual void integrationTestBrokerDisconnectedImpl(const CC_Mqtt5DisconnectInfo* info); virtual void integrationTestMessageReceivedImpl(const CC_Mqtt5MessageInfo* info); virtual void integrationTestConnectCompleteImpl(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5ConnectResponse* response); - virtual void integrationTestSubscribeCompleteImpl(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response); - virtual void integrationTestPublishCompleteImpl(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response); + virtual void integrationTestSubscribeCompleteImpl(CC_Mqtt5SubscribeHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response); + virtual void integrationTestPublishCompleteImpl(CC_Mqtt5PublishHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response); void stopIoPosted(); @@ -79,8 +79,8 @@ class IntegrationTestCommonBase void integrationTestBrokerDisconnectedInternal(const CC_Mqtt5DisconnectInfo* info); void integrationTestMessageReceivedInternal(const CC_Mqtt5MessageInfo* info); void integrationTestConnectCompleteInternal(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5ConnectResponse* response); - void integrationTestSubscribeCompleteInternal(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response); - void integrationTestPublishCompleteInternal(CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response); + void integrationTestSubscribeCompleteInternal(CC_Mqtt5SubscribeHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response); + void integrationTestPublishCompleteInternal(CC_Mqtt5PublishHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response); static void integrationTestTickProgramCb(void* data, unsigned ms); static unsigned integrationTestCancelTickWaitCb(void* data); @@ -88,8 +88,8 @@ class IntegrationTestCommonBase static void integrationTestBrokerDisconnectedCb(void* data, const CC_Mqtt5DisconnectInfo* info); static void integrationTestMessageReceivedCb(void* data, const CC_Mqtt5MessageInfo* info); static void integrationTestConnectCompleteCb(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5ConnectResponse* response); - static void integrationTestSubscribeCompleteCb(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response); - static void integrationTestPublishCompleteCb(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response); + static void integrationTestSubscribeCompleteCb(void* data, CC_Mqtt5SubscribeHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response); + static void integrationTestPublishCompleteCb(void* data, CC_Mqtt5PublishHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response); boost::asio::io_context& m_io; Socket m_socket; diff --git a/client/lib/test/unit/UnitTestCommonBase.cpp b/client/lib/test/unit/UnitTestCommonBase.cpp index 968238b..0187a95 100644 --- a/client/lib/test/unit/UnitTestCommonBase.cpp +++ b/client/lib/test/unit/UnitTestCommonBase.cpp @@ -1258,7 +1258,7 @@ void UnitTestCommonBase::unitTestConnectCompleteCb(void* obj, CC_Mqtt5AsyncOpSta } } -void UnitTestCommonBase::unitTestSubscribeCompleteCb(void* obj, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response) +void UnitTestCommonBase::unitTestSubscribeCompleteCb(void* obj, [[maybe_unused]] CC_Mqtt5SubscribeHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response) { auto* realObj = reinterpret_cast(obj); test_assert(realObj->m_subscribeResp.empty()); @@ -1270,7 +1270,7 @@ void UnitTestCommonBase::unitTestSubscribeCompleteCb(void* obj, CC_Mqtt5AsyncOpS } } -void UnitTestCommonBase::unitTestUnsubscribeCompleteCb(void* obj, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5UnsubscribeResponse* response) +void UnitTestCommonBase::unitTestUnsubscribeCompleteCb(void* obj, [[maybe_unused]] CC_Mqtt5UnsubscribeHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5UnsubscribeResponse* response) { auto* realObj = reinterpret_cast(obj); test_assert(realObj->m_unsubscribeResp.empty()); @@ -1282,7 +1282,7 @@ void UnitTestCommonBase::unitTestUnsubscribeCompleteCb(void* obj, CC_Mqtt5AsyncO } } -void UnitTestCommonBase::unitTestPublishCompleteCb(void* obj, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response) +void UnitTestCommonBase::unitTestPublishCompleteCb(void* obj, [[maybe_unused]] CC_Mqtt5PublishHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response) { auto* realObj = reinterpret_cast(obj); // test_assert(realObj->m_publishResp.empty()); diff --git a/client/lib/test/unit/UnitTestCommonBase.h b/client/lib/test/unit/UnitTestCommonBase.h index 9d8d2f9..a9148e0 100644 --- a/client/lib/test/unit/UnitTestCommonBase.h +++ b/client/lib/test/unit/UnitTestCommonBase.h @@ -490,9 +490,9 @@ class UnitTestCommonBase static void unitTestProgramNextTickCb(void* obj, unsigned duration); static unsigned unitTestCancelNextTickWaitCb(void* obj); static void unitTestConnectCompleteCb(void* obj, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5ConnectResponse* response); - static void unitTestSubscribeCompleteCb(void* obj, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response); - static void unitTestUnsubscribeCompleteCb(void* obj, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5UnsubscribeResponse* response); - static void unitTestPublishCompleteCb(void* obj, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response); + static void unitTestSubscribeCompleteCb(void* obj, CC_Mqtt5SubscribeHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response); + static void unitTestUnsubscribeCompleteCb(void* obj, CC_Mqtt5UnsubscribeHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5UnsubscribeResponse* response); + static void unitTestPublishCompleteCb(void* obj, CC_Mqtt5PublishHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5PublishResponse* response); static void unitTestReauthCompleteCb(void* obj, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5AuthInfo* response); static CC_Mqtt5AuthErrorCode unitTestAuthCb(void* obj, const CC_Mqtt5AuthInfo* authInfoIn, CC_Mqtt5AuthInfo* authInfoOut); From 79534fbee1af7dd2147c0d3c217b24c6279436d8 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 22 Dec 2023 08:47:46 +1000 Subject: [PATCH 07/35] Saving work on fuzzing. --- .github/workflows/actions_build.yml | 4 +- CMakeLists.txt | 3 +- client/afl_fuzz/CMakeLists.txt | 2 +- client/afl_fuzz/main.cpp.templ | 112 +++++++++++++++++++++++++--- client/lib/src/ClientImpl.cpp | 6 +- client/lib/src/op/ConnectOp.cpp | 1 + cmake/Compile.cmake | 7 +- script/full_build.sh | 2 +- 8 files changed, 116 insertions(+), 21 deletions(-) diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index 2f2df99..f263935 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -50,7 +50,7 @@ jobs: run: | cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{matrix.type}} -DCMAKE_INSTALL_PREFIX=install \ -DCMAKE_PREFIX_PATH=${{runner.workspace}}/build/install -DCMAKE_CXX_STANDARD=${{matrix.cpp}} \ - -DCC_MQTT5_WITH_SANITIZERS=ON -DCC_MQTT5_BUILD_UNIT_TESTS=ON -DCC_MQTT5_BUILD_INTEGRATION_TESTS=ON \ + -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 env: CC: gcc-${{matrix.cc_ver}} @@ -118,7 +118,7 @@ jobs: run: | cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{matrix.type}} -DCMAKE_INSTALL_PREFIX=install \ -DCMAKE_PREFIX_PATH=${{runner.workspace}}/build/install -DCMAKE_CXX_STANDARD=${{matrix.cpp}} \ - -DCC_MQTT5_WITH_SANITIZERS=ON -DCC_MQTT5_BUILD_UNIT_TESTS=ON -DCC_MQTT5_BUILD_INTEGRATION_TESTS=ON \ + -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 env: CC: clang-${{matrix.cc_ver}} diff --git a/CMakeLists.txt b/CMakeLists.txt index d4244b6..a4db4e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,8 @@ option (CC_MQTT5_WARN_AS_ERR "Treat warning as error" ON) option (CC_MQTT5_USE_CCACHE "Use ccache on unix system" ON) option (CC_MQTT5_BUILD_UNIT_TESTS "Build unit tests" OFF) option (CC_MQTT5_BUILD_INTEGRATION_TESTS "Build integration tests which require MQTT broker on local port 1883." OFF) -option (CC_MQTT5_WITH_SANITIZERS "Build with sanitizers" OFF) +option (CC_MQTT5_WITH_DEFAULT_SANITIZERS "Build with sanitizers" OFF) +option (CC_MQTT5_DISABLE_FALSE_POSITIVE_SANITIZERS "Disable sanitizers known as false positives" ${CC_MQTT5_WITH_DEFAULT_SANITIZERS}) # Extra Configuration Variables # CC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES - List of custom client configuration files diff --git a/client/afl_fuzz/CMakeLists.txt b/client/afl_fuzz/CMakeLists.txt index f69da71..b75391b 100644 --- a/client/afl_fuzz/CMakeLists.txt +++ b/client/afl_fuzz/CMakeLists.txt @@ -55,7 +55,7 @@ function (gen_mqtt5_client_afl_fuzz config_file) ) add_executable(${app_name} ${src}) - target_link_libraries(${app_name} PRIVATE cc::${lib_name}) + target_link_libraries(${app_name} PRIVATE cc::${lib_name} cc::cc_mqtt5 cc::comms) add_dependencies(${app_name} ${src_tgt_name}) diff --git a/client/afl_fuzz/main.cpp.templ b/client/afl_fuzz/main.cpp.templ index 4f6488c..f2ca124 100644 --- a/client/afl_fuzz/main.cpp.templ +++ b/client/afl_fuzz/main.cpp.templ @@ -2,15 +2,20 @@ #include #include #include +#include #include #include #include +#include #include #include #include "##NAME##client.h" +#include "cc_mqtt5/Message.h" +#include "cc_mqtt5/frame/Frame.h" + namespace { @@ -30,6 +35,7 @@ using AflFuzzClientPtr = std::unique_ptr; struct State { + std::ostream* m_log = nullptr; CC_Mqtt5ClientHandle m_client = nullptr; std::vector m_inData; unsigned m_nextTickDuration = 0U; @@ -39,6 +45,7 @@ struct State bool m_subscribeRequired = true; bool m_subscribed = false; bool m_firstConnect = true; + bool m_disconnected = false; }; State* asState(void* data) @@ -46,6 +53,18 @@ State* asState(void* data) return reinterpret_cast(data); } +std::ostream& infoLog(State* state) +{ + *(state->m_log) << "INFO: "; + return *(state->m_log); +} + +std::ostream& errorLog(State* state) +{ + *(state->m_log) << "ERROR: "; + return *(state->m_log); +} + void nextTickProgramCb(void* data, unsigned duration) { asState(data)->m_nextTickDuration = duration; @@ -60,12 +79,15 @@ unsigned cancelNextTickWaitCb(void* data) void sendOutputDataCb([[maybe_unused]] void* data, [[maybe_unused]] const unsigned char* buf, unsigned bufLen) { - std::cout << "Sending " << bufLen << " bytes." << std::endl; + auto* state = asState(data); + infoLog(state) << "Sending " << bufLen << " bytes." << std::endl; } void brokerDisconnectReportCb(void* data, [[maybe_unused]] const CC_Mqtt5DisconnectInfo* info) { auto* state = asState(data); + infoLog(state) << "Broker disconnected" << std::endl; + state->m_disconnected = true; state->m_reinitRequired = true; state->m_connectRequired = true; state->m_connected = false; @@ -73,12 +95,14 @@ void brokerDisconnectReportCb(void* data, [[maybe_unused]] const CC_Mqtt5Disconn void messageReceivedReportCb([[maybe_unused]] void* data, [[maybe_unused]] const CC_Mqtt5MessageInfo* info) { - std::cout << "Message Received!!!\n" << std::endl; + auto* state = asState(data); + infoLog(state) << "Message Received!!!\n" << std::endl; } void errorLogCb([[maybe_unused]] void* data, const char* msg) { - std::cerr << "ERROR: " << msg << std::endl; + auto* state = asState(data); + errorLog(state) << msg << std::endl; } void connectCompleteCb(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5ConnectResponse* response) @@ -91,18 +115,22 @@ void connectCompleteCb(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5C } assert(response != nullptr); - if (CC_Mqtt5ReasonCode_UnspecifiedError <= response->m_reasonCode) { + if (CC_Mqtt5ReasonCode_Success != response->m_reasonCode) { break; } + assert(cc_mqtt5_##NAME##client_is_connected(state->m_client)); + if (!response->m_sessionPresent) { state->m_subscribeRequired = false; state->m_subscribed = false; } + infoLog(state) << "Connected" << std::endl; state->m_connectRequired = false; state->m_connected = true; state->m_firstConnect = false; + return; } while (false); state->m_connectRequired = true; @@ -132,7 +160,11 @@ void subscribeCompleteCb(void* data, [[maybe_unused]] CC_Mqtt5SubscribeHandle ha break; } + infoLog(state) << "Subscribed" << std::endl; state->m_subscribed = true; + + assert(false); // TODO: remove + return; } while (false); state->m_subscribeRequired = true; @@ -144,31 +176,38 @@ void subscribeCompleteCb(void* data, [[maybe_unused]] CC_Mqtt5SubscribeHandle ha int main([[maybe_unused]] int argc, [[maybe_unused]] const char* argv[]) { + std::ofstream log("/tmp/afl_fuzz.log", std::ios_base::app); + if (!log) { + std::cerr << "Failed to open log file" << std::endl; + return -1; + } + AflFuzzClientPtr client(cc_mqtt5_##NAME##client_alloc()); State state; state.m_client = client.get(); + state.m_log = &log; cc_mqtt5_##NAME##client_set_next_tick_program_callback(client.get(), &nextTickProgramCb, &state); cc_mqtt5_##NAME##client_set_cancel_next_tick_wait_callback(client.get(), &cancelNextTickWaitCb, &state); cc_mqtt5_##NAME##client_set_send_output_data_callback(client.get(), &sendOutputDataCb, &state); cc_mqtt5_##NAME##client_set_broker_disconnect_report_callback(client.get(), &brokerDisconnectReportCb, &state); cc_mqtt5_##NAME##client_set_message_received_report_callback(client.get(), &messageReceivedReportCb, &state); - cc_mqtt5_##NAME##client_set_error_log_callback(client.get(), &errorLogCb, nullptr); + cc_mqtt5_##NAME##client_set_error_log_callback(client.get(), &errorLogCb, &state); sync(); CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_Success; std::uint8_t buf[BufSize] = {0}; for (auto i = 0U; i < IterCount; ++i) { + infoLog(&state) << "Iteration " << i << std::endl; if (state.m_reinitRequired) { state.m_reinitRequired = false; + state.m_disconnected = false; ec = cc_mqtt5_##NAME##client_init(client.get()); assert(ec == CC_Mqtt5ErrorCode_Success); if (ec != CC_Mqtt5ErrorCode_Success) { exit(-1); } - - state.m_inData.clear(); } assert(cc_mqtt5_##NAME##client_is_initialized(client.get())); @@ -176,6 +215,8 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] const char* argv[]) do { if (state.m_connectRequired) { state.m_connected = false; + + infoLog(&state) << "Connecting..." << std::endl; auto connect = cc_mqtt5_##NAME##client_connect_prepare(client.get(), &ec); assert(connect != nullptr); @@ -186,6 +227,7 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] const char* argv[]) CC_Mqtt5ConnectBasicConfig basicConfig; cc_mqtt5_##NAME##client_connect_init_config_basic(&basicConfig); + basicConfig.m_clientId = "some_id"; basicConfig.m_cleanStart = state.m_firstConnect; ec = cc_mqtt5_##NAME##client_connect_config_basic(connect, &basicConfig); @@ -206,11 +248,19 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] const char* argv[]) break; } - assert(cc_mqtt5_##NAME##client_is_connected(client.get())); + if (!cc_mqtt5_##NAME##client_is_connected(client.get())) { + infoLog(&state) << "Not connected yet..." << std::endl; + assert(!state.m_connected); + break; + } + + assert(state.m_connected); if (state.m_subscribeRequired) { state.m_subscribed = false; + infoLog(&state) << "Subscribing..." << std::endl; + auto subscribe = cc_mqtt5_##NAME##client_subscribe_prepare(client.get(), &ec); assert(subscribe != nullptr); assert(ec == CC_Mqtt5ErrorCode_Success); @@ -241,9 +291,9 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] const char* argv[]) } while (false); auto len = read(0, buf, BufSize); - if (len <= 0) { - break; - } + bool noMoreRead = (len <= 0); + len = std::max(len, static_cast(0)); + infoLog(&state) << "Read " << len << " bytes" << std::endl; auto dataPtr = &buf[0]; auto dataLen = static_cast(len); @@ -259,7 +309,42 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] const char* argv[]) dataLen = state.m_inData.size(); } - auto consumed = cc_mqtt5_##NAME##client_process_data(client.get(), dataPtr, static_cast(dataLen)); + using Interface = cc_mqtt5::Message<>; + using IdAndFlagsField = cc_mqtt5::frame::Frame::Layer_idAndFlags::Field; + using SizeField = cc_mqtt5::frame::Frame::Layer_size::Field; + + static constexpr auto MinLen = IdAndFlagsField::minLength() + SizeField::minLength(); + if (dataLen < MinLen) { + if (noMoreRead) { + infoLog(&state) << "Insufficient data in buffer: " << dataLen << std::endl; + break; + } + + continue; + } + + auto iter = dataPtr; + std::advance(iter, IdAndFlagsField::minLength()); + + auto processLen = dataLen; + SizeField sizeField; + auto es = sizeField.read(iter, dataLen - IdAndFlagsField::minLength()); + if (es == comms::ErrorStatus::Success) { + auto msgLen = IdAndFlagsField::minLength() + sizeField.length() + sizeField.getValue(); + processLen = std::min(dataLen, msgLen); + } + + infoLog(&state) << "Processing " << processLen << " out of " << dataLen << " bytes..." << std::endl; + auto consumed = cc_mqtt5_##NAME##client_process_data(client.get(), dataPtr, static_cast(processLen)); + infoLog(&state) << "Consumed: " << consumed << std::endl; + + assert((consumed > 0U) || (!state.m_disconnected)); + + if ((consumed == 0U) && noMoreRead) { + infoLog(&state) << "No data consumed and no more input" << std::endl; + break; + } + if (dataLen <= consumed) { state.m_inData.clear(); continue; @@ -267,11 +352,14 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] const char* argv[]) if (state.m_inData.empty()) { state.m_inData.assign(dataPtr + consumed, dataPtr + dataLen); + infoLog(&state) << "Keeping " << state.m_inData.size() << " bytes for the next iteration" << std::endl; continue; } state.m_inData.erase(state.m_inData.begin(), state.m_inData.begin() + consumed); + infoLog(&state) << "Keeping " << state.m_inData.size() << " bytes for the next iteration" << std::endl; } + infoLog(&state) << "------------------------------------------------" << std::endl; return 0U; } \ No newline at end of file diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index a02f45d..68bbb64 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -140,7 +140,7 @@ unsigned ClientImpl::processData(const std::uint8_t* iter, unsigned len) } if (es != comms::ErrorStatus::Success) { - return consumed; // Disconnect + return len; // Disconnect } if (m_sessionState.m_maxRecvPacketSize > 0U) { @@ -151,7 +151,7 @@ unsigned ClientImpl::processData(const std::uint8_t* iter, unsigned len) if (maxAllowedSize < sizeField.value()) { errorLog("The message length exceeded max packet size"); disconnectReason = DisconnectMsg::Field_reasonCode::Field::ValueType::PacketTooLarge; - return consumed; + return len; } } @@ -164,7 +164,7 @@ unsigned ClientImpl::processData(const std::uint8_t* iter, unsigned len) if (es != comms::ErrorStatus::Success) { errorLog("Unexpected error in framing / payload parsing"); - return consumed; + return len; } COMMS_ASSERT(msg); diff --git a/client/lib/src/op/ConnectOp.cpp b/client/lib/src/op/ConnectOp.cpp index 2978ea0..ae3e1c0 100644 --- a/client/lib/src/op/ConnectOp.cpp +++ b/client/lib/src/op/ConnectOp.cpp @@ -545,6 +545,7 @@ void ConnectOp::handle(ConnackMsg& msg) if (response.m_sessionPresent && m_connectMsg.field_flags().field_low().getBitValue_cleanStart()) { errorLog("Session present when clean session is requested"); + sendDisconnectWithReason(DisconnectReason::ProtocolError); return; } diff --git a/cmake/Compile.cmake b/cmake/Compile.cmake index 326f618..0fe4c1b 100644 --- a/cmake/Compile.cmake +++ b/cmake/Compile.cmake @@ -8,7 +8,7 @@ macro (cc_mqttsn_compile) list (APPEND compile_opts WARN_AS_ERR) endif () - if (CC_MQTT5_WITH_SANITIZERS) + if (CC_MQTT5_WITH_DEFAULT_SANITIZERS) list (APPEND compile_opts DEFAULT_SANITIZERS) endif () @@ -41,6 +41,11 @@ macro (cc_mqttsn_compile) ) endif() + if (CC_MQTT5_DISABLE_FALSE_POSITIVE_SANITIZERS) + list (APPEND extra_flags_list + -fno-sanitize-address-use-after-scope) + endif () + string(REPLACE ";" " " extra_flags "${extra_flags_list}") set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${extra_flags}") endmacro() \ No newline at end of file diff --git a/script/full_build.sh b/script/full_build.sh index 50415ca..2bee77d 100755 --- a/script/full_build.sh +++ b/script/full_build.sh @@ -23,7 +23,7 @@ ${SCRIPT_DIR}/prepare_externals.sh cd ${BUILD_DIR} cmake .. -DCMAKE_INSTALL_PREFIX=${COMMON_INSTALL_DIR} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \ - -DCC_MQTT5_WITH_SANITIZERS=ON -DCC_MQTT5_BUILD_UNIT_TESTS=ON -DCC_MQTT5_BUILD_INTEGRATION_TESTS=ON \ + -DCC_MQTT5_WITH_DEFAULT_SANITIZERS=ON -DCC_MQTT5_BUILD_UNIT_TESTS=ON -DCC_MQTT5_BUILD_INTEGRATION_TESTS=ON \ -DCC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES="${ROOT_DIR}/client/lib/script/BareMetalConfig.cmake" "$@" procs=$(nproc) From d708c46bafae3fcf9bbf4c8f0deb8bd4c75f9a21 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sun, 24 Dec 2023 15:07:31 +1000 Subject: [PATCH 08/35] Saving work on fuzzing. --- client/afl_fuzz/main.cpp.templ | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/client/afl_fuzz/main.cpp.templ b/client/afl_fuzz/main.cpp.templ index f2ca124..c75fe13 100644 --- a/client/afl_fuzz/main.cpp.templ +++ b/client/afl_fuzz/main.cpp.templ @@ -80,7 +80,10 @@ unsigned cancelNextTickWaitCb(void* data) void sendOutputDataCb([[maybe_unused]] void* data, [[maybe_unused]] const unsigned char* buf, unsigned bufLen) { auto* state = asState(data); - infoLog(state) << "Sending " << bufLen << " bytes." << std::endl; + auto& out = infoLog(state); + out << "Sending " << bufLen << " bytes: " << std::hex; + std::copy_n(buf, bufLen, std::ostream_iterator(out, " ")); + out << std::dec << std::endl; } void brokerDisconnectReportCb(void* data, [[maybe_unused]] const CC_Mqtt5DisconnectInfo* info) @@ -121,7 +124,7 @@ void connectCompleteCb(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5C assert(cc_mqtt5_##NAME##client_is_connected(state->m_client)); - if (!response->m_sessionPresent) { + if (response->m_sessionPresent) { state->m_subscribeRequired = false; state->m_subscribed = false; } @@ -163,7 +166,6 @@ void subscribeCompleteCb(void* data, [[maybe_unused]] CC_Mqtt5SubscribeHandle ha infoLog(state) << "Subscribed" << std::endl; state->m_subscribed = true; - assert(false); // TODO: remove return; } while (false); @@ -293,7 +295,10 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] const char* argv[]) auto len = read(0, buf, BufSize); bool noMoreRead = (len <= 0); len = std::max(len, static_cast(0)); - infoLog(&state) << "Read " << len << " bytes" << std::endl; + auto& out = infoLog(&state); + out << "Read " << len << " bytes: " << std::hex; + std::copy_n(buf, len, std::ostream_iterator(out, " ")); + out << std::dec << std::endl; auto dataPtr = &buf[0]; auto dataLen = static_cast(len); From 0885e1d911689a69d2f26d9385c708b2570888c2 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Mon, 25 Dec 2023 15:56:44 +1000 Subject: [PATCH 09/35] Refactoring fuzzing application. --- client/afl_fuzz/AflFuzz.cpp.templ | 639 +++++++++++++++++++++++++++++ client/afl_fuzz/AflFuzz.h | 31 ++ client/afl_fuzz/CMakeLists.txt | 12 +- client/afl_fuzz/Logger.cpp | 53 +++ client/afl_fuzz/Logger.h | 33 ++ client/afl_fuzz/ProgramOptions.cpp | 161 ++++++++ client/afl_fuzz/ProgramOptions.h | 57 +++ client/afl_fuzz/main.cpp | 30 ++ client/afl_fuzz/main.cpp.templ | 370 ----------------- 9 files changed, 1013 insertions(+), 373 deletions(-) create mode 100644 client/afl_fuzz/AflFuzz.cpp.templ create mode 100644 client/afl_fuzz/AflFuzz.h create mode 100644 client/afl_fuzz/Logger.cpp create mode 100644 client/afl_fuzz/Logger.h create mode 100644 client/afl_fuzz/ProgramOptions.cpp create mode 100644 client/afl_fuzz/ProgramOptions.h create mode 100644 client/afl_fuzz/main.cpp delete mode 100644 client/afl_fuzz/main.cpp.templ diff --git a/client/afl_fuzz/AflFuzz.cpp.templ b/client/afl_fuzz/AflFuzz.cpp.templ new file mode 100644 index 0000000..a2b014d --- /dev/null +++ b/client/afl_fuzz/AflFuzz.cpp.templ @@ -0,0 +1,639 @@ +// +// Copyright 2023 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "AflFuzz.h" + +#include "##NAME##client.h" + +#include "cc_mqtt5/Message.h" +#include "cc_mqtt5/frame/Frame.h" + +#include + +#include +#include +#include + +namespace cc_mqtt5_client_afl_fuzz +{ + +namespace +{ + +const std::size_t BufSize = 1024000; + +} // namespace + + +class AflFuzzImpl +{ +public: + explicit AflFuzzImpl(const ProgramOptions& opts, Logger& logger) : + m_opts(opts), + m_logger(logger), + m_client(cc_mqtt5_##NAME##client_alloc()), + m_verbose(opts.verbose()) + { + } + + bool init() + { + auto subTopics = m_opts.subTopics(); + std::transform( + subTopics.begin(), subTopics.end(), std::back_inserter(m_state.m_subs), + [](auto& topicsList) + { + SubscribeInfo info; + info.m_topics = topicsList; + return info; + }); + + cc_mqtt5_##NAME##client_set_next_tick_program_callback(m_client.get(), &AflFuzzImpl::nextTickProgramCb, this); + cc_mqtt5_##NAME##client_set_cancel_next_tick_wait_callback(m_client.get(), &AflFuzzImpl::cancelNextTickWaitCb, this); + cc_mqtt5_##NAME##client_set_send_output_data_callback(m_client.get(), &AflFuzzImpl::sendOutputDataCb, this); + cc_mqtt5_##NAME##client_set_broker_disconnect_report_callback(m_client.get(), &AflFuzzImpl::brokerDisconnectReportCb, this); + cc_mqtt5_##NAME##client_set_message_received_report_callback(m_client.get(), &AflFuzzImpl::messageReceivedReportCb, this); + cc_mqtt5_##NAME##client_set_error_log_callback(m_client.get(), &AflFuzzImpl::errorLogCb, this); + return true; + } + + void run() + { + sync(); + + std::uint8_t buf[BufSize] = {0}; + while (true) { + m_logger.flush(); + + if (m_state.m_reinitRequired) { + doReinit(); + } + + do { + if (m_state.m_connectRequired) { + doConnect(); + break; + } + + if (!cc_mqtt5_##NAME##client_is_connected(m_client.get())) { + infoLog() << "Not connected yet..." << std::endl; + assert(!m_state.m_connected); + break; + } + + assert(m_state.m_connected); + + doSubscribeIfNeeded(); + doReauthIfNeeded(); + + } while (false); + + + auto len = read(0, buf, BufSize); + bool noMoreRead = (len <= 0); + len = std::max(len, static_cast(0)); + infoLog() << "Read " << len << " bytes\n"; + + if (m_verbose) { + auto& out = debugLog(); + out << "Read bytes: " << std::hex; + std::copy_n(buf, len, std::ostream_iterator(out, " ")); + out << std::dec << std::endl; + } + + auto dataPtr = &buf[0]; + auto dataLen = static_cast(len); + if (!m_inData.empty()) { + m_inData.insert(m_inData.end(), dataPtr, dataPtr + dataLen); + static const auto MaxLen = std::numeric_limits::max(); + + if (MaxLen < m_inData.size()) { + m_inData.erase(m_inData.begin(), m_inData.begin() + (m_inData.size() - MaxLen)); + } + + dataPtr = m_inData.data(); + dataLen = m_inData.size(); + } + + using Interface = cc_mqtt5::Message<>; + using IdAndFlagsField = cc_mqtt5::frame::Frame::Layer_idAndFlags::Field; + using SizeField = cc_mqtt5::frame::Frame::Layer_size::Field; + + static constexpr auto MinLen = IdAndFlagsField::minLength() + SizeField::minLength(); + if (dataLen < MinLen) { + if (noMoreRead) { + infoLog() << "Insufficient data in buffer: " << dataLen << std::endl; + break; + } + + continue; + } + + auto iter = dataPtr; + std::advance(iter, IdAndFlagsField::minLength()); + + auto processLen = dataLen; + SizeField sizeField; + auto es = sizeField.read(iter, dataLen - IdAndFlagsField::minLength()); + if (es == comms::ErrorStatus::Success) { + auto msgLen = IdAndFlagsField::minLength() + sizeField.length() + sizeField.getValue(); + processLen = std::min(dataLen, msgLen); + } + + infoLog() << "Processing " << processLen << " out of " << dataLen << " bytes...\n"; + auto consumed = cc_mqtt5_##NAME##client_process_data(m_client.get(), dataPtr, static_cast(processLen)); + infoLog() << "Consumed: " << consumed << '\n'; + + assert((consumed > 0U) || (!m_state.m_disconnected)); + + if ((consumed == 0U) && noMoreRead) { + infoLog() << "No data consumed and no more input\n"; + break; + } + + if (dataLen <= consumed) { + m_inData.clear(); + continue; + } + + if (m_inData.empty()) { + m_inData.assign(dataPtr + consumed, dataPtr + dataLen); + infoLog() << "Keeping " << m_inData.size() << " bytes for the next iteration\n"; + continue; + } + + m_inData.erase(m_inData.begin(), m_inData.begin() + consumed); + infoLog() << "Keeping " << m_inData.size() << " bytes for the next iteration\n"; + } + + infoLog() << "------------------------------------------------" << std::endl; + } + +private: + struct ClientDeleter + { + void operator()(CC_Mqtt5Client* ptr) + { + cc_mqtt5_##NAME##client_free(ptr); + } + }; + + using ClientPtr = std::unique_ptr; + + struct SubscribeInfo + { + std::vector m_topics; + CC_Mqtt5SubscribeHandle m_handle = nullptr; + bool m_acked = false; + }; + + struct State + { + std::vector m_subs; + unsigned m_nextTickDuration = 0U; + bool m_reinitRequired = true; + bool m_connectRequired = true; + bool m_connected = false; + bool m_firstConnect = true; + bool m_disconnected = false; + bool m_reauthRequired = false; + bool m_reauthComplete = false; + }; + + std::ostream& debugLog() + { + return m_logger.infoLog(); + } + + std::ostream& infoLog() + { + return m_logger.infoLog(); + } + + std::ostream& errorLog() + { + return m_logger.infoLog(); + } + + void doReinit() + { + infoLog() << "(Re)Initializing..." << std::endl; + m_state.m_reinitRequired = false; + m_state.m_disconnected = false; + m_state.m_reauthRequired = !m_opts.authMethod().empty(); + + auto ec = cc_mqtt5_##NAME##client_init(m_client.get()); + assert(ec == CC_Mqtt5ErrorCode_Success); + if (ec != CC_Mqtt5ErrorCode_Success) { + exit(-1); + } + } + + void doConnect() + { + m_state.m_connected = false; + + infoLog() << "Connecting..." << std::endl; + CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_ValuesLimit; + auto connect = cc_mqtt5_##NAME##client_connect_prepare(m_client.get(), &ec); + assert(connect != nullptr); + assert(ec == CC_Mqtt5ErrorCode_Success); + if (connect == nullptr) { + errorLog() << "Unexpected failure in connect allocation\n"; + exit(-1); + } + + CC_Mqtt5ConnectBasicConfig basicConfig; + cc_mqtt5_##NAME##client_connect_init_config_basic(&basicConfig); + basicConfig.m_cleanStart = m_state.m_firstConnect; + + auto clientId = m_opts.clientId(); + if (!clientId.empty()) { + basicConfig.m_clientId = clientId.c_str(); + } + + ec = cc_mqtt5_##NAME##client_connect_config_basic(connect, &basicConfig); + assert(ec == CC_Mqtt5ErrorCode_Success); + if (ec != CC_Mqtt5ErrorCode_Success) { + errorLog() << "Unexpected failure in connect basic configuration\n"; + exit(-1); + } + + CC_Mqtt5ConnectExtraConfig extraConfig; + ::cc_mqtt5_##NAME##client_connect_init_config_extra(&extraConfig); + extraConfig.m_receiveMaximum = m_opts.receiveMax(); + extraConfig.m_maxPacketSize = m_opts.maxPacketSize(); + extraConfig.m_topicAliasMaximum = m_opts.topicAliasMax(); + extraConfig.m_requestResponseInfo = m_opts.reqResponseInfo(); + extraConfig.m_requestProblemInfo = m_opts.reqProblemInfo(); + + ec = ::cc_mqtt5_##NAME##client_connect_config_extra(connect, &extraConfig); + if (ec != CC_Mqtt5ErrorCode_Success) { + errorLog() << "Unexpected failure in connect extra configuration\n"; + exit(-1); + } + + auto authMethod = m_opts.authMethod(); + if (!authMethod.empty()) { + CC_Mqtt5AuthConfig authConfig; + ::cc_mqtt5_##NAME##client_connect_init_config_auth(&authConfig); + authConfig.m_authMethod = authMethod.c_str(); + authConfig.m_authData = reinterpret_cast(authMethod.data()); + authConfig.m_authDataLen = static_cast(authMethod.size()); + authConfig.m_authCb = &AflFuzzImpl::authCb; + authConfig.m_authCbData = this; + + ec = ::cc_mqtt5_##NAME##client_connect_config_auth(connect, &authConfig); + if (ec != CC_Mqtt5ErrorCode_Success) { + errorLog() << "Unexpected failure in connect auth configuration\n"; + exit(-1); + } + } + + ec = cc_mqtt5_##NAME##client_connect_send(connect, &AflFuzzImpl::connectCompleteCb, this); + assert(ec == CC_Mqtt5ErrorCode_Success); + if (ec != CC_Mqtt5ErrorCode_Success) { + errorLog() << "Unexpected failure in sending connect request\n"; + exit(-1); + } + + m_state.m_connectRequired = false; + } + + void doSubscribeIfNeeded() { + for (auto& subInfo : m_state.m_subs) { + if (subInfo.m_handle != nullptr) { + // Request is sent + continue; + } + + assert(!subInfo.m_acked); + + CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_ValuesLimit; + auto subscribe = cc_mqtt5_##NAME##client_subscribe_prepare(m_client.get(), &ec); + assert(subscribe != nullptr); + assert(ec == CC_Mqtt5ErrorCode_Success); + if (subscribe == nullptr) { + errorLog() << "Unexpected failure in subscribe allocation\n"; + exit(-1); + } + + for (auto& t : subInfo.m_topics) { + assert(!t.empty()); + CC_Mqtt5SubscribeTopicConfig config; + cc_mqtt5_##NAME##client_subscribe_init_config_topic(&config); + config.m_topic = t.c_str(); + + while (true) { + ec = cc_mqtt5_##NAME##client_subscribe_config_topic(subscribe, &config); + assert(ec == CC_Mqtt5ErrorCode_Success); + if (ec != CC_Mqtt5ErrorCode_Success) { + if (config.m_maxQos > CC_Mqtt5QoS_AtMostOnceDelivery) { + config.m_maxQos = + static_cast( + static_cast(config.m_maxQos) - 1U); + continue; + } + + errorLog() << "Unexpected failure in topic subscribe configuration\n"; + exit(-1); + } + + break; + } + } + + ec = cc_mqtt5_##NAME##client_subscribe_send(subscribe, &AflFuzzImpl::subscribeCompleteCb, this); + assert(ec == CC_Mqtt5ErrorCode_Success); + if (ec != CC_Mqtt5ErrorCode_Success) { + errorLog() << "Unexpected failure in sending subscribe request"; + exit(-1); + } + } + } + + void doReauthIfNeeded() + { + if ((m_state.m_reauthComplete) || (!m_state.m_reauthRequired)) { + return; + } + + CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_ValuesLimit; + auto reauth = cc_mqtt5_##NAME##client_reauth_prepare(m_client.get(), &ec); + assert(reauth != nullptr); + assert(ec == CC_Mqtt5ErrorCode_Success); + if (reauth == nullptr) { + errorLog() << "Unexpected failure in reauth allocation\n"; + exit(-1); + } + + auto authMethod = m_opts.authMethod(); + assert(!authMethod.empty()); + CC_Mqtt5AuthConfig authConfig; + ::cc_mqtt5_##NAME##client_reauth_init_config_auth(&authConfig); + authConfig.m_authMethod = authMethod.c_str(); + authConfig.m_authData = reinterpret_cast(authMethod.data()); + authConfig.m_authDataLen = static_cast(authMethod.size()); + authConfig.m_authCb = &AflFuzzImpl::authCb; + authConfig.m_authCbData = this; + + ec = ::cc_mqtt5_##NAME##client_reauth_config_auth(reauth, &authConfig); + if (ec != CC_Mqtt5ErrorCode_Success) { + errorLog() << "Unexpected failure in reauth configuration\n"; + exit(-1); + } + + ec = ::cc_mqtt5_##NAME##client_reauth_send(reauth, &AflFuzzImpl::reauthCompleteCb, this); + if (ec != CC_Mqtt5ErrorCode_Success) { + errorLog() << "Unexpected failure in sending reauth\n"; + exit(-1); + } + + m_state.m_reauthRequired = false; + } + + static AflFuzzImpl* asThis(void* data) + { + return reinterpret_cast(data); + } + + static void nextTickProgramCb(void* data, unsigned duration) + { + asThis(data)->m_state.m_nextTickDuration = duration; + } + + static unsigned cancelNextTickWaitCb(void* data) + { + auto diff = std::min(asThis(data)->m_state.m_nextTickDuration, 1U); + asThis(data)->m_state.m_nextTickDuration -= diff; + return diff; + } + + static void sendOutputDataCb(void* data, const unsigned char* buf, unsigned bufLen) + { + auto& logger = asThis(data)->m_logger; + logger.infoLog() << "Sending " << bufLen << " bytes\n"; + if (asThis(data)->m_verbose) { + auto& out = logger.debugLog(); + out << "Sent bytes: " << std::hex; + std::copy_n(buf, bufLen, std::ostream_iterator(out, " ")); + out << std::dec << '\n'; + } + } + + static void brokerDisconnectReportCb(void* data, [[maybe_unused]] const CC_Mqtt5DisconnectInfo* info) + { + asThis(data)->m_logger.infoLog() << "Broker disconnected\n"; + auto& state = asThis(data)->m_state; + state.m_disconnected = true; + state.m_reinitRequired = true; + state.m_connectRequired = true; + state.m_connected = false; + } + + static void messageReceivedReportCb(void* data, const CC_Mqtt5MessageInfo* info) + { + auto* thisPtr = asThis(data); + thisPtr->m_logger.infoLog() << "Message Received!!!\n"; + + assert(info != nullptr); + + CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_ValuesLimit; + auto* publish = ::cc_mqtt5_##NAME##client_publish_prepare(thisPtr->m_client.get(), &ec); + assert(ec == CC_Mqtt5ErrorCode_Success); + if (publish == nullptr) { + errorLog() << "Unexpected failure in publish allocation\n"; + return; + } + + CC_Mqtt5PublishBasicConfig basicConfig; + ::cc_mqtt5_##NAME##client_publish_init_config_basic(&basicConfig); + basicConfig.m_topic = info->m_topic; + basicConfig.m_data = info->m_data; + basicConfig.m_dataLen = info->m_dataLen; + basicConfig.m_qos = info->m_qos; + basicConfig.m_retain = info->m_retained; + + ec = cc_mqtt5_##NAME##client_publish_config_basic(publish, &basicConfig); + if (ec != CC_Mqtt5ErrorCode_Success) { + errorLog() << "Unexpected failure in publish configuration\n"; + ::cc_mqtt5_##NAME##client_publish_cancel(publish); + return; + } + + CC_Mqtt5PublishExtraConfig extraConfig; + cc_mqtt5_##NAME##client_publish_init_config_extra(&extraConfig); + extraConfig.m_contentType = info->m_contentType; + extraConfig.m_responseTopic = info->m_responseTopic; + extraConfig.m_correlationData = info->m_correlationData; + extraConfig.m_correlationDataLen = info->m_correlationDataLen; + extraConfig.m_messageExpiryInterval = info->m_messageExpiryInterval; + extraConfig.m_format = info->m_format; + + ec = cc_mqtt5_##NAME##client_publish_config_extra(publish, &extraConfig); + if (ec != CC_Mqtt5ErrorCode_Success) { + errorLog() << "Failure in publish extra configuration\n"; + } + + for (auto idx = 0U; idx < info->m_userPropsCount; ++idx) { + auto& inProp = info->m_userProps[idx]; + CC_Mqtt5UserProp outProp; + outProp.m_key = inProp.m_key; + outProp.m_value = inProp.m_value; + + ec = cc_mqtt5_##NAME##client_publish_add_user_prop(publish, &outProp); + if (ec != CC_Mqtt5ErrorCode_Success) { + errorLog() << "Failure to add publish user property\n"; + } + } + + ec = cc_mqtt5_##NAME##client_publish_send(publish, nullptr, nullptr); + if (ec != CC_Mqtt5ErrorCode_Success) { + errorLog() << "Failure to publish message\n"; + } + } + + static void errorLogCb(void* data, const char* msg) + { + asThis(data)->m_logger.errorLog() << msg << '\n'; + } + + static void connectCompleteCb(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5ConnectResponse* response) + { + auto* thisPtr = asThis(data); + auto& state = thisPtr->m_state; + + do { + if (status != CC_Mqtt5AsyncOpStatus_Complete) { + break; + } + + assert(response != nullptr); + if (CC_Mqtt5ReasonCode_Success != response->m_reasonCode) { + break; + } + + thisPtr->infoLog() << "Connected" << std::endl; + + assert(cc_mqtt5_##NAME##client_is_connected(thisPtr->m_client.get())); + + if (!response->m_sessionPresent) { + for (auto& subInfo : state.m_subs) { + assert(subInfo.m_handle == nullptr); + subInfo.m_acked = false; + } + } + + state.m_connectRequired = false; + state.m_connected = true; + state.m_firstConnect = false; + return; + } while (false); + + state.m_connectRequired = true; + state.m_connected = false; + state.m_reinitRequired = true; + } + + static void subscribeCompleteCb(void* data, CC_Mqtt5SubscribeHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response) + { + auto* thisPtr = asThis(data); + auto& state = thisPtr->m_state; + auto iter = + std::find_if( + state.m_subs.begin(), state.m_subs.end(), + [handle](auto& info) + { + return info.m_handle == handle; + }); + + if (iter == state.m_subs.end()) { + thisPtr->errorLog() << "Unexpected handle of the subscribe completion\n"; + exit(-1); + } + + assert(!iter->m_acked); + iter->m_handle = nullptr; + if (status != CC_Mqtt5AsyncOpStatus_Complete) { + return; + } + + assert(response != nullptr); + bool allOk = true; + for (auto idx = 0U; idx < response->m_reasonCodesCount; ++idx) { + if (CC_Mqtt5ReasonCode_UnspecifiedError <= response->m_reasonCodes[idx]) { + allOk = false; + break; + } + } + + iter->m_acked = allOk; + } + + static CC_Mqtt5AuthErrorCode authCb(void* data, const CC_Mqtt5AuthInfo* authInfoIn, CC_Mqtt5AuthInfo* authInfoOut) + { + assert(authInfoIn != nullptr); + assert(authInfoOut != nullptr); + + if ((authInfoIn->m_authDataLen & 0x1) != 0U) { + // When odd number of authentication bytes are reported, reject the authentication + auto* thisPtr = asThis(data); + auto& state = thisPtr->m_state; + state.m_connected = false; + state.m_reinitRequired = false; + + return CC_Mqtt5AuthErrorCode_Disconnect; + } + + *authInfoOut = *authInfoIn; // echo back + return CC_Mqtt5AuthErrorCode_Continue; + } + + static void reauthCompleteCb(void* data, CC_Mqtt5AsyncOpStatus status, [[maybe_unused]] const CC_Mqtt5AuthInfo* response) + { + auto* thisPtr = asThis(data); + auto& state = thisPtr->m_state; + assert(!state.m_reauthComplete); + + do { + if (status != CC_Mqtt5AsyncOpStatus_Complete) { + break; + } + + state.m_reauthComplete = true; + return; + } while (false); + + state.m_reauthRequired = true; + } + + const ProgramOptions& m_opts; + Logger& m_logger; + ClientPtr m_client; + std::vector m_inData; + State m_state; + bool m_verbose = false; +}; + +AflFuzz::AflFuzz(const ProgramOptions& opts, Logger& logger) : + m_impl(std::make_unique(opts, logger)) +{ +} + +AflFuzz::~AflFuzz() = default; + +bool AflFuzz::init() +{ + return m_impl->init(); +} + +void AflFuzz::run() +{ + m_impl->run(); +} + +} // namespace cc_mqtt5_client_afl_fuzz diff --git a/client/afl_fuzz/AflFuzz.h b/client/afl_fuzz/AflFuzz.h new file mode 100644 index 0000000..96b697c --- /dev/null +++ b/client/afl_fuzz/AflFuzz.h @@ -0,0 +1,31 @@ +// +// 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 "Logger.h" +#include "ProgramOptions.h" + +#include + +namespace cc_mqtt5_client_afl_fuzz +{ + +class AflFuzzImpl; +class AflFuzz +{ +public: + AflFuzz(const ProgramOptions& opts, Logger& logger); + ~AflFuzz(); + + bool init(); + void run(); +private: + std::unique_ptr m_impl; +}; + +} // namespace cc_mqtt5_client_afl_fuzz diff --git a/client/afl_fuzz/CMakeLists.txt b/client/afl_fuzz/CMakeLists.txt index b75391b..ad1c663 100644 --- a/client/afl_fuzz/CMakeLists.txt +++ b/client/afl_fuzz/CMakeLists.txt @@ -2,7 +2,7 @@ if (NOT CC_MQTT5_CLIENT_AFL_FUZZ) return () endif () -set (SRC_TEMPL ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp.templ) +set (SRC_TEMPL ${CMAKE_CURRENT_SOURCE_DIR}/AflFuzz.cpp.templ) set (TEMPL_PROCESS_SCRIPT ${PROJECT_SOURCE_DIR}/cmake/ProcessTemplate.cmake) ###################################################################### @@ -27,7 +27,7 @@ function (gen_mqtt5_client_afl_fuzz config_file) execute_process( COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/${dir}) - set (src_output ${CMAKE_CURRENT_BINARY_DIR}/${dir}/${name}main.cpp) + set (src_output ${CMAKE_CURRENT_BINARY_DIR}/${dir}/${name}AflFuzz.cpp) add_custom_command( OUTPUT "${src_output}" @@ -52,10 +52,14 @@ function (gen_mqtt5_client_afl_fuzz config_file) set (src ${src_output} + Logger.cpp + ProgramOptions.cpp + main.cpp ) add_executable(${app_name} ${src}) - target_link_libraries(${app_name} PRIVATE cc::${lib_name} cc::cc_mqtt5 cc::comms) + target_link_libraries(${app_name} PRIVATE cc::${lib_name} cc::cc_mqtt5 cc::comms Boost::program_options) + target_include_directories(${app_name} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) add_dependencies(${app_name} ${src_tgt_name}) @@ -67,6 +71,8 @@ endfunction() ###################################################################### +find_package (Boost REQUIRED COMPONENTS program_options) + if (CC_MQTT5_CLIENT_DEFAULT_LIB) gen_mqtt5_client_afl_fuzz("") endif () diff --git a/client/afl_fuzz/Logger.cpp b/client/afl_fuzz/Logger.cpp new file mode 100644 index 0000000..6ce2423 --- /dev/null +++ b/client/afl_fuzz/Logger.cpp @@ -0,0 +1,53 @@ +// +// Copyright 2023 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "Logger.h" + +#include + +namespace cc_mqtt5_client_afl_fuzz +{ + +Logger::Logger() : + m_out(&std::cout) +{ +} + +bool Logger::open(const ProgramOptions& opts) +{ + if (opts.hasLogFile()) { + m_fileStream.open(opts.logFile(), std::ios_base::app); + if (!m_fileStream) { + std::cerr << "Failed to open log file: " << opts.logFile() << std::endl; + return false; + } + + m_out = &m_fileStream; + } + return true; +} + +std::ostream& Logger::debugLog() +{ + return *m_out << "DEBUG: "; +} +std::ostream& Logger::infoLog() +{ + return *m_out << "INFO: "; +} + +std::ostream& Logger::errorLog() +{ + return *m_out << "ERROR: "; +} + +void Logger::flush() +{ + m_out->flush(); +} + +} // namespace cc_mqtt5_client_afl_fuzz diff --git a/client/afl_fuzz/Logger.h b/client/afl_fuzz/Logger.h new file mode 100644 index 0000000..b95a996 --- /dev/null +++ b/client/afl_fuzz/Logger.h @@ -0,0 +1,33 @@ +// +// 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 "ProgramOptions.h" + +#include +#include + +namespace cc_mqtt5_client_afl_fuzz +{ + +class Logger +{ +public: + Logger(); + bool open(const ProgramOptions& opts); + std::ostream& debugLog(); + std::ostream& infoLog(); + std::ostream& errorLog(); + void flush(); + +private: + std::ostream* m_out = nullptr; + std::ofstream m_fileStream; +}; + +} // namespace cc_mqtt5_client_afl_fuzz diff --git a/client/afl_fuzz/ProgramOptions.cpp b/client/afl_fuzz/ProgramOptions.cpp new file mode 100644 index 0000000..ea0d133 --- /dev/null +++ b/client/afl_fuzz/ProgramOptions.cpp @@ -0,0 +1,161 @@ +// +// Copyright 2023 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "ProgramOptions.h" + +#include +#include + +namespace po = boost::program_options; + +namespace cc_mqtt5_client_afl_fuzz +{ + +ProgramOptions::ProgramOptions() +{ + po::options_description commonOpts("Common Options"); + commonOpts.add_options() + ("help,h", "Display help message") + ("verbose,v", "Verbose output") + ("log-file,f", po::value(), "Output log file") + ; + + po::options_description connectOpts("Connect Options"); + connectOpts.add_options() + ("client-id,i", po::value()->default_value(std::string()), "Client ID") + ("receive-max", po::value()->default_value(0), "\"Receive Maximum\" property. 0 means not set.") + ("max-packet-size", po::value()->default_value(0), "\"Maximum Packet Size\" property. 0 means no limit.") + ("topic-alias-max", po::value()->default_value(0), "\"Topic Alias Maximum\" property.") + ("req-response-info", "Set \"Request Response Information\" property flag.") + ("req-problem-info", "Set \"Request Problem Information\" property flag.") + ("auth-method", po::value()->default_value(std::string()), + "Enforce exchange of the custom authentication with re-authentication testing") + ; + + po::options_description subOpts("Subscribe Options"); + subOpts.add_options() + ("sub-topic,t", po::value(), "Subscribe topic filters. Can be used multiple times. " + "Multiple topics can also be comma separated in single occurrance, to be packed into single subscribe message. " + "If not specified, single subscribe to \"#\" is assumed") + ; + + m_desc.add(commonOpts); + m_desc.add(connectOpts); + m_desc.add(subOpts); +} + +bool ProgramOptions::parseArgs(int argc, const char* argv[]) +{ + po::store(po::parse_command_line(argc, argv, m_desc), m_vm); + po::notify(m_vm); + + return true; +} + +void ProgramOptions::printHelp() +{ + std::cout << m_desc << std::endl; +} + +bool ProgramOptions::helpRequested() const +{ + return m_vm.count("help") > 0U; +} + +bool ProgramOptions::verbose() const +{ + return m_vm.count("verbose") > 0U; +} + +bool ProgramOptions::hasLogFile() const +{ + return m_vm.count("log-file") > 0U; +} + +std::string ProgramOptions::logFile() const +{ + return m_vm["log-file"].as(); +} + +std::string ProgramOptions::clientId() const +{ + return m_vm["client-id"].as(); +} + +unsigned ProgramOptions::receiveMax() const +{ + return m_vm["receive-max"].as(); +} + +unsigned ProgramOptions::maxPacketSize() const +{ + return m_vm["max-packet-size"].as(); +} + +unsigned ProgramOptions::topicAliasMax() const +{ + return m_vm["topic-alias-max"].as(); +} + +bool ProgramOptions::reqResponseInfo() const +{ + return m_vm.count("req-response-info") > 0U; +} + +bool ProgramOptions::reqProblemInfo() const +{ + return m_vm.count("req-problem-info") > 0U; +} + +std::string ProgramOptions::authMethod() const +{ + return m_vm["auth-method"].as(); +} + +std::vector ProgramOptions::subTopics() const +{ + auto values = stringListOpts("sub-topic"); + if (values.empty()) { + values.push_back("#"); + } + + std::vector result; + result.reserve(values.size()); + std::transform( + values.begin(), values.end(), std::back_inserter(result), + [](auto& str) + { + StringsList topics; + std::size_t pos = 0U; + while (pos < str.size()) { + auto sepPos = str.find(',', pos); + if (sepPos == std::string::npos) { + topics.push_back(str.substr(pos)); + break; + } + + topics.push_back(str.substr(pos, sepPos - pos)); + pos = sepPos + 1U; + } + + return topics; + }); + + return result; +} + +ProgramOptions::StringsList ProgramOptions::stringListOpts(const std::string& name) const +{ + StringsList result; + if (m_vm.count(name) > 0) { + result = m_vm[name].as(); + } + + return result; +} + +} // namespace cc_mqtt5_client_afl_fuzz diff --git a/client/afl_fuzz/ProgramOptions.h b/client/afl_fuzz/ProgramOptions.h new file mode 100644 index 0000000..123754a --- /dev/null +++ b/client/afl_fuzz/ProgramOptions.h @@ -0,0 +1,57 @@ +// +// Copyright 2023 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include + +#include +#include +#include + +namespace cc_mqtt5_client_afl_fuzz +{ + +class ProgramOptions +{ +public: + using OptDesc = boost::program_options::options_description; + using StringsList = std::vector; + using UnsignedsList = std::vector; + + ProgramOptions(); + + void printHelp(); + + bool parseArgs(int argc, const char* argv[]); + + // Common options + bool helpRequested() const; + bool verbose() const; + bool hasLogFile() const; + std::string logFile() const; + + // Connect options + std::string clientId() const; + unsigned receiveMax() const; + unsigned maxPacketSize() const; + unsigned topicAliasMax() const; + bool reqResponseInfo() const; + bool reqProblemInfo() const; + std::string authMethod() const; + + // Subscribe options + std::vector subTopics() const; + +private: + StringsList stringListOpts(const std::string& name) const; + + boost::program_options::variables_map m_vm; + OptDesc m_desc; +}; + +} // namespace cc_mqtt5_client_afl_fuzz diff --git a/client/afl_fuzz/main.cpp b/client/afl_fuzz/main.cpp new file mode 100644 index 0000000..5ac6db5 --- /dev/null +++ b/client/afl_fuzz/main.cpp @@ -0,0 +1,30 @@ +#include "AflFuzz.h" +#include "Logger.h" +#include "ProgramOptions.h" + + +int main(int argc, const char* argv[]) +{ + cc_mqtt5_client_afl_fuzz::ProgramOptions opts; + opts.parseArgs(argc, argv); + + if (opts.helpRequested()) { + opts.printHelp(); + return -1; + } + + // TODO: generate input + + cc_mqtt5_client_afl_fuzz::Logger logger; + if (!logger.open(opts)) { + return -1; + } + + cc_mqtt5_client_afl_fuzz::AflFuzz fuzz(opts, logger); + if (!fuzz.init()) { + return -1; + } + + fuzz.run(); + return 0; +} \ No newline at end of file diff --git a/client/afl_fuzz/main.cpp.templ b/client/afl_fuzz/main.cpp.templ deleted file mode 100644 index c75fe13..0000000 --- a/client/afl_fuzz/main.cpp.templ +++ /dev/null @@ -1,370 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "##NAME##client.h" - -#include "cc_mqtt5/Message.h" -#include "cc_mqtt5/frame/Frame.h" - - -namespace -{ - -const std::size_t BufSize = 1024000; -const unsigned IterCount = 10000; - -struct AflFuzzDeleter -{ - void operator()(CC_Mqtt5Client* ptr) - { - cc_mqtt5_##NAME##client_free(ptr); - } -}; - -using AflFuzzClientPtr = std::unique_ptr; - -struct State -{ - std::ostream* m_log = nullptr; - CC_Mqtt5ClientHandle m_client = nullptr; - std::vector m_inData; - unsigned m_nextTickDuration = 0U; - bool m_reinitRequired = true; - bool m_connectRequired = true; - bool m_connected = false; - bool m_subscribeRequired = true; - bool m_subscribed = false; - bool m_firstConnect = true; - bool m_disconnected = false; -}; - -State* asState(void* data) -{ - return reinterpret_cast(data); -} - -std::ostream& infoLog(State* state) -{ - *(state->m_log) << "INFO: "; - return *(state->m_log); -} - -std::ostream& errorLog(State* state) -{ - *(state->m_log) << "ERROR: "; - return *(state->m_log); -} - -void nextTickProgramCb(void* data, unsigned duration) -{ - asState(data)->m_nextTickDuration = duration; -} - -unsigned cancelNextTickWaitCb(void* data) -{ - auto diff = std::min(asState(data)->m_nextTickDuration, 1U); - asState(data)->m_nextTickDuration -= diff; - return diff; -} - -void sendOutputDataCb([[maybe_unused]] void* data, [[maybe_unused]] const unsigned char* buf, unsigned bufLen) -{ - auto* state = asState(data); - auto& out = infoLog(state); - out << "Sending " << bufLen << " bytes: " << std::hex; - std::copy_n(buf, bufLen, std::ostream_iterator(out, " ")); - out << std::dec << std::endl; -} - -void brokerDisconnectReportCb(void* data, [[maybe_unused]] const CC_Mqtt5DisconnectInfo* info) -{ - auto* state = asState(data); - infoLog(state) << "Broker disconnected" << std::endl; - state->m_disconnected = true; - state->m_reinitRequired = true; - state->m_connectRequired = true; - state->m_connected = false; -} - -void messageReceivedReportCb([[maybe_unused]] void* data, [[maybe_unused]] const CC_Mqtt5MessageInfo* info) -{ - auto* state = asState(data); - infoLog(state) << "Message Received!!!\n" << std::endl; -} - -void errorLogCb([[maybe_unused]] void* data, const char* msg) -{ - auto* state = asState(data); - errorLog(state) << msg << std::endl; -} - -void connectCompleteCb(void* data, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5ConnectResponse* response) -{ - auto* state = asState(data); - - do { - if (status != CC_Mqtt5AsyncOpStatus_Complete) { - break; - } - - assert(response != nullptr); - if (CC_Mqtt5ReasonCode_Success != response->m_reasonCode) { - break; - } - - assert(cc_mqtt5_##NAME##client_is_connected(state->m_client)); - - if (response->m_sessionPresent) { - state->m_subscribeRequired = false; - state->m_subscribed = false; - } - - infoLog(state) << "Connected" << std::endl; - state->m_connectRequired = false; - state->m_connected = true; - state->m_firstConnect = false; - return; - } while (false); - - state->m_connectRequired = true; - state->m_connected = false; - state->m_reinitRequired = true; -} - -void subscribeCompleteCb(void* data, [[maybe_unused]] CC_Mqtt5SubscribeHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5SubscribeResponse* response) -{ - auto* state = asState(data); - - do { - if (status != CC_Mqtt5AsyncOpStatus_Complete) { - break; - } - - assert(response != nullptr); - bool allOk = true; - for (auto idx = 0U; idx < response->m_reasonCodesCount; ++idx) { - if (CC_Mqtt5ReasonCode_UnspecifiedError <= response->m_reasonCodes[idx]) { - allOk = false; - break; - } - } - - if (!allOk) { - break; - } - - infoLog(state) << "Subscribed" << std::endl; - state->m_subscribed = true; - - return; - } while (false); - - state->m_subscribeRequired = true; - state->m_subscribed = false; -} - -} // namespace - -int main([[maybe_unused]] int argc, [[maybe_unused]] const char* argv[]) -{ - - std::ofstream log("/tmp/afl_fuzz.log", std::ios_base::app); - if (!log) { - std::cerr << "Failed to open log file" << std::endl; - return -1; - } - - AflFuzzClientPtr client(cc_mqtt5_##NAME##client_alloc()); - State state; - state.m_client = client.get(); - state.m_log = &log; - - cc_mqtt5_##NAME##client_set_next_tick_program_callback(client.get(), &nextTickProgramCb, &state); - cc_mqtt5_##NAME##client_set_cancel_next_tick_wait_callback(client.get(), &cancelNextTickWaitCb, &state); - cc_mqtt5_##NAME##client_set_send_output_data_callback(client.get(), &sendOutputDataCb, &state); - cc_mqtt5_##NAME##client_set_broker_disconnect_report_callback(client.get(), &brokerDisconnectReportCb, &state); - cc_mqtt5_##NAME##client_set_message_received_report_callback(client.get(), &messageReceivedReportCb, &state); - cc_mqtt5_##NAME##client_set_error_log_callback(client.get(), &errorLogCb, &state); - - sync(); - - CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_Success; - std::uint8_t buf[BufSize] = {0}; - for (auto i = 0U; i < IterCount; ++i) { - infoLog(&state) << "Iteration " << i << std::endl; - if (state.m_reinitRequired) { - state.m_reinitRequired = false; - state.m_disconnected = false; - ec = cc_mqtt5_##NAME##client_init(client.get()); - assert(ec == CC_Mqtt5ErrorCode_Success); - if (ec != CC_Mqtt5ErrorCode_Success) { - exit(-1); - } - } - - assert(cc_mqtt5_##NAME##client_is_initialized(client.get())); - - do { - if (state.m_connectRequired) { - state.m_connected = false; - - infoLog(&state) << "Connecting..." << std::endl; - - auto connect = cc_mqtt5_##NAME##client_connect_prepare(client.get(), &ec); - assert(connect != nullptr); - assert(ec == CC_Mqtt5ErrorCode_Success); - if (connect == nullptr) { - exit(-1); - } - - CC_Mqtt5ConnectBasicConfig basicConfig; - cc_mqtt5_##NAME##client_connect_init_config_basic(&basicConfig); - basicConfig.m_clientId = "some_id"; - basicConfig.m_cleanStart = state.m_firstConnect; - - ec = cc_mqtt5_##NAME##client_connect_config_basic(connect, &basicConfig); - assert(ec == CC_Mqtt5ErrorCode_Success); - if (ec != CC_Mqtt5ErrorCode_Success) { - exit(-1); - } - - // TODO: extra configuration - - ec = cc_mqtt5_##NAME##client_connect_send(connect, &connectCompleteCb, &state); - assert(ec == CC_Mqtt5ErrorCode_Success); - if (ec != CC_Mqtt5ErrorCode_Success) { - exit(-1); - } - - state.m_connectRequired = false; - break; - } - - if (!cc_mqtt5_##NAME##client_is_connected(client.get())) { - infoLog(&state) << "Not connected yet..." << std::endl; - assert(!state.m_connected); - break; - } - - assert(state.m_connected); - - if (state.m_subscribeRequired) { - state.m_subscribed = false; - - infoLog(&state) << "Subscribing..." << std::endl; - - auto subscribe = cc_mqtt5_##NAME##client_subscribe_prepare(client.get(), &ec); - assert(subscribe != nullptr); - assert(ec == CC_Mqtt5ErrorCode_Success); - if (subscribe == nullptr) { - exit(-1); - } - - CC_Mqtt5SubscribeTopicConfig config; - cc_mqtt5_##NAME##client_subscribe_init_config_topic(&config); - config.m_topic = "#"; - - ec = cc_mqtt5_##NAME##client_subscribe_config_topic(subscribe, &config); - assert(ec == CC_Mqtt5ErrorCode_Success); - if (ec != CC_Mqtt5ErrorCode_Success) { - exit(-1); - } - - ec = cc_mqtt5_##NAME##client_subscribe_send(subscribe, &subscribeCompleteCb, &state); - assert(ec == CC_Mqtt5ErrorCode_Success); - if (ec != CC_Mqtt5ErrorCode_Success) { - exit(-1); - } - - state.m_subscribeRequired = false; - break; - } - - } while (false); - - auto len = read(0, buf, BufSize); - bool noMoreRead = (len <= 0); - len = std::max(len, static_cast(0)); - auto& out = infoLog(&state); - out << "Read " << len << " bytes: " << std::hex; - std::copy_n(buf, len, std::ostream_iterator(out, " ")); - out << std::dec << std::endl; - - auto dataPtr = &buf[0]; - auto dataLen = static_cast(len); - if (!state.m_inData.empty()) { - state.m_inData.insert(state.m_inData.end(), dataPtr, dataPtr + dataLen); - static const auto MaxLen = std::numeric_limits::max(); - - if (MaxLen < state.m_inData.size()) { - state.m_inData.erase(state.m_inData.begin(), state.m_inData.begin() + (state.m_inData.size() - MaxLen)); - } - - dataPtr = state.m_inData.data(); - dataLen = state.m_inData.size(); - } - - using Interface = cc_mqtt5::Message<>; - using IdAndFlagsField = cc_mqtt5::frame::Frame::Layer_idAndFlags::Field; - using SizeField = cc_mqtt5::frame::Frame::Layer_size::Field; - - static constexpr auto MinLen = IdAndFlagsField::minLength() + SizeField::minLength(); - if (dataLen < MinLen) { - if (noMoreRead) { - infoLog(&state) << "Insufficient data in buffer: " << dataLen << std::endl; - break; - } - - continue; - } - - auto iter = dataPtr; - std::advance(iter, IdAndFlagsField::minLength()); - - auto processLen = dataLen; - SizeField sizeField; - auto es = sizeField.read(iter, dataLen - IdAndFlagsField::minLength()); - if (es == comms::ErrorStatus::Success) { - auto msgLen = IdAndFlagsField::minLength() + sizeField.length() + sizeField.getValue(); - processLen = std::min(dataLen, msgLen); - } - - infoLog(&state) << "Processing " << processLen << " out of " << dataLen << " bytes..." << std::endl; - auto consumed = cc_mqtt5_##NAME##client_process_data(client.get(), dataPtr, static_cast(processLen)); - infoLog(&state) << "Consumed: " << consumed << std::endl; - - assert((consumed > 0U) || (!state.m_disconnected)); - - if ((consumed == 0U) && noMoreRead) { - infoLog(&state) << "No data consumed and no more input" << std::endl; - break; - } - - if (dataLen <= consumed) { - state.m_inData.clear(); - continue; - } - - if (state.m_inData.empty()) { - state.m_inData.assign(dataPtr + consumed, dataPtr + dataLen); - infoLog(&state) << "Keeping " << state.m_inData.size() << " bytes for the next iteration" << std::endl; - continue; - } - - state.m_inData.erase(state.m_inData.begin(), state.m_inData.begin() + consumed); - infoLog(&state) << "Keeping " << state.m_inData.size() << " bytes for the next iteration" << std::endl; - } - - infoLog(&state) << "------------------------------------------------" << std::endl; - return 0U; -} \ No newline at end of file From e8b706c9f65755d9a37b5ea9fee07063e5e786c7 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Mon, 25 Dec 2023 16:28:03 +1000 Subject: [PATCH 10/35] Fixing fuzzing build. --- client/afl_fuzz/AflFuzz.cpp.templ | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/client/afl_fuzz/AflFuzz.cpp.templ b/client/afl_fuzz/AflFuzz.cpp.templ index a2b014d..9f07dfa 100644 --- a/client/afl_fuzz/AflFuzz.cpp.templ +++ b/client/afl_fuzz/AflFuzz.cpp.templ @@ -438,15 +438,31 @@ private: static void messageReceivedReportCb(void* data, const CC_Mqtt5MessageInfo* info) { auto* thisPtr = asThis(data); + auto* client = thisPtr->m_client.get(); thisPtr->m_logger.infoLog() << "Message Received!!!\n"; assert(info != nullptr); + do { + if (thisPtr->m_opts.topicAliasMax() == 0U) { + // Topic aliases are not in use + break; + } + + if (cc_mqtt5_##NAME##client_pub_topic_alias_is_allocated(client, info->m_topic)) { + // already allocated + break; + } + + [[maybe_unused]] auto allocEc = cc_mqtt5_##NAME##client_pub_topic_alias_alloc(client, info->m_topic, 1U); + // Don't care if really allocated + } while (false); + CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_ValuesLimit; auto* publish = ::cc_mqtt5_##NAME##client_publish_prepare(thisPtr->m_client.get(), &ec); assert(ec == CC_Mqtt5ErrorCode_Success); if (publish == nullptr) { - errorLog() << "Unexpected failure in publish allocation\n"; + thisPtr->errorLog() << "Unexpected failure in publish allocation\n"; return; } @@ -460,7 +476,7 @@ private: ec = cc_mqtt5_##NAME##client_publish_config_basic(publish, &basicConfig); if (ec != CC_Mqtt5ErrorCode_Success) { - errorLog() << "Unexpected failure in publish configuration\n"; + thisPtr->errorLog() << "Unexpected failure in publish configuration\n"; ::cc_mqtt5_##NAME##client_publish_cancel(publish); return; } @@ -476,7 +492,7 @@ private: ec = cc_mqtt5_##NAME##client_publish_config_extra(publish, &extraConfig); if (ec != CC_Mqtt5ErrorCode_Success) { - errorLog() << "Failure in publish extra configuration\n"; + thisPtr->errorLog() << "Failure in publish extra configuration\n"; } for (auto idx = 0U; idx < info->m_userPropsCount; ++idx) { @@ -487,13 +503,13 @@ private: ec = cc_mqtt5_##NAME##client_publish_add_user_prop(publish, &outProp); if (ec != CC_Mqtt5ErrorCode_Success) { - errorLog() << "Failure to add publish user property\n"; + thisPtr->errorLog() << "Failure to add publish user property\n"; } } ec = cc_mqtt5_##NAME##client_publish_send(publish, nullptr, nullptr); if (ec != CC_Mqtt5ErrorCode_Success) { - errorLog() << "Failure to publish message\n"; + thisPtr->errorLog() << "Failure to publish message\n"; } } From 0c21456a9f75f958faab07876c93d529ed5e16e4 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 26 Dec 2023 12:15:12 +1000 Subject: [PATCH 11/35] Saving work on fuzzing, added generator. --- client/afl_fuzz/AflFuzz.cpp.templ | 83 +++++-- client/afl_fuzz/CMakeLists.txt | 1 + client/afl_fuzz/Generator.cpp | 198 +++++++++++++++++ client/afl_fuzz/Generator.h | 335 +++++++++++++++++++++++++++++ client/afl_fuzz/ProgramOptions.cpp | 6 + client/afl_fuzz/ProgramOptions.h | 1 + client/lib/src/ClientImpl.cpp | 1 + 7 files changed, 605 insertions(+), 20 deletions(-) create mode 100644 client/afl_fuzz/Generator.cpp create mode 100644 client/afl_fuzz/Generator.h diff --git a/client/afl_fuzz/AflFuzz.cpp.templ b/client/afl_fuzz/AflFuzz.cpp.templ index 9f07dfa..ccec3d2 100644 --- a/client/afl_fuzz/AflFuzz.cpp.templ +++ b/client/afl_fuzz/AflFuzz.cpp.templ @@ -7,6 +7,8 @@ #include "AflFuzz.h" +#include "Generator.h" + #include "##NAME##client.h" #include "cc_mqtt5/Message.h" @@ -42,6 +44,10 @@ public: bool init() { + if (!createGeneratorIfNeeded()) { + return false; + } + auto subTopics = m_opts.subTopics(); std::transform( subTopics.begin(), subTopics.end(), std::back_inserter(m_state.m_subs), @@ -93,16 +99,20 @@ public: } while (false); - auto len = read(0, buf, BufSize); - bool noMoreRead = (len <= 0); - len = std::max(len, static_cast(0)); - infoLog() << "Read " << len << " bytes\n"; - - if (m_verbose) { - auto& out = debugLog(); - out << "Read bytes: " << std::hex; - std::copy_n(buf, len, std::ostream_iterator(out, " ")); - out << std::dec << std::endl; + ssize_t len = 0; + bool noMoreRead = true; + if (!m_generator) { + len = read(0, buf, BufSize); + noMoreRead = (len <= 0); + len = std::max(len, static_cast(0)); + infoLog() << "Read " << len << " bytes\n"; + + if (m_verbose) { + auto& out = debugLog(); + out << "Read bytes: " << std::hex; + std::copy_n(buf, len, std::ostream_iterator(out, " ")); + out << std::dec << std::endl; + } } auto dataPtr = &buf[0]; @@ -206,7 +216,7 @@ private: std::ostream& debugLog() { - return m_logger.infoLog(); + return m_logger.debugLog(); } std::ostream& infoLog() @@ -216,7 +226,7 @@ private: std::ostream& errorLog() { - return m_logger.infoLog(); + return m_logger.errorLog(); } void doReinit() @@ -306,30 +316,31 @@ private: void doSubscribeIfNeeded() { for (auto& subInfo : m_state.m_subs) { - if (subInfo.m_handle != nullptr) { + if ((subInfo.m_handle != nullptr) || (subInfo.m_acked)) { // Request is sent continue; } - assert(!subInfo.m_acked); + infoLog() << "Attempting subscribe\n"; CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_ValuesLimit; - auto subscribe = cc_mqtt5_##NAME##client_subscribe_prepare(m_client.get(), &ec); - assert(subscribe != nullptr); + subInfo.m_handle = cc_mqtt5_##NAME##client_subscribe_prepare(m_client.get(), &ec); + assert(subInfo.m_handle != nullptr); assert(ec == CC_Mqtt5ErrorCode_Success); - if (subscribe == nullptr) { + if (subInfo.m_handle == nullptr) { errorLog() << "Unexpected failure in subscribe allocation\n"; exit(-1); } for (auto& t : subInfo.m_topics) { assert(!t.empty()); + infoLog() << "Adding topic " << t << "\n"; CC_Mqtt5SubscribeTopicConfig config; cc_mqtt5_##NAME##client_subscribe_init_config_topic(&config); config.m_topic = t.c_str(); while (true) { - ec = cc_mqtt5_##NAME##client_subscribe_config_topic(subscribe, &config); + ec = cc_mqtt5_##NAME##client_subscribe_config_topic(subInfo.m_handle, &config); assert(ec == CC_Mqtt5ErrorCode_Success); if (ec != CC_Mqtt5ErrorCode_Success) { if (config.m_maxQos > CC_Mqtt5QoS_AtMostOnceDelivery) { @@ -347,7 +358,7 @@ private: } } - ec = cc_mqtt5_##NAME##client_subscribe_send(subscribe, &AflFuzzImpl::subscribeCompleteCb, this); + ec = cc_mqtt5_##NAME##client_subscribe_send(subInfo.m_handle, &AflFuzzImpl::subscribeCompleteCb, this); assert(ec == CC_Mqtt5ErrorCode_Success); if (ec != CC_Mqtt5ErrorCode_Success) { errorLog() << "Unexpected failure in sending subscribe request"; @@ -396,6 +407,27 @@ private: m_state.m_reauthRequired = false; } + bool createGeneratorIfNeeded() + { + auto inputFile = m_opts.genInputFile(); + if (inputFile.empty()) { + return true; + } + + m_generator = std::make_unique(m_logger); + if (!m_generator->prepare(inputFile)) { + return false; + } + + m_generator->setDataReportCb( + [this](const std::uint8_t* buf, std::size_t bufLen) + { + m_inData.insert(m_inData.end(), buf, buf + bufLen); + }); + + return true; + } + static AflFuzzImpl* asThis(void* data) { return reinterpret_cast(data); @@ -423,6 +455,11 @@ private: std::copy_n(buf, bufLen, std::ostream_iterator(out, " ")); out << std::dec << '\n'; } + + auto& generator = asThis(data)->m_generator; + if (generator) { + generator->processData(buf, bufLen); + } } static void brokerDisconnectReportCb(void* data, [[maybe_unused]] const CC_Mqtt5DisconnectInfo* info) @@ -439,7 +476,7 @@ private: { auto* thisPtr = asThis(data); auto* client = thisPtr->m_client.get(); - thisPtr->m_logger.infoLog() << "Message Received!!!\n"; + thisPtr->m_logger.infoLog() << "Message Received!\n"; assert(info != nullptr); @@ -575,6 +612,7 @@ private: assert(!iter->m_acked); iter->m_handle = nullptr; if (status != CC_Mqtt5AsyncOpStatus_Complete) { + thisPtr->errorLog() << "Subscribe operation is terminated\n"; return; } @@ -582,11 +620,15 @@ private: bool allOk = true; for (auto idx = 0U; idx < response->m_reasonCodesCount; ++idx) { if (CC_Mqtt5ReasonCode_UnspecifiedError <= response->m_reasonCodes[idx]) { + thisPtr->errorLog() << "Subscribe rejected\n"; allOk = false; break; } } + if (allOk) { + thisPtr->infoLog() << "The subscribe is properly acked\n"; + } iter->m_acked = allOk; } @@ -630,6 +672,7 @@ private: const ProgramOptions& m_opts; Logger& m_logger; ClientPtr m_client; + GeneratorPtr m_generator; std::vector m_inData; State m_state; bool m_verbose = false; diff --git a/client/afl_fuzz/CMakeLists.txt b/client/afl_fuzz/CMakeLists.txt index ad1c663..56af3b7 100644 --- a/client/afl_fuzz/CMakeLists.txt +++ b/client/afl_fuzz/CMakeLists.txt @@ -52,6 +52,7 @@ function (gen_mqtt5_client_afl_fuzz config_file) set (src ${src_output} + Generator.cpp Logger.cpp ProgramOptions.cpp main.cpp diff --git a/client/afl_fuzz/Generator.cpp b/client/afl_fuzz/Generator.cpp new file mode 100644 index 0000000..7e23dcf --- /dev/null +++ b/client/afl_fuzz/Generator.cpp @@ -0,0 +1,198 @@ +// +// Copyright 2023 - 2024 (C). Alex Robenko. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "Generator.h" + +#include "comms/process.h" + +#include +#include + +namespace cc_mqtt5_client_afl_fuzz +{ + +namespace +{ + +template +decltype(auto) addProp(TField& field) +{ + auto& vec = field.value(); + vec.resize(vec.size() + 1U); + return vec.back(); +} + +} // namespace + + +bool Generator::prepare(const std::string& inputFile) +{ + m_stream.open(inputFile); + if (!m_stream) { + m_logger.errorLog() << "Failed to open " << inputFile << " for writing" << std::endl; + return false; + } + + return true; +} + +void Generator::processData(const std::uint8_t* buf, unsigned bufLen) +{ + [[maybe_unused]] auto consumed = comms::processAllWithDispatch(buf, bufLen, m_frame, *this); + assert(consumed == bufLen); +} + +void Generator::handle(const Mqtt5ConnectMsg& msg) +{ + m_logger.infoLog() << "Processing " << msg.name() << "\n"; + PropsHandler propsHandler; + for (auto& p : msg.field_properties().value()) { + p.currentFieldExec(propsHandler); + } + + if (!propsHandler.m_authMethod.empty()) { + m_cachedConnect = std::make_unique(msg); + sendAuth(propsHandler, Mqtt5AuthMsg::Field_reasonCode::Field::ValueType::ContinueAuth); + return; + } + + sendConnack(msg, propsHandler); +} + +void Generator::handle(const Mqtt5SubscribeMsg& msg) +{ + m_logger.infoLog() << "Processing " << msg.name() << "\n"; + auto& subsVec = msg.field_list().value(); + + Mqtt5SubackMsg outMsg; + outMsg.field_packetId().value() = msg.field_packetId().value(); + auto& ackVec = outMsg.field_list().value(); + ackVec.resize(subsVec.size()); + + for (auto& elem : ackVec) { + using ElemType = std::decay_t; + elem.setValue(ElemType::ValueType::GrantedQos2); + } + + sendMessage(outMsg); + + // TODO: send publish +} + +void Generator::handle(const Mqtt5AuthMsg& msg) +{ + m_logger.infoLog() << "Processing " << msg.name() << "\n"; + if (!m_connected) { + assert(m_cachedConnect); + + PropsHandler propsHandler; + for (auto& p : m_cachedConnect->field_properties().value()) { + p.currentFieldExec(propsHandler); + } + + sendConnack(*m_cachedConnect, propsHandler); + m_cachedConnect.reset(); + return; + } + + PropsHandler propsHandler; + for (auto& p : m_cachedConnect->field_properties().value()) { + p.currentFieldExec(propsHandler); + } + + + if (msg.field_reasonCode().field().value() == Mqtt5AuthMsg::Field_reasonCode::Field::ValueType::ReAuth) { + sendAuth(propsHandler, Mqtt5AuthMsg::Field_reasonCode::Field::ValueType::ContinueAuth); + return; + } + + assert(msg.field_reasonCode().field().value() == Mqtt5AuthMsg::Field_reasonCode::Field::ValueType::ContinueAuth); + sendAuth(propsHandler, Mqtt5AuthMsg::Field_reasonCode::Field::ValueType::Success); +} + +void Generator::handle([[maybe_unused]] const Mqtt5Message& msg) +{ + m_logger.infoLog() << "Ignoring " << msg.name() << "\n"; +} + +void Generator::sendMessage(Mqtt5Message& msg) +{ + m_logger.infoLog() << "Generating " << msg.name() << "\n"; + msg.refresh(); + RawDataBuf outBuf; + outBuf.reserve(m_frame.length(msg)); + auto iter = std::back_inserter(outBuf); + [[maybe_unused]] auto es = m_frame.write(msg, iter, outBuf.max_size()); + assert(es == comms::ErrorStatus::Success); + assert(m_dataReportCb); + m_dataReportCb(outBuf.data(), outBuf.size()); +} + +void Generator::sendConnack(const Mqtt5ConnectMsg& msg, const PropsHandler& propsHandler) +{ + Mqtt5ConnackMsg outMsg; + auto& propsField = outMsg.field_properties(); + + if (msg.field_clientId().value().empty()) { + auto& propVar = addProp(propsField); + auto& propBundle = propVar.initField_assignedClientId(); + auto& valueField = propBundle.field_value(); + valueField.setValue("some_client_id"); + } + + if (propsHandler.m_receiveMax > 0U) { + auto& propVar = addProp(propsField); + auto& propBundle = propVar.initField_receiveMax(); + auto& valueField = propBundle.field_value(); + valueField.setValue(propsHandler.m_receiveMax); + } + + if (propsHandler.m_maxPacketSize > 0U) { + auto& propVar = addProp(propsField); + auto& propBundle = propVar.initField_maxPacketSize(); + auto& valueField = propBundle.field_value(); + valueField.setValue(propsHandler.m_maxPacketSize); + } + + if (propsHandler.m_topicAliasMax > 0U) { + auto& propVar = addProp(propsField); + auto& propBundle = propVar.initField_topicAliasMax(); + auto& valueField = propBundle.field_value(); + valueField.setValue(propsHandler.m_topicAliasMax); + } + + sendMessage(outMsg); + m_connected = true; +} + +void Generator::sendAuth(const PropsHandler& propsHandler, Mqtt5AuthMsg::Field_reasonCode::Field::ValueType reasonCode) +{ + Mqtt5AuthMsg outMsg; + outMsg.field_reasonCode().field().setValue(reasonCode); + + auto& propsField = outMsg.field_properties().field(); + if (!propsHandler.m_authMethod.empty()) { + auto& propVar = addProp(propsField); + auto& propBundle = propVar.initField_authMethod(); + auto& valueField = propBundle.field_value(); + valueField.setValue(propsHandler.m_authMethod); + } + + if (!propsHandler.m_authData.empty()) { + auto& propVar = addProp(propsField); + auto& propBundle = propVar.initField_authData(); + auto& valueField = propBundle.field_value(); + valueField.setValue(propsHandler.m_authData); + if ((valueField.value().size() & 0x1) != 0) { + valueField.value().push_back('1'); + } + } + + sendMessage(outMsg); +} + +} // namespace cc_mqtt5_client_afl_fuzz diff --git a/client/afl_fuzz/Generator.h b/client/afl_fuzz/Generator.h new file mode 100644 index 0000000..cf34e50 --- /dev/null +++ b/client/afl_fuzz/Generator.h @@ -0,0 +1,335 @@ +// +// 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 "Logger.h" + +#include "cc_mqtt5/Message.h" +#include "cc_mqtt5/field/Property.h" +#include "cc_mqtt5/frame/Frame.h" + +#include "comms/options.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace cc_mqtt5_client_afl_fuzz +{ + +class Generator +{ +public: + using RawDataBuf = std::vector; + + using Mqtt5Message = + cc_mqtt5::Message< + comms::option::app::ReadIterator, + comms::option::app::WriteIterator>, + comms::option::app::LengthInfoInterface, + comms::option::app::IdInfoInterface, + comms::option::app::NameInterface, + comms::option::app::RefreshInterface, + comms::option::app::Handler + >; + + CC_MQTT5_ALIASES_FOR_ALL_MESSAGES_DEFAULT_OPTIONS(Mqtt5, Msg, Mqtt5Message); + + using DataReportCb = std::function; + + Generator(Logger& logger) : m_logger(logger) {}; + + bool prepare(const std::string& inputFile); + void processData(const std::uint8_t* buf, unsigned bufLen); + + template + void setDataReportCb(TFunc&& func) + { + m_dataReportCb = std::forward(func); + } + + void handle(const Mqtt5ConnectMsg& msg); + void handle(const Mqtt5SubscribeMsg& msg); + void handle(const Mqtt5AuthMsg& msg); + void handle(const Mqtt5Message& msg); + +private: + using Mqtt5Frame = cc_mqtt5::frame::Frame; + + class PropsHandler + { + using Property = cc_mqtt5::field::Property<>; + + public: + using PayloadFormatIndicator = Property::Field_payloadFormatIndicator; + const PayloadFormatIndicator* m_payloadFormatIndicator = nullptr; + template + void operator()([[maybe_unused]]const TField& field) + { + } + + // using MessageExpiryInterval = Property::Field_messageExpiryInterval; + // unsigned m_messageExpiryInterval = nullptr; + // template + // void operator()(const MessageExpiryInterval& field) + // { + // m_messageExpiryInterval = field.field_value().getValue(); + // } + + // using ContentType = Property::Field_contentType; + // const ContentType* m_contentType = nullptr; + // template + // void operator()(const ContentType& field) + // { + // storeProp(field, m_contentType); + // } + + // using ResponseTopic = Property::Field_responseTopic; + // const ResponseTopic* m_responseTopic = nullptr; + // template + // void operator()(const ResponseTopic& field) + // { + // storeProp(field, m_responseTopic); + // } + + // using CorrelationData = Property::Field_correlationData; + // const CorrelationData* m_correlationData = nullptr; + // template + // void operator()(const CorrelationData& field) + // { + // storeProp(field, m_correlationData); + // } + + // using SubscriptionId = Property::Field_subscriptionId; + // using SubscriptionIdsList = ObjListType; + // SubscriptionIdsList m_subscriptionIds; + // template + // void operator()(const SubscriptionId& field) + // { + // if constexpr (Config::HasSubIds) { + // if (m_subscriptionIds.max_size() <= m_subscriptionIds.size()) { + // return; + // } + + // m_subscriptionIds.push_back(&field); + // if (field.field_value().value() == 0U) { + // m_protocolError = true; + // } + // } + // } + + // using SessionExpiryInterval = Property::Field_sessionExpiryInterval; + // const SessionExpiryInterval* m_sessionExpiryInterval = nullptr; + // template + // void operator()(const SessionExpiryInterval& field) + // { + // storeProp(field, m_sessionExpiryInterval); + // } + + // using AssignedClientId = Property::Field_assignedClientId; + // const AssignedClientId* m_assignedClientId = nullptr; + // template + // void operator()(const AssignedClientId& field) + // { + // storeProp(field, m_assignedClientId); + // } + + // using ServerKeepAlive = Property::Field_serverKeepAlive; + // const ServerKeepAlive* m_serverKeepAlive = nullptr; + // template + // void operator()(const ServerKeepAlive& field) + // { + // storeProp(field, m_serverKeepAlive); + // } + + using AuthMethod = Property::Field_authMethod; + std::string m_authMethod; + template + void operator()(const AuthMethod& field) + { + m_authMethod = field.field_value().getValue(); + } + + using AuthData = Property::Field_authData; + RawDataBuf m_authData; + template + void operator()(const AuthData& field) + { + m_authData = field.field_value().getValue(); + } + + // using RequestProblemInfo = Property::Field_requestProblemInfo; + // const RequestProblemInfo* m_requestProblemInfo = nullptr; + // template + // void operator()(const RequestProblemInfo& field) + // { + // storeProp(field, m_requestProblemInfo); + // } + + // using WillDelayInterval = Property::Field_willDelayInterval; + // const WillDelayInterval* m_willDelayInterval = nullptr; + // template + // void operator()(const WillDelayInterval& field) + // { + // storeProp(field, m_willDelayInterval); + // } + + // using RequestResponseInfo = Property::Field_requestResponseInfo; + // const RequestResponseInfo* m_requestResponseInfo = nullptr; + // template + // void operator()(const RequestResponseInfo& field) + // { + // storeProp(field, m_requestResponseInfo); + // } + + // using ResponseInfo = Property::Field_responseInfo; + // const ResponseInfo* m_responseInfo = nullptr; + // template + // void operator()(const ResponseInfo& field) + // { + // storeProp(field, m_responseInfo); + // } + + // using ServerRef = Property::Field_serverRef; + // const ServerRef* m_serverRef = nullptr; + // template + // void operator()(const ServerRef& field) + // { + // storeProp(field, m_serverRef); + // } + + // using ReasonStr = Property::Field_reasonStr; + // const ReasonStr* m_reasonStr = nullptr; + // template + // void operator()(const ReasonStr& field) + // { + // storeProp(field, m_reasonStr); + // } + + using ReceiveMax = Property::Field_receiveMax; + unsigned m_receiveMax = 0U; + template + void operator()(const ReceiveMax& field) + { + m_receiveMax = field.field_value().getValue(); + } + + using TopicAliasMax = Property::Field_topicAliasMax; + unsigned m_topicAliasMax = 0U; + template + void operator()(const TopicAliasMax& field) + { + m_topicAliasMax = field.field_value().getValue(); + } + + // using TopicAlias = Property::Field_topicAlias; + // const TopicAlias* m_topicAlias = nullptr; + // template + // void operator()(const TopicAlias& field) + // { + // storeProp(field, m_topicAlias); + // } + + // using MaxQos = Property::Field_maxQos; + // const MaxQos* m_maxQos = nullptr; + // template + // void operator()(const MaxQos& field) + // { + // storeProp(field, m_maxQos); + // if (field.field_value().value() >= MaxQos::Field_value::ValueType::ExactlyOnceDelivery) { + // m_protocolError = true; + // } + // } + + // using RetainAvailable = Property::Field_retainAvailable; + // const RetainAvailable* m_retainAvailable = nullptr; + // template + // void operator()(const RetainAvailable& field) + // { + // storeProp(field, m_retainAvailable); + // if (!field.field_value().valid()) { + // m_protocolError = true; + // } + // } + + // using UserProperty = Property::Field_userProperty; + // using UserPropsList = ObjListType; + // UserPropsList m_userProps; + // template + // void operator()(const UserProperty& field) + // { + // if constexpr (Config::HasUserProps) { + // if (m_userProps.max_size() <= m_userProps.size()) { + // return; + // } + + // m_userProps.push_back(&field); + // } + // } + + using MaxPacketSize = Property::Field_maxPacketSize; + unsigned m_maxPacketSize = 0U; + template + void operator()(const MaxPacketSize& field) + { + m_maxPacketSize = field.field_value().getValue(); + } + + // using WildcardSubAvail = Property::Field_wildcardSubAvail; + // const WildcardSubAvail* m_wildcardSubAvail = nullptr; + // template + // void operator()(const WildcardSubAvail& field) + // { + // storeProp(field, m_wildcardSubAvail); + // if (!field.field_value().valid()) { + // m_protocolError = true; + // } + // } + + // using SubIdAvail = Property::Field_subIdAvail; + // const SubIdAvail* m_subIdAvail = nullptr; + // template + // void operator()(const SubIdAvail& field) + // { + // storeProp(field, m_subIdAvail); + // if (!field.field_value().valid()) { + // m_protocolError = true; + // } + // } + + // using SharedSubAvail = Property::Field_sharedSubAvail; + // const SharedSubAvail* m_sharedSubAvail = nullptr; + // template + // void operator()(const SharedSubAvail& field) + // { + // storeProp(field, m_sharedSubAvail); + // if (!field.field_value().valid()) { + // m_protocolError = true; + // } + // } + }; + + void sendMessage(Mqtt5Message& msg); + void sendConnack(const Mqtt5ConnectMsg& msg, const PropsHandler& propsHandler); + void sendAuth(const PropsHandler& propsHandler, Mqtt5AuthMsg::Field_reasonCode::Field::ValueType resonCode); + + Logger& m_logger; + std::ofstream m_stream; + DataReportCb m_dataReportCb; + Mqtt5Frame m_frame; + std::unique_ptr m_cachedConnect; + bool m_connected = false;; +}; + +using GeneratorPtr = std::unique_ptr; + +} // namespace cc_mqtt5_client_afl_fuzz diff --git a/client/afl_fuzz/ProgramOptions.cpp b/client/afl_fuzz/ProgramOptions.cpp index ea0d133..a16ec1c 100644 --- a/client/afl_fuzz/ProgramOptions.cpp +++ b/client/afl_fuzz/ProgramOptions.cpp @@ -22,6 +22,7 @@ ProgramOptions::ProgramOptions() ("help,h", "Display help message") ("verbose,v", "Verbose output") ("log-file,f", po::value(), "Output log file") + ("gen-input,g", po::value()->default_value(std::string()), "Generate fuzz input file") ; po::options_description connectOpts("Connect Options"); @@ -81,6 +82,11 @@ std::string ProgramOptions::logFile() const return m_vm["log-file"].as(); } +std::string ProgramOptions::genInputFile() const +{ + return m_vm["gen-input"].as(); +} + std::string ProgramOptions::clientId() const { return m_vm["client-id"].as(); diff --git a/client/afl_fuzz/ProgramOptions.h b/client/afl_fuzz/ProgramOptions.h index 123754a..8e5373e 100644 --- a/client/afl_fuzz/ProgramOptions.h +++ b/client/afl_fuzz/ProgramOptions.h @@ -34,6 +34,7 @@ class ProgramOptions bool verbose() const; bool hasLogFile() const; std::string logFile() const; + std::string genInputFile() const; // Connect options std::string clientId() const; diff --git a/client/lib/src/ClientImpl.cpp b/client/lib/src/ClientImpl.cpp index 68bbb64..2565d3a 100644 --- a/client/lib/src/ClientImpl.cpp +++ b/client/lib/src/ClientImpl.cpp @@ -878,6 +878,7 @@ CC_Mqtt5ErrorCode ClientImpl::sendMessage(const ProtMessage& msg) return CC_Mqtt5ErrorCode_InternalError; } + COMMS_ASSERT(m_sendOutputDataCb != nullptr); m_sendOutputDataCb(m_sendOutputDataData, &m_buf[0], static_cast(len)); for (auto& opPtr : m_keepAliveOps) { From 2a3ae65def2221e9761e67eb1ba8789c64aafa84 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 26 Dec 2023 16:08:31 +1000 Subject: [PATCH 12/35] Increasing robustness of the afl fuzzing app. --- client/afl_fuzz/AflFuzz.cpp.templ | 45 ++++++---- client/afl_fuzz/Generator.cpp | 128 ++++++++++++++++++++++++++++- client/afl_fuzz/Generator.h | 12 ++- client/afl_fuzz/ProgramOptions.cpp | 2 +- 4 files changed, 167 insertions(+), 20 deletions(-) diff --git a/client/afl_fuzz/AflFuzz.cpp.templ b/client/afl_fuzz/AflFuzz.cpp.templ index ccec3d2..f0b0a1e 100644 --- a/client/afl_fuzz/AflFuzz.cpp.templ +++ b/client/afl_fuzz/AflFuzz.cpp.templ @@ -165,12 +165,13 @@ public: break; } - if (dataLen <= consumed) { + if ((dataLen <= consumed) && (dataPtr != m_inData.data())) { + infoLog() << "Consumed all available bytes\n"; m_inData.clear(); continue; } - if (m_inData.empty()) { + if (dataPtr != m_inData.data()) { m_inData.assign(dataPtr + consumed, dataPtr + dataLen); infoLog() << "Keeping " << m_inData.size() << " bytes for the next iteration\n"; continue; @@ -180,6 +181,8 @@ public: infoLog() << "Keeping " << m_inData.size() << " bytes for the next iteration\n"; } + m_client.reset(); + infoLog() << "------------------------------------------------" << std::endl; } @@ -259,7 +262,7 @@ private: CC_Mqtt5ConnectBasicConfig basicConfig; cc_mqtt5_##NAME##client_connect_init_config_basic(&basicConfig); - basicConfig.m_cleanStart = m_state.m_firstConnect; + basicConfig.m_cleanStart = m_state.m_firstConnect || m_opts.clientId().empty(); auto clientId = m_opts.clientId(); if (!clientId.empty()) { @@ -341,20 +344,27 @@ private: while (true) { ec = cc_mqtt5_##NAME##client_subscribe_config_topic(subInfo.m_handle, &config); - assert(ec == CC_Mqtt5ErrorCode_Success); - if (ec != CC_Mqtt5ErrorCode_Success) { - if (config.m_maxQos > CC_Mqtt5QoS_AtMostOnceDelivery) { - config.m_maxQos = - static_cast( - static_cast(config.m_maxQos) - 1U); - continue; - } - - errorLog() << "Unexpected failure in topic subscribe configuration\n"; - exit(-1); - } - - break; + if (ec == CC_Mqtt5ErrorCode_Success) { + break; + } + + if (config.m_maxQos > CC_Mqtt5QoS_AtMostOnceDelivery) { + config.m_maxQos = + static_cast( + static_cast(config.m_maxQos) - 1U); + continue; + } + + auto whildcardPos = std::string(config.m_topic).find_first_of("#+"); + if (whildcardPos != std::string::npos) { + config.m_topic = "some/topic"; + config.m_maxQos = CC_Mqtt5QoS_ExactlyOnceDelivery; + infoLog() << "Changing subscribe topic to " << config.m_topic << std::endl; + continue; + } + + errorLog() << "Unexpected failure in topic subscribe configuration\n"; + exit(-1); } } @@ -423,6 +433,7 @@ private: [this](const std::uint8_t* buf, std::size_t bufLen) { m_inData.insert(m_inData.end(), buf, buf + bufLen); + infoLog() << "Appended " << bufLen << " bytes to buffer, totaling " << m_inData.size() << " bytes\n"; }); return true; diff --git a/client/afl_fuzz/Generator.cpp b/client/afl_fuzz/Generator.cpp index 7e23dcf..e91b583 100644 --- a/client/afl_fuzz/Generator.cpp +++ b/client/afl_fuzz/Generator.cpp @@ -26,6 +26,36 @@ decltype(auto) addProp(TField& field) return vec.back(); } +std::string pubTopicFromFilter(const std::string& filter) +{ + std::string result; + std::size_t pos = 0U; + while (pos < filter.size()) { + auto wildcardPos = filter.find_first_of("#+", pos); + if (wildcardPos == std::string::npos) { + break; + } + + result.append(filter.substr(pos, wildcardPos - pos)); + pos = wildcardPos + 1U; + + if (filter[wildcardPos] == '#') { + result.append("hash"); + pos = filter.size(); + break; + } + + assert(filter[wildcardPos] == '+'); + result.append("plus"); + } + + if (pos < filter.size()) { + result.append(filter.substr(pos)); + } + + return result; +} + } // namespace @@ -80,7 +110,54 @@ void Generator::handle(const Mqtt5SubscribeMsg& msg) sendMessage(outMsg); - // TODO: send publish + for (auto& subElemField : subsVec) { + auto topic = pubTopicFromFilter(subElemField.field_topic().value()); + sendPublish(topic, 0); + sendPublish(topic, 1); + sendPublish(topic, 2); + } +} + +void Generator::handle(const Mqtt5PublishMsg& msg) +{ + m_logger.infoLog() << "Processing " << msg.name() << "\n"; + + using QosValueType = Mqtt5PublishMsg::TransportField_flags::Field_qos::ValueType; + auto qos = msg.transportField_flags().field_qos().value(); + if (qos == QosValueType::AtMostOnceDelivery) { + return; + } + + if (qos == QosValueType::AtLeastOnceDelivery) { + Mqtt5PubackMsg outMsg; + outMsg.field_packetId().setValue(msg.field_packetId().field().getValue()); + sendMessage(outMsg); + return; + } + + assert(qos == QosValueType::ExactlyOnceDelivery); + Mqtt5PubrecMsg outMsg; + outMsg.field_packetId().setValue(msg.field_packetId().field().getValue()); + sendMessage(outMsg); + return; +} + +void Generator::handle(const Mqtt5PubrecMsg& msg) +{ + m_logger.infoLog() << "Processing " << msg.name() << "\n"; + Mqtt5PubrelMsg outMsg; + outMsg.field_packetId().setValue(msg.field_packetId().getValue()); + sendMessage(outMsg); + return; +} + +void Generator::handle(const Mqtt5PubrelMsg& msg) +{ + m_logger.infoLog() << "Processing " << msg.name() << "\n"; + Mqtt5PubcompMsg outMsg; + outMsg.field_packetId().setValue(msg.field_packetId().getValue()); + sendMessage(outMsg); + return; } void Generator::handle(const Mqtt5AuthMsg& msg) @@ -119,6 +196,28 @@ void Generator::handle([[maybe_unused]] const Mqtt5Message& msg) m_logger.infoLog() << "Ignoring " << msg.name() << "\n"; } +unsigned Generator::allocPacketId() +{ + ++m_lastPacketId; + return m_lastPacketId; +} + +unsigned Generator::allocTopicAlias(const std::string& topic) +{ + if (m_topicAliases.size() <= m_topicAliasLimit) { + return 0U; + } + + auto iter = m_topicAliases.find(topic); + if (iter != m_topicAliases.end()) { + return iter->second; + } + + auto topicAlias = static_cast(m_topicAliases.size() + 1U); + m_topicAliases[topic] = topicAlias; + return topicAlias; +} + void Generator::sendMessage(Mqtt5Message& msg) { m_logger.infoLog() << "Generating " << msg.name() << "\n"; @@ -129,6 +228,9 @@ void Generator::sendMessage(Mqtt5Message& msg) [[maybe_unused]] auto es = m_frame.write(msg, iter, outBuf.max_size()); assert(es == comms::ErrorStatus::Success); assert(m_dataReportCb); + + std::ostreambuf_iterator outIter(m_stream); + std::copy(outBuf.begin(), outBuf.end(), outIter); m_dataReportCb(outBuf.data(), outBuf.size()); } @@ -163,12 +265,36 @@ void Generator::sendConnack(const Mqtt5ConnectMsg& msg, const PropsHandler& prop auto& propBundle = propVar.initField_topicAliasMax(); auto& valueField = propBundle.field_value(); valueField.setValue(propsHandler.m_topicAliasMax); + m_topicAliasLimit = propsHandler.m_topicAliasMax; } sendMessage(outMsg); m_connected = true; } +void Generator::sendPublish(const std::string& topic, unsigned qos) +{ + Mqtt5PublishMsg outMsg; + + outMsg.transportField_flags().field_qos().setValue(qos); + outMsg.field_topic().setValue(topic); + if (qos > 0U) { + outMsg.field_packetId().field().setValue(allocPacketId()); + } + outMsg.field_payload().value().assign(topic.begin(), topic.end()); + + auto topicAlias = allocTopicAlias(topic); + if (topicAlias != 0U) { + auto& propsField = outMsg.field_properties(); + auto& propVar = addProp(propsField); + auto& propBundle = propVar.initField_topicAlias(); + auto& valueField = propBundle.field_value(); + valueField.setValue(topicAlias); + } + + sendMessage(outMsg); +} + void Generator::sendAuth(const PropsHandler& propsHandler, Mqtt5AuthMsg::Field_reasonCode::Field::ValueType reasonCode) { Mqtt5AuthMsg outMsg; diff --git a/client/afl_fuzz/Generator.h b/client/afl_fuzz/Generator.h index cf34e50..831dd36 100644 --- a/client/afl_fuzz/Generator.h +++ b/client/afl_fuzz/Generator.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -59,6 +60,9 @@ class Generator void handle(const Mqtt5ConnectMsg& msg); void handle(const Mqtt5SubscribeMsg& msg); + void handle(const Mqtt5PublishMsg& msg); + void handle(const Mqtt5PubrecMsg& msg); + void handle(const Mqtt5PubrelMsg& msg); void handle(const Mqtt5AuthMsg& msg); void handle(const Mqtt5Message& msg); @@ -318,8 +322,11 @@ class Generator // } }; + unsigned allocPacketId(); + unsigned allocTopicAlias(const std::string& topic); void sendMessage(Mqtt5Message& msg); void sendConnack(const Mqtt5ConnectMsg& msg, const PropsHandler& propsHandler); + void sendPublish(const std::string& topic, unsigned qos); void sendAuth(const PropsHandler& propsHandler, Mqtt5AuthMsg::Field_reasonCode::Field::ValueType resonCode); Logger& m_logger; @@ -327,7 +334,10 @@ class Generator DataReportCb m_dataReportCb; Mqtt5Frame m_frame; std::unique_ptr m_cachedConnect; - bool m_connected = false;; + unsigned m_lastPacketId = 0U; + unsigned m_topicAliasLimit = 0U; + std::map m_topicAliases; + bool m_connected = false; }; using GeneratorPtr = std::unique_ptr; diff --git a/client/afl_fuzz/ProgramOptions.cpp b/client/afl_fuzz/ProgramOptions.cpp index a16ec1c..6eedf62 100644 --- a/client/afl_fuzz/ProgramOptions.cpp +++ b/client/afl_fuzz/ProgramOptions.cpp @@ -27,7 +27,7 @@ ProgramOptions::ProgramOptions() po::options_description connectOpts("Connect Options"); connectOpts.add_options() - ("client-id,i", po::value()->default_value(std::string()), "Client ID") + ("client-id,i", po::value()->default_value("afl_client"), "Client ID") ("receive-max", po::value()->default_value(0), "\"Receive Maximum\" property. 0 means not set.") ("max-packet-size", po::value()->default_value(0), "\"Maximum Packet Size\" property. 0 means no limit.") ("topic-alias-max", po::value()->default_value(0), "\"Topic Alias Maximum\" property.") From 4db8d363287a9f0147d71b5a0477f1ea0d0c3ede Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 26 Dec 2023 16:13:49 +1000 Subject: [PATCH 13/35] Added build of cc_mqtt5_client_afl_fuzz application to appveyor and github actions builds. --- .appveyor.yml | 2 +- .github/workflows/actions_build.yml | 12 ++++++++---- script/full_build.sh | 4 +++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 1b304cd..8f7bcc3 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -46,7 +46,7 @@ build_script: - cd %BUILD_DIR% - cmake .. -DCMAKE_BUILD_TYPE=%CONFIGURATION% -G "NMake Makefiles" -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 ^ + -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 - 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 f263935..6cb5e4f 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -50,8 +50,10 @@ jobs: run: | cmake $GITHUB_WORKSPACE -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/BareMetalConfig.cmake + -DCC_MQTT5_WITH_DEFAULT_SANITIZERS=ON -DCC_MQTT5_DISABLE_FALSE_POSITIVE_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_CLIENT_AFL_FUZZ=ON env: CC: gcc-${{matrix.cc_ver}} CXX: g++-${{matrix.cc_ver}} @@ -118,8 +120,10 @@ jobs: run: | cmake $GITHUB_WORKSPACE -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/BareMetalConfig.cmake + -DCC_MQTT5_WITH_DEFAULT_SANITIZERS=ON -DCC_MQTT5_DISABLE_FALSE_POSITIVE_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_CLIENT_AFL_FUZZ=ON env: CC: clang-${{matrix.cc_ver}} CXX: clang++-${{matrix.cc_ver}} diff --git a/script/full_build.sh b/script/full_build.sh index 2bee77d..62506a6 100755 --- a/script/full_build.sh +++ b/script/full_build.sh @@ -23,7 +23,9 @@ ${SCRIPT_DIR}/prepare_externals.sh cd ${BUILD_DIR} cmake .. -DCMAKE_INSTALL_PREFIX=${COMMON_INSTALL_DIR} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \ - -DCC_MQTT5_WITH_DEFAULT_SANITIZERS=ON -DCC_MQTT5_BUILD_UNIT_TESTS=ON -DCC_MQTT5_BUILD_INTEGRATION_TESTS=ON \ + -DCC_MQTT5_WITH_DEFAULT_SANITIZERS=ON -DCC_MQTT5_DISABLE_FALSE_POSITIVE_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" "$@" procs=$(nproc) From 2dc71060af8fc06d4b35ef007abca1d3a6913ff3 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Tue, 26 Dec 2023 19:27:08 +1000 Subject: [PATCH 14/35] Fixing Windows build. --- client/afl_fuzz/AflFuzz.cpp.templ | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/client/afl_fuzz/AflFuzz.cpp.templ b/client/afl_fuzz/AflFuzz.cpp.templ index f0b0a1e..801148f 100644 --- a/client/afl_fuzz/AflFuzz.cpp.templ +++ b/client/afl_fuzz/AflFuzz.cpp.templ @@ -14,7 +14,11 @@ #include "cc_mqtt5/Message.h" #include "cc_mqtt5/frame/Frame.h" +#ifdef WIN32 +#include +#else // #ifdef WIN32 #include +#endif // #ifdef WIN32 #include #include @@ -69,7 +73,9 @@ public: void run() { +#ifndef WIN32 sync(); +#endif std::uint8_t buf[BufSize] = {0}; while (true) { @@ -99,10 +105,14 @@ public: } while (false); - ssize_t len = 0; + int len = 0; bool noMoreRead = true; if (!m_generator) { - len = read(0, buf, BufSize); +#ifdef WIN32 + len = _read(0, buf, BufSize); +#else // #ifdef WIN32 + len = static_cast(read(0, buf, BufSize)); +#endif // #ifdef WIN32 noMoreRead = (len <= 0); len = std::max(len, static_cast(0)); infoLog() << "Read " << len << " bytes\n"; From 44ffaea94ef1a9cf2b4226a769698f3161ec2196 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Wed, 27 Dec 2023 09:11:36 +1000 Subject: [PATCH 15/35] Reducing number of forced exits in the "afl_fuzz" application. --- client/afl_fuzz/AflFuzz.cpp.templ | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/client/afl_fuzz/AflFuzz.cpp.templ b/client/afl_fuzz/AflFuzz.cpp.templ index 801148f..7ac3f20 100644 --- a/client/afl_fuzz/AflFuzz.cpp.templ +++ b/client/afl_fuzz/AflFuzz.cpp.templ @@ -342,7 +342,7 @@ private: assert(ec == CC_Mqtt5ErrorCode_Success); if (subInfo.m_handle == nullptr) { errorLog() << "Unexpected failure in subscribe allocation\n"; - exit(-1); + continue; } for (auto& t : subInfo.m_topics) { @@ -374,15 +374,14 @@ private: } errorLog() << "Unexpected failure in topic subscribe configuration\n"; - exit(-1); + break; } } ec = cc_mqtt5_##NAME##client_subscribe_send(subInfo.m_handle, &AflFuzzImpl::subscribeCompleteCb, this); - assert(ec == CC_Mqtt5ErrorCode_Success); if (ec != CC_Mqtt5ErrorCode_Success) { errorLog() << "Unexpected failure in sending subscribe request"; - exit(-1); + subInfo.m_handle = nullptr; } } } @@ -393,13 +392,16 @@ private: return; } + m_state.m_reauthRequired = false; + CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_ValuesLimit; auto reauth = cc_mqtt5_##NAME##client_reauth_prepare(m_client.get(), &ec); assert(reauth != nullptr); assert(ec == CC_Mqtt5ErrorCode_Success); if (reauth == nullptr) { errorLog() << "Unexpected failure in reauth allocation\n"; - exit(-1); + m_state.m_reauthComplete = true; + return; } auto authMethod = m_opts.authMethod(); @@ -415,16 +417,17 @@ private: ec = ::cc_mqtt5_##NAME##client_reauth_config_auth(reauth, &authConfig); if (ec != CC_Mqtt5ErrorCode_Success) { errorLog() << "Unexpected failure in reauth configuration\n"; - exit(-1); + cc_mqtt5_##NAME##client_reauth_cancel(reauth); + m_state.m_reauthComplete = true; + return; } ec = ::cc_mqtt5_##NAME##client_reauth_send(reauth, &AflFuzzImpl::reauthCompleteCb, this); if (ec != CC_Mqtt5ErrorCode_Success) { errorLog() << "Unexpected failure in sending reauth\n"; - exit(-1); + m_state.m_reauthComplete = true; + return; } - - m_state.m_reauthRequired = false; } bool createGeneratorIfNeeded() From 6e597597a4c6db22aad6edb4c24b2614e74328c9 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 28 Dec 2023 12:43:58 +1000 Subject: [PATCH 16/35] More improvements to fuzz testing. --- client/afl_fuzz/AflFuzz.cpp.templ | 272 +++++++++++++++++++++++++---- client/afl_fuzz/Generator.cpp | 88 +++++++--- client/afl_fuzz/Generator.h | 210 +--------------------- client/afl_fuzz/ProgramOptions.cpp | 13 +- client/afl_fuzz/ProgramOptions.h | 3 + client/lib/src/TimerMgr.cpp | 3 +- 6 files changed, 329 insertions(+), 260 deletions(-) diff --git a/client/afl_fuzz/AflFuzz.cpp.templ b/client/afl_fuzz/AflFuzz.cpp.templ index 7ac3f20..bfaa1f9 100644 --- a/client/afl_fuzz/AflFuzz.cpp.templ +++ b/client/afl_fuzz/AflFuzz.cpp.templ @@ -81,6 +81,11 @@ public: while (true) { m_logger.flush(); + if (cc_mqtt5_##NAME##client_is_network_disconnected(m_client.get())) { + infoLog() << "Terminating execution loop when network is disconnected" << std::endl; + break; + } + if (m_state.m_reinitRequired) { doReinit(); } @@ -101,6 +106,8 @@ public: doSubscribeIfNeeded(); doReauthIfNeeded(); + doUnsubscribeIfNeeded(); + doDisconnectIfNeeded(); } while (false); @@ -127,6 +134,7 @@ public: auto dataPtr = &buf[0]; auto dataLen = static_cast(len); + bool hasPrevData = false; if (!m_inData.empty()) { m_inData.insert(m_inData.end(), dataPtr, dataPtr + dataLen); static const auto MaxLen = std::numeric_limits::max(); @@ -137,6 +145,7 @@ public: dataPtr = m_inData.data(); dataLen = m_inData.size(); + hasPrevData = true; } using Interface = cc_mqtt5::Message<>; @@ -145,12 +154,18 @@ public: static constexpr auto MinLen = IdAndFlagsField::minLength() + SizeField::minLength(); if (dataLen < MinLen) { - if (noMoreRead) { - infoLog() << "Insufficient data in buffer: " << dataLen << std::endl; - break; + if (!noMoreRead) { + continue; } - continue; + 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); + continue; + } + + infoLog() << "Insufficient data in buffer (" << dataLen << "), stopping execution loop" << std::endl; + break; } auto iter = dataPtr; @@ -164,24 +179,34 @@ public: processLen = std::min(dataLen, msgLen); } - infoLog() << "Processing " << processLen << " out of " << dataLen << " bytes...\n"; - auto consumed = cc_mqtt5_##NAME##client_process_data(m_client.get(), dataPtr, static_cast(processLen)); - infoLog() << "Consumed: " << consumed << '\n'; - - assert((consumed > 0U) || (!m_state.m_disconnected)); + auto consumed = 0U; + if (!cc_mqtt5_##NAME##client_is_network_disconnected(m_client.get())) { + infoLog() << "Processing " << processLen << " out of " << dataLen << " bytes...\n"; + consumed = cc_mqtt5_##NAME##client_process_data(m_client.get(), dataPtr, static_cast(processLen)); + infoLog() << "Consumed: " << consumed << '\n'; + } + else { + infoLog() << "Network is disconnected, not consuming any data" << '\n'; + } if ((consumed == 0U) && noMoreRead) { - infoLog() << "No data consumed and no more input\n"; + 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); + continue; + } + + infoLog() << "No data consumed and no more input, stopping execution loop\n"; break; } - if ((dataLen <= consumed) && (dataPtr != m_inData.data())) { + if ((dataLen <= consumed) && (!hasPrevData)) { infoLog() << "Consumed all available bytes\n"; m_inData.clear(); continue; } - if (dataPtr != m_inData.data()) { + if (!hasPrevData) { m_inData.assign(dataPtr + consumed, dataPtr + dataLen); infoLog() << "Keeping " << m_inData.size() << " bytes for the next iteration\n"; continue; @@ -210,14 +235,17 @@ private: struct SubscribeInfo { std::vector m_topics; - CC_Mqtt5SubscribeHandle m_handle = nullptr; + CC_Mqtt5SubscribeHandle m_subHandle = nullptr; + CC_Mqtt5UnsubscribeHandle m_unsubHandle = nullptr; bool m_acked = false; + bool m_unsubscribed = false; }; struct State { std::vector m_subs; unsigned m_nextTickDuration = 0U; + unsigned m_publishCount = 0U; bool m_reinitRequired = true; bool m_connectRequired = true; bool m_connected = false; @@ -248,6 +276,7 @@ private: m_state.m_reinitRequired = false; 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); @@ -329,7 +358,7 @@ private: void doSubscribeIfNeeded() { for (auto& subInfo : m_state.m_subs) { - if ((subInfo.m_handle != nullptr) || (subInfo.m_acked)) { + if ((subInfo.m_subHandle != nullptr) || (subInfo.m_acked)) { // Request is sent continue; } @@ -337,14 +366,16 @@ private: infoLog() << "Attempting subscribe\n"; CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_ValuesLimit; - subInfo.m_handle = cc_mqtt5_##NAME##client_subscribe_prepare(m_client.get(), &ec); - assert(subInfo.m_handle != nullptr); - assert(ec == CC_Mqtt5ErrorCode_Success); - if (subInfo.m_handle == nullptr) { + subInfo.m_subHandle = cc_mqtt5_##NAME##client_subscribe_prepare(m_client.get(), &ec); + + if (subInfo.m_subHandle == nullptr) { errorLog() << "Unexpected failure in subscribe allocation\n"; + subInfo.m_acked = true; continue; } + assert(ec == CC_Mqtt5ErrorCode_Success); + for (auto& t : subInfo.m_topics) { assert(!t.empty()); infoLog() << "Adding topic " << t << "\n"; @@ -353,7 +384,7 @@ private: config.m_topic = t.c_str(); while (true) { - ec = cc_mqtt5_##NAME##client_subscribe_config_topic(subInfo.m_handle, &config); + ec = cc_mqtt5_##NAME##client_subscribe_config_topic(subInfo.m_subHandle, &config); if (ec == CC_Mqtt5ErrorCode_Success) { break; } @@ -378,10 +409,11 @@ private: } } - ec = cc_mqtt5_##NAME##client_subscribe_send(subInfo.m_handle, &AflFuzzImpl::subscribeCompleteCb, this); + ec = cc_mqtt5_##NAME##client_subscribe_send(subInfo.m_subHandle, &AflFuzzImpl::subscribeCompleteCb, this); if (ec != CC_Mqtt5ErrorCode_Success) { errorLog() << "Unexpected failure in sending subscribe request"; - subInfo.m_handle = nullptr; + subInfo.m_subHandle = nullptr; + subInfo.m_acked = true; } } } @@ -396,13 +428,13 @@ private: CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_ValuesLimit; auto reauth = cc_mqtt5_##NAME##client_reauth_prepare(m_client.get(), &ec); - assert(reauth != nullptr); - assert(ec == CC_Mqtt5ErrorCode_Success); if (reauth == nullptr) { errorLog() << "Unexpected failure in reauth allocation\n"; m_state.m_reauthComplete = true; return; - } + } + + assert(ec == CC_Mqtt5ErrorCode_Success); auto authMethod = m_opts.authMethod(); assert(!authMethod.empty()); @@ -430,6 +462,121 @@ private: } } + void doUnsubscribeIfNeeded() { + + if (m_state.m_publishCount < m_opts.minPubCount()) { + return; + } + + bool subsComplete = + std::all_of( + m_state.m_subs.begin(), m_state.m_subs.end(), + [](auto& subInfo) + { + return subInfo.m_acked; + }); + + if (!subsComplete) { + return; + } + + for (auto& subInfo : m_state.m_subs) { + if ((subInfo.m_unsubHandle != nullptr) || (subInfo.m_unsubscribed)) { + // Request is sent / completed + continue; + } + + infoLog() << "Attempting unsubscribe\n"; + + CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_ValuesLimit; + subInfo.m_unsubHandle = cc_mqtt5_##NAME##client_unsubscribe_prepare(m_client.get(), &ec); + if (subInfo.m_unsubHandle == nullptr) { + errorLog() << "Unexpected failure in unsubscribe allocation\n"; + subInfo.m_unsubscribed = true; + continue; + } + + assert(ec == CC_Mqtt5ErrorCode_Success); + + for (auto& t : subInfo.m_topics) { + assert(!t.empty()); + infoLog() << "Adding topic " << t << "\n"; + CC_Mqtt5UnsubscribeTopicConfig config; + cc_mqtt5_##NAME##client_unsubscribe_init_config_topic(&config); + config.m_topic = t.c_str(); + + while (true) { + ec = cc_mqtt5_##NAME##client_unsubscribe_config_topic(subInfo.m_unsubHandle, &config); + if (ec == CC_Mqtt5ErrorCode_Success) { + break; + } + + if (!cc_mqtt5_##NAME##client_get_verify_outgoing_topic_enabled(m_client.get())) { + errorLog() << "Unexpected failure in configuring unsubscribe topic\n"; + cc_mqtt5_##NAME##client_unsubscribe_cancel(subInfo.m_unsubHandle); + break; + } + + cc_mqtt5_##NAME##client_set_verify_outgoing_topic_enabled(m_client.get(), false); + } + } + + ec = cc_mqtt5_##NAME##client_unsubscribe_send(subInfo.m_unsubHandle, &AflFuzzImpl::unsubscribeCompleteCb, this); + if (ec != CC_Mqtt5ErrorCode_Success) { + errorLog() << "Unexpected failure in sending unsubscribe request"; + subInfo.m_unsubHandle = nullptr; + subInfo.m_unsubscribed = true; + } + } + } + + void doDisconnectIfNeeded() + { + if (m_state.m_disconnected) { + return; + } + + bool unsubsComplete = + std::all_of( + m_state.m_subs.begin(), m_state.m_subs.end(), + [](auto& subInfo) + { + return subInfo.m_unsubscribed; + }); + + if (!unsubsComplete) { + return; + } + + m_state.m_disconnected = true; + m_state.m_reinitRequired = true; + + CC_Mqtt5ErrorCode ec = CC_Mqtt5ErrorCode_ValuesLimit; + auto handle = cc_mqtt5_##NAME##client_disconnect_prepare(m_client.get(), &ec); + if (handle == nullptr) { + errorLog() << "Unexpected failure in disconnect allocation\n"; + return; + } + + assert(ec == CC_Mqtt5ErrorCode_Success); + + CC_Mqtt5DisconnectConfig config; + cc_mqtt5_##NAME##client_disconnect_init_config(&config); + + ec = cc_mqtt5_##NAME##client_disconnect_config(handle, &config); + if (ec != CC_Mqtt5ErrorCode_Success) { + errorLog() << "Failed to configure disconnect\n"; + cc_mqtt5_##NAME##client_disconnect_cancel(handle); + return; + } + + ec = cc_mqtt5_##NAME##client_disconnect_send(handle); + if (ec != CC_Mqtt5ErrorCode_Success) { + errorLog() << "Failed to send disconnect\n"; + return; + } + } + bool createGeneratorIfNeeded() { auto inputFile = m_opts.genInputFile(); @@ -437,7 +584,7 @@ private: return true; } - m_generator = std::make_unique(m_logger); + m_generator = std::make_unique(m_logger, m_opts.minPubCount()); if (!m_generator->prepare(inputFile)) { return false; } @@ -491,9 +638,12 @@ private: asThis(data)->m_logger.infoLog() << "Broker disconnected\n"; auto& state = asThis(data)->m_state; state.m_disconnected = true; - state.m_reinitRequired = true; - state.m_connectRequired = 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; + } } static void messageReceivedReportCb(void* data, const CC_Mqtt5MessageInfo* info) @@ -568,7 +718,7 @@ private: } } - ec = cc_mqtt5_##NAME##client_publish_send(publish, nullptr, nullptr); + ec = cc_mqtt5_##NAME##client_publish_send(publish, &AflFuzzImpl::publishCompleteCb, thisPtr); if (ec != CC_Mqtt5ErrorCode_Success) { thisPtr->errorLog() << "Failure to publish message\n"; } @@ -600,13 +750,16 @@ private: if (!response->m_sessionPresent) { for (auto& subInfo : state.m_subs) { - assert(subInfo.m_handle == nullptr); + assert(subInfo.m_subHandle == nullptr); + assert(subInfo.m_unsubHandle == nullptr); subInfo.m_acked = false; + subInfo.m_unsubscribed = false; } } state.m_connectRequired = false; state.m_connected = true; + state.m_disconnected = false; state.m_firstConnect = false; return; } while (false); @@ -625,7 +778,7 @@ private: state.m_subs.begin(), state.m_subs.end(), [handle](auto& info) { - return info.m_handle == handle; + return info.m_subHandle == handle; }); if (iter == state.m_subs.end()) { @@ -634,7 +787,7 @@ private: } assert(!iter->m_acked); - iter->m_handle = nullptr; + iter->m_subHandle = nullptr; if (status != CC_Mqtt5AsyncOpStatus_Complete) { thisPtr->errorLog() << "Subscribe operation is terminated\n"; return; @@ -654,7 +807,22 @@ private: thisPtr->infoLog() << "The subscribe is properly acked\n"; } iter->m_acked = allOk; - } + } + + static void publishCompleteCb( + void* data, + [[maybe_unused]] CC_Mqtt5PublishHandle handle, + CC_Mqtt5AsyncOpStatus status, + [[maybe_unused]] const CC_Mqtt5PublishResponse* response) + { + auto* thisPtr = asThis(data); + if (status != CC_Mqtt5AsyncOpStatus_Complete) { + thisPtr->errorLog() << "Publish operation is terminated\n"; + return; + } + + ++thisPtr->m_state.m_publishCount; + } static CC_Mqtt5AuthErrorCode authCb(void* data, const CC_Mqtt5AuthInfo* authInfoIn, CC_Mqtt5AuthInfo* authInfoOut) { @@ -693,6 +861,46 @@ private: state.m_reauthRequired = true; } + static void unsubscribeCompleteCb(void* data, CC_Mqtt5UnsubscribeHandle handle, CC_Mqtt5AsyncOpStatus status, const CC_Mqtt5UnsubscribeResponse* response) + { + auto* thisPtr = asThis(data); + auto& state = thisPtr->m_state; + auto iter = + std::find_if( + state.m_subs.begin(), state.m_subs.end(), + [handle](auto& info) + { + return info.m_unsubHandle == handle; + }); + + if (iter == state.m_subs.end()) { + thisPtr->errorLog() << "Unexpected handle of the unsubscribe completion\n"; + exit(-1); + } + + assert(!iter->m_unsubscribed); + iter->m_unsubHandle = nullptr; + if (status != CC_Mqtt5AsyncOpStatus_Complete) { + thisPtr->errorLog() << "Unsubscribe operation is terminated\n"; + return; + } + + assert(response != nullptr); + bool allOk = true; + for (auto idx = 0U; idx < response->m_reasonCodesCount; ++idx) { + if (CC_Mqtt5ReasonCode_UnspecifiedError <= response->m_reasonCodes[idx]) { + thisPtr->errorLog() << "Unsubscribe rejected\n"; + allOk = false; + break; + } + } + + if (allOk) { + thisPtr->infoLog() << "The unsubscribe is properly acked\n"; + } + iter->m_unsubscribed = allOk; + } + const ProgramOptions& m_opts; Logger& m_logger; ClientPtr m_client; diff --git a/client/afl_fuzz/Generator.cpp b/client/afl_fuzz/Generator.cpp index e91b583..d1f1b90 100644 --- a/client/afl_fuzz/Generator.cpp +++ b/client/afl_fuzz/Generator.cpp @@ -93,31 +93,6 @@ void Generator::handle(const Mqtt5ConnectMsg& msg) sendConnack(msg, propsHandler); } -void Generator::handle(const Mqtt5SubscribeMsg& msg) -{ - m_logger.infoLog() << "Processing " << msg.name() << "\n"; - auto& subsVec = msg.field_list().value(); - - Mqtt5SubackMsg outMsg; - outMsg.field_packetId().value() = msg.field_packetId().value(); - auto& ackVec = outMsg.field_list().value(); - ackVec.resize(subsVec.size()); - - for (auto& elem : ackVec) { - using ElemType = std::decay_t; - elem.setValue(ElemType::ValueType::GrantedQos2); - } - - sendMessage(outMsg); - - for (auto& subElemField : subsVec) { - auto topic = pubTopicFromFilter(subElemField.field_topic().value()); - sendPublish(topic, 0); - sendPublish(topic, 1); - sendPublish(topic, 2); - } -} - void Generator::handle(const Mqtt5PublishMsg& msg) { m_logger.infoLog() << "Processing " << msg.name() << "\n"; @@ -125,6 +100,8 @@ void Generator::handle(const Mqtt5PublishMsg& msg) using QosValueType = Mqtt5PublishMsg::TransportField_flags::Field_qos::ValueType; auto qos = msg.transportField_flags().field_qos().value(); if (qos == QosValueType::AtMostOnceDelivery) { + m_logger.infoLog() << "at most once" << "\n"; + doNextPublishIfNeeded(); return; } @@ -132,6 +109,7 @@ void Generator::handle(const Mqtt5PublishMsg& msg) Mqtt5PubackMsg outMsg; outMsg.field_packetId().setValue(msg.field_packetId().field().getValue()); sendMessage(outMsg); + doNextPublishIfNeeded(); return; } @@ -157,9 +135,51 @@ void Generator::handle(const Mqtt5PubrelMsg& msg) Mqtt5PubcompMsg outMsg; outMsg.field_packetId().setValue(msg.field_packetId().getValue()); sendMessage(outMsg); + doNextPublishIfNeeded(); return; } +void Generator::handle(const Mqtt5SubscribeMsg& msg) +{ + m_logger.infoLog() << "Processing " << msg.name() << "\n"; + auto& subsVec = msg.field_list().value(); + + Mqtt5SubackMsg outMsg; + outMsg.field_packetId().value() = msg.field_packetId().value(); + auto& ackVec = outMsg.field_list().value(); + ackVec.resize(subsVec.size()); + + for (auto& elem : ackVec) { + using ElemType = std::decay_t; + elem.setValue(ElemType::ValueType::GrantedQos2); + } + + sendMessage(outMsg); + + for (auto& subElemField : subsVec) { + m_lastPubTopic = pubTopicFromFilter(subElemField.field_topic().value()); + doPublish(); + } +} + +void Generator::handle(const Mqtt5UnsubscribeMsg& msg) +{ + m_logger.infoLog() << "Processing " << msg.name() << "\n"; + auto& subsVec = msg.field_list().value(); + + Mqtt5UnsubackMsg outMsg; + outMsg.field_packetId().value() = msg.field_packetId().value(); + auto& ackVec = outMsg.field_list().value(); + ackVec.resize(subsVec.size()); + + for (auto& elem : ackVec) { + using ElemType = std::decay_t; + elem.setValue(ElemType::ValueType::Success); + } + + sendMessage(outMsg); +} + void Generator::handle(const Mqtt5AuthMsg& msg) { m_logger.infoLog() << "Processing " << msg.name() << "\n"; @@ -293,6 +313,7 @@ void Generator::sendPublish(const std::string& topic, unsigned qos) } sendMessage(outMsg); + ++m_pubCount; } void Generator::sendAuth(const PropsHandler& propsHandler, Mqtt5AuthMsg::Field_reasonCode::Field::ValueType reasonCode) @@ -321,4 +342,21 @@ void Generator::sendAuth(const PropsHandler& propsHandler, Mqtt5AuthMsg::Field_r sendMessage(outMsg); } +void Generator::doPublish() +{ + assert(!m_lastPubTopic.empty()); + sendPublish(m_lastPubTopic, m_nextPubQos); + ++m_nextPubQos; + if (m_nextPubQos > 2) { + m_nextPubQos = 0; + } +} + +void Generator::doNextPublishIfNeeded() +{ + if (m_pubCount < m_minPubCount) { + doPublish(); + } +} + } // namespace cc_mqtt5_client_afl_fuzz diff --git a/client/afl_fuzz/Generator.h b/client/afl_fuzz/Generator.h index 831dd36..aa04a8e 100644 --- a/client/afl_fuzz/Generator.h +++ b/client/afl_fuzz/Generator.h @@ -47,7 +47,7 @@ class Generator using DataReportCb = std::function; - Generator(Logger& logger) : m_logger(logger) {}; + Generator(Logger& logger, unsigned minPubCount) : m_logger(logger), m_minPubCount(minPubCount) {}; bool prepare(const std::string& inputFile); void processData(const std::uint8_t* buf, unsigned bufLen); @@ -59,11 +59,12 @@ class Generator } void handle(const Mqtt5ConnectMsg& msg); - void handle(const Mqtt5SubscribeMsg& msg); void handle(const Mqtt5PublishMsg& msg); void handle(const Mqtt5PubrecMsg& msg); void handle(const Mqtt5PubrelMsg& msg); void handle(const Mqtt5AuthMsg& msg); + void handle(const Mqtt5SubscribeMsg& msg); + void handle(const Mqtt5UnsubscribeMsg& msg); void handle(const Mqtt5Message& msg); private: @@ -81,80 +82,6 @@ class Generator { } - // using MessageExpiryInterval = Property::Field_messageExpiryInterval; - // unsigned m_messageExpiryInterval = nullptr; - // template - // void operator()(const MessageExpiryInterval& field) - // { - // m_messageExpiryInterval = field.field_value().getValue(); - // } - - // using ContentType = Property::Field_contentType; - // const ContentType* m_contentType = nullptr; - // template - // void operator()(const ContentType& field) - // { - // storeProp(field, m_contentType); - // } - - // using ResponseTopic = Property::Field_responseTopic; - // const ResponseTopic* m_responseTopic = nullptr; - // template - // void operator()(const ResponseTopic& field) - // { - // storeProp(field, m_responseTopic); - // } - - // using CorrelationData = Property::Field_correlationData; - // const CorrelationData* m_correlationData = nullptr; - // template - // void operator()(const CorrelationData& field) - // { - // storeProp(field, m_correlationData); - // } - - // using SubscriptionId = Property::Field_subscriptionId; - // using SubscriptionIdsList = ObjListType; - // SubscriptionIdsList m_subscriptionIds; - // template - // void operator()(const SubscriptionId& field) - // { - // if constexpr (Config::HasSubIds) { - // if (m_subscriptionIds.max_size() <= m_subscriptionIds.size()) { - // return; - // } - - // m_subscriptionIds.push_back(&field); - // if (field.field_value().value() == 0U) { - // m_protocolError = true; - // } - // } - // } - - // using SessionExpiryInterval = Property::Field_sessionExpiryInterval; - // const SessionExpiryInterval* m_sessionExpiryInterval = nullptr; - // template - // void operator()(const SessionExpiryInterval& field) - // { - // storeProp(field, m_sessionExpiryInterval); - // } - - // using AssignedClientId = Property::Field_assignedClientId; - // const AssignedClientId* m_assignedClientId = nullptr; - // template - // void operator()(const AssignedClientId& field) - // { - // storeProp(field, m_assignedClientId); - // } - - // using ServerKeepAlive = Property::Field_serverKeepAlive; - // const ServerKeepAlive* m_serverKeepAlive = nullptr; - // template - // void operator()(const ServerKeepAlive& field) - // { - // storeProp(field, m_serverKeepAlive); - // } - using AuthMethod = Property::Field_authMethod; std::string m_authMethod; template @@ -171,54 +98,6 @@ class Generator m_authData = field.field_value().getValue(); } - // using RequestProblemInfo = Property::Field_requestProblemInfo; - // const RequestProblemInfo* m_requestProblemInfo = nullptr; - // template - // void operator()(const RequestProblemInfo& field) - // { - // storeProp(field, m_requestProblemInfo); - // } - - // using WillDelayInterval = Property::Field_willDelayInterval; - // const WillDelayInterval* m_willDelayInterval = nullptr; - // template - // void operator()(const WillDelayInterval& field) - // { - // storeProp(field, m_willDelayInterval); - // } - - // using RequestResponseInfo = Property::Field_requestResponseInfo; - // const RequestResponseInfo* m_requestResponseInfo = nullptr; - // template - // void operator()(const RequestResponseInfo& field) - // { - // storeProp(field, m_requestResponseInfo); - // } - - // using ResponseInfo = Property::Field_responseInfo; - // const ResponseInfo* m_responseInfo = nullptr; - // template - // void operator()(const ResponseInfo& field) - // { - // storeProp(field, m_responseInfo); - // } - - // using ServerRef = Property::Field_serverRef; - // const ServerRef* m_serverRef = nullptr; - // template - // void operator()(const ServerRef& field) - // { - // storeProp(field, m_serverRef); - // } - - // using ReasonStr = Property::Field_reasonStr; - // const ReasonStr* m_reasonStr = nullptr; - // template - // void operator()(const ReasonStr& field) - // { - // storeProp(field, m_reasonStr); - // } - using ReceiveMax = Property::Field_receiveMax; unsigned m_receiveMax = 0U; template @@ -235,51 +114,6 @@ class Generator m_topicAliasMax = field.field_value().getValue(); } - // using TopicAlias = Property::Field_topicAlias; - // const TopicAlias* m_topicAlias = nullptr; - // template - // void operator()(const TopicAlias& field) - // { - // storeProp(field, m_topicAlias); - // } - - // using MaxQos = Property::Field_maxQos; - // const MaxQos* m_maxQos = nullptr; - // template - // void operator()(const MaxQos& field) - // { - // storeProp(field, m_maxQos); - // if (field.field_value().value() >= MaxQos::Field_value::ValueType::ExactlyOnceDelivery) { - // m_protocolError = true; - // } - // } - - // using RetainAvailable = Property::Field_retainAvailable; - // const RetainAvailable* m_retainAvailable = nullptr; - // template - // void operator()(const RetainAvailable& field) - // { - // storeProp(field, m_retainAvailable); - // if (!field.field_value().valid()) { - // m_protocolError = true; - // } - // } - - // using UserProperty = Property::Field_userProperty; - // using UserPropsList = ObjListType; - // UserPropsList m_userProps; - // template - // void operator()(const UserProperty& field) - // { - // if constexpr (Config::HasUserProps) { - // if (m_userProps.max_size() <= m_userProps.size()) { - // return; - // } - - // m_userProps.push_back(&field); - // } - // } - using MaxPacketSize = Property::Field_maxPacketSize; unsigned m_maxPacketSize = 0U; template @@ -288,38 +122,6 @@ class Generator m_maxPacketSize = field.field_value().getValue(); } - // using WildcardSubAvail = Property::Field_wildcardSubAvail; - // const WildcardSubAvail* m_wildcardSubAvail = nullptr; - // template - // void operator()(const WildcardSubAvail& field) - // { - // storeProp(field, m_wildcardSubAvail); - // if (!field.field_value().valid()) { - // m_protocolError = true; - // } - // } - - // using SubIdAvail = Property::Field_subIdAvail; - // const SubIdAvail* m_subIdAvail = nullptr; - // template - // void operator()(const SubIdAvail& field) - // { - // storeProp(field, m_subIdAvail); - // if (!field.field_value().valid()) { - // m_protocolError = true; - // } - // } - - // using SharedSubAvail = Property::Field_sharedSubAvail; - // const SharedSubAvail* m_sharedSubAvail = nullptr; - // template - // void operator()(const SharedSubAvail& field) - // { - // storeProp(field, m_sharedSubAvail); - // if (!field.field_value().valid()) { - // m_protocolError = true; - // } - // } }; unsigned allocPacketId(); @@ -328,8 +130,11 @@ class Generator void sendConnack(const Mqtt5ConnectMsg& msg, const PropsHandler& propsHandler); void sendPublish(const std::string& topic, unsigned qos); void sendAuth(const PropsHandler& propsHandler, Mqtt5AuthMsg::Field_reasonCode::Field::ValueType resonCode); + void doPublish(); + void doNextPublishIfNeeded(); Logger& m_logger; + unsigned m_minPubCount = 0U; std::ofstream m_stream; DataReportCb m_dataReportCb; Mqtt5Frame m_frame; @@ -337,6 +142,9 @@ class Generator unsigned m_lastPacketId = 0U; unsigned m_topicAliasLimit = 0U; std::map m_topicAliases; + unsigned m_nextPubQos = 0U; + std::string m_lastPubTopic; + unsigned m_pubCount = 0U; bool m_connected = false; }; diff --git a/client/afl_fuzz/ProgramOptions.cpp b/client/afl_fuzz/ProgramOptions.cpp index 6eedf62..b9aa571 100644 --- a/client/afl_fuzz/ProgramOptions.cpp +++ b/client/afl_fuzz/ProgramOptions.cpp @@ -42,7 +42,13 @@ ProgramOptions::ProgramOptions() ("sub-topic,t", po::value(), "Subscribe topic filters. Can be used multiple times. " "Multiple topics can also be comma separated in single occurrance, to be packed into single subscribe message. " "If not specified, single subscribe to \"#\" is assumed") - ; + ; + + po::options_description pubOpts("Publish Options"); + subOpts.add_options() + ("min-pub-count", po::value()->default_value(3U), + "Number of successful publish counts before unsubscribing to previously subscribed topics") + ; m_desc.add(commonOpts); m_desc.add(connectOpts); @@ -154,6 +160,11 @@ std::vector ProgramOptions::subTopics() const return result; } +unsigned ProgramOptions::minPubCount() const +{ + return m_vm["min-pub-count"].as(); +} + ProgramOptions::StringsList ProgramOptions::stringListOpts(const std::string& name) const { StringsList result; diff --git a/client/afl_fuzz/ProgramOptions.h b/client/afl_fuzz/ProgramOptions.h index 8e5373e..d96b8c9 100644 --- a/client/afl_fuzz/ProgramOptions.h +++ b/client/afl_fuzz/ProgramOptions.h @@ -48,6 +48,9 @@ class ProgramOptions // Subscribe options std::vector subTopics() const; + // Publish options + unsigned minPubCount() const; + private: StringsList stringListOpts(const std::string& name) const; diff --git a/client/lib/src/TimerMgr.cpp b/client/lib/src/TimerMgr.cpp index 74735f0..356f5ad 100644 --- a/client/lib/src/TimerMgr.cpp +++ b/client/lib/src/TimerMgr.cpp @@ -65,7 +65,8 @@ void TimerMgr::tick(unsigned ms) for (auto idx = 0U; idx < m_timers.size(); ++idx) { auto& info = m_timers[idx]; - if ((info.m_timeoutCb == nullptr) || + if ((!info.m_allocated) || + (info.m_timeoutCb == nullptr) || (info.m_suspended)) { continue; } From 5c445f6f7f6f79930d0997b374ad160f9e13868b Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 28 Dec 2023 15:56:53 +1000 Subject: [PATCH 17/35] Fuzz testing documentation. --- CMakeLists.txt | 1 - README.md | 23 ++++- client/afl_fuzz/Generator.cpp | 55 +++++++---- client/lib/src/op/ConnectOp.cpp | 10 +- client/lib/test/unit/UnitTestReauth.th | 70 ++++++++++++++ cmake/Compile.cmake | 5 - doc/BUILD.md | 2 +- doc/fuzz.test.md | 121 +++++++++++++++++++++++++ 8 files changed, 257 insertions(+), 30 deletions(-) create mode 100644 doc/fuzz.test.md diff --git a/CMakeLists.txt b/CMakeLists.txt index a4db4e1..e930a00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,6 @@ option (CC_MQTT5_USE_CCACHE "Use ccache on unix system" ON) option (CC_MQTT5_BUILD_UNIT_TESTS "Build unit tests" OFF) option (CC_MQTT5_BUILD_INTEGRATION_TESTS "Build integration tests which require MQTT broker on local port 1883." OFF) option (CC_MQTT5_WITH_DEFAULT_SANITIZERS "Build with sanitizers" OFF) -option (CC_MQTT5_DISABLE_FALSE_POSITIVE_SANITIZERS "Disable sanitizers known as false positives" ${CC_MQTT5_WITH_DEFAULT_SANITIZERS}) # Extra Configuration Variables # CC_MQTT5_CUSTOM_CLIENT_CONFIG_FILES - List of custom client configuration files diff --git a/README.md b/README.md index 4820d72..4c0b006 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,14 @@ # Overview -This repository provides **single threaded**, **asynchronous**, **non-blocking**, easy to -use, suitable for **embedded** platforms, well documented MQTT5 client library. -It is completely generic and allows end application to have a complete control +This repository provides well documented and easy to use MQTT5 client library. +It is: + +- single threaded +- asynchronous and non-blocking, +- fuzz testable +- compile time configurable (disable unneeded features and/or configure some data types) +- suitable for **embedded** platforms with limited resources and/or without heap + +The library is completely generic and allows end application to have a complete control over the I/O link as well as perform extra manipulation on the exchanged raw data (such as encryption or extra framing). @@ -43,6 +50,16 @@ the events loop and manage network connection(s)). Detailed instructions on how to build and install all the components can be found in [doc/BUILD.md](doc/BUILD.md) file. +# How to Fuzz Test +The provided MQTT5 client library as well as its dependencies from the +[CommsChampion Ecosystem](https://commschamp.github.io/) have been designed with +reliability in mind and to be able to safely handle malformed data as well as +withstand unexpected behaviour from a MQTT broker. In order to +verify the library's reliability it is highly recommended to perform +[AFL++](https://github.com/AFLplusplus/AFLplusplus) based fuzz testing. +The detailed instruction on how to fuzz test the +library can be found in [doc/fuzz_test.md](doc/fuzz_test.md) file. + # Branching Model This repository will follow the [Successful Git Branching Model](http://nvie.com/posts/a-successful-git-branching-model/). diff --git a/client/afl_fuzz/Generator.cpp b/client/afl_fuzz/Generator.cpp index d1f1b90..de2ffcf 100644 --- a/client/afl_fuzz/Generator.cpp +++ b/client/afl_fuzz/Generator.cpp @@ -79,6 +79,7 @@ void Generator::processData(const std::uint8_t* buf, unsigned bufLen) void Generator::handle(const Mqtt5ConnectMsg& msg) { m_logger.infoLog() << "Processing " << msg.name() << "\n"; + m_connected = false; PropsHandler propsHandler; for (auto& p : msg.field_properties().value()) { p.currentFieldExec(propsHandler); @@ -196,17 +197,21 @@ void Generator::handle(const Mqtt5AuthMsg& msg) return; } + assert(msg.field_reasonCode().doesExist()); + assert(msg.field_properties().doesExist()); PropsHandler propsHandler; - for (auto& p : m_cachedConnect->field_properties().value()) { + for (auto& p : msg.field_properties().field().value()) { p.currentFieldExec(propsHandler); } if (msg.field_reasonCode().field().value() == Mqtt5AuthMsg::Field_reasonCode::Field::ValueType::ReAuth) { + m_logger.infoLog() << "Re-authentication request\n"; sendAuth(propsHandler, Mqtt5AuthMsg::Field_reasonCode::Field::ValueType::ContinueAuth); return; } + m_logger.infoLog() << "Completing re-authentication\n"; assert(msg.field_reasonCode().field().value() == Mqtt5AuthMsg::Field_reasonCode::Field::ValueType::ContinueAuth); sendAuth(propsHandler, Mqtt5AuthMsg::Field_reasonCode::Field::ValueType::Success); } @@ -286,6 +291,13 @@ void Generator::sendConnack(const Mqtt5ConnectMsg& msg, const PropsHandler& prop auto& valueField = propBundle.field_value(); valueField.setValue(propsHandler.m_topicAliasMax); m_topicAliasLimit = propsHandler.m_topicAliasMax; + } + + if (!propsHandler.m_authMethod.empty()) { + auto& propVar = addProp(propsField); + auto& propBundle = propVar.initField_authMethod(); + auto& valueField = propBundle.field_value(); + valueField.setValue(propsHandler.m_authMethod); } sendMessage(outMsg); @@ -319,25 +331,34 @@ void Generator::sendPublish(const std::string& topic, unsigned qos) void Generator::sendAuth(const PropsHandler& propsHandler, Mqtt5AuthMsg::Field_reasonCode::Field::ValueType reasonCode) { Mqtt5AuthMsg outMsg; - outMsg.field_reasonCode().field().setValue(reasonCode); + do { + if (reasonCode == Mqtt5AuthMsg::Field_reasonCode::Field::ValueType::Success) { + break; + } - auto& propsField = outMsg.field_properties().field(); - if (!propsHandler.m_authMethod.empty()) { - auto& propVar = addProp(propsField); - auto& propBundle = propVar.initField_authMethod(); - auto& valueField = propBundle.field_value(); - valueField.setValue(propsHandler.m_authMethod); - } + outMsg.field_reasonCode().setExists(); + outMsg.field_reasonCode().field().setValue(reasonCode); + outMsg.field_properties().setExists(); - if (!propsHandler.m_authData.empty()) { - auto& propVar = addProp(propsField); - auto& propBundle = propVar.initField_authData(); - auto& valueField = propBundle.field_value(); - valueField.setValue(propsHandler.m_authData); - if ((valueField.value().size() & 0x1) != 0) { - valueField.value().push_back('1'); + + auto& propsField = outMsg.field_properties().field(); + if (!propsHandler.m_authMethod.empty()) { + auto& propVar = addProp(propsField); + auto& propBundle = propVar.initField_authMethod(); + auto& valueField = propBundle.field_value(); + valueField.setValue(propsHandler.m_authMethod); } - } + + if (!propsHandler.m_authData.empty()) { + auto& propVar = addProp(propsField); + auto& propBundle = propVar.initField_authData(); + auto& valueField = propBundle.field_value(); + valueField.setValue(propsHandler.m_authData); + if ((valueField.value().size() & 0x1) != 0) { + valueField.value().push_back('1'); + } + } + } while (false); sendMessage(outMsg); } diff --git a/client/lib/src/op/ConnectOp.cpp b/client/lib/src/op/ConnectOp.cpp index ae3e1c0..b2f61db 100644 --- a/client/lib/src/op/ConnectOp.cpp +++ b/client/lib/src/op/ConnectOp.cpp @@ -774,10 +774,14 @@ void ConnectOp::handle(AuthMsg& msg) return; // Disconnect is sent and op is competed } - if ((m_authMethod.empty()) || - (msg.field_reasonCode().isMissing()) || + if (m_authMethod.empty()) { + errorLog("The connect hasn't used extended authentication, invalid AUTH message received"); + return; // Disconnect is sent and op is competed + } + + if ((msg.field_reasonCode().isMissing()) || (msg.field_reasonCode().field().value() != AuthMsg::Field_reasonCode::Field::ValueType::ContinueAuth)) { - errorLog("Invalid reason code received received in AUTH message"); + errorLog("Invalid reason code received received in AUTH message during connection"); return; // Disconnect is sent and op is competed } diff --git a/client/lib/test/unit/UnitTestReauth.th b/client/lib/test/unit/UnitTestReauth.th index 6ca6089..9b24c98 100644 --- a/client/lib/test/unit/UnitTestReauth.th +++ b/client/lib/test/unit/UnitTestReauth.th @@ -14,6 +14,7 @@ public: void test5(); void test6(); void test7(); + void test8(); private: virtual void setUp() override @@ -587,4 +588,73 @@ void UnitTestReauth::test7() // TS_ASSERT_EQUALS(respInfo.m_status, CC_Mqtt5AsyncOpStatus_BrokerDisconnected); // unitTestPopReauthResponseInfo(); // TS_ASSERT(unitTestIsDisconnected()); +} + +void UnitTestReauth::test8() +{ + // Simple reauth from client when broker responds without reason code. + auto* client = unitTestAllocAndInitClient(true); + TS_ASSERT_DIFFERS(client, nullptr); + + auto basicConfig = CC_Mqtt5ConnectBasicConfig(); + unitTestConnectInitConfigBasic(&basicConfig); + basicConfig.m_clientId = __FUNCTION__; + basicConfig.m_cleanStart = true; + + const std::string ConnectAuthMethod = "AuthMethod"; + const UnitTestData ConnectAuthData = {0x1, 0x2, 0x3, 0x5, 0xa}; + + auto extraConfig = CC_Mqtt5ConnectExtraConfig(); + unitTestConnectInitConfigExtra(&extraConfig); + extraConfig.m_requestProblemInfo = true; + + auto connectAuthConfig = CC_Mqtt5AuthConfig(); + unitTestConnectInitConfigAuth(&connectAuthConfig); + connectAuthConfig.m_authMethod = ConnectAuthMethod.c_str(); + connectAuthConfig.m_authData = &ConnectAuthData[0]; + connectAuthConfig.m_authDataLen = static_cast(ConnectAuthData.size()); + + unitTestPerformConnect(client, &basicConfig, nullptr, &extraConfig, &connectAuthConfig, nullptr); + TS_ASSERT(unitTestIsConnected(client)); + TS_ASSERT(!unitTestHasSentMessage()); + + auto* reauth = unitTestReauthPrepare(client, nullptr); + TS_ASSERT_DIFFERS(reauth, nullptr); + + const UnitTestData ReAuthData = {0x1, 0x2, 0x3, 0x5, 0x6}; + auto ec = unitTestConfigReauth(reauth, std::string(), ReAuthData); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + ec = unitTestSendReauth(reauth); + TS_ASSERT_EQUALS(ec, CC_Mqtt5ErrorCode_Success); + + auto sentMsg = unitTestGetSentMessage(); + TS_ASSERT(sentMsg); + TS_ASSERT_EQUALS(sentMsg->getId(), cc_mqtt5::MsgId_Auth); + auto* authMsg = dynamic_cast(sentMsg.get()); + TS_ASSERT_DIFFERS(authMsg, nullptr); + TS_ASSERT(authMsg->field_reasonCode().doesExist()); + TS_ASSERT_EQUALS(authMsg->field_reasonCode().field().value(), UnitTestAuthMsg::Field_reasonCode::Field::ValueType::ReAuth); + TS_ASSERT(authMsg->field_properties().doesExist()); + + UnitTestPropsHandler propsHandler; + for (auto& p : authMsg->field_properties().field().value()) { + p.currentFieldExec(propsHandler); + } + + TS_ASSERT_DIFFERS(propsHandler.m_authMethod, nullptr); + TS_ASSERT_EQUALS(propsHandler.m_authMethod->field_value().value(), ConnectAuthMethod); + + TS_ASSERT_DIFFERS(propsHandler.m_authData, nullptr); + TS_ASSERT_EQUALS(propsHandler.m_authData->field_value().value(), ReAuthData); + + TS_ASSERT(!unitTestIsReauthComplete()); + + UnitTestAuthMsg brokerAuth; + unitTestReceiveMessage(brokerAuth); + TS_ASSERT(unitTestIsReauthComplete()); + + auto& respInfo = unitTestReauthResponseInfo(); + TS_ASSERT_EQUALS(respInfo.m_status, CC_Mqtt5AsyncOpStatus_Complete); + unitTestPopReauthResponseInfo(); } \ No newline at end of file diff --git a/cmake/Compile.cmake b/cmake/Compile.cmake index 0fe4c1b..b9043eb 100644 --- a/cmake/Compile.cmake +++ b/cmake/Compile.cmake @@ -41,11 +41,6 @@ macro (cc_mqttsn_compile) ) endif() - if (CC_MQTT5_DISABLE_FALSE_POSITIVE_SANITIZERS) - list (APPEND extra_flags_list - -fno-sanitize-address-use-after-scope) - endif () - string(REPLACE ";" " " extra_flags "${extra_flags_list}") set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${extra_flags}") endmacro() \ No newline at end of file diff --git a/doc/BUILD.md b/doc/BUILD.md index 6fa7175..5b097db 100644 --- a/doc/BUILD.md +++ b/doc/BUILD.md @@ -1,7 +1,7 @@ # How to Build This project uses [CMake](https://cmake.org) cross-platform build system to generate required build files native to the platform. Please refer to the -main [CMakeLists.txt](../CMakeLists.txt) file for the info on available configuraiton options and +main [CMakeLists.txt](../CMakeLists.txt) file for the info on available configuration options and variables. ## External Dependencies diff --git a/doc/fuzz.test.md b/doc/fuzz.test.md new file mode 100644 index 0000000..97eafbf --- /dev/null +++ b/doc/fuzz.test.md @@ -0,0 +1,121 @@ +# Fuzz Testing Application +This repository provides a fuzz testing application called **cc_mqtt5_client_afl_fuzz**. +It expects to receive a sequence of the raw bytes from the **STDIN** and intended to +be used with fuzz testing tools like [AFL++](https://github.com/AFLplusplus/AFLplusplus). + +The **cc_mqtt5_client_afl_fuzz** application will try to perform the following steps: + +1. Initialize the library. +2. Perform "connect" operation and wait for acknowledgement (**CONNACK**). +3. Subscribe to all topics provided via command line arguments. +4. Perform re-authentication if "authentication method" is provided via command line arguments. +5. Echo back all the received messages from the broker, and proceed to the next step only after + the necessary number of messages has been published (configured via command line). +6. Unsubscribe from all the previously subscribed topics +7. Gracefully disconnect from the broker +8. Return to step 1. + +Due to the nature of the fuzz testing the intended workflow from above won't be +completed as intended due to detected malformed packets, protocol errors, and/or +received **DISCONNECT** messages. When such scenario is detected in any of the +steps the application will return to step 1 to try from the beginning on the remaining +input. + +# How to Build +To better understand the build instructions below please read the +[BUILD.md](BUILD.md) document as well as refer to the +main [CMakeLists.txt](../CMakeLists.txt) file for the info on available configuration options and +variables. + +If you're not familiar with the [AFL++](https://github.com/AFLplusplus/AFLplusplus) it +is highly recommended to read through its [instructions](https://github.com/AFLplusplus/AFLplusplus/blob/stable/docs/fuzzing_in_depth.md) +to properly understand what is being done. + +To enable the fuzz testing application use **CC_MQTT5_CLIENT_AFL_FUZZ** cmake option. When +instrumenting the binaries for the fuzz testing other applications are probably not needed +and their build can be disabled using the **CC_MQTT5_CLIENT_APPS** cmake option. + +Also remember to properly set instrumenting compilers provided by the [AFL++](https://github.com/AFLplusplus/AFLplusplus). + +``` +CC=afl-clang-lto CXX=afl-clang-lto++ cmake /path/to/src -DCC_MQTT5_CLIENT_AFL_FUZZ=ON -DCC_MQTT5_CLIENT_APPS=OFF ... +``` + +It is also highly recommended to use "Debug" build to enable all the assertions. +``` +CC=afl-clang-lto CXX=afl-clang-lto++ cmake ... -DCMAKE_BUILD_TYPE=Debug ... +``` + +After the "Debug" build is tested for several days without showing any crashes it also +beneficial to rebuild the fuzzing application as a "Release" and re-run it on the same produced corpus +(passing "-i-" to afl-fuzz) to verify that there are no unexpected pitfalls for the "Release" version. + +Due to the fact that [AFL++](https://github.com/AFLplusplus/AFLplusplus) compilers +receive their configuration via environment variable before the build it is necessary +to disable "ccache" usage in case the sources are going to be built multiple times +with different configurations. + +``` +CC=afl-clang-lto CXX=afl-clang-lto++ cmake ... -DCC_MQTT5_USE_CCACHE=OFF ... +``` + +Enable required sanitizers before the build +``` +export AFL_USE_ASAN=1 +export AFL_USE_UBSAN=1 +``` + +**WARNING**: It has been noticed when too many sanitizers are enabled at the same time the +target either fails to compile or fails to produce proper output +("Illegal instruction" is reported) when some failure happens. + +As the final stage, build the fuzzing application +``` +cmake --build . --target install +``` + +Note that in case [custom](custom_client_build.md) client libraries are built, the +fuzzing application will be built for each such library and the application name will +reflect the custom name selected for the library. + +The typical build sequence may look like this: +``` +cd /path/to/cc.mqtt5.libs +mkdir build +cd build +CC=afl-clang-lto CXX=afl-clang-lto++ cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=install \ + -DCMAKE_PREFIX_PATH=/path/to/comms/install\;/path/to/cc.mqtt5.generated/install \ + -DCC_MQTT5_CLIENT_AFL_FUZZ=ON -DCC_MQTT5_CLIENT_APPS=OFF -DCC_MQTT5_USE_CCACHE=OFF +export AFL_USE_ASAN=1 +export AFL_USE_UBSAN=1 +cmake --build . --target install --parallel 8 +``` + +# How to Fuzz Test +In order to start fuzz testing the [AFL++](https://github.com/AFLplusplus/AFLplusplus) requires +creation of the input corpus. Please use "-h" option to list the available command line parameters: +``` +./install/bin/cc_mqtt5_client_afl_fuzz -h +``` +Note the presence of the "-g" option which can be used to generate a valid input sequence +to perform a full single iteration of the **cc_mqtt5_client_afl_fuzz** described +earlier. If you intend to use some extra command line arguments in the actual +fuzz testing, provide them when generating the input sequence as well. + +For example +``` +mkdir -p /path/to/fuzz/input +mkdir -p /path/to/fuzz/output +./install/bin/cc_mqtt5_client_afl_fuzz -g /path/to/fuzz/input/1.bin --auth-method myauth --min-pub-count 5 +``` + +Now when the input corpus is created it is possible to actually start fuzz testing. Use the +same command line option as ones used for the generation of the input (excluding the "-g" with parameter of course). +``` +afl-fuzz -i /path/to/fuzz/input -o /path/to/fuzz/output -- ./install/bin/cc_mqtt5_client_afl_fuzz --auth-method myauth --min-pub-count 5 +``` + +Note that "afl-fuzz" may request to change your machine configuration before being able to fuzz test. + +In case fuzz testing reports any crash please open an issue for this project reporting +a build configuration and attaching the input file(s) that caused the crash. From edca73c5bd05662c234d5f7219d429446bc20548 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 28 Dec 2023 16:00:15 +1000 Subject: [PATCH 18/35] Some extra cleanup of compilation flags. --- cmake/Compile.cmake | 7 ------- 1 file changed, 7 deletions(-) diff --git a/cmake/Compile.cmake b/cmake/Compile.cmake index b9043eb..bb15927 100644 --- a/cmake/Compile.cmake +++ b/cmake/Compile.cmake @@ -34,13 +34,6 @@ macro (cc_mqttsn_compile) ) endif () - if (CC_MQTT5_CLIENT_AFL_FUZZ AND - (CMAKE_COMPILER_IS_GNUCC OR ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang"))) - list (APPEND extra_flags_list - "-Wno-old-style-cast" - ) - endif() - string(REPLACE ";" " " extra_flags "${extra_flags_list}") set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${extra_flags}") endmacro() \ No newline at end of file From 90a922d42a4ddb617de0db0a66b3bc193d250c48 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 28 Dec 2023 16:05:58 +1000 Subject: [PATCH 19/35] Updated fuzz testing documentation file name. --- doc/{fuzz.test.md => fuzz_test.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename doc/{fuzz.test.md => fuzz_test.md} (100%) diff --git a/doc/fuzz.test.md b/doc/fuzz_test.md similarity index 100% rename from doc/fuzz.test.md rename to doc/fuzz_test.md From 9b78ae60c25fa8536439769d40a5d4e232e54c5e Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Thu, 28 Dec 2023 16:20:34 +1000 Subject: [PATCH 20/35] Updates to fuzzing documentation. --- doc/fuzz_test.md | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/doc/fuzz_test.md b/doc/fuzz_test.md index 97eafbf..f8265dc 100644 --- a/doc/fuzz_test.md +++ b/doc/fuzz_test.md @@ -46,14 +46,15 @@ It is also highly recommended to use "Debug" build to enable all the assertions. CC=afl-clang-lto CXX=afl-clang-lto++ cmake ... -DCMAKE_BUILD_TYPE=Debug ... ``` -After the "Debug" build is tested for several days without showing any crashes it also -beneficial to rebuild the fuzzing application as a "Release" and re-run it on the same produced corpus +After the "Debug" build is tested for several days without showing any crashes it is also +beneficial to rebuild the fuzzing application using the "Release" build type and re-run it on the same produced corpus (passing "-i-" to afl-fuzz) to verify that there are no unexpected pitfalls for the "Release" version. Due to the fact that [AFL++](https://github.com/AFLplusplus/AFLplusplus) compilers -receive their configuration via environment variable before the build it is necessary +receive their configuration via environment variables before the build, it is necessary to disable "ccache" usage in case the sources are going to be built multiple times -with different configurations. +with different configurations. Otherwise the build might produced wierd errors of +linked object files being incompatible. ``` CC=afl-clang-lto CXX=afl-clang-lto++ cmake ... -DCC_MQTT5_USE_CCACHE=OFF ... @@ -65,16 +66,21 @@ export AFL_USE_ASAN=1 export AFL_USE_UBSAN=1 ``` -**WARNING**: It has been noticed when too many sanitizers are enabled at the same time the +**WARNING**: It has been noticed that when too many sanitizers are enabled at the same time the target either fails to compile or fails to produce proper output -("Illegal instruction" is reported) when some failure happens. +("Illegal instruction" is reported) when some failure happens. If such failure happen +try to re-compile the fuzzing application without the sanitizers altogether and see +if any unexpected crash is reported on the generated crash causing input. +After that, try to recompile enabling only one sanitizer enabled at a time and feeding the +same problematic input in attempt to produce reasonable failure report from the +sanitizer. As the final stage, build the fuzzing application ``` cmake --build . --target install ``` -Note that in case [custom](custom_client_build.md) client libraries are built, the +Note that in case of [custom](custom_client_build.md) client libraries are built, the fuzzing application will be built for each such library and the application name will reflect the custom name selected for the library. @@ -99,7 +105,8 @@ creation of the input corpus. Please use "-h" option to list the available comma ``` Note the presence of the "-g" option which can be used to generate a valid input sequence to perform a full single iteration of the **cc_mqtt5_client_afl_fuzz** described -earlier. If you intend to use some extra command line arguments in the actual +earlier. The actual fuzzing will use it as a valid input and then deviate from there. +If you intend to use some extra command line arguments in the actual fuzz testing, provide them when generating the input sequence as well. For example @@ -112,10 +119,15 @@ mkdir -p /path/to/fuzz/output Now when the input corpus is created it is possible to actually start fuzz testing. Use the same command line option as ones used for the generation of the input (excluding the "-g" with parameter of course). ``` -afl-fuzz -i /path/to/fuzz/input -o /path/to/fuzz/output -- ./install/bin/cc_mqtt5_client_afl_fuzz --auth-method myauth --min-pub-count 5 +afl-fuzz -i /path/to/fuzz/input -o /path/to/fuzz/output -a binary -D -- ./install/bin/cc_mqtt5_client_afl_fuzz --auth-method myauth --min-pub-count 5 ``` Note that "afl-fuzz" may request to change your machine configuration before being able to fuzz test. +Also use help proved by the "afl-fuzz" itself to see all the available fuzzing options. +``` +afl-fuzz -h +``` + In case fuzz testing reports any crash please open an issue for this project reporting a build configuration and attaching the input file(s) that caused the crash. From deb8ae5d70ee9132d443c950173a6cfc2310a586 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 29 Dec 2023 09:23:12 +1000 Subject: [PATCH 21/35] Removed usage of the CC_MQTT5_DISABLE_FALSE_POSITIVE_SANITIZERS cmake option. --- .github/workflows/actions_build.yml | 4 ++-- script/full_build.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/actions_build.yml b/.github/workflows/actions_build.yml index 6cb5e4f..02dec62 100644 --- a/.github/workflows/actions_build.yml +++ b/.github/workflows/actions_build.yml @@ -50,7 +50,7 @@ jobs: run: | cmake $GITHUB_WORKSPACE -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_DISABLE_FALSE_POSITIVE_SANITIZERS=ON \ + -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_CLIENT_AFL_FUZZ=ON @@ -120,7 +120,7 @@ jobs: run: | cmake $GITHUB_WORKSPACE -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_DISABLE_FALSE_POSITIVE_SANITIZERS=ON \ + -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_CLIENT_AFL_FUZZ=ON diff --git a/script/full_build.sh b/script/full_build.sh index 62506a6..3a375a4 100755 --- a/script/full_build.sh +++ b/script/full_build.sh @@ -23,7 +23,7 @@ ${SCRIPT_DIR}/prepare_externals.sh cd ${BUILD_DIR} cmake .. -DCMAKE_INSTALL_PREFIX=${COMMON_INSTALL_DIR} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \ - -DCC_MQTT5_WITH_DEFAULT_SANITIZERS=ON -DCC_MQTT5_DISABLE_FALSE_POSITIVE_SANITIZERS=ON \ + -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" "$@" From 918d7efa93bf78be2ba19a43ed7f8ae72bd06808 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 29 Dec 2023 09:37:29 +1000 Subject: [PATCH 22/35] Updating next version to be v0.2 --- 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 fcb3d8c..83e6726 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 1U +#define CC_MQTT5_CLIENT_MINOR_VERSION 2U /// @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 5586e4675b46d24a75a7d1972b0135c79463a243 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 29 Dec 2023 09:37:42 +1000 Subject: [PATCH 23/35] Reporting version in cmake. --- client/lib/CMakeLists.txt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/client/lib/CMakeLists.txt b/client/lib/CMakeLists.txt index db3ecdf..91cfd2f 100644 --- a/client/lib/CMakeLists.txt +++ b/client/lib/CMakeLists.txt @@ -169,6 +169,25 @@ function (gen_lib_mqtt5_client config_file) INTERFACE_LINK_LIBRARIES "" ) add_dependencies(${lib_name} ${header_tgt_name} ${src_tgt_name} ${config_tgt_name} ${prot_opts_tgt_name}) + + file (READ "${CMAKE_CURRENT_SOURCE_DIR}/include/cc_mqtt5_client/common.h" version_file) + string (REGEX MATCH "CC_MQTT5_CLIENT_MAJOR_VERSION ([0-9]*)U*" _ ${version_file}) + set (major_ver ${CMAKE_MATCH_1}) + string (REGEX MATCH "CC_MQTT5_CLIENT_MINOR_VERSION ([0-9]*)U*" _ ${version_file}) + set (minor_ver ${CMAKE_MATCH_1}) + string (REGEX MATCH "CC_MQTT5_CLIENT_PATCH_VERSION ([0-9]*)U*" _ ${version_file}) + set (patch_ver ${CMAKE_MATCH_1}) + set (CC_MQTT5_CLIENT_VERSION "${major_ver}.${minor_ver}.${patch_ver}") + + write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/${lib_name}ConfigVersion.cmake + VERSION ${CC_MQTT5_CLIENT_VERSION} + COMPATIBILITY AnyNewerVersion) + + install ( + FILES ${CMAKE_CURRENT_BINARY_DIR}/${lib_name}ConfigVersion.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/${lib_name}/cmake + ) install ( FILES ${header_output} @@ -237,6 +256,8 @@ install ( DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) +include(CMakePackageConfigHelpers) + if (CC_MQTT5_CLIENT_DEFAULT_LIB) gen_lib_mqtt5_client("") endif () From 80ed4505c1ceca4dfd2de0081e54d3d87ec70cc4 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 29 Dec 2023 10:34:49 +1000 Subject: [PATCH 24/35] Allow chaning generator in prepare_externals.sh --- script/prepare_externals.sh | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/script/prepare_externals.sh b/script/prepare_externals.sh index 2026c37..6e9b344 100755 --- a/script/prepare_externals.sh +++ b/script/prepare_externals.sh @@ -12,9 +12,13 @@ # COMMON_INSTALL_DIR - (Optional) Common directory to perform installations # COMMON_BUILD_TYPE - (Optional) CMake build type # COMMON_CXX_STANDARD - (Optional) CMake C++ standard +# COMMON_CMAKE_GENERATOR - (Optional) CMake generator +# COMMON_CMAKE_PLATFORM - (Optional) CMake platform ##################################### +set -x + if [ -z "${BUILD_DIR}" ]; then echo "BUILD_DIR hasn't been specified" exit 1 @@ -81,8 +85,10 @@ function build_comms() { echo "Building COMMS library..." mkdir -p ${COMMS_BUILD_DIR} - cmake -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} + cmake -S ${COMMS_SRC_DIR} -B ${COMMS_BUILD_DIR} \ + ${COMMON_CMAKE_GENERATOR:+"-G ${COMMON_CMAKE_GENERATOR}"} ${COMMON_CMAKE_PLATFORM:+"-A ${COMMON_CMAKE_PLATFORM}"} \ + -DCMAKE_INSTALL_PREFIX=${COMMS_INSTALL_DIR} -DCMAKE_BUILD_TYPE=${COMMON_BUILD_TYPE} \ + ${COMMON_CXX_STANDARD:+"-DCMAKE_CXX_STANDARD=${COMMON_CXX_STANDARD}"} cmake --build ${COMMS_BUILD_DIR} --config ${COMMON_BUILD_TYPE} --target install ${procs_param} } @@ -103,8 +109,10 @@ function build_mqtt5() { echo "Building cc.mqtt5.generated library..." mkdir -p ${CC_MQTT5_BUILD_DIR} cmake -S ${CC_MQTT5_SRC_DIR} -B ${CC_MQTT5_BUILD_DIR} \ + ${COMMON_CMAKE_GENERATOR:+"-G ${COMMON_CMAKE_GENERATOR}"} ${COMMON_CMAKE_PLATFORM:+"-A ${COMMON_CMAKE_PLATFORM}"} \ -DCMAKE_INSTALL_PREFIX=${CC_MQTT5_INSTALL_DIR} -DCMAKE_BUILD_TYPE=${COMMON_BUILD_TYPE} \ - -DCMAKE_CXX_STANDARD=${COMMON_CXX_STANDARD} -DOPT_REQUIRE_COMMS_LIB=OFF + ${COMMON_CXX_STANDARD:+"-DCMAKE_CXX_STANDARD=${COMMON_CXX_STANDARD}"} \ + -DOPT_REQUIRE_COMMS_LIB=OFF cmake --build ${CC_MQTT5_BUILD_DIR} --config ${COMMON_BUILD_TYPE} --target install ${procs_param} } From f746643b53fe7c2a61420f130e1fe6d25ba07c7a Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 29 Dec 2023 10:35:09 +1000 Subject: [PATCH 25/35] Use VS cmake generators in appveyor. --- .appveyor.yml | 4 +++- script/appveyor_install.bat | 32 ++++++++++---------------------- script/prepare_externals.bat | 7 +++++-- 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 8f7bcc3..2028376 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -39,12 +39,14 @@ install: - set COMMON_INSTALL_DIR=%BUILD_DIR%\install - set COMMON_BUILD_TYPE=%CONFIGURATION% - set COMMON_CXX_STANDARD=%CPP_STD% + - set GENERATOR="%CMAKE_GENERATOR%" + - set PLATFORM="%CMAKE_PLATFORM%" - call script\prepare_externals.bat build_script: - echo ------------------------- Building Project ------------------------- - cd %BUILD_DIR% - - cmake .. -DCMAKE_BUILD_TYPE=%CONFIGURATION% -G "NMake Makefiles" -DBOOST_ROOT="%BOOST_DIR%" ^ + - cmake .. -DCMAKE_BUILD_TYPE=%CONFIGURATION% -G "%CMAKE_GENERATOR%" -A %CMAKE_PLATFORM% -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 diff --git a/script/appveyor_install.bat b/script/appveyor_install.bat index 081f332..472ea7d 100644 --- a/script/appveyor_install.bat +++ b/script/appveyor_install.bat @@ -1,37 +1,25 @@ IF "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" ( set TOOLCHAIN=msvc15 set BOOST_VER=1_69_0 - IF "%PLATFORM%"=="x86" ( - echo Performing x86 build in VS2017 - call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars32.bat" - ) ELSE ( - echo Performing amd64 build in VS2017 - call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" - ) - + set CMAKE_GENERATOR="Visual Studio 15 2017" + ) ELSE IF "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2019" ( set TOOLCHAIN=msvc16 set BOOST_VER=1_77_0 - IF "%PLATFORM%"=="x86" ( - echo Performing x86 build in VS2019 - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars32.bat" - ) ELSE ( - echo Performing amd64 build in VS2019 - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat" - ) + set CMAKE_GENERATOR="Visual Studio 16 2019" ) ELSE IF "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2022" ( set TOOLCHAIN=msvc17 set BOOST_VER=1_83_0 - IF "%PLATFORM%"=="x86" ( - echo Performing x86 build in VS2022 - call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars32.bat" - ) ELSE ( - echo Performing amd64 build in VS2022 - call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" - ) + set CMAKE_GENERATOR="Visual Studio 17 2022" ) ELSE ( echo Toolchain %TOOLCHAIN% is not supported exit -1 ) +IF "%PLATFORM%"=="x86" ( + set CMAKE_PLATFORM="Win32" +) ELSE ( + set CMAKE_PLATFORM="x64" +) + set BOOST_DIR=C:\Libraries\boost_%BOOST_VER% diff --git a/script/prepare_externals.bat b/script/prepare_externals.bat index 9eb8357..f346366 100755 --- a/script/prepare_externals.bat +++ b/script/prepare_externals.bat @@ -1,6 +1,7 @@ rem Input rem BUILD_DIR - Main build directory rem GENERATOR - CMake generator +rem PLATFORM - CMake generator platform rem EXTERNALS_DIR - (Optional) Directory where externals need to be located rem COMMS_REPO - (Optional) Repository of the COMMS library rem COMMS_TAG - (Optional) Tag of the COMMS library @@ -16,6 +17,8 @@ if [%BUILD_DIR%] == [] echo "BUILD_DIR hasn't been specified" & exit /b 1 if [%GENERATOR%] == [] set GENERATOR="NMake Makefiles" +if NOT [%PLATFORM%] == [] set PLATFORM_PARAM="-A %PLATFORM%" + if [%EXTERNALS_DIR%] == [] set EXTERNALS_DIR=%BUILD_DIR%/externals if [%COMMS_REPO%] == [] set COMMS_REPO="https://github.com/commschamp/comms.git" @@ -60,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% -S %COMMS_SRC_DIR% -B %COMMS_BUILD_DIR% ^ +cmake -G %GENERATOR% %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% @@ -86,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% -S %CC_MQTT5_SRC_DIR% -B %CC_MQTT5_BUILD_DIR% ^ +cmake -G %GENERATOR% %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 10ad7ef8ccbd782a32f72a05d8b5347f0166bd57 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 29 Dec 2023 10:37:20 +1000 Subject: [PATCH 26/35] Remove echoing commands in prepare_externals.sh --- script/prepare_externals.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/script/prepare_externals.sh b/script/prepare_externals.sh index 6e9b344..9829487 100755 --- a/script/prepare_externals.sh +++ b/script/prepare_externals.sh @@ -17,8 +17,6 @@ ##################################### -set -x - if [ -z "${BUILD_DIR}" ]; then echo "BUILD_DIR hasn't been specified" exit 1 From 945ee9f4e56ed854d4813d423b3c555590441654 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 29 Dec 2023 10:42:42 +1000 Subject: [PATCH 27/35] Echoing commands in prepare_externals.bat --- script/prepare_externals.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/script/prepare_externals.sh b/script/prepare_externals.sh index 9829487..aabeaa0 100755 --- a/script/prepare_externals.sh +++ b/script/prepare_externals.sh @@ -17,6 +17,8 @@ ##################################### +@echo on + if [ -z "${BUILD_DIR}" ]; then echo "BUILD_DIR hasn't been specified" exit 1 From 0557c432dfbffd88ab1e21d07028ab7fe15b94e9 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 29 Dec 2023 10:48:47 +1000 Subject: [PATCH 28/35] Restoring appveyor environment in appveyor_install.bat script. --- script/appveyor_install.bat | 25 +++++++++++++++++++++++++ script/prepare_externals.bat | 2 ++ script/prepare_externals.sh | 2 -- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/script/appveyor_install.bat b/script/appveyor_install.bat index 472ea7d..65cc5fb 100644 --- a/script/appveyor_install.bat +++ b/script/appveyor_install.bat @@ -2,15 +2,40 @@ IF "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" ( set TOOLCHAIN=msvc15 set BOOST_VER=1_69_0 set CMAKE_GENERATOR="Visual Studio 15 2017" + + IF "%PLATFORM%"=="x86" ( + echo Performing x86 build in VS2017 + call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars32.bat" + ) ELSE ( + echo Performing amd64 build in VS2017 + call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" + ) ) ELSE IF "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2019" ( set TOOLCHAIN=msvc16 set BOOST_VER=1_77_0 set CMAKE_GENERATOR="Visual Studio 16 2019" + + IF "%PLATFORM%"=="x86" ( + echo Performing x86 build in VS2019 + call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars32.bat" + ) ELSE ( + echo Performing amd64 build in VS2019 + call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat" + ) + ) ELSE IF "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2022" ( set TOOLCHAIN=msvc17 set BOOST_VER=1_83_0 set CMAKE_GENERATOR="Visual Studio 17 2022" + + IF "%PLATFORM%"=="x86" ( + echo Performing x86 build in VS2022 + call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars32.bat" + ) ELSE ( + echo Performing amd64 build in VS2022 + call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" + ) ) ELSE ( echo Toolchain %TOOLCHAIN% is not supported exit -1 diff --git a/script/prepare_externals.bat b/script/prepare_externals.bat index f346366..c119d93 100755 --- a/script/prepare_externals.bat +++ b/script/prepare_externals.bat @@ -13,6 +13,8 @@ rem COMMON_CXX_STANDARD - (Optional) CMake C++ standard rem ----------------------------------------------------- +@echo on + if [%BUILD_DIR%] == [] echo "BUILD_DIR hasn't been specified" & exit /b 1 if [%GENERATOR%] == [] set GENERATOR="NMake Makefiles" diff --git a/script/prepare_externals.sh b/script/prepare_externals.sh index aabeaa0..9829487 100755 --- a/script/prepare_externals.sh +++ b/script/prepare_externals.sh @@ -17,8 +17,6 @@ ##################################### -@echo on - if [ -z "${BUILD_DIR}" ]; then echo "BUILD_DIR hasn't been specified" exit 1 From c948bfb78f6b5a18b7299614c1118023b21877cd Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 29 Dec 2023 10:53:54 +1000 Subject: [PATCH 29/35] Updating appveyor_install.bat --- script/appveyor_install.bat | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/script/appveyor_install.bat b/script/appveyor_install.bat index 65cc5fb..4468e87 100644 --- a/script/appveyor_install.bat +++ b/script/appveyor_install.bat @@ -1,7 +1,7 @@ IF "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" ( set TOOLCHAIN=msvc15 set BOOST_VER=1_69_0 - set CMAKE_GENERATOR="Visual Studio 15 2017" + set CMAKE_GENERATOR=Visual Studio 15 2017 IF "%PLATFORM%"=="x86" ( echo Performing x86 build in VS2017 @@ -14,7 +14,7 @@ IF "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" ( ) ELSE IF "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2019" ( set TOOLCHAIN=msvc16 set BOOST_VER=1_77_0 - set CMAKE_GENERATOR="Visual Studio 16 2019" + set CMAKE_GENERATOR=Visual Studio 16 2019 IF "%PLATFORM%"=="x86" ( echo Performing x86 build in VS2019 @@ -27,7 +27,7 @@ IF "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" ( ) ELSE IF "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2022" ( set TOOLCHAIN=msvc17 set BOOST_VER=1_83_0 - set CMAKE_GENERATOR="Visual Studio 17 2022" + set CMAKE_GENERATOR=Visual Studio 17 2022 IF "%PLATFORM%"=="x86" ( echo Performing x86 build in VS2022 @@ -42,9 +42,9 @@ IF "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" ( ) IF "%PLATFORM%"=="x86" ( - set CMAKE_PLATFORM="Win32" + set CMAKE_PLATFORM=Win32 ) ELSE ( - set CMAKE_PLATFORM="x64" + set CMAKE_PLATFORM=x64 ) set BOOST_DIR=C:\Libraries\boost_%BOOST_VER% From 4e4c4d36a817ef36a34714df42265dca95f75e63 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Fri, 29 Dec 2023 10:59:46 +1000 Subject: [PATCH 30/35] Cosmetic update to prepare_externals.bat. --- script/prepare_externals.bat | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/script/prepare_externals.bat b/script/prepare_externals.bat index c119d93..01c78cf 100755 --- a/script/prepare_externals.bat +++ b/script/prepare_externals.bat @@ -13,13 +13,11 @@ rem COMMON_CXX_STANDARD - (Optional) CMake C++ standard rem ----------------------------------------------------- -@echo on - if [%BUILD_DIR%] == [] echo "BUILD_DIR hasn't been specified" & exit /b 1 if [%GENERATOR%] == [] set GENERATOR="NMake Makefiles" -if NOT [%PLATFORM%] == [] set PLATFORM_PARAM="-A %PLATFORM%" +if NOT [%PLATFORM%] == [] set PLATFORM_PARAM=-A %PLATFORM% if [%EXTERNALS_DIR%] == [] set EXTERNALS_DIR=%BUILD_DIR%/externals From e8c214ce9026e1e143f5dab8f7a1f4bcd16fdd65 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sat, 30 Dec 2023 10:02:12 +1000 Subject: [PATCH 31/35] Attempt to fix build with MSVC 2017 on appveyor. --- .appveyor.yml | 6 ++++-- script/prepare_externals.bat | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 2028376..d6f2e85 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,7 +1,8 @@ image: + - Visual Studio 2017 - Visual Studio 2022 - Visual Studio 2019 - - Visual Studio 2017 + init: - git config --global core.autocrlf input @@ -46,7 +47,8 @@ install: build_script: - echo ------------------------- Building Project ------------------------- - cd %BUILD_DIR% - - cmake .. -DCMAKE_BUILD_TYPE=%CONFIGURATION% -G "%CMAKE_GENERATOR%" -A %CMAKE_PLATFORM% -DBOOST_ROOT="%BOOST_DIR%" ^ + - if NOT [%PLATFORM%] == [] set PLATFORM_PARAM=-A %PLATFORM% + - 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 diff --git a/script/prepare_externals.bat b/script/prepare_externals.bat index 01c78cf..8653dd9 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 [%GENERATOR%] == [] set GENERATOR=NMake Makefiles if NOT [%PLATFORM%] == [] set PLATFORM_PARAM=-A %PLATFORM% From 35594cf86df1cfc5137b80529c52cddef5854765 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sat, 30 Dec 2023 10:22:09 +1000 Subject: [PATCH 32/35] Improving client library documentation. --- client/lib/doxygen/main.dox | 40 ++++++++++++++++----- client/lib/include/cc_mqtt5_client/common.h | 16 ++++----- client/lib/templ/client.h.templ | 2 +- 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/client/lib/doxygen/main.dox b/client/lib/doxygen/main.dox index 6ba7973..f2dc347 100644 --- a/client/lib/doxygen/main.dox +++ b/client/lib/doxygen/main.dox @@ -33,7 +33,7 @@ /// @code /// CC_Mqtt5ClientHandle client = cc_mqtt5_client_alloc(); /// @endcode -/// All other functions are client specific, the receive the returned handle +/// All other functions are client specific, they receive the returned handle /// as their first parameter. /// /// When work with allocated client is complete, it must be freed using @@ -77,6 +77,15 @@ /// @endcode /// See also the documentation of the @ref CC_Mqtt5SendOutputDataCb callback function definition. /// +/// In the invoked callback the application is responsible to send the provided data +/// over the I/O link. The application can also perform extra data manipulation like +/// encryption. +/// +/// The reported data resides in internal data structures of the client library, +/// which can be updated / deleted right after the callback function returns. +/// It means the data may need to be copied into some other buffer, which will be +/// held intact until the send over I/O link operation is complete. +/// /// @subsection 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. @@ -93,6 +102,12 @@ /// @endcode /// See also the documentation of the @ref CC_Mqtt5BrokerDisconnectReportCb callback function definition. /// +/// 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 +/// 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. @@ -149,6 +164,8 @@ /// ... /// return ... /* return amount of elapsed milliseconds since last tick program */ /// } +/// +/// cc_mqtt5_client_set_cancel_next_tick_wait_callback(client, &my_cancel_tick_program_cb, data); /// @endcode /// See also the documentation of the @ref CC_Mqtt5CancelNextTickWaitCb callback function definition. /// @@ -171,8 +188,8 @@ /// 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, it is necessary for the application to -/// invoke @b cc_mqtt5_client_init() function to allow all other subsequent operations. +/// 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 @@ -243,7 +260,7 @@ /// @li @b send - Send the configured operation message to the broker. /// /// During the @b send stage the application is expected to provide the callback to -/// report to the application when the operation is complete. The second parameter +/// report to the application when the operation is complete. One of the parameters /// of the callback is always "status" of the @ref CC_Mqtt5AsyncOpStatus type. It /// indicates whether the operation was successfully complete. In addition to the /// status it reports some extra information reported by the broker. The information @@ -712,7 +729,7 @@ /// operation. /// /// Also @b note that the same function controls the verification of the -/// "publish" topic format. +/// "subscribe", "unsubscribe" and "publish" topic formats. /// /// To retrieve the current configuration use @b cc_mqtt5_client_get_verify_outgoing_topic_enabled() /// function. @@ -901,6 +918,9 @@ /// @b NOTE that the configuration is global per client and not per "unsubscribe" /// operation. /// +/// Also @b note that the same function controls the verification of the +/// "subscribe", "unsubscribe" and "publish" topic formats. +/// /// To retrieve the current configuration use @b cc_mqtt5_client_get_verify_outgoing_topic_enabled() /// function. /// @@ -1014,7 +1034,8 @@ /// @b DUP flag is set in the @b PUBLISH message. When the second attempt is /// not acknowledged, then the publish operation is terminated with appropriate /// status report. It is possible to change the default by using -/// @b cc_mqtt5_client_publish_set_resend_attempts() function. +/// @b cc_mqtt5_client_publish_set_resend_attempts() function. The @b DUP flag +/// will be sent for all the re-sent @b PUBLISH messages. /// @code /// ec = cc_mqtt5_client_publish_set_resend_attempts(publish, 3); /// if (ec != CC_Mqtt5ErrorCode_Success) { @@ -1064,7 +1085,7 @@ /// operation. /// /// Also @b note that the same function controls the verification of the -/// "subscribe" and "unsubscribe" topic filter format. +/// "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 @@ -1364,7 +1385,9 @@ /// @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) -/// with the @ref CC_Mqtt5AsyncOpStatus_Aborted "status" value report. +/// 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 /// section below for details on how to proceed after the callback invocation. @@ -1472,6 +1495,7 @@ /// @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. /// /// @section cc_mqtt5_client_network_disconnect Network Disconnection diff --git a/client/lib/include/cc_mqtt5_client/common.h b/client/lib/include/cc_mqtt5_client/common.h index 83e6726..fceb145 100644 --- a/client/lib/include/cc_mqtt5_client/common.h +++ b/client/lib/include/cc_mqtt5_client/common.h @@ -297,8 +297,8 @@ typedef struct typedef struct { unsigned m_sessionExpiryInterval; ///< "Session Expiry Interval" property, defaults to 0, not added when 0. - unsigned m_receiveMaximum; ///< "Receive Maximum" property - allowed amount of incomplete Qos1 and Qos2 publishes, defaults to 0, not added when 0. - ///< When equals to @b 0 (default) no property is added, which defaults to 65,535 on the broker side. + unsigned m_receiveMaximum; ///< "Receive Maximum" property - allowed amount of incomplete Qos1 and Qos2 publishes. + ///< When equals to @b 0 (default) no property is added, which is perceived as 65,535 on the broker side. unsigned m_maxPacketSize; ///< "Maximum Packet Size" property, defaults to 0, not added when 0, which means "no limit". unsigned m_topicAliasMaximum; ///< "Topic Alias Maximum" property, defaults to 0, not added when 0. bool m_requestResponseInfo; ///< "Request Response Information" property, defaults to @b false, not added when @b false. @@ -450,12 +450,12 @@ typedef struct /// @ingroup publish typedef struct { - const char* m_contentType; ///< "Content Type" property, not added NULL. - const char* m_responseTopic; ///< "Response Topic" property, not added when NULL. + const char* m_contentType; ///< "Content Type" property, defaults to NULL, not added when NULL. + const char* m_responseTopic; ///< "Response Topic" property, defaults to NULL, not added when NULL. const unsigned char* m_correlationData; ///< "Correlation Data" property, can be NULL. unsigned m_correlationDataLen; ///< Length of the "Correlation Data", not added when 0. - unsigned m_messageExpiryInterval; ///< "Message Expiry Interval" property, not added when 0. - CC_Mqtt5PayloadFormat m_format; ///< ///< "Payload Format Indicator" property, defaults to CC_Mqtt5PayloadFormat_Unspecified, not added when CC_Mqtt5PayloadFormat_Unspecified. + unsigned m_messageExpiryInterval; ///< "Message Expiry Interval" property, defaults to 0, not added when 0. + CC_Mqtt5PayloadFormat m_format; ///< "Payload Format Indicator" property, defaults to CC_Mqtt5PayloadFormat_Unspecified, not added when CC_Mqtt5PayloadFormat_Unspecified. } CC_Mqtt5PublishExtraConfig; /// @brief Response information from broker to "publish" request @@ -490,8 +490,8 @@ typedef unsigned (*CC_Mqtt5CancelNextTickWaitCb)(void* data); /// @brief Callback used to request to send data to the broker. /// @details The callback is set using /// cc_mqtt5_client_set_send_output_data_callback() function. The reported -/// data resides in internal data structures of the client library, and -/// it can be updated right after the callback function returns. It means +/// data resides in internal data structures of the client library, which +/// can be updated / deleted right after the callback function returns. It means /// the data may need to be copied into some other buffer which will be /// held intact until the send over I/O link operation is complete. /// @param[in] data Pointer to user data object, passed as last parameter to diff --git a/client/lib/templ/client.h.templ b/client/lib/templ/client.h.templ index 762f267..2156121 100644 --- a/client/lib/templ/client.h.templ +++ b/client/lib/templ/client.h.templ @@ -72,7 +72,7 @@ bool cc_mqtt5_##NAME##client_is_initialized(CC_Mqtt5ClientHandle handle); /// @ingroup client void cc_mqtt5_##NAME##client_tick(CC_Mqtt5ClientHandle handle, unsigned ms); -/// @brief Provide data, received over I/O link, to the library for processing. +/// @brief Provide data (received over I/O link), to the library for processing. /// @details This call may cause invocation of some callbacks, such as /// request to cancel the currently running time measurement, send some messages to /// the broker, report incoming application message, and (re)start time From b8fb17c575c272cd05f0b3830be120691a139a03 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sat, 30 Dec 2023 10:22:27 +1000 Subject: [PATCH 33/35] Improving appveyor configuration. --- script/appveyor_install.bat | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/script/appveyor_install.bat b/script/appveyor_install.bat index 4468e87..2482070 100644 --- a/script/appveyor_install.bat +++ b/script/appveyor_install.bat @@ -1,11 +1,12 @@ IF "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" ( set TOOLCHAIN=msvc15 set BOOST_VER=1_69_0 - set CMAKE_GENERATOR=Visual Studio 15 2017 + set CMAKE_GENERATOR=NMake Makefiles IF "%PLATFORM%"=="x86" ( echo Performing x86 build in VS2017 call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars32.bat" + ) ELSE ( echo Performing amd64 build in VS2017 call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" @@ -19,9 +20,11 @@ IF "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" ( IF "%PLATFORM%"=="x86" ( echo Performing x86 build in VS2019 call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars32.bat" + set CMAKE_PLATFORM=Win32 ) ELSE ( echo Performing amd64 build in VS2019 call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat" + set CMAKE_PLATFORM=x64 ) ) ELSE IF "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2022" ( @@ -32,19 +35,15 @@ IF "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" ( IF "%PLATFORM%"=="x86" ( echo Performing x86 build in VS2022 call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars32.bat" + set CMAKE_PLATFORM=Win32 ) ELSE ( echo Performing amd64 build in VS2022 call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" + set CMAKE_PLATFORM=x64 ) ) ELSE ( echo Toolchain %TOOLCHAIN% is not supported exit -1 ) -IF "%PLATFORM%"=="x86" ( - set CMAKE_PLATFORM=Win32 -) ELSE ( - set CMAKE_PLATFORM=x64 -) - set BOOST_DIR=C:\Libraries\boost_%BOOST_VER% From f6f20eb2654cc3d66ba9f81a9fa26d70bb504092 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sat, 30 Dec 2023 10:34:01 +1000 Subject: [PATCH 34/35] Cosmetic update to build. --- cmake/ProcessTemplate.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmake/ProcessTemplate.cmake b/cmake/ProcessTemplate.cmake index ef3ffca..b233392 100644 --- a/cmake/ProcessTemplate.cmake +++ b/cmake/ProcessTemplate.cmake @@ -6,6 +6,8 @@ if (NOT EXISTS "${IN_FILE}") message (FATAL_ERROR "Input file \"${IN_FILE}\" doesn't exist!") endif () +message (STATUS "Processing template \"${IN_FILE}\", updating name to \"${NAME}\"") + file (READ ${IN_FILE} text) string (REPLACE "##NAME##" "${NAME}" text "${text}") file (WRITE "${OUT_FILE}" "${text}") From c4d5c2788c4e3e7c0f9c15afd2d637e5c2ff4e15 Mon Sep 17 00:00:00 2001 From: Alex Robenko Date: Sat, 30 Dec 2023 10:41:12 +1000 Subject: [PATCH 35/35] Fixing write of the protocol option script for earlier versions of cmake. --- client/lib/script/WriteProtocolOptions.cmake | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/client/lib/script/WriteProtocolOptions.cmake b/client/lib/script/WriteProtocolOptions.cmake index 4babeb5..83c21e6 100644 --- a/client/lib/script/WriteProtocolOptions.cmake +++ b/client/lib/script/WriteProtocolOptions.cmake @@ -121,6 +121,12 @@ file (WRITE "${OUT_FILE}.tmp" "${text}") execute_process( COMMAND ${CMAKE_COMMAND} -E copy_if_different "${OUT_FILE}.tmp" "${OUT_FILE}") -execute_process( - COMMAND ${CMAKE_COMMAND} -E rm -rf "${OUT_FILE}.tmp") +if(CMAKE_VERSION VERSION_LESS "3.8.0") + execute_process( + COMMAND ${CMAKE_COMMAND} -E remove -f "${OUT_FILE}.tmp") +else () + execute_process( + COMMAND ${CMAKE_COMMAND} -E rm -rf "${OUT_FILE}.tmp") +endif() +