From 3d275f42b99b41bd9326728ae5628f4f5c77c41b Mon Sep 17 00:00:00 2001 From: yuanjm Date: Fri, 25 Feb 2022 10:12:30 +0800 Subject: [PATCH 1/2] mqtt: Support MQTT5 protocol --- include/mqtt_client.h | 269 ++++++++- lib/include/mqtt5_msg.h | 142 +++++ lib/include/mqtt_config.h | 4 + lib/mqtt5_msg.c | 1040 +++++++++++++++++++++++++++++++++++ mqtt_client.c | 1099 ++++++++++++++++++++++++++++++++++--- 5 files changed, 2490 insertions(+), 64 deletions(-) create mode 100644 lib/include/mqtt5_msg.h create mode 100644 lib/mqtt5_msg.c diff --git a/include/mqtt_client.h b/include/mqtt_client.h index d2f7198..ca5722b 100644 --- a/include/mqtt_client.h +++ b/include/mqtt_client.h @@ -114,9 +114,146 @@ typedef enum esp_mqtt_transport_t { typedef enum esp_mqtt_protocol_ver_t { MQTT_PROTOCOL_UNDEFINED = 0, MQTT_PROTOCOL_V_3_1, - MQTT_PROTOCOL_V_3_1_1 + MQTT_PROTOCOL_V_3_1_1, + MQTT_PROTOCOL_V_5, } esp_mqtt_protocol_ver_t; +#ifdef CONFIG_MQTT_PROTOCOL_5 +/** + * MQTT5 protocol error reason code, more details refer to MQTT5 protocol document section 2.4 + */ +enum mqtt5_error_reason_code { + MQTT5_UNSPECIFIED_ERROR = 0x80, + MQTT5_MALFORMED_PACKET = 0x81, + MQTT5_PROTOCOL_ERROR = 0x82, + MQTT5_IMPLEMENT_SPECIFIC_ERROR = 0x83, + MQTT5_UNSUPPORTED_PROTOCOL_VER = 0x84, + MQTT5_INVAILD_CLIENT_ID = 0x85, + MQTT5_BAD_USERNAME_OR_PWD = 0x86, + MQTT5_NOT_AUTHORIZED = 0x87, + MQTT5_SERVER_UNAVAILABLE = 0x88, + MQTT5_SERVER_BUSY = 0x89, + MQTT5_BANNED = 0x8A, + MQTT5_SERVER_SHUTTING_DOWN = 0x8B, + MQTT5_BAD_AUTH_METHOD = 0x8C, + MQTT5_KEEP_ALIVE_TIMEOUT = 0x8D, + MQTT5_SESSION_TAKEN_OVER = 0x8E, + MQTT5_TOPIC_FILTER_INVAILD = 0x8F, + MQTT5_TOPIC_NAME_INVAILD = 0x90, + MQTT5_PACKET_IDENTIFIER_IN_USE = 0x91, + MQTT5_PACKET_IDENTIFIER_NOT_FOUND = 0x92, + MQTT5_RECEIVE_MAXIMUM_EXCEEDED = 0x93, + MQTT5_TOPIC_ALIAS_INVAILD = 0x94, + MQTT5_PACKET_TOO_LARGE = 0x95, + MQTT5_MESSAGE_RATE_TOO_HIGH = 0x96, + MQTT5_QUOTA_EXCEEDED = 0x97, + MQTT5_ADMINISTRATIVE_ACTION = 0x98, + MQTT5_PAYLOAD_FORMAT_INVAILD = 0x99, + MQTT5_RETAIN_NOT_SUPPORT = 0x9A, + MQTT5_QOS_NOT_SUPPORT = 0x9B, + MQTT5_USE_ANOTHER_SERVER = 0x9C, + MQTT5_SERVER_MOVED = 0x9D, + MQTT5_SHARED_SUBSCR_NOT_SUPPORTED = 0x9E, + MQTT5_CONNECTION_RATE_EXCEEDED = 0x9F, + MQTT5_MAXIMUM_CONNECT_TIME = 0xA0, + MQTT5_SUBSCRIBE_IDENTIFIER_NOT_SUPPORT = 0xA1, + MQTT5_WILDCARD_SUBSCRIBE_NOT_SUPPORT = 0xA2, +}; + +/** + * MQTT5 user property handle + */ +typedef struct mqtt5_user_property_list_t *mqtt5_user_property_handle_t; + +/** + * MQTT5 protocol connect properties and will properties configuration, more details refer to MQTT5 protocol document section 3.1.2.11 and 3.3.2.3 + */ +typedef struct { + uint32_t session_expiry_interval; /*!< The interval time of session expiry */ + uint32_t maximum_packet_size; /*!< The maximum packet size that we can receive */ + uint16_t receive_maximum; /*!< The maximum pakcket count that we process concurrently */ + uint16_t topic_alias_maximum; /*!< The maximum topic alias that we support */ + bool request_resp_info; /*!< This value to request Server to return Response information */ + bool request_problem_info; /*!< This value to indicate whether the reason string or user properties are sent in case of failures */ + mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */ + uint32_t will_delay_interval; /*!< The time interval that server delays publishing will message */ + uint32_t message_expiry_interval; /*!< The time interval that message expiry */ + bool payload_format_indicator; /*!< This value is to indicator will message payload format */ + const char *content_type; /*!< This value is to indicator will message content type, use a MIME content type string */ + const char *response_topic; /*!< Topic name for a response message */ + const char *correlation_data; /*!< Binary data for receiver to match the response message */ + uint16_t correlation_data_len; /*!< The length of correlation data */ + mqtt5_user_property_handle_t will_user_property; /*!< The handle for will message user property, call function esp_mqtt5_client_set_user_property to set it */ +} esp_mqtt5_connection_property_config_t; + +/** + * MQTT5 protocol publish properties configuration, more details refer to MQTT5 protocol document section 3.3.2.3 + */ +typedef struct { + bool payload_format_indicator; /*!< This value is to indicator publish message payload format */ + uint32_t message_expiry_interval; /*!< The time interval that message expiry */ + uint16_t topic_alias; /*!< An interger value to identify the topic instead of using topic name string */ + const char *response_topic; /*!< Topic name for a response message */ + const char *correlation_data; /*!< Binary data for receiver to match the response message */ + uint16_t correlation_data_len; /*!< The length of correlation data */ + const char *content_type; /*!< This value is to indicator publish message content type, use a MIME content type string */ + mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */ +} esp_mqtt5_publish_property_config_t; + +/** + * MQTT5 protocol subscribe properties configuration, more details refer to MQTT5 protocol document section 3.8.2.1 + */ +typedef struct { + uint16_t subscribe_id; /*!< A variable byte represents the identifier of the subscription */ + bool no_local_flag; /*!< Subscription Option to allow that server publish message that client sent */ + bool retain_as_published_flag; /*!< Subscription Option to keep the retain flag as published option */ + uint8_t retain_handle; /*!< Subscription Option to handle retain option */ + bool is_share_subscribe; /*!< Whether subscribe is a shared subscription */ + const char *share_name; /*!< The name of shared subscription which is a part of $share/{share_name}/{topic} */ + mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */ +} esp_mqtt5_subscribe_property_config_t; + +/** + * MQTT5 protocol unsubscribe properties configuration, more details refer to MQTT5 protocol document section 3.10.2.1 + */ +typedef struct { + bool is_share_subscribe; /*!< Whether subscribe is a shared subscription */ + const char *share_name; /*!< The name of shared subscription which is a part of $share/{share_name}/{topic} */ + mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */ +} esp_mqtt5_unsubscribe_property_config_t; + +/** + * MQTT5 protocol disconnect properties configuration, more details refer to MQTT5 protocol document section 3.14.2.2 + */ +typedef struct { + uint32_t session_expiry_interval; /*!< The interval time of session expiry */ + uint8_t disconnect_reason; /*!< The reason that connection disconnet, refer to mqtt5_error_reason_code */ + mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */ +} esp_mqtt5_disconnect_property_config_t; + +/** + * MQTT5 protocol for event properties + */ +typedef struct { + bool payload_format_indicator; /*!< Payload format of the message */ + char *response_topic; /*!< Response topic of the message */ + int response_topic_len; /*!< Response topic length of the message */ + char *correlation_data; /*!< Correlation data of the message */ + uint16_t correlation_data_len; /*!< Correlation data length of the message */ + char *content_type; /*!< Content type of the message */ + int content_type_len; /*!< Content type length of the message */ + mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_delete_user_property to free the memory */ +} esp_mqtt5_event_property_t; + +/** + * MQTT5 protocol for user property + */ +typedef struct { + const char *key; /*!< Item key name */ + const char *value; /*!< Item value string */ +} esp_mqtt5_user_property_item_t; +#endif + /** * @brief MQTT error code structure to be passed as a contextual information into ERROR event * @@ -161,6 +298,10 @@ typedef struct esp_mqtt_event_t { bool retain; /*!< Retained flag of the message associated with this event */ int qos; /*!< qos of the messages associated with this event */ bool dup; /*!< dup flag of the message associated with this event */ + esp_mqtt_protocol_ver_t protocol_ver; /*!< MQTT protocol version used for connection, defaults to value from menuconfig*/ +#ifdef CONFIG_MQTT_PROTOCOL_5 + esp_mqtt5_event_property_t *property; /*!< MQTT 5 property associated with this event */ +#endif } esp_mqtt_event_t; typedef esp_mqtt_event_t *esp_mqtt_event_handle_t; @@ -421,6 +562,132 @@ esp_err_t esp_mqtt_client_register_event(esp_mqtt_client_handle_t client, esp_mq */ int esp_mqtt_client_get_outbox_size(esp_mqtt_client_handle_t client); +#ifdef CONFIG_MQTT_PROTOCOL_5 +/** + * @brief Set MQTT5 client connect property configuration + * + * @param client mqtt client handle + * @param connect_property connect property + * + * @return ESP_ERR_NO_MEM if failed to allocate + * ESP_ERR_INVALID_ARG on wrong initialization + * ESP_FAIL on fail + * ESP_OK on success + */ +esp_err_t esp_mqtt5_client_set_connect_property(esp_mqtt_client_handle_t client, const esp_mqtt5_connection_property_config_t *connect_property); + +/** + * @brief Set MQTT5 client publish property configuration + * + * This API will not store the publish property, it is one-time configuration. + * Before call `esp_mqtt_client_publish` to publish data, call this API to set publish property if have + * + * @param client mqtt client handle + * @param property publish property + * + * @return ESP_ERR_INVALID_ARG on wrong initialization + * ESP_FAIL on fail + * ESP_OK on success + */ +esp_err_t esp_mqtt5_client_set_publish_property(esp_mqtt_client_handle_t client, const esp_mqtt5_publish_property_config_t *property); + +/** + * @brief Set MQTT5 client subscribe property configuration + * + * This API will not store the subscribe property, it is one-time configuration. + * Before call `esp_mqtt_client_subscribe` to subscribe topic, call this API to set subscribe property if have + * + * @param client mqtt client handle + * @param property subscribe property + * + * @return ESP_ERR_INVALID_ARG on wrong initialization + * ESP_FAIL on fail + * ESP_OK on success + */ +esp_err_t esp_mqtt5_client_set_subscribe_property(esp_mqtt_client_handle_t client, const esp_mqtt5_subscribe_property_config_t *property); + +/** + * @brief Set MQTT5 client unsubscribe property configuration + * + * This API will not store the unsubscribe property, it is one-time configuration. + * Before call `esp_mqtt_client_unsubscribe` to unsubscribe topic, call this API to set unsubscribe property if have + * + * @param client mqtt client handle + * @param property unsubscribe property + * + * @return ESP_ERR_INVALID_ARG on wrong initialization + * ESP_FAIL on fail + * ESP_OK on success + */ +esp_err_t esp_mqtt5_client_set_unsubscribe_property(esp_mqtt_client_handle_t client, const esp_mqtt5_unsubscribe_property_config_t *property); + +/** + * @brief Set MQTT5 client disconnect property configuration + * + * This API will not store the disconnect property, it is one-time configuration. + * Before call `esp_mqtt_client_disconnect` to disconnect connection, call this API to set disconnect property if have + * + * @param client mqtt client handle + * @param property disconnect property + * + * @return ESP_ERR_NO_MEM if failed to allocate + * ESP_ERR_INVALID_ARG on wrong initialization + * ESP_FAIL on fail + * ESP_OK on success + */ +esp_err_t esp_mqtt5_client_set_disconnect_property(esp_mqtt_client_handle_t client, const esp_mqtt5_disconnect_property_config_t *property); + +/** + * @brief Set MQTT5 client user property configuration + * + * This API will allocate memory for user_property, please DO NOT forget `call esp_mqtt5_client_delete_user_property` + * after you use it. + * Before publish data, subscribe topic, unsubscribe, etc, call this API to set user property if have + * + * @param user_property user_property handle + * @param item array of user property data (eg. {{"var","val"},{"other","2"}}) + * @param item_num number of items in user property data + * + * @return ESP_ERR_NO_MEM if failed to allocate + * ESP_FAIL on fail + * ESP_OK on success + */ +esp_err_t esp_mqtt5_client_set_user_property(mqtt5_user_property_handle_t *user_property, esp_mqtt5_user_property_item_t item[], uint8_t item_num); + +/** + * @brief Get MQTT5 client user property + * + * @param user_property user_property handle + * @param item point that store user property data + * @param item_num number of items in user property data + * + * This API can use with `esp_mqtt5_client_get_user_property_count` to get list count of user property. + * And malloc number of count item array memory to store the user property data. + * Please DO NOT forget the item memory, key and value point in item memory when get user property data successfully. + * + * @return ESP_ERR_NO_MEM if failed to allocate + * ESP_FAIL on fail + * ESP_OK on success + */ +esp_err_t esp_mqtt5_client_get_user_property(mqtt5_user_property_handle_t user_property, esp_mqtt5_user_property_item_t *item, uint8_t *item_num); + +/** + * @brief Get MQTT5 client user property list count + * + * @param user_property user_property handle + * @return user property list count + */ +uint8_t esp_mqtt5_client_get_user_property_count(mqtt5_user_property_handle_t user_property); + +/** + * @brief Free the user property list + * + * @param user_property user_property handle + * + * This API will free the memory in user property list and free user_property itself + */ +void esp_mqtt5_client_delete_user_property(mqtt5_user_property_handle_t user_property); +#endif #ifdef __cplusplus } #endif //__cplusplus diff --git a/lib/include/mqtt5_msg.h b/lib/include/mqtt5_msg.h new file mode 100644 index 0000000..7e3b33a --- /dev/null +++ b/lib/include/mqtt5_msg.h @@ -0,0 +1,142 @@ +#ifndef MQTT5_MSG_H +#define MQTT5_MSG_H +#include +#include + +#include "mqtt_config.h" +#include "mqtt_msg.h" +#include "mqtt_client.h" +#ifdef __cplusplus +extern "C" { +#endif + +enum mqtt_properties_type { + MQTT5_PROPERTY_PAYLOAD_FORMAT_INDICATOR = 0x01, + MQTT5_PROPERTY_MESSAGE_EXPIRY_INTERVAL = 0x02, + MQTT5_PROPERTY_CONTENT_TYPE = 0x03, + MQTT5_PROPERTY_RESPONSE_TOPIC = 0x08, + MQTT5_PROPERTY_CORRELATION_DATA = 0x09, + MQTT5_PROPERTY_SUBSCRIBE_IDENTIFIER = 0x0B, + MQTT5_PROPERTY_SESSION_EXPIRY_INTERVAL = 0x11, + MQTT5_PROPERTY_ASSIGNED_CLIENT_IDENTIFIER = 0x12, + MQTT5_PROPERTY_SERVER_KEEP_ALIVE = 0x13, + MQTT5_PROPERTY_AUTHENTICATION_METHOD = 0x15, + MQTT5_PROPERTY_AUTHENTICATION_DATA = 0x16, + MQTT5_PROPERTY_REQUEST_PROBLEM_INFO = 0x17, + MQTT5_PROPERTY_WILL_DELAY_INTERVAL = 0x18, + MQTT5_PROPERTY_REQUEST_RESP_INFO = 0x19, + MQTT5_PROPERTY_RESP_INFO = 0x1A, + MQTT5_PROPERTY_SERVER_REFERENCE = 0x1C, + MQTT5_PROPERTY_REASON_STRING = 0x1F, + MQTT5_PROPERTY_RECEIVE_MAXIMUM = 0x21, + MQTT5_PROPERTY_TOPIC_ALIAS_MAXIMIM = 0x22, + MQTT5_PROPERTY_TOPIC_ALIAS = 0x23, + MQTT5_PROPERTY_MAXIMUM_QOS = 0x24, + MQTT5_PROPERTY_RETAIN_AVAILABLE = 0x25, + MQTT5_PROPERTY_USER_PROPERTY = 0x26, + MQTT5_PROPERTY_MAXIMUM_PACKET_SIZE = 0x27, + MQTT5_PROPERTY_WILDCARD_SUBSCR_AVAILABLE = 0x28, + MQTT5_PROPERTY_SUBSCR_IDENTIFIER_AVAILABLE = 0x29, + MQTT5_PROPERTY_SHARED_SUBSCR_AVAILABLE = 0x2A, +}; + +typedef struct mqtt5_user_property { + char *key; + char *value; + STAILQ_ENTRY(mqtt5_user_property) next; +} mqtt5_user_property_t; +STAILQ_HEAD(mqtt5_user_property_list_t, mqtt5_user_property); +typedef struct mqtt5_user_property *mqtt5_user_property_item_t; + +typedef struct { + uint32_t maximum_packet_size; + uint16_t receive_maximum; + uint16_t topic_alias_maximum; + uint8_t max_qos; + bool retain_available; + bool wildcard_subscribe_available; + bool subscribe_identifiers_available; + bool shared_subscribe_available; + char *response_info; +} esp_mqtt5_connection_server_resp_property_t; + +typedef struct { + bool payload_format_indicator; + uint32_t message_expiry_interval; + uint16_t topic_alias; + char *response_topic; + int response_topic_len; + char *correlation_data; + uint16_t correlation_data_len; + char *content_type; + int content_type_len; + uint16_t subscribe_id; +} esp_mqtt5_publish_resp_property_t; + +typedef struct { + uint32_t session_expiry_interval; + uint32_t maximum_packet_size; + uint16_t receive_maximum; + uint16_t topic_alias_maximum; + bool request_resp_info; + bool request_problem_info; + mqtt5_user_property_handle_t user_property; +} esp_mqtt5_connection_property_storage_t; + +typedef struct { + uint32_t will_delay_interval; + uint32_t message_expiry_interval; + bool payload_format_indicator; + char *content_type; + char *response_topic; + char *correlation_data; + uint16_t correlation_data_len; + mqtt5_user_property_handle_t user_property; +} esp_mqtt5_connection_will_property_storage_t; + +#define mqtt5_get_type mqtt_get_type + +#define mqtt5_get_dup mqtt_get_dup + +#define mqtt5_set_dup mqtt_set_dup + +#define mqtt5_get_qos mqtt_get_qos + +#define mqtt5_get_retain mqtt_get_retain + +#define mqtt5_msg_init mqtt_msg_init + +#define mqtt5_get_total_length mqtt_get_total_length + +#define mqtt5_has_valid_msg_hdr mqtt_has_valid_msg_hdr + +#define mqtt5_msg_pingreq mqtt_msg_pingreq + +#define mqtt5_msg_pingresp mqtt_msg_pingresp + +#define mqtt5_get_unsuback_data mqtt5_get_suback_data + +#define mqtt5_get_pubcomp_data mqtt5_get_puback_data + +uint16_t mqtt5_get_id(uint8_t *buffer, size_t length); +char *mqtt5_get_publish_property_payload(uint8_t *buffer, size_t buffer_length, char **msg_topic, size_t *msg_topic_len, esp_mqtt5_publish_resp_property_t *resp_property, uint16_t *property_len, size_t *payload_len, mqtt5_user_property_handle_t *user_property); +char *mqtt5_get_suback_data(uint8_t *buffer, size_t *length, mqtt5_user_property_handle_t *user_property); +char *mqtt5_get_puback_data(uint8_t *buffer, size_t *length, mqtt5_user_property_handle_t *user_property); +mqtt_message_t *mqtt5_msg_connect(mqtt_connection_t *connection, mqtt_connect_info_t *info, esp_mqtt5_connection_property_storage_t *property, esp_mqtt5_connection_will_property_storage_t *will_property); +mqtt_message_t *mqtt5_msg_publish(mqtt_connection_t *connection, const char *topic, const char *data, int data_length, int qos, int retain, uint16_t *message_id, const esp_mqtt5_publish_property_config_t *property, const char *resp_info); +esp_err_t mqtt5_msg_parse_connack_property(uint8_t *buffer, size_t buffer_len, mqtt_connect_info_t *connection_info, esp_mqtt5_connection_property_storage_t *connection_property, esp_mqtt5_connection_server_resp_property_t *resp_property, int *reason_code, uint8_t *ack_flag, mqtt5_user_property_handle_t *user_property); +int mqtt5_msg_get_reason_code(uint8_t *buffer, size_t length); +mqtt_message_t *mqtt5_msg_subscribe(mqtt_connection_t *connection, const char *topic, int qos, uint16_t *message_id, const esp_mqtt5_subscribe_property_config_t *property); +mqtt_message_t *mqtt5_msg_unsubscribe(mqtt_connection_t *connection, const char *topic, uint16_t *message_id, const esp_mqtt5_unsubscribe_property_config_t *property); +mqtt_message_t *mqtt5_msg_disconnect(mqtt_connection_t *connection, esp_mqtt5_disconnect_property_config_t *disconnect_property_info); +mqtt_message_t *mqtt5_msg_pubcomp(mqtt_connection_t *connection, uint16_t message_id); +mqtt_message_t *mqtt5_msg_pubrel(mqtt_connection_t *connection, uint16_t message_id); +mqtt_message_t *mqtt5_msg_pubrec(mqtt_connection_t *connection, uint16_t message_id); +mqtt_message_t *mqtt5_msg_puback(mqtt_connection_t *connection, uint16_t message_id); + +#ifdef __cplusplus +} +#endif + +#endif /* MQTT5_MSG_H */ + diff --git a/lib/include/mqtt_config.h b/lib/include/mqtt_config.h index 25685d6..4cc06d7 100644 --- a/lib/include/mqtt_config.h +++ b/lib/include/mqtt_config.h @@ -12,6 +12,10 @@ #define MQTT_PROTOCOL_311 #endif +#ifdef CONFIG_MQTT_PROTOCOL_5 +#define MQTT_PROTOCOL_5 +#endif + #define MQTT_RECON_DEFAULT_MS (10*1000) #define MQTT_POLL_READ_TIMEOUT_MS (1000) diff --git a/lib/mqtt5_msg.c b/lib/mqtt5_msg.c new file mode 100644 index 0000000..2163c40 --- /dev/null +++ b/lib/mqtt5_msg.c @@ -0,0 +1,1040 @@ +#include +#include "mqtt5_msg.h" +#include "mqtt_config.h" +#include "platform.h" +#include "esp_log.h" + +#define MQTT5_MAX_FIXED_HEADER_SIZE 5 + +static const char *TAG = "mqtt5_msg"; + +#define APPEND_CHECK(a, ret) if(a == -1) { \ + ESP_LOGE(TAG,"%s(%d) fail",__FUNCTION__, __LINE__); \ + return (ret); \ + } +#define MQTT5_SHARED_SUB "$share/%s/%s" +#define MQTT5_CONVERT_ONE_BYTE_TO_FOUR(i, a, b, c, d) i = (a << 24); \ + i |= (b << 16); \ + i |= (c << 8); \ + i |= d; + +#define MQTT5_CONVERT_ONE_BYTE_TO_TWO(i, a, b) i = (a << 8); \ + i |= b; + +#define MQTT5_CONVERT_TWO_BYTE(i, a) i = (a >> 8) & 0xff; \ + i = a & 0xff; + +enum mqtt5_connect_flag { + MQTT5_CONNECT_FLAG_USERNAME = 1 << 7, + MQTT5_CONNECT_FLAG_PASSWORD = 1 << 6, + MQTT5_CONNECT_FLAG_WILL_RETAIN = 1 << 5, + MQTT5_CONNECT_FLAG_WILL = 1 << 2, + MQTT5_CONNECT_FLAG_CLEAN_SESSION = 1 << 1 +}; + +static void generate_variable_len(size_t len, uint8_t *len_bytes, uint8_t *encoded_lens) +{ + uint8_t bytes = 0; + do { + uint8_t i = len % 128; + len /= 128; + if (len > 0) { + i |= 0x80; + } + encoded_lens[bytes ++] = i; + } while (len > 0); + *len_bytes = bytes; +} + +static size_t get_variable_len(uint8_t *buffer, size_t offset, size_t buffer_length, uint8_t *len_bytes) +{ + *len_bytes = 0; + size_t len = 0, i = 0; + for (i = offset; i < buffer_length; i ++) { + len += (buffer[i] & 0x7f) << (7 * (i - offset)); + if ((buffer[i] & 0x80) == 0) { + i ++; + break; + } + } + *len_bytes = i - offset; + return len; +} + +static int update_property_len_value(mqtt_connection_t *connection, size_t property_len, int property_offset) +{ + uint8_t encoded_lens[4] = {0}, len_bytes = 0; + size_t len = property_len, message_offset = property_offset + property_len; + generate_variable_len(len, &len_bytes, encoded_lens); + int offset = len_bytes - 1; + + connection->message.length += offset; + if (connection->message.length > connection->buffer_length) { + return -1; + } + + if (offset > 0) { + for (int i = 0; i < property_len; i ++) { + connection->buffer[message_offset + offset] = connection->buffer[message_offset]; + message_offset --; + } + } + + for (int i = 0; i < len_bytes; i ++) { + connection->buffer[property_offset ++] = encoded_lens[i]; + } + return offset; +} + +static int append_property(mqtt_connection_t *connection, uint8_t property_type, uint8_t len_occupy, const char *data, size_t data_len) +{ + if ((connection->message.length + len_occupy + (data ? data_len : 0) + (property_type ? 1 : 0)) > connection->buffer_length) { + return -1; + } + + size_t origin_message_len = connection->message.length; + if (property_type) { + connection->buffer[connection->message.length ++] = property_type; + } + + if (len_occupy == 0) { + uint8_t encoded_lens[4] = {0}, len_bytes = 0; + generate_variable_len(data_len, &len_bytes, encoded_lens); + for (int j = 0; j < len_bytes; j ++) { + connection->buffer[connection->message.length ++] = encoded_lens[j]; + } + } else { + for (int i = 1; i <= len_occupy; i ++) { + connection->buffer[connection->message.length ++] = (data_len >> (8 * (len_occupy - i))) & 0xff; + } + } + + if (data) { + memcpy(connection->buffer + connection->message.length, data, data_len); + connection->message.length += data_len; + } + + return connection->message.length - origin_message_len; +} + +static uint16_t append_message_id(mqtt_connection_t *connection, uint16_t message_id) +{ + // If message_id is zero then we should assign one, otherwise + // we'll use the one supplied by the caller + while (message_id == 0) { +#if MQTT_MSG_ID_INCREMENTAL + message_id = ++ connection->last_message_id; +#else + message_id = platform_random(65535); +#endif + } + + if (connection->message.length + 2 > connection->buffer_length) { + return 0; + } + + MQTT5_CONVERT_TWO_BYTE(connection->buffer[connection->message.length ++], message_id) + + return message_id; +} + +static int init_message(mqtt_connection_t *connection) +{ + connection->message.length = MQTT5_MAX_FIXED_HEADER_SIZE; + return MQTT5_MAX_FIXED_HEADER_SIZE; +} + +static mqtt_message_t *fail_message(mqtt_connection_t *connection) +{ + connection->message.data = connection->buffer; + connection->message.length = 0; + return &connection->message; +} + +static mqtt_message_t *fini_message(mqtt_connection_t *connection, int type, int dup, int qos, int retain) +{ + int message_length = connection->message.length - MQTT5_MAX_FIXED_HEADER_SIZE; + int total_length = message_length; + uint8_t encoded_lens[4] = {0}, len_bytes = 0; + // Check if we have fragmented message and update total_len + if (connection->message.fragmented_msg_total_length) { + total_length = connection->message.fragmented_msg_total_length - MQTT5_MAX_FIXED_HEADER_SIZE; + } + + // Encode MQTT message length + generate_variable_len(total_length, &len_bytes, encoded_lens); + + // Sanity check for MQTT header + if (len_bytes + 1 > MQTT5_MAX_FIXED_HEADER_SIZE) { + return fail_message(connection); + } + + // Save the header bytes + connection->message.length = message_length + len_bytes + 1; // msg len + encoded_size len + type (1 byte) + int offs = MQTT5_MAX_FIXED_HEADER_SIZE - 1 - len_bytes; + connection->message.data = connection->buffer + offs; + connection->message.fragmented_msg_data_offset -= offs; + // type byte + connection->buffer[offs ++] = ((type & 0x0f) << 4) | ((dup & 1) << 3) | ((qos & 3) << 1) | (retain & 1); + // length bytes + for (int j = 0; j < len_bytes; j ++) { + connection->buffer[offs ++] = encoded_lens[j]; + } + + return &connection->message; +} + +static esp_err_t mqtt5_msg_set_user_property(mqtt5_user_property_handle_t *user_property, char *key, size_t key_len, char *value, size_t value_len) +{ + if (!*user_property) { + *user_property = calloc(1, sizeof(struct mqtt5_user_property_list_t)); + ESP_MEM_CHECK(TAG, *user_property, return ESP_FAIL); + STAILQ_INIT(*user_property); + } + + mqtt5_user_property_item_t user_property_item = calloc(1, sizeof(mqtt5_user_property_t)); + ESP_MEM_CHECK(TAG, user_property_item, return ESP_FAIL;); + user_property_item->key = calloc(1, key_len + 1); + ESP_MEM_CHECK(TAG, user_property_item->key, { + free(user_property_item); + return ESP_FAIL; + }); + memcpy(user_property_item->key, key, key_len); + user_property_item->key[key_len] = '\0'; + + user_property_item->value = calloc(1, value_len + 1); + ESP_MEM_CHECK(TAG, user_property_item->value, { + free(user_property_item->key); + free(user_property_item); + return ESP_FAIL; + }); + memcpy(user_property_item->value, value, value_len); + user_property_item->value[value_len] = '\0'; + + STAILQ_INSERT_TAIL(*user_property, user_property_item, next); + return ESP_OK; +} + +static mqtt5_user_property_handle_t mqtt5_msg_get_user_property(uint8_t *buffer, size_t buffer_length) +{ + mqtt5_user_property_handle_t user_porperty = NULL; + uint8_t *property = buffer; + uint16_t property_offset = 0, len = 0; + while (property_offset < buffer_length) { + uint8_t property_id = property[property_offset ++]; + switch (property_id) { + case MQTT5_PROPERTY_REASON_STRING: //only print now + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_REASON_STRING %.*s", len, &property[property_offset]); + property_offset += len; + continue; + case MQTT5_PROPERTY_USER_PROPERTY: { + uint8_t *key = NULL, *value = NULL; + size_t key_len = 0, value_len = 0; + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + key = &property[property_offset]; + key_len = len; + ESP_LOGD(TAG, "MQTT5_PROPERTY_USER_PROPERTY key: %.*s", key_len, (char *)key); + property_offset += len; + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + value = &property[property_offset]; + value_len = len; + ESP_LOGD(TAG, "MQTT5_PROPERTY_USER_PROPERTY value: %.*s", value_len, (char *)value); + property_offset += len; + if (mqtt5_msg_set_user_property(&user_porperty, (char *)key, key_len, (char *)value, value_len) != ESP_OK) { + ESP_LOGE(TAG, "mqtt5_msg_set_user_property fail"); + goto err; + } + continue; + } + default: + ESP_LOGW(TAG, "Unknow property id 0x%02x", property_id); + goto err; + } + } + return user_porperty; +err: + esp_mqtt5_client_delete_user_property(user_porperty); + return NULL; +} + +uint16_t mqtt5_get_id(uint8_t *buffer, size_t length) +{ + int topiclen = 0; + uint8_t len_bytes = 0; + size_t offset = 1; + size_t totlen = get_variable_len(buffer, offset, length, &len_bytes); + offset += len_bytes; + totlen += offset; + + if (offset + 2 > length) { + return 0; + } + + switch (mqtt5_get_type(buffer)) { + case MQTT_MSG_TYPE_PUBLISH: { + MQTT5_CONVERT_ONE_BYTE_TO_TWO(topiclen, buffer[offset++], buffer[offset++]) + offset += topiclen; + if (offset + 2 > length) { + return 0; + } + if (mqtt_get_qos(buffer) == 0) { + return 0; + } + return (buffer[offset] << 8) | buffer[offset + 1]; + } + case MQTT_MSG_TYPE_PUBACK: + case MQTT_MSG_TYPE_PUBREC: + case MQTT_MSG_TYPE_PUBREL: + case MQTT_MSG_TYPE_PUBCOMP: + case MQTT_MSG_TYPE_SUBACK: + case MQTT_MSG_TYPE_UNSUBACK: + case MQTT_MSG_TYPE_SUBSCRIBE: + case MQTT_MSG_TYPE_UNSUBSCRIBE: { + return (buffer[offset] << 8) | buffer[offset + 1]; + } + default: + return 0; + } +} + +char *mqtt5_get_publish_property_payload(uint8_t *buffer, size_t buffer_length, char **msg_topic, size_t *msg_topic_len, esp_mqtt5_publish_resp_property_t *resp_property, uint16_t *property_len, size_t *payload_len, mqtt5_user_property_handle_t *user_property) +{ + *user_property = NULL; + uint8_t len_bytes = 0; + size_t offset = 1; + size_t totlen = get_variable_len(buffer, offset, buffer_length, &len_bytes); + offset += len_bytes; + totlen += offset; + + size_t topic_len = buffer[offset ++] << 8; + topic_len |= buffer[offset ++] & 0xff; + *msg_topic = (char *)(buffer + offset); + *msg_topic_len = topic_len; + offset += topic_len; + + if (offset >= buffer_length) { + return NULL; + } + + if (mqtt5_get_qos(buffer) > 0) { + if (offset + 2 >= buffer_length) { + return NULL; + } + offset += 2; // skip the message id + } + + *property_len = get_variable_len(buffer, offset, buffer_length, &len_bytes); + offset += len_bytes; + + uint16_t len = 0, property_offset = 0; + uint8_t *property = (buffer + offset); + while (property_offset < *property_len) { + uint8_t property_id = property[property_offset ++]; + switch (property_id) { + case MQTT5_PROPERTY_PAYLOAD_FORMAT_INDICATOR: + resp_property->payload_format_indicator = property[property_offset ++]; + ESP_LOGD(TAG, "MQTT5_PROPERTY_PAYLOAD_FORMAT_INDICATOR %d", resp_property->payload_format_indicator); + continue; + case MQTT5_PROPERTY_MESSAGE_EXPIRY_INTERVAL: + MQTT5_CONVERT_ONE_BYTE_TO_FOUR(resp_property->message_expiry_interval, property[property_offset ++], property[property_offset ++], property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_MESSAGE_EXPIRY_INTERVAL %d", resp_property->message_expiry_interval); + continue; + case MQTT5_PROPERTY_TOPIC_ALIAS: + MQTT5_CONVERT_ONE_BYTE_TO_TWO(resp_property->topic_alias, property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_TOPIC_ALIAS %d", resp_property->topic_alias); + continue; + case MQTT5_PROPERTY_RESPONSE_TOPIC: + MQTT5_CONVERT_ONE_BYTE_TO_TWO(resp_property->response_topic_len, property[property_offset ++], property[property_offset ++]) + resp_property->response_topic = (char *)(property + property_offset); + property_offset += resp_property->response_topic_len; + ESP_LOGD(TAG, "MQTT5_PROPERTY_RESPONSE_TOPIC %.*s", resp_property->response_topic_len, resp_property->response_topic); + continue; + case MQTT5_PROPERTY_CORRELATION_DATA: + MQTT5_CONVERT_ONE_BYTE_TO_TWO(resp_property->correlation_data_len, property[property_offset ++], property[property_offset ++]) + resp_property->correlation_data = (char *)(property + property_offset); + property_offset += resp_property->correlation_data_len; + ESP_LOGD(TAG, "MQTT5_PROPERTY_CORRELATION_DATA length %d", resp_property->correlation_data_len); + continue; + case MQTT5_PROPERTY_SUBSCRIBE_IDENTIFIER: + resp_property->subscribe_id = get_variable_len(property, property_offset, buffer_length, &len_bytes); + property_offset += len_bytes; + ESP_LOGD(TAG, "MQTT5_PROPERTY_SUBSCRIBE_IDENTIFIER %d", resp_property->subscribe_id); + continue; + case MQTT5_PROPERTY_CONTENT_TYPE: + MQTT5_CONVERT_ONE_BYTE_TO_TWO(resp_property->content_type_len, property[property_offset ++], property[property_offset ++]) + resp_property->content_type = (char *)(property + property_offset); + property_offset += resp_property->content_type_len; + ESP_LOGD(TAG, "MQTT5_PROPERTY_CONTENT_TYPE %.*s", resp_property->content_type_len, resp_property->content_type); + continue; + case MQTT5_PROPERTY_USER_PROPERTY: { + uint8_t *key = NULL, *value = NULL; + size_t key_len = 0, value_len = 0; + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + key = &property[property_offset]; + key_len = len; + ESP_LOGD(TAG, "MQTT5_PROPERTY_USER_PROPERTY key: %.*s", key_len, (char *)key); + property_offset += len; + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + value = &property[property_offset]; + value_len = len; + ESP_LOGD(TAG, "MQTT5_PROPERTY_USER_PROPERTY value: %.*s", value_len, (char *)value); + property_offset += len; + if (mqtt5_msg_set_user_property(user_property, (char *)key, key_len, (char *)value, value_len) != ESP_OK) { + esp_mqtt5_client_delete_user_property(*user_property); + *user_property = NULL; + ESP_LOGE(TAG, "mqtt5_msg_set_user_property fail"); + return NULL; + } + continue; + } + case MQTT5_PROPERTY_REASON_STRING: //only print now + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_REASON_STRING %.*s", len, &property[property_offset]); + property_offset += len; + continue; + default: + ESP_LOGW(TAG, "Unknow publish property id 0x%02x", property_id); + return NULL; + } + } + + offset += property_offset; + if (totlen <= buffer_length) { + *payload_len = totlen - offset; + } else { + *payload_len = buffer_length - offset; + } + return (char *)(buffer + offset); +} + +char *mqtt5_get_suback_data(uint8_t *buffer, size_t *length, mqtt5_user_property_handle_t *user_property) +{ + uint8_t len_bytes = 0; + size_t offset = 1; + size_t totlen = get_variable_len(buffer, offset, *length, &len_bytes); + offset += len_bytes; + totlen += offset; + + if (totlen > *length) { + goto err; + } + offset += 2; // skip the message id + if (offset < totlen) { + size_t property_len = get_variable_len(buffer, offset, totlen, &len_bytes); + offset += len_bytes; + *user_property = mqtt5_msg_get_user_property(buffer + offset, property_len); + offset += property_len; + if (offset < totlen) { + *length = totlen - offset; + return (char *)(buffer + offset); + } + } +err: + *user_property = NULL; + *length = 0; + return NULL; +} + +char *mqtt5_get_puback_data(uint8_t *buffer, size_t *length, mqtt5_user_property_handle_t *user_property) +{ + uint8_t len_bytes = 0; + size_t offset = 1; + size_t totlen = get_variable_len(buffer, offset, *length, &len_bytes); + offset += len_bytes; + totlen += offset; + + offset += 2; // skip the message id + if (offset < totlen) { + *length = 1; + char *data = (char *)(buffer + offset); + offset ++; + if (offset < totlen) { + size_t property_len = get_variable_len(buffer, offset, totlen, &len_bytes); + offset += len_bytes; + *user_property = mqtt5_msg_get_user_property(buffer + offset, property_len); + } + return data; + } else { + *length = 0; + return NULL; + } +} + +mqtt_message_t *mqtt5_msg_connect(mqtt_connection_t *connection, mqtt_connect_info_t *info, esp_mqtt5_connection_property_storage_t *property, esp_mqtt5_connection_will_property_storage_t *will_property) +{ + init_message(connection); + connection->buffer[connection->message.length ++] = 0; // Variable header length MSB + /* Defaults to protocol version 5 values */ + connection->buffer[connection->message.length ++] = 4; // Variable header length LSB + memcpy(&connection->buffer[connection->message.length], "MQTT", 4); // Protocol name + connection->message.length += 4; + connection->buffer[connection->message.length ++] = 5; // Protocol version + + int flags_offset = connection->message.length; + connection->buffer[connection->message.length ++] = 0; // Flags + MQTT5_CONVERT_TWO_BYTE(connection->buffer[connection->message.length ++], info->keepalive) // Keep-alive + + if (info->clean_session) { + connection->buffer[flags_offset] |= MQTT5_CONNECT_FLAG_CLEAN_SESSION; + } + + //Add properties + int properties_offset = connection->message.length; + connection->message.length ++; + if (property->session_expiry_interval) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_SESSION_EXPIRY_INTERVAL, 4, NULL, property->session_expiry_interval), fail_message(connection)); + } + if (property->maximum_packet_size) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_MAXIMUM_PACKET_SIZE, 4, NULL, property->maximum_packet_size), fail_message(connection)); + } + if (property->receive_maximum) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_RECEIVE_MAXIMUM, 2, NULL, property->receive_maximum), fail_message(connection)); + } + if (property->topic_alias_maximum) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_TOPIC_ALIAS_MAXIMIM, 2, NULL, property->topic_alias_maximum), fail_message(connection)); + } + if (property->request_resp_info) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_REQUEST_RESP_INFO, 1, NULL, 1), fail_message(connection)); + } + if (property->request_problem_info) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_REQUEST_PROBLEM_INFO, 1, NULL, 1), fail_message(connection)); + } + if (property->user_property) { + mqtt5_user_property_item_t item; + STAILQ_FOREACH(item, property->user_property, next) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_USER_PROPERTY, 2, item->key, strlen(item->key)), fail_message(connection)); + APPEND_CHECK(append_property(connection, 0, 2, item->value, strlen(item->value)), fail_message(connection)); + } + } + APPEND_CHECK(update_property_len_value(connection, connection->message.length - properties_offset - 1, properties_offset), fail_message(connection)); + + if (info->client_id != NULL && info->client_id[0] != '\0') { + APPEND_CHECK(append_property(connection, 0, 2, info->client_id, strlen(info->client_id)), fail_message(connection)); + } else { + APPEND_CHECK(append_property(connection, 0, 2, NULL, 0), fail_message(connection)); + } + + //Add will properties + if (info->will_topic != NULL && info->will_topic[0] != '\0') { + properties_offset = connection->message.length; + connection->message.length ++; + if (will_property->will_delay_interval) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_WILL_DELAY_INTERVAL, 4, NULL, will_property->will_delay_interval), fail_message(connection)); + } + if (will_property->payload_format_indicator) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_PAYLOAD_FORMAT_INDICATOR, 1, NULL, 1), fail_message(connection)); + } + if (will_property->message_expiry_interval) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_MESSAGE_EXPIRY_INTERVAL, 4, NULL, will_property->message_expiry_interval), fail_message(connection)); + } + if (will_property->content_type) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_CONTENT_TYPE, 2, will_property->content_type, strlen(will_property->content_type)), fail_message(connection)); + } + if (will_property->response_topic) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_RESPONSE_TOPIC, 2, will_property->response_topic, strlen(will_property->response_topic)), fail_message(connection)); + } + if (will_property->correlation_data && will_property->correlation_data_len) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_CORRELATION_DATA, 2, will_property->correlation_data, will_property->correlation_data_len), fail_message(connection)); + } + if (will_property->user_property) { + mqtt5_user_property_item_t item; + STAILQ_FOREACH(item, will_property->user_property, next) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_USER_PROPERTY, 2, item->key, strlen(item->key)), fail_message(connection)); + APPEND_CHECK(append_property(connection, 0, 2, item->value, strlen(item->value)), fail_message(connection)); + } + } + APPEND_CHECK(update_property_len_value(connection, connection->message.length - properties_offset - 1, properties_offset), fail_message(connection)); + + APPEND_CHECK(append_property(connection, 0, 2, info->will_topic, strlen(info->will_topic)), fail_message(connection)); + APPEND_CHECK(append_property(connection, 0, 2, info->will_message, info->will_length), fail_message(connection)); + + connection->buffer[flags_offset] |= MQTT5_CONNECT_FLAG_WILL; + if (info->will_retain) { + connection->buffer[flags_offset] |= MQTT5_CONNECT_FLAG_WILL_RETAIN; + } + connection->buffer[flags_offset] |= (info->will_qos & 3) << 3; + } + + if (info->username != NULL && info->username[0] != '\0') { + APPEND_CHECK(append_property(connection, 0, 2, info->username, strlen(info->username)), fail_message(connection)); + connection->buffer[flags_offset] |= MQTT5_CONNECT_FLAG_USERNAME; + } + + if (info->password != NULL && info->password[0] != '\0') { + if (info->username == NULL || info->username[0] == '\0') { + /* In case if password is set without username, we need to set a zero length username. + * (otherwise we violate: MQTT-3.1.2-22: If the User Name Flag is set to 0 then the Password Flag MUST be set to 0.) + */ + APPEND_CHECK(append_property(connection, 0, 2, NULL, 0), fail_message(connection)); + connection->buffer[flags_offset] |= MQTT5_CONNECT_FLAG_USERNAME; + } + APPEND_CHECK(append_property(connection, 0, 2, info->password, strlen(info->password)), fail_message(connection)); + connection->buffer[flags_offset] |= MQTT5_CONNECT_FLAG_PASSWORD; + } + + return fini_message(connection, MQTT_MSG_TYPE_CONNECT, 0, 0, 0); +} + +esp_err_t mqtt5_msg_parse_connack_property(uint8_t *buffer, size_t buffer_len, mqtt_connect_info_t *connection_info, esp_mqtt5_connection_property_storage_t *connection_property, esp_mqtt5_connection_server_resp_property_t *resp_property, int *reason_code, uint8_t *ack_flag, mqtt5_user_property_handle_t *user_property) +{ + *reason_code = 0; + *user_property = NULL; + uint8_t len_bytes = 0; + size_t offset = 1; + size_t totlen = get_variable_len(buffer, offset, buffer_len, &len_bytes); + offset += len_bytes; + totlen += offset; + + if (totlen > buffer_len) { + ESP_LOGE(TAG, "Total length %d is over read len %d", totlen, buffer_len); + return ESP_FAIL; + } + + *ack_flag = buffer[offset ++]; //acknowledge flags + *reason_code = buffer[offset ++]; //reason code + size_t property_len = get_variable_len(buffer, offset, buffer_len, &len_bytes); + offset += len_bytes; + uint16_t property_offset = 0, len = 0; + uint8_t *property = (buffer + offset); + while (property_offset < property_len) { + uint8_t property_id = property[property_offset ++]; + switch (property_id) { + case MQTT5_PROPERTY_SESSION_EXPIRY_INTERVAL: + MQTT5_CONVERT_ONE_BYTE_TO_FOUR(connection_property->session_expiry_interval, property[property_offset ++], property[property_offset ++], property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_SESSION_EXPIRY_INTERVAL %d", connection_property->session_expiry_interval); + continue; + case MQTT5_PROPERTY_RECEIVE_MAXIMUM: + MQTT5_CONVERT_ONE_BYTE_TO_TWO(resp_property->receive_maximum, property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_RECEIVE_MAXIMUM %d", resp_property->receive_maximum); + continue; + case MQTT5_PROPERTY_MAXIMUM_QOS: + resp_property->max_qos = property[property_offset ++]; + ESP_LOGD(TAG, "MQTT5_PROPERTY_MAXIMUM_QOS %d", resp_property->max_qos); + continue; + case MQTT5_PROPERTY_RETAIN_AVAILABLE: + resp_property->retain_available = property[property_offset ++]; + ESP_LOGD(TAG, "MQTT5_PROPERTY_RETAIN_AVAILABLE %d", resp_property->retain_available); + continue; + case MQTT5_PROPERTY_MAXIMUM_PACKET_SIZE: + MQTT5_CONVERT_ONE_BYTE_TO_FOUR(resp_property->maximum_packet_size, property[property_offset ++], property[property_offset ++], property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_MAXIMUM_PACKET_SIZE %d", resp_property->maximum_packet_size); + continue; + case MQTT5_PROPERTY_ASSIGNED_CLIENT_IDENTIFIER: + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + if (connection_info->client_id) { + free(connection_info->client_id); + } + connection_info->client_id = calloc(1, len + 1); + if (!connection_info->client_id) { + ESP_LOGE(TAG, "Failed to calloc %d data", len); + return ESP_FAIL; + } + memcpy(connection_info->client_id, &property[property_offset], len); + connection_info->client_id[len] = '\0'; + property_offset += len; + ESP_LOGD(TAG, "MQTT5_PROPERTY_ASSIGNED_CLIENT_IDENTIFIER %s", connection_info->client_id); + continue; + case MQTT5_PROPERTY_TOPIC_ALIAS_MAXIMIM: + MQTT5_CONVERT_ONE_BYTE_TO_TWO(resp_property->topic_alias_maximum, property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_TOPIC_ALIAS_MAXIMIM %d", resp_property->topic_alias_maximum); + continue; + case MQTT5_PROPERTY_REASON_STRING: //only print now + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_REASON_STRING %.*s", len, &property[property_offset]); + property_offset += len; + continue; + case MQTT5_PROPERTY_USER_PROPERTY: { + uint8_t *key = NULL, *value = NULL; + size_t key_len = 0, value_len = 0; + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + key = &property[property_offset]; + key_len = len; + ESP_LOGD(TAG, "MQTT5_PROPERTY_USER_PROPERTY key: %.*s", key_len, (char *)key); + property_offset += len; + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + value = &property[property_offset]; + value_len = len; + ESP_LOGD(TAG, "MQTT5_PROPERTY_USER_PROPERTY value: %.*s", value_len, (char *)value); + property_offset += len; + if (mqtt5_msg_set_user_property(user_property, (char *)key, key_len, (char *)value, value_len) != ESP_OK) { + esp_mqtt5_client_delete_user_property(*user_property); + *user_property = NULL; + ESP_LOGE(TAG, "mqtt5_msg_set_user_property fail"); + return ESP_FAIL; + } + continue; + } + case MQTT5_PROPERTY_WILDCARD_SUBSCR_AVAILABLE: + resp_property->wildcard_subscribe_available = property[property_offset++]; + ESP_LOGD(TAG, "MQTT5_PROPERTY_WILDCARD_SUBSCR_AVAILABLE %d", resp_property->wildcard_subscribe_available); + continue; + case MQTT5_PROPERTY_SUBSCR_IDENTIFIER_AVAILABLE: + resp_property->subscribe_identifiers_available = property[property_offset++]; + ESP_LOGD(TAG, "MQTT5_PROPERTY_SUBSCR_IDENTIFIER_AVAILABLE %d", resp_property->subscribe_identifiers_available); + continue; + case MQTT5_PROPERTY_SHARED_SUBSCR_AVAILABLE: + resp_property->shared_subscribe_available = property[property_offset++]; + ESP_LOGD(TAG, "MQTT5_PROPERTY_SHARED_SUBSCR_AVAILABLE %d", resp_property->shared_subscribe_available); + continue; + case MQTT5_PROPERTY_SERVER_KEEP_ALIVE: + MQTT5_CONVERT_ONE_BYTE_TO_TWO(connection_info->keepalive, property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_SERVER_KEEP_ALIVE %d", connection_info->keepalive); + continue; + case MQTT5_PROPERTY_RESP_INFO: + if (resp_property->response_info) { + free(resp_property->response_info); + } + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + resp_property->response_info = calloc(1, len + 1); + if (!resp_property->response_info) { + ESP_LOGE(TAG, "Failed to calloc %d data", len); + return ESP_FAIL; + } + memcpy(resp_property->response_info, &property[property_offset], len); + resp_property->response_info[len] = '\0'; + property_offset += len; + ESP_LOGD(TAG, "MQTT5_PROPERTY_RESP_INFO %s", resp_property->response_info); + continue; + case MQTT5_PROPERTY_SERVER_REFERENCE: //only print now + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_SERVER_REFERENCE %.*s", len, &property[property_offset]); + property_offset += len; + continue; + case MQTT5_PROPERTY_AUTHENTICATION_METHOD: //only print now + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_AUTHENTICATION_METHOD %.*s", len, &property[property_offset]); + property_offset += len; + continue; + case MQTT5_PROPERTY_AUTHENTICATION_DATA: //only print now + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_AUTHENTICATION_DATA length %d", len); + property_offset += len; + continue; + default: + ESP_LOGW(TAG, "Unknow connack property id 0x%02x", property_id); + return ESP_FAIL; + } + } + return ESP_OK; +} + +mqtt_message_t *mqtt5_msg_publish(mqtt_connection_t *connection, const char *topic, const char *data, int data_length, int qos, int retain, uint16_t *message_id, const esp_mqtt5_publish_property_config_t *property, const char *resp_info) +{ + init_message(connection); + + if (topic == NULL || topic[0] == '\0') { + return fail_message(connection); + } + + APPEND_CHECK(append_property(connection, 0, 2, topic, strlen(topic)), fail_message(connection)); + + if (data == NULL && data_length > 0) { + return fail_message(connection); + } + + if (qos > 0) { + if ((*message_id = append_message_id(connection, 0)) == 0) { + return fail_message(connection); + } + } else { + *message_id = 0; + } + + int properties_offset = connection->message.length; + connection->message.length ++; + + if (property) { + if (property->payload_format_indicator) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_PAYLOAD_FORMAT_INDICATOR, 1, NULL, 1), fail_message(connection)); + } + if (property->message_expiry_interval) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_MESSAGE_EXPIRY_INTERVAL, 4, NULL, property->message_expiry_interval), fail_message(connection)); + } + if (property->topic_alias) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_TOPIC_ALIAS, 2, NULL, property->topic_alias), fail_message(connection)); + } + if (property->response_topic) { + if (resp_info && strlen(resp_info)) { + uint16_t response_topic_size = strlen(property->response_topic) + strlen(resp_info) + 1; + char *response_topic = calloc(1, response_topic_size); + if (!response_topic) { + ESP_LOGE(TAG, "Failed to calloc %d memory", response_topic_size); + fail_message(connection); + } + snprintf(response_topic, response_topic_size, "%s/%s", property->response_topic, resp_info); + if (append_property(connection, MQTT5_PROPERTY_RESPONSE_TOPIC, 2, response_topic, response_topic_size) == -1) { + ESP_LOGE(TAG,"%s(%d) fail",__FUNCTION__, __LINE__); + free(response_topic); + return fail_message(connection); + } + free(response_topic); + } else { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_RESPONSE_TOPIC, 2, property->response_topic, strlen(property->response_topic)), fail_message(connection)); + } + } + if (property->correlation_data && property->correlation_data_len) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_CORRELATION_DATA, 2, property->correlation_data, property->correlation_data_len), fail_message(connection)); + } + if (property->user_property) { + mqtt5_user_property_item_t item; + STAILQ_FOREACH(item, property->user_property, next) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_USER_PROPERTY, 2, item->key, strlen(item->key)), fail_message(connection)); + APPEND_CHECK(append_property(connection, 0, 2, item->value, strlen(item->value)), fail_message(connection)); + } + } + if (property->content_type) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_CONTENT_TYPE, 2, property->content_type, strlen(property->content_type)), fail_message(connection)); + } + } + APPEND_CHECK(update_property_len_value(connection, connection->message.length - properties_offset - 1, properties_offset), fail_message(connection)); + + if (connection->message.length + data_length > connection->buffer_length) { + // Not enough size in buffer -> fragment this message + connection->message.fragmented_msg_data_offset = connection->message.length; + memcpy(connection->buffer + connection->message.length, data, connection->buffer_length - connection->message.length); + connection->message.length = connection->buffer_length; + connection->message.fragmented_msg_total_length = data_length + connection->message.fragmented_msg_data_offset; + } else { + if (data != NULL) { + memcpy(connection->buffer + connection->message.length, data, data_length); + connection->message.length += data_length; + } + connection->message.fragmented_msg_total_length = 0; + } + return fini_message(connection, MQTT_MSG_TYPE_PUBLISH, 0, qos, retain); +} + +int mqtt5_msg_get_reason_code(uint8_t *buffer, size_t length) +{ + uint8_t len_bytes = 0; + size_t offset = 1; + size_t totlen = get_variable_len(buffer, offset, length, &len_bytes); + offset += len_bytes; + totlen += offset; + + switch (mqtt5_get_type(buffer)) { + case MQTT_MSG_TYPE_PUBACK: + case MQTT_MSG_TYPE_PUBREC: + case MQTT_MSG_TYPE_PUBREL: + case MQTT_MSG_TYPE_PUBCOMP: + offset += 2; //skip the message id + if (offset >= length) { + return -1; + } + return buffer[offset]; + case MQTT_MSG_TYPE_SUBACK: + case MQTT_MSG_TYPE_UNSUBACK: { + offset += 2; //skip the message id + if (offset >= length) { + return -1; + } + size_t property_len = get_variable_len(buffer, offset, length, &len_bytes); + offset = offset + len_bytes + property_len; + if (offset >= length) { + return -1; + } else { + return buffer[offset]; + } + } + case MQTT_MSG_TYPE_DISCONNECT: + if (offset >= length) { + return -1; + } else { + return buffer[offset]; + } + default: + break; + } + return -1; +} + +mqtt_message_t *mqtt5_msg_subscribe(mqtt_connection_t *connection, const char *topic, int qos, uint16_t *message_id, const esp_mqtt5_subscribe_property_config_t *property) +{ + init_message(connection); + + if (topic == NULL || topic[0] == '\0') { + return fail_message(connection); + } + + if ((*message_id = append_message_id(connection, 0)) == 0) { + return fail_message(connection); + } + + int properties_offset = connection->message.length; + connection->message.length ++; + + if (property) { + if (property->subscribe_id) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_SUBSCRIBE_IDENTIFIER, 0, NULL, property->subscribe_id), fail_message(connection)); + } + if (property->user_property) { + mqtt5_user_property_item_t item; + STAILQ_FOREACH(item, property->user_property, next) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_USER_PROPERTY, 2, item->key, strlen(item->key)), fail_message(connection)); + APPEND_CHECK(append_property(connection, 0, 2, item->value, strlen(item->value)), fail_message(connection)); + } + } + } + APPEND_CHECK(update_property_len_value(connection, connection->message.length - properties_offset - 1, properties_offset), fail_message(connection)); + if (property && property->is_share_subscribe) { + uint16_t shared_topic_size = strlen(topic) + strlen(MQTT5_SHARED_SUB) + strlen(property->share_name); + char *shared_topic = calloc(1, shared_topic_size); + if (!shared_topic) { + ESP_LOGE(TAG, "Failed to calloc %d memory", shared_topic_size); + fail_message(connection); + } + snprintf(shared_topic, shared_topic_size, MQTT5_SHARED_SUB, property->share_name, topic); + if (append_property(connection, 0, 2, shared_topic, strlen(shared_topic)) == -1) { + ESP_LOGE(TAG,"%s(%d) fail",__FUNCTION__, __LINE__); + free(shared_topic); + return fail_message(connection); + } + free(shared_topic); + } else { + APPEND_CHECK(append_property(connection, 0, 2, topic, strlen(topic)), fail_message(connection)); + } + + if (connection->message.length + 1 > connection->buffer_length) { + return fail_message(connection); + } + connection->buffer[connection->message.length] = 0; + if (property) { + if (property->retain_handle > 0 && property->retain_handle < 3) { + connection->buffer[connection->message.length] |= (property->retain_handle & 3) << 4; + } + if (property->no_local_flag) { + connection->buffer[connection->message.length] |= (property->no_local_flag << 2); + } + if (property->retain_as_published_flag) { + connection->buffer[connection->message.length] |= (property->retain_as_published_flag << 3); + } + } + connection->buffer[connection->message.length] |= (qos & 3); + connection->message.length ++; + return fini_message(connection, MQTT_MSG_TYPE_SUBSCRIBE, 0, 1, 0); +} + +mqtt_message_t *mqtt5_msg_disconnect(mqtt_connection_t *connection, esp_mqtt5_disconnect_property_config_t *disconnect_property_info) +{ + init_message(connection); + int reason_offset = connection->message.length; + connection->buffer[connection->message.length ++] = 0; + int properties_offset = connection->message.length; + connection->message.length ++; + if (disconnect_property_info) { + if (disconnect_property_info->session_expiry_interval) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_SESSION_EXPIRY_INTERVAL, 4, NULL, disconnect_property_info->session_expiry_interval), fail_message(connection)); + } + if (disconnect_property_info->user_property) { + mqtt5_user_property_item_t item; + STAILQ_FOREACH(item, disconnect_property_info->user_property, next) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_USER_PROPERTY, 2, item->key, strlen(item->key)), fail_message(connection)); + APPEND_CHECK(append_property(connection, 0, 2, item->value, strlen(item->value)), fail_message(connection)); + } + } + if (disconnect_property_info->disconnect_reason) { + connection->buffer[reason_offset] = disconnect_property_info->disconnect_reason; + } + } + APPEND_CHECK(update_property_len_value(connection, connection->message.length - properties_offset - 1, properties_offset), fail_message(connection)); + return fini_message(connection, MQTT_MSG_TYPE_DISCONNECT, 0, 0, 0); +} + +mqtt_message_t *mqtt5_msg_unsubscribe(mqtt_connection_t *connection, const char *topic, uint16_t *message_id, const esp_mqtt5_unsubscribe_property_config_t *property) +{ + init_message(connection); + + if (topic == NULL || topic[0] == '\0') { + return fail_message(connection); + } + + if ((*message_id = append_message_id(connection, 0)) == 0) { + return fail_message(connection); + } + + int properties_offset = connection->message.length; + connection->message.length ++; + if (property) { + if (property->user_property) { + mqtt5_user_property_item_t item; + STAILQ_FOREACH(item, property->user_property, next) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_USER_PROPERTY, 2, item->key, strlen(item->key)), fail_message(connection)); + APPEND_CHECK(append_property(connection, 0, 2, item->value, strlen(item->value)), fail_message(connection)); + } + } + } + + APPEND_CHECK(update_property_len_value(connection, connection->message.length - properties_offset - 1, properties_offset), fail_message(connection)); + if (property && property->is_share_subscribe) { + uint16_t shared_topic_size = strlen(topic) + strlen(MQTT5_SHARED_SUB) + strlen(property->share_name); + char *shared_topic = calloc(1, shared_topic_size); + if (!shared_topic) { + ESP_LOGE(TAG, "Failed to calloc %d memory", shared_topic_size); + fail_message(connection); + } + snprintf(shared_topic, shared_topic_size, MQTT5_SHARED_SUB, property->share_name, topic); + if (append_property(connection, 0, 2, shared_topic, strlen(shared_topic)) == -1) { + ESP_LOGE(TAG,"%s(%d) fail",__FUNCTION__, __LINE__); + free(shared_topic); + return fail_message(connection); + } + free(shared_topic); + } else { + APPEND_CHECK(append_property(connection, 0, 2, topic, strlen(topic)), fail_message(connection)); + } + + return fini_message(connection, MQTT_MSG_TYPE_UNSUBSCRIBE, 0, 1, 0); +} + +mqtt_message_t *mqtt5_msg_puback(mqtt_connection_t *connection, uint16_t message_id) +{ + init_message(connection); + if (append_message_id(connection, message_id) == 0) { + return fail_message(connection); + } + connection->buffer[connection->message.length ++] = 0; // Regard it is success + int properties_offset = connection->message.length; + connection->message.length ++; + APPEND_CHECK(update_property_len_value(connection, connection->message.length - properties_offset - 1, properties_offset), fail_message(connection)); + return fini_message(connection, MQTT_MSG_TYPE_PUBACK, 0, 0, 0); +} + +mqtt_message_t *mqtt5_msg_pubrec(mqtt_connection_t *connection, uint16_t message_id) +{ + init_message(connection); + if (append_message_id(connection, message_id) == 0) { + return fail_message(connection); + } + connection->buffer[connection->message.length ++] = 0; // Regard it is success + int properties_offset = connection->message.length; + connection->message.length ++; + APPEND_CHECK(update_property_len_value(connection, connection->message.length - properties_offset - 1, properties_offset), fail_message(connection)); + return fini_message(connection, MQTT_MSG_TYPE_PUBREC, 0, 0, 0); +} + +mqtt_message_t *mqtt5_msg_pubrel(mqtt_connection_t *connection, uint16_t message_id) +{ + init_message(connection); + if (append_message_id(connection, message_id) == 0) { + return fail_message(connection); + } + connection->buffer[connection->message.length ++] = 0; // Regard it is success + int properties_offset = connection->message.length; + connection->message.length ++; + APPEND_CHECK(update_property_len_value(connection, connection->message.length - properties_offset - 1, properties_offset), fail_message(connection)); + return fini_message(connection, MQTT_MSG_TYPE_PUBREL, 0, 1, 0); +} + +mqtt_message_t *mqtt5_msg_pubcomp(mqtt_connection_t *connection, uint16_t message_id) +{ + init_message(connection); + if (append_message_id(connection, message_id) == 0) { + return fail_message(connection); + } + connection->buffer[connection->message.length ++] = 0; // Regard it is success + int properties_offset = connection->message.length; + connection->message.length ++; + APPEND_CHECK(update_property_len_value(connection, connection->message.length - properties_offset - 1, properties_offset), fail_message(connection)); + return fini_message(connection, MQTT_MSG_TYPE_PUBCOMP, 0, 0, 0); +} diff --git a/mqtt_client.c b/mqtt_client.c index 794bfa8..8fd76af 100644 --- a/mqtt_client.c +++ b/mqtt_client.c @@ -6,6 +6,9 @@ #include "esp_event.h" #include "mqtt_client.h" #include "mqtt_msg.h" +#ifdef MQTT_PROTOCOL_5 +#include "mqtt5_msg.h" +#endif #include "esp_transport.h" #include "esp_transport_tcp.h" #include "esp_transport_ssl.h" @@ -105,6 +108,46 @@ typedef enum { MQTT_STATE_WAIT_RECONNECT, } mqtt_client_state_t; +#ifdef MQTT_PROTOCOL_5 +typedef struct mqtt5_topic_alias { + char *topic; + uint16_t topic_len; + uint16_t topic_alias; + STAILQ_ENTRY(mqtt5_topic_alias) next; +} mqtt5_topic_alias_t; +STAILQ_HEAD(mqtt5_topic_alias_list_t, mqtt5_topic_alias); +typedef struct mqtt5_topic_alias_list_t *mqtt5_topic_alias_handle_t; +typedef struct mqtt5_topic_alias *mqtt5_topic_alias_item_t; + +typedef struct { + esp_mqtt5_connection_property_storage_t connect_property_info; + esp_mqtt5_connection_will_property_storage_t will_property_info; + esp_mqtt5_connection_server_resp_property_t server_resp_property_info; + esp_mqtt5_disconnect_property_config_t disconnect_property_info; + const esp_mqtt5_publish_property_config_t *publish_property_info; + const esp_mqtt5_subscribe_property_config_t *subscribe_property_info; + const esp_mqtt5_unsubscribe_property_config_t *unsubscribe_property_info; + mqtt5_topic_alias_handle_t peer_topic_alias; +} mqtt5_config_storage_t; + +static void esp_mqtt5_flow_control(esp_mqtt_client_handle_t client); +static void esp_mqtt5_parse_pubcomp(esp_mqtt_client_handle_t client); +static void esp_mqtt5_parse_puback(esp_mqtt_client_handle_t client); +static void esp_mqtt5_parse_unsuback(esp_mqtt_client_handle_t client); +static void esp_mqtt5_parse_suback(esp_mqtt_client_handle_t client); +static esp_err_t esp_mqtt5_parse_connack(esp_mqtt_client_handle_t client, int *connect_rsp_code); +static void esp_mqtt5_client_destory(esp_mqtt_client_handle_t client); +static esp_err_t esp_mqtt5_client_publish_check(esp_mqtt_client_handle_t client, int qos, int retain); +static esp_err_t esp_mqtt5_client_subscribe_check(esp_mqtt_client_handle_t client, int qos); +static void esp_mqtt5_print_error_code(esp_mqtt_client_handle_t client, int code); +static esp_err_t esp_mqtt5_create_default_config(esp_mqtt_client_handle_t client); +static esp_err_t esp_mqtt5_get_publish_data(esp_mqtt_client_handle_t client, uint8_t *msg_buf, size_t msg_read_len, char **msg_topic, size_t *msg_topic_len, char **msg_data, size_t *msg_data_len); +static esp_err_t esp_mqtt5_client_update_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle, uint16_t topic_alias, char *topic, size_t topic_len); +static char *esp_mqtt5_client_get_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle, uint16_t topic_alias, size_t *topic_length); +static void esp_mqtt5_client_delete_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle); +static esp_err_t esp_mqtt5_user_property_copy(mqtt5_user_property_handle_t user_property_new, const mqtt5_user_property_handle_t user_property_old); +#endif + struct esp_mqtt_client { esp_transport_list_handle_t transport_list; esp_transport_handle_t transport; @@ -115,6 +158,10 @@ struct esp_mqtt_client { uint64_t refresh_connection_tick; int64_t keepalive_tick; uint64_t reconnect_tick; +#ifdef MQTT_PROTOCOL_5 + mqtt5_config_storage_t *mqtt5_config; + uint16_t send_publish_packet_count; // This is for MQTT v5.0 flow control +#endif int wait_timeout_ms; int auto_reconnect; esp_mqtt_event_t event; @@ -546,6 +593,11 @@ esp_err_t esp_mqtt_set_config(esp_mqtt_client_handle_t client, const esp_mqtt_cl client->connect_info.protocol_ver = MQTT_PROTOCOL_V_3_1_1; #else client->connect_info.protocol_ver = MQTT_PROTOCOL_V_3_1; +#endif + } else if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifndef MQTT_PROTOCOL_5 + ESP_LOGE(TAG, "Please first enable MQTT_PROTOCOL_5 feature in menuconfig"); + goto _mqtt_set_config_failed; #endif } @@ -683,6 +735,9 @@ static void esp_mqtt_destroy_config(esp_mqtt_client_handle_t client) free(client->connect_info.client_id); free(client->connect_info.username); free(client->connect_info.password); +#ifdef MQTT_PROTOCOL_5 + esp_mqtt5_client_destory(client); +#endif memset(&client->connect_info, 0, sizeof(mqtt_connect_info_t)); #ifdef MQTT_SUPPORTED_FEATURE_EVENT_LOOP if (client->config->event_loop_handle) { @@ -746,18 +801,32 @@ static inline esp_err_t esp_mqtt_write(esp_mqtt_client_handle_t client) static esp_err_t esp_mqtt_connect(esp_mqtt_client_handle_t client, int timeout_ms) { - int read_len, connect_rsp_code; + int read_len, connect_rsp_code = 0; client->wait_for_ping_resp = false; - client->mqtt_state.outbound_message = mqtt_msg_connect(&client->mqtt_state.mqtt_connection, + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + client->mqtt_state.outbound_message = mqtt5_msg_connect(&client->mqtt_state.mqtt_connection, + &client->connect_info, &client->mqtt5_config->connect_property_info, &client->mqtt5_config->will_property_info); +#endif + } else { + client->mqtt_state.outbound_message = mqtt_msg_connect(&client->mqtt_state.mqtt_connection, &client->connect_info); + } if (client->mqtt_state.outbound_message->length == 0) { ESP_LOGE(TAG, "Connect message cannot be created"); return ESP_FAIL; } client->mqtt_state.pending_msg_type = mqtt_get_type(client->mqtt_state.outbound_message->data); - client->mqtt_state.pending_msg_id = mqtt_get_id(client->mqtt_state.outbound_message->data, + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + client->mqtt_state.pending_msg_id = mqtt5_get_id(client->mqtt_state.outbound_message->data, client->mqtt_state.outbound_message->length); +#endif + } else { + client->mqtt_state.pending_msg_id = mqtt_get_id(client->mqtt_state.outbound_message->data, + client->mqtt_state.outbound_message->length); + } ESP_LOGD(TAG, "Sending MQTT CONNECT message, type: %d, id: %04X", client->mqtt_state.pending_msg_type, client->mqtt_state.pending_msg_id); @@ -784,28 +853,36 @@ static esp_err_t esp_mqtt_connect(esp_mqtt_client_handle_t client, int timeout_m ESP_LOGE(TAG, "Invalid MSG_TYPE response: %d, read_len: %d", mqtt_get_type(client->mqtt_state.in_buffer), read_len); return ESP_FAIL; } - client->mqtt_state.in_buffer_read_len = 0; - connect_rsp_code = mqtt_get_connect_return_code(client->mqtt_state.in_buffer); - if (connect_rsp_code == MQTT_CONNECTION_ACCEPTED) { - ESP_LOGD(TAG, "Connected"); - return ESP_OK; - } - switch (connect_rsp_code) { - case MQTT_CONNECTION_REFUSE_PROTOCOL: - ESP_LOGW(TAG, "Connection refused, bad protocol"); - break; - case MQTT_CONNECTION_REFUSE_SERVER_UNAVAILABLE: - ESP_LOGW(TAG, "Connection refused, server unavailable"); - break; - case MQTT_CONNECTION_REFUSE_BAD_USERNAME: - ESP_LOGW(TAG, "Connection refused, bad username or password"); - break; - case MQTT_CONNECTION_REFUSE_NOT_AUTHORIZED: - ESP_LOGW(TAG, "Connection refused, not authorized"); - break; - default: - ESP_LOGW(TAG, "Connection refused, Unknow reason"); - break; + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + if (esp_mqtt5_parse_connack(client, &connect_rsp_code) == ESP_OK) { + return ESP_OK; + } +#endif + } else { + client->mqtt_state.in_buffer_read_len = 0; + connect_rsp_code = mqtt_get_connect_return_code(client->mqtt_state.in_buffer); + if (connect_rsp_code == MQTT_CONNECTION_ACCEPTED) { + ESP_LOGD(TAG, "Connected"); + return ESP_OK; + } + switch (connect_rsp_code) { + case MQTT_CONNECTION_REFUSE_PROTOCOL: + ESP_LOGW(TAG, "Connection refused, bad protocol"); + break; + case MQTT_CONNECTION_REFUSE_SERVER_UNAVAILABLE: + ESP_LOGW(TAG, "Connection refused, server unavailable"); + break; + case MQTT_CONNECTION_REFUSE_BAD_USERNAME: + ESP_LOGW(TAG, "Connection refused, bad username or password"); + break; + case MQTT_CONNECTION_REFUSE_NOT_AUTHORIZED: + ESP_LOGW(TAG, "Connection refused, not authorized"); + break; + default: + ESP_LOGW(TAG, "Connection refused, Unknow reason"); + break; + } } /* propagate event with connection refused error */ client->event.event_id = MQTT_EVENT_ERROR; @@ -888,7 +965,11 @@ esp_mqtt_client_handle_t esp_mqtt_client_init(const esp_mqtt_client_config_t *co mqtt_msg_init(&client->mqtt_state.mqtt_connection, client->mqtt_state.out_buffer, client->mqtt_state.out_buffer_length); - +#ifdef MQTT_PROTOCOL_5 + if (esp_mqtt5_create_default_config(client) != ESP_OK) { + goto _mqtt_init_failed; + } +#endif return client; _mqtt_init_failed: esp_mqtt_client_destroy(client); @@ -998,12 +1079,21 @@ static esp_err_t mqtt_write_data(esp_mqtt_client_handle_t client) esp_mqtt_client_dispatch_transport_error(client); return ESP_FAIL; } +#ifdef MQTT_PROTOCOL_5 + esp_mqtt5_flow_control(client); +#endif return ESP_OK; } static esp_err_t esp_mqtt_dispatch_event_with_msgid(esp_mqtt_client_handle_t client) { - client->event.msg_id = mqtt_get_id(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_length); + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + client->event.msg_id = mqtt5_get_id(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_length); +#endif + } else { + client->event.msg_id = mqtt_get_id(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_length); + } return esp_mqtt_dispatch_event(client); } @@ -1011,17 +1101,26 @@ static esp_err_t esp_mqtt_dispatch_event(esp_mqtt_client_handle_t client) { client->event.user_context = client->config->user_context; client->event.client = client; + client->event.protocol_ver = client->connect_info.protocol_ver; + esp_err_t ret = ESP_FAIL; if (client->config->event_handle) { - return client->config->event_handle(&client->event); + ret = client->config->event_handle(&client->event); } else { #ifdef MQTT_SUPPORTED_FEATURE_EVENT_LOOP esp_event_post_to(client->config->event_loop_handle, MQTT_EVENTS, client->event.event_id, &client->event, sizeof(client->event), portMAX_DELAY); - return esp_event_loop_run(client->config->event_loop_handle, 0); + ret = esp_event_loop_run(client->config->event_loop_handle, 0); #else return ESP_FAIL; #endif } + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + esp_mqtt5_client_delete_user_property(client->event.property->user_property); + client->event.property->user_property = NULL; +#endif + } + return ret; } static esp_err_t deliver_publish(esp_mqtt_client_handle_t client) @@ -1033,28 +1132,41 @@ static esp_err_t deliver_publish(esp_mqtt_client_handle_t client) size_t msg_data_offset = 0; char *msg_topic = NULL, *msg_data = NULL; - // get topic - msg_topic = mqtt_get_publish_topic(msg_buf, &msg_topic_len); - if (msg_topic == NULL) { - ESP_LOGE(TAG, "%s: mqtt_get_publish_topic() failed", __func__); - return ESP_FAIL; - } - ESP_LOGD(TAG, "%s: msg_topic_len=%zu", __func__, msg_topic_len); + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + if (esp_mqtt5_get_publish_data(client, msg_buf, msg_read_len, &msg_topic, &msg_topic_len, &msg_data, &msg_data_len) != ESP_OK) { + ESP_LOGE(TAG, "%s: esp_mqtt5_get_publish_data() failed", __func__); + return ESP_FAIL; + } +#endif + } else { + // get topic + msg_topic = mqtt_get_publish_topic(msg_buf, &msg_topic_len); + if (msg_topic == NULL) { + ESP_LOGE(TAG, "%s: mqtt_get_publish_topic() failed", __func__); + return ESP_FAIL; + } + ESP_LOGD(TAG, "%s: msg_topic_len=%zu", __func__, msg_topic_len); - // get payload - msg_data = mqtt_get_publish_data(msg_buf, &msg_data_len); - if (msg_data_len > 0 && msg_data == NULL) { - ESP_LOGE(TAG, "%s: mqtt_get_publish_data() failed", __func__); - return ESP_FAIL; + // get payload + msg_data = mqtt_get_publish_data(msg_buf, &msg_data_len); + if (msg_data_len > 0 && msg_data == NULL) { + ESP_LOGE(TAG, "%s: mqtt_get_publish_data() failed", __func__); + return ESP_FAIL; + } } - // post data event client->event.retain = mqtt_get_retain(msg_buf); - client->event.msg_id = mqtt_get_id(msg_buf, msg_data_len); + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + client->event.msg_id = mqtt5_get_id(msg_buf, msg_read_len); +#endif + } else { + client->event.msg_id = mqtt_get_id(msg_buf, msg_read_len); + } client->event.qos = mqtt_get_qos(msg_buf); client->event.dup = mqtt_get_dup(msg_buf); client->event.total_data_len = msg_data_len + msg_total_len - msg_read_len; - post_data_event: ESP_LOGD(TAG, "Get data len= %zu, topic len=%zu, total_data: %d offset: %zu", msg_data_len, msg_topic_len, client->event.total_data_len, msg_data_offset); @@ -1093,7 +1205,13 @@ static esp_err_t deliver_suback(esp_mqtt_client_handle_t client) size_t msg_data_len = client->mqtt_state.in_buffer_read_len; char *msg_data = NULL; - msg_data = mqtt_get_suback_data(msg_buf, &msg_data_len); + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + msg_data = mqtt5_get_suback_data(msg_buf, &msg_data_len, &client->event.property->user_property); +#endif + } else { + msg_data = mqtt_get_suback_data(msg_buf, &msg_data_len); + } if (msg_data_len <= 0) { ESP_LOGE(TAG, "Failed to acquire suback data"); return ESP_FAIL; @@ -1277,9 +1395,8 @@ err: static esp_err_t mqtt_process_receive(esp_mqtt_client_handle_t client) { - uint8_t msg_type; - uint8_t msg_qos; - uint16_t msg_id; + uint8_t msg_type = 0, msg_qos = 0; + uint16_t msg_id = 0; /* non-blocking receive in order not to block other tasks */ int recv = mqtt_message_receive(client, 0); @@ -1295,13 +1412,22 @@ static esp_err_t mqtt_process_receive(esp_mqtt_client_handle_t client) // If the message was valid, get the type, quality of service and id of the message msg_type = mqtt_get_type(client->mqtt_state.in_buffer); msg_qos = mqtt_get_qos(client->mqtt_state.in_buffer); - msg_id = mqtt_get_id(client->mqtt_state.in_buffer, read_len); + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + msg_id = mqtt5_get_id(client->mqtt_state.in_buffer, read_len); +#endif + } else { + msg_id = mqtt_get_id(client->mqtt_state.in_buffer, read_len); + } ESP_LOGD(TAG, "msg_type=%d, msg_id=%d", msg_type, msg_id); switch (msg_type) { case MQTT_MSG_TYPE_SUBACK: if (is_valid_mqtt_msg(client, MQTT_MSG_TYPE_SUBSCRIBE, msg_id)) { +#ifdef MQTT_PROTOCOL_5 + esp_mqtt5_parse_suback(client); +#endif ESP_LOGD(TAG, "deliver_suback, message_length_read=%zu, message_length=%zu", client->mqtt_state.in_buffer_read_len, client->mqtt_state.message_length); if (deliver_suback(client) != ESP_OK) { ESP_LOGE(TAG, "Failed to deliver suback message id=%d", msg_id); @@ -1311,6 +1437,9 @@ static esp_err_t mqtt_process_receive(esp_mqtt_client_handle_t client) break; case MQTT_MSG_TYPE_UNSUBACK: if (is_valid_mqtt_msg(client, MQTT_MSG_TYPE_UNSUBSCRIBE, msg_id)) { +#ifdef MQTT_PROTOCOL_5 + esp_mqtt5_parse_unsuback(client); +#endif ESP_LOGD(TAG, "UnSubscribe successful"); client->event.event_id = MQTT_EVENT_UNSUBSCRIBED; esp_mqtt_dispatch_event_with_msgid(client); @@ -1323,9 +1452,21 @@ static esp_err_t mqtt_process_receive(esp_mqtt_client_handle_t client) return ESP_FAIL; } if (msg_qos == 1) { - client->mqtt_state.outbound_message = mqtt_msg_puback(&client->mqtt_state.mqtt_connection, msg_id); + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + client->mqtt_state.outbound_message = mqtt5_msg_puback(&client->mqtt_state.mqtt_connection, msg_id); +#endif + } else { + client->mqtt_state.outbound_message = mqtt_msg_puback(&client->mqtt_state.mqtt_connection, msg_id); + } } else if (msg_qos == 2) { - client->mqtt_state.outbound_message = mqtt_msg_pubrec(&client->mqtt_state.mqtt_connection, msg_id); + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + client->mqtt_state.outbound_message = mqtt5_msg_pubrec(&client->mqtt_state.mqtt_connection, msg_id); +#endif + } else { + client->mqtt_state.outbound_message = mqtt_msg_pubrec(&client->mqtt_state.mqtt_connection, msg_id); + } } if (client->mqtt_state.outbound_message->length == 0) { ESP_LOGE(TAG, "Publish response message PUBACK or PUBREC cannot be created"); @@ -1344,13 +1485,23 @@ static esp_err_t mqtt_process_receive(esp_mqtt_client_handle_t client) case MQTT_MSG_TYPE_PUBACK: if (is_valid_mqtt_msg(client, MQTT_MSG_TYPE_PUBLISH, msg_id)) { ESP_LOGD(TAG, "received MQTT_MSG_TYPE_PUBACK, finish QoS1 publish"); +#ifdef MQTT_PROTOCOL_5 + esp_mqtt5_parse_puback(client); +#endif client->event.event_id = MQTT_EVENT_PUBLISHED; esp_mqtt_dispatch_event_with_msgid(client); } break; case MQTT_MSG_TYPE_PUBREC: ESP_LOGD(TAG, "received MQTT_MSG_TYPE_PUBREC"); - client->mqtt_state.outbound_message = mqtt_msg_pubrel(&client->mqtt_state.mqtt_connection, msg_id); + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + ESP_LOGI(TAG, "MQTT_MSG_TYPE_PUBREC return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len)); + client->mqtt_state.outbound_message = mqtt5_msg_pubrel(&client->mqtt_state.mqtt_connection, msg_id); +#endif + } else { + client->mqtt_state.outbound_message = mqtt_msg_pubrel(&client->mqtt_state.mqtt_connection, msg_id); + } if (client->mqtt_state.outbound_message->length == 0) { ESP_LOGE(TAG, "Publish response message PUBREL cannot be created"); return ESP_FAIL; @@ -1361,7 +1512,14 @@ static esp_err_t mqtt_process_receive(esp_mqtt_client_handle_t client) break; case MQTT_MSG_TYPE_PUBREL: ESP_LOGD(TAG, "received MQTT_MSG_TYPE_PUBREL"); - client->mqtt_state.outbound_message = mqtt_msg_pubcomp(&client->mqtt_state.mqtt_connection, msg_id); + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + ESP_LOGI(TAG, "MQTT_MSG_TYPE_PUBREL return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len)); + client->mqtt_state.outbound_message = mqtt5_msg_pubcomp(&client->mqtt_state.mqtt_connection, msg_id); +#endif + } else { + client->mqtt_state.outbound_message = mqtt_msg_pubcomp(&client->mqtt_state.mqtt_connection, msg_id); + } if (client->mqtt_state.outbound_message->length == 0) { ESP_LOGE(TAG, "Publish response message PUBCOMP cannot be created"); return ESP_FAIL; @@ -1373,6 +1531,9 @@ static esp_err_t mqtt_process_receive(esp_mqtt_client_handle_t client) ESP_LOGD(TAG, "received MQTT_MSG_TYPE_PUBCOMP"); if (is_valid_mqtt_msg(client, MQTT_MSG_TYPE_PUBLISH, msg_id)) { ESP_LOGD(TAG, "Receive MQTT_MSG_TYPE_PUBCOMP, finish QoS2 publish"); +#ifdef MQTT_PROTOCOL_5 + esp_mqtt5_parse_pubcomp(client); +#endif client->event.event_id = MQTT_EVENT_PUBLISHED; esp_mqtt_dispatch_event_with_msgid(client); } @@ -1500,7 +1661,9 @@ static void esp_mqtt_task(void *pv) break; } client->event.event_id = MQTT_EVENT_CONNECTED; - client->event.session_present = mqtt_get_connect_session_present(client->mqtt_state.in_buffer); + if (client->connect_info.protocol_ver != MQTT_PROTOCOL_V_5) { + client->event.session_present = mqtt_get_connect_session_present(client->mqtt_state.in_buffer); + } client->state = MQTT_STATE_CONNECTED; esp_mqtt_dispatch_event_with_msgid(client); client->refresh_connection_tick = platform_tick_get_ms(); @@ -1654,7 +1817,18 @@ esp_err_t esp_mqtt_client_reconnect(esp_mqtt_client_handle_t client) static esp_err_t send_disconnect_msg(esp_mqtt_client_handle_t client) { // Notify the broker we are disconnecting - client->mqtt_state.outbound_message = mqtt_msg_disconnect(&client->mqtt_state.mqtt_connection); + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + client->mqtt_state.outbound_message = mqtt5_msg_disconnect(&client->mqtt_state.mqtt_connection, &client->mqtt5_config->disconnect_property_info); + if (client->mqtt_state.outbound_message->length) { + esp_mqtt5_client_delete_user_property(client->mqtt5_config->disconnect_property_info.user_property); + client->mqtt5_config->disconnect_property_info.user_property = NULL; + memset(&client->mqtt5_config->disconnect_property_info, 0, sizeof(esp_mqtt5_disconnect_property_config_t)); + } +#endif + } else { + client->mqtt_state.outbound_message = mqtt_msg_disconnect(&client->mqtt_state.mqtt_connection); + } if (client->mqtt_state.outbound_message->length == 0) { ESP_LOGE(TAG, "Disconnect message cannot be created"); return ESP_FAIL; @@ -1729,9 +1903,25 @@ int esp_mqtt_client_subscribe(esp_mqtt_client_handle_t client, const char *topic MQTT_API_UNLOCK(client); return -1; } - client->mqtt_state.outbound_message = mqtt_msg_subscribe(&client->mqtt_state.mqtt_connection, + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + if (esp_mqtt5_client_subscribe_check(client, qos) != ESP_OK) { + ESP_LOGI(TAG, "MQTT5 subscribe check fail"); + MQTT_API_UNLOCK(client); + return -1; + } + client->mqtt_state.outbound_message = mqtt5_msg_subscribe(&client->mqtt_state.mqtt_connection, + topic, qos, + &client->mqtt_state.pending_msg_id, client->mqtt5_config->subscribe_property_info); + if (client->mqtt_state.outbound_message->length) { + client->mqtt5_config->subscribe_property_info = NULL; + } +#endif + } else { + client->mqtt_state.outbound_message = mqtt_msg_subscribe(&client->mqtt_state.mqtt_connection, topic, qos, &client->mqtt_state.pending_msg_id); + } if (client->mqtt_state.outbound_message->length == 0) { ESP_LOGE(TAG, "Subscribe message cannot be created"); MQTT_API_UNLOCK(client); @@ -1770,9 +1960,20 @@ int esp_mqtt_client_unsubscribe(esp_mqtt_client_handle_t client, const char *top ESP_LOGE(TAG, "Client has not connected"); return -1; } - client->mqtt_state.outbound_message = mqtt_msg_unsubscribe(&client->mqtt_state.mqtt_connection, + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + client->mqtt_state.outbound_message = mqtt5_msg_unsubscribe(&client->mqtt_state.mqtt_connection, + topic, + &client->mqtt_state.pending_msg_id, client->mqtt5_config->unsubscribe_property_info); + if (client->mqtt_state.outbound_message->length) { + client->mqtt5_config->unsubscribe_property_info = NULL; + } +#endif + } else { + client->mqtt_state.outbound_message = mqtt_msg_unsubscribe(&client->mqtt_state.mqtt_connection, topic, &client->mqtt_state.pending_msg_id); + } if (client->mqtt_state.outbound_message->length == 0) { MQTT_API_UNLOCK(client); ESP_LOGE(TAG, "Unubscribe message cannot be created"); @@ -1803,17 +2004,28 @@ static inline int mqtt_client_enqueue_priv(esp_mqtt_client_handle_t client, cons int len, int qos, int retain, bool store) { uint16_t pending_msg_id = 0; - mqtt_message_t *publish_msg = mqtt_msg_publish(&client->mqtt_state.mqtt_connection, - topic, data, len, - qos, retain, - &pending_msg_id); + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + client->mqtt_state.outbound_message = mqtt5_msg_publish(&client->mqtt_state.mqtt_connection, + topic, data, len, + qos, retain, + &pending_msg_id, client->mqtt5_config->publish_property_info, client->mqtt5_config->server_resp_property_info.response_info); + if (client->mqtt_state.outbound_message->length) { + client->mqtt5_config->publish_property_info = NULL; + } +#endif + } else { + client->mqtt_state.outbound_message = mqtt_msg_publish(&client->mqtt_state.mqtt_connection, + topic, data, len, + qos, retain, + &pending_msg_id); + } - if (publish_msg->length == 0) { + if (client->mqtt_state.outbound_message->length == 0) { ESP_LOGE(TAG, "Publish message cannot be created"); return -1; } /* We have to set as pending all the qos>0 messages */ - client->mqtt_state.outbound_message = publish_msg; if (qos > 0 || store) { client->mqtt_state.pending_msg_type = mqtt_get_type(client->mqtt_state.outbound_message->data); client->mqtt_state.pending_msg_id = pending_msg_id; @@ -1850,6 +2062,16 @@ int esp_mqtt_client_publish(esp_mqtt_client_handle_t client, const char *topic, } #endif +#ifdef MQTT_PROTOCOL_5 + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { + if (esp_mqtt5_client_publish_check(client, qos, retain) != ESP_OK) { + ESP_LOGI(TAG, "MQTT5 publish check fail"); + MQTT_API_UNLOCK(client); + return -1; + } + } +#endif + /* Acceptable publish messages: data == NULL, len == 0: publish null message data valid, len == 0: publish all data, payload len is determined from string length @@ -1955,6 +2177,15 @@ int esp_mqtt_client_enqueue(esp_mqtt_client_handle_t client, const char *topic, } MQTT_API_LOCK(client); +#ifdef MQTT_PROTOCOL_5 + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { + if (esp_mqtt5_client_publish_check(client, qos, retain) != ESP_OK) { + ESP_LOGI(TAG, "esp_mqtt_client_enqueue check fail"); + MQTT_API_UNLOCK(client); + return -1; + } + } +#endif int ret = mqtt_client_enqueue_priv(client, topic, data, len, qos, retain, store); MQTT_API_UNLOCK(client); if (ret == 0 && store == false) { @@ -2017,3 +2248,745 @@ int esp_mqtt_client_get_outbox_size(esp_mqtt_client_handle_t client) return outbox_size; } + +#ifdef MQTT_PROTOCOL_5 +static void esp_mqtt5_flow_control(esp_mqtt_client_handle_t client) +{ + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { + int msg_type = mqtt5_get_type(client->mqtt_state.outbound_message->data); + if (msg_type == MQTT_MSG_TYPE_PUBLISH) { + int msg_qos = mqtt5_get_qos(client->mqtt_state.outbound_message->data); + if (msg_qos > 0) { + client->send_publish_packet_count ++; + ESP_LOGD(TAG, "Sent (%d) qos > 0 publish packet without ack", client->send_publish_packet_count); + } + } + } +} + +static void esp_mqtt5_parse_pubcomp(esp_mqtt_client_handle_t client) +{ + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { + ESP_LOGI(TAG, "MQTT_MSG_TYPE_PUBCOMP return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len)); + size_t msg_data_len = client->mqtt_state.in_buffer_read_len; + client->event.data = mqtt5_get_pubcomp_data(client->mqtt_state.in_buffer, &msg_data_len, &client->event.property->user_property); + client->event.data_len = msg_data_len; + client->event.total_data_len = msg_data_len; + client->event.current_data_offset = 0; + } +} + +static void esp_mqtt5_parse_puback(esp_mqtt_client_handle_t client) +{ + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { + ESP_LOGI(TAG, "MQTT_MSG_TYPE_PUBACK return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len)); + size_t msg_data_len = client->mqtt_state.in_buffer_read_len; + client->event.data = mqtt5_get_puback_data(client->mqtt_state.in_buffer, &msg_data_len, &client->event.property->user_property); + client->event.data_len = msg_data_len; + client->event.total_data_len = msg_data_len; + client->event.current_data_offset = 0; + client->send_publish_packet_count --; + } +} + +static void esp_mqtt5_parse_unsuback(esp_mqtt_client_handle_t client) +{ + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { + ESP_LOGI(TAG, "MQTT_MSG_TYPE_UNSUBACK return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len)); + size_t msg_data_len = client->mqtt_state.in_buffer_read_len; + client->event.data = mqtt5_get_unsuback_data(client->mqtt_state.in_buffer, &msg_data_len, &client->event.property->user_property); + client->event.data_len = msg_data_len; + client->event.total_data_len = msg_data_len; + client->event.current_data_offset = 0; + } +} + +static void esp_mqtt5_parse_suback(esp_mqtt_client_handle_t client) +{ + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { + ESP_LOGI(TAG, "MQTT_MSG_TYPE_SUBACK return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len)); + } +} + +static esp_err_t esp_mqtt5_parse_connack(esp_mqtt_client_handle_t client, int *connect_rsp_code) +{ + size_t len = client->mqtt_state.in_buffer_read_len; + client->mqtt_state.in_buffer_read_len = 0; + uint8_t ack_flag = 0; + if (mqtt5_msg_parse_connack_property(client->mqtt_state.in_buffer, len, &client->connect_info, &client->mqtt5_config->connect_property_info, &client->mqtt5_config->server_resp_property_info, connect_rsp_code, &ack_flag, &client->event.property->user_property) != ESP_OK) { + ESP_LOGE(TAG, "Failed to parse CONNACK packet"); + return ESP_FAIL; + } + if (*connect_rsp_code == MQTT_CONNECTION_ACCEPTED) { + ESP_LOGD(TAG, "Connected"); + client->event.session_present = ack_flag & 0x01; + return ESP_OK; + } + esp_mqtt5_print_error_code(client, *connect_rsp_code); + return ESP_FAIL; +} + +static esp_err_t esp_mqtt5_get_publish_data(esp_mqtt_client_handle_t client, uint8_t *msg_buf, size_t msg_read_len, char **msg_topic, size_t *msg_topic_len, char **msg_data, size_t *msg_data_len) +{ + // get property + uint16_t property_len = 0; + esp_mqtt5_publish_resp_property_t property = {0}; + *msg_data = mqtt5_get_publish_property_payload(msg_buf, msg_read_len, msg_topic, msg_topic_len, &property, &property_len, msg_data_len, &client->event.property->user_property); + if (*msg_data_len == 0 || *msg_data == NULL) { + ESP_LOGE(TAG, "%s: mqtt5_get_publish_property_payload() failed", __func__); + return ESP_FAIL; + } + + if (property.topic_alias > client->mqtt5_config->connect_property_info.topic_alias_maximum) { + ESP_LOGE(TAG, "%s: Broker response topic alias %d is over the max topic alias %d", __func__, property.topic_alias, client->mqtt5_config->connect_property_info.topic_alias_maximum); + return ESP_FAIL; + } + + if (property.topic_alias) { + if (*msg_topic_len == 0) { + ESP_LOGI(TAG, "Publish topic is empty, use topic alias"); + *msg_topic = esp_mqtt5_client_get_topic_alias(client->mqtt5_config->peer_topic_alias, property.topic_alias, msg_topic_len); + if (!*msg_topic) { + ESP_LOGE(TAG, "%s: esp_mqtt5_client_get_topic_alias() failed", __func__); + return ESP_FAIL; + } + } else { + if (esp_mqtt5_client_update_topic_alias(client->mqtt5_config->peer_topic_alias, property.topic_alias, *msg_topic, *msg_topic_len) != ESP_OK) { + ESP_LOGE(TAG, "%s: esp_mqtt5_client_update_topic_alias() failed", __func__); + return ESP_FAIL; + } + } + } + + client->event.property->payload_format_indicator = property.payload_format_indicator; + client->event.property->response_topic = property.response_topic; + client->event.property->response_topic_len = property.response_topic_len; + client->event.property->correlation_data = property.correlation_data; + client->event.property->correlation_data_len = property.correlation_data_len; + client->event.property->content_type = property.content_type; + client->event.property->content_type_len = property.content_type_len; + return ESP_OK; +} + +static esp_err_t esp_mqtt5_create_default_config(esp_mqtt_client_handle_t client) +{ + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { + client->event.property = calloc(1, sizeof(esp_mqtt5_event_property_t)); + ESP_MEM_CHECK(TAG, client->event.property, return ESP_FAIL) + client->mqtt5_config = calloc(1, sizeof(mqtt5_config_storage_t)); + ESP_MEM_CHECK(TAG, client->mqtt5_config, return ESP_FAIL) + client->mqtt5_config->server_resp_property_info.max_qos = 2; + client->mqtt5_config->server_resp_property_info.retain_available = true; + client->mqtt5_config->server_resp_property_info.wildcard_subscribe_available = true; + client->mqtt5_config->server_resp_property_info.subscribe_identifiers_available = true; + client->mqtt5_config->server_resp_property_info.shared_subscribe_available = true; + client->mqtt5_config->server_resp_property_info.receive_maximum = 65535; + } + return ESP_OK; +} + +static void esp_mqtt5_print_error_code(esp_mqtt_client_handle_t client, int code) +{ + switch (code) { + case MQTT5_UNSPECIFIED_ERROR: + ESP_LOGW(TAG, "Unspecified error"); + break; + case MQTT5_MALFORMED_PACKET: + ESP_LOGW(TAG, "Malformed Packet"); + break; + case MQTT5_PROTOCOL_ERROR: + ESP_LOGW(TAG, "Protocol Error"); + break; + case MQTT5_IMPLEMENT_SPECIFIC_ERROR: + ESP_LOGW(TAG, "Implementation specific error"); + break; + case MQTT5_UNSUPPORTED_PROTOCOL_VER: + ESP_LOGW(TAG, "Unsupported Protocol Version"); + break; + case MQTT5_INVAILD_CLIENT_ID: + ESP_LOGW(TAG, "Client Identifier not valid"); + break; + case MQTT5_BAD_USERNAME_OR_PWD: + ESP_LOGW(TAG, "Bad User Name or Password"); + break; + case MQTT5_NOT_AUTHORIZED: + ESP_LOGW(TAG, "Not authorized"); + break; + case MQTT5_SERVER_UNAVAILABLE: + ESP_LOGW(TAG, "Server unavailable"); + break; + case MQTT5_SERVER_BUSY: + ESP_LOGW(TAG, "Server busy"); + break; + case MQTT5_BANNED: + ESP_LOGW(TAG, "Banned"); + break; + case MQTT5_SERVER_SHUTTING_DOWN: + ESP_LOGW(TAG, "Server shutting down"); + break; + case MQTT5_BAD_AUTH_METHOD: + ESP_LOGW(TAG, "Bad authentication method"); + break; + case MQTT5_KEEP_ALIVE_TIMEOUT: + ESP_LOGW(TAG, "Keep Alive timeout"); + break; + case MQTT5_SESSION_TAKEN_OVER: + ESP_LOGW(TAG, "Session taken over"); + break; + case MQTT5_TOPIC_FILTER_INVAILD: + ESP_LOGW(TAG, "Topic Filter invalid"); + break; + case MQTT5_TOPIC_NAME_INVAILD: + ESP_LOGW(TAG, "Topic Name invalid"); + break; + case MQTT5_PACKET_IDENTIFIER_IN_USE: + ESP_LOGW(TAG, "Packet Identifier in use"); + break; + case MQTT5_PACKET_IDENTIFIER_NOT_FOUND: + ESP_LOGW(TAG, "Packet Identifier not found"); + break; + case MQTT5_RECEIVE_MAXIMUM_EXCEEDED: + ESP_LOGW(TAG, "Receive Maximum exceeded"); + break; + case MQTT5_TOPIC_ALIAS_INVAILD: + ESP_LOGW(TAG, "Topic Alias invalid"); + break; + case MQTT5_PACKET_TOO_LARGE: + ESP_LOGW(TAG, "Packet too large"); + break; + case MQTT5_MESSAGE_RATE_TOO_HIGH: + ESP_LOGW(TAG, "Message rate too high"); + break; + case MQTT5_QUOTA_EXCEEDED: + ESP_LOGW(TAG, "Quota exceeded"); + break; + case MQTT5_ADMINISTRATIVE_ACTION: + ESP_LOGW(TAG, "Administrative action"); + break; + case MQTT5_PAYLOAD_FORMAT_INVAILD: + ESP_LOGW(TAG, "Payload format invalid"); + break; + case MQTT5_RETAIN_NOT_SUPPORT: + ESP_LOGW(TAG, "Retain not supported"); + break; + case MQTT5_QOS_NOT_SUPPORT: + ESP_LOGW(TAG, "QoS not supported"); + break; + case MQTT5_USE_ANOTHER_SERVER: + ESP_LOGW(TAG, "Use another server"); + break; + case MQTT5_SERVER_MOVED: + ESP_LOGW(TAG, "Server moved"); + break; + case MQTT5_SHARED_SUBSCR_NOT_SUPPORTED: + ESP_LOGW(TAG, "Shared Subscriptions not supported"); + break; + case MQTT5_CONNECTION_RATE_EXCEEDED: + ESP_LOGW(TAG, "Connection rate exceeded"); + break; + case MQTT5_MAXIMUM_CONNECT_TIME: + ESP_LOGW(TAG, "Maximum connect time"); + break; + case MQTT5_SUBSCRIBE_IDENTIFIER_NOT_SUPPORT: + ESP_LOGW(TAG, "Subscription Identifiers not supported"); + break; + case MQTT5_WILDCARD_SUBSCRIBE_NOT_SUPPORT: + ESP_LOGW(TAG, "Wildcard Subscriptions not supported"); + break; + default: + ESP_LOGW(TAG, "Connection refused, Unknow reason"); + break; + } +} + +static esp_err_t esp_mqtt5_client_subscribe_check(esp_mqtt_client_handle_t client, int qos) +{ + /* Check Server support QoS level */ + if (client->mqtt5_config->server_resp_property_info.max_qos < qos) { + ESP_LOGE(TAG, "Server only support max QoS level %d", client->mqtt5_config->server_resp_property_info.max_qos); + return ESP_FAIL; + } + + return ESP_OK; +} + +static esp_err_t esp_mqtt5_client_publish_check(esp_mqtt_client_handle_t client, int qos, int retain) +{ + /* Check Server support QoS level */ + if (client->mqtt5_config->server_resp_property_info.max_qos < qos) { + ESP_LOGE(TAG, "Server only support max QoS level %d", client->mqtt5_config->server_resp_property_info.max_qos); + return ESP_FAIL; + } + + /* Check Server support RETAIN */ + if (!client->mqtt5_config->server_resp_property_info.retain_available && retain) { + ESP_LOGE(TAG, "Server not support retain"); + return ESP_FAIL; + } + + /* Flow control to check PUBLISH(No PUBACK or PUBCOMP received) packet sent count(Only record QoS1 and QoS2)*/ + if (client->send_publish_packet_count >= client->mqtt5_config->server_resp_property_info.receive_maximum) { + ESP_LOGE(TAG, "Client send more than %d QoS1 and QoS2 PUBLISH packet without no ack", client->mqtt5_config->server_resp_property_info.receive_maximum); + return ESP_FAIL; + } + + return ESP_OK; +} + +static void esp_mqtt5_client_destory(esp_mqtt_client_handle_t client) +{ + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { + if (client->mqtt5_config) { + free(client->mqtt5_config->will_property_info.content_type); + free(client->mqtt5_config->will_property_info.response_topic); + free(client->mqtt5_config->will_property_info.correlation_data); + free(client->mqtt5_config->server_resp_property_info.response_info); + esp_mqtt5_client_delete_topic_alias(client->mqtt5_config->peer_topic_alias); + esp_mqtt5_client_delete_user_property(client->mqtt5_config->connect_property_info.user_property); + esp_mqtt5_client_delete_user_property(client->mqtt5_config->will_property_info.user_property); + esp_mqtt5_client_delete_user_property(client->mqtt5_config->disconnect_property_info.user_property); + free(client->mqtt5_config); + } + free(client->event.property); + } +} + +static void esp_mqtt5_client_delete_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle) +{ + if (topic_alias_handle) { + mqtt5_topic_alias_item_t item, tmp; + STAILQ_FOREACH_SAFE(item, topic_alias_handle, next, tmp) { + STAILQ_REMOVE(topic_alias_handle, item, mqtt5_topic_alias, next); + free(item->topic); + free(item); + } + free(topic_alias_handle); + } +} + +static esp_err_t esp_mqtt5_client_update_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle, uint16_t topic_alias, char *topic, size_t topic_len) +{ + mqtt5_topic_alias_item_t item; + bool found = false; + STAILQ_FOREACH(item, topic_alias_handle, next) { + if (item->topic_alias == topic_alias) { + found = true; + break; + } + } + if (found) { + if ((item->topic_len != topic_len) || strncmp(topic, item->topic, topic_len)) { + free(item->topic); + item->topic = calloc(1, topic_len); + ESP_MEM_CHECK(TAG, item->topic, return ESP_FAIL); + memcpy(item->topic, topic, topic_len); + item->topic_len = topic_len; + } + } else { + item = calloc(1, sizeof(mqtt5_topic_alias_t)); + ESP_MEM_CHECK(TAG, item, return ESP_FAIL); + item->topic_alias = topic_alias; + item->topic_len = topic_len; + item->topic = calloc(1, topic_len); + ESP_MEM_CHECK(TAG, item->topic, { + free(item); + return ESP_FAIL; + }); + memcpy(item->topic, topic, topic_len); + STAILQ_INSERT_TAIL(topic_alias_handle, item, next); + } + return ESP_OK; +} + +static char *esp_mqtt5_client_get_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle, uint16_t topic_alias, size_t *topic_length) +{ + mqtt5_topic_alias_item_t item; + STAILQ_FOREACH(item, topic_alias_handle, next) { + if (item->topic_alias == topic_alias) { + *topic_length = item->topic_len; + return item->topic; + } + } + *topic_length = 0; + return NULL; +} + +static esp_err_t esp_mqtt5_user_property_copy(mqtt5_user_property_handle_t user_property_new, const mqtt5_user_property_handle_t user_property_old) +{ + if (!user_property_new || !user_property_old) { + ESP_LOGE(TAG, "Input is NULL"); + return ESP_FAIL; + } + + mqtt5_user_property_item_t old_item, new_item; + STAILQ_FOREACH(old_item, user_property_old, next) { + new_item = calloc(1, sizeof(mqtt5_user_property_t)); + ESP_MEM_CHECK(TAG, new_item, return ESP_FAIL); + new_item->key = strdup(old_item->key); + ESP_MEM_CHECK(TAG, new_item->key, { + free(new_item); + return ESP_FAIL; + }); + new_item->value = strdup(old_item->value); + ESP_MEM_CHECK(TAG, new_item->value, { + free(new_item->key); + free(new_item); + return ESP_FAIL; + }); + STAILQ_INSERT_TAIL(user_property_new, new_item, next); + } + return ESP_OK; +} + +esp_err_t esp_mqtt5_client_set_publish_property(esp_mqtt_client_handle_t client, const esp_mqtt5_publish_property_config_t *property) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return ESP_ERR_INVALID_ARG; + } + MQTT_API_LOCK(client); + + /* Check protocol version */ + if(client->connect_info.protocol_ver != MQTT_PROTOCOL_V_5) { + ESP_LOGE(TAG, "MQTT protocol version is not v5"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + /* Check topic alias less than server maximum topic alias */ + if (property->topic_alias > client->mqtt5_config->server_resp_property_info.topic_alias_maximum) { + ESP_LOGE(TAG, "Topic alias %d is bigger than server support %d", property->topic_alias, client->mqtt5_config->server_resp_property_info.topic_alias_maximum); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + client->mqtt5_config->publish_property_info = property; + MQTT_API_UNLOCK(client); + return ESP_OK; +} + +esp_err_t esp_mqtt5_client_set_subscribe_property(esp_mqtt_client_handle_t client, const esp_mqtt5_subscribe_property_config_t *property) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return ESP_ERR_INVALID_ARG; + } + if (property->retain_handle > 2) { + ESP_LOGE(TAG, "retain_handle only support 0, 1, 2"); + return -1; + } + MQTT_API_LOCK(client); + + /* Check protocol version */ + if (client->connect_info.protocol_ver != MQTT_PROTOCOL_V_5) { + ESP_LOGE(TAG, "MQTT protocol version is not v5"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + if (property->is_share_subscribe) { + if (property->no_local_flag) { + // MQTT-3.8.3-4 not allow that No Local bit to 1 on a Shared Subscription + ESP_LOGE(TAG, "Protocol error that no local flag set on shared subscription"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + if (!client->mqtt5_config->server_resp_property_info.shared_subscribe_available) { + ESP_LOGE(TAG, "MQTT broker not support shared subscribe"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + if (!property->share_name || !strlen(property->share_name)) { + ESP_LOGE(TAG, "Share name can't be empty for shared subscribe"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + } + client->mqtt5_config->subscribe_property_info = property; + MQTT_API_UNLOCK(client); + return ESP_OK; +} + +esp_err_t esp_mqtt5_client_set_unsubscribe_property(esp_mqtt_client_handle_t client, const esp_mqtt5_unsubscribe_property_config_t *property) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return ESP_ERR_INVALID_ARG; + } + MQTT_API_LOCK(client); + + /* Check protocol version */ + if (client->connect_info.protocol_ver != MQTT_PROTOCOL_V_5) { + ESP_LOGE(TAG, "MQTT protocol version is not v5"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + if (property->is_share_subscribe) { + if (!client->mqtt5_config->server_resp_property_info.shared_subscribe_available) { + ESP_LOGE(TAG, "MQTT broker not support shared subscribe"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + if (!property->share_name || !strlen(property->share_name)) { + ESP_LOGE(TAG, "Share name can't be empty for shared subscribe"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + } + client->mqtt5_config->unsubscribe_property_info = property; + MQTT_API_UNLOCK(client); + return ESP_OK; +} + +esp_err_t esp_mqtt5_client_set_disconnect_property(esp_mqtt_client_handle_t client, const esp_mqtt5_disconnect_property_config_t *property) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return ESP_ERR_INVALID_ARG; + } + MQTT_API_LOCK(client); + + /* Check protocol version */ + if (client->connect_info.protocol_ver != MQTT_PROTOCOL_V_5) { + ESP_LOGE(TAG, "MQTT protocol version is not v5"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + if (property) { + if (property->session_expiry_interval) { + client->mqtt5_config->disconnect_property_info.session_expiry_interval = property->session_expiry_interval; + } + if (property->disconnect_reason) { + client->mqtt5_config->disconnect_property_info.disconnect_reason = property->disconnect_reason; + } + if (property->user_property) { + esp_mqtt5_client_delete_user_property(client->mqtt5_config->disconnect_property_info.user_property); + client->mqtt5_config->disconnect_property_info.user_property = calloc(1, sizeof(struct mqtt5_user_property_list_t)); + ESP_MEM_CHECK(TAG, client->mqtt5_config->disconnect_property_info.user_property, { + MQTT_API_UNLOCK(client); + return ESP_ERR_NO_MEM; + }); + STAILQ_INIT(client->mqtt5_config->disconnect_property_info.user_property); + if (esp_mqtt5_user_property_copy(client->mqtt5_config->disconnect_property_info.user_property, property->user_property) != ESP_OK) { + ESP_LOGE(TAG, "esp_mqtt5_user_property_copy fail"); + free(client->mqtt5_config->disconnect_property_info.user_property); + client->mqtt5_config->disconnect_property_info.user_property = NULL; + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + } + } + + MQTT_API_UNLOCK(client); + return ESP_OK; +} + +esp_err_t esp_mqtt5_client_set_connect_property(esp_mqtt_client_handle_t client, const esp_mqtt5_connection_property_config_t *connect_property) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return ESP_ERR_INVALID_ARG; + } + MQTT_API_LOCK(client); + + /* Check protocol version */ + if (client->connect_info.protocol_ver != MQTT_PROTOCOL_V_5) { + ESP_LOGE(TAG, "MQTT protocol version is not v5"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + if (connect_property) { + if (connect_property->session_expiry_interval) { + client->mqtt5_config->connect_property_info.session_expiry_interval = connect_property->session_expiry_interval; + } + if (connect_property->maximum_packet_size) { + if (connect_property->maximum_packet_size > client->mqtt_state.in_buffer_length) { + ESP_LOGW(TAG, "Connect maximum_packet_size property is over buffer_size(%d), Please first change it", client->mqtt_state.in_buffer_length); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } else { + client->mqtt5_config->connect_property_info.maximum_packet_size = connect_property->maximum_packet_size; + } + } else { + client->mqtt5_config->connect_property_info.maximum_packet_size = client->mqtt_state.in_buffer_length; + } + if (connect_property->receive_maximum) { + client->mqtt5_config->connect_property_info.receive_maximum = connect_property->receive_maximum; + } + if (connect_property->topic_alias_maximum) { + client->mqtt5_config->connect_property_info.topic_alias_maximum = connect_property->topic_alias_maximum; + if (!client->mqtt5_config->peer_topic_alias) { + client->mqtt5_config->peer_topic_alias = calloc(1, sizeof(struct mqtt5_topic_alias_list_t)); + ESP_MEM_CHECK(TAG, client->mqtt5_config->peer_topic_alias, goto _mqtt_set_config_failed); + STAILQ_INIT(client->mqtt5_config->peer_topic_alias); + } + } + if (connect_property->request_resp_info) { + client->mqtt5_config->connect_property_info.request_resp_info = connect_property->request_resp_info; + } + if (connect_property->request_problem_info) { + client->mqtt5_config->connect_property_info.request_problem_info = connect_property->request_problem_info; + } + if (connect_property->user_property) { + esp_mqtt5_client_delete_user_property(client->mqtt5_config->connect_property_info.user_property); + client->mqtt5_config->connect_property_info.user_property = calloc(1, sizeof(struct mqtt5_user_property_list_t)); + ESP_MEM_CHECK(TAG, client->mqtt5_config->connect_property_info.user_property, goto _mqtt_set_config_failed); + STAILQ_INIT(client->mqtt5_config->connect_property_info.user_property); + if (esp_mqtt5_user_property_copy(client->mqtt5_config->connect_property_info.user_property, connect_property->user_property) != ESP_OK) { + ESP_LOGE(TAG, "esp_mqtt5_user_property_copy fail"); + goto _mqtt_set_config_failed; + } + } + if (connect_property->payload_format_indicator) { + client->mqtt5_config->will_property_info.payload_format_indicator = connect_property->payload_format_indicator; + } + if (connect_property->will_delay_interval) { + client->mqtt5_config->will_property_info.will_delay_interval = connect_property->will_delay_interval; + } + if (connect_property->message_expiry_interval) { + client->mqtt5_config->will_property_info.message_expiry_interval = connect_property->message_expiry_interval; + } + ESP_MEM_CHECK(TAG, set_if_config(connect_property->content_type, &client->mqtt5_config->will_property_info.content_type), goto _mqtt_set_config_failed); + ESP_MEM_CHECK(TAG, set_if_config(connect_property->response_topic, &client->mqtt5_config->will_property_info.response_topic), goto _mqtt_set_config_failed); + if (connect_property->correlation_data && connect_property->correlation_data_len) { + free(client->mqtt5_config->will_property_info.correlation_data); + 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); + memcpy(client->mqtt5_config->will_property_info.correlation_data, connect_property->correlation_data, connect_property->correlation_data_len); + client->mqtt5_config->will_property_info.correlation_data_len = connect_property->correlation_data_len; + } + if (connect_property->will_user_property) { + esp_mqtt5_client_delete_user_property(client->mqtt5_config->will_property_info.user_property); + client->mqtt5_config->will_property_info.user_property = calloc(1, sizeof(struct mqtt5_user_property_list_t)); + ESP_MEM_CHECK(TAG, client->mqtt5_config->will_property_info.user_property, goto _mqtt_set_config_failed); + STAILQ_INIT(client->mqtt5_config->will_property_info.user_property); + if (esp_mqtt5_user_property_copy(client->mqtt5_config->will_property_info.user_property, connect_property->will_user_property) != ESP_OK) { + ESP_LOGE(TAG, "esp_mqtt5_user_property_copy fail"); + goto _mqtt_set_config_failed; + } + } + } + MQTT_API_UNLOCK(client); + return ESP_OK; +_mqtt_set_config_failed: + esp_mqtt_destroy_config(client); + MQTT_API_UNLOCK(client); + return ESP_ERR_NO_MEM; +} + +esp_err_t esp_mqtt5_client_set_user_property(mqtt5_user_property_handle_t *user_property, esp_mqtt5_user_property_item_t item[], uint8_t item_num) +{ + if (!item_num || !item) { + ESP_LOGE(TAG, "Input value is NULL"); + return ESP_FAIL; + } + + if (!*user_property) { + *user_property = calloc(1, sizeof(struct mqtt5_user_property_list_t)); + ESP_MEM_CHECK(TAG, *user_property, return ESP_ERR_NO_MEM); + STAILQ_INIT(*user_property); + } + + for (int i = 0; i < item_num; i ++) { + if (item[i].key && item[i].value) { + mqtt5_user_property_item_t user_property_item = calloc(1, sizeof(mqtt5_user_property_t)); + ESP_MEM_CHECK(TAG, user_property_item, goto err); + size_t key_len = strlen(item[i].key); + size_t value_len = strlen(item[i].value); + + user_property_item->key = calloc(1, key_len + 1); + ESP_MEM_CHECK(TAG, user_property_item->key, { + free(user_property_item); + goto err; + }); + memcpy(user_property_item->key, item[i].key, key_len); + user_property_item->key[key_len] = '\0'; + + user_property_item->value = calloc(1, value_len + 1); + ESP_MEM_CHECK(TAG, user_property_item->value, { + free(user_property_item->key); + free(user_property_item); + goto err; + }); + memcpy(user_property_item->value, item[i].value, value_len); + user_property_item->value[value_len] = '\0'; + + STAILQ_INSERT_TAIL(*user_property, user_property_item, next); + } + } + return ESP_OK; +err: + esp_mqtt5_client_delete_user_property(*user_property); + *user_property = NULL; + return ESP_ERR_NO_MEM; +} + +esp_err_t esp_mqtt5_client_get_user_property(mqtt5_user_property_handle_t user_property, esp_mqtt5_user_property_item_t *item, uint8_t *item_num) +{ + int i = 0, j = 0; + if (user_property && item && *item_num) { + mqtt5_user_property_item_t user_property_item; + uint8_t num = *item_num; + STAILQ_FOREACH(user_property_item, user_property, next) { + if (i < num) { + size_t item_key_len = strlen(user_property_item->key); + size_t item_value_len = strlen(user_property_item->value); + char *key = calloc(1, item_key_len + 1); + ESP_MEM_CHECK(TAG, key, goto err); + memcpy(key, user_property_item->key, item_key_len); + key[item_key_len] = '\0'; + char *value = calloc(1, item_value_len + 1); + ESP_MEM_CHECK(TAG, value, { + free(key); + goto err; + }); + memcpy(value, user_property_item->value, item_value_len); + value[item_value_len] = '\0'; + item[i].key = key; + item[i].value = value; + i ++; + } else { + break; + } + } + *item_num = i; + return ESP_OK; + } else { + ESP_LOGE(TAG, "Input value is NULL or item_num is 0"); + return ESP_FAIL; + } +err: + for (j = 0; j < i; j ++) { + if (item[j].key) { + free((char *)item[j].key); + } + if (item[j].value) { + free((char *)item[j].value); + } + } + return ESP_ERR_NO_MEM; +} + +uint8_t esp_mqtt5_client_get_user_property_count(mqtt5_user_property_handle_t user_property) +{ + uint8_t count = 0; + if (user_property) { + mqtt5_user_property_item_t item; + STAILQ_FOREACH(item, user_property, next) { + count ++; + } + } + return count; +} + +void esp_mqtt5_client_delete_user_property(mqtt5_user_property_handle_t user_property) +{ + if (user_property) { + mqtt5_user_property_item_t item, tmp; + STAILQ_FOREACH_SAFE(item, user_property, next, tmp) { + STAILQ_REMOVE(user_property, item, mqtt5_user_property, next); + free(item->key); + free(item->value); + free(item); + } + } + free(user_property); +} +#endif From fdf2aeb36f6757f5fe50d2cea66e88a185c24cd6 Mon Sep 17 00:00:00 2001 From: yuanjm Date: Fri, 20 May 2022 11:55:28 +0800 Subject: [PATCH 2/2] Seperate MQTT5 from MQTT 3.1.1 --- include/mqtt5_client.h | 280 ++++++++++ include/mqtt_client.h | 266 +--------- lib/include/mqtt5_client_priv.h | 54 ++ lib/include/mqtt5_msg.h | 2 +- lib/include/mqtt_client_priv.h | 136 +++++ lib/mqtt5_msg.c | 2 +- mqtt5_client.c | 757 ++++++++++++++++++++++++++ mqtt_client.c | 915 +------------------------------- 8 files changed, 1242 insertions(+), 1170 deletions(-) create mode 100644 include/mqtt5_client.h create mode 100644 lib/include/mqtt5_client_priv.h create mode 100644 lib/include/mqtt_client_priv.h create mode 100644 mqtt5_client.c diff --git a/include/mqtt5_client.h b/include/mqtt5_client.h new file mode 100644 index 0000000..c3c0178 --- /dev/null +++ b/include/mqtt5_client.h @@ -0,0 +1,280 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _MQTT5_CLIENT_H_ +#define _MQTT5_CLIENT_H_ + +#include "mqtt_client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct esp_mqtt_client *esp_mqtt5_client_handle_t; + +/** + * MQTT5 protocol error reason code, more details refer to MQTT5 protocol document section 2.4 + */ +enum mqtt5_error_reason_code { + MQTT5_UNSPECIFIED_ERROR = 0x80, + MQTT5_MALFORMED_PACKET = 0x81, + MQTT5_PROTOCOL_ERROR = 0x82, + MQTT5_IMPLEMENT_SPECIFIC_ERROR = 0x83, + MQTT5_UNSUPPORTED_PROTOCOL_VER = 0x84, + MQTT5_INVAILD_CLIENT_ID = 0x85, + MQTT5_BAD_USERNAME_OR_PWD = 0x86, + MQTT5_NOT_AUTHORIZED = 0x87, + MQTT5_SERVER_UNAVAILABLE = 0x88, + MQTT5_SERVER_BUSY = 0x89, + MQTT5_BANNED = 0x8A, + MQTT5_SERVER_SHUTTING_DOWN = 0x8B, + MQTT5_BAD_AUTH_METHOD = 0x8C, + MQTT5_KEEP_ALIVE_TIMEOUT = 0x8D, + MQTT5_SESSION_TAKEN_OVER = 0x8E, + MQTT5_TOPIC_FILTER_INVAILD = 0x8F, + MQTT5_TOPIC_NAME_INVAILD = 0x90, + MQTT5_PACKET_IDENTIFIER_IN_USE = 0x91, + MQTT5_PACKET_IDENTIFIER_NOT_FOUND = 0x92, + MQTT5_RECEIVE_MAXIMUM_EXCEEDED = 0x93, + MQTT5_TOPIC_ALIAS_INVAILD = 0x94, + MQTT5_PACKET_TOO_LARGE = 0x95, + MQTT5_MESSAGE_RATE_TOO_HIGH = 0x96, + MQTT5_QUOTA_EXCEEDED = 0x97, + MQTT5_ADMINISTRATIVE_ACTION = 0x98, + MQTT5_PAYLOAD_FORMAT_INVAILD = 0x99, + MQTT5_RETAIN_NOT_SUPPORT = 0x9A, + MQTT5_QOS_NOT_SUPPORT = 0x9B, + MQTT5_USE_ANOTHER_SERVER = 0x9C, + MQTT5_SERVER_MOVED = 0x9D, + MQTT5_SHARED_SUBSCR_NOT_SUPPORTED = 0x9E, + MQTT5_CONNECTION_RATE_EXCEEDED = 0x9F, + MQTT5_MAXIMUM_CONNECT_TIME = 0xA0, + MQTT5_SUBSCRIBE_IDENTIFIER_NOT_SUPPORT = 0xA1, + MQTT5_WILDCARD_SUBSCRIBE_NOT_SUPPORT = 0xA2, +}; + +/** + * MQTT5 user property handle + */ +typedef struct mqtt5_user_property_list_t *mqtt5_user_property_handle_t; + +/** + * MQTT5 protocol connect properties and will properties configuration, more details refer to MQTT5 protocol document section 3.1.2.11 and 3.3.2.3 + */ +typedef struct { + uint32_t session_expiry_interval; /*!< The interval time of session expiry */ + uint32_t maximum_packet_size; /*!< The maximum packet size that we can receive */ + uint16_t receive_maximum; /*!< The maximum pakcket count that we process concurrently */ + uint16_t topic_alias_maximum; /*!< The maximum topic alias that we support */ + bool request_resp_info; /*!< This value to request Server to return Response information */ + bool request_problem_info; /*!< This value to indicate whether the reason string or user properties are sent in case of failures */ + mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */ + uint32_t will_delay_interval; /*!< The time interval that server delays publishing will message */ + uint32_t message_expiry_interval; /*!< The time interval that message expiry */ + bool payload_format_indicator; /*!< This value is to indicator will message payload format */ + const char *content_type; /*!< This value is to indicator will message content type, use a MIME content type string */ + const char *response_topic; /*!< Topic name for a response message */ + const char *correlation_data; /*!< Binary data for receiver to match the response message */ + uint16_t correlation_data_len; /*!< The length of correlation data */ + mqtt5_user_property_handle_t will_user_property; /*!< The handle for will message user property, call function esp_mqtt5_client_set_user_property to set it */ +} esp_mqtt5_connection_property_config_t; + +/** + * MQTT5 protocol publish properties configuration, more details refer to MQTT5 protocol document section 3.3.2.3 + */ +typedef struct { + bool payload_format_indicator; /*!< This value is to indicator publish message payload format */ + uint32_t message_expiry_interval; /*!< The time interval that message expiry */ + uint16_t topic_alias; /*!< An interger value to identify the topic instead of using topic name string */ + const char *response_topic; /*!< Topic name for a response message */ + const char *correlation_data; /*!< Binary data for receiver to match the response message */ + uint16_t correlation_data_len; /*!< The length of correlation data */ + const char *content_type; /*!< This value is to indicator publish message content type, use a MIME content type string */ + mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */ +} esp_mqtt5_publish_property_config_t; + +/** + * MQTT5 protocol subscribe properties configuration, more details refer to MQTT5 protocol document section 3.8.2.1 + */ +typedef struct { + uint16_t subscribe_id; /*!< A variable byte represents the identifier of the subscription */ + bool no_local_flag; /*!< Subscription Option to allow that server publish message that client sent */ + bool retain_as_published_flag; /*!< Subscription Option to keep the retain flag as published option */ + uint8_t retain_handle; /*!< Subscription Option to handle retain option */ + bool is_share_subscribe; /*!< Whether subscribe is a shared subscription */ + const char *share_name; /*!< The name of shared subscription which is a part of $share/{share_name}/{topic} */ + mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */ +} esp_mqtt5_subscribe_property_config_t; + +/** + * MQTT5 protocol unsubscribe properties configuration, more details refer to MQTT5 protocol document section 3.10.2.1 + */ +typedef struct { + bool is_share_subscribe; /*!< Whether subscribe is a shared subscription */ + const char *share_name; /*!< The name of shared subscription which is a part of $share/{share_name}/{topic} */ + mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */ +} esp_mqtt5_unsubscribe_property_config_t; + +/** + * MQTT5 protocol disconnect properties configuration, more details refer to MQTT5 protocol document section 3.14.2.2 + */ +typedef struct { + uint32_t session_expiry_interval; /*!< The interval time of session expiry */ + uint8_t disconnect_reason; /*!< The reason that connection disconnet, refer to mqtt5_error_reason_code */ + mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */ +} esp_mqtt5_disconnect_property_config_t; + +/** + * MQTT5 protocol for event properties + */ +typedef struct { + bool payload_format_indicator; /*!< Payload format of the message */ + char *response_topic; /*!< Response topic of the message */ + int response_topic_len; /*!< Response topic length of the message */ + char *correlation_data; /*!< Correlation data of the message */ + uint16_t correlation_data_len; /*!< Correlation data length of the message */ + char *content_type; /*!< Content type of the message */ + int content_type_len; /*!< Content type length of the message */ + mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_delete_user_property to free the memory */ +} esp_mqtt5_event_property_t; + +/** + * MQTT5 protocol for user property + */ +typedef struct { + const char *key; /*!< Item key name */ + const char *value; /*!< Item value string */ +} esp_mqtt5_user_property_item_t; + +/** + * @brief Set MQTT5 client connect property configuration + * + * @param client mqtt client handle + * @param connect_property connect property + * + * @return ESP_ERR_NO_MEM if failed to allocate + * ESP_ERR_INVALID_ARG on wrong initialization + * ESP_FAIL on fail + * ESP_OK on success + */ +esp_err_t esp_mqtt5_client_set_connect_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_connection_property_config_t *connect_property); + +/** + * @brief Set MQTT5 client publish property configuration + * + * This API will not store the publish property, it is one-time configuration. + * Before call `esp_mqtt_client_publish` to publish data, call this API to set publish property if have + * + * @param client mqtt client handle + * @param property publish property + * + * @return ESP_ERR_INVALID_ARG on wrong initialization + * ESP_FAIL on fail + * ESP_OK on success + */ +esp_err_t esp_mqtt5_client_set_publish_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_publish_property_config_t *property); + +/** + * @brief Set MQTT5 client subscribe property configuration + * + * This API will not store the subscribe property, it is one-time configuration. + * Before call `esp_mqtt_client_subscribe` to subscribe topic, call this API to set subscribe property if have + * + * @param client mqtt client handle + * @param property subscribe property + * + * @return ESP_ERR_INVALID_ARG on wrong initialization + * ESP_FAIL on fail + * ESP_OK on success + */ +esp_err_t esp_mqtt5_client_set_subscribe_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_subscribe_property_config_t *property); + +/** + * @brief Set MQTT5 client unsubscribe property configuration + * + * This API will not store the unsubscribe property, it is one-time configuration. + * Before call `esp_mqtt_client_unsubscribe` to unsubscribe topic, call this API to set unsubscribe property if have + * + * @param client mqtt client handle + * @param property unsubscribe property + * + * @return ESP_ERR_INVALID_ARG on wrong initialization + * ESP_FAIL on fail + * ESP_OK on success + */ +esp_err_t esp_mqtt5_client_set_unsubscribe_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_unsubscribe_property_config_t *property); + +/** + * @brief Set MQTT5 client disconnect property configuration + * + * This API will not store the disconnect property, it is one-time configuration. + * Before call `esp_mqtt_client_disconnect` to disconnect connection, call this API to set disconnect property if have + * + * @param client mqtt client handle + * @param property disconnect property + * + * @return ESP_ERR_NO_MEM if failed to allocate + * ESP_ERR_INVALID_ARG on wrong initialization + * ESP_FAIL on fail + * ESP_OK on success + */ +esp_err_t esp_mqtt5_client_set_disconnect_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_disconnect_property_config_t *property); + +/** + * @brief Set MQTT5 client user property configuration + * + * This API will allocate memory for user_property, please DO NOT forget `call esp_mqtt5_client_delete_user_property` + * after you use it. + * Before publish data, subscribe topic, unsubscribe, etc, call this API to set user property if have + * + * @param user_property user_property handle + * @param item array of user property data (eg. {{"var","val"},{"other","2"}}) + * @param item_num number of items in user property data + * + * @return ESP_ERR_NO_MEM if failed to allocate + * ESP_FAIL on fail + * ESP_OK on success + */ +esp_err_t esp_mqtt5_client_set_user_property(mqtt5_user_property_handle_t *user_property, esp_mqtt5_user_property_item_t item[], uint8_t item_num); + +/** + * @brief Get MQTT5 client user property + * + * @param user_property user_property handle + * @param item point that store user property data + * @param item_num number of items in user property data + * + * This API can use with `esp_mqtt5_client_get_user_property_count` to get list count of user property. + * And malloc number of count item array memory to store the user property data. + * Please DO NOT forget the item memory, key and value point in item memory when get user property data successfully. + * + * @return ESP_ERR_NO_MEM if failed to allocate + * ESP_FAIL on fail + * ESP_OK on success + */ +esp_err_t esp_mqtt5_client_get_user_property(mqtt5_user_property_handle_t user_property, esp_mqtt5_user_property_item_t *item, uint8_t *item_num); + +/** + * @brief Get MQTT5 client user property list count + * + * @param user_property user_property handle + * @return user property list count + */ +uint8_t esp_mqtt5_client_get_user_property_count(mqtt5_user_property_handle_t user_property); + +/** + * @brief Free the user property list + * + * @param user_property user_property handle + * + * This API will free the memory in user property list and free user_property itself + */ +void esp_mqtt5_client_delete_user_property(mqtt5_user_property_handle_t user_property); +#ifdef __cplusplus +} +#endif //__cplusplus + +#endif diff --git a/include/mqtt_client.h b/include/mqtt_client.h index ca5722b..4c0e43b 100644 --- a/include/mqtt_client.h +++ b/include/mqtt_client.h @@ -12,6 +12,9 @@ #include #include "esp_err.h" #include "esp_event.h" +#ifdef CONFIG_MQTT_PROTOCOL_5 +#include "mqtt5_client.h" +#endif #ifdef __cplusplus extern "C" { @@ -118,142 +121,6 @@ typedef enum esp_mqtt_protocol_ver_t { MQTT_PROTOCOL_V_5, } esp_mqtt_protocol_ver_t; -#ifdef CONFIG_MQTT_PROTOCOL_5 -/** - * MQTT5 protocol error reason code, more details refer to MQTT5 protocol document section 2.4 - */ -enum mqtt5_error_reason_code { - MQTT5_UNSPECIFIED_ERROR = 0x80, - MQTT5_MALFORMED_PACKET = 0x81, - MQTT5_PROTOCOL_ERROR = 0x82, - MQTT5_IMPLEMENT_SPECIFIC_ERROR = 0x83, - MQTT5_UNSUPPORTED_PROTOCOL_VER = 0x84, - MQTT5_INVAILD_CLIENT_ID = 0x85, - MQTT5_BAD_USERNAME_OR_PWD = 0x86, - MQTT5_NOT_AUTHORIZED = 0x87, - MQTT5_SERVER_UNAVAILABLE = 0x88, - MQTT5_SERVER_BUSY = 0x89, - MQTT5_BANNED = 0x8A, - MQTT5_SERVER_SHUTTING_DOWN = 0x8B, - MQTT5_BAD_AUTH_METHOD = 0x8C, - MQTT5_KEEP_ALIVE_TIMEOUT = 0x8D, - MQTT5_SESSION_TAKEN_OVER = 0x8E, - MQTT5_TOPIC_FILTER_INVAILD = 0x8F, - MQTT5_TOPIC_NAME_INVAILD = 0x90, - MQTT5_PACKET_IDENTIFIER_IN_USE = 0x91, - MQTT5_PACKET_IDENTIFIER_NOT_FOUND = 0x92, - MQTT5_RECEIVE_MAXIMUM_EXCEEDED = 0x93, - MQTT5_TOPIC_ALIAS_INVAILD = 0x94, - MQTT5_PACKET_TOO_LARGE = 0x95, - MQTT5_MESSAGE_RATE_TOO_HIGH = 0x96, - MQTT5_QUOTA_EXCEEDED = 0x97, - MQTT5_ADMINISTRATIVE_ACTION = 0x98, - MQTT5_PAYLOAD_FORMAT_INVAILD = 0x99, - MQTT5_RETAIN_NOT_SUPPORT = 0x9A, - MQTT5_QOS_NOT_SUPPORT = 0x9B, - MQTT5_USE_ANOTHER_SERVER = 0x9C, - MQTT5_SERVER_MOVED = 0x9D, - MQTT5_SHARED_SUBSCR_NOT_SUPPORTED = 0x9E, - MQTT5_CONNECTION_RATE_EXCEEDED = 0x9F, - MQTT5_MAXIMUM_CONNECT_TIME = 0xA0, - MQTT5_SUBSCRIBE_IDENTIFIER_NOT_SUPPORT = 0xA1, - MQTT5_WILDCARD_SUBSCRIBE_NOT_SUPPORT = 0xA2, -}; - -/** - * MQTT5 user property handle - */ -typedef struct mqtt5_user_property_list_t *mqtt5_user_property_handle_t; - -/** - * MQTT5 protocol connect properties and will properties configuration, more details refer to MQTT5 protocol document section 3.1.2.11 and 3.3.2.3 - */ -typedef struct { - uint32_t session_expiry_interval; /*!< The interval time of session expiry */ - uint32_t maximum_packet_size; /*!< The maximum packet size that we can receive */ - uint16_t receive_maximum; /*!< The maximum pakcket count that we process concurrently */ - uint16_t topic_alias_maximum; /*!< The maximum topic alias that we support */ - bool request_resp_info; /*!< This value to request Server to return Response information */ - bool request_problem_info; /*!< This value to indicate whether the reason string or user properties are sent in case of failures */ - mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */ - uint32_t will_delay_interval; /*!< The time interval that server delays publishing will message */ - uint32_t message_expiry_interval; /*!< The time interval that message expiry */ - bool payload_format_indicator; /*!< This value is to indicator will message payload format */ - const char *content_type; /*!< This value is to indicator will message content type, use a MIME content type string */ - const char *response_topic; /*!< Topic name for a response message */ - const char *correlation_data; /*!< Binary data for receiver to match the response message */ - uint16_t correlation_data_len; /*!< The length of correlation data */ - mqtt5_user_property_handle_t will_user_property; /*!< The handle for will message user property, call function esp_mqtt5_client_set_user_property to set it */ -} esp_mqtt5_connection_property_config_t; - -/** - * MQTT5 protocol publish properties configuration, more details refer to MQTT5 protocol document section 3.3.2.3 - */ -typedef struct { - bool payload_format_indicator; /*!< This value is to indicator publish message payload format */ - uint32_t message_expiry_interval; /*!< The time interval that message expiry */ - uint16_t topic_alias; /*!< An interger value to identify the topic instead of using topic name string */ - const char *response_topic; /*!< Topic name for a response message */ - const char *correlation_data; /*!< Binary data for receiver to match the response message */ - uint16_t correlation_data_len; /*!< The length of correlation data */ - const char *content_type; /*!< This value is to indicator publish message content type, use a MIME content type string */ - mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */ -} esp_mqtt5_publish_property_config_t; - -/** - * MQTT5 protocol subscribe properties configuration, more details refer to MQTT5 protocol document section 3.8.2.1 - */ -typedef struct { - uint16_t subscribe_id; /*!< A variable byte represents the identifier of the subscription */ - bool no_local_flag; /*!< Subscription Option to allow that server publish message that client sent */ - bool retain_as_published_flag; /*!< Subscription Option to keep the retain flag as published option */ - uint8_t retain_handle; /*!< Subscription Option to handle retain option */ - bool is_share_subscribe; /*!< Whether subscribe is a shared subscription */ - const char *share_name; /*!< The name of shared subscription which is a part of $share/{share_name}/{topic} */ - mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */ -} esp_mqtt5_subscribe_property_config_t; - -/** - * MQTT5 protocol unsubscribe properties configuration, more details refer to MQTT5 protocol document section 3.10.2.1 - */ -typedef struct { - bool is_share_subscribe; /*!< Whether subscribe is a shared subscription */ - const char *share_name; /*!< The name of shared subscription which is a part of $share/{share_name}/{topic} */ - mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */ -} esp_mqtt5_unsubscribe_property_config_t; - -/** - * MQTT5 protocol disconnect properties configuration, more details refer to MQTT5 protocol document section 3.14.2.2 - */ -typedef struct { - uint32_t session_expiry_interval; /*!< The interval time of session expiry */ - uint8_t disconnect_reason; /*!< The reason that connection disconnet, refer to mqtt5_error_reason_code */ - mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */ -} esp_mqtt5_disconnect_property_config_t; - -/** - * MQTT5 protocol for event properties - */ -typedef struct { - bool payload_format_indicator; /*!< Payload format of the message */ - char *response_topic; /*!< Response topic of the message */ - int response_topic_len; /*!< Response topic length of the message */ - char *correlation_data; /*!< Correlation data of the message */ - uint16_t correlation_data_len; /*!< Correlation data length of the message */ - char *content_type; /*!< Content type of the message */ - int content_type_len; /*!< Content type length of the message */ - mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_delete_user_property to free the memory */ -} esp_mqtt5_event_property_t; - -/** - * MQTT5 protocol for user property - */ -typedef struct { - const char *key; /*!< Item key name */ - const char *value; /*!< Item value string */ -} esp_mqtt5_user_property_item_t; -#endif - /** * @brief MQTT error code structure to be passed as a contextual information into ERROR event * @@ -561,133 +428,6 @@ esp_err_t esp_mqtt_client_register_event(esp_mqtt_client_handle_t client, esp_mq * 0 on wrong initialization */ int esp_mqtt_client_get_outbox_size(esp_mqtt_client_handle_t client); - -#ifdef CONFIG_MQTT_PROTOCOL_5 -/** - * @brief Set MQTT5 client connect property configuration - * - * @param client mqtt client handle - * @param connect_property connect property - * - * @return ESP_ERR_NO_MEM if failed to allocate - * ESP_ERR_INVALID_ARG on wrong initialization - * ESP_FAIL on fail - * ESP_OK on success - */ -esp_err_t esp_mqtt5_client_set_connect_property(esp_mqtt_client_handle_t client, const esp_mqtt5_connection_property_config_t *connect_property); - -/** - * @brief Set MQTT5 client publish property configuration - * - * This API will not store the publish property, it is one-time configuration. - * Before call `esp_mqtt_client_publish` to publish data, call this API to set publish property if have - * - * @param client mqtt client handle - * @param property publish property - * - * @return ESP_ERR_INVALID_ARG on wrong initialization - * ESP_FAIL on fail - * ESP_OK on success - */ -esp_err_t esp_mqtt5_client_set_publish_property(esp_mqtt_client_handle_t client, const esp_mqtt5_publish_property_config_t *property); - -/** - * @brief Set MQTT5 client subscribe property configuration - * - * This API will not store the subscribe property, it is one-time configuration. - * Before call `esp_mqtt_client_subscribe` to subscribe topic, call this API to set subscribe property if have - * - * @param client mqtt client handle - * @param property subscribe property - * - * @return ESP_ERR_INVALID_ARG on wrong initialization - * ESP_FAIL on fail - * ESP_OK on success - */ -esp_err_t esp_mqtt5_client_set_subscribe_property(esp_mqtt_client_handle_t client, const esp_mqtt5_subscribe_property_config_t *property); - -/** - * @brief Set MQTT5 client unsubscribe property configuration - * - * This API will not store the unsubscribe property, it is one-time configuration. - * Before call `esp_mqtt_client_unsubscribe` to unsubscribe topic, call this API to set unsubscribe property if have - * - * @param client mqtt client handle - * @param property unsubscribe property - * - * @return ESP_ERR_INVALID_ARG on wrong initialization - * ESP_FAIL on fail - * ESP_OK on success - */ -esp_err_t esp_mqtt5_client_set_unsubscribe_property(esp_mqtt_client_handle_t client, const esp_mqtt5_unsubscribe_property_config_t *property); - -/** - * @brief Set MQTT5 client disconnect property configuration - * - * This API will not store the disconnect property, it is one-time configuration. - * Before call `esp_mqtt_client_disconnect` to disconnect connection, call this API to set disconnect property if have - * - * @param client mqtt client handle - * @param property disconnect property - * - * @return ESP_ERR_NO_MEM if failed to allocate - * ESP_ERR_INVALID_ARG on wrong initialization - * ESP_FAIL on fail - * ESP_OK on success - */ -esp_err_t esp_mqtt5_client_set_disconnect_property(esp_mqtt_client_handle_t client, const esp_mqtt5_disconnect_property_config_t *property); - -/** - * @brief Set MQTT5 client user property configuration - * - * This API will allocate memory for user_property, please DO NOT forget `call esp_mqtt5_client_delete_user_property` - * after you use it. - * Before publish data, subscribe topic, unsubscribe, etc, call this API to set user property if have - * - * @param user_property user_property handle - * @param item array of user property data (eg. {{"var","val"},{"other","2"}}) - * @param item_num number of items in user property data - * - * @return ESP_ERR_NO_MEM if failed to allocate - * ESP_FAIL on fail - * ESP_OK on success - */ -esp_err_t esp_mqtt5_client_set_user_property(mqtt5_user_property_handle_t *user_property, esp_mqtt5_user_property_item_t item[], uint8_t item_num); - -/** - * @brief Get MQTT5 client user property - * - * @param user_property user_property handle - * @param item point that store user property data - * @param item_num number of items in user property data - * - * This API can use with `esp_mqtt5_client_get_user_property_count` to get list count of user property. - * And malloc number of count item array memory to store the user property data. - * Please DO NOT forget the item memory, key and value point in item memory when get user property data successfully. - * - * @return ESP_ERR_NO_MEM if failed to allocate - * ESP_FAIL on fail - * ESP_OK on success - */ -esp_err_t esp_mqtt5_client_get_user_property(mqtt5_user_property_handle_t user_property, esp_mqtt5_user_property_item_t *item, uint8_t *item_num); - -/** - * @brief Get MQTT5 client user property list count - * - * @param user_property user_property handle - * @return user property list count - */ -uint8_t esp_mqtt5_client_get_user_property_count(mqtt5_user_property_handle_t user_property); - -/** - * @brief Free the user property list - * - * @param user_property user_property handle - * - * This API will free the memory in user property list and free user_property itself - */ -void esp_mqtt5_client_delete_user_property(mqtt5_user_property_handle_t user_property); -#endif #ifdef __cplusplus } #endif //__cplusplus diff --git a/lib/include/mqtt5_client_priv.h b/lib/include/mqtt5_client_priv.h new file mode 100644 index 0000000..e233429 --- /dev/null +++ b/lib/include/mqtt5_client_priv.h @@ -0,0 +1,54 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _MQTT5_CLIENT_PRIV_H_ +#define _MQTT5_CLIENT_PRIV_H_ + +#include "mqtt5_client.h" +#include "mqtt_client_priv.h" +#include "mqtt5_msg.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct mqtt5_topic_alias { + char *topic; + uint16_t topic_len; + uint16_t topic_alias; + STAILQ_ENTRY(mqtt5_topic_alias) next; +} mqtt5_topic_alias_t; +STAILQ_HEAD(mqtt5_topic_alias_list_t, mqtt5_topic_alias); +typedef struct mqtt5_topic_alias_list_t *mqtt5_topic_alias_handle_t; +typedef struct mqtt5_topic_alias *mqtt5_topic_alias_item_t; + +typedef struct { + esp_mqtt5_connection_property_storage_t connect_property_info; + esp_mqtt5_connection_will_property_storage_t will_property_info; + esp_mqtt5_connection_server_resp_property_t server_resp_property_info; + esp_mqtt5_disconnect_property_config_t disconnect_property_info; + const esp_mqtt5_publish_property_config_t *publish_property_info; + const esp_mqtt5_subscribe_property_config_t *subscribe_property_info; + const esp_mqtt5_unsubscribe_property_config_t *unsubscribe_property_info; + mqtt5_topic_alias_handle_t peer_topic_alias; +} mqtt5_config_storage_t; + +void esp_mqtt5_flow_control(esp_mqtt5_client_handle_t client); +void esp_mqtt5_parse_pubcomp(esp_mqtt5_client_handle_t client); +void esp_mqtt5_parse_puback(esp_mqtt5_client_handle_t client); +void esp_mqtt5_parse_unsuback(esp_mqtt5_client_handle_t client); +void esp_mqtt5_parse_suback(esp_mqtt5_client_handle_t client); +esp_err_t esp_mqtt5_parse_connack(esp_mqtt5_client_handle_t client, int *connect_rsp_code); +void esp_mqtt5_client_destory(esp_mqtt5_client_handle_t client); +esp_err_t esp_mqtt5_client_publish_check(esp_mqtt5_client_handle_t client, int qos, int retain); +esp_err_t esp_mqtt5_client_subscribe_check(esp_mqtt5_client_handle_t client, int qos); +esp_err_t esp_mqtt5_create_default_config(esp_mqtt5_client_handle_t client); +esp_err_t esp_mqtt5_get_publish_data(esp_mqtt5_client_handle_t client, uint8_t *msg_buf, size_t msg_read_len, char **msg_topic, size_t *msg_topic_len, char **msg_data, size_t *msg_data_len); +#ifdef __cplusplus +} +#endif //__cplusplus + +#endif \ No newline at end of file diff --git a/lib/include/mqtt5_msg.h b/lib/include/mqtt5_msg.h index 7e3b33a..c66c231 100644 --- a/lib/include/mqtt5_msg.h +++ b/lib/include/mqtt5_msg.h @@ -2,7 +2,7 @@ #define MQTT5_MSG_H #include #include - +#include "sys/queue.h" #include "mqtt_config.h" #include "mqtt_msg.h" #include "mqtt_client.h" diff --git a/lib/include/mqtt_client_priv.h b/lib/include/mqtt_client_priv.h new file mode 100644 index 0000000..29e405e --- /dev/null +++ b/lib/include/mqtt_client_priv.h @@ -0,0 +1,136 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _MQTT_CLIENT_PRIV_H_ +#define _MQTT_CLIENT_PRIV_H_ + +#include +#include +#include "esp_err.h" +#include "platform.h" + +#include "esp_event.h" +#include "mqtt_client.h" +#include "mqtt_msg.h" +#ifdef MQTT_PROTOCOL_5 +#include "mqtt5_client_priv.h" +#endif +#include "esp_transport.h" +#include "esp_transport_tcp.h" +#include "esp_transport_ssl.h" +#include "esp_transport_ws.h" +#include "esp_log.h" +#include "mqtt_outbox.h" +#include "freertos/event_groups.h" +#include +#include + +#include "mqtt_supported_features.h" + +/* using uri parser */ +#include "http_parser.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef MQTT_DISABLE_API_LOCKS +# define MQTT_API_LOCK(c) +# define MQTT_API_UNLOCK(c) +#else +# define MQTT_API_LOCK(c) xSemaphoreTakeRecursive(c->api_lock, portMAX_DELAY) +# define MQTT_API_UNLOCK(c) xSemaphoreGiveRecursive(c->api_lock) +#endif /* MQTT_USE_API_LOCKS */ + +typedef struct mqtt_state { + uint8_t *in_buffer; + uint8_t *out_buffer; + int in_buffer_length; + int out_buffer_length; + size_t message_length; + size_t in_buffer_read_len; + mqtt_message_t *outbound_message; + mqtt_connection_t mqtt_connection; + uint16_t pending_msg_id; + int pending_msg_type; + int pending_publish_qos; + int pending_msg_count; +} mqtt_state_t; + +typedef struct { + mqtt_event_callback_t event_handle; + esp_event_loop_handle_t event_loop_handle; + int task_stack; + int task_prio; + char *uri; + char *host; + char *path; + char *scheme; + int port; + bool auto_reconnect; + void *user_context; + int network_timeout_ms; + int refresh_connection_after_ms; + int reconnect_timeout_ms; + char **alpn_protos; + int num_alpn_protos; + char *clientkey_password; + int clientkey_password_len; + bool use_global_ca_store; + esp_err_t ((*crt_bundle_attach)(void *conf)); + const char *cacert_buf; + size_t cacert_bytes; + const char *clientcert_buf; + size_t clientcert_bytes; + const char *clientkey_buf; + size_t clientkey_bytes; + const struct psk_key_hint *psk_hint_key; + bool skip_cert_common_name_check; + bool use_secure_element; + void *ds_data; + int message_retransmit_timeout; +} mqtt_config_storage_t; + +typedef enum { + MQTT_STATE_INIT = 0, + MQTT_STATE_DISCONNECTED, + MQTT_STATE_CONNECTED, + MQTT_STATE_WAIT_RECONNECT, +} mqtt_client_state_t; + +struct esp_mqtt_client { + esp_transport_list_handle_t transport_list; + esp_transport_handle_t transport; + mqtt_config_storage_t *config; + mqtt_state_t mqtt_state; + mqtt_connect_info_t connect_info; + mqtt_client_state_t state; + uint64_t refresh_connection_tick; + int64_t keepalive_tick; + uint64_t reconnect_tick; +#ifdef MQTT_PROTOCOL_5 + mqtt5_config_storage_t *mqtt5_config; + uint16_t send_publish_packet_count; // This is for MQTT v5.0 flow control +#endif + int wait_timeout_ms; + int auto_reconnect; + esp_mqtt_event_t event; + bool run; + bool wait_for_ping_resp; + outbox_handle_t outbox; + EventGroupHandle_t status_bits; + SemaphoreHandle_t api_lock; + TaskHandle_t task_handle; +}; + +bool esp_mqtt_set_if_config(char const *const new_config, char **old_config); +void esp_mqtt_destroy_config(esp_mqtt_client_handle_t client); + +#ifdef __cplusplus +} +#endif //__cplusplus + +#endif diff --git a/lib/mqtt5_msg.c b/lib/mqtt5_msg.c index 2163c40..7af30ff 100644 --- a/lib/mqtt5_msg.c +++ b/lib/mqtt5_msg.c @@ -679,7 +679,7 @@ esp_err_t mqtt5_msg_parse_connack_property(uint8_t *buffer, size_t buffer_len, m continue; case MQTT5_PROPERTY_SERVER_KEEP_ALIVE: MQTT5_CONVERT_ONE_BYTE_TO_TWO(connection_info->keepalive, property[property_offset ++], property[property_offset ++]) - ESP_LOGD(TAG, "MQTT5_PROPERTY_SERVER_KEEP_ALIVE %d", connection_info->keepalive); + ESP_LOGD(TAG, "MQTT5_PROPERTY_SERVER_KEEP_ALIVE %lld", connection_info->keepalive); continue; case MQTT5_PROPERTY_RESP_INFO: if (resp_property->response_info) { diff --git a/mqtt5_client.c b/mqtt5_client.c new file mode 100644 index 0000000..be3a2ef --- /dev/null +++ b/mqtt5_client.c @@ -0,0 +1,757 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "mqtt_client_priv.h" +#include "esp_log.h" +#include + +static const char *TAG = "mqtt5_client"; + +static void esp_mqtt5_print_error_code(esp_mqtt5_client_handle_t client, int code); +static esp_err_t esp_mqtt5_client_update_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle, uint16_t topic_alias, char *topic, size_t topic_len); +static char *esp_mqtt5_client_get_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle, uint16_t topic_alias, size_t *topic_length); +static void esp_mqtt5_client_delete_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle); +static esp_err_t esp_mqtt5_user_property_copy(mqtt5_user_property_handle_t user_property_new, const mqtt5_user_property_handle_t user_property_old); + +void esp_mqtt5_flow_control(esp_mqtt5_client_handle_t client) +{ + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { + int msg_type = mqtt5_get_type(client->mqtt_state.outbound_message->data); + if (msg_type == MQTT_MSG_TYPE_PUBLISH) { + int msg_qos = mqtt5_get_qos(client->mqtt_state.outbound_message->data); + if (msg_qos > 0) { + client->send_publish_packet_count ++; + ESP_LOGD(TAG, "Sent (%d) qos > 0 publish packet without ack", client->send_publish_packet_count); + } + } + } +} + +void esp_mqtt5_parse_pubcomp(esp_mqtt5_client_handle_t client) +{ + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { + ESP_LOGI(TAG, "MQTT_MSG_TYPE_PUBCOMP return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len)); + size_t msg_data_len = client->mqtt_state.in_buffer_read_len; + client->event.data = mqtt5_get_pubcomp_data(client->mqtt_state.in_buffer, &msg_data_len, &client->event.property->user_property); + client->event.data_len = msg_data_len; + client->event.total_data_len = msg_data_len; + client->event.current_data_offset = 0; + } +} + +void esp_mqtt5_parse_puback(esp_mqtt5_client_handle_t client) +{ + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { + ESP_LOGI(TAG, "MQTT_MSG_TYPE_PUBACK return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len)); + size_t msg_data_len = client->mqtt_state.in_buffer_read_len; + client->event.data = mqtt5_get_puback_data(client->mqtt_state.in_buffer, &msg_data_len, &client->event.property->user_property); + client->event.data_len = msg_data_len; + client->event.total_data_len = msg_data_len; + client->event.current_data_offset = 0; + client->send_publish_packet_count --; + } +} + +void esp_mqtt5_parse_unsuback(esp_mqtt5_client_handle_t client) +{ + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { + ESP_LOGI(TAG, "MQTT_MSG_TYPE_UNSUBACK return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len)); + size_t msg_data_len = client->mqtt_state.in_buffer_read_len; + client->event.data = mqtt5_get_unsuback_data(client->mqtt_state.in_buffer, &msg_data_len, &client->event.property->user_property); + client->event.data_len = msg_data_len; + client->event.total_data_len = msg_data_len; + client->event.current_data_offset = 0; + } +} + +void esp_mqtt5_parse_suback(esp_mqtt5_client_handle_t client) +{ + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { + ESP_LOGI(TAG, "MQTT_MSG_TYPE_SUBACK return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len)); + } +} + +esp_err_t esp_mqtt5_parse_connack(esp_mqtt5_client_handle_t client, int *connect_rsp_code) +{ + size_t len = client->mqtt_state.in_buffer_read_len; + client->mqtt_state.in_buffer_read_len = 0; + uint8_t ack_flag = 0; + if (mqtt5_msg_parse_connack_property(client->mqtt_state.in_buffer, len, &client->connect_info, &client->mqtt5_config->connect_property_info, &client->mqtt5_config->server_resp_property_info, connect_rsp_code, &ack_flag, &client->event.property->user_property) != ESP_OK) { + ESP_LOGE(TAG, "Failed to parse CONNACK packet"); + return ESP_FAIL; + } + if (*connect_rsp_code == MQTT_CONNECTION_ACCEPTED) { + ESP_LOGD(TAG, "Connected"); + client->event.session_present = ack_flag & 0x01; + return ESP_OK; + } + esp_mqtt5_print_error_code(client, *connect_rsp_code); + return ESP_FAIL; +} + +esp_err_t esp_mqtt5_get_publish_data(esp_mqtt5_client_handle_t client, uint8_t *msg_buf, size_t msg_read_len, char **msg_topic, size_t *msg_topic_len, char **msg_data, size_t *msg_data_len) +{ + // get property + uint16_t property_len = 0; + esp_mqtt5_publish_resp_property_t property = {0}; + *msg_data = mqtt5_get_publish_property_payload(msg_buf, msg_read_len, msg_topic, msg_topic_len, &property, &property_len, msg_data_len, &client->event.property->user_property); + if (*msg_data_len == 0 || *msg_data == NULL) { + ESP_LOGE(TAG, "%s: mqtt5_get_publish_property_payload() failed", __func__); + return ESP_FAIL; + } + + if (property.topic_alias > client->mqtt5_config->connect_property_info.topic_alias_maximum) { + ESP_LOGE(TAG, "%s: Broker response topic alias %d is over the max topic alias %d", __func__, property.topic_alias, client->mqtt5_config->connect_property_info.topic_alias_maximum); + return ESP_FAIL; + } + + if (property.topic_alias) { + if (*msg_topic_len == 0) { + ESP_LOGI(TAG, "Publish topic is empty, use topic alias"); + *msg_topic = esp_mqtt5_client_get_topic_alias(client->mqtt5_config->peer_topic_alias, property.topic_alias, msg_topic_len); + if (!*msg_topic) { + ESP_LOGE(TAG, "%s: esp_mqtt5_client_get_topic_alias() failed", __func__); + return ESP_FAIL; + } + } else { + if (esp_mqtt5_client_update_topic_alias(client->mqtt5_config->peer_topic_alias, property.topic_alias, *msg_topic, *msg_topic_len) != ESP_OK) { + ESP_LOGE(TAG, "%s: esp_mqtt5_client_update_topic_alias() failed", __func__); + return ESP_FAIL; + } + } + } + + client->event.property->payload_format_indicator = property.payload_format_indicator; + client->event.property->response_topic = property.response_topic; + client->event.property->response_topic_len = property.response_topic_len; + client->event.property->correlation_data = property.correlation_data; + client->event.property->correlation_data_len = property.correlation_data_len; + client->event.property->content_type = property.content_type; + client->event.property->content_type_len = property.content_type_len; + return ESP_OK; +} + +esp_err_t esp_mqtt5_create_default_config(esp_mqtt5_client_handle_t client) +{ + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { + client->event.property = calloc(1, sizeof(esp_mqtt5_event_property_t)); + ESP_MEM_CHECK(TAG, client->event.property, return ESP_FAIL) + client->mqtt5_config = calloc(1, sizeof(mqtt5_config_storage_t)); + ESP_MEM_CHECK(TAG, client->mqtt5_config, return ESP_FAIL) + client->mqtt5_config->server_resp_property_info.max_qos = 2; + client->mqtt5_config->server_resp_property_info.retain_available = true; + client->mqtt5_config->server_resp_property_info.wildcard_subscribe_available = true; + client->mqtt5_config->server_resp_property_info.subscribe_identifiers_available = true; + client->mqtt5_config->server_resp_property_info.shared_subscribe_available = true; + client->mqtt5_config->server_resp_property_info.receive_maximum = 65535; + } + return ESP_OK; +} + +static void esp_mqtt5_print_error_code(esp_mqtt5_client_handle_t client, int code) +{ + switch (code) { + case MQTT5_UNSPECIFIED_ERROR: + ESP_LOGW(TAG, "Unspecified error"); + break; + case MQTT5_MALFORMED_PACKET: + ESP_LOGW(TAG, "Malformed Packet"); + break; + case MQTT5_PROTOCOL_ERROR: + ESP_LOGW(TAG, "Protocol Error"); + break; + case MQTT5_IMPLEMENT_SPECIFIC_ERROR: + ESP_LOGW(TAG, "Implementation specific error"); + break; + case MQTT5_UNSUPPORTED_PROTOCOL_VER: + ESP_LOGW(TAG, "Unsupported Protocol Version"); + break; + case MQTT5_INVAILD_CLIENT_ID: + ESP_LOGW(TAG, "Client Identifier not valid"); + break; + case MQTT5_BAD_USERNAME_OR_PWD: + ESP_LOGW(TAG, "Bad User Name or Password"); + break; + case MQTT5_NOT_AUTHORIZED: + ESP_LOGW(TAG, "Not authorized"); + break; + case MQTT5_SERVER_UNAVAILABLE: + ESP_LOGW(TAG, "Server unavailable"); + break; + case MQTT5_SERVER_BUSY: + ESP_LOGW(TAG, "Server busy"); + break; + case MQTT5_BANNED: + ESP_LOGW(TAG, "Banned"); + break; + case MQTT5_SERVER_SHUTTING_DOWN: + ESP_LOGW(TAG, "Server shutting down"); + break; + case MQTT5_BAD_AUTH_METHOD: + ESP_LOGW(TAG, "Bad authentication method"); + break; + case MQTT5_KEEP_ALIVE_TIMEOUT: + ESP_LOGW(TAG, "Keep Alive timeout"); + break; + case MQTT5_SESSION_TAKEN_OVER: + ESP_LOGW(TAG, "Session taken over"); + break; + case MQTT5_TOPIC_FILTER_INVAILD: + ESP_LOGW(TAG, "Topic Filter invalid"); + break; + case MQTT5_TOPIC_NAME_INVAILD: + ESP_LOGW(TAG, "Topic Name invalid"); + break; + case MQTT5_PACKET_IDENTIFIER_IN_USE: + ESP_LOGW(TAG, "Packet Identifier in use"); + break; + case MQTT5_PACKET_IDENTIFIER_NOT_FOUND: + ESP_LOGW(TAG, "Packet Identifier not found"); + break; + case MQTT5_RECEIVE_MAXIMUM_EXCEEDED: + ESP_LOGW(TAG, "Receive Maximum exceeded"); + break; + case MQTT5_TOPIC_ALIAS_INVAILD: + ESP_LOGW(TAG, "Topic Alias invalid"); + break; + case MQTT5_PACKET_TOO_LARGE: + ESP_LOGW(TAG, "Packet too large"); + break; + case MQTT5_MESSAGE_RATE_TOO_HIGH: + ESP_LOGW(TAG, "Message rate too high"); + break; + case MQTT5_QUOTA_EXCEEDED: + ESP_LOGW(TAG, "Quota exceeded"); + break; + case MQTT5_ADMINISTRATIVE_ACTION: + ESP_LOGW(TAG, "Administrative action"); + break; + case MQTT5_PAYLOAD_FORMAT_INVAILD: + ESP_LOGW(TAG, "Payload format invalid"); + break; + case MQTT5_RETAIN_NOT_SUPPORT: + ESP_LOGW(TAG, "Retain not supported"); + break; + case MQTT5_QOS_NOT_SUPPORT: + ESP_LOGW(TAG, "QoS not supported"); + break; + case MQTT5_USE_ANOTHER_SERVER: + ESP_LOGW(TAG, "Use another server"); + break; + case MQTT5_SERVER_MOVED: + ESP_LOGW(TAG, "Server moved"); + break; + case MQTT5_SHARED_SUBSCR_NOT_SUPPORTED: + ESP_LOGW(TAG, "Shared Subscriptions not supported"); + break; + case MQTT5_CONNECTION_RATE_EXCEEDED: + ESP_LOGW(TAG, "Connection rate exceeded"); + break; + case MQTT5_MAXIMUM_CONNECT_TIME: + ESP_LOGW(TAG, "Maximum connect time"); + break; + case MQTT5_SUBSCRIBE_IDENTIFIER_NOT_SUPPORT: + ESP_LOGW(TAG, "Subscription Identifiers not supported"); + break; + case MQTT5_WILDCARD_SUBSCRIBE_NOT_SUPPORT: + ESP_LOGW(TAG, "Wildcard Subscriptions not supported"); + break; + default: + ESP_LOGW(TAG, "Connection refused, Unknow reason"); + break; + } +} + +esp_err_t esp_mqtt5_client_subscribe_check(esp_mqtt5_client_handle_t client, int qos) +{ + /* Check Server support QoS level */ + if (client->mqtt5_config->server_resp_property_info.max_qos < qos) { + ESP_LOGE(TAG, "Server only support max QoS level %d", client->mqtt5_config->server_resp_property_info.max_qos); + return ESP_FAIL; + } + + return ESP_OK; +} + +esp_err_t esp_mqtt5_client_publish_check(esp_mqtt5_client_handle_t client, int qos, int retain) +{ + /* Check Server support QoS level */ + if (client->mqtt5_config->server_resp_property_info.max_qos < qos) { + ESP_LOGE(TAG, "Server only support max QoS level %d", client->mqtt5_config->server_resp_property_info.max_qos); + return ESP_FAIL; + } + + /* Check Server support RETAIN */ + if (!client->mqtt5_config->server_resp_property_info.retain_available && retain) { + ESP_LOGE(TAG, "Server not support retain"); + return ESP_FAIL; + } + + /* Flow control to check PUBLISH(No PUBACK or PUBCOMP received) packet sent count(Only record QoS1 and QoS2)*/ + if (client->send_publish_packet_count >= client->mqtt5_config->server_resp_property_info.receive_maximum) { + ESP_LOGE(TAG, "Client send more than %d QoS1 and QoS2 PUBLISH packet without no ack", client->mqtt5_config->server_resp_property_info.receive_maximum); + return ESP_FAIL; + } + + return ESP_OK; +} + +void esp_mqtt5_client_destory(esp_mqtt5_client_handle_t client) +{ + if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { + if (client->mqtt5_config) { + free(client->mqtt5_config->will_property_info.content_type); + free(client->mqtt5_config->will_property_info.response_topic); + free(client->mqtt5_config->will_property_info.correlation_data); + free(client->mqtt5_config->server_resp_property_info.response_info); + esp_mqtt5_client_delete_topic_alias(client->mqtt5_config->peer_topic_alias); + esp_mqtt5_client_delete_user_property(client->mqtt5_config->connect_property_info.user_property); + esp_mqtt5_client_delete_user_property(client->mqtt5_config->will_property_info.user_property); + esp_mqtt5_client_delete_user_property(client->mqtt5_config->disconnect_property_info.user_property); + free(client->mqtt5_config); + } + free(client->event.property); + } +} + +static void esp_mqtt5_client_delete_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle) +{ + if (topic_alias_handle) { + mqtt5_topic_alias_item_t item, tmp; + STAILQ_FOREACH_SAFE(item, topic_alias_handle, next, tmp) { + STAILQ_REMOVE(topic_alias_handle, item, mqtt5_topic_alias, next); + free(item->topic); + free(item); + } + free(topic_alias_handle); + } +} + +static esp_err_t esp_mqtt5_client_update_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle, uint16_t topic_alias, char *topic, size_t topic_len) +{ + mqtt5_topic_alias_item_t item; + bool found = false; + STAILQ_FOREACH(item, topic_alias_handle, next) { + if (item->topic_alias == topic_alias) { + found = true; + break; + } + } + if (found) { + if ((item->topic_len != topic_len) || strncmp(topic, item->topic, topic_len)) { + free(item->topic); + item->topic = calloc(1, topic_len); + ESP_MEM_CHECK(TAG, item->topic, return ESP_FAIL); + memcpy(item->topic, topic, topic_len); + item->topic_len = topic_len; + } + } else { + item = calloc(1, sizeof(mqtt5_topic_alias_t)); + ESP_MEM_CHECK(TAG, item, return ESP_FAIL); + item->topic_alias = topic_alias; + item->topic_len = topic_len; + item->topic = calloc(1, topic_len); + ESP_MEM_CHECK(TAG, item->topic, { + free(item); + return ESP_FAIL; + }); + memcpy(item->topic, topic, topic_len); + STAILQ_INSERT_TAIL(topic_alias_handle, item, next); + } + return ESP_OK; +} + +static char *esp_mqtt5_client_get_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle, uint16_t topic_alias, size_t *topic_length) +{ + mqtt5_topic_alias_item_t item; + STAILQ_FOREACH(item, topic_alias_handle, next) { + if (item->topic_alias == topic_alias) { + *topic_length = item->topic_len; + return item->topic; + } + } + *topic_length = 0; + return NULL; +} + +static esp_err_t esp_mqtt5_user_property_copy(mqtt5_user_property_handle_t user_property_new, const mqtt5_user_property_handle_t user_property_old) +{ + if (!user_property_new || !user_property_old) { + ESP_LOGE(TAG, "Input is NULL"); + return ESP_FAIL; + } + + mqtt5_user_property_item_t old_item, new_item; + STAILQ_FOREACH(old_item, user_property_old, next) { + new_item = calloc(1, sizeof(mqtt5_user_property_t)); + ESP_MEM_CHECK(TAG, new_item, return ESP_FAIL); + new_item->key = strdup(old_item->key); + ESP_MEM_CHECK(TAG, new_item->key, { + free(new_item); + return ESP_FAIL; + }); + new_item->value = strdup(old_item->value); + ESP_MEM_CHECK(TAG, new_item->value, { + free(new_item->key); + free(new_item); + return ESP_FAIL; + }); + STAILQ_INSERT_TAIL(user_property_new, new_item, next); + } + return ESP_OK; +} + +esp_err_t esp_mqtt5_client_set_publish_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_publish_property_config_t *property) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return ESP_ERR_INVALID_ARG; + } + MQTT_API_LOCK(client); + + /* Check protocol version */ + if(client->connect_info.protocol_ver != MQTT_PROTOCOL_V_5) { + ESP_LOGE(TAG, "MQTT protocol version is not v5"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + /* Check topic alias less than server maximum topic alias */ + if (property->topic_alias > client->mqtt5_config->server_resp_property_info.topic_alias_maximum) { + ESP_LOGE(TAG, "Topic alias %d is bigger than server support %d", property->topic_alias, client->mqtt5_config->server_resp_property_info.topic_alias_maximum); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + client->mqtt5_config->publish_property_info = property; + MQTT_API_UNLOCK(client); + return ESP_OK; +} + +esp_err_t esp_mqtt5_client_set_subscribe_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_subscribe_property_config_t *property) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return ESP_ERR_INVALID_ARG; + } + if (property->retain_handle > 2) { + ESP_LOGE(TAG, "retain_handle only support 0, 1, 2"); + return -1; + } + MQTT_API_LOCK(client); + + /* Check protocol version */ + if (client->connect_info.protocol_ver != MQTT_PROTOCOL_V_5) { + ESP_LOGE(TAG, "MQTT protocol version is not v5"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + if (property->is_share_subscribe) { + if (property->no_local_flag) { + // MQTT-3.8.3-4 not allow that No Local bit to 1 on a Shared Subscription + ESP_LOGE(TAG, "Protocol error that no local flag set on shared subscription"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + if (!client->mqtt5_config->server_resp_property_info.shared_subscribe_available) { + ESP_LOGE(TAG, "MQTT broker not support shared subscribe"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + if (!property->share_name || !strlen(property->share_name)) { + ESP_LOGE(TAG, "Share name can't be empty for shared subscribe"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + } + client->mqtt5_config->subscribe_property_info = property; + MQTT_API_UNLOCK(client); + return ESP_OK; +} + +esp_err_t esp_mqtt5_client_set_unsubscribe_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_unsubscribe_property_config_t *property) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return ESP_ERR_INVALID_ARG; + } + MQTT_API_LOCK(client); + + /* Check protocol version */ + if (client->connect_info.protocol_ver != MQTT_PROTOCOL_V_5) { + ESP_LOGE(TAG, "MQTT protocol version is not v5"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + if (property->is_share_subscribe) { + if (!client->mqtt5_config->server_resp_property_info.shared_subscribe_available) { + ESP_LOGE(TAG, "MQTT broker not support shared subscribe"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + if (!property->share_name || !strlen(property->share_name)) { + ESP_LOGE(TAG, "Share name can't be empty for shared subscribe"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + } + client->mqtt5_config->unsubscribe_property_info = property; + MQTT_API_UNLOCK(client); + return ESP_OK; +} + +esp_err_t esp_mqtt5_client_set_disconnect_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_disconnect_property_config_t *property) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return ESP_ERR_INVALID_ARG; + } + MQTT_API_LOCK(client); + + /* Check protocol version */ + if (client->connect_info.protocol_ver != MQTT_PROTOCOL_V_5) { + ESP_LOGE(TAG, "MQTT protocol version is not v5"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + if (property) { + if (property->session_expiry_interval) { + client->mqtt5_config->disconnect_property_info.session_expiry_interval = property->session_expiry_interval; + } + if (property->disconnect_reason) { + client->mqtt5_config->disconnect_property_info.disconnect_reason = property->disconnect_reason; + } + if (property->user_property) { + esp_mqtt5_client_delete_user_property(client->mqtt5_config->disconnect_property_info.user_property); + client->mqtt5_config->disconnect_property_info.user_property = calloc(1, sizeof(struct mqtt5_user_property_list_t)); + ESP_MEM_CHECK(TAG, client->mqtt5_config->disconnect_property_info.user_property, { + MQTT_API_UNLOCK(client); + return ESP_ERR_NO_MEM; + }); + STAILQ_INIT(client->mqtt5_config->disconnect_property_info.user_property); + if (esp_mqtt5_user_property_copy(client->mqtt5_config->disconnect_property_info.user_property, property->user_property) != ESP_OK) { + ESP_LOGE(TAG, "esp_mqtt5_user_property_copy fail"); + free(client->mqtt5_config->disconnect_property_info.user_property); + client->mqtt5_config->disconnect_property_info.user_property = NULL; + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + } + } + + MQTT_API_UNLOCK(client); + return ESP_OK; +} + +esp_err_t esp_mqtt5_client_set_connect_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_connection_property_config_t *connect_property) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return ESP_ERR_INVALID_ARG; + } + MQTT_API_LOCK(client); + + /* Check protocol version */ + if (client->connect_info.protocol_ver != MQTT_PROTOCOL_V_5) { + ESP_LOGE(TAG, "MQTT protocol version is not v5"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + if (connect_property) { + if (connect_property->session_expiry_interval) { + client->mqtt5_config->connect_property_info.session_expiry_interval = connect_property->session_expiry_interval; + } + if (connect_property->maximum_packet_size) { + if (connect_property->maximum_packet_size > client->mqtt_state.in_buffer_length) { + ESP_LOGW(TAG, "Connect maximum_packet_size property is over buffer_size(%d), Please first change it", client->mqtt_state.in_buffer_length); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } else { + client->mqtt5_config->connect_property_info.maximum_packet_size = connect_property->maximum_packet_size; + } + } else { + client->mqtt5_config->connect_property_info.maximum_packet_size = client->mqtt_state.in_buffer_length; + } + if (connect_property->receive_maximum) { + client->mqtt5_config->connect_property_info.receive_maximum = connect_property->receive_maximum; + } + if (connect_property->topic_alias_maximum) { + client->mqtt5_config->connect_property_info.topic_alias_maximum = connect_property->topic_alias_maximum; + if (!client->mqtt5_config->peer_topic_alias) { + client->mqtt5_config->peer_topic_alias = calloc(1, sizeof(struct mqtt5_topic_alias_list_t)); + ESP_MEM_CHECK(TAG, client->mqtt5_config->peer_topic_alias, goto _mqtt_set_config_failed); + STAILQ_INIT(client->mqtt5_config->peer_topic_alias); + } + } + if (connect_property->request_resp_info) { + client->mqtt5_config->connect_property_info.request_resp_info = connect_property->request_resp_info; + } + if (connect_property->request_problem_info) { + client->mqtt5_config->connect_property_info.request_problem_info = connect_property->request_problem_info; + } + if (connect_property->user_property) { + esp_mqtt5_client_delete_user_property(client->mqtt5_config->connect_property_info.user_property); + client->mqtt5_config->connect_property_info.user_property = calloc(1, sizeof(struct mqtt5_user_property_list_t)); + ESP_MEM_CHECK(TAG, client->mqtt5_config->connect_property_info.user_property, goto _mqtt_set_config_failed); + STAILQ_INIT(client->mqtt5_config->connect_property_info.user_property); + if (esp_mqtt5_user_property_copy(client->mqtt5_config->connect_property_info.user_property, connect_property->user_property) != ESP_OK) { + ESP_LOGE(TAG, "esp_mqtt5_user_property_copy fail"); + goto _mqtt_set_config_failed; + } + } + if (connect_property->payload_format_indicator) { + client->mqtt5_config->will_property_info.payload_format_indicator = connect_property->payload_format_indicator; + } + if (connect_property->will_delay_interval) { + client->mqtt5_config->will_property_info.will_delay_interval = connect_property->will_delay_interval; + } + if (connect_property->message_expiry_interval) { + client->mqtt5_config->will_property_info.message_expiry_interval = connect_property->message_expiry_interval; + } + ESP_MEM_CHECK(TAG, esp_mqtt_set_if_config(connect_property->content_type, &client->mqtt5_config->will_property_info.content_type), goto _mqtt_set_config_failed); + ESP_MEM_CHECK(TAG, esp_mqtt_set_if_config(connect_property->response_topic, &client->mqtt5_config->will_property_info.response_topic), goto _mqtt_set_config_failed); + if (connect_property->correlation_data && connect_property->correlation_data_len) { + free(client->mqtt5_config->will_property_info.correlation_data); + 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); + memcpy(client->mqtt5_config->will_property_info.correlation_data, connect_property->correlation_data, connect_property->correlation_data_len); + client->mqtt5_config->will_property_info.correlation_data_len = connect_property->correlation_data_len; + } + if (connect_property->will_user_property) { + esp_mqtt5_client_delete_user_property(client->mqtt5_config->will_property_info.user_property); + client->mqtt5_config->will_property_info.user_property = calloc(1, sizeof(struct mqtt5_user_property_list_t)); + ESP_MEM_CHECK(TAG, client->mqtt5_config->will_property_info.user_property, goto _mqtt_set_config_failed); + STAILQ_INIT(client->mqtt5_config->will_property_info.user_property); + if (esp_mqtt5_user_property_copy(client->mqtt5_config->will_property_info.user_property, connect_property->will_user_property) != ESP_OK) { + ESP_LOGE(TAG, "esp_mqtt5_user_property_copy fail"); + goto _mqtt_set_config_failed; + } + } + } + MQTT_API_UNLOCK(client); + return ESP_OK; +_mqtt_set_config_failed: + esp_mqtt_destroy_config(client); + MQTT_API_UNLOCK(client); + return ESP_ERR_NO_MEM; +} + +esp_err_t esp_mqtt5_client_set_user_property(mqtt5_user_property_handle_t *user_property, esp_mqtt5_user_property_item_t item[], uint8_t item_num) +{ + if (!item_num || !item) { + ESP_LOGE(TAG, "Input value is NULL"); + return ESP_FAIL; + } + + if (!*user_property) { + *user_property = calloc(1, sizeof(struct mqtt5_user_property_list_t)); + ESP_MEM_CHECK(TAG, *user_property, return ESP_ERR_NO_MEM); + STAILQ_INIT(*user_property); + } + + for (int i = 0; i < item_num; i ++) { + if (item[i].key && item[i].value) { + mqtt5_user_property_item_t user_property_item = calloc(1, sizeof(mqtt5_user_property_t)); + ESP_MEM_CHECK(TAG, user_property_item, goto err); + size_t key_len = strlen(item[i].key); + size_t value_len = strlen(item[i].value); + + user_property_item->key = calloc(1, key_len + 1); + ESP_MEM_CHECK(TAG, user_property_item->key, { + free(user_property_item); + goto err; + }); + memcpy(user_property_item->key, item[i].key, key_len); + user_property_item->key[key_len] = '\0'; + + user_property_item->value = calloc(1, value_len + 1); + ESP_MEM_CHECK(TAG, user_property_item->value, { + free(user_property_item->key); + free(user_property_item); + goto err; + }); + memcpy(user_property_item->value, item[i].value, value_len); + user_property_item->value[value_len] = '\0'; + + STAILQ_INSERT_TAIL(*user_property, user_property_item, next); + } + } + return ESP_OK; +err: + esp_mqtt5_client_delete_user_property(*user_property); + *user_property = NULL; + return ESP_ERR_NO_MEM; +} + +esp_err_t esp_mqtt5_client_get_user_property(mqtt5_user_property_handle_t user_property, esp_mqtt5_user_property_item_t *item, uint8_t *item_num) +{ + int i = 0, j = 0; + if (user_property && item && *item_num) { + mqtt5_user_property_item_t user_property_item; + uint8_t num = *item_num; + STAILQ_FOREACH(user_property_item, user_property, next) { + if (i < num) { + size_t item_key_len = strlen(user_property_item->key); + size_t item_value_len = strlen(user_property_item->value); + char *key = calloc(1, item_key_len + 1); + ESP_MEM_CHECK(TAG, key, goto err); + memcpy(key, user_property_item->key, item_key_len); + key[item_key_len] = '\0'; + char *value = calloc(1, item_value_len + 1); + ESP_MEM_CHECK(TAG, value, { + free(key); + goto err; + }); + memcpy(value, user_property_item->value, item_value_len); + value[item_value_len] = '\0'; + item[i].key = key; + item[i].value = value; + i ++; + } else { + break; + } + } + *item_num = i; + return ESP_OK; + } else { + ESP_LOGE(TAG, "Input value is NULL or item_num is 0"); + return ESP_FAIL; + } +err: + for (j = 0; j < i; j ++) { + if (item[j].key) { + free((char *)item[j].key); + } + if (item[j].value) { + free((char *)item[j].value); + } + } + return ESP_ERR_NO_MEM; +} + +uint8_t esp_mqtt5_client_get_user_property_count(mqtt5_user_property_handle_t user_property) +{ + uint8_t count = 0; + if (user_property) { + mqtt5_user_property_item_t item; + STAILQ_FOREACH(item, user_property, next) { + count ++; + } + } + return count; +} + +void esp_mqtt5_client_delete_user_property(mqtt5_user_property_handle_t user_property) +{ + if (user_property) { + mqtt5_user_property_item_t item, tmp; + STAILQ_FOREACH_SAFE(item, user_property, next, tmp) { + STAILQ_REMOVE(user_property, item, mqtt5_user_property, next); + free(item->key); + free(item->value); + free(item); + } + } + free(user_property); +} \ No newline at end of file diff --git a/mqtt_client.c b/mqtt_client.c index 8fd76af..8b3c49e 100644 --- a/mqtt_client.c +++ b/mqtt_client.c @@ -1,36 +1,4 @@ -#include -#include -#include "esp_err.h" -#include "platform.h" - -#include "esp_event.h" -#include "mqtt_client.h" -#include "mqtt_msg.h" -#ifdef MQTT_PROTOCOL_5 -#include "mqtt5_msg.h" -#endif -#include "esp_transport.h" -#include "esp_transport_tcp.h" -#include "esp_transport_ssl.h" -#include "esp_transport_ws.h" -#include "esp_log.h" -#include "mqtt_outbox.h" -#include "freertos/event_groups.h" -#include -#include - -#include "mqtt_supported_features.h" - -/* using uri parser */ -#include "http_parser.h" - -#ifdef MQTT_DISABLE_API_LOCKS -# define MQTT_API_LOCK(c) -# define MQTT_API_UNLOCK(c) -#else -# define MQTT_API_LOCK(c) xSemaphoreTakeRecursive(c->api_lock, portMAX_DELAY) -# define MQTT_API_UNLOCK(c) xSemaphoreGiveRecursive(c->api_lock) -#endif /* MQTT_USE_API_LOCKS */ +#include "mqtt_client_priv.h" _Static_assert(sizeof(uint64_t) == sizeof(outbox_tick_t), "mqtt-client tick type size different from outbox tick type"); #ifdef ESP_EVENT_ANY_ID @@ -52,134 +20,12 @@ ESP_EVENT_DEFINE_BASE(MQTT_EVENTS); #define MQTT_OVER_WS_SCHEME "ws" #define MQTT_OVER_WSS_SCHEME "wss" -typedef struct mqtt_state { - uint8_t *in_buffer; - uint8_t *out_buffer; - int in_buffer_length; - int out_buffer_length; - size_t message_length; - size_t in_buffer_read_len; - mqtt_message_t *outbound_message; - mqtt_connection_t mqtt_connection; - uint16_t pending_msg_id; - int pending_msg_type; - int pending_publish_qos; - int pending_msg_count; -} mqtt_state_t; - -typedef struct { - mqtt_event_callback_t event_handle; - esp_event_loop_handle_t event_loop_handle; - int task_stack; - int task_prio; - char *uri; - char *host; - char *path; - char *scheme; - int port; - bool auto_reconnect; - void *user_context; - int network_timeout_ms; - int refresh_connection_after_ms; - int reconnect_timeout_ms; - char **alpn_protos; - int num_alpn_protos; - char *clientkey_password; - int clientkey_password_len; - bool use_global_ca_store; - esp_err_t ((*crt_bundle_attach)(void *conf)); - const char *cacert_buf; - size_t cacert_bytes; - const char *clientcert_buf; - size_t clientcert_bytes; - const char *clientkey_buf; - size_t clientkey_bytes; - const struct psk_key_hint *psk_hint_key; - bool skip_cert_common_name_check; - bool use_secure_element; - void *ds_data; - int message_retransmit_timeout; -} mqtt_config_storage_t; - -typedef enum { - MQTT_STATE_INIT = 0, - MQTT_STATE_DISCONNECTED, - MQTT_STATE_CONNECTED, - MQTT_STATE_WAIT_RECONNECT, -} mqtt_client_state_t; - -#ifdef MQTT_PROTOCOL_5 -typedef struct mqtt5_topic_alias { - char *topic; - uint16_t topic_len; - uint16_t topic_alias; - STAILQ_ENTRY(mqtt5_topic_alias) next; -} mqtt5_topic_alias_t; -STAILQ_HEAD(mqtt5_topic_alias_list_t, mqtt5_topic_alias); -typedef struct mqtt5_topic_alias_list_t *mqtt5_topic_alias_handle_t; -typedef struct mqtt5_topic_alias *mqtt5_topic_alias_item_t; - -typedef struct { - esp_mqtt5_connection_property_storage_t connect_property_info; - esp_mqtt5_connection_will_property_storage_t will_property_info; - esp_mqtt5_connection_server_resp_property_t server_resp_property_info; - esp_mqtt5_disconnect_property_config_t disconnect_property_info; - const esp_mqtt5_publish_property_config_t *publish_property_info; - const esp_mqtt5_subscribe_property_config_t *subscribe_property_info; - const esp_mqtt5_unsubscribe_property_config_t *unsubscribe_property_info; - mqtt5_topic_alias_handle_t peer_topic_alias; -} mqtt5_config_storage_t; - -static void esp_mqtt5_flow_control(esp_mqtt_client_handle_t client); -static void esp_mqtt5_parse_pubcomp(esp_mqtt_client_handle_t client); -static void esp_mqtt5_parse_puback(esp_mqtt_client_handle_t client); -static void esp_mqtt5_parse_unsuback(esp_mqtt_client_handle_t client); -static void esp_mqtt5_parse_suback(esp_mqtt_client_handle_t client); -static esp_err_t esp_mqtt5_parse_connack(esp_mqtt_client_handle_t client, int *connect_rsp_code); -static void esp_mqtt5_client_destory(esp_mqtt_client_handle_t client); -static esp_err_t esp_mqtt5_client_publish_check(esp_mqtt_client_handle_t client, int qos, int retain); -static esp_err_t esp_mqtt5_client_subscribe_check(esp_mqtt_client_handle_t client, int qos); -static void esp_mqtt5_print_error_code(esp_mqtt_client_handle_t client, int code); -static esp_err_t esp_mqtt5_create_default_config(esp_mqtt_client_handle_t client); -static esp_err_t esp_mqtt5_get_publish_data(esp_mqtt_client_handle_t client, uint8_t *msg_buf, size_t msg_read_len, char **msg_topic, size_t *msg_topic_len, char **msg_data, size_t *msg_data_len); -static esp_err_t esp_mqtt5_client_update_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle, uint16_t topic_alias, char *topic, size_t topic_len); -static char *esp_mqtt5_client_get_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle, uint16_t topic_alias, size_t *topic_length); -static void esp_mqtt5_client_delete_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle); -static esp_err_t esp_mqtt5_user_property_copy(mqtt5_user_property_handle_t user_property_new, const mqtt5_user_property_handle_t user_property_old); -#endif - -struct esp_mqtt_client { - esp_transport_list_handle_t transport_list; - esp_transport_handle_t transport; - mqtt_config_storage_t *config; - mqtt_state_t mqtt_state; - mqtt_connect_info_t connect_info; - mqtt_client_state_t state; - uint64_t refresh_connection_tick; - int64_t keepalive_tick; - uint64_t reconnect_tick; -#ifdef MQTT_PROTOCOL_5 - mqtt5_config_storage_t *mqtt5_config; - uint16_t send_publish_packet_count; // This is for MQTT v5.0 flow control -#endif - int wait_timeout_ms; - int auto_reconnect; - esp_mqtt_event_t event; - bool run; - bool wait_for_ping_resp; - outbox_handle_t outbox; - EventGroupHandle_t status_bits; - SemaphoreHandle_t api_lock; - TaskHandle_t task_handle; -}; - const static int STOPPED_BIT = (1 << 0); const static int RECONNECT_BIT = (1 << 1); const static int DISCONNECT_BIT = (1 << 2); static esp_err_t esp_mqtt_dispatch_event(esp_mqtt_client_handle_t client); static esp_err_t esp_mqtt_dispatch_event_with_msgid(esp_mqtt_client_handle_t client); -static void esp_mqtt_destroy_config(esp_mqtt_client_handle_t client); static esp_err_t esp_mqtt_connect(esp_mqtt_client_handle_t client, int timeout_ms); static void esp_mqtt_abort_connection(esp_mqtt_client_handle_t client); static esp_err_t esp_mqtt_client_ping(esp_mqtt_client_handle_t client); @@ -409,7 +255,7 @@ static esp_err_t esp_mqtt_check_cfg_conflict(const mqtt_config_storage_t *cfg, c return ret; } -static bool set_if_config(char const *const new_config, char **old_config) +bool esp_mqtt_set_if_config(char const *const new_config, char **old_config) { if (new_config) { free(*old_config); @@ -529,14 +375,14 @@ esp_err_t esp_mqtt_set_config(esp_mqtt_client_handle_t client, const esp_mqtt_cl } err = ESP_ERR_NO_MEM; - ESP_MEM_CHECK(TAG, set_if_config(config->host, &client->config->host), goto _mqtt_set_config_failed); - ESP_MEM_CHECK(TAG, set_if_config(config->path, &client->config->path), goto _mqtt_set_config_failed); - ESP_MEM_CHECK(TAG, set_if_config(config->username, &client->connect_info.username), goto _mqtt_set_config_failed); - ESP_MEM_CHECK(TAG, set_if_config(config->password, &client->connect_info.password), goto _mqtt_set_config_failed); + ESP_MEM_CHECK(TAG, esp_mqtt_set_if_config(config->host, &client->config->host), goto _mqtt_set_config_failed); + ESP_MEM_CHECK(TAG, esp_mqtt_set_if_config(config->path, &client->config->path), goto _mqtt_set_config_failed); + ESP_MEM_CHECK(TAG, esp_mqtt_set_if_config(config->username, &client->connect_info.username), goto _mqtt_set_config_failed); + ESP_MEM_CHECK(TAG, esp_mqtt_set_if_config(config->password, &client->connect_info.password), goto _mqtt_set_config_failed); if (!config->set_null_client_id) { if (config->client_id) { - ESP_MEM_CHECK(TAG, set_if_config(config->client_id, &client->connect_info.client_id), goto _mqtt_set_config_failed); + ESP_MEM_CHECK(TAG, esp_mqtt_set_if_config(config->client_id, &client->connect_info.client_id), goto _mqtt_set_config_failed); } else if (client->connect_info.client_id == NULL) { client->connect_info.client_id = platform_create_id_string(); } @@ -544,8 +390,8 @@ esp_err_t esp_mqtt_set_config(esp_mqtt_client_handle_t client, const esp_mqtt_cl ESP_LOGD(TAG, "MQTT client_id=%s", client->connect_info.client_id); } - ESP_MEM_CHECK(TAG, set_if_config(config->uri, &client->config->uri), goto _mqtt_set_config_failed); - ESP_MEM_CHECK(TAG, set_if_config(config->lwt_topic, &client->connect_info.will_topic), goto _mqtt_set_config_failed); + ESP_MEM_CHECK(TAG, esp_mqtt_set_if_config(config->uri, &client->config->uri), goto _mqtt_set_config_failed); + ESP_MEM_CHECK(TAG, esp_mqtt_set_if_config(config->lwt_topic, &client->connect_info.will_topic), goto _mqtt_set_config_failed); if (config->lwt_msg_len && config->lwt_msg) { free(client->connect_info.will_message); @@ -716,7 +562,7 @@ _mqtt_set_config_failed: return err; } -static void esp_mqtt_destroy_config(esp_mqtt_client_handle_t client) +void esp_mqtt_destroy_config(esp_mqtt_client_handle_t client) { if (client->config == NULL) { return; @@ -2249,744 +2095,3 @@ int esp_mqtt_client_get_outbox_size(esp_mqtt_client_handle_t client) return outbox_size; } -#ifdef MQTT_PROTOCOL_5 -static void esp_mqtt5_flow_control(esp_mqtt_client_handle_t client) -{ - if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { - int msg_type = mqtt5_get_type(client->mqtt_state.outbound_message->data); - if (msg_type == MQTT_MSG_TYPE_PUBLISH) { - int msg_qos = mqtt5_get_qos(client->mqtt_state.outbound_message->data); - if (msg_qos > 0) { - client->send_publish_packet_count ++; - ESP_LOGD(TAG, "Sent (%d) qos > 0 publish packet without ack", client->send_publish_packet_count); - } - } - } -} - -static void esp_mqtt5_parse_pubcomp(esp_mqtt_client_handle_t client) -{ - if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { - ESP_LOGI(TAG, "MQTT_MSG_TYPE_PUBCOMP return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len)); - size_t msg_data_len = client->mqtt_state.in_buffer_read_len; - client->event.data = mqtt5_get_pubcomp_data(client->mqtt_state.in_buffer, &msg_data_len, &client->event.property->user_property); - client->event.data_len = msg_data_len; - client->event.total_data_len = msg_data_len; - client->event.current_data_offset = 0; - } -} - -static void esp_mqtt5_parse_puback(esp_mqtt_client_handle_t client) -{ - if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { - ESP_LOGI(TAG, "MQTT_MSG_TYPE_PUBACK return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len)); - size_t msg_data_len = client->mqtt_state.in_buffer_read_len; - client->event.data = mqtt5_get_puback_data(client->mqtt_state.in_buffer, &msg_data_len, &client->event.property->user_property); - client->event.data_len = msg_data_len; - client->event.total_data_len = msg_data_len; - client->event.current_data_offset = 0; - client->send_publish_packet_count --; - } -} - -static void esp_mqtt5_parse_unsuback(esp_mqtt_client_handle_t client) -{ - if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { - ESP_LOGI(TAG, "MQTT_MSG_TYPE_UNSUBACK return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len)); - size_t msg_data_len = client->mqtt_state.in_buffer_read_len; - client->event.data = mqtt5_get_unsuback_data(client->mqtt_state.in_buffer, &msg_data_len, &client->event.property->user_property); - client->event.data_len = msg_data_len; - client->event.total_data_len = msg_data_len; - client->event.current_data_offset = 0; - } -} - -static void esp_mqtt5_parse_suback(esp_mqtt_client_handle_t client) -{ - if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { - ESP_LOGI(TAG, "MQTT_MSG_TYPE_SUBACK return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len)); - } -} - -static esp_err_t esp_mqtt5_parse_connack(esp_mqtt_client_handle_t client, int *connect_rsp_code) -{ - size_t len = client->mqtt_state.in_buffer_read_len; - client->mqtt_state.in_buffer_read_len = 0; - uint8_t ack_flag = 0; - if (mqtt5_msg_parse_connack_property(client->mqtt_state.in_buffer, len, &client->connect_info, &client->mqtt5_config->connect_property_info, &client->mqtt5_config->server_resp_property_info, connect_rsp_code, &ack_flag, &client->event.property->user_property) != ESP_OK) { - ESP_LOGE(TAG, "Failed to parse CONNACK packet"); - return ESP_FAIL; - } - if (*connect_rsp_code == MQTT_CONNECTION_ACCEPTED) { - ESP_LOGD(TAG, "Connected"); - client->event.session_present = ack_flag & 0x01; - return ESP_OK; - } - esp_mqtt5_print_error_code(client, *connect_rsp_code); - return ESP_FAIL; -} - -static esp_err_t esp_mqtt5_get_publish_data(esp_mqtt_client_handle_t client, uint8_t *msg_buf, size_t msg_read_len, char **msg_topic, size_t *msg_topic_len, char **msg_data, size_t *msg_data_len) -{ - // get property - uint16_t property_len = 0; - esp_mqtt5_publish_resp_property_t property = {0}; - *msg_data = mqtt5_get_publish_property_payload(msg_buf, msg_read_len, msg_topic, msg_topic_len, &property, &property_len, msg_data_len, &client->event.property->user_property); - if (*msg_data_len == 0 || *msg_data == NULL) { - ESP_LOGE(TAG, "%s: mqtt5_get_publish_property_payload() failed", __func__); - return ESP_FAIL; - } - - if (property.topic_alias > client->mqtt5_config->connect_property_info.topic_alias_maximum) { - ESP_LOGE(TAG, "%s: Broker response topic alias %d is over the max topic alias %d", __func__, property.topic_alias, client->mqtt5_config->connect_property_info.topic_alias_maximum); - return ESP_FAIL; - } - - if (property.topic_alias) { - if (*msg_topic_len == 0) { - ESP_LOGI(TAG, "Publish topic is empty, use topic alias"); - *msg_topic = esp_mqtt5_client_get_topic_alias(client->mqtt5_config->peer_topic_alias, property.topic_alias, msg_topic_len); - if (!*msg_topic) { - ESP_LOGE(TAG, "%s: esp_mqtt5_client_get_topic_alias() failed", __func__); - return ESP_FAIL; - } - } else { - if (esp_mqtt5_client_update_topic_alias(client->mqtt5_config->peer_topic_alias, property.topic_alias, *msg_topic, *msg_topic_len) != ESP_OK) { - ESP_LOGE(TAG, "%s: esp_mqtt5_client_update_topic_alias() failed", __func__); - return ESP_FAIL; - } - } - } - - client->event.property->payload_format_indicator = property.payload_format_indicator; - client->event.property->response_topic = property.response_topic; - client->event.property->response_topic_len = property.response_topic_len; - client->event.property->correlation_data = property.correlation_data; - client->event.property->correlation_data_len = property.correlation_data_len; - client->event.property->content_type = property.content_type; - client->event.property->content_type_len = property.content_type_len; - return ESP_OK; -} - -static esp_err_t esp_mqtt5_create_default_config(esp_mqtt_client_handle_t client) -{ - if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { - client->event.property = calloc(1, sizeof(esp_mqtt5_event_property_t)); - ESP_MEM_CHECK(TAG, client->event.property, return ESP_FAIL) - client->mqtt5_config = calloc(1, sizeof(mqtt5_config_storage_t)); - ESP_MEM_CHECK(TAG, client->mqtt5_config, return ESP_FAIL) - client->mqtt5_config->server_resp_property_info.max_qos = 2; - client->mqtt5_config->server_resp_property_info.retain_available = true; - client->mqtt5_config->server_resp_property_info.wildcard_subscribe_available = true; - client->mqtt5_config->server_resp_property_info.subscribe_identifiers_available = true; - client->mqtt5_config->server_resp_property_info.shared_subscribe_available = true; - client->mqtt5_config->server_resp_property_info.receive_maximum = 65535; - } - return ESP_OK; -} - -static void esp_mqtt5_print_error_code(esp_mqtt_client_handle_t client, int code) -{ - switch (code) { - case MQTT5_UNSPECIFIED_ERROR: - ESP_LOGW(TAG, "Unspecified error"); - break; - case MQTT5_MALFORMED_PACKET: - ESP_LOGW(TAG, "Malformed Packet"); - break; - case MQTT5_PROTOCOL_ERROR: - ESP_LOGW(TAG, "Protocol Error"); - break; - case MQTT5_IMPLEMENT_SPECIFIC_ERROR: - ESP_LOGW(TAG, "Implementation specific error"); - break; - case MQTT5_UNSUPPORTED_PROTOCOL_VER: - ESP_LOGW(TAG, "Unsupported Protocol Version"); - break; - case MQTT5_INVAILD_CLIENT_ID: - ESP_LOGW(TAG, "Client Identifier not valid"); - break; - case MQTT5_BAD_USERNAME_OR_PWD: - ESP_LOGW(TAG, "Bad User Name or Password"); - break; - case MQTT5_NOT_AUTHORIZED: - ESP_LOGW(TAG, "Not authorized"); - break; - case MQTT5_SERVER_UNAVAILABLE: - ESP_LOGW(TAG, "Server unavailable"); - break; - case MQTT5_SERVER_BUSY: - ESP_LOGW(TAG, "Server busy"); - break; - case MQTT5_BANNED: - ESP_LOGW(TAG, "Banned"); - break; - case MQTT5_SERVER_SHUTTING_DOWN: - ESP_LOGW(TAG, "Server shutting down"); - break; - case MQTT5_BAD_AUTH_METHOD: - ESP_LOGW(TAG, "Bad authentication method"); - break; - case MQTT5_KEEP_ALIVE_TIMEOUT: - ESP_LOGW(TAG, "Keep Alive timeout"); - break; - case MQTT5_SESSION_TAKEN_OVER: - ESP_LOGW(TAG, "Session taken over"); - break; - case MQTT5_TOPIC_FILTER_INVAILD: - ESP_LOGW(TAG, "Topic Filter invalid"); - break; - case MQTT5_TOPIC_NAME_INVAILD: - ESP_LOGW(TAG, "Topic Name invalid"); - break; - case MQTT5_PACKET_IDENTIFIER_IN_USE: - ESP_LOGW(TAG, "Packet Identifier in use"); - break; - case MQTT5_PACKET_IDENTIFIER_NOT_FOUND: - ESP_LOGW(TAG, "Packet Identifier not found"); - break; - case MQTT5_RECEIVE_MAXIMUM_EXCEEDED: - ESP_LOGW(TAG, "Receive Maximum exceeded"); - break; - case MQTT5_TOPIC_ALIAS_INVAILD: - ESP_LOGW(TAG, "Topic Alias invalid"); - break; - case MQTT5_PACKET_TOO_LARGE: - ESP_LOGW(TAG, "Packet too large"); - break; - case MQTT5_MESSAGE_RATE_TOO_HIGH: - ESP_LOGW(TAG, "Message rate too high"); - break; - case MQTT5_QUOTA_EXCEEDED: - ESP_LOGW(TAG, "Quota exceeded"); - break; - case MQTT5_ADMINISTRATIVE_ACTION: - ESP_LOGW(TAG, "Administrative action"); - break; - case MQTT5_PAYLOAD_FORMAT_INVAILD: - ESP_LOGW(TAG, "Payload format invalid"); - break; - case MQTT5_RETAIN_NOT_SUPPORT: - ESP_LOGW(TAG, "Retain not supported"); - break; - case MQTT5_QOS_NOT_SUPPORT: - ESP_LOGW(TAG, "QoS not supported"); - break; - case MQTT5_USE_ANOTHER_SERVER: - ESP_LOGW(TAG, "Use another server"); - break; - case MQTT5_SERVER_MOVED: - ESP_LOGW(TAG, "Server moved"); - break; - case MQTT5_SHARED_SUBSCR_NOT_SUPPORTED: - ESP_LOGW(TAG, "Shared Subscriptions not supported"); - break; - case MQTT5_CONNECTION_RATE_EXCEEDED: - ESP_LOGW(TAG, "Connection rate exceeded"); - break; - case MQTT5_MAXIMUM_CONNECT_TIME: - ESP_LOGW(TAG, "Maximum connect time"); - break; - case MQTT5_SUBSCRIBE_IDENTIFIER_NOT_SUPPORT: - ESP_LOGW(TAG, "Subscription Identifiers not supported"); - break; - case MQTT5_WILDCARD_SUBSCRIBE_NOT_SUPPORT: - ESP_LOGW(TAG, "Wildcard Subscriptions not supported"); - break; - default: - ESP_LOGW(TAG, "Connection refused, Unknow reason"); - break; - } -} - -static esp_err_t esp_mqtt5_client_subscribe_check(esp_mqtt_client_handle_t client, int qos) -{ - /* Check Server support QoS level */ - if (client->mqtt5_config->server_resp_property_info.max_qos < qos) { - ESP_LOGE(TAG, "Server only support max QoS level %d", client->mqtt5_config->server_resp_property_info.max_qos); - return ESP_FAIL; - } - - return ESP_OK; -} - -static esp_err_t esp_mqtt5_client_publish_check(esp_mqtt_client_handle_t client, int qos, int retain) -{ - /* Check Server support QoS level */ - if (client->mqtt5_config->server_resp_property_info.max_qos < qos) { - ESP_LOGE(TAG, "Server only support max QoS level %d", client->mqtt5_config->server_resp_property_info.max_qos); - return ESP_FAIL; - } - - /* Check Server support RETAIN */ - if (!client->mqtt5_config->server_resp_property_info.retain_available && retain) { - ESP_LOGE(TAG, "Server not support retain"); - return ESP_FAIL; - } - - /* Flow control to check PUBLISH(No PUBACK or PUBCOMP received) packet sent count(Only record QoS1 and QoS2)*/ - if (client->send_publish_packet_count >= client->mqtt5_config->server_resp_property_info.receive_maximum) { - ESP_LOGE(TAG, "Client send more than %d QoS1 and QoS2 PUBLISH packet without no ack", client->mqtt5_config->server_resp_property_info.receive_maximum); - return ESP_FAIL; - } - - return ESP_OK; -} - -static void esp_mqtt5_client_destory(esp_mqtt_client_handle_t client) -{ - if (client->connect_info.protocol_ver == MQTT_PROTOCOL_V_5) { - if (client->mqtt5_config) { - free(client->mqtt5_config->will_property_info.content_type); - free(client->mqtt5_config->will_property_info.response_topic); - free(client->mqtt5_config->will_property_info.correlation_data); - free(client->mqtt5_config->server_resp_property_info.response_info); - esp_mqtt5_client_delete_topic_alias(client->mqtt5_config->peer_topic_alias); - esp_mqtt5_client_delete_user_property(client->mqtt5_config->connect_property_info.user_property); - esp_mqtt5_client_delete_user_property(client->mqtt5_config->will_property_info.user_property); - esp_mqtt5_client_delete_user_property(client->mqtt5_config->disconnect_property_info.user_property); - free(client->mqtt5_config); - } - free(client->event.property); - } -} - -static void esp_mqtt5_client_delete_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle) -{ - if (topic_alias_handle) { - mqtt5_topic_alias_item_t item, tmp; - STAILQ_FOREACH_SAFE(item, topic_alias_handle, next, tmp) { - STAILQ_REMOVE(topic_alias_handle, item, mqtt5_topic_alias, next); - free(item->topic); - free(item); - } - free(topic_alias_handle); - } -} - -static esp_err_t esp_mqtt5_client_update_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle, uint16_t topic_alias, char *topic, size_t topic_len) -{ - mqtt5_topic_alias_item_t item; - bool found = false; - STAILQ_FOREACH(item, topic_alias_handle, next) { - if (item->topic_alias == topic_alias) { - found = true; - break; - } - } - if (found) { - if ((item->topic_len != topic_len) || strncmp(topic, item->topic, topic_len)) { - free(item->topic); - item->topic = calloc(1, topic_len); - ESP_MEM_CHECK(TAG, item->topic, return ESP_FAIL); - memcpy(item->topic, topic, topic_len); - item->topic_len = topic_len; - } - } else { - item = calloc(1, sizeof(mqtt5_topic_alias_t)); - ESP_MEM_CHECK(TAG, item, return ESP_FAIL); - item->topic_alias = topic_alias; - item->topic_len = topic_len; - item->topic = calloc(1, topic_len); - ESP_MEM_CHECK(TAG, item->topic, { - free(item); - return ESP_FAIL; - }); - memcpy(item->topic, topic, topic_len); - STAILQ_INSERT_TAIL(topic_alias_handle, item, next); - } - return ESP_OK; -} - -static char *esp_mqtt5_client_get_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle, uint16_t topic_alias, size_t *topic_length) -{ - mqtt5_topic_alias_item_t item; - STAILQ_FOREACH(item, topic_alias_handle, next) { - if (item->topic_alias == topic_alias) { - *topic_length = item->topic_len; - return item->topic; - } - } - *topic_length = 0; - return NULL; -} - -static esp_err_t esp_mqtt5_user_property_copy(mqtt5_user_property_handle_t user_property_new, const mqtt5_user_property_handle_t user_property_old) -{ - if (!user_property_new || !user_property_old) { - ESP_LOGE(TAG, "Input is NULL"); - return ESP_FAIL; - } - - mqtt5_user_property_item_t old_item, new_item; - STAILQ_FOREACH(old_item, user_property_old, next) { - new_item = calloc(1, sizeof(mqtt5_user_property_t)); - ESP_MEM_CHECK(TAG, new_item, return ESP_FAIL); - new_item->key = strdup(old_item->key); - ESP_MEM_CHECK(TAG, new_item->key, { - free(new_item); - return ESP_FAIL; - }); - new_item->value = strdup(old_item->value); - ESP_MEM_CHECK(TAG, new_item->value, { - free(new_item->key); - free(new_item); - return ESP_FAIL; - }); - STAILQ_INSERT_TAIL(user_property_new, new_item, next); - } - return ESP_OK; -} - -esp_err_t esp_mqtt5_client_set_publish_property(esp_mqtt_client_handle_t client, const esp_mqtt5_publish_property_config_t *property) -{ - if (!client) { - ESP_LOGE(TAG, "Client was not initialized"); - return ESP_ERR_INVALID_ARG; - } - MQTT_API_LOCK(client); - - /* Check protocol version */ - if(client->connect_info.protocol_ver != MQTT_PROTOCOL_V_5) { - ESP_LOGE(TAG, "MQTT protocol version is not v5"); - MQTT_API_UNLOCK(client); - return ESP_FAIL; - } - /* Check topic alias less than server maximum topic alias */ - if (property->topic_alias > client->mqtt5_config->server_resp_property_info.topic_alias_maximum) { - ESP_LOGE(TAG, "Topic alias %d is bigger than server support %d", property->topic_alias, client->mqtt5_config->server_resp_property_info.topic_alias_maximum); - MQTT_API_UNLOCK(client); - return ESP_FAIL; - } - client->mqtt5_config->publish_property_info = property; - MQTT_API_UNLOCK(client); - return ESP_OK; -} - -esp_err_t esp_mqtt5_client_set_subscribe_property(esp_mqtt_client_handle_t client, const esp_mqtt5_subscribe_property_config_t *property) -{ - if (!client) { - ESP_LOGE(TAG, "Client was not initialized"); - return ESP_ERR_INVALID_ARG; - } - if (property->retain_handle > 2) { - ESP_LOGE(TAG, "retain_handle only support 0, 1, 2"); - return -1; - } - MQTT_API_LOCK(client); - - /* Check protocol version */ - if (client->connect_info.protocol_ver != MQTT_PROTOCOL_V_5) { - ESP_LOGE(TAG, "MQTT protocol version is not v5"); - MQTT_API_UNLOCK(client); - return ESP_FAIL; - } - if (property->is_share_subscribe) { - if (property->no_local_flag) { - // MQTT-3.8.3-4 not allow that No Local bit to 1 on a Shared Subscription - ESP_LOGE(TAG, "Protocol error that no local flag set on shared subscription"); - MQTT_API_UNLOCK(client); - return ESP_FAIL; - } - if (!client->mqtt5_config->server_resp_property_info.shared_subscribe_available) { - ESP_LOGE(TAG, "MQTT broker not support shared subscribe"); - MQTT_API_UNLOCK(client); - return ESP_FAIL; - } - if (!property->share_name || !strlen(property->share_name)) { - ESP_LOGE(TAG, "Share name can't be empty for shared subscribe"); - MQTT_API_UNLOCK(client); - return ESP_FAIL; - } - } - client->mqtt5_config->subscribe_property_info = property; - MQTT_API_UNLOCK(client); - return ESP_OK; -} - -esp_err_t esp_mqtt5_client_set_unsubscribe_property(esp_mqtt_client_handle_t client, const esp_mqtt5_unsubscribe_property_config_t *property) -{ - if (!client) { - ESP_LOGE(TAG, "Client was not initialized"); - return ESP_ERR_INVALID_ARG; - } - MQTT_API_LOCK(client); - - /* Check protocol version */ - if (client->connect_info.protocol_ver != MQTT_PROTOCOL_V_5) { - ESP_LOGE(TAG, "MQTT protocol version is not v5"); - MQTT_API_UNLOCK(client); - return ESP_FAIL; - } - if (property->is_share_subscribe) { - if (!client->mqtt5_config->server_resp_property_info.shared_subscribe_available) { - ESP_LOGE(TAG, "MQTT broker not support shared subscribe"); - MQTT_API_UNLOCK(client); - return ESP_FAIL; - } - if (!property->share_name || !strlen(property->share_name)) { - ESP_LOGE(TAG, "Share name can't be empty for shared subscribe"); - MQTT_API_UNLOCK(client); - return ESP_FAIL; - } - } - client->mqtt5_config->unsubscribe_property_info = property; - MQTT_API_UNLOCK(client); - return ESP_OK; -} - -esp_err_t esp_mqtt5_client_set_disconnect_property(esp_mqtt_client_handle_t client, const esp_mqtt5_disconnect_property_config_t *property) -{ - if (!client) { - ESP_LOGE(TAG, "Client was not initialized"); - return ESP_ERR_INVALID_ARG; - } - MQTT_API_LOCK(client); - - /* Check protocol version */ - if (client->connect_info.protocol_ver != MQTT_PROTOCOL_V_5) { - ESP_LOGE(TAG, "MQTT protocol version is not v5"); - MQTT_API_UNLOCK(client); - return ESP_FAIL; - } - if (property) { - if (property->session_expiry_interval) { - client->mqtt5_config->disconnect_property_info.session_expiry_interval = property->session_expiry_interval; - } - if (property->disconnect_reason) { - client->mqtt5_config->disconnect_property_info.disconnect_reason = property->disconnect_reason; - } - if (property->user_property) { - esp_mqtt5_client_delete_user_property(client->mqtt5_config->disconnect_property_info.user_property); - client->mqtt5_config->disconnect_property_info.user_property = calloc(1, sizeof(struct mqtt5_user_property_list_t)); - ESP_MEM_CHECK(TAG, client->mqtt5_config->disconnect_property_info.user_property, { - MQTT_API_UNLOCK(client); - return ESP_ERR_NO_MEM; - }); - STAILQ_INIT(client->mqtt5_config->disconnect_property_info.user_property); - if (esp_mqtt5_user_property_copy(client->mqtt5_config->disconnect_property_info.user_property, property->user_property) != ESP_OK) { - ESP_LOGE(TAG, "esp_mqtt5_user_property_copy fail"); - free(client->mqtt5_config->disconnect_property_info.user_property); - client->mqtt5_config->disconnect_property_info.user_property = NULL; - MQTT_API_UNLOCK(client); - return ESP_FAIL; - } - } - } - - MQTT_API_UNLOCK(client); - return ESP_OK; -} - -esp_err_t esp_mqtt5_client_set_connect_property(esp_mqtt_client_handle_t client, const esp_mqtt5_connection_property_config_t *connect_property) -{ - if (!client) { - ESP_LOGE(TAG, "Client was not initialized"); - return ESP_ERR_INVALID_ARG; - } - MQTT_API_LOCK(client); - - /* Check protocol version */ - if (client->connect_info.protocol_ver != MQTT_PROTOCOL_V_5) { - ESP_LOGE(TAG, "MQTT protocol version is not v5"); - MQTT_API_UNLOCK(client); - return ESP_FAIL; - } - if (connect_property) { - if (connect_property->session_expiry_interval) { - client->mqtt5_config->connect_property_info.session_expiry_interval = connect_property->session_expiry_interval; - } - if (connect_property->maximum_packet_size) { - if (connect_property->maximum_packet_size > client->mqtt_state.in_buffer_length) { - ESP_LOGW(TAG, "Connect maximum_packet_size property is over buffer_size(%d), Please first change it", client->mqtt_state.in_buffer_length); - MQTT_API_UNLOCK(client); - return ESP_FAIL; - } else { - client->mqtt5_config->connect_property_info.maximum_packet_size = connect_property->maximum_packet_size; - } - } else { - client->mqtt5_config->connect_property_info.maximum_packet_size = client->mqtt_state.in_buffer_length; - } - if (connect_property->receive_maximum) { - client->mqtt5_config->connect_property_info.receive_maximum = connect_property->receive_maximum; - } - if (connect_property->topic_alias_maximum) { - client->mqtt5_config->connect_property_info.topic_alias_maximum = connect_property->topic_alias_maximum; - if (!client->mqtt5_config->peer_topic_alias) { - client->mqtt5_config->peer_topic_alias = calloc(1, sizeof(struct mqtt5_topic_alias_list_t)); - ESP_MEM_CHECK(TAG, client->mqtt5_config->peer_topic_alias, goto _mqtt_set_config_failed); - STAILQ_INIT(client->mqtt5_config->peer_topic_alias); - } - } - if (connect_property->request_resp_info) { - client->mqtt5_config->connect_property_info.request_resp_info = connect_property->request_resp_info; - } - if (connect_property->request_problem_info) { - client->mqtt5_config->connect_property_info.request_problem_info = connect_property->request_problem_info; - } - if (connect_property->user_property) { - esp_mqtt5_client_delete_user_property(client->mqtt5_config->connect_property_info.user_property); - client->mqtt5_config->connect_property_info.user_property = calloc(1, sizeof(struct mqtt5_user_property_list_t)); - ESP_MEM_CHECK(TAG, client->mqtt5_config->connect_property_info.user_property, goto _mqtt_set_config_failed); - STAILQ_INIT(client->mqtt5_config->connect_property_info.user_property); - if (esp_mqtt5_user_property_copy(client->mqtt5_config->connect_property_info.user_property, connect_property->user_property) != ESP_OK) { - ESP_LOGE(TAG, "esp_mqtt5_user_property_copy fail"); - goto _mqtt_set_config_failed; - } - } - if (connect_property->payload_format_indicator) { - client->mqtt5_config->will_property_info.payload_format_indicator = connect_property->payload_format_indicator; - } - if (connect_property->will_delay_interval) { - client->mqtt5_config->will_property_info.will_delay_interval = connect_property->will_delay_interval; - } - if (connect_property->message_expiry_interval) { - client->mqtt5_config->will_property_info.message_expiry_interval = connect_property->message_expiry_interval; - } - ESP_MEM_CHECK(TAG, set_if_config(connect_property->content_type, &client->mqtt5_config->will_property_info.content_type), goto _mqtt_set_config_failed); - ESP_MEM_CHECK(TAG, set_if_config(connect_property->response_topic, &client->mqtt5_config->will_property_info.response_topic), goto _mqtt_set_config_failed); - if (connect_property->correlation_data && connect_property->correlation_data_len) { - free(client->mqtt5_config->will_property_info.correlation_data); - 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); - memcpy(client->mqtt5_config->will_property_info.correlation_data, connect_property->correlation_data, connect_property->correlation_data_len); - client->mqtt5_config->will_property_info.correlation_data_len = connect_property->correlation_data_len; - } - if (connect_property->will_user_property) { - esp_mqtt5_client_delete_user_property(client->mqtt5_config->will_property_info.user_property); - client->mqtt5_config->will_property_info.user_property = calloc(1, sizeof(struct mqtt5_user_property_list_t)); - ESP_MEM_CHECK(TAG, client->mqtt5_config->will_property_info.user_property, goto _mqtt_set_config_failed); - STAILQ_INIT(client->mqtt5_config->will_property_info.user_property); - if (esp_mqtt5_user_property_copy(client->mqtt5_config->will_property_info.user_property, connect_property->will_user_property) != ESP_OK) { - ESP_LOGE(TAG, "esp_mqtt5_user_property_copy fail"); - goto _mqtt_set_config_failed; - } - } - } - MQTT_API_UNLOCK(client); - return ESP_OK; -_mqtt_set_config_failed: - esp_mqtt_destroy_config(client); - MQTT_API_UNLOCK(client); - return ESP_ERR_NO_MEM; -} - -esp_err_t esp_mqtt5_client_set_user_property(mqtt5_user_property_handle_t *user_property, esp_mqtt5_user_property_item_t item[], uint8_t item_num) -{ - if (!item_num || !item) { - ESP_LOGE(TAG, "Input value is NULL"); - return ESP_FAIL; - } - - if (!*user_property) { - *user_property = calloc(1, sizeof(struct mqtt5_user_property_list_t)); - ESP_MEM_CHECK(TAG, *user_property, return ESP_ERR_NO_MEM); - STAILQ_INIT(*user_property); - } - - for (int i = 0; i < item_num; i ++) { - if (item[i].key && item[i].value) { - mqtt5_user_property_item_t user_property_item = calloc(1, sizeof(mqtt5_user_property_t)); - ESP_MEM_CHECK(TAG, user_property_item, goto err); - size_t key_len = strlen(item[i].key); - size_t value_len = strlen(item[i].value); - - user_property_item->key = calloc(1, key_len + 1); - ESP_MEM_CHECK(TAG, user_property_item->key, { - free(user_property_item); - goto err; - }); - memcpy(user_property_item->key, item[i].key, key_len); - user_property_item->key[key_len] = '\0'; - - user_property_item->value = calloc(1, value_len + 1); - ESP_MEM_CHECK(TAG, user_property_item->value, { - free(user_property_item->key); - free(user_property_item); - goto err; - }); - memcpy(user_property_item->value, item[i].value, value_len); - user_property_item->value[value_len] = '\0'; - - STAILQ_INSERT_TAIL(*user_property, user_property_item, next); - } - } - return ESP_OK; -err: - esp_mqtt5_client_delete_user_property(*user_property); - *user_property = NULL; - return ESP_ERR_NO_MEM; -} - -esp_err_t esp_mqtt5_client_get_user_property(mqtt5_user_property_handle_t user_property, esp_mqtt5_user_property_item_t *item, uint8_t *item_num) -{ - int i = 0, j = 0; - if (user_property && item && *item_num) { - mqtt5_user_property_item_t user_property_item; - uint8_t num = *item_num; - STAILQ_FOREACH(user_property_item, user_property, next) { - if (i < num) { - size_t item_key_len = strlen(user_property_item->key); - size_t item_value_len = strlen(user_property_item->value); - char *key = calloc(1, item_key_len + 1); - ESP_MEM_CHECK(TAG, key, goto err); - memcpy(key, user_property_item->key, item_key_len); - key[item_key_len] = '\0'; - char *value = calloc(1, item_value_len + 1); - ESP_MEM_CHECK(TAG, value, { - free(key); - goto err; - }); - memcpy(value, user_property_item->value, item_value_len); - value[item_value_len] = '\0'; - item[i].key = key; - item[i].value = value; - i ++; - } else { - break; - } - } - *item_num = i; - return ESP_OK; - } else { - ESP_LOGE(TAG, "Input value is NULL or item_num is 0"); - return ESP_FAIL; - } -err: - for (j = 0; j < i; j ++) { - if (item[j].key) { - free((char *)item[j].key); - } - if (item[j].value) { - free((char *)item[j].value); - } - } - return ESP_ERR_NO_MEM; -} - -uint8_t esp_mqtt5_client_get_user_property_count(mqtt5_user_property_handle_t user_property) -{ - uint8_t count = 0; - if (user_property) { - mqtt5_user_property_item_t item; - STAILQ_FOREACH(item, user_property, next) { - count ++; - } - } - return count; -} - -void esp_mqtt5_client_delete_user_property(mqtt5_user_property_handle_t user_property) -{ - if (user_property) { - mqtt5_user_property_item_t item, tmp; - STAILQ_FOREACH_SAFE(item, user_property, next, tmp) { - STAILQ_REMOVE(user_property, item, mqtt5_user_property, next); - free(item->key); - free(item->value); - free(item); - } - } - free(user_property); -} -#endif