mirror of
https://github.com/espressif/esp-mqtt.git
synced 2026-10-30 19:16:24 +00:00
Merge branch 'split_msg_lib' into 'master'
MR: Split message lib into a sub component See merge request espressif/esp-mqtt!237
This commit is contained in:
@@ -38,8 +38,8 @@ examples_and_unit_tests:
|
||||
|
||||
third_party_libs:
|
||||
include:
|
||||
- "lib/mqtt_msg.c"
|
||||
- "lib/include/mqtt_msg.h"
|
||||
- "lib/mqtt_msg/mqtt_msg.c"
|
||||
- "lib/mqtt_msg/include/mqtt_msg.h"
|
||||
- "test/host/mocks/include/sys/queue.h"
|
||||
allowed_licenses:
|
||||
- BSD-3-Clause
|
||||
|
||||
@@ -39,5 +39,7 @@ host_tests:
|
||||
./build_linux_default/mqtt_utils_host_test.elf
|
||||
cd ../mqtt_outbox_host_test
|
||||
./build_linux_coverage/mqtt_outbox_host_test.elf
|
||||
cd ../mqtt_msg_host_test
|
||||
./build_linux_coverage/mqtt_msg_host_test.elf
|
||||
cd ../..
|
||||
gcovr --gcov-ignore-parse-errors -k -r . --exclude '.*/managed_components/.*' --print-summary --html coverage.html -x coverage.xml
|
||||
|
||||
+2
-2
@@ -128,8 +128,8 @@ mqtt_conformance_coverage_report{{ settings.gitlab.test_pipeline.job_name_suffix
|
||||
gcovr_filters=(
|
||||
--filter="${CI_PROJECT_DIR}/mqtt_client.c"
|
||||
--filter="${CI_PROJECT_DIR}/mqtt5_client.c"
|
||||
--filter="${CI_PROJECT_DIR}/lib/mqtt_msg.c"
|
||||
--filter="${CI_PROJECT_DIR}/lib/mqtt5_msg.c"
|
||||
--filter="${CI_PROJECT_DIR}/lib/mqtt_msg/mqtt_msg.c"
|
||||
--filter="${CI_PROJECT_DIR}/lib/mqtt_msg/mqtt5_msg.c"
|
||||
--filter="${CI_PROJECT_DIR}/lib/platform.c"
|
||||
)
|
||||
shopt -s nullglob
|
||||
|
||||
+6
-3
@@ -1,12 +1,12 @@
|
||||
set(srcs mqtt_client.c lib/mqtt_msg.c lib/platform.c)
|
||||
set(srcs mqtt_client.c lib/platform.c)
|
||||
|
||||
if(CONFIG_MQTT_PROTOCOL_5)
|
||||
list(APPEND srcs lib/mqtt5_msg.c mqtt5_client.c)
|
||||
list(APPEND srcs mqtt5_client.c)
|
||||
endif()
|
||||
|
||||
list(TRANSFORM srcs PREPEND ${CMAKE_CURRENT_LIST_DIR}/)
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS ${CMAKE_CURRENT_LIST_DIR}/include
|
||||
INCLUDE_DIRS ${CMAKE_CURRENT_LIST_DIR}/include ${CMAKE_CURRENT_LIST_DIR}/lib/mqtt_msg/include
|
||||
PRIV_INCLUDE_DIRS ${CMAKE_CURRENT_LIST_DIR}/lib/include
|
||||
REQUIRES esp_event tcp_transport
|
||||
PRIV_REQUIRES esp_timer http_parser esp_hw_support heap
|
||||
@@ -16,5 +16,8 @@ idf_component_register(SRCS "${srcs}"
|
||||
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/lib/mqtt_utils)
|
||||
target_link_libraries(${COMPONENT_LIB} PUBLIC idf::mqtt::utils)
|
||||
|
||||
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/lib/mqtt_msg)
|
||||
target_link_libraries(${COMPONENT_LIB} PRIVATE idf::mqtt::msg)
|
||||
|
||||
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/lib/mqtt_outbox)
|
||||
target_link_libraries(${COMPONENT_LIB} PRIVATE idf::mqtt::outbox)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
set(srcs "${CMAKE_CURRENT_LIST_DIR}/mqtt_msg.c")
|
||||
|
||||
if(CONFIG_MQTT_PROTOCOL_5)
|
||||
list(APPEND srcs "${CMAKE_CURRENT_LIST_DIR}/mqtt5_msg.c")
|
||||
endif()
|
||||
|
||||
add_library(mqtt_msg_lib ${srcs})
|
||||
target_include_directories(mqtt_msg_lib
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}/include
|
||||
${CMAKE_CURRENT_LIST_DIR}/../../include
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_LIST_DIR}/../include)
|
||||
|
||||
idf_component_get_property(heap heap COMPONENT_LIB)
|
||||
idf_component_get_property(log log COMPONENT_LIB)
|
||||
target_link_libraries(mqtt_msg_lib PRIVATE ${heap} ${log})
|
||||
|
||||
add_library(idf::mqtt::msg ALIAS mqtt_msg_lib)
|
||||
@@ -331,6 +331,9 @@ static mqtt5_user_property_handle_t mqtt5_msg_parse_user_property(uint8_t *buffe
|
||||
/* False positive: user_porperty is released at err or returned to the caller.
|
||||
* The analyzer cannot track its cleanup across the translation-unit boundary. */
|
||||
#pragma GCC diagnostic push
|
||||
#ifdef __clang__
|
||||
#pragma GCC diagnostic ignored "-Wunknown-warning-option"
|
||||
#endif
|
||||
#pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak"
|
||||
|
||||
if (mqtt5_msg_set_user_property(&user_property, (char *)key, key_len, (char *)value, value_len) != ESP_OK) {
|
||||
@@ -228,14 +228,14 @@ char *mqtt_get_publish_data(uint8_t *buffer, size_t *length)
|
||||
topiclen = buffer[i++] << 8;
|
||||
topiclen |= buffer[i++];
|
||||
|
||||
if (i + topiclen >= blength) {
|
||||
if (i + topiclen > blength) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
i += topiclen;
|
||||
|
||||
if (mqtt_get_qos(buffer) > 0) {
|
||||
if (i + 2 >= blength) {
|
||||
if (i + 2 > blength) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -6,5 +6,7 @@ target_include_directories(mqtt_outbox_lib PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}/../../include)
|
||||
idf_component_get_property(heap heap COMPONENT_LIB)
|
||||
idf_component_get_property(log log COMPONENT_LIB)
|
||||
target_link_libraries(mqtt_outbox_lib PRIVATE ${heap} ${log})
|
||||
target_link_libraries(mqtt_outbox_lib
|
||||
PUBLIC idf::mqtt::msg
|
||||
PRIVATE ${heap} ${log})
|
||||
add_library(idf::mqtt::outbox ALIAS mqtt_outbox_lib)
|
||||
|
||||
@@ -796,6 +796,9 @@ esp_err_t esp_mqtt5_client_set_connect_property(esp_mqtt5_client_handle_t client
|
||||
* esp_mqtt_destroy_config() calls esp_mqtt5_client_destory(), which frees this allocation.
|
||||
* The cleanup crosses a translation-unit boundary that analyzer does not follow. */
|
||||
#pragma GCC diagnostic push
|
||||
#ifdef __clang__
|
||||
#pragma GCC diagnostic ignored "-Wunknown-warning-option"
|
||||
#endif
|
||||
#pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak"
|
||||
client->mqtt5_config->will_property_info.correlation_data = malloc(connect_property->correlation_data_len);
|
||||
ESP_MEM_CHECK(TAG, client->mqtt5_config->will_property_info.correlation_data, goto _mqtt_set_config_failed);
|
||||
|
||||
@@ -301,6 +301,9 @@ bool esp_mqtt_set_if_config(char const *const new_config, char **old_config)
|
||||
* (e.g. client->config->uri). On the failure path esp_mqtt_destroy_config frees it,
|
||||
* but the analyzer cannot follow that cross-TU cleanup chain. */
|
||||
#pragma GCC diagnostic push
|
||||
#ifdef __clang__
|
||||
#pragma GCC diagnostic ignored "-Wunknown-warning-option"
|
||||
#endif
|
||||
#pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak"
|
||||
*old_config = strdup(new_config);
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@ if(CONFIG_ESP_GCOV_ENABLE AND COMMAND idf_create_coverage_report)
|
||||
GCOV_OPTIONS
|
||||
"--filter=${mqtt_root}/mqtt_client.c"
|
||||
"--filter=${mqtt_root}/mqtt5_client.c"
|
||||
"--filter=${mqtt_root}/lib/mqtt_msg.c"
|
||||
"--filter=${mqtt_root}/lib/mqtt5_msg.c"
|
||||
"--filter=${mqtt_root}/lib/mqtt_msg/mqtt_msg.c"
|
||||
"--filter=${mqtt_root}/lib/mqtt_msg/mqtt5_msg.c"
|
||||
"--filter=${mqtt_root}/lib/platform.c"
|
||||
"--xml=${CMAKE_CURRENT_BINARY_DIR}/coverage_report/coverage.xml")
|
||||
idf_clean_coverage_report(${CMAKE_CURRENT_BINARY_DIR}/coverage_report)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../../cmake/CPM.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../../cmake/RapidCheck.cmake)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
|
||||
project(mqtt_msg_host_test)
|
||||
@@ -0,0 +1,13 @@
|
||||
# mqtt_msg host tests
|
||||
|
||||
Isolated host tests for `lib/mqtt_msg`. Tests call the message API directly —
|
||||
no outbox, MQTT client, transport, or FreeRTOS scheduler required.
|
||||
|
||||
## Build and run
|
||||
|
||||
```bash
|
||||
cd test/mqtt_msg_host_test
|
||||
idf.py --preview set-target linux
|
||||
idf.py build
|
||||
./build/mqtt_msg_host_test.elf
|
||||
```
|
||||
@@ -0,0 +1,24 @@
|
||||
idf_component_register(SRCS "test_main.cpp" "test_mqtt_msg.cpp"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES heap log
|
||||
WHOLE_ARCHIVE)
|
||||
|
||||
set(SANITIZER_FLAGS -fsanitize=address,undefined -fno-sanitize-recover=undefined)
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${SANITIZER_FLAGS} -Wno-missing-field-initializers)
|
||||
target_link_options(${COMPONENT_LIB} INTERFACE ${SANITIZER_FLAGS})
|
||||
|
||||
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../../../lib/mqtt_msg
|
||||
${CMAKE_CURRENT_BINARY_DIR}/mqtt_msg)
|
||||
|
||||
target_compile_options(mqtt_msg_lib PRIVATE ${SANITIZER_FLAGS})
|
||||
|
||||
target_link_libraries(${COMPONENT_LIB} PUBLIC
|
||||
idf::mqtt::msg
|
||||
Catch2::Catch2WithMain
|
||||
rapidcheck
|
||||
rapidcheck_catch)
|
||||
|
||||
if(CONFIG_GCOV_ENABLED)
|
||||
target_compile_options(mqtt_msg_lib PUBLIC --coverage -fprofile-arcs -ftest-coverage)
|
||||
target_link_options(mqtt_msg_lib PUBLIC --coverage -fprofile-arcs -ftest-coverage)
|
||||
endif()
|
||||
@@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
espressif/catch2:
|
||||
version: "*"
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <catch2/catch_session.hpp>
|
||||
|
||||
extern "C" void app_main(void)
|
||||
{
|
||||
int argc = 1;
|
||||
const char *argv[2] = {"mqtt_msg_host_test", nullptr};
|
||||
int result = Catch::Session().run(argc, argv);
|
||||
|
||||
if (result != 0) {
|
||||
printf("Test failed with result %d\n", result);
|
||||
exit(1);
|
||||
} else {
|
||||
printf("Test passed.\n");
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <rapidcheck.h>
|
||||
#include <rapidcheck/catch.h>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <string>
|
||||
|
||||
extern "C" {
|
||||
#include "mqtt_msg.h"
|
||||
|
||||
int platform_random(int max)
|
||||
{
|
||||
if (max <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static std::mt19937 engine{std::random_device{}()};
|
||||
std::uniform_int_distribution<int> distribution(0, max - 1);
|
||||
return distribution(engine);
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
constexpr size_t mqtt_fixed_header_size = 5;
|
||||
|
||||
struct MessageDeleter {
|
||||
void operator()(mqtt_message_t *message) const
|
||||
{
|
||||
mqtt_msg_destroy(message);
|
||||
}
|
||||
};
|
||||
|
||||
using MessagePtr = std::unique_ptr<mqtt_message_t, MessageDeleter>;
|
||||
|
||||
MessagePtr make_message(size_t buffer_size)
|
||||
{
|
||||
return MessagePtr(mqtt_msg_create(buffer_size));
|
||||
}
|
||||
|
||||
std::string generated_bytes(int length, int minimum, int maximum)
|
||||
{
|
||||
std::string value(static_cast<size_t>(length), '\0');
|
||||
|
||||
for (char &byte : value) {
|
||||
byte = static_cast<char>(*rc::gen::inRange(minimum, maximum));
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("mqtt_msg_pingreq encodes the fixed MQTT control packet")
|
||||
{
|
||||
MessagePtr message = make_message(mqtt_fixed_header_size);
|
||||
REQUIRE(message != nullptr);
|
||||
mqtt_message_t *encoded = mqtt_msg_pingreq(message.get());
|
||||
REQUIRE(encoded == message.get());
|
||||
REQUIRE(encoded->length == 2);
|
||||
REQUIRE(mqtt_get_type(encoded->data) == MQTT_MSG_TYPE_PINGREQ);
|
||||
REQUIRE(encoded->data[0] == 0xC0);
|
||||
REQUIRE(encoded->data[1] == 0x00);
|
||||
REQUIRE(mqtt_has_valid_msg_hdr(encoded->data, encoded->length) == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("mqtt_msg_publish rejects an empty topic")
|
||||
{
|
||||
MessagePtr message = make_message(32);
|
||||
REQUIRE(message != nullptr);
|
||||
uint16_t message_id = 7;
|
||||
mqtt_message_t *encoded = mqtt_msg_publish(message.get(), "", "payload", 7, 0, 0, &message_id);
|
||||
REQUIRE(encoded == message.get());
|
||||
REQUIRE(encoded->length == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("mqtt_msg_publish round-trips an empty payload")
|
||||
{
|
||||
const int qos_values[] = {0, 1};
|
||||
|
||||
for (const int qos : qos_values) {
|
||||
const size_t buffer_size = mqtt_fixed_header_size + 2 + 1 + (qos > 0 ? 2 : 0);
|
||||
MessagePtr message = make_message(buffer_size);
|
||||
REQUIRE(message != nullptr);
|
||||
uint16_t message_id = 0;
|
||||
mqtt_message_t *encoded = mqtt_msg_publish(message.get(), "t", nullptr, 0, qos, 0, &message_id);
|
||||
REQUIRE(encoded == message.get());
|
||||
REQUIRE(encoded->length > 0);
|
||||
size_t payload_length = encoded->length;
|
||||
const char *payload = mqtt_get_publish_data(encoded->data, &payload_length);
|
||||
REQUIRE(payload != nullptr);
|
||||
REQUIRE(payload_length == 0);
|
||||
|
||||
if (qos == 0) {
|
||||
REQUIRE(message_id == 0);
|
||||
} else {
|
||||
REQUIRE(message_id != 0);
|
||||
REQUIRE(mqtt_get_id(encoded->data, encoded->length) == message_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("mqtt_msg_publish round-trips a complete packet")
|
||||
{
|
||||
rc::prop("encoded publish recovers topic, payload, flags, and packet id", []() {
|
||||
const int qos = *rc::gen::inRange(0, 3);
|
||||
const int retain = *rc::gen::inRange(0, 2);
|
||||
const std::string topic = generated_bytes(*rc::gen::inRange(1, 65), 1, 127);
|
||||
const std::string payload = generated_bytes(*rc::gen::inRange(0, 129), 0, 256);
|
||||
const size_t buffer_size = mqtt_fixed_header_size + 2 + topic.size() +
|
||||
(qos > 0 ? 2 : 0) + payload.size();
|
||||
MessagePtr message = make_message(buffer_size);
|
||||
RC_ASSERT(message != nullptr);
|
||||
uint16_t message_id = 0;
|
||||
mqtt_message_t *encoded = mqtt_msg_publish(message.get(), topic.c_str(), payload.data(),
|
||||
static_cast<int>(payload.size()), qos, retain, &message_id);
|
||||
RC_ASSERT(encoded == message.get());
|
||||
RC_ASSERT(encoded->length > 0);
|
||||
RC_ASSERT(encoded->length <= buffer_size);
|
||||
int fixed_header_size = 0;
|
||||
const size_t total_length = mqtt_get_total_length(encoded->data, encoded->length, &fixed_header_size);
|
||||
RC_ASSERT(total_length == encoded->length);
|
||||
RC_ASSERT(fixed_header_size >= 2);
|
||||
RC_ASSERT(mqtt_get_type(encoded->data) == MQTT_MSG_TYPE_PUBLISH);
|
||||
RC_ASSERT(mqtt_get_qos(encoded->data) == qos);
|
||||
RC_ASSERT(mqtt_get_retain(encoded->data) == retain);
|
||||
RC_ASSERT(mqtt_get_dup(encoded->data) == 0);
|
||||
RC_ASSERT(mqtt_has_valid_msg_hdr(encoded->data, encoded->length) == 1);
|
||||
size_t topic_length = encoded->length;
|
||||
const char *encoded_topic = mqtt_get_publish_topic(encoded->data, &topic_length);
|
||||
RC_ASSERT(encoded_topic != nullptr);
|
||||
RC_ASSERT(topic_length == topic.size());
|
||||
RC_ASSERT(std::memcmp(encoded_topic, topic.data(), topic.size()) == 0);
|
||||
size_t payload_length = encoded->length;
|
||||
const char *encoded_payload = mqtt_get_publish_data(encoded->data, &payload_length);
|
||||
RC_ASSERT(encoded_payload != nullptr);
|
||||
RC_ASSERT(payload_length == payload.size());
|
||||
RC_ASSERT(std::memcmp(encoded_payload, payload.data(), payload.size()) == 0);
|
||||
|
||||
if (qos == 0) {
|
||||
RC_ASSERT(message_id == 0);
|
||||
} else {
|
||||
RC_ASSERT(message_id != 0);
|
||||
RC_ASSERT(mqtt_get_id(encoded->data, encoded->length) == message_id);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_GCOV_ENABLED=y
|
||||
@@ -0,0 +1,8 @@
|
||||
CONFIG_IDF_TARGET="linux"
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS=y
|
||||
CONFIG_COMPILER_CXX_RTTI=y
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=0
|
||||
CONFIG_COMPILER_DISABLE_DEFAULT_ERRORS=y
|
||||
CONFIG_LOG_COLORS=n
|
||||
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=10000
|
||||
@@ -7,22 +7,16 @@ set(SANITIZER_FLAGS -fsanitize=address,undefined -fno-sanitize-recover=undefined
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${SANITIZER_FLAGS} -Wno-missing-field-initializers)
|
||||
target_link_options(${COMPONENT_LIB} INTERFACE ${SANITIZER_FLAGS})
|
||||
|
||||
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../../../lib/mqtt_msg
|
||||
${CMAKE_CURRENT_BINARY_DIR}/mqtt_msg)
|
||||
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../../../lib/mqtt_outbox
|
||||
${CMAKE_CURRENT_BINARY_DIR}/mqtt_outbox)
|
||||
|
||||
# mqtt_outbox.c now owns mqtt_message_t and calls mqtt_msg_destroy().
|
||||
add_library(mqtt_msg_lib "${CMAKE_CURRENT_LIST_DIR}/../../../lib/mqtt_msg.c")
|
||||
target_include_directories(mqtt_msg_lib PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}/../../../lib/include
|
||||
${CMAKE_CURRENT_LIST_DIR}/../../../include)
|
||||
idf_component_get_property(heap heap COMPONENT_LIB)
|
||||
idf_component_get_property(log log COMPONENT_LIB)
|
||||
target_link_libraries(mqtt_msg_lib PRIVATE ${heap} ${log})
|
||||
target_compile_options(mqtt_msg_lib PRIVATE ${SANITIZER_FLAGS})
|
||||
|
||||
target_link_libraries(${COMPONENT_LIB} PUBLIC
|
||||
idf::mqtt::outbox
|
||||
mqtt_msg_lib
|
||||
idf::mqtt::msg
|
||||
Catch2::Catch2WithMain
|
||||
rapidcheck
|
||||
rapidcheck_catch)
|
||||
|
||||
Reference in New Issue
Block a user