diff --git a/doc/connectivity/networking/api/mqtt.rst b/doc/connectivity/networking/api/mqtt.rst index 79da5abe57be..3d222d53816a 100644 --- a/doc/connectivity/networking/api/mqtt.rst +++ b/doc/connectivity/networking/api/mqtt.rst @@ -18,7 +18,7 @@ For more information about the protocol itself, see http://mqtt.org/. Zephyr provides an MQTT client library built on top of BSD sockets API. The library can be enabled with :kconfig:option:`CONFIG_MQTT_LIB` Kconfig option and is configurable at a per-client basis, with support for MQTT versions -3.1.0 and 3.1.1. The Zephyr MQTT implementation can be used with either plain +3.1.0, 3.1.1 and 5.0. The Zephyr MQTT implementation can be used with either plain sockets communicating over TCP, or with secure sockets communicating over TLS. See :ref:`bsd_sockets_interface` for more information about Zephyr sockets. diff --git a/include/zephyr/net/mqtt.h b/include/zephyr/net/mqtt.h index 9c09069b1075..b06c743201b3 100644 --- a/include/zephyr/net/mqtt.h +++ b/include/zephyr/net/mqtt.h @@ -79,12 +79,18 @@ enum mqtt_evt_type { /** Ping Response from server. */ MQTT_EVT_PINGRESP, + +#if defined(CONFIG_MQTT_VERSION_5_0) || defined(__DOXYGEN__) + /** Authentication packet received from server. MQTT 5.0 only. */ + MQTT_EVT_AUTH, +#endif /* CONFIG_MQTT_VERSION_5_0 */ }; /** @brief MQTT version protocol level. */ enum mqtt_version { MQTT_VERSION_3_1_0 = 3, /**< Protocol level for 3.1.0. */ - MQTT_VERSION_3_1_1 = 4 /**< Protocol level for 3.1.1. */ + MQTT_VERSION_3_1_1 = 4, /**< Protocol level for 3.1.1. */ + MQTT_VERSION_5_0 = 5, /**< Protocol level for 5.0. */ }; /** @brief MQTT Quality of Service types. */ @@ -106,7 +112,7 @@ enum mqtt_qos { MQTT_QOS_2_EXACTLY_ONCE = 0x02 }; -/** @brief MQTT CONNACK return codes. */ +/** @brief MQTT 3.1 CONNACK return codes. */ enum mqtt_conn_return_code { /** Connection accepted. */ MQTT_CONNECTION_ACCEPTED = 0x00, @@ -133,6 +139,32 @@ enum mqtt_conn_return_code { MQTT_NOT_AUTHORIZED = 0x05 }; +/** @brief MQTT 5.0 CONNACK reason codes (MQTT 5.0, chapter 3.2.2.2). */ +enum mqtt_connack_reason_code { + MQTT_CONNACK_SUCCESS = 0, + MQTT_CONNACK_UNSPECIFIED_ERROR = 128, + MQTT_CONNACK_MALFORMED_PACKET = 129, + MQTT_CONNACK_PROTOCOL_ERROR = 130, + MQTT_CONNACK_IMPL_SPECIFIC_ERROR = 131, + MQTT_CONNACK_UNSUPPORTED_PROTO_ERROR = 132, + MQTT_CONNACK_CLIENT_ID_NOT_VALID = 133, + MQTT_CONNACK_BAD_USERNAME_OR_PASS = 134, + MQTT_CONNACK_NOT_AUTHORIZED = 135, + MQTT_CONNACK_SERVER_UNAVAILABLE = 136, + MQTT_CONNACK_SERVER_BUSY = 137, + MQTT_CONNACK_BANNED = 138, + MQTT_CONNACK_BAD_AUTH_METHOD = 140, + MQTT_CONNACK_TOPIC_NAME_INVALID = 144, + MQTT_CONNACK_PACKET_TOO_LARGE = 149, + MQTT_CONNACK_QUOTA_EXCEEDED = 151, + MQTT_CONNACK_PAYLOAD_FORMAT_INVALID = 153, + MQTT_CONNACK_RETAIN_NOT_SUPPORTED = 154, + MQTT_CONNACK_QOS_NOT_SUPPORTED = 155, + MQTT_CONNACK_USE_ANOTHER_SERVER = 156, + MQTT_CONNACK_SERVER_MOVED = 157, + MQTT_CONNACK_CONNECTION_RATE_EXCEEDED = 159, +}; + /** @brief MQTT SUBACK return codes. */ enum mqtt_suback_return_code { /** Subscription with QoS 0 succeeded. */ @@ -148,6 +180,46 @@ enum mqtt_suback_return_code { MQTT_SUBACK_FAILURE = 0x80 }; +/** @brief MQTT Disconnect reason codes (MQTT 5.0, chapter 3.14.2.1). */ +enum mqtt_disconnect_reason_code { + MQTT_DISCONNECT_NORMAL = 0, + MQTT_DISCONNECT_WITH_WILL_MSG = 4, + MQTT_DISCONNECT_UNSPECIFIED_ERROR = 128, + MQTT_DISCONNECT_MALFORMED_PACKET = 129, + MQTT_DISCONNECT_PROTOCOL_ERROR = 130, + MQTT_DISCONNECT_IMPL_SPECIFIC_ERROR = 131, + MQTT_DISCONNECT_NOT_AUTHORIZED = 135, + MQTT_DISCONNECT_SERVER_BUSY = 137, + MQTT_DISCONNECT_SERVER_SHUTTING_DOWN = 139, + MQTT_DISCONNECT_KEEP_ALIVE_TIMEOUT = 141, + MQTT_DISCONNECT_SESSION_TAKE_OVER = 142, + MQTT_DISCONNECT_TOPIC_FILTER_INVALID = 143, + MQTT_DISCONNECT_TOPIC_NAME_INVALID = 144, + MQTT_DISCONNECT_RECV_MAX_EXCEEDED = 147, + MQTT_DISCONNECT_TOPIC_ALIAS_INVALID = 148, + MQTT_DISCONNECT_PACKET_TOO_LARGE = 149, + MQTT_DISCONNECT_MESSAGE_RATE_TOO_HIGH = 150, + MQTT_DISCONNECT_QUOTA_EXCEEDED = 151, + MQTT_DISCONNECT_ADMIN_ACTION = 152, + MQTT_DISCONNECT_PAYLOAD_FORMAT_INVALID = 153, + MQTT_DISCONNECT_RETAIN_NOT_SUPPORTED = 154, + MQTT_DISCONNECT_QOS_NOT_SUPPORTED = 155, + MQTT_DISCONNECT_USE_ANOTHER_SERVER = 156, + MQTT_DISCONNECT_SERVER_MOVED = 157, + MQTT_DISCONNECT_SHARED_SUB_NOT_SUPPORTED = 158, + MQTT_DISCONNECT_CONNECTION_RATE_EXCEEDED = 159, + MQTT_DISCONNECT_MAX_CONNECT_TIME = 160, + MQTT_DISCONNECT_SUB_ID_NOT_SUPPORTED = 161, + MQTT_DISCONNECT_WILDCARD_SUB_NOT_SUPPORTED = 162, +}; + +/** @brief MQTT Authenticate reason codes (MQTT 5.0, chapter 3.15.2.1). */ +enum mqtt_auth_reason_code { + MQTT_AUTH_SUCCESS = 0, + MQTT_AUTH_CONTINUE_AUTHENTICATION = 24, + MQTT_AUTH_RE_AUTHENTICATE = 25, +}; + /** @brief Abstracts UTF-8 encoded strings. */ struct mqtt_utf8 { const uint8_t *utf8; /**< Pointer to UTF-8 string. */ @@ -172,6 +244,17 @@ struct mqtt_binstr { uint32_t len; /**< Length of binary stream. */ }; +/** @brief Abstracts aliased topic. */ +struct mqtt_topic_alias { +#if defined(CONFIG_MQTT_VERSION_5_0) || defined(__DOXYGEN__) + /** UTF-8 encoded topic name. */ + uint8_t topic_buf[CONFIG_MQTT_TOPIC_ALIAS_STRING_MAX]; + + /** Topic name size. */ + uint16_t topic_size; +#endif /* CONFIG_MQTT_VERSION_5_0 */ +}; + /** @brief Abstracts MQTT UTF-8 encoded topic that can be subscribed * to or published. */ @@ -185,6 +268,13 @@ struct mqtt_topic { uint8_t qos; }; +/** @brief Abstracts MQTT UTF-8 encoded string pair. + */ +struct mqtt_utf8_pair { + struct mqtt_utf8 name; + struct mqtt_utf8 value; +}; + /** @brief Parameters for a publish message. */ struct mqtt_publish_message { struct mqtt_topic topic; /**< Topic on which data was published. */ @@ -201,48 +291,210 @@ struct mqtt_connack_param { /** The appropriate non-zero Connect return code indicates if the Server * is unable to process a connection request for some reason. + * MQTT 3.1 - Return codes specified in @ref mqtt_conn_return_code + * MQTT 5.0 - Reason codes specified in @ref mqtt_connack_reason_code */ - enum mqtt_conn_return_code return_code; + uint8_t return_code; + +#if defined(CONFIG_MQTT_VERSION_5_0) || defined(__DOXYGEN__) + struct { + /** MQTT 5.0, chapter 3.2.2.3.10 User Property. */ + struct mqtt_utf8_pair user_prop[CONFIG_MQTT_USER_PROPERTIES_MAX]; + + /** MQTT 5.0, chapter 3.2.2.3.7 Assigned Client Identifier. */ + struct mqtt_utf8 assigned_client_id; + + /** MQTT 5.0, chapter 3.2.2.3.9 Reason String. */ + struct mqtt_utf8 reason_string; + + /** MQTT 5.0, chapter 3.2.2.3.15 Response Information. */ + struct mqtt_utf8 response_information; + + /** MQTT 5.0, chapter 3.2.2.3.16 Server Reference. */ + struct mqtt_utf8 server_reference; + + /** MQTT 5.0, chapter 3.2.2.3.17 Authentication Method. */ + struct mqtt_utf8 auth_method; + + /** MQTT 5.0, chapter 3.2.2.3.18 Authentication Data. */ + struct mqtt_binstr auth_data; + + /** MQTT 5.0, chapter 3.2.2.3.2 Session Expiry Interval. */ + uint32_t session_expiry_interval; + + /** MQTT 5.0, chapter 3.2.2.3.6 Maximum Packet Size. */ + uint32_t maximum_packet_size; + + /** MQTT 5.0, chapter 3.3.2.3.3 Receive Maximum. */ + uint16_t receive_maximum; + + /** MQTT 5.0, chapter 3.2.2.3.8 Topic Alias Maximum. */ + uint16_t topic_alias_maximum; + + /** MQTT 5.0, chapter 3.2.2.3.14 Server Keep Alive. */ + uint16_t server_keep_alive; + + /** MQTT 5.0, chapter 3.2.2.3.4 Maximum QoS. */ + uint8_t maximum_qos; + + /** MQTT 5.0, chapter 3.2.2.3.5 Retain Available. */ + uint8_t retain_available; + + /** MQTT 5.0, chapter 3.2.2.3.11 Wildcard Subscription Available. */ + uint8_t wildcard_sub_available; + + /** MQTT 5.0, chapter 3.2.2.3.12 Subscription Identifiers Available. */ + uint8_t subscription_ids_available; + + /** MQTT 5.0, chapter 3.2.2.3.13 Shared Subscription Available. */ + uint8_t shared_sub_available; + + /** Flags indicating whether given property was present in received packet. */ + struct { + /** Session Expiry Interval property was present. */ + bool has_session_expiry_interval; + /** Receive Maximum property was present. */ + bool has_receive_maximum; + /** Maximum QoS property was present. */ + bool has_maximum_qos; + /** Retain Available property was present. */ + bool has_retain_available; + /** Maximum Packet Size property was present. */ + bool has_maximum_packet_size; + /** Assigned Client Identifier property was present. */ + bool has_assigned_client_id; + /** Topic Alias Maximum property was present. */ + bool has_topic_alias_maximum; + /** Reason String property was present. */ + bool has_reason_string; + /** User Property property was present. */ + bool has_user_prop; + /** Wildcard Subscription Available property was present. */ + bool has_wildcard_sub_available; + /** Subscription Identifiers Available property was present. */ + bool has_subscription_ids_available; + /** Shared Subscription Available property was present. */ + bool has_shared_sub_available; + /** Server Keep Alive property was present. */ + bool has_server_keep_alive; + /** Response Information property was present. */ + bool has_response_information; + /** Server Reference property was present. */ + bool has_server_reference; + /** Authentication Method property was present. */ + bool has_auth_method; + /** Authentication Data property was present. */ + bool has_auth_data; + } rx; + } prop; +#endif /* CONFIG_MQTT_VERSION_5_0 */ +}; + +/** @brief Common MQTT 5.0 properties shared across all ack-type messages. */ +struct mqtt_common_ack_properties { +#if defined(CONFIG_MQTT_VERSION_5_0) || defined(__DOXYGEN__) + /** MQTT 5.0, chapter 3.4.2.2.3 User Property. */ + struct mqtt_utf8_pair user_prop[CONFIG_MQTT_USER_PROPERTIES_MAX]; + + /** MQTT 5.0, chapter 3.4.2.2.2 Reason String. */ + struct mqtt_utf8 reason_string; + + /** Flags indicating whether given property was present in received packet. */ + struct { + /** Reason String property was present. */ + bool has_reason_string; + /** User Property property was present. */ + bool has_user_prop; + } rx; +#endif /* CONFIG_MQTT_VERSION_5_0 */ }; /** @brief Parameters for MQTT publish acknowledgment (PUBACK). */ struct mqtt_puback_param { /** Message id of the PUBLISH message being acknowledged */ uint16_t message_id; + +#if defined(CONFIG_MQTT_VERSION_5_0) || defined(__DOXYGEN__) + /** MQTT 5.0 reason code. */ + uint8_t reason_code; + + /** MQTT 5.0 properties. */ + struct mqtt_common_ack_properties prop; +#endif /* CONFIG_MQTT_VERSION_5_0 */ }; /** @brief Parameters for MQTT publish receive (PUBREC). */ struct mqtt_pubrec_param { /** Message id of the PUBLISH message being acknowledged */ uint16_t message_id; + +#if defined(CONFIG_MQTT_VERSION_5_0) || defined(__DOXYGEN__) + /** MQTT 5.0 reason code. */ + uint8_t reason_code; + + /** MQTT 5.0 properties. */ + struct mqtt_common_ack_properties prop; +#endif /* CONFIG_MQTT_VERSION_5_0 */ }; /** @brief Parameters for MQTT publish release (PUBREL). */ struct mqtt_pubrel_param { /** Message id of the PUBREC message being acknowledged */ uint16_t message_id; + +#if defined(CONFIG_MQTT_VERSION_5_0) || defined(__DOXYGEN__) + /** MQTT 5.0 reason code. */ + uint8_t reason_code; + + /** MQTT 5.0 properties. */ + struct mqtt_common_ack_properties prop; +#endif /* CONFIG_MQTT_VERSION_5_0 */ }; /** @brief Parameters for MQTT publish complete (PUBCOMP). */ struct mqtt_pubcomp_param { /** Message id of the PUBREL message being acknowledged */ uint16_t message_id; + +#if defined(CONFIG_MQTT_VERSION_5_0) || defined(__DOXYGEN__) + /** MQTT 5.0 reason code. */ + uint8_t reason_code; + + /** MQTT 5.0 properties. */ + struct mqtt_common_ack_properties prop; +#endif /* CONFIG_MQTT_VERSION_5_0 */ }; /** @brief Parameters for MQTT subscription acknowledgment (SUBACK). */ struct mqtt_suback_param { /** Message id of the SUBSCRIBE message being acknowledged */ uint16_t message_id; - /** Return codes indicating maximum QoS level granted for each topic - * in the subscription list. + + /** MQTT 3.1 - Return codes indicating maximum QoS level granted for + * each topic in the subscription list. + * MQTT 5.0 - Reason codes corresponding to each topic in the + * subscription list. */ struct mqtt_binstr return_codes; + +#if defined(CONFIG_MQTT_VERSION_5_0) || defined(__DOXYGEN__) + /** MQTT 5.0 properties. */ + struct mqtt_common_ack_properties prop; +#endif /* CONFIG_MQTT_VERSION_5_0 */ }; /** @brief Parameters for MQTT unsubscribe acknowledgment (UNSUBACK). */ struct mqtt_unsuback_param { /** Message id of the UNSUBSCRIBE message being acknowledged */ uint16_t message_id; + +#if defined(CONFIG_MQTT_VERSION_5_0) || defined(__DOXYGEN__) + /** Reason codes corresponding to each topic in the unsubscription list. */ + struct mqtt_binstr reason_codes; + + /** MQTT 5.0 properties. */ + struct mqtt_common_ack_properties prop; +#endif /* CONFIG_MQTT_VERSION_5_0 */ }; /** @brief Parameters for a publish message (PUBLISH). */ @@ -264,9 +516,58 @@ struct mqtt_publish_param { * by the broker. */ uint8_t retain_flag : 1; + +#if defined(CONFIG_MQTT_VERSION_5_0) || defined(__DOXYGEN__) + /** MQTT 5.0 properties. */ + struct { + /** MQTT 5.0, chapter 3.3.2.3.7 User Property. */ + struct mqtt_utf8_pair user_prop[CONFIG_MQTT_USER_PROPERTIES_MAX]; + + /** MQTT 5.0, chapter 3.3.2.3.5 Response Topic. */ + struct mqtt_utf8 response_topic; + + /** MQTT 5.0, chapter 3.3.2.3.6 Correlation Data. */ + struct mqtt_binstr correlation_data; + + /** MQTT 5.0, chapter 3.3.2.3.9 Content Type. */ + struct mqtt_utf8 content_type; + + /** MQTT 5.0, chapter 3.3.2.3.8 Subscription Identifier. */ + uint32_t subscription_identifier[CONFIG_MQTT_SUBSCRIPTION_ID_PROPERTIES_MAX]; + + /** MQTT 5.0, chapter 3.3.2.3.3 Message Expiry Interval. */ + uint32_t message_expiry_interval; + + /** MQTT 5.0, chapter 3.3.2.3.4 Topic Alias. */ + uint16_t topic_alias; + + /** MQTT 5.0, chapter 3.3.2.3.2 Payload Format Indicator. */ + uint8_t payload_format_indicator; + + /** Flags indicating whether given property was present in received packet. */ + struct { + /** Payload Format Indicator property was present. */ + bool has_payload_format_indicator; + /** Message Expiry Interval property was present. */ + bool has_message_expiry_interval; + /** Topic Alias property was present. */ + bool has_topic_alias; + /** Response Topic property was present. */ + bool has_response_topic; + /** Correlation Data property was present. */ + bool has_correlation_data; + /** User Property property was present. */ + bool has_user_prop; + /** Subscription Identifier property was present. */ + bool has_subscription_identifier; + /** Content Type property was present. */ + bool has_content_type; + } rx; + } prop; +#endif /* CONFIG_MQTT_VERSION_5_0 */ }; -/** @brief List of topics in a subscription request. */ +/** @brief Parameters for subscribe/unsubscribe message. */ struct mqtt_subscription_list { /** Array containing topics along with QoS for each. */ struct mqtt_topic *list; @@ -276,6 +577,88 @@ struct mqtt_subscription_list { /** Message id used to identify subscription request. */ uint16_t message_id; + +#if defined(CONFIG_MQTT_VERSION_5_0) || defined(__DOXYGEN__) + /** MQTT 5.0 properties. */ + struct { + /** MQTT 5.0, chapter 3.8.2.1.3 / 3.10.2.1.2 User Property. */ + struct mqtt_utf8_pair user_prop[CONFIG_MQTT_USER_PROPERTIES_MAX]; + + /** MQTT 5.0, chapter 3.8.2.1.2 Subscription Identifier. + * Ignored for UNSUBSCRIBE requests. + */ + uint32_t subscription_identifier; + } prop; +#endif /* CONFIG_MQTT_VERSION_5_0 */ +}; + +/** @brief Parameters for disconnect message. */ +struct mqtt_disconnect_param { +#if defined(CONFIG_MQTT_VERSION_5_0) || defined(__DOXYGEN__) + /* MQTT 5.0 Disconnect reason code. */ + enum mqtt_disconnect_reason_code reason_code; + + /** MQTT 5.0 properties. */ + struct { + /** MQTT 5.0, chapter 3.14.2.2.4 User Property. */ + struct mqtt_utf8_pair user_prop[CONFIG_MQTT_USER_PROPERTIES_MAX]; + + /** MQTT 5.0, chapter 3.14.2.2.3 Reason String. */ + struct mqtt_utf8 reason_string; + + /** MQTT 5.0, chapter 3.14.2.2.5 Server Reference. */ + struct mqtt_utf8 server_reference; + + /** MQTT 5.0, chapter 3.14.2.2.2 Session Expiry Interval. */ + uint32_t session_expiry_interval; + + /** Flags indicating whether given property was present in received packet. */ + struct { + /** Session Expiry Interval property was present. */ + bool has_session_expiry_interval; + /** Reason String property was present. */ + bool has_reason_string; + /** User Property property was present. */ + bool has_user_prop; + /** Server Reference property was present. */ + bool has_server_reference; + } rx; + } prop; +#endif /* CONFIG_MQTT_VERSION_5_0 */ +}; + +/** @brief Parameters for auth message. */ +struct mqtt_auth_param { +#if defined(CONFIG_MQTT_VERSION_5_0) || defined(__DOXYGEN__) + /* MQTT 5.0, chapter 3.15.2.1 Authenticate Reason Code */ + enum mqtt_auth_reason_code reason_code; + + struct { + /** MQTT 5.0, chapter 3.15.2.2.5 User Property. */ + struct mqtt_utf8_pair user_prop[CONFIG_MQTT_USER_PROPERTIES_MAX]; + + /** MQTT 5.0, chapter 3.15.2.2.2 Authentication Method. */ + struct mqtt_utf8 auth_method; + + /** MQTT 5.0, chapter 3.15.2.2.3 Authentication Data. */ + struct mqtt_binstr auth_data; + + /** MQTT 5.0, chapter 3.15.2.2.4 Reason String. */ + struct mqtt_utf8 reason_string; + + /** Flags indicating whether given property was present in received packet. */ + struct { + /** Authentication Method property was present. */ + bool has_auth_method; + /** Authentication Data property was present. */ + bool has_auth_data; + /** Reason String property was present. */ + bool has_reason_string; + /** User Property property was present. */ + bool has_user_prop; + } rx; + } prop; +#endif /* CONFIG_MQTT_VERSION_5_0 */ }; /** @@ -311,6 +694,14 @@ union mqtt_evt_param { /** Parameters accompanying MQTT_EVT_UNSUBACK event. */ struct mqtt_unsuback_param unsuback; + +#if defined(CONFIG_MQTT_VERSION_5_0) || defined(__DOXYGEN__) + /** Parameters accompanying MQTT_EVT_DISCONNECT event. */ + struct mqtt_disconnect_param disconnect; + + /** Parameters accompanying MQTT_EVT_AUTH event. */ + struct mqtt_auth_param auth; +#endif /* CONFIG_MQTT_VERSION_5_0 */ }; /** @brief Defines MQTT asynchronous event notified to the application. */ @@ -491,6 +882,14 @@ struct mqtt_internal { /** Internal. Remaining payload length to read. */ uint32_t remaining_payload; + +#if defined(CONFIG_MQTT_VERSION_5_0) || defined(__DOXYGEN__) + /** Internal. MQTT 5.0 topic alias mapping. */ + struct mqtt_topic_alias topic_aliases[CONFIG_MQTT_TOPIC_ALIAS_MAX]; + + /** Internal. MQTT 5.0 disconnect reason set in case of processing errors. */ + enum mqtt_disconnect_reason_code disconnect_reason; +#endif /* CONFIG_MQTT_VERSION_5_0 */ }; /** @@ -523,6 +922,32 @@ struct mqtt_client { */ struct mqtt_utf8 *password; +#if defined(CONFIG_MQTT_VERSION_5_0) || defined(__DOXYGEN__) + /** MQTT 5.0 Will properties. */ + struct { + /** MQTT 5.0, chapter 3.1.3.2.8 User Property. */ + struct mqtt_utf8_pair user_prop[CONFIG_MQTT_USER_PROPERTIES_MAX]; + + /** MQTT 5.0, chapter 3.1.3.2.5 Content Type. */ + struct mqtt_utf8 content_type; + + /** MQTT 5.0, chapter 3.1.3.2.6 Response Topic. */ + struct mqtt_utf8 response_topic; + + /** MQTT 5.0, chapter 3.1.3.2.7 Correlation Data. */ + struct mqtt_binstr correlation_data; + + /** MQTT 5.0, chapter 3.1.3.2.2 Will Delay Interval. */ + uint32_t will_delay_interval; + + /** MQTT 5.0, chapter 3.1.3.2.4 Message Expiry Interval. */ + uint32_t message_expiry_interval; + + /** MQTT 5.0, chapter 3.1.3.2.3 Payload Format Indicator. */ + uint8_t payload_format_indicator; + } will_prop; +#endif /* CONFIG_MQTT_VERSION_5_0 */ + /** Will topic and QoS. Can be NULL. */ struct mqtt_topic *will_topic; @@ -558,6 +983,35 @@ struct mqtt_client { /** Unanswered PINGREQ count on this connection. */ int8_t unacked_ping; +#if defined(CONFIG_MQTT_VERSION_5_0) || defined(__DOXYGEN__) + /** MQTT 5.0 properties. */ + struct { + /** MQTT 5.0, chapter 3.1.2.11.8 User Property. */ + struct mqtt_utf8_pair user_prop[CONFIG_MQTT_USER_PROPERTIES_MAX]; + + /** MQTT 5.0, chapter 3.1.2.11.9 Authentication Method. */ + struct mqtt_utf8 auth_method; + + /** MQTT 5.0, chapter 3.1.2.11.10 Authentication Data. */ + struct mqtt_binstr auth_data; + + /** MQTT 5.0, chapter 3.1.2.11.2 Session Expiry Interval. */ + uint32_t session_expiry_interval; + + /** MQTT 5.0, chapter 3.1.2.11.4 Maximum Packet Size. */ + uint32_t maximum_packet_size; + + /** MQTT 5.0, chapter 3.1.2.11.3 Receive Maximum. */ + uint16_t receive_maximum; + + /** MQTT 5.0, chapter 3.1.2.11.6 Request Response Information. */ + bool request_response_info; + + /** MQTT 5.0, chapter 3.1.2.11.7 Request Response Information. */ + bool request_problem_info; + } prop; +#endif /* CONFIG_MQTT_VERSION_5_0 */ + /** Will retain flag, 1 if will message shall be retained persistently. */ uint8_t will_retain : 1; @@ -731,10 +1185,27 @@ int mqtt_ping(struct mqtt_client *client); * * @param[in] client Identifies client instance for which procedure is * requested. + * @param[in] param Optional Disconnect parameters. May be NULL. + * Ignored if MQTT 3.1.1 is used. + * + * @return 0 or a negative error code (errno.h) indicating reason of failure. + */ +int mqtt_disconnect(struct mqtt_client *client, + const struct mqtt_disconnect_param *param); + +#if defined(CONFIG_MQTT_VERSION_5_0) || defined(__DOXYGEN__) +/** + * @brief API to send an authentication packet to the server. + * + * @param[in] client Client instance for which the procedure is requested. + * Shall not be NULL. + * @param[in] param Parameters to be used for the auth message. + * Shall not be NULL. * * @return 0 or a negative error code (errno.h) indicating reason of failure. */ -int mqtt_disconnect(struct mqtt_client *client); +int mqtt_auth(struct mqtt_client *client, const struct mqtt_auth_param *param); +#endif /* CONFIG_MQTT_VERSION_5_0 */ /** * @brief API to abort MQTT connection. This will close the corresponding diff --git a/samples/net/cloud/aws_iot_mqtt/src/main.c b/samples/net/cloud/aws_iot_mqtt/src/main.c index c03f250bcaa2..4bb226ea97a2 100644 --- a/samples/net/cloud/aws_iot_mqtt/src/main.c +++ b/samples/net/cloud/aws_iot_mqtt/src/main.c @@ -413,7 +413,7 @@ void aws_client_loop(void) } cleanup: - mqtt_disconnect(&client_ctx); + mqtt_disconnect(&client_ctx, NULL); close(fds.fd); fds.fd = -1; diff --git a/samples/net/mqtt_publisher/src/main.c b/samples/net/mqtt_publisher/src/main.c index faa96e33b5c0..7ae967058e3c 100644 --- a/samples/net/mqtt_publisher/src/main.c +++ b/samples/net/mqtt_publisher/src/main.c @@ -475,7 +475,7 @@ static int publisher(void) r = 0; } - rc = mqtt_disconnect(&client_ctx); + rc = mqtt_disconnect(&client_ctx, NULL); PRINT_RESULT("mqtt_disconnect", rc); LOG_INF("Bye!"); diff --git a/samples/net/secure_mqtt_sensor_actuator/src/mqtt_client.c b/samples/net/secure_mqtt_sensor_actuator/src/mqtt_client.c index 3fdb6f3abf47..df515b2e5b2f 100644 --- a/samples/net/secure_mqtt_sensor_actuator/src/mqtt_client.c +++ b/samples/net/secure_mqtt_sensor_actuator/src/mqtt_client.c @@ -402,7 +402,7 @@ void app_mqtt_run(struct mqtt_client *client) } } /* Gracefully close connection */ - mqtt_disconnect(client); + mqtt_disconnect(client, NULL); } void app_mqtt_connect(struct mqtt_client *client) diff --git a/subsys/net/lib/mqtt/Kconfig b/subsys/net/lib/mqtt/Kconfig index 11e59b527e99..6257e0d2b069 100644 --- a/subsys/net/lib/mqtt/Kconfig +++ b/subsys/net/lib/mqtt/Kconfig @@ -17,6 +17,29 @@ module-str=Log level for MQTT module-help=Enables mqtt debug messages. source "subsys/net/Kconfig.template.log_config.net" +choice MQTT_VERSION_SUPPORT + prompt "Maximum MQTT protocol version supported" + default MQTT_VERSION_3_1_1 + help + Maximum MQTT protocol version supported by the library. + +config MQTT_VERSION_3_1_1 + bool "Support up to version 3.1.1 of the MQTT protocol" + help + The MQTT version 3.1.1 will be the default library choice. Applications + will still be able to choose to use an older MQTT protocol version + with respective runtime MQTT client configuration. + +config MQTT_VERSION_5_0 + bool "Support up to version 5.0 of the MQTT protocol [EXPERIMENTAL]" + select EXPERIMENTAL + help + The MQTT version 5.0 will be the default library choice. Applications + will still be able to choose to use an older MQTT protocol version + with respective runtime MQTT client configuration. + +endchoice + config MQTT_KEEPALIVE int "Maximum number of clients Keep alive time for MQTT (in seconds)" default 60 @@ -58,4 +81,39 @@ config MQTT_CLEAN_SESSION the client. Setting this flag to 0 allows the client to create a persistent session. +#if MQTT_VERSION_5_0 + +config MQTT_USER_PROPERTIES_MAX + int "Maximum number of user properties in a packet" + default 1 + range 1 $(UINT16_MAX) + help + Maximum number of user properties that the client can include in a + packet or parse on input. + +config MQTT_SUBSCRIPTION_ID_PROPERTIES_MAX + int "Maximum number of Subscription ID properties in a Publish packet" + default 1 + range 1 32 + help + Maximum number of Subscription ID properties that the client can + parse when processing Publish message. + +config MQTT_TOPIC_ALIAS_MAX + int "Maximum number of supported topic aliases" + default 5 + range 0 $(UINT16_MAX) + help + Maximum number of supported topic aliases used in PUBLISH packets. + If set to 0, topic aliasing is disabled. + +config MQTT_TOPIC_ALIAS_STRING_MAX + int "The longest topic name that can be aliased" + default 64 + range 8 $(UINT16_MAX) + help + Specifies a size of a buffer for storing aliased topics. + +#endif # MQTT_VERSION_5_0 + endif # MQTT_LIB diff --git a/subsys/net/lib/mqtt/mqtt.c b/subsys/net/lib/mqtt/mqtt.c index 1b8d31280ba1..3af78b13f015 100644 --- a/subsys/net/lib/mqtt/mqtt.c +++ b/subsys/net/lib/mqtt/mqtt.c @@ -46,8 +46,7 @@ void event_notify(struct mqtt_client *client, const struct mqtt_evt *evt) } } -static void client_disconnect(struct mqtt_client *client, int result, - bool notify) +void mqtt_client_disconnect(struct mqtt_client *client, int result, bool notify) { int err_code; @@ -105,10 +104,62 @@ static int client_connect(struct mqtt_client *client) return 0; error: - client_disconnect(client, err_code, false); + mqtt_client_disconnect(client, err_code, false); return err_code; } +static int client_write(struct mqtt_client *client, const uint8_t *data, + uint32_t datalen); + +#if defined(CONFIG_MQTT_VERSION_5_0) +static void disconnect_5_0_notify(struct mqtt_client *client, int err) +{ + struct mqtt_disconnect_param param = { }; + struct buf_ctx packet; + + /* Parser might've set custom failure reason, in such case skip generic + * mapping from errno to reason code. + */ + if (client->internal.disconnect_reason != MQTT_DISCONNECT_NORMAL) { + param.reason_code = client->internal.disconnect_reason; + } else { + switch (err) { + case ECONNREFUSED: + case ENOTCONN: + /* Connection rejected/closed, skip disconnect. */ + return; + case EINVAL: + param.reason_code = MQTT_DISCONNECT_MALFORMED_PACKET; + break; + case EBADMSG: + param.reason_code = MQTT_DISCONNECT_PROTOCOL_ERROR; + break; + case ENOMEM: + param.reason_code = MQTT_DISCONNECT_PACKET_TOO_LARGE; + break; + default: + param.reason_code = MQTT_DISCONNECT_UNSPECIFIED_ERROR; + break; + } + } + + tx_buf_init(client, &packet); + + if (disconnect_encode(client, ¶m, &packet) < 0) { + return; + } + + (void)client_write(client, packet.cur, packet.end - packet.cur); +} +#else +static void disconnect_5_0_notify(struct mqtt_client *client, int err) +{ + ARG_UNUSED(client); + ARG_UNUSED(err); +} +#endif /* CONFIG_MQTT_VERSION_5_0 */ + + static int client_read(struct mqtt_client *client) { int err_code; @@ -119,7 +170,12 @@ static int client_read(struct mqtt_client *client) err_code = mqtt_handle_rx(client); if (err_code < 0) { - client_disconnect(client, err_code, true); + if (mqtt_is_version_5_0(client)) { + /* Best effort send, if it fails just shut the connection. */ + disconnect_5_0_notify(client, -err_code); + } + + mqtt_client_disconnect(client, err_code, true); } return err_code; @@ -136,7 +192,7 @@ static int client_write(struct mqtt_client *client, const uint8_t *data, if (err_code < 0) { NET_ERR("Transport write failed, err_code = %d, " "closing connection", err_code); - client_disconnect(client, err_code, true); + mqtt_client_disconnect(client, err_code, true); return err_code; } @@ -157,7 +213,7 @@ static int client_write_msg(struct mqtt_client *client, if (err_code < 0) { NET_ERR("Transport write failed, err_code = %d, " "closing connection", err_code); - client_disconnect(client, err_code, true); + mqtt_client_disconnect(client, err_code, true); return err_code; } @@ -176,7 +232,8 @@ void mqtt_client_init(struct mqtt_client *client) MQTT_STATE_INIT(client); mqtt_mutex_init(client); - client->protocol_version = MQTT_VERSION_3_1_1; + client->protocol_version = IS_ENABLED(CONFIG_MQTT_VERSION_5_0) ? + MQTT_VERSION_5_0 : MQTT_VERSION_3_1_1; client->clean_session = MQTT_CLEAN_SESSION; client->keepalive = MQTT_KEEPALIVE; } @@ -261,7 +318,7 @@ int mqtt_publish(struct mqtt_client *client, goto error; } - err_code = publish_encode(param, &packet); + err_code = publish_encode(client, param, &packet); if (err_code < 0) { goto error; } @@ -308,7 +365,7 @@ int mqtt_publish_qos1_ack(struct mqtt_client *client, goto error; } - err_code = publish_ack_encode(param, &packet); + err_code = publish_ack_encode(client, param, &packet); if (err_code < 0) { goto error; } @@ -345,7 +402,7 @@ int mqtt_publish_qos2_receive(struct mqtt_client *client, goto error; } - err_code = publish_receive_encode(param, &packet); + err_code = publish_receive_encode(client, param, &packet); if (err_code < 0) { goto error; } @@ -382,7 +439,7 @@ int mqtt_publish_qos2_release(struct mqtt_client *client, goto error; } - err_code = publish_release_encode(param, &packet); + err_code = publish_release_encode(client, param, &packet); if (err_code < 0) { goto error; } @@ -419,7 +476,7 @@ int mqtt_publish_qos2_complete(struct mqtt_client *client, goto error; } - err_code = publish_complete_encode(param, &packet); + err_code = publish_complete_encode(client, param, &packet); if (err_code < 0) { goto error; } @@ -438,7 +495,8 @@ int mqtt_publish_qos2_complete(struct mqtt_client *client, return err_code; } -int mqtt_disconnect(struct mqtt_client *client) +int mqtt_disconnect(struct mqtt_client *client, + const struct mqtt_disconnect_param *param) { int err_code; struct buf_ctx packet; @@ -454,7 +512,7 @@ int mqtt_disconnect(struct mqtt_client *client) goto error; } - err_code = disconnect_encode(&packet); + err_code = disconnect_encode(client, param, &packet); if (err_code < 0) { goto error; } @@ -464,7 +522,7 @@ int mqtt_disconnect(struct mqtt_client *client) goto error; } - client_disconnect(client, 0, true); + mqtt_client_disconnect(client, 0, true); error: mqtt_mutex_unlock(client); @@ -494,7 +552,7 @@ int mqtt_subscribe(struct mqtt_client *client, goto error; } - err_code = subscribe_encode(param, &packet); + err_code = subscribe_encode(client, param, &packet); if (err_code < 0) { goto error; } @@ -528,7 +586,7 @@ int mqtt_unsubscribe(struct mqtt_client *client, goto error; } - err_code = unsubscribe_encode(param, &packet); + err_code = unsubscribe_encode(client, param, &packet); if (err_code < 0) { goto error; } @@ -576,6 +634,58 @@ int mqtt_ping(struct mqtt_client *client) return err_code; } +#if defined(CONFIG_MQTT_VERSION_5_0) +static int verify_auth_state(const struct mqtt_client *client) +{ + /* Enhanced authentication is only allowed when connecting at MQTT + * level (before CONNACK is received). + */ + if (MQTT_HAS_STATE(client, MQTT_STATE_TCP_CONNECTED) && + !MQTT_HAS_STATE(client, MQTT_STATE_CONNECTED)) { + return 0; + } + + /* Return generic protocol error */ + return -EPROTO; +} + +int mqtt_auth(struct mqtt_client *client, const struct mqtt_auth_param *param) +{ + int err_code; + struct buf_ctx packet; + + NULL_PARAM_CHECK(client); + NULL_PARAM_CHECK(param); + + mqtt_mutex_lock(client); + + if (!mqtt_is_version_5_0(client)) { + NET_ERR("Auth packet only supported in MQTT 5.0"); + err_code = -ENOTSUP; + goto error; + } + + tx_buf_init(client, &packet); + + err_code = verify_auth_state(client); + if (err_code < 0) { + goto error; + } + + err_code = auth_encode(param, &packet); + if (err_code < 0) { + goto error; + } + + err_code = client_write(client, packet.cur, packet.end - packet.cur); + +error: + mqtt_mutex_unlock(client); + + return err_code; +} +#endif /* CONFIG_MQTT_VERSION_5_0 */ + int mqtt_abort(struct mqtt_client *client) { NULL_PARAM_CHECK(client); @@ -583,7 +693,7 @@ int mqtt_abort(struct mqtt_client *client) mqtt_mutex_lock(client); if (client->internal.state != MQTT_STATE_IDLE) { - client_disconnect(client, -ECONNABORTED, true); + mqtt_client_disconnect(client, -ECONNABORTED, true); } mqtt_mutex_unlock(client); @@ -685,7 +795,7 @@ static int read_publish_payload(struct mqtt_client *client, void *buffer, ret = -ENOTCONN; } - client_disconnect(client, ret, true); + mqtt_client_disconnect(client, ret, true); goto exit; } diff --git a/subsys/net/lib/mqtt/mqtt_decoder.c b/subsys/net/lib/mqtt/mqtt_decoder.c index 678546b2f111..2670ff7bc994 100644 --- a/subsys/net/lib/mqtt/mqtt_decoder.c +++ b/subsys/net/lib/mqtt/mqtt_decoder.c @@ -119,7 +119,8 @@ static int unpack_utf8_str(struct buf_ctx *buf, struct mqtt_utf8 *str) } /** - * @brief Unpacks binary string from the buffer from the offset requested. + * @brief Unpacks binary string from the buffer from the offset requested with + * the length provided. * * @param[in] length Binary string length. * @param[inout] buf A pointer to the buf_ctx structure containing current @@ -130,8 +131,8 @@ static int unpack_utf8_str(struct buf_ctx *buf, struct mqtt_utf8 *str) * @retval 0 if the procedure is successful. * @retval -EINVAL if the buffer would be exceeded during the read */ -static int unpack_data(uint32_t length, struct buf_ctx *buf, - struct mqtt_binstr *str) +static int unpack_raw_data(uint32_t length, struct buf_ctx *buf, + struct mqtt_binstr *str) { NET_DBG(">> cur:%p, end:%p", (void *)buf->cur, (void *)buf->end); @@ -154,23 +155,12 @@ static int unpack_data(uint32_t length, struct buf_ctx *buf, return 0; } -/**@brief Decode MQTT Packet Length in the MQTT fixed header. - * - * @param[inout] buf A pointer to the buf_ctx structure containing current - * buffer position. - * @param[out] length Length of variable header and payload in the - * MQTT message. - * - * @retval 0 if the procedure is successful. - * @retval -EINVAL if the length decoding would use more that 4 bytes. - * @retval -EAGAIN if the buffer would be exceeded during the read. - */ -static int packet_length_decode(struct buf_ctx *buf, uint32_t *length) +int unpack_variable_int(struct buf_ctx *buf, uint32_t *val) { uint8_t shift = 0U; - uint8_t bytes = 0U; + int bytes = 0; - *length = 0U; + *val = 0U; do { if (bytes >= MQTT_MAX_LENGTH_BYTES) { return -EINVAL; @@ -180,19 +170,19 @@ static int packet_length_decode(struct buf_ctx *buf, uint32_t *length) return -EAGAIN; } - *length += ((uint32_t)*(buf->cur) & MQTT_LENGTH_VALUE_MASK) + *val += ((uint32_t)*(buf->cur) & MQTT_LENGTH_VALUE_MASK) << shift; shift += MQTT_LENGTH_SHIFT; bytes++; } while ((*(buf->cur++) & MQTT_LENGTH_CONTINUATION_BIT) != 0U); - if (*length > MQTT_MAX_PAYLOAD_SIZE) { + if (*val > MQTT_MAX_PAYLOAD_SIZE) { return -EINVAL; } - NET_DBG("length:0x%08x", *length); + NET_DBG("variable int:0x%08x", *val); - return 0; + return bytes; } int fixed_header_decode(struct buf_ctx *buf, uint8_t *type_and_flags, @@ -205,8 +195,473 @@ int fixed_header_decode(struct buf_ctx *buf, uint8_t *type_and_flags, return err_code; } - return packet_length_decode(buf, length); + err_code = unpack_variable_int(buf, length); + if (err_code < 0) { + return err_code; + } + + return 0; +} + +#if defined(CONFIG_MQTT_VERSION_5_0) +/** + * @brief Unpacks unsigned 32 bit value from the buffer from the offset + * requested. + * + * @param[inout] buf A pointer to the buf_ctx structure containing current + * buffer position. + * @param[out] val Memory where the value is to be unpacked. + * + * @retval 0 if the procedure is successful. + * @retval -EINVAL if the buffer would be exceeded during the read + */ +static int unpack_uint32(struct buf_ctx *buf, uint32_t *val) +{ + uint8_t *cur = buf->cur; + uint8_t *end = buf->end; + + NET_DBG(">> cur:%p, end:%p", (void *)cur, (void *)end); + + if ((end - cur) < sizeof(uint32_t)) { + return -EINVAL; + } + + *val = sys_get_be32(cur); + buf->cur = (cur + sizeof(uint32_t)); + + NET_DBG("<< val:%08x", *val); + + return 0; +} + +/** + * @brief Unpacks binary string from the buffer from the offset requested. + * Binary string length is decoded from the first two bytes of the buffer. + * + * @param[inout] buf A pointer to the buf_ctx structure containing current + * buffer position. + * @param[out] bin Pointer to a binary string that will hold the binary string + * location in the buffer. + * + * @retval 0 if the procedure is successful. + * @retval -EINVAL if the buffer would be exceeded during the read + */ +static int unpack_binary_data(struct buf_ctx *buf, struct mqtt_binstr *bin) +{ + uint16_t len; + int err; + + NET_DBG(">> cur:%p, end:%p", (void *)buf->cur, (void *)buf->end); + + err = unpack_uint16(buf, &len); + if (err != 0) { + return err; + } + + if ((buf->end - buf->cur) < len) { + return -EINVAL; + } + + bin->len = len; + /* Zero length binary strings are permitted. */ + if (len > 0) { + bin->data = buf->cur; + buf->cur += len; + } else { + bin->data = NULL; + } + + NET_DBG("<< bin len:%08x", GET_BINSTR_BUFFER_SIZE(bin)); + + return 0; +} + +struct property_decoder { + void *data; + bool *found; + uint8_t type; +}; + +int decode_uint32_property(struct property_decoder *prop, + uint32_t *remaining_len, + struct buf_ctx *buf) +{ + uint32_t *value = prop->data; + + if (*remaining_len < sizeof(uint32_t)) { + return -EINVAL; + } + + if (unpack_uint32(buf, value) < 0) { + return -EINVAL; + } + + *remaining_len -= sizeof(uint32_t); + *prop->found = true; + + return 0; +} + +int decode_uint16_property(struct property_decoder *prop, + uint32_t *remaining_len, + struct buf_ctx *buf) +{ + uint16_t *value = prop->data; + + if (*remaining_len < sizeof(uint16_t)) { + return -EINVAL; + } + + if (unpack_uint16(buf, value) < 0) { + return -EINVAL; + } + + *remaining_len -= sizeof(uint16_t); + *prop->found = true; + + return 0; +} + +int decode_uint8_property(struct property_decoder *prop, + uint32_t *remaining_len, + struct buf_ctx *buf) +{ + uint8_t *value = prop->data; + + if (*remaining_len < sizeof(uint8_t)) { + return -EINVAL; + } + + if (unpack_uint8(buf, value) < 0) { + return -EINVAL; + } + + *remaining_len -= sizeof(uint8_t); + *prop->found = true; + + return 0; +} + +int decode_string_property(struct property_decoder *prop, + uint32_t *remaining_len, + struct buf_ctx *buf) +{ + struct mqtt_utf8 *str = prop->data; + + if (unpack_utf8_str(buf, str) < 0) { + return -EINVAL; + } + + if (*remaining_len < sizeof(uint16_t) + str->size) { + return -EINVAL; + } + + *remaining_len -= sizeof(uint16_t) + str->size; + *prop->found = true; + + return 0; +} + +int decode_binary_property(struct property_decoder *prop, + uint32_t *remaining_len, + struct buf_ctx *buf) +{ + struct mqtt_binstr *bin = prop->data; + + if (unpack_binary_data(buf, bin) < 0) { + return -EINVAL; + } + + if (*remaining_len < sizeof(uint16_t) + bin->len) { + return -EINVAL; + } + + *remaining_len -= sizeof(uint16_t) + bin->len; + *prop->found = true; + + return 0; +} + +int decode_user_property(struct property_decoder *prop, + uint32_t *remaining_len, + struct buf_ctx *buf) +{ + struct mqtt_utf8_pair *user_prop = prop->data; + struct mqtt_utf8_pair *chosen = NULL; + struct mqtt_utf8_pair temp = { 0 }; + size_t prop_len; + + if (unpack_utf8_str(buf, &temp.name) < 0) { + return -EINVAL; + } + + if (unpack_utf8_str(buf, &temp.value) < 0) { + return -EINVAL; + } + + prop_len = (2 * sizeof(uint16_t)) + temp.name.size + temp.value.size; + if (*remaining_len < prop_len) { + return -EINVAL; + } + + *remaining_len -= prop_len; + *prop->found = true; + + for (int i = 0; i < CONFIG_MQTT_USER_PROPERTIES_MAX; i++) { + if (user_prop[i].name.utf8 == NULL) { + chosen = &user_prop[i]; + break; + } + } + + if (chosen == NULL) { + NET_DBG("Cannot parse all user properties, ignore excess"); + } else { + memcpy(chosen, &temp, sizeof(struct mqtt_utf8_pair)); + } + + return 0; +} + +int decode_sub_id_property(struct property_decoder *prop, + uint32_t *remaining_len, + struct buf_ctx *buf) +{ + uint32_t *sub_id_array = prop->data; + uint32_t *chosen = NULL; + uint32_t value; + int bytes; + + bytes = unpack_variable_int(buf, &value); + if (bytes < 0) { + return -EINVAL; + } + + if (*remaining_len < bytes) { + return -EINVAL; + } + + *remaining_len -= bytes; + *prop->found = true; + + for (int i = 0; i < CONFIG_MQTT_SUBSCRIPTION_ID_PROPERTIES_MAX; i++) { + if (sub_id_array[i] == 0) { + chosen = &sub_id_array[i]; + break; + } + } + + if (chosen == NULL) { + NET_DBG("Cannot parse all subscription id properties, ignore excess"); + } else { + *chosen = value; + } + + return 0; +} + +static int properties_decode(struct property_decoder *prop, uint8_t cnt, + struct buf_ctx *buf) +{ + uint32_t properties_len; + int bytes; + int err; + + bytes = unpack_variable_int(buf, &properties_len); + if (bytes < 0) { + return -EINVAL; + } + + bytes += (int)properties_len; + + while (properties_len > 0) { + struct property_decoder *current_prop = NULL; + uint8_t type; + + /* Decode property type */ + err = unpack_uint8(buf, &type); + if (err < 0) { + return -EINVAL; + } + + properties_len--; + + /* Search if the property is supported in the provided property + * array. + */ + for (int i = 0; i < cnt; i++) { + if (type == prop[i].type) { + current_prop = &prop[i]; + } + } + + if (current_prop == NULL) { + NET_DBG("Unsupported property %u", type); + return -EBADMSG; + } + + /* Decode property value. */ + switch (type) { + case MQTT_PROP_SESSION_EXPIRY_INTERVAL: + case MQTT_PROP_MAXIMUM_PACKET_SIZE: + case MQTT_PROP_MESSAGE_EXPIRY_INTERVAL: + err = decode_uint32_property(current_prop, + &properties_len, buf); + break; + case MQTT_PROP_RECEIVE_MAXIMUM: + case MQTT_PROP_TOPIC_ALIAS_MAXIMUM: + case MQTT_PROP_SERVER_KEEP_ALIVE: + case MQTT_PROP_TOPIC_ALIAS: + err = decode_uint16_property(current_prop, + &properties_len, buf); + break; + case MQTT_PROP_MAXIMUM_QOS: + case MQTT_PROP_RETAIN_AVAILABLE: + case MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE: + case MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE: + case MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE: + case MQTT_PROP_PAYLOAD_FORMAT_INDICATOR: + err = decode_uint8_property(current_prop, + &properties_len, buf); + break; + case MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER: + case MQTT_PROP_REASON_STRING: + case MQTT_PROP_RESPONSE_INFORMATION: + case MQTT_PROP_SERVER_REFERENCE: + case MQTT_PROP_AUTHENTICATION_METHOD: + case MQTT_PROP_RESPONSE_TOPIC: + case MQTT_PROP_CONTENT_TYPE: + err = decode_string_property(current_prop, + &properties_len, buf); + break; + case MQTT_PROP_USER_PROPERTY: + err = decode_user_property(current_prop, + &properties_len, buf); + break; + case MQTT_PROP_AUTHENTICATION_DATA: + case MQTT_PROP_CORRELATION_DATA: + err = decode_binary_property(current_prop, + &properties_len, buf); + break; + case MQTT_PROP_SUBSCRIPTION_IDENTIFIER: + err = decode_sub_id_property(current_prop, + &properties_len, buf); + break; + default: + err = -ENOTSUP; + } + + if (err < 0) { + return err; + } + } + + return bytes; +} + +static int connack_properties_decode(struct buf_ctx *buf, + struct mqtt_connack_param *param) +{ + struct property_decoder prop[] = { + { + ¶m->prop.session_expiry_interval, + ¶m->prop.rx.has_session_expiry_interval, + MQTT_PROP_SESSION_EXPIRY_INTERVAL + }, + { + ¶m->prop.receive_maximum, + ¶m->prop.rx.has_receive_maximum, + MQTT_PROP_RECEIVE_MAXIMUM + }, + { + ¶m->prop.maximum_qos, + ¶m->prop.rx.has_maximum_qos, + MQTT_PROP_MAXIMUM_QOS + }, + { + ¶m->prop.retain_available, + ¶m->prop.rx.has_retain_available, + MQTT_PROP_RETAIN_AVAILABLE + }, + { + ¶m->prop.maximum_packet_size, + ¶m->prop.rx.has_maximum_packet_size, + MQTT_PROP_MAXIMUM_PACKET_SIZE + }, + { + ¶m->prop.assigned_client_id, + ¶m->prop.rx.has_assigned_client_id, + MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER + }, + { + ¶m->prop.topic_alias_maximum, + ¶m->prop.rx.has_topic_alias_maximum, + MQTT_PROP_TOPIC_ALIAS_MAXIMUM + }, + { + ¶m->prop.reason_string, + ¶m->prop.rx.has_reason_string, + MQTT_PROP_REASON_STRING + }, + { + ¶m->prop.user_prop, + ¶m->prop.rx.has_user_prop, + MQTT_PROP_USER_PROPERTY + }, + { + ¶m->prop.wildcard_sub_available, + ¶m->prop.rx.has_wildcard_sub_available, + MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE + }, + { + ¶m->prop.subscription_ids_available, + ¶m->prop.rx.has_subscription_ids_available, + MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE + }, + { + ¶m->prop.shared_sub_available, + ¶m->prop.rx.has_shared_sub_available, + MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE + }, + { + ¶m->prop.server_keep_alive, + ¶m->prop.rx.has_server_keep_alive, + MQTT_PROP_SERVER_KEEP_ALIVE + }, + { + ¶m->prop.response_information, + ¶m->prop.rx.has_response_information, + MQTT_PROP_RESPONSE_INFORMATION + }, + { + ¶m->prop.server_reference, + ¶m->prop.rx.has_server_reference, + MQTT_PROP_SERVER_REFERENCE + }, + { + ¶m->prop.auth_method, + ¶m->prop.rx.has_auth_method, + MQTT_PROP_AUTHENTICATION_METHOD + }, + { + ¶m->prop.auth_data, + ¶m->prop.rx.has_auth_data, + MQTT_PROP_AUTHENTICATION_DATA + } + }; + + return properties_decode(prop, ARRAY_SIZE(prop), buf); +} +#else +static int connack_properties_decode(struct buf_ctx *buf, + struct mqtt_connack_param *param) +{ + ARG_UNUSED(param); + ARG_UNUSED(buf); + + return -ENOTSUP; } +#endif /* CONFIG_MQTT_VERSION_5_0 */ int connect_ack_decode(const struct mqtt_client *client, struct buf_ctx *buf, struct mqtt_connack_param *param) @@ -224,20 +679,150 @@ int connect_ack_decode(const struct mqtt_client *client, struct buf_ctx *buf, return err_code; } - if (client->protocol_version == MQTT_VERSION_3_1_1) { - param->session_present_flag = - flags & MQTT_CONNACK_FLAG_SESSION_PRESENT; + param->return_code = ret_code; + + if (client->protocol_version == MQTT_VERSION_3_1_0) { + goto out; + } + + param->session_present_flag = flags & MQTT_CONNACK_FLAG_SESSION_PRESENT; + NET_DBG("[CID %p]: session_present_flag: %d", client, + param->session_present_flag); + + if (mqtt_is_version_5_0(client)) { + err_code = connack_properties_decode(buf, param); + if (err_code < 0) { + return err_code; + } + } + +out: + return 0; +} + +#if defined(CONFIG_MQTT_VERSION_5_0) +static int publish_properties_decode(struct buf_ctx *buf, + struct mqtt_publish_param *param) +{ + struct property_decoder prop[] = { + { + ¶m->prop.payload_format_indicator, + ¶m->prop.rx.has_payload_format_indicator, + MQTT_PROP_PAYLOAD_FORMAT_INDICATOR + }, + { + ¶m->prop.message_expiry_interval, + ¶m->prop.rx.has_message_expiry_interval, + MQTT_PROP_MESSAGE_EXPIRY_INTERVAL + }, + { + ¶m->prop.topic_alias, + ¶m->prop.rx.has_topic_alias, + MQTT_PROP_TOPIC_ALIAS + }, + { + ¶m->prop.response_topic, + ¶m->prop.rx.has_response_topic, + MQTT_PROP_RESPONSE_TOPIC + }, + { + ¶m->prop.correlation_data, + ¶m->prop.rx.has_correlation_data, + MQTT_PROP_CORRELATION_DATA + }, + { + ¶m->prop.user_prop, + ¶m->prop.rx.has_user_prop, + MQTT_PROP_USER_PROPERTY + }, + { + ¶m->prop.subscription_identifier, + ¶m->prop.rx.has_subscription_identifier, + MQTT_PROP_SUBSCRIPTION_IDENTIFIER + }, + { + ¶m->prop.content_type, + ¶m->prop.rx.has_content_type, + MQTT_PROP_CONTENT_TYPE + } + }; + + return properties_decode(prop, ARRAY_SIZE(prop), buf); +} + +static int publish_topic_alias_check(struct mqtt_client *client, + struct mqtt_publish_param *param) +{ + uint16_t alias = param->prop.topic_alias; + struct mqtt_utf8 *topic_str = ¶m->message.topic.topic; + struct mqtt_topic_alias *topic_alias; + + /* Topic alias must not exceed configured CONFIG_MQTT_TOPIC_ALIAS_MAX */ + if (alias > CONFIG_MQTT_TOPIC_ALIAS_MAX) { + set_disconnect_reason(client, MQTT_DISCONNECT_TOPIC_ALIAS_INVALID); + return -EBADMSG; + } + + if (topic_str->size == 0) { + /* In case there's no topic, topic alias must be set */ + if (alias == 0) { + return -EBADMSG; + } + + /* Topic alias number corresponds to the aliases array index. */ + topic_alias = &client->internal.topic_aliases[alias - 1]; - NET_DBG("[CID %p]: session_present_flag: %d", client, - param->session_present_flag); + /* In case topic alias has not been configured yet, report an error. */ + if (topic_alias->topic_size == 0) { + return -EBADMSG; + } + + topic_str->utf8 = topic_alias->topic_buf; + topic_str->size = topic_alias->topic_size; + + return 0; } - param->return_code = (enum mqtt_conn_return_code)ret_code; + /* If topic is present and topic alias is set, configure the alias locally. */ + if (alias > 0) { + topic_alias = &client->internal.topic_aliases[alias - 1]; + + if (topic_str->size > ARRAY_SIZE(topic_alias->topic_buf)) { + NET_ERR("Topic too long (%d bytes) to store, increase " + "CONFIG_MQTT_TOPIC_ALIAS_STRING_MAX", + topic_str->size); + set_disconnect_reason(client, MQTT_DISCONNECT_IMPL_SPECIFIC_ERROR); + return -ENOMEM; + } + + memcpy(topic_alias->topic_buf, topic_str->utf8, topic_str->size); + topic_alias->topic_size = topic_str->size; + } return 0; } +#else +static int publish_properties_decode(struct buf_ctx *buf, + struct mqtt_publish_param *param) +{ + ARG_UNUSED(param); + ARG_UNUSED(buf); + + return -ENOTSUP; +} + +static int publish_topic_alias_check(struct mqtt_client *client, + struct mqtt_publish_param *param) +{ + ARG_UNUSED(client); + ARG_UNUSED(param); + + return -ENOTSUP; +} +#endif /* CONFIG_MQTT_VERSION_5_0 */ -int publish_decode(uint8_t flags, uint32_t var_length, struct buf_ctx *buf, +int publish_decode(struct mqtt_client *client, uint8_t flags, + uint32_t var_length, struct buf_ctx *buf, struct mqtt_publish_param *param) { int err_code; @@ -263,6 +848,21 @@ int publish_decode(uint8_t flags, uint32_t var_length, struct buf_ctx *buf, var_header_length += sizeof(uint16_t); } + if (mqtt_is_version_5_0(client)) { + err_code = publish_properties_decode(buf, param); + if (err_code < 0) { + return err_code; + } + + /* Add parsed properties length */ + var_header_length += err_code; + + err_code = publish_topic_alias_check(client, param); + if (err_code < 0) { + return err_code; + } + } + if (var_length < var_header_length) { NET_ERR("Corrupted PUBLISH message, header length (%u) larger " "than total length (%u)", var_header_length, @@ -276,28 +876,153 @@ int publish_decode(uint8_t flags, uint32_t var_length, struct buf_ctx *buf, return 0; } -int publish_ack_decode(struct buf_ctx *buf, struct mqtt_puback_param *param) +#if defined(CONFIG_MQTT_VERSION_5_0) +static int common_ack_properties_decode(struct buf_ctx *buf, + struct mqtt_common_ack_properties *prop) +{ + struct property_decoder prop_dec[] = { + { + &prop->reason_string, + &prop->rx.has_reason_string, + MQTT_PROP_REASON_STRING + }, + { + &prop->user_prop, + &prop->rx.has_user_prop, + MQTT_PROP_USER_PROPERTY + } + }; + + return properties_decode(prop_dec, ARRAY_SIZE(prop_dec), buf); +} +#else +static int common_ack_properties_decode(struct buf_ctx *buf, + struct mqtt_common_ack_properties *prop) +{ + ARG_UNUSED(prop); + ARG_UNUSED(buf); + + return -ENOTSUP; +} +#endif /* CONFIG_MQTT_VERSION_5_0 */ + +static int common_pub_ack_decode(struct buf_ctx *buf, uint16_t *message_id, + uint8_t *reason_code, + struct mqtt_common_ack_properties *prop) +{ + size_t remaining_len; + int err; + + err = unpack_uint16(buf, message_id); + if (err < 0) { + return err; + } + + remaining_len = buf->end - buf->cur; + + /* For MQTT < 5.0 properties are NULL. */ + if (prop != NULL && reason_code != NULL) { + if (remaining_len > 0) { + err = unpack_uint8(buf, reason_code); + if (err < 0) { + return err; + } + } + + if (remaining_len > 1) { + err = common_ack_properties_decode(buf, prop); + if (err < 0) { + return err; + } + } + } + + return 0; +} + +int publish_ack_decode(const struct mqtt_client *client, struct buf_ctx *buf, + struct mqtt_puback_param *param) { - return unpack_uint16(buf, ¶m->message_id); + struct mqtt_common_ack_properties *prop = NULL; + uint8_t *reason_code = NULL; + +#if defined(CONFIG_MQTT_VERSION_5_0) + if (mqtt_is_version_5_0(client)) { + prop = ¶m->prop; + reason_code = ¶m->reason_code; + } +#endif + + return common_pub_ack_decode(buf, ¶m->message_id, reason_code, prop); } -int publish_receive_decode(struct buf_ctx *buf, struct mqtt_pubrec_param *param) +int publish_receive_decode(const struct mqtt_client *client, struct buf_ctx *buf, + struct mqtt_pubrec_param *param) { - return unpack_uint16(buf, ¶m->message_id); + struct mqtt_common_ack_properties *prop = NULL; + uint8_t *reason_code = NULL; + +#if defined(CONFIG_MQTT_VERSION_5_0) + if (mqtt_is_version_5_0(client)) { + prop = ¶m->prop; + reason_code = ¶m->reason_code; + } +#endif + + return common_pub_ack_decode(buf, ¶m->message_id, reason_code, prop); } -int publish_release_decode(struct buf_ctx *buf, struct mqtt_pubrel_param *param) +int publish_release_decode(const struct mqtt_client *client, struct buf_ctx *buf, + struct mqtt_pubrel_param *param) { - return unpack_uint16(buf, ¶m->message_id); + struct mqtt_common_ack_properties *prop = NULL; + uint8_t *reason_code = NULL; + +#if defined(CONFIG_MQTT_VERSION_5_0) + if (mqtt_is_version_5_0(client)) { + prop = ¶m->prop; + reason_code = ¶m->reason_code; + } +#endif + + return common_pub_ack_decode(buf, ¶m->message_id, reason_code, prop); } -int publish_complete_decode(struct buf_ctx *buf, +int publish_complete_decode(const struct mqtt_client *client, struct buf_ctx *buf, struct mqtt_pubcomp_param *param) { - return unpack_uint16(buf, ¶m->message_id); + struct mqtt_common_ack_properties *prop = NULL; + uint8_t *reason_code = NULL; + +#if defined(CONFIG_MQTT_VERSION_5_0) + if (mqtt_is_version_5_0(client)) { + prop = ¶m->prop; + reason_code = ¶m->reason_code; + } +#endif + + return common_pub_ack_decode(buf, ¶m->message_id, reason_code, prop); +} + +#if defined(CONFIG_MQTT_VERSION_5_0) +static int suback_properties_decode(struct buf_ctx *buf, + struct mqtt_suback_param *param) +{ + return common_ack_properties_decode(buf, ¶m->prop); } +#else +static int suback_properties_decode(struct buf_ctx *buf, + struct mqtt_suback_param *param) +{ + ARG_UNUSED(param); + ARG_UNUSED(buf); + + return -ENOTSUP; +} +#endif /* CONFIG_MQTT_VERSION_5_0 */ -int subscribe_ack_decode(struct buf_ctx *buf, struct mqtt_suback_param *param) +int subscribe_ack_decode(const struct mqtt_client *client, struct buf_ctx *buf, + struct mqtt_suback_param *param) { int err_code; @@ -306,11 +1031,178 @@ int subscribe_ack_decode(struct buf_ctx *buf, struct mqtt_suback_param *param) return err_code; } - return unpack_data(buf->end - buf->cur, buf, ¶m->return_codes); + if (mqtt_is_version_5_0(client)) { + err_code = suback_properties_decode(buf, param); + if (err_code < 0) { + return err_code; + } + } + + return unpack_raw_data(buf->end - buf->cur, buf, ¶m->return_codes); } -int unsubscribe_ack_decode(struct buf_ctx *buf, +#if defined(CONFIG_MQTT_VERSION_5_0) +static int unsuback_5_0_decode(struct buf_ctx *buf, + struct mqtt_unsuback_param *param) +{ + int err; + + err = common_ack_properties_decode(buf, ¶m->prop); + if (err < 0) { + return err; + } + + return unpack_raw_data(buf->end - buf->cur, buf, ¶m->reason_codes); +} +#else +static int unsuback_5_0_decode(struct buf_ctx *buf, + struct mqtt_unsuback_param *param) +{ + ARG_UNUSED(param); + ARG_UNUSED(buf); + + return -ENOTSUP; +} +#endif /* CONFIG_MQTT_VERSION_5_0 */ + +int unsubscribe_ack_decode(const struct mqtt_client *client, struct buf_ctx *buf, struct mqtt_unsuback_param *param) { - return unpack_uint16(buf, ¶m->message_id); + int err; + + err = unpack_uint16(buf, ¶m->message_id); + if (err < 0) { + return 0; + } + + if (mqtt_is_version_5_0(client)) { + return unsuback_5_0_decode(buf, param); + } + + return 0; +} + +#if defined(CONFIG_MQTT_VERSION_5_0) +static int disconnect_properties_decode(struct buf_ctx *buf, + struct mqtt_disconnect_param *param) +{ + struct property_decoder prop[] = { + { + ¶m->prop.session_expiry_interval, + ¶m->prop.rx.has_session_expiry_interval, + MQTT_PROP_SESSION_EXPIRY_INTERVAL + }, + { + ¶m->prop.reason_string, + ¶m->prop.rx.has_reason_string, + MQTT_PROP_REASON_STRING + }, + { + ¶m->prop.user_prop, + ¶m->prop.rx.has_user_prop, + MQTT_PROP_USER_PROPERTY + }, + { + ¶m->prop.server_reference, + ¶m->prop.rx.has_server_reference, + MQTT_PROP_SERVER_REFERENCE + } + }; + + return properties_decode(prop, ARRAY_SIZE(prop), buf); +} + +int disconnect_decode(const struct mqtt_client *client, struct buf_ctx *buf, + struct mqtt_disconnect_param *param) +{ + + size_t remaining_len; + uint8_t reason_code; + int err; + + if (!mqtt_is_version_5_0(client)) { + return -ENOTSUP; + } + + remaining_len = buf->end - buf->cur; + + if (remaining_len > 0) { + err = unpack_uint8(buf, &reason_code); + if (err < 0) { + return err; + } + + param->reason_code = reason_code; + } + + if (remaining_len > 1) { + err = disconnect_properties_decode(buf, param); + if (err < 0) { + return err; + } + } + + return 0; +} + +static int auth_properties_decode(struct buf_ctx *buf, + struct mqtt_auth_param *param) +{ + struct property_decoder prop[] = { + { + ¶m->prop.auth_method, + ¶m->prop.rx.has_auth_method, + MQTT_PROP_AUTHENTICATION_METHOD + }, + { + ¶m->prop.auth_data, + ¶m->prop.rx.has_auth_data, + MQTT_PROP_AUTHENTICATION_DATA + }, + { + ¶m->prop.reason_string, + ¶m->prop.rx.has_reason_string, + MQTT_PROP_REASON_STRING + }, + { + ¶m->prop.user_prop, + ¶m->prop.rx.has_user_prop, + MQTT_PROP_USER_PROPERTY + } + }; + + return properties_decode(prop, ARRAY_SIZE(prop), buf); +} + +int auth_decode(const struct mqtt_client *client, struct buf_ctx *buf, + struct mqtt_auth_param *param) +{ + size_t remaining_len; + uint8_t reason_code; + int err; + + if (!mqtt_is_version_5_0(client)) { + return -ENOTSUP; + } + + remaining_len = buf->end - buf->cur; + + if (remaining_len > 0) { + err = unpack_uint8(buf, &reason_code); + if (err < 0) { + return err; + } + + param->reason_code = reason_code; + } + + if (remaining_len > 1) { + err = auth_properties_decode(buf, param); + if (err < 0) { + return err; + } + } + + return 0; } +#endif /* CONFIG_MQTT_VERSION_5_0 */ diff --git a/subsys/net/lib/mqtt/mqtt_encoder.c b/subsys/net/lib/mqtt/mqtt_encoder.c index 7297e8226ff5..4d43e3d03977 100644 --- a/subsys/net/lib/mqtt/mqtt_encoder.c +++ b/subsys/net/lib/mqtt/mqtt_encoder.c @@ -18,7 +18,7 @@ LOG_MODULE_REGISTER(net_mqtt_enc, CONFIG_MQTT_LOG_LEVEL); static const struct mqtt_utf8 mqtt_3_1_0_proto_desc = MQTT_UTF8_LITERAL("MQIsdp"); -static const struct mqtt_utf8 mqtt_3_1_1_proto_desc = +static const struct mqtt_utf8 mqtt_proto_desc = MQTT_UTF8_LITERAL("MQTT"); /** Never changing ping request, needed for Keep Alive. */ @@ -28,7 +28,7 @@ static const uint8_t ping_packet[MQTT_FIXED_HEADER_MIN_SIZE] = { }; /** Never changing disconnect request. */ -static const uint8_t disc_packet[MQTT_FIXED_HEADER_MIN_SIZE] = { +static const uint8_t empty_disc_packet[MQTT_FIXED_HEADER_MIN_SIZE] = { MQTT_PKT_TYPE_DISCONNECT, 0x00 }; @@ -118,10 +118,9 @@ static int pack_utf8_str(const struct mqtt_utf8 *str, struct buf_ctx *buf) } /** - * @brief Computes and encodes length for the MQTT fixed header. + * @brief Computes and encodes variable length integer. * - * @note The remaining length is not packed as a fixed unsigned 32 bit integer. - * Instead it is packed on algorithm below: + * @note The variable length integer is based on algorithm below: * * @code * do @@ -135,36 +134,40 @@ static int pack_utf8_str(const struct mqtt_utf8 *str, struct buf_ctx *buf) * while ( X > 0 ) * @endcode * - * @param[in] length Length of variable header and payload in the MQTT message. + * @param[in] value Integer value to encode. * @param[inout] buf A pointer to the buf_ctx structure containing current * buffer position. May be NULL (in this case function will * only calculate number of bytes needed). * - * @return Number of bytes needed to encode length value. + * @return Number of bytes needed to encode integer or a negative error code. */ -static uint8_t packet_length_encode(uint32_t length, struct buf_ctx *buf) +static int pack_variable_int(uint32_t value, struct buf_ctx *buf) { - uint8_t encoded_bytes = 0U; + int encoded_bytes = 0U; - NET_DBG(">> length:0x%08x cur:%p, end:%p", length, + NET_DBG(">> value:0x%08x cur:%p, end:%p", value, (buf == NULL) ? 0 : (void *)buf->cur, (buf == NULL) ? 0 : (void *)buf->end); do { encoded_bytes++; if (buf != NULL) { - *(buf->cur) = length & MQTT_LENGTH_VALUE_MASK; + if (buf->cur >= buf->end) { + return -ENOMEM; + } + + *(buf->cur) = value & MQTT_LENGTH_VALUE_MASK; } - length >>= MQTT_LENGTH_SHIFT; + value >>= MQTT_LENGTH_SHIFT; if (buf != NULL) { - if (length > 0) { + if (value > 0) { *(buf->cur) |= MQTT_LENGTH_CONTINUATION_BIT; } buf->cur++; } - } while (length > 0); + } while (value > 0); return encoded_bytes; } @@ -202,7 +205,7 @@ static uint32_t mqtt_encode_fixed_header(uint8_t message_type, uint8_t *start, NET_DBG("<< msg type:0x%02x length:0x%08x", message_type, length); - fixed_header_length = packet_length_encode(length, NULL); + fixed_header_length = pack_variable_int(length, NULL); fixed_header_length += sizeof(uint8_t); NET_DBG("Fixed header length = %02x", fixed_header_length); @@ -211,7 +214,7 @@ static uint32_t mqtt_encode_fixed_header(uint8_t message_type, uint8_t *start, buf->cur = start - fixed_header_length; (void)pack_uint8(message_type, buf); - (void)packet_length_encode(length, buf); + (void)pack_variable_int(length, buf); /* Set the cur pointer back at the start of the frame, * and end pointer to the end of the frame. @@ -239,65 +242,505 @@ static int zero_len_str_encode(struct buf_ctx *buf) return pack_uint16(0x0000, buf); } +#if defined(CONFIG_MQTT_VERSION_5_0) /** - * @brief Encodes and sends messages that contain only message id in - * the variable header. + * @brief Packs unsigned 32 bit value to the buffer at the offset requested. * - * @param[in] message_type Message type and reserved bit fields. - * @param[in] message_id Message id to be encoded in the variable header. - * @param[inout] buf_ctx Pointer to the buffer context structure, - * containing buffer for the encoded message. + * @param[in] val Value to be packed. + * @param[inout] buf A pointer to the buf_ctx structure containing current + * buffer position. * - * @retval 0 or an error code indicating a reason for failure. + * @retval 0 if the procedure is successful. + * @retval -ENOMEM if there is no place in the buffer to store the value. */ -static int mqtt_message_id_only_enc(uint8_t message_type, uint16_t message_id, - struct buf_ctx *buf) +static int pack_uint32(uint32_t val, struct buf_ctx *buf) { - int err_code; - uint8_t *start; + uint8_t *cur = buf->cur; + uint8_t *end = buf->end; - /* Message id zero is not permitted by spec. */ - if (message_id == 0U) { - return -EINVAL; + if ((end - cur) < sizeof(uint32_t)) { + return -ENOMEM; } - /* Reserve space for fixed header. */ - buf->cur += MQTT_FIXED_HEADER_MAX_SIZE; - start = buf->cur; + /* Pack value. */ + sys_put_be32(val, cur); + buf->cur = (cur + sizeof(uint32_t)); - err_code = pack_uint16(message_id, buf); - if (err_code != 0) { - return err_code; + return 0; +} + +/** + * @brief Packs binary data to the buffer at the offset requested. + * + * @param[in] bin Binary data and its length to be packed. + * @param[inout] buf A pointer to the buf_ctx structure containing current + * buffer position. + * + * @retval 0 if the procedure is successful. + * @retval -ENOMEM if there is no place in the buffer to store the data. + */ +static int pack_bin_data(const struct mqtt_binstr *bin, struct buf_ctx *buf) +{ + if ((buf->end - buf->cur) < GET_BINSTR_BUFFER_SIZE(bin)) { + return -ENOMEM; } - return mqtt_encode_fixed_header(message_type, start, buf); + /* Pack length followed by binary data. */ + (void)pack_uint16(bin->len, buf); + + memcpy(buf->cur, bin->data, bin->len); + buf->cur += bin->len; + + return 0; +} + +static uint8_t get_uint8_property_default(uint8_t prop) +{ + if (prop == MQTT_PROP_REQUEST_PROBLEM_INFORMATION) { + return 1; + } + + return 0; +} + +static size_t uint8_property_length(uint8_t prop, uint8_t value) +{ + uint8_t prop_default = get_uint8_property_default(prop); + + if (value == prop_default) { + return 0; + } + + return sizeof(uint8_t) + sizeof(uint8_t); +} + +static int encode_uint8_property(uint8_t prop, uint8_t value, struct buf_ctx *buf) +{ + uint8_t prop_default = get_uint8_property_default(prop); + int err; + + if (value == prop_default) { + return 0; + } + + err = pack_uint8(prop, buf); + if (err < 0) { + return err; + } + + return pack_uint8(value, buf); +} + +static size_t uint16_property_length(uint16_t value) +{ + if (value == 0) { + return 0; + } + + return sizeof(uint8_t) + sizeof(uint16_t); +} + +static int encode_uint16_property(uint8_t prop, uint16_t value, + struct buf_ctx *buf) +{ + int err; + + if (value == 0) { + return 0; + } + + err = pack_uint8(prop, buf); + if (err < 0) { + return err; + } + + return pack_uint16(value, buf); +} + +static size_t uint32_property_length(uint32_t value) +{ + if (value == 0) { + return 0; + } + + return sizeof(uint8_t) + sizeof(uint32_t); +} + +static int encode_uint32_property(uint8_t prop, uint32_t value, + struct buf_ctx *buf) +{ + int err; + + if (value == 0) { + return 0; + } + + err = pack_uint8(prop, buf); + if (err < 0) { + return err; + } + + return pack_uint32(value, buf); +} + +static size_t var_int_property_length(uint32_t value) +{ + if (value == 0) { + return 0; + } + + return sizeof(uint8_t) + (size_t)pack_variable_int(value, NULL); +} + +static int encode_var_int_property(uint8_t prop, uint32_t value, + struct buf_ctx *buf) +{ + int err; + + if (value == 0) { + return 0; + } + + err = pack_uint8(prop, buf); + if (err < 0) { + return err; + } + + return pack_variable_int(value, buf); +} + +static size_t string_property_length(const struct mqtt_utf8 *str) +{ + if (str->size == 0) { + return 0; + } + + return sizeof(uint8_t) + GET_UT8STR_BUFFER_SIZE(str); +} + +static int encode_string_property(uint8_t prop, const struct mqtt_utf8 *str, + struct buf_ctx *buf) +{ + int err; + + if (str->size == 0) { + return 0; + } + + err = pack_uint8(prop, buf); + if (err < 0) { + return err; + } + + return pack_utf8_str(str, buf); +} + +static size_t string_pair_property_length(const struct mqtt_utf8 *name, + const struct mqtt_utf8 *value) +{ + if ((name->size == 0) || (value->size == 0)) { + return 0; + } + + return sizeof(uint8_t) + GET_UT8STR_BUFFER_SIZE(name) + + GET_UT8STR_BUFFER_SIZE(value); +} + +static int encode_string_pair_property(uint8_t prop, const struct mqtt_utf8 *name, + const struct mqtt_utf8 *value, + struct buf_ctx *buf) +{ + int err; + + if ((name->size == 0) || (value->size == 0)) { + return 0; + } + + err = pack_uint8(prop, buf); + if (err < 0) { + return err; + } + + err = pack_utf8_str(name, buf); + if (err < 0) { + return err; + } + + return pack_utf8_str(value, buf); +} + +static size_t binary_property_length(const struct mqtt_binstr *bin) +{ + if (bin->len == 0) { + return 0; + } + + return sizeof(uint8_t) + GET_BINSTR_BUFFER_SIZE(bin); +} + +static int encode_binary_property(uint8_t prop, const struct mqtt_binstr *bin, + struct buf_ctx *buf) +{ + int err; + + if (bin->len == 0) { + return 0; + } + + err = pack_uint8(prop, buf); + if (err < 0) { + return err; + } + + return pack_bin_data(bin, buf); +} + +static size_t user_properties_length(const struct mqtt_utf8_pair *user_props) +{ + size_t total_len = 0; + size_t len; + + for (int i = 0; i < CONFIG_MQTT_USER_PROPERTIES_MAX; i++) { + len = string_pair_property_length(&user_props[i].name, + &user_props[i].value); + if (len == 0) { + break; + } + + total_len += len; + } + + return total_len; +} + +static int encode_user_properties(const struct mqtt_utf8_pair *user_props, + struct buf_ctx *buf) +{ + size_t len; + int err; + + for (int i = 0; i < CONFIG_MQTT_USER_PROPERTIES_MAX; i++) { + len = string_pair_property_length(&user_props[i].name, + &user_props[i].value); + + if (len == 0) { + break; + } + + err = encode_string_pair_property(MQTT_PROP_USER_PROPERTY, + &user_props[i].name, + &user_props[i].value, buf); + if (err < 0) { + return err; + } + } + + return 0; +} + +static uint32_t connect_properties_length(const struct mqtt_client *client) +{ + return uint32_property_length(client->prop.session_expiry_interval) + + uint16_property_length(client->prop.receive_maximum) + + uint32_property_length(client->prop.maximum_packet_size) + + uint16_property_length(CONFIG_MQTT_TOPIC_ALIAS_MAX) + + uint8_property_length(MQTT_PROP_REQUEST_RESPONSE_INFORMATION, + client->prop.request_response_info ? 1U : 0U) + + uint8_property_length(MQTT_PROP_REQUEST_PROBLEM_INFORMATION, + client->prop.request_problem_info ? 1U : 0U) + + user_properties_length(client->prop.user_prop) + + string_property_length(&client->prop.auth_method) + + binary_property_length(&client->prop.auth_data); +} + +static int connect_properties_encode(const struct mqtt_client *client, + struct buf_ctx *buf) +{ + uint32_t properties_len; + int err; + + /* Precalculate total properties length */ + properties_len = connect_properties_length(client); + err = pack_variable_int(properties_len, buf); + if (err < 0) { + return err; + } + + err = encode_uint32_property(MQTT_PROP_SESSION_EXPIRY_INTERVAL, + client->prop.session_expiry_interval, buf); + if (err < 0) { + return err; + } + + err = encode_uint16_property(MQTT_PROP_RECEIVE_MAXIMUM, + client->prop.receive_maximum, buf); + if (err < 0) { + return err; + } + + err = encode_uint32_property(MQTT_PROP_MAXIMUM_PACKET_SIZE, + client->prop.maximum_packet_size, buf); + if (err < 0) { + return err; + } + + err = encode_uint16_property(MQTT_PROP_TOPIC_ALIAS_MAXIMUM, + CONFIG_MQTT_TOPIC_ALIAS_MAX, buf); + if (err < 0) { + return err; + } + + err = encode_uint8_property(MQTT_PROP_REQUEST_RESPONSE_INFORMATION, + client->prop.request_response_info ? 1U : 0U, + buf); + if (err < 0) { + return err; + } + + err = encode_uint8_property(MQTT_PROP_REQUEST_PROBLEM_INFORMATION, + client->prop.request_problem_info ? 1U : 0U, + buf); + if (err < 0) { + return err; + } + + err = encode_user_properties(client->prop.user_prop, buf); + if (err < 0) { + return err; + } + + err = encode_string_property(MQTT_PROP_AUTHENTICATION_METHOD, + &client->prop.auth_method, buf); + if (err < 0) { + return err; + } + + err = encode_binary_property(MQTT_PROP_AUTHENTICATION_DATA, + &client->prop.auth_data, buf); + if (err < 0) { + return err; + } + + return 0; +} + +static uint32_t will_properties_length(const struct mqtt_client *client) +{ + return uint32_property_length(client->will_prop.will_delay_interval) + + uint8_property_length(MQTT_PROP_PAYLOAD_FORMAT_INDICATOR, + client->will_prop.payload_format_indicator) + + uint32_property_length(client->will_prop.message_expiry_interval) + + string_property_length(&client->will_prop.content_type) + + string_property_length(&client->will_prop.response_topic) + + binary_property_length(&client->will_prop.correlation_data) + + user_properties_length(client->will_prop.user_prop); } +static int will_properties_encode(const struct mqtt_client *client, + struct buf_ctx *buf) +{ + uint32_t properties_len; + int err; + + /* Precalculate total properties length */ + properties_len = will_properties_length(client); + err = pack_variable_int(properties_len, buf); + if (err < 0) { + return err; + } + + err = encode_uint32_property(MQTT_PROP_WILL_DELAY_INTERVAL, + client->will_prop.will_delay_interval, buf); + if (err < 0) { + return err; + } + + err = encode_uint8_property(MQTT_PROP_PAYLOAD_FORMAT_INDICATOR, + client->will_prop.payload_format_indicator, + buf); + if (err < 0) { + return err; + } + + err = encode_uint32_property(MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, + client->will_prop.message_expiry_interval, + buf); + if (err < 0) { + return err; + } + + err = encode_string_property(MQTT_PROP_CONTENT_TYPE, + &client->will_prop.content_type, buf); + if (err < 0) { + return err; + } + + err = encode_string_property(MQTT_PROP_RESPONSE_TOPIC, + &client->will_prop.response_topic, buf); + if (err < 0) { + return err; + } + + err = encode_binary_property(MQTT_PROP_CORRELATION_DATA, + &client->will_prop.correlation_data, buf); + if (err < 0) { + return err; + } + + err = encode_user_properties(client->will_prop.user_prop, buf); + if (err < 0) { + return err; + } + + return 0; +} + +#else +static int connect_properties_encode(const struct mqtt_client *client, + struct buf_ctx *buf) +{ + ARG_UNUSED(client); + ARG_UNUSED(buf); + + return -ENOTSUP; +} + +static int will_properties_encode(const struct mqtt_client *client, + struct buf_ctx *buf) +{ + ARG_UNUSED(client); + ARG_UNUSED(buf); + + return -ENOTSUP; +} +#endif /* CONFIG_MQTT_VERSION_5_0 */ + int connect_request_encode(const struct mqtt_client *client, struct buf_ctx *buf) { uint8_t connect_flags = client->clean_session << 1; const uint8_t message_type = MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_CONNECT, 0, 0, 0); - const struct mqtt_utf8 *mqtt_proto_desc; + const struct mqtt_utf8 *proto_desc; uint8_t *connect_flags_pos; int err_code; uint8_t *start; - if (client->protocol_version == MQTT_VERSION_3_1_1) { - mqtt_proto_desc = &mqtt_3_1_1_proto_desc; + if (client->protocol_version == MQTT_VERSION_3_1_0) { + proto_desc = &mqtt_3_1_0_proto_desc; } else { - mqtt_proto_desc = &mqtt_3_1_0_proto_desc; + /* MQTT 3.1.1 and newer share the same protocol prefix. */ + proto_desc = &mqtt_proto_desc; } /* Reserve space for fixed header. */ buf->cur += MQTT_FIXED_HEADER_MAX_SIZE; start = buf->cur; - NET_HEXDUMP_DBG(mqtt_proto_desc->utf8, mqtt_proto_desc->size, + NET_HEXDUMP_DBG(proto_desc->utf8, proto_desc->size, "Encoding Protocol Description."); - err_code = pack_utf8_str(mqtt_proto_desc, buf); + err_code = pack_utf8_str(proto_desc, buf); if (err_code != 0) { return err_code; } @@ -324,6 +767,14 @@ int connect_request_encode(const struct mqtt_client *client, return err_code; } + /* Properties (MQTT 5.0 only) */ + if (mqtt_is_version_5_0(client)) { + err_code = connect_properties_encode(client, buf); + if (err_code != 0) { + return err_code; + } + } + NET_HEXDUMP_DBG(client->client_id.utf8, client->client_id.size, "Encoding Client Id."); err_code = pack_utf8_str(&client->client_id, buf); @@ -338,6 +789,14 @@ int connect_request_encode(const struct mqtt_client *client, connect_flags |= ((client->will_topic->qos & 0x03) << 3); connect_flags |= client->will_retain << 5; + /* Will properties (MQTT 5.0 only) */ + if (mqtt_is_version_5_0(client)) { + err_code = will_properties_encode(client, buf); + if (err_code != 0) { + return err_code; + } + } + NET_HEXDUMP_DBG(client->will_topic->topic.utf8, client->will_topic->topic.size, "Encoding Will Topic."); @@ -395,7 +854,92 @@ int connect_request_encode(const struct mqtt_client *client, return mqtt_encode_fixed_header(message_type, start, buf); } -int publish_encode(const struct mqtt_publish_param *param, struct buf_ctx *buf) +#if defined(CONFIG_MQTT_VERSION_5_0) +static uint32_t publish_properties_length(const struct mqtt_publish_param *param) +{ + return uint8_property_length(MQTT_PROP_PAYLOAD_FORMAT_INDICATOR, + param->prop.payload_format_indicator) + + uint32_property_length(param->prop.message_expiry_interval) + + uint16_property_length(param->prop.topic_alias) + + string_property_length(¶m->prop.response_topic) + + binary_property_length(¶m->prop.correlation_data) + + user_properties_length(param->prop.user_prop) + + /* Client does not include Subscription Identifier in any case. */ + string_property_length(¶m->prop.content_type); +} + +static int publish_properties_encode(const struct mqtt_publish_param *param, + struct buf_ctx *buf) +{ + uint32_t properties_len; + int err; + + /* Precalculate total properties length */ + properties_len = publish_properties_length(param); + err = pack_variable_int(properties_len, buf); + if (err < 0) { + return err; + } + + err = encode_uint8_property(MQTT_PROP_PAYLOAD_FORMAT_INDICATOR, + param->prop.payload_format_indicator, buf); + if (err < 0) { + return err; + } + + err = encode_uint32_property(MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, + param->prop.message_expiry_interval, buf); + if (err < 0) { + return err; + } + + err = encode_uint16_property(MQTT_PROP_TOPIC_ALIAS, + param->prop.topic_alias, buf); + if (err < 0) { + return err; + } + + err = encode_string_property(MQTT_PROP_RESPONSE_TOPIC, + ¶m->prop.response_topic, buf); + if (err < 0) { + return err; + } + + err = encode_binary_property(MQTT_PROP_CORRELATION_DATA, + ¶m->prop.correlation_data, buf); + if (err < 0) { + return err; + } + + err = encode_user_properties(param->prop.user_prop, buf); + if (err < 0) { + return err; + } + + /* Client does not include Subscription Identifier in any case. */ + + err = encode_string_property(MQTT_PROP_CONTENT_TYPE, + ¶m->prop.content_type, buf); + if (err < 0) { + return err; + } + + return 0; +} +#else +static int publish_properties_encode(const struct mqtt_publish_param *param, + struct buf_ctx *buf) +{ + ARG_UNUSED(param); + ARG_UNUSED(buf); + + return -ENOTSUP; +} +#endif /* CONFIG_MQTT_VERSION_5_0 */ + +int publish_encode(const struct mqtt_client *client, + const struct mqtt_publish_param *param, + struct buf_ctx *buf) { const uint8_t message_type = MQTT_MESSAGES_OPTIONS( MQTT_PKT_TYPE_PUBLISH, param->dup_flag, @@ -424,6 +968,13 @@ int publish_encode(const struct mqtt_publish_param *param, struct buf_ctx *buf) } } + if (mqtt_is_version_5_0(client)) { + err_code = publish_properties_encode(param, buf); + if (err_code != 0) { + return err_code; + } + } + /* Do not copy payload. We move the buffer pointer to ensure that * message length in fixed header is encoded correctly. */ @@ -439,58 +990,366 @@ int publish_encode(const struct mqtt_publish_param *param, struct buf_ctx *buf) return 0; } -int publish_ack_encode(const struct mqtt_puback_param *param, +#if defined(CONFIG_MQTT_VERSION_5_0) +static uint32_t common_ack_properties_length( + const struct mqtt_common_ack_properties *prop) +{ + return user_properties_length(prop->user_prop) + + string_property_length(&prop->reason_string); +} + +static int common_ack_properties_encode( + const struct mqtt_common_ack_properties *prop, + struct buf_ctx *buf) +{ + uint32_t properties_len; + int err; + + /* Precalculate total properties length */ + properties_len = common_ack_properties_length(prop); + /* Properties length can be omitted if equal to 0. */ + if (properties_len == 0) { + return 0; + } + + err = pack_variable_int(properties_len, buf); + if (err < 0) { + return err; + } + + err = encode_user_properties(prop->user_prop, buf); + if (err < 0) { + return err; + } + + err = encode_string_property(MQTT_PROP_REASON_STRING, + &prop->reason_string, buf); + if (err < 0) { + return err; + } + + return 0; +} +#else /* CONFIG_MQTT_VERSION_5_0 */ +static uint32_t common_ack_properties_length( + const struct mqtt_common_ack_properties *prop) +{ + return 0; +} + +static int common_ack_properties_encode( + const struct mqtt_common_ack_properties *prop, + struct buf_ctx *buf) +{ + ARG_UNUSED(prop); + ARG_UNUSED(buf); + + return -ENOTSUP; +} +#endif /* CONFIG_MQTT_VERSION_5_0 */ + +static int common_ack_encode( + uint8_t message_type, uint16_t message_id, uint8_t reason_code, + const struct mqtt_common_ack_properties *prop, + struct buf_ctx *buf) +{ + int err_code; + uint8_t *start; + + /* Message id zero is not permitted by spec. */ + if (message_id == 0U) { + return -EINVAL; + } + + /* Reserve space for fixed header. */ + buf->cur += MQTT_FIXED_HEADER_MAX_SIZE; + start = buf->cur; + + err_code = pack_uint16(message_id, buf); + if (err_code != 0) { + return err_code; + } + + /* For MQTT < 5.0 properties are NULL. */ + if (prop != NULL) { + /* The Reason Code and Property Length can be omitted if the + * Reason Code is 0x00 (Success) and there are no Properties. + */ + if (common_ack_properties_length(prop) == 0 && + reason_code == 0) { + goto out; + } + + err_code = pack_uint8(reason_code, buf); + if (err_code != 0) { + return err_code; + } + + err_code = common_ack_properties_encode(prop, buf); + if (err_code != 0) { + return err_code; + } + } + +out: + return mqtt_encode_fixed_header(message_type, start, buf); +} + +int publish_ack_encode(const struct mqtt_client *client, + const struct mqtt_puback_param *param, struct buf_ctx *buf) { const uint8_t message_type = MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_PUBACK, 0, 0, 0); + const struct mqtt_common_ack_properties *prop = NULL; + uint8_t reason_code = 0; - return mqtt_message_id_only_enc(message_type, param->message_id, buf); +#if defined(CONFIG_MQTT_VERSION_5_0) + if (mqtt_is_version_5_0(client)) { + prop = ¶m->prop; + reason_code = param->reason_code; + } +#endif + + return common_ack_encode(message_type, param->message_id, + reason_code, prop, buf); } -int publish_receive_encode(const struct mqtt_pubrec_param *param, +int publish_receive_encode(const struct mqtt_client *client, + const struct mqtt_pubrec_param *param, struct buf_ctx *buf) { const uint8_t message_type = MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_PUBREC, 0, 0, 0); + const struct mqtt_common_ack_properties *prop = NULL; + uint8_t reason_code = 0; + +#if defined(CONFIG_MQTT_VERSION_5_0) + if (mqtt_is_version_5_0(client)) { + prop = ¶m->prop; + reason_code = param->reason_code; + } +#endif - return mqtt_message_id_only_enc(message_type, param->message_id, buf); + return common_ack_encode(message_type, param->message_id, + reason_code, prop, buf); } -int publish_release_encode(const struct mqtt_pubrel_param *param, +int publish_release_encode(const struct mqtt_client *client, + const struct mqtt_pubrel_param *param, struct buf_ctx *buf) { const uint8_t message_type = MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_PUBREL, 0, 1, 0); + const struct mqtt_common_ack_properties *prop = NULL; + uint8_t reason_code = 0; - return mqtt_message_id_only_enc(message_type, param->message_id, buf); +#if defined(CONFIG_MQTT_VERSION_5_0) + if (mqtt_is_version_5_0(client)) { + prop = ¶m->prop; + reason_code = param->reason_code; + } +#endif + + return common_ack_encode(message_type, param->message_id, + reason_code, prop, buf); } -int publish_complete_encode(const struct mqtt_pubcomp_param *param, +int publish_complete_encode(const struct mqtt_client *client, + const struct mqtt_pubcomp_param *param, struct buf_ctx *buf) { const uint8_t message_type = MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_PUBCOMP, 0, 0, 0); + const struct mqtt_common_ack_properties *prop = NULL; + uint8_t reason_code = 0; + +#if defined(CONFIG_MQTT_VERSION_5_0) + if (mqtt_is_version_5_0(client)) { + prop = ¶m->prop; + reason_code = param->reason_code; + } +#endif - return mqtt_message_id_only_enc(message_type, param->message_id, buf); + return common_ack_encode(message_type, param->message_id, + reason_code, prop, buf); } -int disconnect_encode(struct buf_ctx *buf) +static int empty_disconnect_encode(struct buf_ctx *buf) { uint8_t *cur = buf->cur; uint8_t *end = buf->end; - if ((end - cur) < sizeof(disc_packet)) { + if ((end - cur) < sizeof(empty_disc_packet)) { return -ENOMEM; } - memcpy(cur, disc_packet, sizeof(disc_packet)); - buf->end = (cur + sizeof(disc_packet)); + memcpy(cur, empty_disc_packet, sizeof(empty_disc_packet)); + buf->end = (cur + sizeof(empty_disc_packet)); + + return 0; +} + +#if defined(CONFIG_MQTT_VERSION_5_0) +static uint32_t disconnect_properties_length(const struct mqtt_disconnect_param *param) +{ + return uint32_property_length(param->prop.session_expiry_interval) + + string_property_length(¶m->prop.reason_string) + + user_properties_length(param->prop.user_prop) + + string_property_length(¶m->prop.server_reference); +} + +static int disconnect_properties_encode(const struct mqtt_disconnect_param *param, + struct buf_ctx *buf) +{ + uint32_t properties_len; + int err; + + /* Precalculate total properties length */ + properties_len = disconnect_properties_length(param); + /* Disconnect properties length can be omitted if equal to 0. */ + if (properties_len == 0) { + return 0; + } + + err = pack_variable_int(properties_len, buf); + if (err < 0) { + return err; + } + + err = encode_uint32_property(MQTT_PROP_SESSION_EXPIRY_INTERVAL, + param->prop.session_expiry_interval, buf); + if (err < 0) { + return err; + } + + err = encode_string_property(MQTT_PROP_REASON_STRING, + ¶m->prop.reason_string, buf); + if (err < 0) { + return err; + } + + err = encode_user_properties(param->prop.user_prop, buf); + if (err < 0) { + return err; + } + + err = encode_string_property(MQTT_PROP_SERVER_REFERENCE, + ¶m->prop.server_reference, buf); + if (err < 0) { + return err; + } return 0; } -int subscribe_encode(const struct mqtt_subscription_list *param, +static int disconnect_5_0_encode(const struct mqtt_disconnect_param *param, + struct buf_ctx *buf) +{ + const uint8_t message_type = + MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_DISCONNECT, 0, 0, 0); + uint8_t *start; + int err; + + /* The Reason Code and Property Length can be omitted if the Reason Code + * is 0x00 (Normal disconnecton) and there are no Properties. + */ + if ((param->reason_code == MQTT_DISCONNECT_NORMAL) && + (disconnect_properties_length(param) == 0U)) { + return empty_disconnect_encode(buf); + } + + /* Reserve space for fixed header. */ + buf->cur += MQTT_FIXED_HEADER_MAX_SIZE; + start = buf->cur; + + err = pack_uint8(param->reason_code, buf); + if (err < 0) { + return err; + } + + err = disconnect_properties_encode(param, buf); + if (err != 0) { + return err; + } + + err = mqtt_encode_fixed_header(message_type, start, buf); + if (err != 0) { + return err; + } + + return 0; +} +#else +static int disconnect_5_0_encode(const struct mqtt_disconnect_param *param, + struct buf_ctx *buf) +{ + ARG_UNUSED(param); + ARG_UNUSED(buf); + + return -ENOTSUP; +} +#endif /* CONFIG_MQTT_VERSION_5_0 */ + +int disconnect_encode(const struct mqtt_client *client, + const struct mqtt_disconnect_param *param, + struct buf_ctx *buf) +{ + if (!mqtt_is_version_5_0(client) || param == NULL) { + return empty_disconnect_encode(buf); + } + + return disconnect_5_0_encode(param, buf); +} + +#if defined(CONFIG_MQTT_VERSION_5_0) +static uint32_t subscribe_properties_length( + const struct mqtt_subscription_list *param) +{ + return var_int_property_length(param->prop.subscription_identifier) + + user_properties_length(param->prop.user_prop); +} + +static int subscribe_properties_encode(const struct mqtt_subscription_list *param, + struct buf_ctx *buf) +{ + uint32_t properties_len; + int err; + + /* Precalculate total properties length */ + properties_len = subscribe_properties_length(param); + err = pack_variable_int(properties_len, buf); + if (err < 0) { + return err; + } + + err = encode_var_int_property(MQTT_PROP_SUBSCRIPTION_IDENTIFIER, + param->prop.subscription_identifier, + buf); + if (err < 0) { + return err; + } + + err = encode_user_properties(param->prop.user_prop, buf); + if (err < 0) { + return err; + } + + return 0; +} +#else +static int subscribe_properties_encode(const struct mqtt_subscription_list *param, + struct buf_ctx *buf) +{ + ARG_UNUSED(param); + ARG_UNUSED(buf); + + return -ENOTSUP; +} +#endif /* CONFIG_MQTT_VERSION_5_0 */ + +int subscribe_encode(const struct mqtt_client *client, + const struct mqtt_subscription_list *param, struct buf_ctx *buf) { const uint8_t message_type = MQTT_MESSAGES_OPTIONS( @@ -512,6 +1371,13 @@ int subscribe_encode(const struct mqtt_subscription_list *param, return err_code; } + if (mqtt_is_version_5_0(client)) { + err_code = subscribe_properties_encode(param, buf); + if (err_code != 0) { + return err_code; + } + } + for (i = 0; i < param->list_count; i++) { err_code = pack_utf8_str(¶m->list[i].topic, buf); if (err_code != 0) { @@ -527,7 +1393,46 @@ int subscribe_encode(const struct mqtt_subscription_list *param, return mqtt_encode_fixed_header(message_type, start, buf); } -int unsubscribe_encode(const struct mqtt_subscription_list *param, +#if defined(CONFIG_MQTT_VERSION_5_0) +static uint32_t unsubscribe_properties_length( + const struct mqtt_subscription_list *param) +{ + return user_properties_length(param->prop.user_prop); +} + +static int unsubscribe_properties_encode( + const struct mqtt_subscription_list *param, struct buf_ctx *buf) +{ + uint32_t properties_len; + int err; + + /* Precalculate total properties length */ + properties_len = unsubscribe_properties_length(param); + err = pack_variable_int(properties_len, buf); + if (err < 0) { + return err; + } + + err = encode_user_properties(param->prop.user_prop, buf); + if (err < 0) { + return err; + } + + return 0; +} +#else +static int unsubscribe_properties_encode( + const struct mqtt_subscription_list *param, struct buf_ctx *buf) +{ + ARG_UNUSED(param); + ARG_UNUSED(buf); + + return -ENOTSUP; +} +#endif /* CONFIG_MQTT_VERSION_5_0 */ + +int unsubscribe_encode(const struct mqtt_client *client, + const struct mqtt_subscription_list *param, struct buf_ctx *buf) { const uint8_t message_type = MQTT_MESSAGES_OPTIONS( @@ -544,6 +1449,13 @@ int unsubscribe_encode(const struct mqtt_subscription_list *param, return err_code; } + if (mqtt_is_version_5_0(client)) { + err_code = unsubscribe_properties_encode(param, buf); + if (err_code != 0) { + return err_code; + } + } + for (i = 0; i < param->list_count; i++) { err_code = pack_utf8_str(¶m->list[i].topic, buf); if (err_code != 0) { @@ -568,3 +1480,109 @@ int ping_request_encode(struct buf_ctx *buf) return 0; } + +#if defined(CONFIG_MQTT_VERSION_5_0) +static uint32_t auth_properties_length(const struct mqtt_auth_param *param) +{ + return string_property_length(¶m->prop.auth_method) + + binary_property_length(¶m->prop.auth_data) + + string_property_length(¶m->prop.reason_string) + + user_properties_length(param->prop.user_prop); +} + +static int auth_properties_encode(const struct mqtt_auth_param *param, + struct buf_ctx *buf) +{ + uint32_t properties_len; + int err; + + /* Precalculate total properties length */ + properties_len = auth_properties_length(param); + if (properties_len == 0) { + return 0; + } + + err = pack_variable_int(properties_len, buf); + if (err < 0) { + return err; + } + + err = encode_string_property(MQTT_PROP_AUTHENTICATION_METHOD, + ¶m->prop.auth_method, buf); + if (err < 0) { + return err; + } + + err = encode_binary_property(MQTT_PROP_AUTHENTICATION_DATA, + ¶m->prop.auth_data, buf); + if (err < 0) { + return err; + } + + err = encode_string_property(MQTT_PROP_REASON_STRING, + ¶m->prop.reason_string, buf); + if (err < 0) { + return err; + } + + err = encode_user_properties(param->prop.user_prop, buf); + if (err < 0) { + return err; + } + + return 0; +} + +static int empty_auth_encode(struct buf_ctx *buf) +{ + const uint8_t empty_auth_packet[] = { + MQTT_PKT_TYPE_AUTH, + 0x00 + }; + uint8_t *cur = buf->cur; + uint8_t *end = buf->end; + + if ((end - cur) < sizeof(empty_auth_packet)) { + return -ENOMEM; + } + + memcpy(cur, empty_auth_packet, sizeof(empty_auth_packet)); + buf->end = (cur + sizeof(empty_auth_packet)); + + return 0; +} + +int auth_encode(const struct mqtt_auth_param *param, struct buf_ctx *buf) +{ + const uint8_t message_type = + MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_AUTH, 0, 0, 0); + uint8_t *start; + int err; + + /* Reserve space for fixed header. */ + buf->cur += MQTT_FIXED_HEADER_MAX_SIZE; + start = buf->cur; + + if ((param->reason_code == MQTT_AUTH_SUCCESS) && + (auth_properties_length(param) == 0U)) { + return empty_auth_encode(buf); + } + + err = pack_uint8(param->reason_code, buf); + if (err < 0) { + return err; + } + + err = auth_properties_encode(param, buf); + if (err != 0) { + return err; + } + + err = mqtt_encode_fixed_header(message_type, start, buf); + if (err != 0) { + return err; + } + + return 0; +} +#endif /* CONFIG_MQTT_VERSION_5_0 */ diff --git a/subsys/net/lib/mqtt/mqtt_internal.h b/subsys/net/lib/mqtt/mqtt_internal.h index e64bac87f4ba..a065712fe5f8 100644 --- a/subsys/net/lib/mqtt/mqtt_internal.h +++ b/subsys/net/lib/mqtt/mqtt_internal.h @@ -54,6 +54,36 @@ extern "C" { #define MQTT_PKT_TYPE_PINGREQ 0xC0 #define MQTT_PKT_TYPE_PINGRSP 0xD0 #define MQTT_PKT_TYPE_DISCONNECT 0xE0 +#define MQTT_PKT_TYPE_AUTH 0xF0 + +/**@brief MQTT Property Types. */ +#define MQTT_PROP_PAYLOAD_FORMAT_INDICATOR 0x01 +#define MQTT_PROP_MESSAGE_EXPIRY_INTERVAL 0x02 +#define MQTT_PROP_CONTENT_TYPE 0x03 +#define MQTT_PROP_RESPONSE_TOPIC 0x08 +#define MQTT_PROP_CORRELATION_DATA 0x09 +#define MQTT_PROP_SUBSCRIPTION_IDENTIFIER 0x0B +#define MQTT_PROP_SESSION_EXPIRY_INTERVAL 0x11 +#define MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER 0x12 +#define MQTT_PROP_SERVER_KEEP_ALIVE 0x13 +#define MQTT_PROP_AUTHENTICATION_METHOD 0x15 +#define MQTT_PROP_AUTHENTICATION_DATA 0x16 +#define MQTT_PROP_REQUEST_PROBLEM_INFORMATION 0x17 +#define MQTT_PROP_WILL_DELAY_INTERVAL 0x18 +#define MQTT_PROP_REQUEST_RESPONSE_INFORMATION 0x19 +#define MQTT_PROP_RESPONSE_INFORMATION 0x1A +#define MQTT_PROP_SERVER_REFERENCE 0x1C +#define MQTT_PROP_REASON_STRING 0x1F +#define MQTT_PROP_RECEIVE_MAXIMUM 0x21 +#define MQTT_PROP_TOPIC_ALIAS_MAXIMUM 0x22 +#define MQTT_PROP_TOPIC_ALIAS 0x23 +#define MQTT_PROP_MAXIMUM_QOS 0x24 +#define MQTT_PROP_RETAIN_AVAILABLE 0x25 +#define MQTT_PROP_USER_PROPERTY 0x26 +#define MQTT_PROP_MAXIMUM_PACKET_SIZE 0x27 +#define MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE 0x28 +#define MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE 0x29 +#define MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE 0x2A /**@brief Masks for MQTT header flags. */ #define MQTT_HEADER_DUP_MASK 0x08 @@ -76,7 +106,7 @@ extern "C" { #define GET_UT8STR_BUFFER_SIZE(STR) (sizeof(uint16_t) + (STR)->size) /**@brief Computes total size needed to pack a binary stream. */ -#define GET_BINSTR_BUFFER_SIZE(STR) ((STR)->len) +#define GET_BINSTR_BUFFER_SIZE(STR) (sizeof(uint16_t) + (STR)->len) /**@brief Sets MQTT Client's state with one indicated in 'STATE'. */ #define MQTT_SET_STATE(CLIENT, STATE) ((CLIENT)->internal.state |= (STATE)) @@ -147,6 +177,12 @@ enum mqtt_state { MQTT_STATE_CONNECTED = 0x00000004, }; +static inline int mqtt_is_version_5_0(const struct mqtt_client *client) +{ + return (IS_ENABLED(CONFIG_MQTT_VERSION_5_0) && + client->protocol_version == MQTT_VERSION_5_0); +} + /**@brief Notify application about MQTT event. * * @param[in] client Identifies the client for which event occurred. @@ -162,6 +198,16 @@ void event_notify(struct mqtt_client *client, const struct mqtt_evt *evt); */ int mqtt_handle_rx(struct mqtt_client *client); +/**@brief Disconnect MQTT client. + * + * @param[in] client Identifies the client which disconnects. + * @param[in] result Disconnect reason. + * @param[in] notify Whether to notify the app or not. + + * @return 0 if the procedure is successful, an error code otherwise. + */ +void mqtt_client_disconnect(struct mqtt_client *client, int result, bool notify); + /**@brief Constructs/encodes Connect packet. * * @param[in] client Identifies the client for which the procedure is requested. @@ -189,7 +235,9 @@ int connect_request_encode(const struct mqtt_client *client, * * @return 0 if the procedure is successful, an error code otherwise. */ -int publish_encode(const struct mqtt_publish_param *param, struct buf_ctx *buf); +int publish_encode(const struct mqtt_client *client, + const struct mqtt_publish_param *param, + struct buf_ctx *buf); /**@brief Constructs/encodes Publish Ack packet. * @@ -201,7 +249,8 @@ int publish_encode(const struct mqtt_publish_param *param, struct buf_ctx *buf); * * @return 0 if the procedure is successful, an error code otherwise. */ -int publish_ack_encode(const struct mqtt_puback_param *param, +int publish_ack_encode(const struct mqtt_client *client, + const struct mqtt_puback_param *param, struct buf_ctx *buf); /**@brief Constructs/encodes Publish Receive packet. @@ -214,7 +263,8 @@ int publish_ack_encode(const struct mqtt_puback_param *param, * * @return 0 if the procedure is successful, an error code otherwise. */ -int publish_receive_encode(const struct mqtt_pubrec_param *param, +int publish_receive_encode(const struct mqtt_client *client, + const struct mqtt_pubrec_param *param, struct buf_ctx *buf); /**@brief Constructs/encodes Publish Release packet. @@ -227,7 +277,8 @@ int publish_receive_encode(const struct mqtt_pubrec_param *param, * * @return 0 if the procedure is successful, an error code otherwise. */ -int publish_release_encode(const struct mqtt_pubrel_param *param, +int publish_release_encode(const struct mqtt_client *client, + const struct mqtt_pubrel_param *param, struct buf_ctx *buf); /**@brief Constructs/encodes Publish Complete packet. @@ -240,7 +291,8 @@ int publish_release_encode(const struct mqtt_pubrel_param *param, * * @return 0 if the procedure is successful, an error code otherwise. */ -int publish_complete_encode(const struct mqtt_pubcomp_param *param, +int publish_complete_encode(const struct mqtt_client *client, + const struct mqtt_pubcomp_param *param, struct buf_ctx *buf); /**@brief Constructs/encodes Disconnect packet. @@ -252,7 +304,9 @@ int publish_complete_encode(const struct mqtt_pubcomp_param *param, * * @return 0 if the procedure is successful, an error code otherwise. */ -int disconnect_encode(struct buf_ctx *buf); +int disconnect_encode(const struct mqtt_client *client, + const struct mqtt_disconnect_param *param, + struct buf_ctx *buf); /**@brief Constructs/encodes Subscribe packet. * @@ -264,7 +318,8 @@ int disconnect_encode(struct buf_ctx *buf); * * @return 0 if the procedure is successful, an error code otherwise. */ -int subscribe_encode(const struct mqtt_subscription_list *param, +int subscribe_encode(const struct mqtt_client *client, + const struct mqtt_subscription_list *param, struct buf_ctx *buf); /**@brief Constructs/encodes Unsubscribe packet. @@ -277,7 +332,8 @@ int subscribe_encode(const struct mqtt_subscription_list *param, * * @return 0 if the procedure is successful, an error code otherwise. */ -int unsubscribe_encode(const struct mqtt_subscription_list *param, +int unsubscribe_encode(const struct mqtt_client *client, + const struct mqtt_subscription_list *param, struct buf_ctx *buf); /**@brief Constructs/encodes Ping Request packet. @@ -291,6 +347,20 @@ int unsubscribe_encode(const struct mqtt_subscription_list *param, */ int ping_request_encode(struct buf_ctx *buf); +#if defined(CONFIG_MQTT_VERSION_5_0) +/**@brief Constructs/encodes Authenticate packet. + * + * @param[in] param Authenticate message parameters. + * @param[inout] buf_ctx Pointer to the buffer context structure, + * containing buffer for the encoded message. + * As output points to the beginning and end of + * the frame. + * + * @return 0 if the procedure is successful, an error code otherwise. + */ +int auth_encode(const struct mqtt_auth_param *param, struct buf_ctx *buf); +#endif /* CONFIG_MQTT_VERSION_5_0 */ + /**@brief Decode MQTT Packet Type and Length in the MQTT fixed header. * * @param[inout] buf A pointer to the buf_ctx structure containing current @@ -317,6 +387,7 @@ int connect_ack_decode(const struct mqtt_client *client, struct buf_ctx *buf, /**@brief Decode MQTT Publish packet. * + * @param[inout] MQTT client for which packet is decoded. * @param[in] flags Byte containing message type and flags. * @param[in] var_length Length of the variable part of the message. * @param[inout] buf A pointer to the buf_ctx structure containing current @@ -325,74 +396,144 @@ int connect_ack_decode(const struct mqtt_client *client, struct buf_ctx *buf, * * @return 0 if the procedure is successful, an error code otherwise. */ -int publish_decode(uint8_t flags, uint32_t var_length, struct buf_ctx *buf, +int publish_decode(struct mqtt_client *client, uint8_t flags, + uint32_t var_length, struct buf_ctx *buf, struct mqtt_publish_param *param); /**@brief Decode MQTT Publish Ack packet. * + * @param[in] MQTT client for which packet is decoded. * @param[inout] buf A pointer to the buf_ctx structure containing current * buffer position. * @param[out] param Pointer to buffer for decoded Publish Ack parameters. * * @return 0 if the procedure is successful, an error code otherwise. */ -int publish_ack_decode(struct buf_ctx *buf, struct mqtt_puback_param *param); +int publish_ack_decode(const struct mqtt_client *client, struct buf_ctx *buf, + struct mqtt_puback_param *param); /**@brief Decode MQTT Publish Receive packet. * + * @param[in] MQTT client for which packet is decoded. * @param[inout] buf A pointer to the buf_ctx structure containing current * buffer position. * @param[out] param Pointer to buffer for decoded Publish Receive parameters. * * @return 0 if the procedure is successful, an error code otherwise. */ -int publish_receive_decode(struct buf_ctx *buf, +int publish_receive_decode(const struct mqtt_client *client, struct buf_ctx *buf, struct mqtt_pubrec_param *param); /**@brief Decode MQTT Publish Release packet. * + * @param[in] MQTT client for which packet is decoded. * @param[inout] buf A pointer to the buf_ctx structure containing current * buffer position. * @param[out] param Pointer to buffer for decoded Publish Release parameters. * * @return 0 if the procedure is successful, an error code otherwise. */ -int publish_release_decode(struct buf_ctx *buf, +int publish_release_decode(const struct mqtt_client *client, struct buf_ctx *buf, struct mqtt_pubrel_param *param); /**@brief Decode MQTT Publish Complete packet. * + * @param[in] MQTT client for which packet is decoded. * @param[inout] buf A pointer to the buf_ctx structure containing current * buffer position. * @param[out] param Pointer to buffer for decoded Publish Complete parameters. * * @return 0 if the procedure is successful, an error code otherwise. */ -int publish_complete_decode(struct buf_ctx *buf, +int publish_complete_decode(const struct mqtt_client *client, struct buf_ctx *buf, struct mqtt_pubcomp_param *param); /**@brief Decode MQTT Subscribe packet. * + * @param[in] MQTT client for which packet is decoded. * @param[inout] buf A pointer to the buf_ctx structure containing current * buffer position. * @param[out] param Pointer to buffer for decoded Subscribe parameters. * * @return 0 if the procedure is successful, an error code otherwise. */ -int subscribe_ack_decode(struct buf_ctx *buf, +int subscribe_ack_decode(const struct mqtt_client *client, struct buf_ctx *buf, struct mqtt_suback_param *param); /**@brief Decode MQTT Unsubscribe packet. * + * @param[in] MQTT client for which packet is decoded. * @param[inout] buf A pointer to the buf_ctx structure containing current * buffer position. * @param[out] param Pointer to buffer for decoded Unsubscribe parameters. * * @return 0 if the procedure is successful, an error code otherwise. */ -int unsubscribe_ack_decode(struct buf_ctx *buf, +int unsubscribe_ack_decode(const struct mqtt_client *client, struct buf_ctx *buf, struct mqtt_unsuback_param *param); +#if defined(CONFIG_MQTT_VERSION_5_0) +/**@brief Decode MQTT Disconnect packet. + * + * @param[in] MQTT client for which packet is decoded. + * @param[inout] buf A pointer to the buf_ctx structure containing current + * buffer position. + * @param[out] param Pointer to buffer for decoded Disconnect parameters. + * + * @return 0 if the procedure is successful, an error code otherwise. + */ +int disconnect_decode(const struct mqtt_client *client, struct buf_ctx *buf, + struct mqtt_disconnect_param *param); + +/**@brief Decode MQTT Auth packet. + * + * @param[in] MQTT client for which packet is decoded. + * @param[inout] buf A pointer to the buf_ctx structure containing current + * buffer position. + * @param[out] param Pointer to buffer for decoded Auth parameters. + * + * @return 0 if the procedure is successful, an error code otherwise. + */ +int auth_decode(const struct mqtt_client *client, struct buf_ctx *buf, + struct mqtt_auth_param *param); + +/**@brief Set MQTT 5.0 disconnect reason. + * + * Packet parser can use this function to set a custom disconnect reason code, + * if not set, the client will use the default mapping between errno values and + * reason codes. + * + * @param[inout] MQTT client. + * @param[in] reason MQTT 5.0 disconnect reason code. + */ +static inline void set_disconnect_reason(struct mqtt_client *client, + enum mqtt_disconnect_reason_code reason) +{ + client->internal.disconnect_reason = reason; +} +#else +static inline void set_disconnect_reason(struct mqtt_client *client, + enum mqtt_disconnect_reason_code reason) +{ + ARG_UNUSED(client); + ARG_UNUSED(reason); +} +#endif /* CONFIG_MQTT_VERSION_5_0 */ + +/** + * @brief Unpacks variable length integer from the buffer from the offset + * requested. + * + * @param[inout] buf A pointer to the buf_ctx structure containing current + * buffer position. + * @param[out] val Memory where the value is to be unpacked. + * + * @retval Number of bytes parsed if the procedure is successful. + * @retval -EINVAL if the length decoding would use more that 4 bytes. + * @retval -EAGAIN if the buffer would be exceeded during the read. + */ +int unpack_variable_int(struct buf_ctx *buf, uint32_t *val); + #ifdef __cplusplus } #endif diff --git a/subsys/net/lib/mqtt/mqtt_rx.c b/subsys/net/lib/mqtt/mqtt_rx.c index 27fa2410d2c7..057bccce9cb1 100644 --- a/subsys/net/lib/mqtt/mqtt_rx.c +++ b/subsys/net/lib/mqtt/mqtt_rx.c @@ -23,7 +23,7 @@ static int mqtt_handle_packet(struct mqtt_client *client, { int err_code = 0; bool notify_event = true; - struct mqtt_evt evt; + struct mqtt_evt evt = { 0 }; /* Success by default, overwritten in special cases. */ evt.result = 0; @@ -38,6 +38,9 @@ static int mqtt_handle_packet(struct mqtt_client *client, NET_DBG("[CID %p]: return_code: %d", client, evt.param.connack.return_code); + /* For MQTT 5.0 this is still valid as MQTT_CONNACK_SUCCESS + * is encoded as 0 as well. + */ if (evt.param.connack.return_code == MQTT_CONNECTION_ACCEPTED) { /* Set state. */ @@ -57,8 +60,8 @@ static int mqtt_handle_packet(struct mqtt_client *client, NET_DBG("[CID %p]: Received MQTT_PKT_TYPE_PUBLISH", client); evt.type = MQTT_EVT_PUBLISH; - err_code = publish_decode(type_and_flags, var_length, buf, - &evt.param.publish); + err_code = publish_decode(client, type_and_flags, var_length, + buf, &evt.param.publish); evt.result = err_code; client->internal.remaining_payload = @@ -75,7 +78,7 @@ static int mqtt_handle_packet(struct mqtt_client *client, NET_DBG("[CID %p]: Received MQTT_PKT_TYPE_PUBACK!", client); evt.type = MQTT_EVT_PUBACK; - err_code = publish_ack_decode(buf, &evt.param.puback); + err_code = publish_ack_decode(client, buf, &evt.param.puback); evt.result = err_code; break; @@ -83,7 +86,8 @@ static int mqtt_handle_packet(struct mqtt_client *client, NET_DBG("[CID %p]: Received MQTT_PKT_TYPE_PUBREC!", client); evt.type = MQTT_EVT_PUBREC; - err_code = publish_receive_decode(buf, &evt.param.pubrec); + err_code = publish_receive_decode(client, buf, + &evt.param.pubrec); evt.result = err_code; break; @@ -91,7 +95,8 @@ static int mqtt_handle_packet(struct mqtt_client *client, NET_DBG("[CID %p]: Received MQTT_PKT_TYPE_PUBREL!", client); evt.type = MQTT_EVT_PUBREL; - err_code = publish_release_decode(buf, &evt.param.pubrel); + err_code = publish_release_decode(client, buf, + &evt.param.pubrel); evt.result = err_code; break; @@ -99,7 +104,8 @@ static int mqtt_handle_packet(struct mqtt_client *client, NET_DBG("[CID %p]: Received MQTT_PKT_TYPE_PUBCOMP!", client); evt.type = MQTT_EVT_PUBCOMP; - err_code = publish_complete_decode(buf, &evt.param.pubcomp); + err_code = publish_complete_decode(client, buf, + &evt.param.pubcomp); evt.result = err_code; break; @@ -107,7 +113,7 @@ static int mqtt_handle_packet(struct mqtt_client *client, NET_DBG("[CID %p]: Received MQTT_PKT_TYPE_SUBACK!", client); evt.type = MQTT_EVT_SUBACK; - err_code = subscribe_ack_decode(buf, &evt.param.suback); + err_code = subscribe_ack_decode(client, buf, &evt.param.suback); evt.result = err_code; break; @@ -115,7 +121,8 @@ static int mqtt_handle_packet(struct mqtt_client *client, NET_DBG("[CID %p]: Received MQTT_PKT_TYPE_UNSUBACK!", client); evt.type = MQTT_EVT_UNSUBACK; - err_code = unsubscribe_ack_decode(buf, &evt.param.unsuback); + err_code = unsubscribe_ack_decode(client, buf, + &evt.param.unsuback); evt.result = err_code; break; @@ -132,6 +139,35 @@ static int mqtt_handle_packet(struct mqtt_client *client, evt.type = MQTT_EVT_PINGRESP; break; +#if defined(CONFIG_MQTT_VERSION_5_0) + case MQTT_PKT_TYPE_DISCONNECT: + evt.type = MQTT_EVT_DISCONNECT; + err_code = disconnect_decode(client, buf, &evt.param.disconnect); + if (err_code == 0) { + evt.result = evt.param.disconnect.reason_code; + /* Don't notify yet, the code below will handle this. */ + mqtt_client_disconnect(client, evt.result, false); + } else { + /* Again, don't notify yet, error handling code will + * disconnect and report error. + */ + notify_event = false; + } + + break; + + case MQTT_PKT_TYPE_AUTH: + evt.type = MQTT_EVT_AUTH; + err_code = auth_decode(client, buf, &evt.param.auth); + if (err_code == 0) { + evt.result = evt.param.auth.reason_code; + } else { + notify_event = false; + } + + break; +#endif + default: /* Nothing to notify. */ notify_event = false; @@ -218,7 +254,42 @@ static int mqtt_read_publish_var_header(struct mqtt_client *client, variable_header_length += sizeof(uint16_t); } - /* Now we can read the whole header. */ + if (mqtt_is_version_5_0(client)) { + struct buf_ctx backup; + uint8_t var_len = 1; + uint32_t prop_len = 0; + + while (true) { + err_code = mqtt_read_message_chunk( + client, buf, variable_header_length + var_len); + if (err_code < 0) { + return err_code; + } + + backup = *buf; + buf->cur += variable_header_length; + + /* Try to decode variable integer, in case integer is + * not complete, read more bytes from the stream and retry. + */ + err_code = unpack_variable_int(buf, &prop_len); + if (err_code >= 0) { + break; + } + + if (err_code != -EAGAIN) { + return err_code; + } + + /* Try again. */ + var_len++; + *buf = backup; + } + + *buf = backup; + variable_header_length += var_len + prop_len; + } + err_code = mqtt_read_message_chunk(client, buf, variable_header_length); if (err_code < 0) { diff --git a/subsys/shell/backends/shell_mqtt.c b/subsys/shell/backends/shell_mqtt.c index 9b474436fd4e..d94c43f144e5 100644 --- a/subsys/shell/backends/shell_mqtt.c +++ b/subsys/shell/backends/shell_mqtt.c @@ -166,7 +166,7 @@ static void sh_mqtt_close_and_cleanup(void) */ if ((sh_mqtt->network_state == SHELL_MQTT_NETWORK_CONNECTED) && (sh_mqtt->transport_state == SHELL_MQTT_TRANSPORT_CONNECTED)) { - rc = mqtt_disconnect(&sh_mqtt->mqtt_cli); + rc = mqtt_disconnect(&sh_mqtt->mqtt_cli, NULL); } /* If network/mqtt disconnected, or mqtt_disconnect failed, do mqtt_abort */ diff --git a/tests/net/lib/mqtt/v3_1_1/mqtt_client/prj.conf b/tests/net/lib/mqtt/v3_1_1/mqtt_client/prj.conf index 3a5f7c771cc0..7060b3c22459 100644 --- a/tests/net/lib/mqtt/v3_1_1/mqtt_client/prj.conf +++ b/tests/net/lib/mqtt/v3_1_1/mqtt_client/prj.conf @@ -16,6 +16,7 @@ CONFIG_NET_PKT_RX_COUNT=16 CONFIG_NET_TCP=y CONFIG_NET_TCP_TIME_WAIT_DELAY=0 CONFIG_MQTT_LIB=y +CONFIG_MQTT_VERSION_3_1_1=y CONFIG_MAIN_STACK_SIZE=2048 CONFIG_ZTEST_STACK_SIZE=2048 diff --git a/tests/net/lib/mqtt/v3_1_1/mqtt_client/src/main.c b/tests/net/lib/mqtt/v3_1_1/mqtt_client/src/main.c index 12c9eb317409..164546161c70 100644 --- a/tests/net/lib/mqtt/v3_1_1/mqtt_client/src/main.c +++ b/tests/net/lib/mqtt/v3_1_1/mqtt_client/src/main.c @@ -693,7 +693,7 @@ static void test_disconnect(void) { int ret; - ret = mqtt_disconnect(&client_ctx); + ret = mqtt_disconnect(&client_ctx, NULL); zassert_ok(ret, "MQTT client failed to disconnect (%d)", ret); broker_process(MQTT_PKT_TYPE_DISCONNECT); diff --git a/tests/net/lib/mqtt/v3_1_1/mqtt_client/testcase.yaml b/tests/net/lib/mqtt/v3_1_1/mqtt_client/testcase.yaml index d96ed8fda59b..eb371eddfbd3 100644 --- a/tests/net/lib/mqtt/v3_1_1/mqtt_client/testcase.yaml +++ b/tests/net/lib/mqtt/v3_1_1/mqtt_client/testcase.yaml @@ -11,3 +11,6 @@ tests: net.mqtt.client.preempt: extra_configs: - CONFIG_NET_TC_THREAD_PREEMPTIVE=y + net.mqtt.client.mqtt_5_0: + extra_configs: + - CONFIG_MQTT_VERSION_5_0=y diff --git a/tests/net/lib/mqtt/v3_1_1/mqtt_packet/prj.conf b/tests/net/lib/mqtt/v3_1_1/mqtt_packet/prj.conf index 1c4a907a1558..44ec7a64aaa0 100644 --- a/tests/net/lib/mqtt/v3_1_1/mqtt_packet/prj.conf +++ b/tests/net/lib/mqtt/v3_1_1/mqtt_packet/prj.conf @@ -8,6 +8,7 @@ CONFIG_TEST_RANDOM_GENERATOR=y # enable the MQTT lib CONFIG_MQTT_LIB=y +CONFIG_MQTT_VERSION_3_1_1=y CONFIG_ZTEST=y CONFIG_TEST_USERSPACE=y CONFIG_MAIN_STACK_SIZE=1280 diff --git a/tests/net/lib/mqtt/v3_1_1/mqtt_packet/src/mqtt_packet.c b/tests/net/lib/mqtt/v3_1_1/mqtt_packet/src/mqtt_packet.c index b6cf7390d7fc..6a47b4c3a810 100644 --- a/tests/net/lib/mqtt/v3_1_1/mqtt_packet/src/mqtt_packet.c +++ b/tests/net/lib/mqtt/v3_1_1/mqtt_packet/src/mqtt_packet.c @@ -746,7 +746,7 @@ static int eval_msg_disconnect(struct mqtt_test *mqtt_test) buf.cur = client.tx_buf; buf.end = client.tx_buf + client.tx_buf_size; - rc = disconnect_encode(&buf); + rc = disconnect_encode(&client, NULL, &buf); /**TESTPOINTS: Check disconnect_encode functions*/ zassert_false(rc, "disconnect_encode failed"); @@ -773,7 +773,7 @@ static int eval_msg_publish(struct mqtt_test *mqtt_test) buf.cur = client.tx_buf; buf.end = client.tx_buf + client.tx_buf_size; - rc = publish_encode(param, &buf); + rc = publish_encode(&client, param, &buf); /* Payload is not copied, copy it manually just after the header.*/ memcpy(buf.end, param->message.payload.data, @@ -791,7 +791,7 @@ static int eval_msg_publish(struct mqtt_test *mqtt_test) zassert_false(rc, "fixed_header_decode failed"); - rc = publish_decode(type_and_flags, length, &buf, &dec_param); + rc = publish_decode(&client, type_and_flags, length, &buf, &dec_param); /**TESTPOINT: Check publish_decode function*/ zassert_false(rc, "publish_decode failed"); @@ -830,7 +830,7 @@ static int eval_msg_corrupted_publish(struct mqtt_test *mqtt_test) rc = fixed_header_decode(buf, &type_and_flags, &length); zassert_equal(rc, 0, "fixed_header_decode failed"); - rc = publish_decode(type_and_flags, length, buf, &dec_param); + rc = publish_decode(&client, type_and_flags, length, buf, &dec_param); zassert_equal(rc, -EINVAL, "publish_decode should fail"); return TC_PASS; @@ -846,7 +846,7 @@ static int eval_msg_subscribe(struct mqtt_test *mqtt_test) buf.cur = client.tx_buf; buf.end = client.tx_buf + client.tx_buf_size; - rc = subscribe_encode(param, &buf); + rc = subscribe_encode(&client, param, &buf); /**TESTPOINT: Check subscribe_encode function*/ zassert_false(rc, "subscribe_encode failed"); @@ -874,7 +874,7 @@ static int eval_msg_suback(struct mqtt_test *mqtt_test) zassert_false(rc, "fixed_header_decode failed"); - rc = subscribe_ack_decode(&buf, &dec_param); + rc = subscribe_ack_decode(&client, &buf, &dec_param); /**TESTPOINT: Check subscribe_ack_decode function*/ zassert_false(rc, "subscribe_ack_decode failed"); @@ -927,7 +927,7 @@ static int eval_msg_puback(struct mqtt_test *mqtt_test) buf.cur = client.tx_buf; buf.end = client.tx_buf + client.tx_buf_size; - rc = publish_ack_encode(param, &buf); + rc = publish_ack_encode(&client, param, &buf); /**TESTPOINTS: Check publish_ack_encode functions*/ zassert_false(rc, "publish_ack_encode failed"); @@ -940,7 +940,7 @@ static int eval_msg_puback(struct mqtt_test *mqtt_test) zassert_false(rc, "fixed_header_decode failed"); - rc = publish_ack_decode(&buf, &dec_param); + rc = publish_ack_decode(&client, &buf, &dec_param); zassert_false(rc, "publish_ack_decode failed"); @@ -965,7 +965,7 @@ static int eval_msg_pubcomp(struct mqtt_test *mqtt_test) buf.cur = client.tx_buf; buf.end = client.tx_buf + client.tx_buf_size; - rc = publish_complete_encode(param, &buf); + rc = publish_complete_encode(&client, param, &buf); /**TESTPOINTS: Check publish_complete_encode functions*/ zassert_false(rc, "publish_complete_encode failed"); @@ -978,7 +978,7 @@ static int eval_msg_pubcomp(struct mqtt_test *mqtt_test) zassert_false(rc, "fixed_header_decode failed"); - rc = publish_complete_decode(&buf, &dec_param); + rc = publish_complete_decode(&client, &buf, &dec_param); zassert_false(rc, "publish_complete_decode failed"); @@ -1003,7 +1003,7 @@ static int eval_msg_pubrec(struct mqtt_test *mqtt_test) buf.cur = client.tx_buf; buf.end = client.tx_buf + client.tx_buf_size; - rc = publish_receive_encode(param, &buf); + rc = publish_receive_encode(&client, param, &buf); /**TESTPOINTS: Check publish_receive_encode functions*/ zassert_false(rc, "publish_receive_encode failed"); @@ -1016,7 +1016,7 @@ static int eval_msg_pubrec(struct mqtt_test *mqtt_test) zassert_false(rc, "fixed_header_decode failed"); - rc = publish_receive_decode(&buf, &dec_param); + rc = publish_receive_decode(&client, &buf, &dec_param); zassert_false(rc, "publish_receive_decode failed"); @@ -1041,7 +1041,7 @@ static int eval_msg_pubrel(struct mqtt_test *mqtt_test) buf.cur = client.tx_buf; buf.end = client.tx_buf + client.tx_buf_size; - rc = publish_release_encode(param, &buf); + rc = publish_release_encode(&client, param, &buf); /**TESTPOINTS: Check publish_release_encode functions*/ zassert_false(rc, "publish_release_encode failed"); @@ -1054,7 +1054,7 @@ static int eval_msg_pubrel(struct mqtt_test *mqtt_test) zassert_false(rc, "fixed_header_decode failed"); - rc = publish_release_decode(&buf, &dec_param); + rc = publish_release_decode(&client, &buf, &dec_param); zassert_false(rc, "publish_release_decode failed"); @@ -1083,7 +1083,7 @@ static int eval_msg_unsuback(struct mqtt_test *mqtt_test) zassert_false(rc, "fixed_header_decode failed"); - rc = unsubscribe_ack_decode(&buf, &dec_param); + rc = unsubscribe_ack_decode(&client, &buf, &dec_param); zassert_false(rc, "unsubscribe_ack_decode failed"); diff --git a/tests/net/lib/mqtt/v3_1_1/mqtt_packet/testcase.yaml b/tests/net/lib/mqtt/v3_1_1/mqtt_packet/testcase.yaml index af39e89499aa..fdb05ed97adb 100644 --- a/tests/net/lib/mqtt/v3_1_1/mqtt_packet/testcase.yaml +++ b/tests/net/lib/mqtt/v3_1_1/mqtt_packet/testcase.yaml @@ -1,9 +1,12 @@ common: + tags: + - mqtt + - net + - userspace + min_ram: 16 depends_on: netif tests: - net.mqtt.packet: - min_ram: 16 - tags: - - mqtt - - net - - userspace + net.mqtt.packet: {} + net.mqtt.packet.mqtt_5_0: + extra_configs: + - CONFIG_MQTT_VERSION_5_0=y diff --git a/tests/net/lib/mqtt/v5_0/mqtt_packet/CMakeLists.txt b/tests/net/lib/mqtt/v5_0/mqtt_packet/CMakeLists.txt new file mode 100644 index 000000000000..7d7d86b29a95 --- /dev/null +++ b/tests/net/lib/mqtt/v5_0/mqtt_packet/CMakeLists.txt @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(mqtt_5_packet) + +target_include_directories(app PRIVATE + ${ZEPHYR_BASE}/subsys/net/lib/mqtt +) +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/net/lib/mqtt/v5_0/mqtt_packet/prj.conf b/tests/net/lib/mqtt/v5_0/mqtt_packet/prj.conf new file mode 100644 index 000000000000..60552beb0b2c --- /dev/null +++ b/tests/net/lib/mqtt/v5_0/mqtt_packet/prj.conf @@ -0,0 +1,7 @@ +CONFIG_ZTEST=y +CONFIG_NET_TEST=y +CONFIG_NETWORKING=y +CONFIG_ENTROPY_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_MQTT_LIB=y +CONFIG_MQTT_VERSION_5_0=y diff --git a/tests/net/lib/mqtt/v5_0/mqtt_packet/src/mqtt_packet.c b/tests/net/lib/mqtt/v5_0/mqtt_packet/src/mqtt_packet.c new file mode 100644 index 000000000000..80eb96738d63 --- /dev/null +++ b/tests/net/lib/mqtt/v5_0/mqtt_packet/src/mqtt_packet.c @@ -0,0 +1,2068 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "mqtt_internal.h" + +static uint8_t auth_data[] = { 0x01, 0x02, 0x03, 0x04 }; +static uint8_t correlation_data[] = { 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 }; +static uint8_t test_payload[] = { 't', 'e', 's', 't', '_', 'p', 'a', 'y', 'l', + 'o', 'a', 'd' }; + +#define BUFFER_SIZE 256 +#define CLIENTID MQTT_UTF8_LITERAL("test_id") +#define WILL_TOPIC MQTT_UTF8_LITERAL("test_will") +#define WILL_MSG MQTT_UTF8_LITERAL("test_will_msg") +#define USER_PROP_NAME MQTT_UTF8_LITERAL("test_name") +#define USER_PROP_VALUE MQTT_UTF8_LITERAL("test_value") +#define AUTH_METHOD MQTT_UTF8_LITERAL("test_authentication") +#define AUTH_DATA (struct mqtt_binstr) { auth_data, sizeof(auth_data) } +#define CONTENT_TYPE MQTT_UTF8_LITERAL("test_content_type") +#define RESPONSE_TOPIC MQTT_UTF8_LITERAL("test_response_topic") +#define CORRELATION_DATA (struct mqtt_binstr) { correlation_data, sizeof(correlation_data) } +#define TEST_TOPIC MQTT_UTF8_LITERAL("test_topic") +#define TEST_PAYLOAD (struct mqtt_binstr) { test_payload, sizeof(test_payload) } +#define TEST_MSG_ID 0x1234 + +static uint8_t rx_buffer[BUFFER_SIZE]; +static uint8_t tx_buffer[BUFFER_SIZE]; +static struct mqtt_client client; + +struct mqtt_test { + /* Test name */ + const char *test_name; + + /* Test context, for example `struct mqtt_publish_param *` */ + void *ctx; + + /* Test function */ + void (*test_fcn)(struct mqtt_test *tests); + + /* Expected encoded packet */ + uint8_t *expected; + + /* Length packet length */ + uint16_t expected_len; +}; + +static void run_packet_tests(struct mqtt_test *tests, size_t count) +{ + for (size_t i = 0; i < count; i++) { + struct mqtt_test *test = &tests[i]; + + TC_PRINT("Test #%u - %s\n", i + 1, test->test_name != NULL ? + test->test_name : ""); + + test->test_fcn(test); + }; +} + +static void print_buffer(const uint8_t *buf, uint16_t size) +{ + TC_PRINT("\n"); + for (uint16_t i = 0U; i < size; i++) { + TC_PRINT("%02x ", buf[i]); + if ((i + 1) % 8 == 0U) { + TC_PRINT("\n"); + } + } + TC_PRINT("\n"); +} + +static int validate_buffers(const struct buf_ctx *buf, const uint8_t *expected, + uint16_t len) +{ + if (buf->end - buf->cur != len) { + goto fail; + } + + if (memcmp(expected, buf->cur, buf->end - buf->cur) != 0) { + goto fail; + } + + return 0; + +fail: + TC_PRINT("Computed:"); + print_buffer(buf->cur, buf->end - buf->cur); + TC_PRINT("Expected:"); + print_buffer(expected, len); + + return -EBADMSG; +} + +static int validate_structs(const void *computed, const void *expected, + size_t struct_size) +{ + if (memcmp(expected, computed, struct_size) != 0) { + goto fail; + } + + return 0; + +fail: + TC_PRINT("Computed:"); + print_buffer(computed, struct_size); + TC_PRINT("Expected:"); + print_buffer(expected, struct_size); + + return -EBADMSG; +} + +static void test_msg_connect(struct mqtt_test *test) +{ + struct mqtt_client *test_client; + struct buf_ctx buf; + int ret; + + test_client = (struct mqtt_client *)test->ctx; + + client.client_id = test_client->client_id; + client.will_topic = test_client->will_topic; + client.prop = test_client->prop; + client.will_retain = test_client->will_retain; + client.will_message = test_client->will_message; + client.will_prop = test_client->will_prop; + + buf.cur = client.tx_buf; + buf.end = client.tx_buf + client.tx_buf_size; + + ret = connect_request_encode(&client, &buf); + zassert_ok(ret, "connect_request_encode failed"); + zassert_ok(validate_buffers(&buf, test->expected, + test->expected_len), + "Invalid packet content"); +} + +#define ENCODED_MID 0x12, 0x34 +#define ENCODED_PROP_DEFAULT_TOPIC_ALIAS_MAXIMUM 0x22, 0x00, 0x05 +#define ENCODED_PROP_DEFAULT_REQUEST_PROBLEM_INFORMATION 0x17, 0x00 +#define ENCODED_PROP_USER_PROPERTY \ + 0x26, 0x00, 0x09, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, \ + 0x00, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65 +#define ENCODED_PROP_REASON_STRING \ + 0x1f, 0x00, 0x12, 0x74, 0x65, 0x73, 0x74, 0x5F, 0x72, 0x65, 0x61, 0x73, \ + 0x6F, 0x6E, 0x5F, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67 +#define ENCODED_PROP_SESSION_EXPIRY_INTERVAL 0x11, 0x00, 0x00, 0x03, 0xe8 +#define ENCODED_PROP_RECEIVE_MAXIMUM 0x21, 0x00, 0x0a +#define ENCODED_PROP_MAXIMUM_PACKET_SIZE 0x27, 0x00, 0x00, 0x03, 0xe8 +#define ENCODED_PROP_REQUEST_RESPONSE_INFORMATION 0x19, 0x01 +#define ENCODED_PROP_AUTHENTICATION_METHOD \ + 0x15, 0x00, 0x13, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, \ + 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e +#define ENCODED_PROP_AUTHENTICATION_DATA \ + 0x16, 0x00, 0x04, 0x01, 0x02, 0x03, 0x04 +#define ENCODED_PROP_WILL_DELAY_INTERVAL 0x18, 0x00, 0x00, 0x00, 0x64 +#define ENCODED_PROP_PAYLOAD_FORMAT_INDICATOR 0x01, 0x01 +#define ENCODED_PROP_MESSAGE_EXPIRY_INTERVAL 0x02, 0x00, 0x00, 0x03, 0xe8 +#define ENCODED_PROP_CONTENT_TYPE \ + 0x03, 0x00, 0x11, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, \ + 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65 +#define ENCODED_PROP_RESPONSE_TOPIC \ + 0x08, 0x00, 0x13, 0x74, 0x65, 0x73, 0x74, 0x5F, 0x72, 0x65, 0x73, 0x70, \ + 0x6F, 0x6E, 0x73, 0x65, 0x5F, 0x74, 0x6F, 0x70, 0x69, 0x63 +#define ENCODED_PROP_CORRELATION_DATA \ + 0x09, 0x00, 0x06, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 +#define ENCODED_PROP_MAXIMUM_QOS 0x24, 0x01 +#define ENCODED_PROP_RETAIN_AVAILABLE 0x25, 0x01 +#define ENCODED_PROP_ASSIGNED_CLIENT_ID \ + 0x12, 0x00, 0x07, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64 +#define ENCODED_PROP_TOPIC_ALIAS_MAXIMUM 0x22, 0x00, 0x0a +#define ENCODED_PROP_WILDCARD_SUB_AVAILABLE 0x28, 0x01 +#define ENCODED_PROP_SUBSCRIPTION_IDS_AVAILABLE 0x29, 0x01 +#define ENCODED_PROP_SHARED_SUB_AVAILABLE 0x2a, 0x01 +#define ENCODED_PROP_SERVER_KEEP_ALIVE 0x13, 0x00, 0x64 +#define ENCODED_PROP_RESPONSE_INFORMATION \ + 0x1a, 0x00, 0x09, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f +#define ENCODED_PROP_SERVER_REFERENCE \ + 0x1c, 0x00, 0x0e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x66, 0x65, \ + 0x72, 0x65, 0x6e, 0x63, 0x65 +#define ENCODED_PROP_TOPIC_ALIAS 0x23, 0x00, 0x04 +#define ENCODED_PROP_CORRELATION_DATA \ + 0x09, 0x00, 0x06, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 +#define ENCODED_PROP_SUBSCRIPTION_IDENTIFIER 0x0b, 0xe8, 0x07 + +#define TEST_PROP_SESSION_EXPIRY_INTERVAL 1000 +#define TEST_PROP_RECEIVE_MAXIMUM 10 +#define TEST_PROP_MAXIMUM_PACKET_SIZE 1000 +#define TEST_PROP_WILL_DELAY_INTERVAL 100 +#define TEST_PROP_PAYLOAD_FORMAT_INDICATOR 1 +#define TEST_PROP_MESSAGE_EXPIRY_INTERVAL 1000 +#define TEST_PROP_MAXIMUM_QOS 1 +#define TEST_PROP_RETAIN_AVAILABLE 1 +#define TEST_PROP_TOPIC_ALIAS_MAXIMUM 10 +#define TEST_PROP_WILDCARD_SUB_AVAILABLE 1 +#define TEST_PROP_SUBSCRIPTION_IDS_AVAILABLE 1 +#define TEST_PROP_SHARED_SUB_AVAILABLE 1 +#define TEST_PROP_SERVER_KEEP_ALIVE 100 +#define TEST_PROP_SUBSCRIPTION_IDENTIFIER 1000 + +#define ENCODED_CONNECT_VAR_HEADER_COMMON \ + 0x00, 0x04, 0x4d, 0x51, 0x54, 0x54, 0x05, 0x00, 0x00, 0x3c +#define ENCODED_CONNECT_PROPERTIES_DEFAULT \ + 0x05, 0x22, 0x00, 0x05, 0x17, 0x00 +#define ENCODED_CONNECT_CLIENT_ID \ + 0x00, 0x07, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64 + +static uint8_t expect_connect_default[] = { + 0x10, 0x19, ENCODED_CONNECT_VAR_HEADER_COMMON, + ENCODED_CONNECT_PROPERTIES_DEFAULT, + ENCODED_CONNECT_CLIENT_ID, +}; +static struct mqtt_client client_connect_default = { + .client_id = CLIENTID, +}; + +static uint8_t expect_connect_prop_sei[] = { + 0x10, 0x1e, ENCODED_CONNECT_VAR_HEADER_COMMON, + /* Properties */ + 0x0a, ENCODED_PROP_SESSION_EXPIRY_INTERVAL, + ENCODED_PROP_DEFAULT_TOPIC_ALIAS_MAXIMUM, + ENCODED_PROP_DEFAULT_REQUEST_PROBLEM_INFORMATION, + /* Payload */ + ENCODED_CONNECT_CLIENT_ID, +}; +static struct mqtt_client client_connect_prop_sei = { + .client_id = CLIENTID, + .prop.session_expiry_interval = TEST_PROP_SESSION_EXPIRY_INTERVAL, +}; + +static uint8_t expect_connect_prop_rm[] = { + 0x10, 0x1c, ENCODED_CONNECT_VAR_HEADER_COMMON, + /* Properties */ + 0x08, ENCODED_PROP_RECEIVE_MAXIMUM, + ENCODED_PROP_DEFAULT_TOPIC_ALIAS_MAXIMUM, + ENCODED_PROP_DEFAULT_REQUEST_PROBLEM_INFORMATION, + /* Payload */ + ENCODED_CONNECT_CLIENT_ID, +}; +static struct mqtt_client client_connect_prop_rm = { + .client_id = CLIENTID, + .prop.receive_maximum = TEST_PROP_RECEIVE_MAXIMUM, +}; + +static uint8_t expect_connect_prop_mps[] = { + 0x10, 0x1e, ENCODED_CONNECT_VAR_HEADER_COMMON, + /* Properties */ + 0x0a, ENCODED_PROP_MAXIMUM_PACKET_SIZE, + ENCODED_PROP_DEFAULT_TOPIC_ALIAS_MAXIMUM, + ENCODED_PROP_DEFAULT_REQUEST_PROBLEM_INFORMATION, + /* Payload */ + ENCODED_CONNECT_CLIENT_ID, + +}; +static struct mqtt_client client_connect_prop_mps = { + .client_id = CLIENTID, + .prop.maximum_packet_size = TEST_PROP_MAXIMUM_PACKET_SIZE, +}; + +static uint8_t expect_connect_prop_rri[] = { + 0x10, 0x1b, ENCODED_CONNECT_VAR_HEADER_COMMON, + /* Properties */ + 0x07, ENCODED_PROP_DEFAULT_TOPIC_ALIAS_MAXIMUM, + ENCODED_PROP_REQUEST_RESPONSE_INFORMATION, + ENCODED_PROP_DEFAULT_REQUEST_PROBLEM_INFORMATION, + /* Payload */ + ENCODED_CONNECT_CLIENT_ID, +}; +static struct mqtt_client client_connect_prop_rri = { + .client_id = CLIENTID, + .prop.request_response_info = true, +}; + +static uint8_t expect_connect_prop_rpi[] = { + 0x10, 0x17, ENCODED_CONNECT_VAR_HEADER_COMMON, + /* Properties */ + 0x03, ENCODED_PROP_DEFAULT_TOPIC_ALIAS_MAXIMUM, + /* Payload */ + ENCODED_CONNECT_CLIENT_ID, +}; +static struct mqtt_client client_connect_prop_rpi = { + .client_id = CLIENTID, + /* True is MQTT default so property should be omitted. */ + .prop.request_problem_info = true, +}; + +static uint8_t expect_connect_prop_up[] = { + 0x10, 0x31, ENCODED_CONNECT_VAR_HEADER_COMMON, + /* Properties */ + 0x1d, ENCODED_PROP_DEFAULT_TOPIC_ALIAS_MAXIMUM, + ENCODED_PROP_DEFAULT_REQUEST_PROBLEM_INFORMATION, + ENCODED_PROP_USER_PROPERTY, + /* Payload */ + ENCODED_CONNECT_CLIENT_ID, +}; +static struct mqtt_client client_connect_prop_up = { + .client_id = CLIENTID, + .prop.user_prop[0] = { + .name = USER_PROP_NAME, + .value = USER_PROP_VALUE, + }, +}; + +static uint8_t expect_connect_prop_am_ad[] = { + 0x10, 0x36, ENCODED_CONNECT_VAR_HEADER_COMMON, + /* Properties */ + 0x22, ENCODED_PROP_DEFAULT_TOPIC_ALIAS_MAXIMUM, + ENCODED_PROP_DEFAULT_REQUEST_PROBLEM_INFORMATION, + ENCODED_PROP_AUTHENTICATION_METHOD, + ENCODED_PROP_AUTHENTICATION_DATA, + /* Payload */ + ENCODED_CONNECT_CLIENT_ID, +}; +static struct mqtt_client client_connect_prop_am_ad = { + .client_id = CLIENTID, + .prop.auth_method = AUTH_METHOD, + .prop.auth_data = AUTH_DATA, +}; + +static struct mqtt_utf8 will_msg = WILL_MSG; +static struct mqtt_topic will_topic = { + .qos = 0, + .topic = WILL_TOPIC, +}; + +#define CONNECT_WILL_COMMON \ + .client_id = CLIENTID, \ + .will_topic = &will_topic, \ + .will_message = &will_msg \ + +#define ENCODED_CONNECT_WILL_VAR_HEADER_COMMON \ + 0x00, 0x04, 0x4d, 0x51, 0x54, 0x54, 0x05, 0x04, 0x00, 0x3c +#define ENCODED_CONNECT_WILL_TOPIC_AND_PAYLOAD \ + 0x00, 0x09, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x77, 0x69, 0x6c, 0x6c, \ + 0x00, 0x0d, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x77, 0x69, 0x6c, 0x6c, 0x5f, \ + 0x6d, 0x73, 0x67 + +static uint8_t expect_connect_will_default[] = { + 0x10, 0x34, ENCODED_CONNECT_WILL_VAR_HEADER_COMMON, + ENCODED_CONNECT_PROPERTIES_DEFAULT, + ENCODED_CONNECT_CLIENT_ID, + /* Will properties */ + 0x00, + ENCODED_CONNECT_WILL_TOPIC_AND_PAYLOAD, +}; +static struct mqtt_client client_connect_will_default = { + CONNECT_WILL_COMMON, +}; + +static uint8_t expect_connect_will_prop_di[] = { + 0x10, 0x39, ENCODED_CONNECT_WILL_VAR_HEADER_COMMON, + /* Properties */ + ENCODED_CONNECT_PROPERTIES_DEFAULT, + /* Payload */ + ENCODED_CONNECT_CLIENT_ID, + /* Will properties */ + 0x05, ENCODED_PROP_WILL_DELAY_INTERVAL, + ENCODED_CONNECT_WILL_TOPIC_AND_PAYLOAD, +}; +static struct mqtt_client client_connect_will_prop_di = { + CONNECT_WILL_COMMON, + .will_prop.will_delay_interval = TEST_PROP_WILL_DELAY_INTERVAL, +}; + +static uint8_t expect_connect_will_prop_pfi[] = { + 0x10, 0x36, ENCODED_CONNECT_WILL_VAR_HEADER_COMMON, + /* Properties */ + ENCODED_CONNECT_PROPERTIES_DEFAULT, + /* Payload */ + ENCODED_CONNECT_CLIENT_ID, + /* Will properties */ + 0x02, ENCODED_PROP_PAYLOAD_FORMAT_INDICATOR, + ENCODED_CONNECT_WILL_TOPIC_AND_PAYLOAD, +}; +static struct mqtt_client client_connect_will_prop_pfi = { + CONNECT_WILL_COMMON, + .will_prop.payload_format_indicator = TEST_PROP_PAYLOAD_FORMAT_INDICATOR, +}; + +static uint8_t expect_connect_will_prop_mei[] = { + 0x10, 0x39, ENCODED_CONNECT_WILL_VAR_HEADER_COMMON, + /* Properties */ + ENCODED_CONNECT_PROPERTIES_DEFAULT, + /* Payload */ + ENCODED_CONNECT_CLIENT_ID, + /* Will properties */ + 0x05, ENCODED_PROP_MESSAGE_EXPIRY_INTERVAL, + ENCODED_CONNECT_WILL_TOPIC_AND_PAYLOAD, +}; +static struct mqtt_client client_connect_will_prop_mei = { + CONNECT_WILL_COMMON, + .will_prop.message_expiry_interval = TEST_PROP_MESSAGE_EXPIRY_INTERVAL, +}; + +static uint8_t expect_connect_will_prop_ct[] = { + 0x10, 0x48, ENCODED_CONNECT_WILL_VAR_HEADER_COMMON, + /* Properties */ + ENCODED_CONNECT_PROPERTIES_DEFAULT, + /* Payload */ + ENCODED_CONNECT_CLIENT_ID, + /* Will properties */ + 0x14, ENCODED_PROP_CONTENT_TYPE, + ENCODED_CONNECT_WILL_TOPIC_AND_PAYLOAD, +}; +static struct mqtt_client client_connect_will_prop_ct = { + CONNECT_WILL_COMMON, + .will_prop.content_type = CONTENT_TYPE, +}; + +static uint8_t expect_connect_will_prop_rt[] = { + 0x10, 0x4a, ENCODED_CONNECT_WILL_VAR_HEADER_COMMON, + /* Properties */ + ENCODED_CONNECT_PROPERTIES_DEFAULT, + /* Payload */ + ENCODED_CONNECT_CLIENT_ID, + /* Will properties */ + 0x16, ENCODED_PROP_RESPONSE_TOPIC, + ENCODED_CONNECT_WILL_TOPIC_AND_PAYLOAD, +}; +static struct mqtt_client client_connect_will_prop_rt = { + CONNECT_WILL_COMMON, + .will_prop.response_topic = RESPONSE_TOPIC, +}; + +static uint8_t expect_connect_will_prop_cd[] = { + 0x10, 0x3d, ENCODED_CONNECT_WILL_VAR_HEADER_COMMON, + /* Properties */ + ENCODED_CONNECT_PROPERTIES_DEFAULT, + /* Payload */ + ENCODED_CONNECT_CLIENT_ID, + /* Will properties */ + 0x09, ENCODED_PROP_CORRELATION_DATA, + ENCODED_CONNECT_WILL_TOPIC_AND_PAYLOAD, +}; +static struct mqtt_client client_connect_will_prop_cd = { + CONNECT_WILL_COMMON, + .will_prop.correlation_data = CORRELATION_DATA, +}; + +static uint8_t expect_connect_will_prop_up[] = { + 0x10, 0x4c, ENCODED_CONNECT_WILL_VAR_HEADER_COMMON, + /* Properties */ + ENCODED_CONNECT_PROPERTIES_DEFAULT, + /* Payload */ + ENCODED_CONNECT_CLIENT_ID, + /* Will properties */ + 0x18, ENCODED_PROP_USER_PROPERTY, + ENCODED_CONNECT_WILL_TOPIC_AND_PAYLOAD, +}; +static struct mqtt_client client_connect_will_prop_up = { + CONNECT_WILL_COMMON, + .will_prop.user_prop[0] = { + .name = USER_PROP_NAME, + .value = USER_PROP_VALUE, + }, +}; + +static struct mqtt_test connect_tests[] = { + { .test_name = "CONNECT, default", + .ctx = &client_connect_default, .test_fcn = test_msg_connect, + .expected = expect_connect_default, + .expected_len = sizeof(expect_connect_default) }, + { .test_name = "CONNECT, property Session Expiry Interval", + .ctx = &client_connect_prop_sei, .test_fcn = test_msg_connect, + .expected = expect_connect_prop_sei, + .expected_len = sizeof(expect_connect_prop_sei) }, + { .test_name = "CONNECT, property Receive Maximum", + .ctx = &client_connect_prop_rm, .test_fcn = test_msg_connect, + .expected = expect_connect_prop_rm, + .expected_len = sizeof(expect_connect_prop_rm) }, + { .test_name = "CONNECT, property Maximum Packet Size", + .ctx = &client_connect_prop_mps, .test_fcn = test_msg_connect, + .expected = expect_connect_prop_mps, + .expected_len = sizeof(expect_connect_prop_mps) }, + { .test_name = "CONNECT, property Request Response Information", + .ctx = &client_connect_prop_rri, .test_fcn = test_msg_connect, + .expected = expect_connect_prop_rri, + .expected_len = sizeof(expect_connect_prop_rri) }, + { .test_name = "CONNECT, property Request Problem Information", + .ctx = &client_connect_prop_rpi, .test_fcn = test_msg_connect, + .expected = expect_connect_prop_rpi, + .expected_len = sizeof(expect_connect_prop_rpi) }, + { .test_name = "CONNECT, property User Property", + .ctx = &client_connect_prop_up, .test_fcn = test_msg_connect, + .expected = expect_connect_prop_up, + .expected_len = sizeof(expect_connect_prop_up) }, + { .test_name = "CONNECT, property Authentication Method and Data", + .ctx = &client_connect_prop_am_ad, .test_fcn = test_msg_connect, + .expected = expect_connect_prop_am_ad, + .expected_len = sizeof(expect_connect_prop_am_ad) }, + { .test_name = "CONNECT, WILL default", + .ctx = &client_connect_will_default, .test_fcn = test_msg_connect, + .expected = expect_connect_will_default, + .expected_len = sizeof(expect_connect_will_default) }, + { .test_name = "CONNECT, WILL property Will Delay Interval", + .ctx = &client_connect_will_prop_di, .test_fcn = test_msg_connect, + .expected = expect_connect_will_prop_di, + .expected_len = sizeof(expect_connect_will_prop_di) }, + { .test_name = "CONNECT, WILL property Payload Format Indicator", + .ctx = &client_connect_will_prop_pfi, .test_fcn = test_msg_connect, + .expected = expect_connect_will_prop_pfi, + .expected_len = sizeof(expect_connect_will_prop_pfi) }, + { .test_name = "CONNECT, WILL property Message Expiry Interval", + .ctx = &client_connect_will_prop_mei, .test_fcn = test_msg_connect, + .expected = expect_connect_will_prop_mei, + .expected_len = sizeof(expect_connect_will_prop_mei) }, + { .test_name = "CONNECT, WILL property Content Type", + .ctx = &client_connect_will_prop_ct, .test_fcn = test_msg_connect, + .expected = expect_connect_will_prop_ct, + .expected_len = sizeof(expect_connect_will_prop_ct) }, + { .test_name = "CONNECT, WILL property Response Topic", + .ctx = &client_connect_will_prop_rt, .test_fcn = test_msg_connect, + .expected = expect_connect_will_prop_rt, + .expected_len = sizeof(expect_connect_will_prop_rt) }, + { .test_name = "CONNECT, WILL property Correlation Data", + .ctx = &client_connect_will_prop_cd, .test_fcn = test_msg_connect, + .expected = expect_connect_will_prop_cd, + .expected_len = sizeof(expect_connect_will_prop_cd) }, + { .test_name = "CONNECT, WILL property User Property", + .ctx = &client_connect_will_prop_up, .test_fcn = test_msg_connect, + .expected = expect_connect_will_prop_up, + .expected_len = sizeof(expect_connect_will_prop_up) }, +}; + +ZTEST(mqtt_5_packet, test_mqtt_5_connect) +{ + run_packet_tests(connect_tests, ARRAY_SIZE(connect_tests)); +} + +static void test_msg_connack(struct mqtt_test *test) +{ + struct mqtt_connack_param *exp_param = + (struct mqtt_connack_param *)test->ctx; + struct mqtt_connack_param dec_param = { 0 }; + int ret; + uint8_t type_and_flags; + uint32_t length; + struct buf_ctx buf; + + buf.cur = test->expected; + buf.end = test->expected + test->expected_len; + + ret = fixed_header_decode(&buf, &type_and_flags, &length); + zassert_ok(ret, "fixed_header_decode failed"); + zassert_equal(type_and_flags & 0xF0, MQTT_PKT_TYPE_CONNACK, + "Invalid packet type"); + zassert_equal(length, test->expected_len - 2, + "Invalid packet length"); + + ret = connect_ack_decode(&client, &buf, &dec_param); + zassert_ok(ret, "connect_ack_decode failed"); + zassert_ok(validate_structs(&dec_param, exp_param, sizeof(dec_param)), + "Incorrect CONNACK params decoded"); +} + +static uint8_t expect_connack_default[] = { + 0x20, 0x03, 0x01, 0x00, 0x00, +}; +static struct mqtt_connack_param connack_default = { + .session_present_flag = true, + .return_code = MQTT_CONNACK_SUCCESS, +}; + +static uint8_t expect_connack_error[] = { + 0x20, 0x03, 0x00, 0x80, 0x00, +}; +static struct mqtt_connack_param connack_error = { + .return_code = MQTT_CONNACK_UNSPECIFIED_ERROR, +}; + +static uint8_t expect_connack_prop_sei[] = { + 0x20, 0x08, 0x00, 0x00, + /* Properties */ + 0x05, ENCODED_PROP_SESSION_EXPIRY_INTERVAL, +}; +static struct mqtt_connack_param connack_prop_sei = { + .prop.rx.has_session_expiry_interval = true, + .prop.session_expiry_interval = TEST_PROP_SESSION_EXPIRY_INTERVAL, +}; + +static uint8_t expect_connack_prop_rm[] = { + 0x20, 0x06, 0x00, 0x00, + /* Properties */ + 0x03, ENCODED_PROP_RECEIVE_MAXIMUM, +}; +static struct mqtt_connack_param connack_prop_rm = { + .prop.rx.has_receive_maximum = true, + .prop.receive_maximum = TEST_PROP_RECEIVE_MAXIMUM, +}; + +static uint8_t expect_connack_prop_mqos[] = { + 0x20, 0x05, 0x00, 0x00, + /* Properties */ + 0x02, ENCODED_PROP_MAXIMUM_QOS, +}; +static struct mqtt_connack_param connack_prop_mqos = { + .prop.rx.has_maximum_qos = true, + .prop.maximum_qos = TEST_PROP_MAXIMUM_QOS, +}; + +static uint8_t expect_connack_prop_ra[] = { + 0x20, 0x05, 0x00, 0x00, + /* Properties */ + 0x02, ENCODED_PROP_RETAIN_AVAILABLE, +}; +static struct mqtt_connack_param connack_prop_ra = { + .prop.rx.has_retain_available = true, + .prop.retain_available = TEST_PROP_RETAIN_AVAILABLE, +}; + +static uint8_t expect_connack_prop_mps[] = { + 0x20, 0x08, 0x00, 0x00, + /* Properties */ + 0x05, ENCODED_PROP_MAXIMUM_PACKET_SIZE, +}; +static struct mqtt_connack_param connack_prop_mps = { + .prop.rx.has_maximum_packet_size = true, + .prop.maximum_packet_size = TEST_PROP_MAXIMUM_PACKET_SIZE, +}; + +static uint8_t expect_connack_prop_aci[] = { + 0x20, 0x0d, 0x00, 0x00, + /* Properties */ + 0x0a, ENCODED_PROP_ASSIGNED_CLIENT_ID, +}; +static struct mqtt_connack_param connack_prop_aci = { + .prop.rx.has_assigned_client_id = true, + .prop.assigned_client_id.utf8 = expect_connack_prop_aci + 8, + .prop.assigned_client_id.size = 7, +}; + +static uint8_t expect_connack_prop_tam[] = { + 0x20, 0x06, 0x00, 0x00, + /* Properties */ + 0x03, ENCODED_PROP_TOPIC_ALIAS_MAXIMUM, +}; +static struct mqtt_connack_param connack_prop_tam = { + .prop.rx.has_topic_alias_maximum = true, + .prop.topic_alias_maximum = TEST_PROP_TOPIC_ALIAS_MAXIMUM, +}; + +static uint8_t expect_connack_prop_rs[] = { + 0x20, 0x18, 0x00, 0x00, + /* Properties */ + 0x15, ENCODED_PROP_REASON_STRING, +}; +static struct mqtt_connack_param connack_prop_rs = { + .prop.rx.has_reason_string = true, + .prop.reason_string.utf8 = expect_connack_prop_rs + 8, + .prop.reason_string.size = 18, +}; + +static uint8_t expect_connack_prop_up[] = { + 0x20, 0x1b, 0x00, 0x00, + /* Properties */ + 0x18, ENCODED_PROP_USER_PROPERTY, +}; +static struct mqtt_connack_param connack_prop_up = { + .prop.rx.has_user_prop = true, + .prop.user_prop[0].name.utf8 = expect_connack_prop_up + 8, + .prop.user_prop[0].name.size = 9, + .prop.user_prop[0].value.utf8 = expect_connack_prop_up + 19, + .prop.user_prop[0].value.size = 10, +}; + +static uint8_t expect_connack_prop_wsa[] = { + 0x20, 0x05, 0x00, 0x00, + /* Properties */ + 0x02, ENCODED_PROP_WILDCARD_SUB_AVAILABLE, +}; +static struct mqtt_connack_param connack_prop_wsa = { + .prop.rx.has_wildcard_sub_available = true, + .prop.wildcard_sub_available = TEST_PROP_WILDCARD_SUB_AVAILABLE, +}; + +static uint8_t expect_connack_prop_sia[] = { + 0x20, 0x05, 0x00, 0x00, + /* Properties */ + 0x02, ENCODED_PROP_SUBSCRIPTION_IDS_AVAILABLE, +}; +static struct mqtt_connack_param connack_prop_sia = { + .prop.rx.has_subscription_ids_available = true, + .prop.subscription_ids_available = TEST_PROP_SUBSCRIPTION_IDS_AVAILABLE, +}; + +static uint8_t expect_connack_prop_ssa[] = { + 0x20, 0x05, 0x00, 0x00, + /* Properties */ + 0x02, ENCODED_PROP_SHARED_SUB_AVAILABLE, +}; +static struct mqtt_connack_param connack_prop_ssa = { + .prop.rx.has_shared_sub_available = true, + .prop.shared_sub_available = TEST_PROP_SHARED_SUB_AVAILABLE, +}; + +static uint8_t expect_connack_prop_ska[] = { + 0x20, 0x06, 0x00, 0x00, + /* Properties */ + 0x03, ENCODED_PROP_SERVER_KEEP_ALIVE, +}; +static struct mqtt_connack_param connack_prop_ska = { + .prop.rx.has_server_keep_alive = true, + .prop.server_keep_alive = TEST_PROP_SERVER_KEEP_ALIVE, +}; + +static uint8_t expect_connack_prop_ri[] = { + 0x20, 0x0f, 0x00, 0x00, + /* Properties */ + 0x0c, ENCODED_PROP_RESPONSE_INFORMATION, +}; +static struct mqtt_connack_param connack_prop_ri = { + .prop.rx.has_response_information = true, + .prop.response_information.utf8 = expect_connack_prop_ri + 8, + .prop.response_information.size = 9, +}; + +static uint8_t expect_connack_prop_sr[] = { + 0x20, 0x14, 0x00, 0x00, + /* Properties */ + 0x11, ENCODED_PROP_SERVER_REFERENCE, +}; +static struct mqtt_connack_param connack_prop_sr = { + .prop.rx.has_server_reference = true, + .prop.server_reference.utf8 = expect_connack_prop_sr + 8, + .prop.server_reference.size = 14, +}; + +static uint8_t expect_connack_prop_am_ad[] = { + 0x20, 0x20, 0x00, 0x00, + /* Properties */ + 0x1d, ENCODED_PROP_AUTHENTICATION_METHOD, + ENCODED_PROP_AUTHENTICATION_DATA +}; +static struct mqtt_connack_param connack_prop_am_ad = { + .prop.rx.has_auth_method = true, + .prop.auth_method.utf8 = expect_connack_prop_am_ad + 8, + .prop.auth_method.size = 19, + .prop.rx.has_auth_data = true, + .prop.auth_data.data = expect_connack_prop_am_ad + 30, + .prop.auth_data.len = 4, +}; + +static struct mqtt_test connack_tests[] = { + { .test_name = "CONNACK, default", + .ctx = &connack_default, .test_fcn = test_msg_connack, + .expected = expect_connack_default, + .expected_len = sizeof(expect_connack_default) }, + { .test_name = "CONNACK, error", + .ctx = &connack_error, .test_fcn = test_msg_connack, + .expected = expect_connack_error, + .expected_len = sizeof(expect_connack_error) }, + { .test_name = "CONNACK, property Session Expiry Interval", + .ctx = &connack_prop_sei, .test_fcn = test_msg_connack, + .expected = expect_connack_prop_sei, + .expected_len = sizeof(expect_connack_prop_sei) }, + { .test_name = "CONNACK, property Receive Maximum", + .ctx = &connack_prop_rm, .test_fcn = test_msg_connack, + .expected = expect_connack_prop_rm, + .expected_len = sizeof(expect_connack_prop_rm) }, + { .test_name = "CONNACK, property Maximum QoS", + .ctx = &connack_prop_mqos, .test_fcn = test_msg_connack, + .expected = expect_connack_prop_mqos, + .expected_len = sizeof(expect_connack_prop_mqos) }, + { .test_name = "CONNACK, property Retain Available", + .ctx = &connack_prop_ra, .test_fcn = test_msg_connack, + .expected = expect_connack_prop_ra, + .expected_len = sizeof(expect_connack_prop_ra) }, + { .test_name = "CONNACK, property Maximum Packet Size", + .ctx = &connack_prop_mps, .test_fcn = test_msg_connack, + .expected = expect_connack_prop_mps, + .expected_len = sizeof(expect_connack_prop_mps) }, + { .test_name = "CONNACK, property Assigned Client Identifier", + .ctx = &connack_prop_aci, .test_fcn = test_msg_connack, + .expected = expect_connack_prop_aci, + .expected_len = sizeof(expect_connack_prop_aci) }, + { .test_name = "CONNACK, property Topic Alias Maximum", + .ctx = &connack_prop_tam, .test_fcn = test_msg_connack, + .expected = expect_connack_prop_tam, + .expected_len = sizeof(expect_connack_prop_tam) }, + { .test_name = "CONNACK, property Reason String", + .ctx = &connack_prop_rs, .test_fcn = test_msg_connack, + .expected = expect_connack_prop_rs, + .expected_len = sizeof(expect_connack_prop_rs) }, + { .test_name = "CONNACK, property User Property", + .ctx = &connack_prop_up, .test_fcn = test_msg_connack, + .expected = expect_connack_prop_up, + .expected_len = sizeof(expect_connack_prop_up) }, + { .test_name = "CONNACK, property Wildcard Subscription Available", + .ctx = &connack_prop_wsa, .test_fcn = test_msg_connack, + .expected = expect_connack_prop_wsa, + .expected_len = sizeof(expect_connack_prop_wsa) }, + { .test_name = "CONNACK, property Subscription Identifiers Available", + .ctx = &connack_prop_sia, .test_fcn = test_msg_connack, + .expected = expect_connack_prop_sia, + .expected_len = sizeof(expect_connack_prop_sia) }, + { .test_name = "CONNACK, property Shared Subscription Available", + .ctx = &connack_prop_ssa, .test_fcn = test_msg_connack, + .expected = expect_connack_prop_ssa, + .expected_len = sizeof(expect_connack_prop_ssa) }, + { .test_name = "CONNACK, property Server Keep Alive", + .ctx = &connack_prop_ska, .test_fcn = test_msg_connack, + .expected = expect_connack_prop_ska, + .expected_len = sizeof(expect_connack_prop_ska) }, + { .test_name = "CONNACK, property Response Information", + .ctx = &connack_prop_ri, .test_fcn = test_msg_connack, + .expected = expect_connack_prop_ri, + .expected_len = sizeof(expect_connack_prop_ri) }, + { .test_name = "CONNACK, property Server Reference", + .ctx = &connack_prop_sr, .test_fcn = test_msg_connack, + .expected = expect_connack_prop_sr, + .expected_len = sizeof(expect_connack_prop_sr) }, + { .test_name = "CONNACK, property Authentication Method and Data", + .ctx = &connack_prop_am_ad, .test_fcn = test_msg_connack, + .expected = expect_connack_prop_am_ad, + .expected_len = sizeof(expect_connack_prop_am_ad) }, +}; + +ZTEST(mqtt_5_packet, test_mqtt_5_connack) +{ + run_packet_tests(connack_tests, ARRAY_SIZE(connack_tests)); +} + +static void test_msg_publish_dec_only(struct mqtt_test *test) +{ + struct mqtt_publish_param *exp_param = + (struct mqtt_publish_param *)test->ctx; + struct mqtt_publish_param dec_param = { 0 }; + uint8_t type_and_flags; + uint32_t length; + struct buf_ctx buf; + int ret; + + buf.cur = test->expected; + buf.end = test->expected + test->expected_len; + + ret = fixed_header_decode(&buf, &type_and_flags, &length); + zassert_ok(ret, "fixed_header_decode failed"); + zassert_equal(type_and_flags & 0xF0, MQTT_PKT_TYPE_PUBLISH, + "Invalid packet type"); + zassert_equal(length, test->expected_len - 2, + "Invalid packet length"); + + ret = publish_decode(&client, type_and_flags, length, &buf, &dec_param); + zassert_ok(ret, "publish_decode failed"); + + zassert_equal(dec_param.message_id, exp_param->message_id, + "Incorrect message_id"); + zassert_equal(dec_param.dup_flag, exp_param->dup_flag, + "Incorrect dup flag"); + zassert_equal(dec_param.retain_flag, exp_param->retain_flag, + "Incorrect retain flag"); + zassert_equal(dec_param.message.topic.qos, exp_param->message.topic.qos, + "Incorrect topic qos"); + zassert_equal(dec_param.message.topic.topic.size, + exp_param->message.topic.topic.size, + "Incorrect topic len"); + zassert_mem_equal(dec_param.message.topic.topic.utf8, + exp_param->message.topic.topic.utf8, + dec_param.message.topic.topic.size, + "Incorrect topic content"); + zassert_equal(dec_param.message.payload.len, + exp_param->message.payload.len, + "Incorrect payload len"); + zassert_ok(validate_structs(&dec_param.prop, &(exp_param->prop), + sizeof(dec_param.prop)), + "Incorrect PUBLISH properties decoded"); +} + +static void test_msg_publish(struct mqtt_test *test) +{ + struct mqtt_publish_param *exp_param = + (struct mqtt_publish_param *)test->ctx; + struct buf_ctx buf; + int ret; + + buf.cur = client.tx_buf; + buf.end = client.tx_buf + client.tx_buf_size; + + ret = publish_encode(&client, exp_param, &buf); + zassert_ok(ret, "publish_encode failed"); + + /* Payload is not copied, copy it manually just after the header.*/ + memcpy(buf.end, exp_param->message.payload.data, + exp_param->message.payload.len); + buf.end += exp_param->message.payload.len; + + zassert_ok(validate_buffers(&buf, test->expected, + test->expected_len), + "Invalid packet content"); + + test_msg_publish_dec_only(test); +} + +#define PUBLISH_COMMON \ + .message.topic.qos = MQTT_QOS_1_AT_LEAST_ONCE, \ + .message.topic.topic = TEST_TOPIC, \ + .message.payload = TEST_PAYLOAD, \ + .message_id = TEST_MSG_ID + +#define ENCODED_PUBLISH_TOPIC \ + 0x00, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x70, 0x69, 0x63 +#define ENCODED_PUBLISH_PAYLOAD \ + 0x74, 0x65, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64 + +static uint8_t expect_publish_default[] = { + 0x32, 0x1b, ENCODED_PUBLISH_TOPIC, ENCODED_MID, + /* No properties */ + 0x00, + /* Payload */ + ENCODED_PUBLISH_PAYLOAD, +}; +static struct mqtt_publish_param publish_default = { + PUBLISH_COMMON, +}; + +static uint8_t expect_publish_prop_pfi[] = { + 0x32, 0x1d, ENCODED_PUBLISH_TOPIC, ENCODED_MID, + /* Properties */ + 0x02, ENCODED_PROP_PAYLOAD_FORMAT_INDICATOR, + /* Payload */ + ENCODED_PUBLISH_PAYLOAD, +}; +static struct mqtt_publish_param publish_prop_pfi = { + PUBLISH_COMMON, + .prop.rx.has_payload_format_indicator = true, + .prop.payload_format_indicator = TEST_PROP_PAYLOAD_FORMAT_INDICATOR, +}; + +static uint8_t expect_publish_prop_mei[] = { + 0x32, 0x20, ENCODED_PUBLISH_TOPIC, ENCODED_MID, + /* Properties */ + 0x05, ENCODED_PROP_MESSAGE_EXPIRY_INTERVAL, + /* Payload */ + ENCODED_PUBLISH_PAYLOAD, +}; +static struct mqtt_publish_param publish_prop_mei = { + PUBLISH_COMMON, + .prop.rx.has_message_expiry_interval = true, + .prop.message_expiry_interval = TEST_PROP_MESSAGE_EXPIRY_INTERVAL, +}; + +static uint8_t expect_publish_prop_ta[] = { + 0x32, 0x1e, ENCODED_PUBLISH_TOPIC, ENCODED_MID, + /* Properties */ + 0x03, ENCODED_PROP_TOPIC_ALIAS, + /* Payload */ + ENCODED_PUBLISH_PAYLOAD, +}; +static struct mqtt_publish_param publish_prop_ta = { + PUBLISH_COMMON, + .prop.rx.has_topic_alias = true, + .prop.topic_alias = 4, +}; + +static uint8_t expect_publish_prop_rt[] = { + 0x32, 0x31, ENCODED_PUBLISH_TOPIC, ENCODED_MID, + /* Properties */ + 0x16, ENCODED_PROP_RESPONSE_TOPIC, + /* Payload */ + ENCODED_PUBLISH_PAYLOAD, +}; +static struct mqtt_publish_param publish_prop_rt = { + PUBLISH_COMMON, + .prop.rx.has_response_topic = true, + .prop.response_topic.utf8 = expect_publish_prop_rt + 20, + .prop.response_topic.size = 19, +}; + +static uint8_t expect_publish_prop_cd[] = { + 0x32, 0x24, ENCODED_PUBLISH_TOPIC, ENCODED_MID, + /* Properties */ + 0x09, ENCODED_PROP_CORRELATION_DATA, + /* Payload */ + ENCODED_PUBLISH_PAYLOAD, +}; +static struct mqtt_publish_param publish_prop_cd = { + PUBLISH_COMMON, + .prop.rx.has_correlation_data = true, + .prop.correlation_data.data = expect_publish_prop_cd + 20, + .prop.correlation_data.len = 6, +}; + +static uint8_t expect_publish_prop_up[] = { + 0x32, 0x33, ENCODED_PUBLISH_TOPIC, ENCODED_MID, + /* Properties */ + 0x18, ENCODED_PROP_USER_PROPERTY, + /* Payload */ + ENCODED_PUBLISH_PAYLOAD, +}; +static struct mqtt_publish_param publish_prop_up = { + PUBLISH_COMMON, + .prop.rx.has_user_prop = true, + .prop.user_prop[0].name.utf8 = expect_publish_prop_up + 20, + .prop.user_prop[0].name.size = 9, + .prop.user_prop[0].value.utf8 = expect_publish_prop_up + 31, + .prop.user_prop[0].value.size = 10, +}; + +static uint8_t expect_publish_prop_si[] = { + 0x32, 0x1e, ENCODED_PUBLISH_TOPIC, ENCODED_MID, + /* Properties */ + 0x03, ENCODED_PROP_SUBSCRIPTION_IDENTIFIER, + /* Payload */ + ENCODED_PUBLISH_PAYLOAD, +}; +static struct mqtt_publish_param publish_prop_si = { + PUBLISH_COMMON, + .prop.rx.has_subscription_identifier = true, + .prop.subscription_identifier[0] = TEST_PROP_SUBSCRIPTION_IDENTIFIER, +}; + +static uint8_t expect_publish_prop_ct[] = { + 0x32, 0x2f, ENCODED_PUBLISH_TOPIC, ENCODED_MID, + /* Properties */ + 0x14, ENCODED_PROP_CONTENT_TYPE, + /* Payload */ + ENCODED_PUBLISH_PAYLOAD, +}; +static struct mqtt_publish_param publish_prop_ct = { + PUBLISH_COMMON, + .prop.rx.has_content_type = true, + .prop.content_type.utf8 = expect_publish_prop_ct + 20, + .prop.content_type.size = 17, +}; + +static struct mqtt_test publish_tests[] = { + { .test_name = "PUBLISH, default", + .ctx = &publish_default, .test_fcn = test_msg_publish, + .expected = expect_publish_default, + .expected_len = sizeof(expect_publish_default) }, + { .test_name = "PUBLISH, property Payload Format Indicator", + .ctx = &publish_prop_pfi, .test_fcn = test_msg_publish, + .expected = expect_publish_prop_pfi, + .expected_len = sizeof(expect_publish_prop_pfi) }, + { .test_name = "PUBLISH, property Message Expiry Interval", + .ctx = &publish_prop_mei, .test_fcn = test_msg_publish, + .expected = expect_publish_prop_mei, + .expected_len = sizeof(expect_publish_prop_mei) }, + { .test_name = "PUBLISH, property Topic Alias", + .ctx = &publish_prop_ta, .test_fcn = test_msg_publish, + .expected = expect_publish_prop_ta, + .expected_len = sizeof(expect_publish_prop_ta) }, + { .test_name = "PUBLISH, property Response Topic", + .ctx = &publish_prop_rt, .test_fcn = test_msg_publish, + .expected = expect_publish_prop_rt, + .expected_len = sizeof(expect_publish_prop_rt) }, + { .test_name = "PUBLISH, property Correlation Data", + .ctx = &publish_prop_cd, .test_fcn = test_msg_publish, + .expected = expect_publish_prop_cd, + .expected_len = sizeof(expect_publish_prop_cd) }, + { .test_name = "PUBLISH, property User Property", + .ctx = &publish_prop_up, .test_fcn = test_msg_publish, + .expected = expect_publish_prop_up, + .expected_len = sizeof(expect_publish_prop_up) }, + { .test_name = "PUBLISH, property Subscription Identifier", + .ctx = &publish_prop_si, .test_fcn = test_msg_publish_dec_only, + .expected = expect_publish_prop_si, + .expected_len = sizeof(expect_publish_prop_si) }, + { .test_name = "PUBLISH, property Content Type", + .ctx = &publish_prop_ct, .test_fcn = test_msg_publish, + .expected = expect_publish_prop_ct, + .expected_len = sizeof(expect_publish_prop_ct) }, +}; + +ZTEST(mqtt_5_packet, test_mqtt_5_publish) +{ + run_packet_tests(publish_tests, ARRAY_SIZE(publish_tests)); +} + +static void test_msg_puback(struct mqtt_test *test) +{ + struct mqtt_puback_param *exp_param = + (struct mqtt_puback_param *)test->ctx; + struct mqtt_puback_param dec_param = { 0 }; + uint8_t type_and_flags; + uint32_t length; + struct buf_ctx buf; + int ret; + + buf.cur = client.tx_buf; + buf.end = client.tx_buf + client.tx_buf_size; + + ret = publish_ack_encode(&client, exp_param, &buf); + zassert_ok(ret, "publish_ack_encode failed"); + zassert_ok(validate_buffers(&buf, test->expected, + test->expected_len), + "Invalid packet content"); + + buf.cur = test->expected; + buf.end = test->expected + test->expected_len; + + ret = fixed_header_decode(&buf, &type_and_flags, &length); + zassert_ok(ret, "fixed_header_decode failed"); + zassert_equal(type_and_flags & 0xF0, MQTT_PKT_TYPE_PUBACK, + "Invalid packet type"); + zassert_equal(length, test->expected_len - 2, + "Invalid packet length"); + + ret = publish_ack_decode(&client, &buf, &dec_param); + zassert_ok(ret, "publish_ack_decode failed"); + zassert_ok(validate_structs(&dec_param, exp_param, sizeof(dec_param)), + "Incorrect PUBACK params decoded"); +} + +#define ENCODED_COMMON_ACK_DEFAULT 0x02, ENCODED_MID +#define COMMON_ACK_DEFAULT \ + .message_id = TEST_MSG_ID + +static uint8_t expect_puback_default[] = { + 0x40, ENCODED_COMMON_ACK_DEFAULT, +}; +static struct mqtt_puback_param puback_default = { + COMMON_ACK_DEFAULT, +}; + +#define ENCODED_COMMON_ACK_ERROR 0x03, ENCODED_MID, 0x80 +#define COMMON_ACK_ERROR \ + .message_id = TEST_MSG_ID, \ + .reason_code = 0x80 + +static uint8_t expect_puback_error[] = { + 0x40, ENCODED_COMMON_ACK_ERROR, +}; +static struct mqtt_puback_param puback_error = { + COMMON_ACK_ERROR, +}; + +#define ENCODED_COMMON_ACK_REASON_STRING \ + 0x19, ENCODED_MID, 0x80, \ + 0x15, ENCODED_PROP_REASON_STRING + +#define COMMON_ACK_REASON_STRING(exp_payload) \ + .message_id = TEST_MSG_ID, \ + .reason_code = 0x80, \ + .prop.rx.has_reason_string = true, \ + .prop.reason_string.utf8 = exp_payload + 9, \ + .prop.reason_string.size = 18 + +static uint8_t expect_puback_prop_rs[] = { + 0x40, ENCODED_COMMON_ACK_REASON_STRING, +}; +static struct mqtt_puback_param puback_prop_rs = { + COMMON_ACK_REASON_STRING(expect_puback_prop_rs), +}; + +#define ENCODED_COMMON_ACK_USER_PROP \ + 0x1c, ENCODED_MID, 0x00, \ + 0x18, ENCODED_PROP_USER_PROPERTY + +#define COMMON_ACK_USER_PROP(exp_payload) \ + .message_id = TEST_MSG_ID, \ + .prop.rx.has_user_prop = true, \ + .prop.user_prop[0].name.utf8 = exp_payload + 9, \ + .prop.user_prop[0].name.size = 9, \ + .prop.user_prop[0].value.utf8 = exp_payload + 20, \ + .prop.user_prop[0].value.size = 10 + +static uint8_t expect_puback_prop_up[] = { + 0x40, ENCODED_COMMON_ACK_USER_PROP, +}; +static struct mqtt_puback_param puback_prop_up = { + COMMON_ACK_USER_PROP(expect_puback_prop_up), +}; + +static struct mqtt_test puback_tests[] = { + { .test_name = "PUBACK, default", + .ctx = &puback_default, .test_fcn = test_msg_puback, + .expected = expect_puback_default, + .expected_len = sizeof(expect_puback_default) }, + { .test_name = "PUBACK, error", + .ctx = &puback_error, .test_fcn = test_msg_puback, + .expected = expect_puback_error, + .expected_len = sizeof(expect_puback_error) }, + { .test_name = "PUBACK, property Reason String", + .ctx = &puback_prop_rs, .test_fcn = test_msg_puback, + .expected = expect_puback_prop_rs, + .expected_len = sizeof(expect_puback_prop_rs) }, + { .test_name = "PUBACK, property User Property", + .ctx = &puback_prop_up, .test_fcn = test_msg_puback, + .expected = expect_puback_prop_up, + .expected_len = sizeof(expect_puback_prop_up) }, +}; + +ZTEST(mqtt_5_packet, test_mqtt_5_puback) +{ + run_packet_tests(puback_tests, ARRAY_SIZE(puback_tests)); +} + +static void test_msg_pubrec(struct mqtt_test *test) +{ + struct mqtt_pubrec_param *exp_param = + (struct mqtt_pubrec_param *)test->ctx; + struct mqtt_pubrec_param dec_param = { 0 }; + uint8_t type_and_flags; + uint32_t length; + struct buf_ctx buf; + int ret; + + buf.cur = client.tx_buf; + buf.end = client.tx_buf + client.tx_buf_size; + + ret = publish_receive_encode(&client, exp_param, &buf); + zassert_ok(ret, "publish_receive_encode failed"); + zassert_ok(validate_buffers(&buf, test->expected, + test->expected_len), + "Invalid packet content"); + + buf.cur = test->expected; + buf.end = test->expected + test->expected_len; + + ret = fixed_header_decode(&buf, &type_and_flags, &length); + zassert_ok(ret, "fixed_header_decode failed"); + zassert_equal(type_and_flags & 0xF0, MQTT_PKT_TYPE_PUBREC, + "Invalid packet type"); + zassert_equal(length, test->expected_len - 2, + "Invalid packet length"); + + ret = publish_receive_decode(&client, &buf, &dec_param); + zassert_ok(ret, "publish_receive_decode failed"); + zassert_ok(validate_structs(&dec_param, exp_param, sizeof(dec_param)), + "Incorrect PUBREC params decoded"); +} + +static uint8_t expect_pubrec_default[] = { + 0x50, ENCODED_COMMON_ACK_DEFAULT, +}; +static struct mqtt_pubrec_param pubrec_default = { + COMMON_ACK_DEFAULT, +}; + +static uint8_t expect_pubrec_error[] = { + 0x50, ENCODED_COMMON_ACK_ERROR, +}; +static struct mqtt_pubrec_param pubrec_error = { + COMMON_ACK_ERROR, +}; + +static uint8_t expect_pubrec_prop_rs[] = { + 0x50, ENCODED_COMMON_ACK_REASON_STRING, +}; +static struct mqtt_pubrec_param pubrec_prop_rs = { + COMMON_ACK_REASON_STRING(expect_pubrec_prop_rs), +}; + +static uint8_t expect_pubrec_prop_up[] = { + 0x50, ENCODED_COMMON_ACK_USER_PROP, +}; +static struct mqtt_pubrec_param pubrec_prop_up = { + COMMON_ACK_USER_PROP(expect_pubrec_prop_up), +}; + +static struct mqtt_test pubrec_tests[] = { + { .test_name = "PUBREC, default", + .ctx = &pubrec_default, .test_fcn = test_msg_pubrec, + .expected = expect_pubrec_default, + .expected_len = sizeof(expect_pubrec_default) }, + { .test_name = "PUBREC, error", + .ctx = &pubrec_error, .test_fcn = test_msg_pubrec, + .expected = expect_pubrec_error, + .expected_len = sizeof(expect_pubrec_error) }, + { .test_name = "PUBREC, property Reason String", + .ctx = &pubrec_prop_rs, .test_fcn = test_msg_pubrec, + .expected = expect_pubrec_prop_rs, + .expected_len = sizeof(expect_pubrec_prop_rs) }, + { .test_name = "PUBREC, property User Property", + .ctx = &pubrec_prop_up, .test_fcn = test_msg_pubrec, + .expected = expect_pubrec_prop_up, + .expected_len = sizeof(expect_pubrec_prop_up) }, +}; + +ZTEST(mqtt_5_packet, test_mqtt_5_pubrec) +{ + run_packet_tests(pubrec_tests, ARRAY_SIZE(pubrec_tests)); +} + +static void test_msg_pubrel(struct mqtt_test *test) +{ + struct mqtt_pubrel_param *exp_param = + (struct mqtt_pubrel_param *)test->ctx; + struct mqtt_pubrel_param dec_param = { 0 }; + uint8_t type_and_flags; + uint32_t length; + struct buf_ctx buf; + int ret; + + buf.cur = client.tx_buf; + buf.end = client.tx_buf + client.tx_buf_size; + + ret = publish_release_encode(&client, exp_param, &buf); + zassert_ok(ret, "publish_release_encode failed"); + zassert_ok(validate_buffers(&buf, test->expected, + test->expected_len), + "Invalid packet content"); + + buf.cur = test->expected; + buf.end = test->expected + test->expected_len; + + ret = fixed_header_decode(&buf, &type_and_flags, &length); + zassert_ok(ret, "fixed_header_decode failed"); + zassert_equal(type_and_flags & 0xF0, MQTT_PKT_TYPE_PUBREL, + "Invalid packet type"); + zassert_equal(length, test->expected_len - 2, + "Invalid packet length"); + + ret = publish_release_decode(&client, &buf, &dec_param); + zassert_ok(ret, "publish_release_decode failed"); + zassert_ok(validate_structs(&dec_param, exp_param, sizeof(dec_param)), + "Incorrect PUBREL params decoded"); +} + +static uint8_t expect_pubrel_default[] = { + 0x62, ENCODED_COMMON_ACK_DEFAULT, +}; +static struct mqtt_pubrel_param pubrel_default = { + COMMON_ACK_DEFAULT, +}; + +static uint8_t expect_pubrel_error[] = { + 0x62, ENCODED_COMMON_ACK_ERROR, +}; +static struct mqtt_pubrel_param pubrel_error = { + COMMON_ACK_ERROR, +}; + +static uint8_t expect_pubrel_prop_rs[] = { + 0x62, ENCODED_COMMON_ACK_REASON_STRING, +}; +static struct mqtt_pubrel_param pubrel_prop_rs = { + COMMON_ACK_REASON_STRING(expect_pubrel_prop_rs), +}; + +static uint8_t expect_pubrel_prop_up[] = { + 0x62, ENCODED_COMMON_ACK_USER_PROP, +}; +static struct mqtt_pubrel_param pubrel_prop_up = { + COMMON_ACK_USER_PROP(expect_pubrel_prop_up), +}; + +static struct mqtt_test pubrel_tests[] = { + { .test_name = "PUBREL, default", + .ctx = &pubrel_default, .test_fcn = test_msg_pubrel, + .expected = expect_pubrel_default, + .expected_len = sizeof(expect_pubrel_default) }, + { .test_name = "PUBREL, error", + .ctx = &pubrel_error, .test_fcn = test_msg_pubrel, + .expected = expect_pubrel_error, + .expected_len = sizeof(expect_pubrel_error) }, + { .test_name = "PUBREL, property Reason String", + .ctx = &pubrel_prop_rs, .test_fcn = test_msg_pubrel, + .expected = expect_pubrel_prop_rs, + .expected_len = sizeof(expect_pubrel_prop_rs) }, + { .test_name = "PUBREL, property User Property", + .ctx = &pubrel_prop_up, .test_fcn = test_msg_pubrel, + .expected = expect_pubrel_prop_up, + .expected_len = sizeof(expect_pubrel_prop_up) }, +}; + +ZTEST(mqtt_5_packet, test_mqtt_5_pubrel) +{ + run_packet_tests(pubrel_tests, ARRAY_SIZE(pubrel_tests)); +} + +static void test_msg_pubcomp(struct mqtt_test *test) +{ + struct mqtt_pubcomp_param *exp_param = + (struct mqtt_pubcomp_param *)test->ctx; + struct mqtt_pubcomp_param dec_param = { 0 }; + uint8_t type_and_flags; + uint32_t length; + struct buf_ctx buf; + int ret; + + buf.cur = client.tx_buf; + buf.end = client.tx_buf + client.tx_buf_size; + + ret = publish_complete_encode(&client, exp_param, &buf); + zassert_ok(ret, "publish_complete_encode failed"); + zassert_ok(validate_buffers(&buf, test->expected, + test->expected_len), + "Invalid packet content"); + + buf.cur = test->expected; + buf.end = test->expected + test->expected_len; + + ret = fixed_header_decode(&buf, &type_and_flags, &length); + zassert_ok(ret, "fixed_header_decode failed"); + zassert_equal(type_and_flags & 0xF0, MQTT_PKT_TYPE_PUBCOMP, + "Invalid packet type"); + zassert_equal(length, test->expected_len - 2, + "Invalid packet length"); + + ret = publish_complete_decode(&client, &buf, &dec_param); + zassert_ok(ret, "publish_complete_decode failed"); + zassert_ok(validate_structs(&dec_param, exp_param, sizeof(dec_param)), + "Incorrect PUBCOMP params decoded"); +} + +static uint8_t expect_pubcomp_default[] = { + 0x70, ENCODED_COMMON_ACK_DEFAULT, +}; +static struct mqtt_pubcomp_param pubcomp_default = { + COMMON_ACK_DEFAULT, +}; + +static uint8_t expect_pubcomp_error[] = { + 0x70, ENCODED_COMMON_ACK_ERROR, +}; +static struct mqtt_pubcomp_param pubcomp_error = { + COMMON_ACK_ERROR, +}; + +static uint8_t expect_pubcomp_prop_rs[] = { + 0x70, ENCODED_COMMON_ACK_REASON_STRING, +}; +static struct mqtt_pubcomp_param pubcomp_prop_rs = { + COMMON_ACK_REASON_STRING(expect_pubcomp_prop_rs), +}; + +static uint8_t expect_pubcomp_prop_up[] = { + 0x70, ENCODED_COMMON_ACK_USER_PROP, +}; +static struct mqtt_pubcomp_param pubcomp_prop_up = { + COMMON_ACK_USER_PROP(expect_pubcomp_prop_up), +}; + +static struct mqtt_test pubcomp_tests[] = { + { .test_name = "PUBCOMP, default", + .ctx = &pubcomp_default, .test_fcn = test_msg_pubcomp, + .expected = expect_pubcomp_default, + .expected_len = sizeof(expect_pubcomp_default) }, + { .test_name = "PUBCOMP, error", + .ctx = &pubcomp_error, .test_fcn = test_msg_pubcomp, + .expected = expect_pubcomp_error, + .expected_len = sizeof(expect_pubcomp_error) }, + { .test_name = "PUBCOMP, property Reason String", + .ctx = &pubcomp_prop_rs, .test_fcn = test_msg_pubcomp, + .expected = expect_pubcomp_prop_rs, + .expected_len = sizeof(expect_pubcomp_prop_rs) }, + { .test_name = "PUBCOMP, property User Property", + .ctx = &pubcomp_prop_up, .test_fcn = test_msg_pubcomp, + .expected = expect_pubcomp_prop_up, + .expected_len = sizeof(expect_pubcomp_prop_up) }, +}; + +ZTEST(mqtt_5_packet, test_mqtt_5_pubcomp) +{ + run_packet_tests(pubcomp_tests, ARRAY_SIZE(pubcomp_tests)); +} + +static void test_msg_subscribe(struct mqtt_test *test) +{ + struct mqtt_subscription_list *exp_param = + (struct mqtt_subscription_list *)test->ctx; + struct buf_ctx buf; + int ret; + + buf.cur = client.tx_buf; + buf.end = client.tx_buf + client.tx_buf_size; + + ret = subscribe_encode(&client, exp_param, &buf); + zassert_ok(ret, "subscribe_encode failed"); + zassert_ok(validate_buffers(&buf, test->expected, + test->expected_len), + "Invalid packet content"); +} + +static struct mqtt_topic test_sub_topic = { + .qos = MQTT_QOS_1_AT_LEAST_ONCE, + .topic = TEST_TOPIC, +}; + +#define SUBSCRIBE_COMMON \ + .list = &test_sub_topic, \ + .list_count = 1, \ + .message_id = TEST_MSG_ID + +#define ENCODED_SUBSCRIBE_TOPIC \ + 0x00, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x70, 0x69, 0x63, \ + 0x01 + +static uint8_t expect_subscribe_default[] = { + 0x82, 0x10, ENCODED_MID, + /* No properties */ + 0x00, + /* Payload */ + ENCODED_SUBSCRIBE_TOPIC, +}; +static struct mqtt_subscription_list subscribe_default = { + SUBSCRIBE_COMMON, +}; + +static uint8_t expect_subscribe_prop_si[] = { + 0x82, 0x13, ENCODED_MID, + /* Properties */ + 0x03, ENCODED_PROP_SUBSCRIPTION_IDENTIFIER, + /* Payload */ + ENCODED_SUBSCRIBE_TOPIC, +}; +static struct mqtt_subscription_list subscribe_prop_si = { + SUBSCRIBE_COMMON, + .prop.subscription_identifier = 1000, +}; + +static uint8_t expect_subscribe_prop_up[] = { + 0x82, 0x28, ENCODED_MID, + /* Properties */ + 0x18, ENCODED_PROP_USER_PROPERTY, + /* Payload */ + ENCODED_SUBSCRIBE_TOPIC, +}; +static struct mqtt_subscription_list subscribe_prop_up = { + SUBSCRIBE_COMMON, + .prop.user_prop[0] = { + .name = USER_PROP_NAME, + .value = USER_PROP_VALUE, + }, +}; + +static struct mqtt_test subscribe_tests[] = { + { .test_name = "SUBSCRIBE, default", + .ctx = &subscribe_default, .test_fcn = test_msg_subscribe, + .expected = expect_subscribe_default, + .expected_len = sizeof(expect_subscribe_default) }, + { .test_name = "SUBSCRIBE, property Subscription Identifier", + .ctx = &subscribe_prop_si, .test_fcn = test_msg_subscribe, + .expected = expect_subscribe_prop_si, + .expected_len = sizeof(expect_subscribe_prop_si) }, + { .test_name = "SUBSCRIBE, property User Property", + .ctx = &subscribe_prop_up, .test_fcn = test_msg_subscribe, + .expected = expect_subscribe_prop_up, + .expected_len = sizeof(expect_subscribe_prop_up) }, +}; + +ZTEST(mqtt_5_packet, test_mqtt_5_subscribe) +{ + run_packet_tests(subscribe_tests, ARRAY_SIZE(subscribe_tests)); +} + +static void test_msg_suback(struct mqtt_test *test) +{ + struct mqtt_suback_param *exp_param = + (struct mqtt_suback_param *)test->ctx; + struct mqtt_suback_param dec_param = { 0 }; + int ret; + uint8_t type_and_flags; + uint32_t length; + struct buf_ctx buf; + + buf.cur = test->expected; + buf.end = test->expected + test->expected_len; + + ret = fixed_header_decode(&buf, &type_and_flags, &length); + zassert_ok(ret, "fixed_header_decode failed"); + zassert_equal(type_and_flags & 0xF0, MQTT_PKT_TYPE_SUBACK, + "Invalid packet type"); + zassert_equal(length, test->expected_len - 2, + "Invalid packet length"); + + ret = subscribe_ack_decode(&client, &buf, &dec_param); + zassert_ok(ret, "subscribe_ack_decode failed"); + zassert_ok(validate_structs(&dec_param, exp_param, sizeof(dec_param)), + "Incorrect SUBACK params decoded"); +} + +static uint8_t expect_suback_default[] = { + 0x90, 0x05, ENCODED_MID, + /* No properties */ + 0x00, + /* Payload */ + 0x01, 0x02, +}; +static struct mqtt_suback_param suback_default = { + .message_id = TEST_MSG_ID, + .return_codes.data = expect_suback_default + 5, + .return_codes.len = 2, +}; + +static uint8_t expect_suback_prop_rs[] = { + 0x90, 0x1a, ENCODED_MID, + /* Properties */ + 0x15, ENCODED_PROP_REASON_STRING, + /* Payload */ + 0x01, 0x02, +}; +static struct mqtt_suback_param suback_prop_rs = { + .message_id = TEST_MSG_ID, + .prop.rx.has_reason_string = true, + .prop.reason_string.utf8 = expect_suback_prop_rs + 8, + .prop.reason_string.size = 18, + .return_codes.data = expect_suback_prop_rs + 26, + .return_codes.len = 2, +}; + +static uint8_t expect_suback_prop_up[] = { + 0x90, 0x1d, ENCODED_MID, + /* Properties */ + 0x18, ENCODED_PROP_USER_PROPERTY, + /* Payload */ + 0x01, 0x02, +}; +static struct mqtt_suback_param suback_prop_up = { + .message_id = TEST_MSG_ID, + .prop.rx.has_user_prop = true, + .prop.user_prop[0].name.utf8 = expect_suback_prop_up + 8, + .prop.user_prop[0].name.size = 9, + .prop.user_prop[0].value.utf8 = expect_suback_prop_up + 19, + .prop.user_prop[0].value.size = 10, + .return_codes.data = expect_suback_prop_up + 29, + .return_codes.len = 2, +}; + +static struct mqtt_test suback_tests[] = { + { .test_name = "SUBACK, default", + .ctx = &suback_default, .test_fcn = test_msg_suback, + .expected = expect_suback_default, + .expected_len = sizeof(expect_suback_default) }, + { .test_name = "SUBACK, property Reason String", + .ctx = &suback_prop_rs, .test_fcn = test_msg_suback, + .expected = expect_suback_prop_rs, + .expected_len = sizeof(expect_suback_prop_rs) }, + { .test_name = "SUBACK, property User Property", + .ctx = &suback_prop_up, .test_fcn = test_msg_suback, + .expected = expect_suback_prop_up, + .expected_len = sizeof(expect_suback_prop_up) }, +}; + +ZTEST(mqtt_5_packet, test_mqtt_5_suback) +{ + run_packet_tests(suback_tests, ARRAY_SIZE(suback_tests)); +} + +static void test_msg_unsubscribe(struct mqtt_test *test) +{ + struct mqtt_subscription_list *exp_param = + (struct mqtt_subscription_list *)test->ctx; + struct buf_ctx buf; + int ret; + + buf.cur = client.tx_buf; + buf.end = client.tx_buf + client.tx_buf_size; + + ret = unsubscribe_encode(&client, exp_param, &buf); + zassert_ok(ret, "unsubscribe_encode failed"); + zassert_ok(validate_buffers(&buf, test->expected, + test->expected_len), + "Invalid packet content"); +} + +static struct mqtt_topic test_unsub_topic = { + .qos = MQTT_QOS_1_AT_LEAST_ONCE, + .topic = TEST_TOPIC, +}; + +#define UNSUBSCRIBE_COMMON \ + .list = &test_unsub_topic, \ + .list_count = 1, \ + .message_id = TEST_MSG_ID + +#define ENCODED_UNSUBSCRIBE_TOPIC \ + 0x00, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x70, 0x69, 0x63 + +static uint8_t expect_unsubscribe_default[] = { + 0xa2, 0x0f, ENCODED_MID, + /* No properties */ + 0x00, + /* Payload */ + ENCODED_UNSUBSCRIBE_TOPIC, +}; +static struct mqtt_subscription_list unsubscribe_default = { + UNSUBSCRIBE_COMMON, +}; + +static uint8_t expect_unsubscribe_prop_up[] = { + 0xa2, 0x27, ENCODED_MID, + /* Properties */ + 0x18, ENCODED_PROP_USER_PROPERTY, + /* Payload */ + ENCODED_UNSUBSCRIBE_TOPIC, +}; +static struct mqtt_subscription_list unsubscribe_prop_up = { + UNSUBSCRIBE_COMMON, + .prop.user_prop[0] = { + .name = USER_PROP_NAME, + .value = USER_PROP_VALUE, + }, +}; + +static struct mqtt_test unsubscribe_tests[] = { + { .test_name = "UNSUBSCRIBE, default", + .ctx = &unsubscribe_default, .test_fcn = test_msg_unsubscribe, + .expected = expect_unsubscribe_default, + .expected_len = sizeof(expect_unsubscribe_default) }, + { .test_name = "UNSUBSCRIBE, property User Property", + .ctx = &unsubscribe_prop_up, .test_fcn = test_msg_unsubscribe, + .expected = expect_unsubscribe_prop_up, + .expected_len = sizeof(expect_unsubscribe_prop_up) }, +}; + +ZTEST(mqtt_5_packet, test_mqtt_5_unsubscribe) +{ + run_packet_tests(unsubscribe_tests, ARRAY_SIZE(unsubscribe_tests)); +} + +static void test_msg_unsuback(struct mqtt_test *test) +{ + struct mqtt_unsuback_param *exp_param = + (struct mqtt_unsuback_param *)test->ctx; + struct mqtt_unsuback_param dec_param = { 0 }; + int ret; + uint8_t type_and_flags; + uint32_t length; + struct buf_ctx buf; + + buf.cur = test->expected; + buf.end = test->expected + test->expected_len; + + ret = fixed_header_decode(&buf, &type_and_flags, &length); + zassert_ok(ret, "fixed_header_decode failed"); + zassert_equal(type_and_flags & 0xF0, MQTT_PKT_TYPE_UNSUBACK, + "Invalid packet type"); + zassert_equal(length, test->expected_len - 2, + "Invalid packet length"); + + ret = unsubscribe_ack_decode(&client, &buf, &dec_param); + zassert_ok(ret, "unsubscribe_ack_decode failed"); + zassert_ok(validate_structs(&dec_param, exp_param, sizeof(dec_param)), + "Incorrect UNSUBACK params decoded"); +} + +static uint8_t expect_unsuback_default[] = { + 0xb0, 0x05, ENCODED_MID, + /* No properties */ + 0x00, + /* Payload */ + 0x00, 0x00, +}; +static struct mqtt_unsuback_param unsuback_default = { + .message_id = TEST_MSG_ID, + .reason_codes.data = expect_unsuback_default + 5, + .reason_codes.len = 2, +}; + +static uint8_t expect_unsuback_prop_rs[] = { + 0xb0, 0x19, ENCODED_MID, + /* Properties */ + 0x15, ENCODED_PROP_REASON_STRING, + /* Payload */ + 0x00, +}; +static struct mqtt_unsuback_param unsuback_prop_rs = { + .message_id = TEST_MSG_ID, + .prop.rx.has_reason_string = true, + .prop.reason_string.utf8 = expect_unsuback_prop_rs + 8, + .prop.reason_string.size = 18, + .reason_codes.data = expect_unsuback_prop_rs + 26, + .reason_codes.len = 1, +}; + +static uint8_t expect_unsuback_prop_up[] = { + 0xb0, 0x1c, ENCODED_MID, + /* Properties */ + 0x18, ENCODED_PROP_USER_PROPERTY, + /* Payload */ + 0x00, +}; +static struct mqtt_unsuback_param unsuback_prop_up = { + .message_id = TEST_MSG_ID, + .prop.rx.has_user_prop = true, + .prop.user_prop[0].name.utf8 = expect_unsuback_prop_up + 8, + .prop.user_prop[0].name.size = 9, + .prop.user_prop[0].value.utf8 = expect_unsuback_prop_up + 19, + .prop.user_prop[0].value.size = 10, + .reason_codes.data = expect_unsuback_prop_up + 29, + .reason_codes.len = 1, +}; + +static struct mqtt_test unsuback_tests[] = { + { .test_name = "UNSUBACK, default", + .ctx = &unsuback_default, .test_fcn = test_msg_unsuback, + .expected = expect_unsuback_default, + .expected_len = sizeof(expect_unsuback_default) }, + { .test_name = "UNSUBACK, property Reason String", + .ctx = &unsuback_prop_rs, .test_fcn = test_msg_unsuback, + .expected = expect_unsuback_prop_rs, + .expected_len = sizeof(expect_unsuback_prop_rs) }, + { .test_name = "UNSUBACK, property User Property", + .ctx = &unsuback_prop_up, .test_fcn = test_msg_unsuback, + .expected = expect_unsuback_prop_up, + .expected_len = sizeof(expect_unsuback_prop_up) }, +}; + +ZTEST(mqtt_5_packet, test_mqtt_5_unsuback) +{ + run_packet_tests(unsuback_tests, ARRAY_SIZE(unsuback_tests)); +} + +static void test_msg_disconnect(struct mqtt_test *test) +{ + struct mqtt_disconnect_param *exp_param = + (struct mqtt_disconnect_param *)test->ctx; + struct mqtt_disconnect_param dec_param = { 0 }; + uint8_t type_and_flags; + struct buf_ctx buf; + uint32_t length; + int ret; + + buf.cur = client.tx_buf; + buf.end = client.tx_buf + client.tx_buf_size; + + ret = disconnect_encode(&client, exp_param, &buf); + zassert_ok(ret, "disconnect_encode failed"); + zassert_ok(validate_buffers(&buf, test->expected, + test->expected_len), + "Invalid packet content"); + + buf.cur = test->expected; + buf.end = test->expected + test->expected_len; + + ret = fixed_header_decode(&buf, &type_and_flags, &length); + zassert_ok(ret, "fixed_header_decode failed"); + zassert_equal(type_and_flags & 0xF0, MQTT_PKT_TYPE_DISCONNECT, + "Invalid packet type"); + zassert_equal(length, test->expected_len - 2, + "Invalid packet length"); + + ret = disconnect_decode(&client, &buf, &dec_param); + zassert_ok(ret, "disconnect_decode failed"); + zassert_ok(validate_structs(&dec_param, exp_param, sizeof(dec_param)), + "Incorrect DISCONNECT params decoded"); +} + +static uint8_t expect_disconnect_default[] = { + 0xe0, 0x00, +}; +static struct mqtt_disconnect_param disconnect_default = { + .reason_code = MQTT_DISCONNECT_NORMAL, +}; + +static uint8_t expect_disconnect_error[] = { + 0xe0, 0x01, 0x82, +}; +static struct mqtt_disconnect_param disconnect_error = { + .reason_code = MQTT_DISCONNECT_PROTOCOL_ERROR, +}; + +static uint8_t expect_disconnect_prop_sei[] = { + 0xe0, 0x07, 0x00, + /* Properties */ + 0x05, ENCODED_PROP_SESSION_EXPIRY_INTERVAL, +}; +static struct mqtt_disconnect_param disconnect_prop_sei = { + .reason_code = MQTT_DISCONNECT_NORMAL, + .prop.rx.has_session_expiry_interval = true, + .prop.session_expiry_interval = TEST_PROP_SESSION_EXPIRY_INTERVAL, +}; + +static uint8_t expect_disconnect_prop_rs[] = { + 0xe0, 0x17, 0x82, + /* Properties */ + 0x15, ENCODED_PROP_REASON_STRING, +}; +static struct mqtt_disconnect_param disconnect_prop_rs = { + .reason_code = MQTT_DISCONNECT_PROTOCOL_ERROR, + .prop.rx.has_reason_string = true, + .prop.reason_string.utf8 = expect_disconnect_prop_rs + 7, + .prop.reason_string.size = 18, +}; + +static uint8_t expect_disconnect_prop_up[] = { + 0xe0, 0x1a, 0x00, + /* Properties */ + 0x18, ENCODED_PROP_USER_PROPERTY, +}; +static struct mqtt_disconnect_param disconnect_prop_up = { + .reason_code = MQTT_DISCONNECT_NORMAL, + .prop.rx.has_user_prop = true, + .prop.user_prop[0].name.utf8 = expect_disconnect_prop_up + 7, + .prop.user_prop[0].name.size = 9, + .prop.user_prop[0].value.utf8 = expect_disconnect_prop_up + 18, + .prop.user_prop[0].value.size = 10, +}; + +static uint8_t expect_disconnect_prop_sr[] = { + 0xe0, 0x13, 0x00, + /* Properties */ + 0x11, ENCODED_PROP_SERVER_REFERENCE, +}; +static struct mqtt_disconnect_param disconnect_prop_sr = { + .reason_code = MQTT_DISCONNECT_NORMAL, + .prop.rx.has_server_reference = true, + .prop.server_reference.utf8 = expect_disconnect_prop_sr + 7, + .prop.server_reference.size = 14, +}; + +static struct mqtt_test disconnect_tests[] = { + { .test_name = "DISCONNECT, default", + .ctx = &disconnect_default, .test_fcn = test_msg_disconnect, + .expected = expect_disconnect_default, + .expected_len = sizeof(expect_disconnect_default) }, + { .test_name = "DISCONNECT, error", + .ctx = &disconnect_error, .test_fcn = test_msg_disconnect, + .expected = expect_disconnect_error, + .expected_len = sizeof(expect_disconnect_error) }, + { .test_name = "DISCONNECT, property Session Expiry Interval", + .ctx = &disconnect_prop_sei, .test_fcn = test_msg_disconnect, + .expected = expect_disconnect_prop_sei, + .expected_len = sizeof(expect_disconnect_prop_sei) }, + { .test_name = "DISCONNECT, property Reason String", + .ctx = &disconnect_prop_rs, .test_fcn = test_msg_disconnect, + .expected = expect_disconnect_prop_rs, + .expected_len = sizeof(expect_disconnect_prop_rs) }, + { .test_name = "DISCONNECT, property User Property", + .ctx = &disconnect_prop_up, .test_fcn = test_msg_disconnect, + .expected = expect_disconnect_prop_up, + .expected_len = sizeof(expect_disconnect_prop_up) }, + { .test_name = "DISCONNECT, property Server Reference", + .ctx = &disconnect_prop_sr, .test_fcn = test_msg_disconnect, + .expected = expect_disconnect_prop_sr, + .expected_len = sizeof(expect_disconnect_prop_sr) }, +}; + +ZTEST(mqtt_5_packet, test_mqtt_5_disconnect) +{ + run_packet_tests(disconnect_tests, ARRAY_SIZE(disconnect_tests)); +} + +static void test_msg_auth(struct mqtt_test *test) +{ + struct mqtt_auth_param *exp_param = + (struct mqtt_auth_param *)test->ctx; + struct mqtt_auth_param dec_param = { 0 }; + uint8_t type_and_flags; + struct buf_ctx buf; + uint32_t length; + int ret; + + buf.cur = client.tx_buf; + buf.end = client.tx_buf + client.tx_buf_size; + + ret = auth_encode(exp_param, &buf); + zassert_ok(ret, "auth_encode failed"); + zassert_ok(validate_buffers(&buf, test->expected, + test->expected_len), + "Invalid packet content"); + + buf.cur = test->expected; + buf.end = test->expected + test->expected_len; + + ret = fixed_header_decode(&buf, &type_and_flags, &length); + zassert_ok(ret, "fixed_header_decode failed"); + zassert_equal(type_and_flags & 0xF0, MQTT_PKT_TYPE_AUTH, + "Invalid packet type"); + zassert_equal(length, test->expected_len - 2, + "Invalid packet length"); + + ret = auth_decode(&client, &buf, &dec_param); + zassert_ok(ret, "auth_decode failed"); + zassert_ok(validate_structs(&dec_param, exp_param, sizeof(dec_param)), + "Incorrect AUTH params decoded"); +} + +static uint8_t expect_auth_default[] = { + 0xf0, 0x00, +}; +static struct mqtt_auth_param auth_default = { + .reason_code = MQTT_AUTH_SUCCESS, +}; + +static uint8_t expect_auth_reason[] = { + 0xf0, 0x01, 0x18, +}; +static struct mqtt_auth_param auth_reason = { + .reason_code = MQTT_AUTH_CONTINUE_AUTHENTICATION, +}; + +static uint8_t expect_auth_prop_am_ad[] = { + 0xf0, 0x1f, 0x00, + /* Properties */ + 0x1d, ENCODED_PROP_AUTHENTICATION_METHOD, + ENCODED_PROP_AUTHENTICATION_DATA, +}; +static struct mqtt_auth_param auth_prop_am_ad = { + .reason_code = MQTT_AUTH_SUCCESS, + .prop.rx.has_auth_method = true, + .prop.auth_method.utf8 = expect_auth_prop_am_ad + 7, + .prop.auth_method.size = 19, + .prop.rx.has_auth_data = true, + .prop.auth_data.data = expect_auth_prop_am_ad + 29, + .prop.auth_data.len = 4, +}; + +static uint8_t expect_auth_prop_rs[] = { + 0xf0, 0x17, 0x18, + /* Properties */ + 0x15, ENCODED_PROP_REASON_STRING, +}; +static struct mqtt_auth_param auth_prop_rs = { + .reason_code = MQTT_AUTH_CONTINUE_AUTHENTICATION, + .prop.rx.has_reason_string = true, + .prop.reason_string.utf8 = expect_auth_prop_rs + 7, + .prop.reason_string.size = 18, +}; + +static uint8_t expect_auth_prop_up[] = { + 0xf0, 0x1a, 0x00, + /* Properties */ + 0x18, ENCODED_PROP_USER_PROPERTY, +}; +static struct mqtt_auth_param auth_prop_up = { + .reason_code = MQTT_AUTH_SUCCESS, + .prop.rx.has_user_prop = true, + .prop.user_prop[0].name.utf8 = expect_auth_prop_up + 7, + .prop.user_prop[0].name.size = 9, + .prop.user_prop[0].value.utf8 = expect_auth_prop_up + 18, + .prop.user_prop[0].value.size = 10, +}; + +static struct mqtt_test auth_tests[] = { + { .test_name = "AUTH, default", + .ctx = &auth_default, .test_fcn = test_msg_auth, + .expected = expect_auth_default, + .expected_len = sizeof(expect_auth_default) }, + { .test_name = "AUTH, reason code", + .ctx = &auth_reason, .test_fcn = test_msg_auth, + .expected = expect_auth_reason, + .expected_len = sizeof(expect_auth_reason) }, + { .test_name = "AUTH, property Authentication Method and Data", + .ctx = &auth_prop_am_ad, .test_fcn = test_msg_auth, + .expected = expect_auth_prop_am_ad, + .expected_len = sizeof(expect_auth_prop_am_ad) }, + { .test_name = "AUTH, property Reason String", + .ctx = &auth_prop_rs, .test_fcn = test_msg_auth, + .expected = expect_auth_prop_rs, + .expected_len = sizeof(expect_auth_prop_rs) }, + { .test_name = "AUTH, property User Property", + .ctx = &auth_prop_up, .test_fcn = test_msg_auth, + .expected = expect_auth_prop_up, + .expected_len = sizeof(expect_auth_prop_up) }, +}; + +ZTEST(mqtt_5_packet, test_mqtt_5_auth) +{ + run_packet_tests(auth_tests, ARRAY_SIZE(auth_tests)); +} + +static void packet_tests_before(void *fixture) +{ + ARG_UNUSED(fixture); + + mqtt_client_init(&client); + client.protocol_version = MQTT_VERSION_5_0; + client.rx_buf = rx_buffer; + client.rx_buf_size = sizeof(rx_buffer); + client.tx_buf = tx_buffer; + client.tx_buf_size = sizeof(tx_buffer); +} + +static void packet_tests_after(void *fixture) +{ + ARG_UNUSED(fixture); + + memset(&client, 0, sizeof(client)); +} + +ZTEST_SUITE(mqtt_5_packet, NULL, NULL, packet_tests_before, packet_tests_after, NULL); diff --git a/tests/net/lib/mqtt/v5_0/mqtt_packet/testcase.yaml b/tests/net/lib/mqtt/v5_0/mqtt_packet/testcase.yaml new file mode 100644 index 000000000000..8c639e76cefe --- /dev/null +++ b/tests/net/lib/mqtt/v5_0/mqtt_packet/testcase.yaml @@ -0,0 +1,9 @@ +common: + tags: + - mqtt + - net + - userspace + min_ram: 16 + depends_on: netif +tests: + net.mqtt_5.packet: {}