diff --git a/include/net/mqtt.h b/include/net/mqtt.h index 65752a583729e..2aec0ed71dd10 100644 --- a/include/net/mqtt.h +++ b/include/net/mqtt.h @@ -1,474 +1,689 @@ /* - * Copyright (c) 2016 Intel Corporation + * Copyright (c) 2018 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ +/** @file mqtt.h + * + * @defgroup mqtt_socket MQTT Client library + * @ingroup networking + * @{ + * @brief MQTT Client Implementation + * + * @details + * MQTT Client's Application interface is defined in this header. + * + * @note The implementation assumes TCP module is enabled. + * + * @note By default the implementation uses MQTT version 3.1.1. + */ + #ifndef ZEPHYR_INCLUDE_NET_MQTT_H_ #define ZEPHYR_INCLUDE_NET_MQTT_H_ -#include -#include -#include +#include + +#include +#include +#include #ifdef __cplusplus extern "C" { #endif /** - * @brief MQTT library - * @defgroup mqtt MQTT library - * @ingroup networking - * @{ + * @brief MQTT Asynchronous Events notified to the application from the module + * through the callback registered by the application. */ +enum mqtt_evt_type { + /** Acknowledgment of connection request. Event result accompanying + * the event indicates whether the connection failed or succeeded. + */ + MQTT_EVT_CONNACK, -/** - * MQTT application type - */ -enum mqtt_app { - /** Publisher and Subscriber application */ - MQTT_APP_PUBLISHER_SUBSCRIBER, - /** Publisher only application */ - MQTT_APP_PUBLISHER, - /** Subscriber only application */ - MQTT_APP_SUBSCRIBER, - /** MQTT Server */ - MQTT_APP_SERVER + /** Disconnection Event. MQTT Client Reference is no longer valid once + * this event is received for the client. + */ + MQTT_EVT_DISCONNECT, + + /** Publish event received when message is published on a topic client + * is subscribed to. + * + * @note PUBLISH event structure only contains payload size, the payload + * data parameter should be ignored. Payload content has to be + * read manually with @ref mqtt_read_publish_payload function. + */ + MQTT_EVT_PUBLISH, + + /** Acknowledgment for published message with QoS 1. */ + MQTT_EVT_PUBACK, + + /** Reception confirmation for published message with QoS 2. */ + MQTT_EVT_PUBREC, + + /** Release of published message with QoS 2. */ + MQTT_EVT_PUBREL, + + /** Confirmation to a publish release message with QoS 2. */ + MQTT_EVT_PUBCOMP, + + /** Acknowledgment to a subscribe request. */ + MQTT_EVT_SUBACK, + + /** Acknowledgment to a unsubscribe request. */ + MQTT_EVT_UNSUBACK }; -/** - * MQTT context structure - * - * @details Context structure for the MQTT high-level API with support for QoS. - * - * This API is designed for asynchronous operation, so callbacks are - * executed when some events must be addressed outside the MQTT routines. - * Those events are triggered by the reception or transmission of MQTT messages - * and are defined by the MQTT v3.1.1 spec, see: - * - * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/mqtt-v3.1.1.html - * - * For example, assume that Zephyr is operating as a MQTT_APP_SUBSCRIBER, so it - * may receive the MQTT PUBLISH and MQTT PUBREL (QoS2) messages. Once the - * messages are parsed and validated, the #publish_rx callback is executed. - * - * Internally, the #publish_rx callback must store the #mqtt_publish_msg message - * when a MQTT PUBLISH msg is received. When a MQTT PUBREL message is received, - * the application must evaluate if the PUBREL's Packet Identifier matches a - * previous received MQTT PUBLISH message. In this case, the user may provide a - * collection of #mqtt_publish_msg structs (array of structs) to store those - * messages. - * - * NOTE: The application (and not the API) is in charge of keeping track of - * the state of the received and sent messages. - */ -struct mqtt_ctx { - /** Net app context structure */ - struct net_app_ctx net_app_ctx; - s32_t net_init_timeout; - s32_t net_timeout; +/** @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. */ +}; - /** Connectivity */ - char *peer_addr_str; - u16_t peer_port; +/** @brief MQTT Quality of Service types. */ +enum mqtt_qos { + /** Lowest Quality of Service, no acknowledgment needed for published + * message. + */ + MQTT_QOS_0_AT_MOST_ONCE = 0x00, -#if defined(CONFIG_MQTT_LIB_TLS) - /** TLS parameters */ - u8_t *request_buf; - size_t request_buf_len; - u8_t *personalization_data; - size_t personalization_data_len; - char *cert_host; - - /** TLS thread parameters */ - struct k_mem_pool *tls_mem_pool; - k_thread_stack_t *tls_stack; - size_t tls_stack_size; - - /** TLS callback */ - net_app_ca_cert_cb_t cert_cb; - net_app_entropy_src_cb_t entropy_src_cb; - - /** TLS handshake */ - struct k_sem tls_hs_wait; - s32_t tls_hs_timeout; -#endif + /** Medium Quality of Service, if acknowledgment expected for published + * message, duplicate messages permitted. + */ + MQTT_QOS_1_AT_LEAST_ONCE = 0x01, - /** Callback executed when a MQTT CONNACK msg is received and validated. - * If this function pointer is not used, must be set to NULL. + /** Highest Quality of Service, acknowledgment expected and message + * shall be published only once. Message not published to interested + * parties unless client issues a PUBREL. */ - void (*connect)(struct mqtt_ctx *ctx); + MQTT_QOS_2_EXACTLY_ONCE = 0x02 +}; + +/** @brief MQTT CONNACK return codes. */ +enum mqtt_conn_return_code { + /** Connection accepted. */ + MQTT_CONNECTION_ACCEPTED = 0x00, - /** Callback executed when a MQTT DISCONNECT msg is sent. - * If this function pointer is not used, must be set to NULL. + /** The Server does not support the level of the MQTT protocol + * requested by the Client. */ - void (*disconnect)(struct mqtt_ctx *ctx); - - /** Callback executed when a #MQTT_APP_PUBLISHER application receives - * a MQTT PUBxxxx msg. - * If type is MQTT_PUBACK, MQTT_PUBCOMP or MQTT_PUBREC, this callback - * must return 0 if pkt_id matches the packet id of a previously - * received MQTT_PUBxxx message. If this callback returns 0, the caller - * will continue. - * Any other value will stop the QoS handshake and the caller will - * return -EINVAL. The application must discard all the messages - * already processed. - * - * Note: this callback must be not NULL - * - * @param [in] ctx MQTT context - * @param [in] pkt_id Packet Identifier for the input MQTT msg - * @param [in] type Packet type + MQTT_UNACCEPTABLE_PROTOCOL_VERSION = 0x01, + + /** The Client identifier is correct UTF-8 but not allowed by the + * Server. */ - int (*publish_tx)(struct mqtt_ctx *ctx, u16_t pkt_id, - enum mqtt_packet type); - - /** Callback executed when a MQTT_APP_SUBSCRIBER, - * MQTT_APP_PUBLISHER_SUBSCRIBER or MQTT_APP_SERVER applications receive - * a MQTT PUBxxxx msg. If this callback returns 0, the caller will - * continue. If type is MQTT_PUBREL this callback must return 0 if - * pkt_id matches the packet id of a previously received MQTT_PUBxxx - * message. Any other value will stop the QoS handshake and the caller - * will return -EINVAL - * - * Note: this callback must be not NULL - * - * @param [in] ctx MQTT context - * @param [in] msg Publish message, this parameter is only used - * when the type is MQTT_PUBLISH - * @param [in] pkt_id Packet Identifier for the input msg - * @param [in] type Packet type + MQTT_IDENTIFIER_REJECTED = 0x02, + + /** The Network Connection has been made but the MQTT service is + * unavailable. */ - int (*publish_rx)(struct mqtt_ctx *ctx, struct mqtt_publish_msg *msg, - u16_t pkt_id, enum mqtt_packet type); + MQTT_SERVER_UNAVAILABLE = 0x03, - /** Callback executed when a MQTT_APP_SUBSCRIBER or - * MQTT_APP_PUBLISHER_SUBSCRIBER receives the MQTT SUBACK message - * If this callback returns 0, the caller will continue. Any other - * value will make the caller return -EINVAL. - * - * Note: this callback must be not NULL - * - * @param [in] ctx MQTT context - * @param [in] pkt_id Packet Identifier for the MQTT SUBACK msg - * @param [in] items Number of elements in the qos array - * @param [in] qos Array of QoS values + /** The data in the user name or password is malformed. */ + MQTT_BAD_USER_NAME_OR_PASSWORD = 0x04, + + /** The Client is not authorized to connect. */ + MQTT_NOT_AUTHORIZED = 0x05 +}; + +/** @brief MQTT SUBACK return codes. */ +enum mqtt_suback_return_code { + /** Subscription with QoS 0 succeeded. */ + MQTT_SUBACK_SUCCESS_QoS_0 = 0x00, + + /** Subscription with QoS 1 succeeded. */ + MQTT_SUBACK_SUCCESS_QoS_1 = 0x01, + + /** Subscription with QoS 2 succeeded. */ + MQTT_SUBACK_SUCCESS_QoS_2 = 0x02, + + /** Subscription for a topic failed. */ + MQTT_SUBACK_FAILURE = 0x80 +}; + +/** @brief Abstracts UTF-8 encoded strings. */ +struct mqtt_utf8 { + u8_t *utf8; /**< Pointer to UTF-8 string. */ + u32_t size; /**< Size of UTF string, in bytes. */ +}; + +/** @brief Abstracts binary strings. */ +struct mqtt_binstr { + u8_t *data; /**< Pointer to binary stream. */ + u32_t len; /**< Length of binary stream. */ +}; + +/** @brief Abstracts MQTT UTF-8 encoded topic that can be subscribed + * to or published. + */ +struct mqtt_topic { + /** Topic on to be published or subscribed to. */ + struct mqtt_utf8 topic; + + /** Quality of service requested for the subscription. + * @ref mqtt_qos for details. */ - int (*subscribe)(struct mqtt_ctx *ctx, u16_t pkt_id, - u8_t items, enum mqtt_qos qos[]); + u8_t qos; +}; - /** Callback executed when a MQTT_APP_SUBSCRIBER or - * MQTT_APP_PUBLISHER_SUBSCRIBER receives the MQTT UNSUBACK message - * If this callback returns 0, the caller will continue. Any other value - * will make the caller return -EINVAL - * - * Note: this callback must be not NULL - * - * @param [in] ctx MQTT context - * @param [in] pkt_id Packet Identifier for the MQTT SUBACK msg +/** @brief Parameters for a publish message. */ +struct mqtt_publish_message { + struct mqtt_topic topic; /**< Topic on which data was published. */ + struct mqtt_binstr payload; /**< Payload on the topic published. */ +}; + +/** @brief Parameters for a connection acknowledgment (CONNACK). */ +struct mqtt_connack_param { + /** The Session Present flag enables a Client to establish whether + * the Client and Server have a consistent view about whether there + * is already stored Session state. */ - int (*unsubscribe)(struct mqtt_ctx *ctx, u16_t pkt_id); + u8_t session_present_flag; - /** Callback executed when an incoming message doesn't pass the - * validation stage. This callback may be NULL. - * The pkt_type variable may be set to MQTT_INVALID, if the parsing - * stage is aborted before determining the MQTT msg packet type. - * - * @param [in] ctx MQTT context - * @param [in] pkt_type MQTT Packet type + /** The appropriate non-zero Connect return code indicates if the Server + * is unable to process a connection request for some reason. */ - void (*malformed)(struct mqtt_ctx *ctx, u16_t pkt_type); + enum mqtt_conn_return_code return_code; +}; + +/** @brief Parameters for MQTT publish acknowledgment (PUBACK). */ +struct mqtt_puback_param { + u16_t message_id; +}; - /* Internal use only */ - int (*rcv)(struct mqtt_ctx *ctx, struct net_pkt *); +/** @brief Parameters for MQTT publish receive (PUBREC). */ +struct mqtt_pubrec_param { + u16_t message_id; +}; - /** Application type, see: enum mqtt_app */ - u8_t app_type; +/** @brief Parameters for MQTT publish release (PUBREL). */ +struct mqtt_pubrel_param { + u16_t message_id; +}; - /* Clean session is also part of the MQTT CONNECT msg, however app - * behavior is influenced by this parameter, so we keep a copy here +/** @brief Parameters for MQTT publish complete (PUBCOMP). */ +struct mqtt_pubcomp_param { + u16_t message_id; +}; + +/** @brief Parameters for MQTT subscription acknowledgment (SUBACK). */ +struct mqtt_suback_param { + u16_t message_id; + struct mqtt_binstr return_codes; +}; + +/** @brief Parameters for MQTT unsubscribe acknowledgment (UNSUBACK). */ +struct mqtt_unsuback_param { + u16_t message_id; +}; + +/** @brief Parameters for a publish message. */ +struct mqtt_publish_param { + /** Messages including topic, QoS and its payload (if any) + * to be published. + */ + struct mqtt_publish_message message; + + /** Message id used for the publish message. Redundant for QoS 0. */ + u16_t message_id; + + /** Duplicate flag. If 1, it indicates the message is being + * retransmitted. Has no meaning with QoS 0. + */ + u8_t dup_flag : 1; + + /** Retain flag. If 1, the message shall be stored persistently + * by the broker. */ - /** MQTT Clean Session parameter */ - u8_t clean_session:1; + u8_t retain_flag : 1; +}; + +/** @brief List of topics in a subscription request. */ +struct mqtt_subscription_list { + /** Array containing topics along with QoS for each. */ + struct mqtt_topic *list; - /** 1 if the MQTT application is connected and 0 otherwise */ - u8_t connected:1; + /** Number of topics in the subscription list */ + u16_t list_count; + + /** Message id used to identify subscription request. */ + u16_t message_id; }; /** - * Initializes the MQTT context structure - * - * @param ctx MQTT context structure - * @param app_type See enum mqtt_app - * @retval 0 always + * @brief Defines event parameters notified along with asynchronous events + * to the application. */ -int mqtt_init(struct mqtt_ctx *ctx, enum mqtt_app app_type); +union mqtt_evt_param { + /** Parameters accompanying MQTT_EVT_CONNACK event. */ + struct mqtt_connack_param connack; + + /** Parameters accompanying MQTT_EVT_PUBLISH event. + * + * @note PUBLISH event structure only contains payload size, the payload + * data parameter should be ignored. Payload content has to be + * read manually with @ref mqtt_read_publish_payload function. + */ + struct mqtt_publish_param publish; + + /** Parameters accompanying MQTT_EVT_PUBACK event. */ + struct mqtt_puback_param puback; + + /** Parameters accompanying MQTT_EVT_PUBREC event. */ + struct mqtt_pubrec_param pubrec; + + /** Parameters accompanying MQTT_EVT_PUBREL event. */ + struct mqtt_pubrel_param pubrel; + + /** Parameters accompanying MQTT_EVT_PUBCOMP event. */ + struct mqtt_pubcomp_param pubcomp; + + /** Parameters accompanying MQTT_EVT_SUBACK event. */ + struct mqtt_suback_param suback; + + /** Parameters accompanying MQTT_EVT_UNSUBACK event. */ + struct mqtt_unsuback_param unsuback; +}; + +/** @brief Defines MQTT asynchronous event notified to the application. */ +struct mqtt_evt { + /** Identifies the event. */ + enum mqtt_evt_type type; + + /** Contains parameters (if any) accompanying the event. */ + union mqtt_evt_param param; + + /** Event result. 0 or a negative error code (errno.h) indicating + * reason of failure. + */ + int result; +}; + +struct mqtt_client; /** - * Release the MQTT context structure + * @brief Asynchronous event notification callback registered by the + * application. * - * @param ctx MQTT context structure - * @retval 0 on success, and <0 if error + * @param[in] client Identifies the client for which the event is notified. + * @param[in] evt Event description along with result and associated + * parameters (if any). */ -int mqtt_close(struct mqtt_ctx *ctx); +typedef void (*mqtt_evt_cb_t)(struct mqtt_client *client, + const struct mqtt_evt *evt); + +/** @brief TLS configuration for secure MQTT transports. */ +struct mqtt_sec_config { + /** Indicates the preference for peer verification. */ + int peer_verify; + + /** Indicates the number of entries in the cipher list. */ + u32_t cipher_count; + + /** Indicates the list of ciphers to be used for the session. + * May be NULL to use the default ciphers. + */ + int *cipher_list; + + /** Indicates the number of entries in the sec tag list. */ + u32_t sec_tag_count; + + /** Indicates the list of security tags to be used for the session. */ + sec_tag_t *seg_tag_list; + + /** Peer hostname for ceritificate verification. + * May be NULL to skip hostname verification. + */ + char *hostname; +}; + +/** @brief MQTT transport type. */ +enum mqtt_transport_type { + /** Use non secure TCP transport for MQTT connection. */ + MQTT_TRANSPORT_NON_SECURE = 0x00, + +#if defined(CONFIG_MQTT_LIB_TLS) + /** Use secure TCP transport (TLS) for MQTT connection. */ + MQTT_TRANSPORT_SECURE = 0x01, +#endif /* CONFIG_MQTT_LIB_TLS */ + + /** Shall not be used as a transport type. + * Indicator of maximum transport types possible. + */ + MQTT_TRANSPORT_NUM = 0x02 +}; + +/** @brief MQTT transport specific data. */ +struct mqtt_transport { + /** Transport type selection for client instance. + * @ref mqtt_transport_type for possible values. MQTT_TRANSPORT_MAX + * is not a valid type. + */ + enum mqtt_transport_type type; + + union { + /* TCP socket transport for MQTT */ + struct { + /** Socket descriptor. */ + int sock; + } tcp; + +#if defined(CONFIG_MQTT_LIB_TLS) + /* TLS socket transport for MQTT */ + struct { + /** Socket descriptor. */ + int sock; + + /** TLS configuration. See @ref mqtt_sec_config for + * details. + */ + struct mqtt_sec_config config; + } tls; +#endif /* CONFIG_MQTT_LIB_TLS */ + }; +}; + +/** @brief MQTT internal state. */ +struct mqtt_internal { + /** Internal. Mutex to protect access to the client instance. */ + struct k_mutex mutex; + + /** Internal. Wall clock value (in milliseconds) of the last activity + * that occurred. Needed for periodic PING. + */ + u32_t last_activity; + + /** Internal. Client's state in the connection. */ + u32_t state; + + /** Internal. Packet length read so far. */ + u32_t rx_buf_datalen; + + /** Internal. Remaining payload length to read. */ + u32_t remaining_payload; +}; /** - * Connect to an MQTT broker - * - * @param ctx MQTT context structure - * @retval 0 on success, and <0 if error + * @brief MQTT Client definition to maintain information relevant to the + * client. */ +struct mqtt_client { + /** MQTT client internal state. */ + struct mqtt_internal internal; + + /** MQTT transport configuration and data. */ + struct mqtt_transport transport; + + /** Unique client identification to be used for the connection. */ + struct mqtt_utf8 client_id; + + /** Broker details, for example, address, port. Address type should + * be compatible with transport used. + */ + const void *broker; + + /** User name (if any) to be used for the connection. NULL indicates + * no user name. + */ + struct mqtt_utf8 *user_name; + + /** Password (if any) to be used for the connection. Note that if + * password is provided, user name shall also be provided. NULL + * indicates no password. + */ + struct mqtt_utf8 *password; + + /** Will topic and QoS. Can be NULL. */ + struct mqtt_topic *will_topic; + + /** Will message. Can be NULL. Non NULL value valid only if will topic + * is not NULL. + */ + struct mqtt_utf8 *will_message; + + /** Application callback registered with the module to get MQTT events. + */ + mqtt_evt_cb_t evt_cb; + + /** Receive buffer used for MQTT packet reception in RX path. */ + u8_t *rx_buf; + + /** Size of receive buffer. */ + u32_t rx_buf_size; + + /** Transmit buffer used for creating MQTT packet in TX path. */ + u8_t *tx_buf; -int mqtt_connect(struct mqtt_ctx *ctx); + /** Size of transmit buffer. */ + u32_t tx_buf_size; + + /** MQTT protocol version. */ + u8_t protocol_version; + + /** Will retain flag, 1 if will message shall be retained persistently. + */ + u8_t will_retain : 1; + + /** Clean session flag indicating a fresh (1) or a retained session (0). + * Default is 1. + */ + u8_t clean_session : 1; +}; /** - * Sends the MQTT CONNECT message + * @brief Initializes the client instance. * - * @param [in] ctx MQTT context structure - * @param [in] msg MQTT CONNECT msg + * @param[in] client Client instance for which the procedure is requested. + * Shall not be NULL. * - * @retval 0 on success - * @retval -EIO - * @retval -ENOMEM - * @retval -EINVAL + * @note Shall be called to initialize client structure, before setting any + * client parameters and before connecting to broker. */ -int mqtt_tx_connect(struct mqtt_ctx *ctx, struct mqtt_connect_msg *msg); +void mqtt_client_init(struct mqtt_client *client); /** - * Send the MQTT DISCONNECT message + * @brief API to request new MQTT client connection. * - * @param [in] ctx MQTT context structure + * @param[in] client Client instance for which the procedure is requested. + * Shall not be NULL. * - * @retval 0 on success - * @retval -EIO - * @retval -ENOMEM - * @retval -EINVAL - */ -int mqtt_tx_disconnect(struct mqtt_ctx *ctx); - -/** - * Sends the MQTT PUBACK message with the given packet id + * @note This memory is assumed to be resident until mqtt_disconnect is called. + * @note Any subsequent changes to parameters like broker address, user name, + * device id, etc. have no effect once MQTT connection is established. * - * @param [in] ctx MQTT context structure - * @param [in] id MQTT Packet Identifier + * @return 0 or a negative error code (errno.h) indicating reason of failure. * - * @retval 0 on success - * @retval -EINVAL - * @retval -ENOMEM - * @retval -EIO + * @note Default protocol revision used for connection request is 3.1.1. Please + * set client.protocol_version = MQTT_VERSION_3_1_0 to use protocol 3.1.0. + * @note Please modify :option:`CONFIG_MQTT_KEEPALIVE` time to override default + * of 1 minute. */ -int mqtt_tx_puback(struct mqtt_ctx *ctx, u16_t id); +int mqtt_connect(struct mqtt_client *client); /** - * Sends the MQTT PUBCOMP message with the given packet id + * @brief API to publish messages on topics. * - * @param [in] ctx MQTT context structure - * @param [in] id MQTT Packet Identifier + * @param[in] client Client instance for which the procedure is requested. + * Shall not be NULL. + * @param[in] param Parameters to be used for the publish message. + * Shall not be NULL. * - * @retval 0 on success - * @retval -EINVAL - * @retval -ENOMEM - * @retval -EIO + * @return 0 or a negative error code (errno.h) indicating reason of failure. */ -int mqtt_tx_pubcomp(struct mqtt_ctx *ctx, u16_t id); +int mqtt_publish(struct mqtt_client *client, + const struct mqtt_publish_param *param); /** - * Sends the MQTT PUBREC message with the given packet id - * - * @param [in] ctx MQTT context structure - * @param [in] id MQTT Packet Identifier - * @retval 0 on success - * @retval -EINVAL - * @retval -ENOMEM - * @retval -EIO - */ -int mqtt_tx_pubrec(struct mqtt_ctx *ctx, u16_t id); - -/** - * Sends the MQTT PUBREL message with the given packet id + * @brief API used by client to send acknowledgment on receiving QoS1 publish + * message. Should be called on reception of @ref MQTT_EVT_PUBLISH with + * QoS level @ref MQTT_QOS_1_AT_LEAST_ONCE. * - * @param [in] ctx MQTT context structure - * @param [in] id MQTT Packet Identifier + * @param[in] client Client instance for which the procedure is requested. + * Shall not be NULL. + * @param[in] param Identifies message being acknowledged. * - * @retval 0 on success - * @retval -EINVAL - * @retval -ENOMEM - * @retval -EIO + * @return 0 or a negative error code (errno.h) indicating reason of failure. */ -int mqtt_tx_pubrel(struct mqtt_ctx *ctx, u16_t id); +int mqtt_publish_qos1_ack(struct mqtt_client *client, + const struct mqtt_puback_param *param); /** - * Sends the MQTT PUBLISH message + * @brief API used by client to send acknowledgment on receiving QoS2 publish + * message. Should be called on reception of @ref MQTT_EVT_PUBLISH with + * QoS level @ref MQTT_QOS_2_EXACTLY_ONCE. * - * @param [in] ctx MQTT context structure - * @param [in] msg MQTT PUBLISH msg + * @param[in] client Identifies client instance for which the procedure is + * requested. Shall not be NULL. + * @param[in] param Identifies message being acknowledged. * - * @retval 0 on success - * @retval -EINVAL - * @retval -ENOMEM - * @retval -EIO + * @return 0 or a negative error code (errno.h) indicating reason of failure. */ -int mqtt_tx_publish(struct mqtt_ctx *ctx, struct mqtt_publish_msg *msg); +int mqtt_publish_qos2_receive(struct mqtt_client *client, + const struct mqtt_pubrec_param *param); /** - * Sends the MQTT PINGREQ message + * @brief API used by client to request release of QoS2 publish message. + * Should be called on reception of @ref MQTT_EVT_PUBREC. * - * @param [in] ctx MQTT context structure + * @param[in] client Client instance for which the procedure is requested. + * Shall not be NULL. + * @param[in] param Identifies message being released. * - * @retval 0 on success - * @retval -EINVAL - * @retval -ENOMEM - * @retval -EIO + * @return 0 or a negative error code (errno.h) indicating reason of failure. */ -int mqtt_tx_pingreq(struct mqtt_ctx *ctx); +int mqtt_publish_qos2_release(struct mqtt_client *client, + const struct mqtt_pubrel_param *param); /** - * Sends the MQTT SUBSCRIBE message - * - * @param [in] ctx MQTT context structure - * @param [in] pkt_id Packet identifier for the MQTT SUBSCRIBE msg - * @param [in] items Number of elements in 'topics' and 'qos' arrays - * @param [in] topics Array of 'items' elements containing C strings. - * For example: {"sensors", "lights", "doors"} - * @param [in] qos Array of 'items' elements containing MQTT QoS values: - * MQTT_QoS0, MQTT_QoS1, MQTT_QoS2. For example for the 'topics' - * array above the following QoS may be used: {MQTT_QoS0, - * MQTT_QoS2, MQTT_QoS1}, indicating that the subscription to - * 'lights' must be done with MQTT_QoS2 - * - * @retval 0 on success - * @retval -EINVAL - * @retval -ENOMEM - * @retval -EIO - */ -int mqtt_tx_subscribe(struct mqtt_ctx *ctx, u16_t pkt_id, u8_t items, - const char *topics[], const enum mqtt_qos qos[]); - -/** - * Sends the MQTT UNSUBSCRIBE message + * @brief API used by client to send acknowledgment on receiving QoS2 publish + * release message. Should be called on reception of + * @ref MQTT_EVT_PUBREL. * - * @param [in] ctx MQTT context structure - * @param [in] pkt_id Packet identifier for the MQTT UNSUBSCRIBE msg - * @param [in] items Number of elements in the 'topics' array - * @param [in] topics Array of 'items' elements containing C strings + * @param[in] client Identifies client instance for which the procedure is + * requested. Shall not be NULL. + * @param[in] param Identifies message being completed. * - * @retval 0 on success - * @retval -EINVAL - * @retval -ENOMEM - * @retval -EIO + * @return 0 or a negative error code (errno.h) indicating reason of failure. */ -int mqtt_tx_unsubscribe(struct mqtt_ctx *ctx, u16_t pkt_id, u8_t items, - const char *topics[]); - +int mqtt_publish_qos2_complete(struct mqtt_client *client, + const struct mqtt_pubcomp_param *param); /** - * Parses and validates the MQTT CONNACK msg + * @brief API to request subscription of one or more topics on the connection. * - * @param [in] ctx MQTT context structure - * @param [in] rx Data buffer - * @param [in] clean_session MQTT clean session parameter + * @param[in] client Identifies client instance for which the procedure + * is requested. Shall not be NULL. + * @param[in] param Subscription parameters. Shall not be NULL. * - * @retval 0 on success - * @retval -EINVAL + * @return 0 or a negative error code (errno.h) indicating reason of failure. */ -int mqtt_rx_connack(struct mqtt_ctx *ctx, struct net_buf *rx, - int clean_session); +int mqtt_subscribe(struct mqtt_client *client, + const struct mqtt_subscription_list *param); /** - * Parses and validates the MQTT PUBACK message + * @brief API to request unsubscribtion of one or more topics on the connection. * - * @param [in] ctx MQTT context structure - * @param [in] rx Data buffer + * @param[in] client Identifies client instance for which the procedure is + * requested. Shall not be NULL. + * @param[in] param Parameters describing topics being unsubscribed from. + * Shall not be NULL. * - * @retval 0 on success - * @retval -EINVAL + * @note QoS included in topic description is unused in this API. + * + * @return 0 or a negative error code (errno.h) indicating reason of failure. */ -int mqtt_rx_puback(struct mqtt_ctx *ctx, struct net_buf *rx); +int mqtt_unsubscribe(struct mqtt_client *client, + const struct mqtt_subscription_list *param); /** - * Parses and validates the MQTT PUBCOMP message + * @brief API to send MQTT ping. The use of this API is optional, as the library + * handles the connection keep-alive on it's own, see @ref mqtt_live. * - * @param [in] ctx MQTT context structure - * @param [in] rx Data buffer + * @param[in] client Identifies client instance for which procedure is + * requested. * - * @retval 0 on success - * @retval -EINVAL + * @return 0 or a negative error code (errno.h) indicating reason of failure. */ -int mqtt_rx_pubcomp(struct mqtt_ctx *ctx, struct net_buf *rx); +int mqtt_ping(struct mqtt_client *client); /** - * Parses and validates the MQTT PUBREC message + * @brief API to disconnect MQTT connection. * - * @param [in] ctx MQTT context structure - * @param [in] rx Data buffer + * @param[in] client Identifies client instance for which procedure is + * requested. * - * @retval 0 on success - * @retval -EINVAL + * @return 0 or a negative error code (errno.h) indicating reason of failure. */ -int mqtt_rx_pubrec(struct mqtt_ctx *ctx, struct net_buf *rx); +int mqtt_disconnect(struct mqtt_client *client); /** - * Parses and validates the MQTT PUBREL message + * @brief API to abort MQTT connection. This will close the corresponding + * transport without closing the connection gracefully at the MQTT level + * (with disconnect message). * - * @param [in] ctx MQTT context structure - * @param [in] rx Data buffer + * @param[in] client Identifies client instance for which procedure is + * requested. * - * @retval 0 on success - * @retval -EINVAL + * @return 0 or a negative error code (errno.h) indicating reason of failure. */ -int mqtt_rx_pubrel(struct mqtt_ctx *ctx, struct net_buf *rx); +int mqtt_abort(struct mqtt_client *client); /** - * Parses the MQTT PINGRESP message + * @brief This API should be called periodically for the client to be able + * to keep the connection alive by sending Ping Requests if need be. + * + * @param[in] client Client instance for which the procedure is requested. + * Shall not be NULL. * - * @param [in] ctx MQTT context structure - * @param [in] rx Data buffer + * @note Application shall ensure that the periodicity of calling this function + * makes it possible to respect the Keep Alive time agreed with the + * broker on connection. @ref mqtt_connect for details on Keep Alive + * time. * - * @retval 0 on success - * @retval -EINVAL + * @return 0 or a negative error code (errno.h) indicating reason of failure. */ -int mqtt_rx_pingresp(struct mqtt_ctx *ctx, struct net_buf *rx); +int mqtt_live(struct mqtt_client *client); /** - * Parses the MQTT SUBACK message + * @brief Receive an incoming MQTT packet. The registered callback will be + * called with the packet content. * - * @param [in] ctx MQTT context structure - * @param [in] rx Data buffer + * @note In case of PUBLISH message, the payload has to be read separately with + * @ref mqtt_read_publish_payload function. The size of the payload to + * read is provided in the publish event structure. * - * @retval 0 on success - * @retval -EINVAL - */ -int mqtt_rx_suback(struct mqtt_ctx *ctx, struct net_buf *rx); - -/** - * Parses the MQTT UNSUBACK message + * @note This is a non-blocking call. * - * @param [in] ctx MQTT context structure - * @param [in] rx Data buffer + * @param[in] client Client instance for which the procedure is requested. + * Shall not be NULL. * - * @retval 0 on success - * @retval -EINVAL + * @return 0 or a negative error code (errno.h) indicating reason of failure. */ -int mqtt_rx_unsuback(struct mqtt_ctx *ctx, struct net_buf *rx); +int mqtt_input(struct mqtt_client *client); /** - * Parses the MQTT PUBLISH message + * @brief Read the payload of the received PUBLISH message. This function should + * be called within the MQTT event handler, when MQTT PUBLISH message is + * notified. * - * @param [in] ctx MQTT context structure - * @param [in] rx Data buffer + * @note This is a non-blocking call. * - * @retval 0 on success - * @retval -EINVAL - * @retval -ENOMEM - */ -int mqtt_rx_publish(struct mqtt_ctx *ctx, struct net_buf *rx); - -/** - * @} + * @param[in] client Client instance for which the procedure is requested. + * Shall not be NULL. + * @param[out] buffer Buffer where payload should be stored. + * @param[in] length Length of the buffer, in bytes. + * + * @return Number of bytes read or a negative error code (errno.h) indicating + * reason of failure. */ +int mqtt_read_publish_payload(struct mqtt_client *client, void *buffer, + size_t length); #ifdef __cplusplus } #endif #endif /* ZEPHYR_INCLUDE_NET_MQTT_H_ */ + +/**@} */ diff --git a/include/net/mqtt_legacy.h b/include/net/mqtt_legacy.h new file mode 100644 index 0000000000000..88e738bd30fad --- /dev/null +++ b/include/net/mqtt_legacy.h @@ -0,0 +1,474 @@ +/* + * Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_NET_MQTT_H_ +#define ZEPHYR_INCLUDE_NET_MQTT_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief MQTT library + * @defgroup mqtt MQTT library + * @ingroup networking + * @{ + */ + +/** + * MQTT application type + */ +enum mqtt_app { + /** Publisher and Subscriber application */ + MQTT_APP_PUBLISHER_SUBSCRIBER, + /** Publisher only application */ + MQTT_APP_PUBLISHER, + /** Subscriber only application */ + MQTT_APP_SUBSCRIBER, + /** MQTT Server */ + MQTT_APP_SERVER +}; + +/** + * MQTT context structure + * + * @details Context structure for the MQTT high-level API with support for QoS. + * + * This API is designed for asynchronous operation, so callbacks are + * executed when some events must be addressed outside the MQTT routines. + * Those events are triggered by the reception or transmission of MQTT messages + * and are defined by the MQTT v3.1.1 spec, see: + * + * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/mqtt-v3.1.1.html + * + * For example, assume that Zephyr is operating as a MQTT_APP_SUBSCRIBER, so it + * may receive the MQTT PUBLISH and MQTT PUBREL (QoS2) messages. Once the + * messages are parsed and validated, the #publish_rx callback is executed. + * + * Internally, the #publish_rx callback must store the #mqtt_publish_msg message + * when a MQTT PUBLISH msg is received. When a MQTT PUBREL message is received, + * the application must evaluate if the PUBREL's Packet Identifier matches a + * previous received MQTT PUBLISH message. In this case, the user may provide a + * collection of #mqtt_publish_msg structs (array of structs) to store those + * messages. + * + * NOTE: The application (and not the API) is in charge of keeping track of + * the state of the received and sent messages. + */ +struct mqtt_ctx { + /** Net app context structure */ + struct net_app_ctx net_app_ctx; + s32_t net_init_timeout; + s32_t net_timeout; + + /** Connectivity */ + char *peer_addr_str; + u16_t peer_port; + +#if defined(CONFIG_MQTT_LEGACY_LIB_TLS) + /** TLS parameters */ + u8_t *request_buf; + size_t request_buf_len; + u8_t *personalization_data; + size_t personalization_data_len; + char *cert_host; + + /** TLS thread parameters */ + struct k_mem_pool *tls_mem_pool; + k_thread_stack_t *tls_stack; + size_t tls_stack_size; + + /** TLS callback */ + net_app_ca_cert_cb_t cert_cb; + net_app_entropy_src_cb_t entropy_src_cb; + + /** TLS handshake */ + struct k_sem tls_hs_wait; + s32_t tls_hs_timeout; +#endif + + /** Callback executed when a MQTT CONNACK msg is received and validated. + * If this function pointer is not used, must be set to NULL. + */ + void (*connect)(struct mqtt_ctx *ctx); + + /** Callback executed when a MQTT DISCONNECT msg is sent. + * If this function pointer is not used, must be set to NULL. + */ + void (*disconnect)(struct mqtt_ctx *ctx); + + /** Callback executed when a #MQTT_APP_PUBLISHER application receives + * a MQTT PUBxxxx msg. + * If type is MQTT_PUBACK, MQTT_PUBCOMP or MQTT_PUBREC, this callback + * must return 0 if pkt_id matches the packet id of a previously + * received MQTT_PUBxxx message. If this callback returns 0, the caller + * will continue. + * Any other value will stop the QoS handshake and the caller will + * return -EINVAL. The application must discard all the messages + * already processed. + * + * Note: this callback must be not NULL + * + * @param [in] ctx MQTT context + * @param [in] pkt_id Packet Identifier for the input MQTT msg + * @param [in] type Packet type + */ + int (*publish_tx)(struct mqtt_ctx *ctx, u16_t pkt_id, + enum mqtt_packet type); + + /** Callback executed when a MQTT_APP_SUBSCRIBER, + * MQTT_APP_PUBLISHER_SUBSCRIBER or MQTT_APP_SERVER applications receive + * a MQTT PUBxxxx msg. If this callback returns 0, the caller will + * continue. If type is MQTT_PUBREL this callback must return 0 if + * pkt_id matches the packet id of a previously received MQTT_PUBxxx + * message. Any other value will stop the QoS handshake and the caller + * will return -EINVAL + * + * Note: this callback must be not NULL + * + * @param [in] ctx MQTT context + * @param [in] msg Publish message, this parameter is only used + * when the type is MQTT_PUBLISH + * @param [in] pkt_id Packet Identifier for the input msg + * @param [in] type Packet type + */ + int (*publish_rx)(struct mqtt_ctx *ctx, struct mqtt_publish_msg *msg, + u16_t pkt_id, enum mqtt_packet type); + + /** Callback executed when a MQTT_APP_SUBSCRIBER or + * MQTT_APP_PUBLISHER_SUBSCRIBER receives the MQTT SUBACK message + * If this callback returns 0, the caller will continue. Any other + * value will make the caller return -EINVAL. + * + * Note: this callback must be not NULL + * + * @param [in] ctx MQTT context + * @param [in] pkt_id Packet Identifier for the MQTT SUBACK msg + * @param [in] items Number of elements in the qos array + * @param [in] qos Array of QoS values + */ + int (*subscribe)(struct mqtt_ctx *ctx, u16_t pkt_id, + u8_t items, enum mqtt_qos qos[]); + + /** Callback executed when a MQTT_APP_SUBSCRIBER or + * MQTT_APP_PUBLISHER_SUBSCRIBER receives the MQTT UNSUBACK message + * If this callback returns 0, the caller will continue. Any other value + * will make the caller return -EINVAL + * + * Note: this callback must be not NULL + * + * @param [in] ctx MQTT context + * @param [in] pkt_id Packet Identifier for the MQTT SUBACK msg + */ + int (*unsubscribe)(struct mqtt_ctx *ctx, u16_t pkt_id); + + /** Callback executed when an incoming message doesn't pass the + * validation stage. This callback may be NULL. + * The pkt_type variable may be set to MQTT_INVALID, if the parsing + * stage is aborted before determining the MQTT msg packet type. + * + * @param [in] ctx MQTT context + * @param [in] pkt_type MQTT Packet type + */ + void (*malformed)(struct mqtt_ctx *ctx, u16_t pkt_type); + + /* Internal use only */ + int (*rcv)(struct mqtt_ctx *ctx, struct net_pkt *); + + /** Application type, see: enum mqtt_app */ + u8_t app_type; + + /* Clean session is also part of the MQTT CONNECT msg, however app + * behavior is influenced by this parameter, so we keep a copy here + */ + /** MQTT Clean Session parameter */ + u8_t clean_session:1; + + /** 1 if the MQTT application is connected and 0 otherwise */ + u8_t connected:1; +}; + +/** + * Initializes the MQTT context structure + * + * @param ctx MQTT context structure + * @param app_type See enum mqtt_app + * @retval 0 always + */ +int mqtt_init(struct mqtt_ctx *ctx, enum mqtt_app app_type); + +/** + * Release the MQTT context structure + * + * @param ctx MQTT context structure + * @retval 0 on success, and <0 if error + */ +int mqtt_close(struct mqtt_ctx *ctx); + +/** + * Connect to an MQTT broker + * + * @param ctx MQTT context structure + * @retval 0 on success, and <0 if error + */ + +int mqtt_connect(struct mqtt_ctx *ctx); + +/** + * Sends the MQTT CONNECT message + * + * @param [in] ctx MQTT context structure + * @param [in] msg MQTT CONNECT msg + * + * @retval 0 on success + * @retval -EIO + * @retval -ENOMEM + * @retval -EINVAL + */ +int mqtt_tx_connect(struct mqtt_ctx *ctx, struct mqtt_connect_msg *msg); + +/** + * Send the MQTT DISCONNECT message + * + * @param [in] ctx MQTT context structure + * + * @retval 0 on success + * @retval -EIO + * @retval -ENOMEM + * @retval -EINVAL + */ +int mqtt_tx_disconnect(struct mqtt_ctx *ctx); + +/** + * Sends the MQTT PUBACK message with the given packet id + * + * @param [in] ctx MQTT context structure + * @param [in] id MQTT Packet Identifier + * + * @retval 0 on success + * @retval -EINVAL + * @retval -ENOMEM + * @retval -EIO + */ +int mqtt_tx_puback(struct mqtt_ctx *ctx, u16_t id); + +/** + * Sends the MQTT PUBCOMP message with the given packet id + * + * @param [in] ctx MQTT context structure + * @param [in] id MQTT Packet Identifier + * + * @retval 0 on success + * @retval -EINVAL + * @retval -ENOMEM + * @retval -EIO + */ +int mqtt_tx_pubcomp(struct mqtt_ctx *ctx, u16_t id); + +/** + * Sends the MQTT PUBREC message with the given packet id + * + * @param [in] ctx MQTT context structure + * @param [in] id MQTT Packet Identifier + * @retval 0 on success + * @retval -EINVAL + * @retval -ENOMEM + * @retval -EIO + */ +int mqtt_tx_pubrec(struct mqtt_ctx *ctx, u16_t id); + +/** + * Sends the MQTT PUBREL message with the given packet id + * + * @param [in] ctx MQTT context structure + * @param [in] id MQTT Packet Identifier + * + * @retval 0 on success + * @retval -EINVAL + * @retval -ENOMEM + * @retval -EIO + */ +int mqtt_tx_pubrel(struct mqtt_ctx *ctx, u16_t id); + +/** + * Sends the MQTT PUBLISH message + * + * @param [in] ctx MQTT context structure + * @param [in] msg MQTT PUBLISH msg + * + * @retval 0 on success + * @retval -EINVAL + * @retval -ENOMEM + * @retval -EIO + */ +int mqtt_tx_publish(struct mqtt_ctx *ctx, struct mqtt_publish_msg *msg); + +/** + * Sends the MQTT PINGREQ message + * + * @param [in] ctx MQTT context structure + * + * @retval 0 on success + * @retval -EINVAL + * @retval -ENOMEM + * @retval -EIO + */ +int mqtt_tx_pingreq(struct mqtt_ctx *ctx); + +/** + * Sends the MQTT SUBSCRIBE message + * + * @param [in] ctx MQTT context structure + * @param [in] pkt_id Packet identifier for the MQTT SUBSCRIBE msg + * @param [in] items Number of elements in 'topics' and 'qos' arrays + * @param [in] topics Array of 'items' elements containing C strings. + * For example: {"sensors", "lights", "doors"} + * @param [in] qos Array of 'items' elements containing MQTT QoS values: + * MQTT_QoS0, MQTT_QoS1, MQTT_QoS2. For example for the 'topics' + * array above the following QoS may be used: {MQTT_QoS0, + * MQTT_QoS2, MQTT_QoS1}, indicating that the subscription to + * 'lights' must be done with MQTT_QoS2 + * + * @retval 0 on success + * @retval -EINVAL + * @retval -ENOMEM + * @retval -EIO + */ +int mqtt_tx_subscribe(struct mqtt_ctx *ctx, u16_t pkt_id, u8_t items, + const char *topics[], const enum mqtt_qos qos[]); + +/** + * Sends the MQTT UNSUBSCRIBE message + * + * @param [in] ctx MQTT context structure + * @param [in] pkt_id Packet identifier for the MQTT UNSUBSCRIBE msg + * @param [in] items Number of elements in the 'topics' array + * @param [in] topics Array of 'items' elements containing C strings + * + * @retval 0 on success + * @retval -EINVAL + * @retval -ENOMEM + * @retval -EIO + */ +int mqtt_tx_unsubscribe(struct mqtt_ctx *ctx, u16_t pkt_id, u8_t items, + const char *topics[]); + + +/** + * Parses and validates the MQTT CONNACK msg + * + * @param [in] ctx MQTT context structure + * @param [in] rx Data buffer + * @param [in] clean_session MQTT clean session parameter + * + * @retval 0 on success + * @retval -EINVAL + */ +int mqtt_rx_connack(struct mqtt_ctx *ctx, struct net_buf *rx, + int clean_session); + +/** + * Parses and validates the MQTT PUBACK message + * + * @param [in] ctx MQTT context structure + * @param [in] rx Data buffer + * + * @retval 0 on success + * @retval -EINVAL + */ +int mqtt_rx_puback(struct mqtt_ctx *ctx, struct net_buf *rx); + +/** + * Parses and validates the MQTT PUBCOMP message + * + * @param [in] ctx MQTT context structure + * @param [in] rx Data buffer + * + * @retval 0 on success + * @retval -EINVAL + */ +int mqtt_rx_pubcomp(struct mqtt_ctx *ctx, struct net_buf *rx); + +/** + * Parses and validates the MQTT PUBREC message + * + * @param [in] ctx MQTT context structure + * @param [in] rx Data buffer + * + * @retval 0 on success + * @retval -EINVAL + */ +int mqtt_rx_pubrec(struct mqtt_ctx *ctx, struct net_buf *rx); + +/** + * Parses and validates the MQTT PUBREL message + * + * @param [in] ctx MQTT context structure + * @param [in] rx Data buffer + * + * @retval 0 on success + * @retval -EINVAL + */ +int mqtt_rx_pubrel(struct mqtt_ctx *ctx, struct net_buf *rx); + +/** + * Parses the MQTT PINGRESP message + * + * @param [in] ctx MQTT context structure + * @param [in] rx Data buffer + * + * @retval 0 on success + * @retval -EINVAL + */ +int mqtt_rx_pingresp(struct mqtt_ctx *ctx, struct net_buf *rx); + +/** + * Parses the MQTT SUBACK message + * + * @param [in] ctx MQTT context structure + * @param [in] rx Data buffer + * + * @retval 0 on success + * @retval -EINVAL + */ +int mqtt_rx_suback(struct mqtt_ctx *ctx, struct net_buf *rx); + +/** + * Parses the MQTT UNSUBACK message + * + * @param [in] ctx MQTT context structure + * @param [in] rx Data buffer + * + * @retval 0 on success + * @retval -EINVAL + */ +int mqtt_rx_unsuback(struct mqtt_ctx *ctx, struct net_buf *rx); + +/** + * Parses the MQTT PUBLISH message + * + * @param [in] ctx MQTT context structure + * @param [in] rx Data buffer + * + * @retval 0 on success + * @retval -EINVAL + * @retval -ENOMEM + */ +int mqtt_rx_publish(struct mqtt_ctx *ctx, struct net_buf *rx); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_NET_MQTT_H_ */ diff --git a/include/net/mqtt_types.h b/include/net/mqtt_legacy_types.h similarity index 100% rename from include/net/mqtt_types.h rename to include/net/mqtt_legacy_types.h diff --git a/samples/net/mqtt_publisher/overlay-tls.conf b/samples/net/mqtt_publisher/overlay-tls.conf new file mode 100644 index 0000000000000..cc05134e18fe2 --- /dev/null +++ b/samples/net/mqtt_publisher/overlay-tls.conf @@ -0,0 +1,10 @@ +CONFIG_MAIN_STACK_SIZE=4096 +CONFIG_MQTT_LIB_TLS=y +CONFIG_NET_SOCKETS_SOCKOPT_TLS=y + +# TLS configuration +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_BUILTIN=y +CONFIG_MBEDTLS_ENABLE_HEAP=y +CONFIG_MBEDTLS_HEAP_SIZE=30000 +CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=2048 diff --git a/samples/net/mqtt_publisher/prj_frdm_k64f_tls.conf b/samples/net/mqtt_publisher/prj_frdm_k64f_tls.conf deleted file mode 100644 index 43665ee6f68b6..0000000000000 --- a/samples/net/mqtt_publisher/prj_frdm_k64f_tls.conf +++ /dev/null @@ -1,45 +0,0 @@ -CONFIG_NETWORKING=y -CONFIG_NET_TCP=y -CONFIG_ENTROPY_GENERATOR=y -CONFIG_NET_ARP=y -CONFIG_NET_L2_ETHERNET=y -CONFIG_NET_LOG=y -CONFIG_INIT_STACKS=y - -CONFIG_NET_PKT_RX_COUNT=16 -CONFIG_NET_PKT_TX_COUNT=16 -CONFIG_NET_BUF_RX_COUNT=16 -CONFIG_NET_BUF_TX_COUNT=16 - -CONFIG_NET_IPV6_RA_RDNSS=y -CONFIG_NET_IF_UNICAST_IPV4_ADDR_COUNT=3 - -CONFIG_PRINTK=y -#CONFIG_NET_DEBUG_NET_PKT=y - -CONFIG_NET_IPV4=n -# Enable IPv6 support -CONFIG_NET_IPV6=y - -# Enable the MQTT Lib -CONFIG_MQTT_LIB=y -CONFIG_MQTT_LIB_TLS=y - -CONFIG_NET_CONFIG_SETTINGS=y -CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1" -CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::2" - -CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.168.1.101" -CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.168.1.10" - -CONFIG_MAIN_STACK_SIZE=2048 - -# For IPv6 -CONFIG_NET_BUF_DATA_SIZE=256 - -CONFIG_MBEDTLS=y -CONFIG_MBEDTLS_BUILTIN=y -CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=2500 -CONFIG_MBEDTLS_ENABLE_HEAP=y -CONFIG_MBEDTLS_HEAP_SIZE=30000 -CONFIG_MBEDTLS_CFG_FILE="config-mini-tls1_2.h" diff --git a/samples/net/mqtt_publisher/src/config.h b/samples/net/mqtt_publisher/src/config.h index 1271c3f691ab2..0356c2852dd9c 100644 --- a/samples/net/mqtt_publisher/src/config.h +++ b/samples/net/mqtt_publisher/src/config.h @@ -39,6 +39,8 @@ #define APP_MAX_ITERATIONS 100 +#define APP_MQTT_BUFFER_SIZE 128 + #define MQTT_CLIENTID "zephyr_publisher" /* Set the following to 1 to enable the Bluemix topic format */ diff --git a/samples/net/mqtt_publisher/src/main.c b/samples/net/mqtt_publisher/src/main.c index 665f88ed55201..64ab8f7afef08 100644 --- a/samples/net/mqtt_publisher/src/main.c +++ b/samples/net/mqtt_publisher/src/main.c @@ -5,10 +5,9 @@ */ #include +#include #include -#include - #include #include #include @@ -20,48 +19,20 @@ #include "config.h" -#define CONN_TRIES 20 - -/* Container for some structures used by the MQTT publisher app. */ -struct mqtt_client_ctx { - /** - * The connect message structure is only used during the connect - * stage. Developers must set some msg properties before calling the - * mqtt_tx_connect routine. See below. - */ - struct mqtt_connect_msg connect_msg; - /** - * This is the message that will be received by the server - * (MQTT broker). - */ - struct mqtt_publish_msg pub_msg; - - /** - * This is the MQTT application context variable. - */ - struct mqtt_ctx mqtt_ctx; - - /** - * This variable will be passed to the connect callback, declared inside - * the mqtt context struct. If not used, it could be set to NULL. - */ - void *connect_data; - - /** - * This variable will be passed to the disconnect callback, declared - * inside the mqtt context struct. If not used, it could be set to NULL. - */ - void *disconnect_data; - - /** - * This variable will be passed to the publish_tx callback, declared - * inside the mqtt context struct. If not used, it could be set to NULL. - */ - void *publish_data; -}; +/* Buffers for MQTT client. */ +static u8_t rx_buffer[APP_MQTT_BUFFER_SIZE]; +static u8_t tx_buffer[APP_MQTT_BUFFER_SIZE]; /* The mqtt client struct */ -static struct mqtt_client_ctx client_ctx; +static struct mqtt_client client_ctx; + +/* MQTT Broker details. */ +static struct sockaddr_storage broker; + +static struct pollfd fds[1]; +static int nfds; + +static bool connected; /* This routine sets some basic properties for the network context variable */ static int network_setup(void); @@ -70,154 +41,152 @@ static int network_setup(void); #include "test_certs.h" -/* TLS */ #define TLS_SNI_HOSTNAME "localhost" -#define TLS_REQUEST_BUF_SIZE 1500 -#define TLS_PRIVATE_DATA "Zephyr TLS mqtt publisher" +#define APP_CA_CERT_TAG 1 +#define APP_PSK_TAG 2 -static u8_t tls_request_buf[TLS_REQUEST_BUF_SIZE]; - -NET_STACK_DEFINE(mqtt_tls_stack, tls_stack, - CONFIG_NET_APP_TLS_STACK_SIZE, CONFIG_NET_APP_TLS_STACK_SIZE); - -NET_APP_TLS_POOL_DEFINE(tls_mem_pool, 30); - -int setup_cert(struct net_app_ctx *ctx, void *cert) -{ +static sec_tag_t m_sec_tags[] = { +#if defined(MBEDTLS_X509_CRT_PARSE_C) + APP_CA_CERT_TAG, +#endif #if defined(MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED) - mbedtls_ssl_conf_psk(&ctx->tls.mbedtls.conf, - client_psk, sizeof(client_psk), - (const unsigned char *)client_psk_id, - sizeof(client_psk_id) - 1); + APP_PSK_TAG, #endif +}; -#if defined(MBEDTLS_X509_CRT_PARSE_C) - { - mbedtls_x509_crt *ca_cert = cert; - int ret; - - ret = mbedtls_x509_crt_parse_der(ca_cert, - ca_certificate, - sizeof(ca_certificate)); - if (ret != 0) { - NET_ERR("mbedtls_x509_crt_parse_der failed " - "(-0x%x)", -ret); - return ret; - } - - /* mbedtls_x509_crt_verify() should be called to verify the - * cerificate in the real cases - */ - - mbedtls_ssl_conf_ca_chain(&ctx->tls.mbedtls.conf, - ca_cert, NULL); +static int tls_init(void) +{ + int err = -EINVAL; - mbedtls_ssl_conf_authmode(&ctx->tls.mbedtls.conf, - MBEDTLS_SSL_VERIFY_REQUIRED); +#if defined(MBEDTLS_X509_CRT_PARSE_C) + err = tls_credential_add(APP_CA_CERT_TAG, TLS_CREDENTIAL_CA_CERTIFICATE, + ca_certificate, sizeof(ca_certificate)); + if (err < 0) { + NET_ERR("Failed to register public certificate: %d", err); + return err; + } +#endif - mbedtls_ssl_conf_cert_profile(&ctx->tls.mbedtls.conf, - &mbedtls_x509_crt_profile_default); +#if defined(MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED) + err = tls_credential_add(APP_PSK_TAG, TLS_CREDENTIAL_PSK, + client_psk, sizeof(client_psk)); + if (err < 0) { + NET_ERR("Failed to register PSK: %d", err); + return err; } -#endif /* MBEDTLS_X509_CRT_PARSE_C */ - return 0; -} + err = tls_credential_add(APP_PSK_TAG, TLS_CREDENTIAL_PSK_ID, + client_psk_id, sizeof(client_psk_id) - 1); + if (err < 0) { + NET_ERR("Failed to register PSK ID: %d", err); + } #endif -/* The signature of this routine must match the connect callback declared at - * the mqtt.h header. - */ -static void connect_cb(struct mqtt_ctx *mqtt_ctx) -{ - struct mqtt_client_ctx *client_ctx; - - client_ctx = CONTAINER_OF(mqtt_ctx, struct mqtt_client_ctx, mqtt_ctx); + return err; +} - printk("[%s:%d]", __func__, __LINE__); +#endif /* CONFIG_MQTT_LIB_TLS */ - if (client_ctx->connect_data) { - printk(" user_data: %s", - (const char *)client_ctx->connect_data); +static void prepare_fds(struct mqtt_client *client) +{ + if (client->transport.type == MQTT_TRANSPORT_NON_SECURE) { + fds[0].fd = client->transport.tcp.sock; } +#if defined(CONFIG_MQTT_LIB_TLS) + else if (client->transport.type == MQTT_TRANSPORT_SECURE) { + fds[0].fd = client->transport.tls.sock; + } +#endif - printk("\n"); + fds[0].events = ZSOCK_POLLIN; + nfds = 1; } -/* The signature of this routine must match the disconnect callback declared at - * the mqtt.h header. - */ -static void disconnect_cb(struct mqtt_ctx *mqtt_ctx) +static void clear_fds(void) { - struct mqtt_client_ctx *client_ctx; - - client_ctx = CONTAINER_OF(mqtt_ctx, struct mqtt_client_ctx, mqtt_ctx); - - printk("[%s:%d]", __func__, __LINE__); + nfds = 0; +} - if (client_ctx->disconnect_data) { - printk(" user_data: %s", - (const char *)client_ctx->disconnect_data); +static void wait(int timeout) +{ + if (nfds > 0) { + if (poll(fds, nfds, timeout) < 0) { + printk("poll error: %d\n", errno); + } } - - printk("\n"); } -/** - * The signature of this routine must match the publish_tx callback declared at - * the mqtt.h header. - * - * NOTE: we have two callbacks for MQTT Publish related stuff: - * - publish_tx, for publishers - * - publish_rx, for subscribers - * - * Applications must keep a "message database" with pkt_id's. So far, this is - * not implemented here. For example, if we receive a PUBREC message with an - * unknown pkt_id, this routine must return an error, for example -EINVAL or - * any negative value. - */ -static int publish_cb(struct mqtt_ctx *mqtt_ctx, u16_t pkt_id, - enum mqtt_packet type) +void mqtt_evt_handler(struct mqtt_client *const client, + const struct mqtt_evt *evt) { - struct mqtt_client_ctx *client_ctx; - const char *str; - int rc = 0; + int err; + + switch (evt->type) { + case MQTT_EVT_CONNACK: + if (evt->result != 0) { + printk("MQTT connect failed %d\n", evt->result); + break; + } - client_ctx = CONTAINER_OF(mqtt_ctx, struct mqtt_client_ctx, mqtt_ctx); + connected = true; + printk("[%s:%d] MQTT client connected!\n", __func__, __LINE__); - switch (type) { - case MQTT_PUBACK: - str = "MQTT_PUBACK"; break; - case MQTT_PUBCOMP: - str = "MQTT_PUBCOMP"; + + case MQTT_EVT_DISCONNECT: + printk("[%s:%d] MQTT client disconnected %d\n", __func__, + __LINE__, evt->result); + + connected = false; + clear_fds(); + break; - case MQTT_PUBREC: - str = "MQTT_PUBREC"; + + case MQTT_EVT_PUBACK: + if (evt->result != 0) { + printk("MQTT PUBACK error %d\n", evt->result); + break; + } + + printk("[%s:%d] PUBACK packet id: %u\n", __func__, __LINE__, + evt->param.puback.message_id); + break; - default: - rc = -EINVAL; - str = "Invalid MQTT packet"; - } - printk("[%s:%d] <%s> packet id: %u", __func__, __LINE__, str, pkt_id); + case MQTT_EVT_PUBREC: + if (evt->result != 0) { + printk("MQTT PUBREC error %d\n", evt->result); + break; + } - if (client_ctx->publish_data) { - printk(", user_data: %s", - (const char *)client_ctx->publish_data); - } + printk("[%s:%d] PUBREC packet id: %u\n", __func__, __LINE__, + evt->param.pubrec.message_id); - printk("\n"); + const struct mqtt_pubrel_param rel_param = { + .message_id = evt->param.pubrec.message_id + }; - return rc; -} + err = mqtt_publish_qos2_release(client, &rel_param); + if (err != 0) { + printk("Failed to send MQTT PUBREL: %d\n", err); + } -/** - * The signature of this routine must match the malformed callback declared at - * the mqtt.h header. - */ -static void malformed_cb(struct mqtt_ctx *mqtt_ctx, u16_t pkt_type) -{ - printk("[%s:%d] pkt_type: %u\n", __func__, __LINE__, pkt_type); + break; + + case MQTT_EVT_PUBCOMP: + if (evt->result != 0) { + printk("MQTT PUBCOMP error %d\n", evt->result); + break; + } + + printk("[%s:%d] PUBCOMP packet id: %u\n", __func__, __LINE__, + evt->param.pubcomp.message_id); + + break; + + default: + break; + } } static char *get_mqtt_payload(enum mqtt_qos qos) @@ -246,159 +215,207 @@ static char *get_mqtt_topic(void) #endif } -static void prepare_mqtt_publish_msg(struct mqtt_publish_msg *pub_msg, - enum mqtt_qos qos) +static int publish(struct mqtt_client *client, enum mqtt_qos qos) { - /* MQTT message payload may be anything, we we use C strings */ - pub_msg->msg = get_mqtt_payload(qos); - /* Payload's length */ - pub_msg->msg_len = strlen(client_ctx.pub_msg.msg); - /* MQTT Quality of Service */ - pub_msg->qos = qos; - /* Message's topic */ - pub_msg->topic = get_mqtt_topic(); - pub_msg->topic_len = strlen(client_ctx.pub_msg.topic); - /* Packet Identifier, always use different values */ - pub_msg->pkt_id = sys_rand32_get(); + struct mqtt_publish_param param; + + param.message.topic.qos = qos; + param.message.topic.topic.utf8 = (u8_t *)get_mqtt_topic(); + param.message.topic.topic.size = + strlen(param.message.topic.topic.utf8); + param.message.payload.data = get_mqtt_payload(qos); + param.message.payload.len = + strlen(param.message.payload.data); + param.message_id = sys_rand32_get(); + param.dup_flag = 0; + param.retain_flag = 0; + + return mqtt_publish(client, ¶m); } -#define RC_STR(rc) ((rc) == 0 ? "OK" : "ERROR") +#define RC_STR(rc) ((rc) == 0 ? "OK" : "ERROR") -#define PRINT_RESULT(func, rc) \ +#define PRINT_RESULT(func, rc) \ printk("[%s:%d] %s: %d <%s>\n", __func__, __LINE__, \ (func), rc, RC_STR(rc)) +static void broker_init(void) +{ +#if defined(CONFIG_NET_IPV6) + struct sockaddr_in6 *broker6 = (struct sockaddr_in6 *)&broker; + + broker6->sin6_family = AF_INET6; + broker6->sin6_port = htons(SERVER_PORT); + inet_pton(AF_INET6, SERVER_ADDR, &broker6->sin6_addr); +#else + struct sockaddr_in *broker4 = (struct sockaddr_in *)&broker; + + broker4->sin_family = AF_INET; + broker4->sin_port = htons(SERVER_PORT); + inet_pton(AF_INET, SERVER_ADDR, &broker4->sin_addr); +#endif +} + +static void client_init(struct mqtt_client *client) +{ + mqtt_client_init(client); + + broker_init(); + + /* MQTT client configuration */ + client->broker = &broker; + client->evt_cb = mqtt_evt_handler; + client->client_id.utf8 = (u8_t *)MQTT_CLIENTID; + client->client_id.size = strlen(MQTT_CLIENTID); + client->password = NULL; + client->user_name = NULL; + client->protocol_version = MQTT_VERSION_3_1_1; + + /* MQTT buffers configuration */ + client->rx_buf = rx_buffer; + client->rx_buf_size = sizeof(rx_buffer); + client->tx_buf = tx_buffer; + client->tx_buf_size = sizeof(tx_buffer); + + /* MQTT transport configuration */ +#if defined(CONFIG_MQTT_LIB_TLS) + client->transport.type = MQTT_TRANSPORT_SECURE; + + struct mqtt_sec_config *tls_config = &client->transport.tls.config; + + tls_config->peer_verify = 2; + tls_config->cipher_list = NULL; + tls_config->seg_tag_list = m_sec_tags; + tls_config->sec_tag_count = ARRAY_SIZE(m_sec_tags); +#if defined(MBEDTLS_X509_CRT_PARSE_C) + tls_config->hostname = TLS_SNI_HOSTNAME; +#else + tls_config->hostname = NULL; +#endif + +#else + client->transport.type = MQTT_TRANSPORT_NON_SECURE; +#endif +} + /* In this routine we block until the connected variable is 1 */ -static int try_to_connect(struct mqtt_client_ctx *client_ctx) +static int try_to_connect(struct mqtt_client *client) { - int i = 0; + int rc, i = 0; + + while (i++ < APP_CONNECT_TRIES && !connected) { - while (i++ < APP_CONNECT_TRIES && !client_ctx->mqtt_ctx.connected) { - int rc; + client_init(client); - rc = mqtt_tx_connect(&client_ctx->mqtt_ctx, - &client_ctx->connect_msg); - k_sleep(APP_SLEEP_MSECS); - PRINT_RESULT("mqtt_tx_connect", rc); + rc = mqtt_connect(client); if (rc != 0) { + PRINT_RESULT("mqtt_connect", rc); + k_sleep(APP_SLEEP_MSECS); continue; } + + prepare_fds(client); + + wait(APP_SLEEP_MSECS); + mqtt_input(client); + + if (!connected) { + mqtt_abort(client); + } } - if (client_ctx->mqtt_ctx.connected) { + if (connected) { return 0; } return -EINVAL; } -static void publisher(void) +static int process_mqtt_and_sleep(struct mqtt_client *client, int timeout) { - int i, rc; + s64_t remaining = timeout; + s64_t start_time = k_uptime_get(); + int rc; + + while (remaining > 0 && connected) { + wait(remaining); - /* Set everything to 0 and later just assign the required fields. */ - (void)memset(&client_ctx, 0x00, sizeof(client_ctx)); + rc = mqtt_live(client); + if (rc != 0) { + PRINT_RESULT("mqtt_live", rc); + return rc; + } + + rc = mqtt_input(client); + if (rc != 0) { + PRINT_RESULT("mqtt_input", rc); + return rc; + } - /* connect, disconnect and malformed may be set to NULL */ - client_ctx.mqtt_ctx.connect = connect_cb; + remaining = timeout + start_time - k_uptime_get(); + } - client_ctx.mqtt_ctx.disconnect = disconnect_cb; - client_ctx.mqtt_ctx.malformed = malformed_cb; + return 0; +} - client_ctx.mqtt_ctx.net_init_timeout = APP_NET_INIT_TIMEOUT; - client_ctx.mqtt_ctx.net_timeout = APP_TX_RX_TIMEOUT; +#define SUCCESS_OR_EXIT(rc) { if (rc != 0) { return; } } +#define SUCCESS_OR_BREAK(rc) { if (rc != 0) { break; } } - client_ctx.mqtt_ctx.peer_addr_str = SERVER_ADDR; - client_ctx.mqtt_ctx.peer_port = SERVER_PORT; +static void publisher(void) +{ + int i, rc; #if defined(CONFIG_MQTT_LIB_TLS) - /** TLS setup */ - client_ctx.mqtt_ctx.request_buf = tls_request_buf; - client_ctx.mqtt_ctx.request_buf_len = TLS_REQUEST_BUF_SIZE; - client_ctx.mqtt_ctx.personalization_data = TLS_PRIVATE_DATA; - client_ctx.mqtt_ctx.personalization_data_len = strlen(TLS_PRIVATE_DATA); - client_ctx.mqtt_ctx.cert_host = TLS_SNI_HOSTNAME; - client_ctx.mqtt_ctx.tls_mem_pool = &tls_mem_pool; - client_ctx.mqtt_ctx.tls_stack = tls_stack; - client_ctx.mqtt_ctx.tls_stack_size = K_THREAD_STACK_SIZEOF(tls_stack); - client_ctx.mqtt_ctx.cert_cb = setup_cert; - client_ctx.mqtt_ctx.entropy_src_cb = NULL; + rc = tls_init(); + PRINT_RESULT("tls_init", rc); + SUCCESS_OR_EXIT(rc); #endif - /* Publisher apps TX the MQTT PUBLISH msg */ - client_ctx.mqtt_ctx.publish_tx = publish_cb; + rc = network_setup(); + PRINT_RESULT("network_setup", rc); + SUCCESS_OR_EXIT(rc); - /* The connect message will be sent to the MQTT server (broker). - * If clean_session here is 0, the mqtt_ctx clean_session variable - * will be set to 0 also. Please don't do that, set always to 1. - * Clean session = 0 is not yet supported. - */ - client_ctx.connect_msg.client_id = MQTT_CLIENTID; - client_ctx.connect_msg.client_id_len = strlen(MQTT_CLIENTID); - client_ctx.connect_msg.clean_session = 1; + rc = try_to_connect(&client_ctx); + PRINT_RESULT("try_to_connect", rc); + SUCCESS_OR_EXIT(rc); - client_ctx.connect_data = "CONNECTED"; - client_ctx.disconnect_data = "DISCONNECTED"; - client_ctx.publish_data = "PUBLISH"; + i = 0; + while (i++ < APP_MAX_ITERATIONS && connected) { + rc = mqtt_ping(&client_ctx); + PRINT_RESULT("mqtt_ping", rc); + SUCCESS_OR_BREAK(rc); - rc = network_setup(); - PRINT_RESULT("network_setup", rc); - if (rc < 0) { - return; - } + rc = process_mqtt_and_sleep(&client_ctx, APP_SLEEP_MSECS); + SUCCESS_OR_BREAK(rc); - rc = mqtt_init(&client_ctx.mqtt_ctx, MQTT_APP_PUBLISHER); - PRINT_RESULT("mqtt_init", rc); - if (rc != 0) { - return; - } + rc = publish(&client_ctx, MQTT_QOS_0_AT_MOST_ONCE); + PRINT_RESULT("mqtt_publish", rc); + SUCCESS_OR_BREAK(rc); - for (i = 0; i < CONN_TRIES; i++) { - rc = mqtt_connect(&client_ctx.mqtt_ctx); - PRINT_RESULT("mqtt_connect", rc); - if (!rc) { - goto connected; - } - } + rc = process_mqtt_and_sleep(&client_ctx, APP_SLEEP_MSECS); + SUCCESS_OR_BREAK(rc); - goto exit_app; + rc = publish(&client_ctx, MQTT_QOS_1_AT_LEAST_ONCE); + PRINT_RESULT("mqtt_publish", rc); + SUCCESS_OR_BREAK(rc); -connected: + rc = process_mqtt_and_sleep(&client_ctx, APP_SLEEP_MSECS); + SUCCESS_OR_BREAK(rc); - rc = try_to_connect(&client_ctx); - PRINT_RESULT("try_to_connect", rc); - if (rc != 0) { - goto exit_app; - } + rc = publish(&client_ctx, MQTT_QOS_2_EXACTLY_ONCE); + PRINT_RESULT("mqtt_publish", rc); + SUCCESS_OR_BREAK(rc); - i = 0; - while (i++ < APP_MAX_ITERATIONS) { - rc = mqtt_tx_pingreq(&client_ctx.mqtt_ctx); - k_sleep(APP_SLEEP_MSECS); - PRINT_RESULT("mqtt_tx_pingreq", rc); - - prepare_mqtt_publish_msg(&client_ctx.pub_msg, MQTT_QoS0); - rc = mqtt_tx_publish(&client_ctx.mqtt_ctx, &client_ctx.pub_msg); - k_sleep(APP_SLEEP_MSECS); - PRINT_RESULT("mqtt_tx_publish", rc); - - prepare_mqtt_publish_msg(&client_ctx.pub_msg, MQTT_QoS1); - rc = mqtt_tx_publish(&client_ctx.mqtt_ctx, &client_ctx.pub_msg); - k_sleep(APP_SLEEP_MSECS); - PRINT_RESULT("mqtt_tx_publish", rc); - - prepare_mqtt_publish_msg(&client_ctx.pub_msg, MQTT_QoS2); - rc = mqtt_tx_publish(&client_ctx.mqtt_ctx, &client_ctx.pub_msg); - k_sleep(APP_SLEEP_MSECS); - PRINT_RESULT("mqtt_tx_publish", rc); + rc = process_mqtt_and_sleep(&client_ctx, APP_SLEEP_MSECS); + SUCCESS_OR_BREAK(rc); } - rc = mqtt_tx_disconnect(&client_ctx.mqtt_ctx); - PRINT_RESULT("mqtt_tx_disconnect", rc); + rc = mqtt_disconnect(&client_ctx); + PRINT_RESULT("mqtt_disconnect", rc); -exit_app: - - mqtt_close(&client_ctx.mqtt_ctx); + wait(APP_SLEEP_MSECS); + rc = mqtt_input(&client_ctx); + PRINT_RESULT("mqtt_input", rc); printk("\nBye!\n"); } @@ -428,7 +445,6 @@ struct bt_conn_cb bt_conn_cb = { static int network_setup(void) { - #if defined(CONFIG_NET_L2_BT) const char *progress_mark = "/-\\|"; int i = 0; diff --git a/samples/net/mqtt_publisher/src/test_certs.h b/samples/net/mqtt_publisher/src/test_certs.h index 2bab1cab7373a..ce185c2809f8d 100644 --- a/samples/net/mqtt_publisher/src/test_certs.h +++ b/samples/net/mqtt_publisher/src/test_certs.h @@ -7,6 +7,8 @@ #ifndef __TEST_CERTS_H__ #define __TEST_CERTS_H__ +#include + #if defined(MBEDTLS_X509_CRT_PARSE_C) /* This byte array can be generated by * "cat ca.crt | sed -e '1d;$d' | base64 -d |xxd -i" diff --git a/subsys/net/lib/CMakeLists.txt b/subsys/net/lib/CMakeLists.txt index 77e214761ea1a..39a0f40b354d2 100644 --- a/subsys/net/lib/CMakeLists.txt +++ b/subsys/net/lib/CMakeLists.txt @@ -3,7 +3,8 @@ add_subdirectory_if_kconfig(coap_sock) add_subdirectory_if_kconfig(lwm2m) add_subdirectory_if_kconfig(sntp) add_subdirectory_ifdef(CONFIG_DNS_RESOLVER dns) -add_subdirectory_ifdef(CONFIG_MQTT_LIB mqtt) +add_subdirectory_ifdef(CONFIG_MQTT_LEGACY_LIB mqtt) +add_subdirectory_ifdef(CONFIG_MQTT_LIB mqtt_sock) add_subdirectory_ifdef(CONFIG_NET_APP app) add_subdirectory_ifdef(CONFIG_NET_CONFIG_SETTINGS config) add_subdirectory_ifdef(CONFIG_NET_SOCKETS sockets) diff --git a/subsys/net/lib/Kconfig b/subsys/net/lib/Kconfig index 153ce0b2b8229..94e4f377e3339 100644 --- a/subsys/net/lib/Kconfig +++ b/subsys/net/lib/Kconfig @@ -14,6 +14,8 @@ source "subsys/net/lib/dns/Kconfig" source "subsys/net/lib/mqtt/Kconfig" +source "subsys/net/lib/mqtt_sock/Kconfig" + source "subsys/net/lib/http/Kconfig" source "subsys/net/lib/lwm2m/Kconfig" diff --git a/subsys/net/lib/mqtt/Kconfig b/subsys/net/lib/mqtt/Kconfig index 481342e3e3e89..7efad12e4e84d 100644 --- a/subsys/net/lib/mqtt/Kconfig +++ b/subsys/net/lib/mqtt/Kconfig @@ -6,42 +6,42 @@ # SPDX-License-Identifier: Apache-2.0 # -config MQTT_LIB - bool "MQTT Library Support" +config MQTT_LEGACY_LIB + bool "Legacy MQTT Library Support" select NET_APP_CLIENT help Enable the Zephyr MQTT Library -config MQTT_MSG_MAX_SIZE +config MQTT_LEGACY_MSG_MAX_SIZE int "Max size of a MQTT message" - depends on MQTT_LIB + depends on MQTT_LEGACY_LIB default 128 range 128 1024 help Set the maximum size of the MQTT message. So, no messages longer than CONFIG_MQTT_MSG_SIZE will be processed. -config MQTT_ADDITIONAL_BUFFER_CTR +config MQTT_LEGACY_ADDITIONAL_BUFFER_CTR int "Additional buffers available for the MQTT application" - depends on MQTT_LIB + depends on MQTT_LEGACY_LIB default 0 help Set some additional buffers. When two or more concurrent contexts are used in the same application, additional buffers may help to have a 1:1 relation between application contexts and internal buffers. -config MQTT_SUBSCRIBE_MAX_TOPICS +config MQTT_LEGACY_SUBSCRIBE_MAX_TOPICS int "Max number of topics to subscribe to" - depends on MQTT_LIB + depends on MQTT_LEGACY_LIB default 1 range 1 8 help Set the maximum number of topics handled by the SUBSCRIBE/SUBACK messages during reception. -config MQTT_LIB_TLS +config MQTT_LEGACY_LIB_TLS bool "Enable TLS support for the MQTT application" - depends on MQTT_LIB + depends on MQTT_LEGACY_LIB select NET_APP_TLS help Enables MQTT library with TLS support diff --git a/subsys/net/lib/mqtt/mqtt.c b/subsys/net/lib/mqtt/mqtt.c index 48619349e0a29..a316d4d8bab07 100644 --- a/subsys/net/lib/mqtt/mqtt.c +++ b/subsys/net/lib/mqtt/mqtt.c @@ -7,7 +7,7 @@ #define LOG_MODULE_NAME net_mqtt #define NET_LOG_LEVEL LOG_LEVEL_ERR -#include +#include #include "mqtt_pkt.h" #include @@ -16,8 +16,8 @@ #include #include -#define MSG_SIZE CONFIG_MQTT_MSG_MAX_SIZE -#define MQTT_BUF_CTR (1 + CONFIG_MQTT_ADDITIONAL_BUFFER_CTR) +#define MSG_SIZE CONFIG_MQTT_LEGACY_MSG_MAX_SIZE +#define MQTT_BUF_CTR (1 + CONFIG_MQTT_LEGACY_ADDITIONAL_BUFFER_CTR) /* Memory pool internally used to handle messages that may exceed the size of * system defined network buffer. By using this memory pool, routines don't deal @@ -27,7 +27,7 @@ NET_BUF_POOL_DEFINE(mqtt_msg_pool, MQTT_BUF_CTR, MSG_SIZE, 0, NULL); #define MQTT_PUBLISHER_MIN_MSG_SIZE 2 -#if defined(CONFIG_MQTT_LIB_TLS) +#if defined(CONFIG_MQTT_LEGACY_LIB_TLS) #define TLS_HS_DEFAULT_TIMEOUT 3000 #endif @@ -556,7 +556,7 @@ int mqtt_rx_pingresp(struct mqtt_ctx *ctx, struct net_buf *rx) int mqtt_rx_suback(struct mqtt_ctx *ctx, struct net_buf *rx) { - enum mqtt_qos suback_qos[CONFIG_MQTT_SUBSCRIBE_MAX_TOPICS]; + enum mqtt_qos suback_qos[CONFIG_MQTT_LEGACY_SUBSCRIBE_MAX_TOPICS]; u16_t pkt_id; u16_t len; u8_t items; @@ -567,7 +567,8 @@ int mqtt_rx_suback(struct mqtt_ctx *ctx, struct net_buf *rx) len = rx->len; rc = mqtt_unpack_suback(data, len, &pkt_id, &items, - CONFIG_MQTT_SUBSCRIBE_MAX_TOPICS, suback_qos); + CONFIG_MQTT_LEGACY_SUBSCRIBE_MAX_TOPICS, + suback_qos); if (rc != 0) { return -EINVAL; } @@ -663,12 +664,12 @@ struct net_buf *mqtt_linearize_packet(struct mqtt_ctx *ctx, struct net_pkt *rx, u16_t offset; int rc; - /* CONFIG_MQTT_MSG_MAX_SIZE is defined via Kconfig. So here it's + /* CONFIG_MQTT_LEGACY_MSG_MAX_SIZE is defined via Kconfig. So here it's * determined if the input packet could fit our data buffer or if * it has the expected size. */ data_len = net_pkt_appdatalen(rx); - if (data_len < min_size || data_len > CONFIG_MQTT_MSG_MAX_SIZE) { + if (data_len < min_size || data_len > CONFIG_MQTT_LEGACY_MSG_MAX_SIZE) { return NULL; } @@ -779,7 +780,7 @@ void app_connected(struct net_app_ctx *ctx, int status, void *data) return; } -#if defined(CONFIG_MQTT_LIB_TLS) +#if defined(CONFIG_MQTT_LEGACY_LIB_TLS) k_sem_give(&mqtt->tls_hs_wait); #endif } @@ -835,7 +836,7 @@ int mqtt_connect(struct mqtt_ctx *ctx) goto error_connect; } -#if defined(CONFIG_MQTT_LIB_TLS) +#if defined(CONFIG_MQTT_LEGACY_LIB_TLS) rc = net_app_client_tls(&ctx->net_app_ctx, ctx->request_buf, ctx->request_buf_len, @@ -857,7 +858,7 @@ int mqtt_connect(struct mqtt_ctx *ctx) goto error_connect; } -#if defined(CONFIG_MQTT_LIB_TLS) +#if defined(CONFIG_MQTT_LEGACY_LIB_TLS) /* TLS handshake is not finished until app_connected is called */ rc = k_sem_take(&ctx->tls_hs_wait, ctx->tls_hs_timeout); if (rc < 0) { @@ -884,7 +885,7 @@ int mqtt_init(struct mqtt_ctx *ctx, enum mqtt_app app_type) ctx->app_type = app_type; ctx->rcv = mqtt_parser; -#if defined(CONFIG_MQTT_LIB_TLS) +#if defined(CONFIG_MQTT_LEGACY_LIB_TLS) if (ctx->tls_hs_timeout == 0) { ctx->tls_hs_timeout = TLS_HS_DEFAULT_TIMEOUT; } diff --git a/subsys/net/lib/mqtt/mqtt_pkt.h b/subsys/net/lib/mqtt/mqtt_pkt.h index 25305bf9497fc..ba6fa719afffa 100644 --- a/subsys/net/lib/mqtt/mqtt_pkt.h +++ b/subsys/net/lib/mqtt/mqtt_pkt.h @@ -20,7 +20,7 @@ #include #include -#include +#include #define MQTT_PACKET_TYPE(first_byte) (((first_byte) & 0xF0) >> 4) diff --git a/subsys/net/lib/mqtt_sock/CMakeLists.txt b/subsys/net/lib/mqtt_sock/CMakeLists.txt new file mode 100644 index 0000000000000..b6cf9e26842eb --- /dev/null +++ b/subsys/net/lib/mqtt_sock/CMakeLists.txt @@ -0,0 +1,14 @@ +zephyr_library() + +zephyr_library_sources( + mqtt_decoder.c + mqtt_encoder.c + mqtt_rx.c + mqtt_transport_socket_tcp.c + mqtt_transport.c + mqtt.c + ) + +zephyr_library_sources_ifdef(CONFIG_MQTT_LIB_TLS + mqtt_transport_socket_tls.c + ) diff --git a/subsys/net/lib/mqtt_sock/Kconfig b/subsys/net/lib/mqtt_sock/Kconfig new file mode 100644 index 0000000000000..b53e00dadccee --- /dev/null +++ b/subsys/net/lib/mqtt_sock/Kconfig @@ -0,0 +1,35 @@ +# Kconfig - Socket MQTT Library for Zephyr +# +# Copyright (c) 2018 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +config MQTT_LIB + bool "Socket MQTT Library Support" + select NET_SOCKETS + select NET_SOCKETS_POSIX_NAMES + help + Enable the Zephyr MQTT Library + +if MQTT_LIB + +module=MQTT +module-dep=NET_LOG +module-str=Log level for MQTT +module-help=Enables mqtt debug messages. +source "subsys/net/Kconfig.template.log_config.net" + +config MQTT_KEEPALIVE + int "Maximum number of clients Keep alive time for MQTT (in seconds)" + default 60 + help + Keep alive time for MQTT (in seconds). Sending of Ping Requests to + keep the connection alive are governed by this value. + +config MQTT_LIB_TLS + bool "TLS support for socket MQTT Library" + help + Enable TLS support for socket MQTT Library + +endif # MQTT_LIB diff --git a/subsys/net/lib/mqtt_sock/mqtt.c b/subsys/net/lib/mqtt_sock/mqtt.c new file mode 100644 index 0000000000000..d1d947517d2ee --- /dev/null +++ b/subsys/net/lib/mqtt_sock/mqtt.c @@ -0,0 +1,639 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @file mqtt.c + * + * @brief MQTT Client API Implementation. + */ + +#define LOG_MODULE_NAME net_mqtt +#define NET_LOG_LEVEL CONFIG_MQTT_LOG_LEVEL + +#include + +#include "mqtt_transport.h" +#include "mqtt_internal.h" +#include "mqtt_os.h" + +static void client_reset(struct mqtt_client *client) +{ + MQTT_STATE_INIT(client); + + client->internal.last_activity = 0; + client->internal.rx_buf_datalen = 0; + client->internal.remaining_payload = 0; +} + +/** @brief Initialize tx buffer. */ +static void tx_buf_init(struct mqtt_client *client, struct buf_ctx *buf) +{ + memset(client->tx_buf, 0, client->tx_buf_size); + buf->cur = client->tx_buf; + buf->end = client->tx_buf + client->tx_buf_size; +} + +/**@brief Notifies disconnection event to the application. + * + * @param[in] client Identifies the client for which the procedure is requested. + * @param[in] result Reason for disconnection. + */ +static void disconnect_event_notify(struct mqtt_client *client, int result) +{ + struct mqtt_evt evt; + + /* Determine appropriate event to generate. */ + if (MQTT_HAS_STATE(client, MQTT_STATE_CONNECTED) || + MQTT_HAS_STATE(client, MQTT_STATE_DISCONNECTING)) { + evt.type = MQTT_EVT_DISCONNECT; + evt.result = result; + } else { + evt.type = MQTT_EVT_CONNACK; + evt.result = -ECONNREFUSED; + } + + /* Notify application. */ + event_notify(client, &evt); + + /* Reset internal state. */ + client_reset(client); +} + +void event_notify(struct mqtt_client *client, const struct mqtt_evt *evt) +{ + if (client->evt_cb != NULL) { + mqtt_mutex_unlock(client); + + client->evt_cb(client, evt); + + mqtt_mutex_lock(client); + } +} + +static void client_disconnect(struct mqtt_client *client, int result) +{ + int err_code; + + err_code = mqtt_transport_disconnect(client); + if (err_code < 0) { + MQTT_ERR("Failed to disconnect transport!"); + } + + disconnect_event_notify(client, result); +} + +static int client_connect(struct mqtt_client *client) +{ + int err_code; + struct buf_ctx packet; + + err_code = mqtt_transport_connect(client); + if (err_code < 0) { + return err_code; + } + + tx_buf_init(client, &packet); + MQTT_SET_STATE(client, MQTT_STATE_TCP_CONNECTED); + + err_code = connect_request_encode(client, &packet); + if (err_code < 0) { + goto error; + } + + /* Send MQTT identification message to broker. */ + err_code = mqtt_transport_write(client, packet.cur, + packet.end - packet.cur); + if (err_code < 0) { + goto error; + } + + client->internal.last_activity = mqtt_sys_tick_in_ms_get(); + + MQTT_TRC("Connect completed"); + + return 0; + +error: + client_disconnect(client, err_code); + return err_code; +} + +static int client_read(struct mqtt_client *client) +{ + int err_code; + + if (client->internal.remaining_payload > 0) { + return -EBUSY; + } + + err_code = mqtt_handle_rx(client); + if (err_code < 0) { + client_disconnect(client, err_code); + } + + return err_code; +} + +static int client_write(struct mqtt_client *client, const u8_t *data, + u32_t datalen) +{ + int err_code; + + MQTT_TRC("[%p]: Transport writing %d bytes.", client, datalen); + + err_code = mqtt_transport_write(client, data, datalen); + if (err_code < 0) { + MQTT_TRC("TCP write failed, errno = %d, " + "closing connection", errno); + client_disconnect(client, err_code); + return err_code; + } + + MQTT_TRC("[%p]: Transport write complete.", client); + client->internal.last_activity = mqtt_sys_tick_in_ms_get(); + + return 0; +} + +void mqtt_client_init(struct mqtt_client *client) +{ + NULL_PARAM_CHECK_VOID(client); + + memset(client, 0, sizeof(*client)); + + MQTT_STATE_INIT(client); + mqtt_mutex_init(client); + + client->protocol_version = MQTT_VERSION_3_1_1; + client->clean_session = 1; +} + +int mqtt_connect(struct mqtt_client *client) +{ + int err_code; + + NULL_PARAM_CHECK(client); + NULL_PARAM_CHECK(client->client_id.utf8); + + mqtt_mutex_lock(client); + + if ((client->tx_buf == NULL) || (client->rx_buf == NULL)) { + err_code = -ENOMEM; + goto error; + } + + err_code = client_connect(client); + if (err_code < 0) { + err_code = -ECONNREFUSED; + goto error; + } + + return 0; + +error: + client_reset(client); + mqtt_mutex_unlock(client); + return err_code; +} + +static int verify_tx_state(const struct mqtt_client *client) +{ + if (!MQTT_HAS_STATE(client, MQTT_STATE_CONNECTED)) { + return -ENOTCONN; + } + + return 0; +} + +int mqtt_publish(struct mqtt_client *client, + const struct mqtt_publish_param *param) +{ + int err_code; + struct buf_ctx packet; + + NULL_PARAM_CHECK(client); + NULL_PARAM_CHECK(param); + + MQTT_TRC("[CID %p]:[State 0x%02x]: >> Topic size 0x%08x, " + "Data size 0x%08x", client, client->internal.state, + param->message.topic.topic.size, + param->message.payload.len); + + mqtt_mutex_lock(client); + + tx_buf_init(client, &packet); + + err_code = verify_tx_state(client); + if (err_code < 0) { + goto error; + } + + err_code = publish_encode(param, &packet); + if (err_code < 0) { + goto error; + } + + err_code = client_write(client, packet.cur, packet.end - packet.cur); + if (err_code < 0) { + goto error; + } + + err_code = client_write(client, param->message.payload.data, + param->message.payload.len); + +error: + MQTT_TRC("[CID %p]:[State 0x%02x]: << result 0x%08x", + client, client->internal.state, err_code); + + mqtt_mutex_unlock(client); + + return err_code; +} + +int mqtt_publish_qos1_ack(struct mqtt_client *client, + const struct mqtt_puback_param *param) +{ + int err_code; + struct buf_ctx packet; + + NULL_PARAM_CHECK(client); + NULL_PARAM_CHECK(param); + + MQTT_TRC("[CID %p]:[State 0x%02x]: >> Message id 0x%04x", + client, client->internal.state, param->message_id); + + mqtt_mutex_lock(client); + + tx_buf_init(client, &packet); + + err_code = verify_tx_state(client); + if (err_code < 0) { + goto error; + } + + err_code = publish_ack_encode(param, &packet); + if (err_code < 0) { + goto error; + } + + err_code = client_write(client, packet.cur, packet.end - packet.cur); + +error: + MQTT_TRC("[CID %p]:[State 0x%02x]: << result 0x%08x", + client, client->internal.state, err_code); + + mqtt_mutex_unlock(client); + + return err_code; +} + +int mqtt_publish_qos2_receive(struct mqtt_client *client, + const struct mqtt_pubrec_param *param) +{ + int err_code; + struct buf_ctx packet; + + NULL_PARAM_CHECK(client); + NULL_PARAM_CHECK(param); + + MQTT_TRC("[CID %p]:[State 0x%02x]: >> Message id 0x%04x", + client, client->internal.state, param->message_id); + + mqtt_mutex_lock(client); + + tx_buf_init(client, &packet); + + err_code = verify_tx_state(client); + if (err_code < 0) { + goto error; + } + + err_code = publish_receive_encode(param, &packet); + if (err_code < 0) { + goto error; + } + + err_code = client_write(client, packet.cur, packet.end - packet.cur); + +error: + MQTT_TRC("[CID %p]:[State 0x%02x]: << result 0x%08x", + client, client->internal.state, err_code); + + mqtt_mutex_unlock(client); + + return err_code; +} + +int mqtt_publish_qos2_release(struct mqtt_client *client, + const struct mqtt_pubrel_param *param) +{ + int err_code; + struct buf_ctx packet; + + NULL_PARAM_CHECK(client); + NULL_PARAM_CHECK(param); + + MQTT_TRC("[CID %p]:[State 0x%02x]: >> Message id 0x%04x", + client, client->internal.state, param->message_id); + + mqtt_mutex_lock(client); + + tx_buf_init(client, &packet); + + err_code = verify_tx_state(client); + if (err_code < 0) { + goto error; + } + + err_code = publish_release_encode(param, &packet); + if (err_code < 0) { + goto error; + } + + err_code = client_write(client, packet.cur, packet.end - packet.cur); + +error: + MQTT_TRC("[CID %p]:[State 0x%02x]: << result 0x%08x", + client, client->internal.state, err_code); + + mqtt_mutex_unlock(client); + + return err_code; +} + +int mqtt_publish_qos2_complete(struct mqtt_client *client, + const struct mqtt_pubcomp_param *param) +{ + int err_code; + struct buf_ctx packet; + + NULL_PARAM_CHECK(client); + NULL_PARAM_CHECK(param); + + MQTT_TRC("[CID %p]:[State 0x%02x]: >> Message id 0x%04x", + client, client->internal.state, param->message_id); + + mqtt_mutex_lock(client); + + tx_buf_init(client, &packet); + + err_code = verify_tx_state(client); + if (err_code < 0) { + goto error; + } + + err_code = publish_complete_encode(param, &packet); + if (err_code < 0) { + goto error; + } + + err_code = client_write(client, packet.cur, packet.end - packet.cur); + if (err_code < 0) { + goto error; + } + +error: + MQTT_TRC("[CID %p]:[State 0x%02x]: << result 0x%08x", + client, client->internal.state, err_code); + + mqtt_mutex_unlock(client); + + return err_code; +} + +int mqtt_disconnect(struct mqtt_client *client) +{ + int err_code; + struct buf_ctx packet; + + NULL_PARAM_CHECK(client); + + mqtt_mutex_lock(client); + + tx_buf_init(client, &packet); + + err_code = verify_tx_state(client); + if (err_code < 0) { + goto error; + } + + err_code = disconnect_encode(&packet); + if (err_code < 0) { + goto error; + } + + err_code = client_write(client, packet.cur, packet.end - packet.cur); + if (err_code < 0) { + goto error; + } + + MQTT_SET_STATE_EXCLUSIVE(client, MQTT_STATE_DISCONNECTING); + +error: + mqtt_mutex_unlock(client); + + return err_code; +} + +int mqtt_subscribe(struct mqtt_client *client, + const struct mqtt_subscription_list *param) +{ + int err_code; + struct buf_ctx packet; + + NULL_PARAM_CHECK(client); + NULL_PARAM_CHECK(param); + + MQTT_TRC("[CID %p]:[State 0x%02x]: >> message id 0x%04x " + "topic count 0x%04x", client, client->internal.state, + param->message_id, param->list_count); + + mqtt_mutex_lock(client); + + tx_buf_init(client, &packet); + + err_code = verify_tx_state(client); + if (err_code < 0) { + goto error; + } + + err_code = subscribe_encode(param, &packet); + if (err_code < 0) { + goto error; + } + + err_code = client_write(client, packet.cur, packet.end - packet.cur); + +error: + MQTT_TRC("[CID %p]:[State 0x%02x]: << result 0x%08x", + client, client->internal.state, err_code); + + mqtt_mutex_unlock(client); + + return err_code; +} + +int mqtt_unsubscribe(struct mqtt_client *client, + const struct mqtt_subscription_list *param) +{ + int err_code; + struct buf_ctx packet; + + NULL_PARAM_CHECK(client); + NULL_PARAM_CHECK(param); + + mqtt_mutex_lock(client); + + tx_buf_init(client, &packet); + + err_code = verify_tx_state(client); + if (err_code < 0) { + goto error; + } + + err_code = unsubscribe_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; +} + +int mqtt_ping(struct mqtt_client *client) +{ + int err_code; + struct buf_ctx packet; + + NULL_PARAM_CHECK(client); + + mqtt_mutex_lock(client); + + tx_buf_init(client, &packet); + + err_code = verify_tx_state(client); + if (err_code < 0) { + goto error; + } + + err_code = ping_request_encode(&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; +} + +int mqtt_abort(struct mqtt_client *client) +{ + mqtt_mutex_lock(client); + + NULL_PARAM_CHECK(client); + + if (client->internal.state != MQTT_STATE_IDLE) { + client_disconnect(client, -ECONNABORTED); + } + + mqtt_mutex_unlock(client); + + return 0; +} + +int mqtt_live(struct mqtt_client *client) +{ + u32_t elapsed_time; + + NULL_PARAM_CHECK(client); + + mqtt_mutex_lock(client); + + if (MQTT_HAS_STATE(client, MQTT_STATE_DISCONNECTING)) { + client_disconnect(client, 0); + } else { + elapsed_time = mqtt_elapsed_time_in_ms_get( + client->internal.last_activity); + + if ((MQTT_KEEPALIVE > 0) && + (elapsed_time >= (MQTT_KEEPALIVE * 1000))) { + (void)mqtt_ping(client); + } + } + + mqtt_mutex_unlock(client); + + return 0; +} + +int mqtt_input(struct mqtt_client *client) +{ + int err_code = 0; + + NULL_PARAM_CHECK(client); + + mqtt_mutex_lock(client); + + MQTT_TRC("state:0x%08x", client->internal.state); + + if (MQTT_HAS_STATE(client, MQTT_STATE_DISCONNECTING)) { + client_disconnect(client, 0); + } else if (MQTT_HAS_STATE(client, MQTT_STATE_TCP_CONNECTED)) { + err_code = client_read(client); + } else { + err_code = -EACCES; + } + + mqtt_mutex_unlock(client); + + return err_code; +} + +int mqtt_read_publish_payload(struct mqtt_client *client, void *buffer, + size_t length) +{ + int ret; + + NULL_PARAM_CHECK(client); + + mqtt_mutex_lock(client); + + if (client->internal.remaining_payload == 0) { + ret = 0; + goto exit; + } + + if (client->internal.remaining_payload < length) { + length = client->internal.remaining_payload; + } + + ret = mqtt_transport_read(client, buffer, length); + if (ret == -EAGAIN) { + goto exit; + } + + if (ret <= 0) { + if (ret == 0) { + ret = -ENOTCONN; + } + + client_disconnect(client, ret); + goto exit; + } + + client->internal.remaining_payload -= ret; + +exit: + mqtt_mutex_unlock(client); + + return ret; +} diff --git a/subsys/net/lib/mqtt_sock/mqtt_decoder.c b/subsys/net/lib/mqtt_sock/mqtt_decoder.c new file mode 100644 index 0000000000000..d46a104eefa93 --- /dev/null +++ b/subsys/net/lib/mqtt_sock/mqtt_decoder.c @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @file mqtt_decoder.c + * + * @brief Decoder functions needed for decoding packets received from the + * broker. + */ + +#define LOG_MODULE_NAME net_mqtt_dec +#define NET_LOG_LEVEL CONFIG_MQTT_LOG_LEVEL + +#include "mqtt_internal.h" +#include "mqtt_os.h" + +/** + * @brief Unpacks unsigned 8 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_uint8(struct buf_ctx *buf, u8_t *val) +{ + MQTT_TRC(">> cur:%p, end:%p", buf->cur, buf->end); + + if ((buf->end - buf->cur) < sizeof(u8_t)) { + return -EINVAL; + } + + *val = *(buf->cur++); + + MQTT_TRC("<< val:%02x", *val); + + return 0; +} + +/** + * @brief Unpacks unsigned 16 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_uint16(struct buf_ctx *buf, u16_t *val) +{ + MQTT_TRC(">> cur:%p, end:%p", buf->cur, buf->end); + + if ((buf->end - buf->cur) < sizeof(u16_t)) { + return -EINVAL; + } + + *val = *(buf->cur++) << 8; /* MSB */ + *val |= *(buf->cur++); /* LSB */ + + MQTT_TRC("<< val:%04x", *val); + + return 0; +} + +/** + * @brief Unpacks utf8 string from the buffer from the offset requested. + * + * @param[inout] buf A pointer to the buf_ctx structure containing current + * buffer position. + * @param[out] str Pointer to a string that will hold the 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_utf8_str(struct buf_ctx *buf, struct mqtt_utf8 *str) +{ + u16_t utf8_strlen; + int err_code; + + MQTT_TRC(">> cur:%p, end:%p", buf->cur, buf->end); + + err_code = unpack_uint16(buf, &utf8_strlen); + if (err_code != 0) { + return err_code; + } + + if ((buf->end - buf->cur) < utf8_strlen) { + return -EINVAL; + } + + str->size = utf8_strlen; + /* Zero length UTF8 strings permitted. */ + if (utf8_strlen) { + /* Point to right location in buffer. */ + str->utf8 = buf->cur; + buf->cur += utf8_strlen; + } else { + str->utf8 = NULL; + } + + MQTT_TRC("<< str_size:%08x", (u32_t)GET_UT8STR_BUFFER_SIZE(str)); + + return 0; +} + +/** + * @brief Unpacks binary string from the buffer from the offset requested. + * + * @param[in] length Binary string length. + * @param[inout] buf A pointer to the buf_ctx structure containing current + * buffer position. + * @param[out] str 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_data(u32_t length, struct buf_ctx *buf, + struct mqtt_binstr *str) +{ + MQTT_TRC(">> cur:%p, end:%p", buf->cur, buf->end); + + if ((buf->end - buf->cur) < length) { + return -EINVAL; + } + + str->len = length; + + /* Zero length binary strings are permitted. */ + if (length > 0) { + str->data = buf->cur; + buf->cur += length; + } else { + str->data = NULL; + } + + MQTT_TRC("<< bin len:%08x", GET_BINSTR_BUFFER_SIZE(str)); + + 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. + */ +int packet_length_decode(struct buf_ctx *buf, u32_t *length) +{ + u8_t shift = 0; + u8_t bytes = 0; + + *length = 0; + do { + if (bytes > MQTT_MAX_LENGTH_BYTES) { + return -EINVAL; + } + + if (buf->cur >= buf->end) { + return -EAGAIN; + } + + *length += ((u32_t)*(buf->cur) & MQTT_LENGTH_VALUE_MASK) + << shift; + shift += MQTT_LENGTH_SHIFT; + bytes++; + } while ((*(buf->cur++) & MQTT_LENGTH_CONTINUATION_BIT) != 0); + + MQTT_TRC("length:0x%08x", *length); + + return 0; +} + +int fixed_header_decode(struct buf_ctx *buf, u8_t *type_and_flags, + u32_t *length) +{ + int err_code; + + err_code = unpack_uint8(buf, type_and_flags); + if (err_code != 0) { + return err_code; + } + + return packet_length_decode(buf, length); +} + +int connect_ack_decode(const struct mqtt_client *client, struct buf_ctx *buf, + struct mqtt_connack_param *param) +{ + int err_code; + u8_t flags, ret_code; + + err_code = unpack_uint8(buf, &flags); + if (err_code != 0) { + return err_code; + } + + err_code = unpack_uint8(buf, &ret_code); + if (err_code != 0) { + return err_code; + } + + if (client->protocol_version == MQTT_VERSION_3_1_1) { + param->session_present_flag = + flags & MQTT_CONNACK_FLAG_SESSION_PRESENT; + + MQTT_TRC("[CID %p]: session_present_flag: %d", client, + param->session_present_flag); + } + + param->return_code = (enum mqtt_conn_return_code)ret_code; + + return 0; +} + +int publish_decode(u8_t flags, u32_t var_length, struct buf_ctx *buf, + struct mqtt_publish_param *param) +{ + int err_code; + u32_t var_header_length; + + param->dup_flag = flags & MQTT_HEADER_DUP_MASK; + param->retain_flag = flags & MQTT_HEADER_RETAIN_MASK; + param->message.topic.qos = ((flags & MQTT_HEADER_QOS_MASK) >> 1); + + err_code = unpack_utf8_str(buf, ¶m->message.topic.topic); + if (err_code != 0) { + return err_code; + } + + var_header_length = param->message.topic.topic.size + sizeof(u16_t); + + if (param->message.topic.qos > MQTT_QOS_0_AT_MOST_ONCE) { + err_code = unpack_uint16(buf, ¶m->message_id); + if (err_code != 0) { + return err_code; + } + + var_header_length += sizeof(u16_t); + } + + param->message.payload.data = NULL; + param->message.payload.len = var_length - var_header_length; + + return 0; +} + +int publish_ack_decode(struct buf_ctx *buf, struct mqtt_puback_param *param) +{ + return unpack_uint16(buf, ¶m->message_id); +} + +int publish_receive_decode(struct buf_ctx *buf, struct mqtt_pubrec_param *param) +{ + return unpack_uint16(buf, ¶m->message_id); +} + +int publish_release_decode(struct buf_ctx *buf, struct mqtt_pubrel_param *param) +{ + return unpack_uint16(buf, ¶m->message_id); +} + +int publish_complete_decode(struct buf_ctx *buf, + struct mqtt_pubcomp_param *param) +{ + return unpack_uint16(buf, ¶m->message_id); +} + +int subscribe_ack_decode(struct buf_ctx *buf, struct mqtt_suback_param *param) +{ + int err_code; + + err_code = unpack_uint16(buf, ¶m->message_id); + if (err_code != 0) { + return err_code; + } + + return unpack_data(buf->end - buf->cur, buf, ¶m->return_codes); +} + +int unsubscribe_ack_decode(struct buf_ctx *buf, + struct mqtt_unsuback_param *param) +{ + return unpack_uint16(buf, ¶m->message_id); +} diff --git a/subsys/net/lib/mqtt_sock/mqtt_encoder.c b/subsys/net/lib/mqtt_sock/mqtt_encoder.c new file mode 100644 index 0000000000000..8b5c52dcc5065 --- /dev/null +++ b/subsys/net/lib/mqtt_sock/mqtt_encoder.c @@ -0,0 +1,568 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @file mqtt_encoder.c + * + * @brief Encoding functions needed to create packet to be sent to the broker. + */ + +#define LOG_MODULE_NAME net_mqtt_enc +#define NET_LOG_LEVEL CONFIG_MQTT_LOG_LEVEL + +#include "mqtt_internal.h" +#include "mqtt_os.h" + +#define MQTT_3_1_0_PROTO_DESC_LEN 6 +#define MQTT_3_1_1_PROTO_DESC_LEN 4 + +static const u8_t mqtt_3_1_0_proto_desc_str[MQTT_3_1_0_PROTO_DESC_LEN] = { + 'M', 'Q', 'I', 's', 'd', 'p' +}; +static const u8_t mqtt_3_1_1_proto_desc_str[MQTT_3_1_1_PROTO_DESC_LEN] = { + 'M', 'Q', 'T', 'T' +}; + +static const struct mqtt_utf8 mqtt_3_1_0_proto_desc = { + .utf8 = (u8_t *)mqtt_3_1_0_proto_desc_str, + .size = MQTT_3_1_0_PROTO_DESC_LEN +}; + +static const struct mqtt_utf8 mqtt_3_1_1_proto_desc = { + .utf8 = (u8_t *)mqtt_3_1_1_proto_desc_str, + .size = MQTT_3_1_1_PROTO_DESC_LEN +}; + +/** Never changing ping request, needed for Keep Alive. */ +static const u8_t ping_packet[MQTT_FIXED_HEADER_MIN_SIZE] = { + MQTT_PKT_TYPE_PINGREQ, + 0x00 +}; + +/** Never changing disconnect request. */ +static const u8_t disc_packet[MQTT_FIXED_HEADER_MIN_SIZE] = { + MQTT_PKT_TYPE_DISCONNECT, + 0x00 +}; + +/** + * @brief Packs unsigned 8 bit value to the buffer at the offset requested. + * + * @param[in] val Value to be packed. + * @param[inout] buf A pointer to the buf_ctx structure containing current + * buffer position. + * + * @retval 0 if procedure is successful. + * @retval -ENOMEM if there is no place in the buffer to store the value. + */ +static int pack_uint8(u8_t val, struct buf_ctx *buf) +{ + if ((buf->end - buf->cur) < sizeof(u8_t)) { + return -ENOMEM; + } + + MQTT_TRC(">> val:%02x cur:%p, end:%p", val, buf->cur, buf->end); + + /* Pack value. */ + *(buf->cur++) = val; + + return 0; +} + +/** + * @brief Packs unsigned 16 bit value to the buffer at the offset requested. + * + * @param[in] val Value 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 value. + */ +static int pack_uint16(u16_t val, struct buf_ctx *buf) +{ + if ((buf->end - buf->cur) < sizeof(u16_t)) { + return -ENOMEM; + } + + MQTT_TRC(">> val:%04x cur:%p, end:%p", val, buf->cur, buf->end); + + /* Pack value. */ + *(buf->cur++) = (val >> 8) & 0xFF; + *(buf->cur++) = val & 0xFF; + + return 0; +} + +/** + * @brief Packs utf8 string to the buffer at the offset requested. + * + * @param[in] str UTF-8 string 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 string. + */ +static int pack_utf8_str(const struct mqtt_utf8 *str, struct buf_ctx *buf) +{ + if ((buf->end - buf->cur) < GET_UT8STR_BUFFER_SIZE(str)) { + return -ENOMEM; + } + + MQTT_TRC(">> str_size:%08x cur:%p, end:%p", + (u32_t)GET_UT8STR_BUFFER_SIZE(str), buf->cur, buf->end); + + /* Pack length followed by string. */ + (void)pack_uint16(str->size, buf); + + memcpy(buf->cur, str->utf8, str->size); + buf->cur += str->size; + + return 0; +} + +/** + * @brief Computes and encodes length for the MQTT fixed header. + * + * @note The remaining length is not packed as a fixed unsigned 32 bit integer. + * Instead it is packed on algorithm below: + * + * @code + * do + * encodedByte = X MOD 128 + * X = X DIV 128 + * // if there are more data to encode, set the top bit of this byte + * if ( X > 0 ) + * encodedByte = encodedByte OR 128 + * endif + * 'output' encodedByte + * while ( X > 0 ) + * @endcode + * + * @param[in] length Length of variable header and payload in the MQTT message. + * @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. + */ +static u8_t packet_length_encode(u32_t length, struct buf_ctx *buf) +{ + u8_t encoded_bytes = 0; + + MQTT_TRC(">> length:0x%08x cur:%p, end:%p", length, buf->cur, buf->end); + + do { + encoded_bytes++; + + if (buf != NULL) { + *(buf->cur) = length & MQTT_LENGTH_VALUE_MASK; + } + + length >>= MQTT_LENGTH_SHIFT; + + if (buf != NULL) { + if (length > 0) { + *(buf->cur) |= MQTT_LENGTH_CONTINUATION_BIT; + } + buf->cur++; + } + } while (length > 0); + + return encoded_bytes; +} + +/** + * @brief Encodes fixed header for the MQTT message and provides pointer to + * start of the header. + * + * @param[in] message_type Message type containing packet type and the flags. + * Use @ref MQTT_MESSAGES_OPTIONS to construct the + * message_type. + * @param[in] start Pointer to the start of the variable header. + * @param[inout] buf Buffer context used to encode the frame. + * The 5 bytes before the start of the message are assumed + * by the routine to be available to pack the fixed header. + * However, since the fixed header length is variable + * length, the pointer to the start of the MQTT message + * along with encoded fixed header is supplied as output + * parameter if the procedure was successful. + * As output, the pointers will point to beginning and the end + * of the frame. + * + * @retval 0 if the procedure is successful. + * @retval -EMSGSIZE if the message is too big for MQTT. + */ +static u32_t mqtt_encode_fixed_header(u8_t message_type, u8_t *start, + struct buf_ctx *buf) +{ + u32_t length = buf->cur - start; + u8_t fixed_header_length; + + if (length > MQTT_MAX_PAYLOAD_SIZE) { + return -EMSGSIZE; + } + + MQTT_TRC("<< msg type:0x%02x length:0x%08x", message_type, length); + + fixed_header_length = packet_length_encode(length, NULL); + fixed_header_length += sizeof(u8_t); + + MQTT_TRC("Fixed header length = %02x", fixed_header_length); + + /* Set the pointer at the start of the frame before encoding. */ + buf->cur = start - fixed_header_length; + + (void)pack_uint8(message_type, buf); + (void)packet_length_encode(length, buf); + + /* Set the cur pointer back at the start of the frame, + * and end pointer to the end of the frame. + */ + buf->cur = buf->cur - fixed_header_length; + buf->end = buf->cur + length + fixed_header_length; + + return 0; +} + +/** + * @brief Encodes a string of a zero length. + * + * @param[in] buffer_len Total size of the buffer on which string will be + * encoded. This shall not be zero. + * @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 binary + * string. + */ +static int zero_len_str_encode(struct buf_ctx *buf) +{ + return pack_uint16(0x0000, buf); +} + +/** + * @brief Encodes and sends messages that contain only message id in + * the variable header. + * + * @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. + * + * @retval 0 or an error code indicating a reason for failure. + */ +static int mqtt_message_id_only_enc(u8_t message_type, u16_t message_id, + struct buf_ctx *buf) +{ + int err_code; + u8_t *start; + + /* Message id zero is not permitted by spec. */ + if (message_id == 0) { + 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; + } + + return mqtt_encode_fixed_header(message_type, start, buf); +} + +int connect_request_encode(const struct mqtt_client *client, + struct buf_ctx *buf) +{ + u8_t connect_flags = client->clean_session << 1; + const u8_t message_type = + MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_CONNECT, 0, 0, 0); + const struct mqtt_utf8 *mqtt_proto_desc; + u8_t *connect_flags_pos; + int err_code; + u8_t *start; + + if (client->protocol_version == MQTT_VERSION_3_1_1) { + mqtt_proto_desc = &mqtt_3_1_1_proto_desc; + } else { + mqtt_proto_desc = &mqtt_3_1_0_proto_desc; + } + + /* Reserve space for fixed header. */ + buf->cur += MQTT_FIXED_HEADER_MAX_SIZE; + start = buf->cur; + + MQTT_TRC("Encoding Protocol Description. Str:%s Size:%08x.", + mqtt_proto_desc->utf8, mqtt_proto_desc->size); + + err_code = pack_utf8_str(mqtt_proto_desc, buf); + if (err_code != 0) { + return err_code; + } + + MQTT_TRC("Encoding Protocol Version %02x.", client->protocol_version); + err_code = pack_uint8(client->protocol_version, buf); + if (err_code != 0) { + return err_code; + } + + /* Remember position of connect flag and leave one byte for it to + * be packed once we determine its value. + */ + connect_flags_pos = buf->cur; + + err_code = pack_uint8(0, buf); + if (err_code != 0) { + return err_code; + } + + MQTT_TRC("Encoding Keep Alive Time %04x.", MQTT_KEEPALIVE); + err_code = pack_uint16(MQTT_KEEPALIVE, buf); + if (err_code != 0) { + return err_code; + } + + MQTT_TRC("Encoding Client Id. Str:%s Size:%08x.", + client->client_id.utf8, client->client_id.size); + err_code = pack_utf8_str(&client->client_id, buf); + if (err_code != 0) { + return err_code; + } + + /* Pack will topic and QoS */ + if (client->will_topic != NULL) { + connect_flags |= MQTT_CONNECT_FLAG_WILL_TOPIC; + /* QoS is always 1 as of now. */ + connect_flags |= ((client->will_topic->qos & 0x03) << 3); + connect_flags |= client->will_retain << 5; + + MQTT_TRC("Encoding Will Topic. Str:%s Size:%08x.", + client->will_topic->topic.utf8, + client->will_topic->topic.size); + err_code = pack_utf8_str(&client->will_topic->topic, buf); + if (err_code != 0) { + return err_code; + } + + if (client->will_message != NULL) { + MQTT_TRC("Encoding Will Message. Str:%s Size:%08x.", + client->will_message->utf8, + client->will_message->size); + err_code = pack_utf8_str(client->will_message, buf); + if (err_code != 0) { + return err_code; + } + } else { + MQTT_TRC("Encoding Zero Length Will Message."); + err_code = zero_len_str_encode(buf); + if (err_code != 0) { + return err_code; + } + } + } + + /* Pack Username if any. */ + if (client->user_name != NULL) { + connect_flags |= MQTT_CONNECT_FLAG_USERNAME; + + MQTT_TRC("Encoding Username. Str:%s, Size:%08x.", + client->user_name->utf8, client->user_name->size); + err_code = pack_utf8_str(client->user_name, buf); + if (err_code != 0) { + return err_code; + } + } + + /* Pack Password if any. */ + if (client->password != NULL) { + connect_flags |= MQTT_CONNECT_FLAG_PASSWORD; + + MQTT_TRC("Encoding Password. Str:%s Size:%08x.", + client->password->utf8, client->password->size); + err_code = pack_utf8_str(client->password, buf); + if (err_code != 0) { + return err_code; + } + } + + /* Write the flags the connect flags. */ + *connect_flags_pos = connect_flags; + + return mqtt_encode_fixed_header(message_type, start, buf); +} + +int publish_encode(const struct mqtt_publish_param *param, struct buf_ctx *buf) +{ + const u8_t message_type = MQTT_MESSAGES_OPTIONS( + MQTT_PKT_TYPE_PUBLISH, param->dup_flag, + param->message.topic.qos, param->retain_flag); + int err_code; + u8_t *start; + + /* Message id zero is not permitted by spec. */ + if ((param->message.topic.qos) && (param->message_id == 0)) { + return -EINVAL; + } + + /* Reserve space for fixed header. */ + buf->cur += MQTT_FIXED_HEADER_MAX_SIZE; + start = buf->cur; + + err_code = pack_utf8_str(¶m->message.topic.topic, buf); + if (err_code != 0) { + return err_code; + } + + if (param->message.topic.qos) { + err_code = pack_uint16(param->message_id, 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. + */ + buf->cur += param->message.payload.len; + + err_code = mqtt_encode_fixed_header(message_type, start, buf); + if (err_code != 0) { + return err_code; + } + + buf->end -= param->message.payload.len; + + return 0; +} + +int publish_ack_encode(const struct mqtt_puback_param *param, + struct buf_ctx *buf) +{ + const u8_t message_type = + MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_PUBACK, 0, 0, 0); + + return mqtt_message_id_only_enc(message_type, param->message_id, buf); +} + +int publish_receive_encode(const struct mqtt_pubrec_param *param, + struct buf_ctx *buf) +{ + const u8_t message_type = + MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_PUBREC, 0, 0, 0); + + return mqtt_message_id_only_enc(message_type, param->message_id, buf); +} + +int publish_release_encode(const struct mqtt_pubrel_param *param, + struct buf_ctx *buf) +{ + const u8_t message_type = + MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_PUBREL, 0, 1, 0); + + return mqtt_message_id_only_enc(message_type, param->message_id, buf); +} + +int publish_complete_encode(const struct mqtt_pubcomp_param *param, + struct buf_ctx *buf) +{ + const u8_t message_type = + MQTT_MESSAGES_OPTIONS(MQTT_PKT_TYPE_PUBCOMP, 0, 0, 0); + + return mqtt_message_id_only_enc(message_type, param->message_id, buf); +} + +int disconnect_encode(struct buf_ctx *buf) +{ + if (buf->end - buf->cur < sizeof(disc_packet)) { + return -ENOMEM; + } + + memcpy(buf->cur, disc_packet, sizeof(disc_packet)); + buf->end = buf->cur + sizeof(disc_packet); + + return 0; +} + +int subscribe_encode(const struct mqtt_subscription_list *param, + struct buf_ctx *buf) +{ + const u8_t message_type = MQTT_MESSAGES_OPTIONS( + MQTT_PKT_TYPE_SUBSCRIBE, 0, 1, 0); + int err_code, i; + u8_t *start; + + /* Message id zero is not permitted by spec. */ + if (param->message_id == 0) { + return -EINVAL; + } + + /* Reserve space for fixed header. */ + buf->cur += MQTT_FIXED_HEADER_MAX_SIZE; + start = buf->cur; + + err_code = pack_uint16(param->message_id, 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) { + return err_code; + } + + err_code = pack_uint8(param->list[i].qos, buf); + if (err_code != 0) { + return err_code; + } + } + + return mqtt_encode_fixed_header(message_type, start, buf); +} + +int unsubscribe_encode(const struct mqtt_subscription_list *param, + struct buf_ctx *buf) +{ + const u8_t message_type = MQTT_MESSAGES_OPTIONS( + MQTT_PKT_TYPE_UNSUBSCRIBE, 0, MQTT_QOS_1_AT_LEAST_ONCE, 0); + int err_code, i; + u8_t *start; + + /* Reserve space for fixed header. */ + buf->cur += MQTT_FIXED_HEADER_MAX_SIZE; + start = buf->cur; + + err_code = pack_uint16(param->message_id, 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) { + return err_code; + } + } + + return mqtt_encode_fixed_header(message_type, start, buf); +} + +int ping_request_encode(struct buf_ctx *buf) +{ + if (buf->end - buf->cur < sizeof(ping_packet)) { + return -ENOMEM; + } + + memcpy(buf->cur, ping_packet, sizeof(ping_packet)); + buf->end = buf->cur + sizeof(ping_packet); + + return 0; +} diff --git a/subsys/net/lib/mqtt_sock/mqtt_internal.h b/subsys/net/lib/mqtt_sock/mqtt_internal.h new file mode 100644 index 0000000000000..189ad1d9b32b2 --- /dev/null +++ b/subsys/net/lib/mqtt_sock/mqtt_internal.h @@ -0,0 +1,399 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @file mqtt_internal.h + * + * @brief Function and data structures internal to MQTT module. + */ + +#ifndef MQTT_INTERNAL_H_ +#define MQTT_INTERNAL_H_ + +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**@brief Keep alive time for MQTT (in seconds). Sending of Ping Requests to + * keep the connection alive are governed by this value. + */ +#define MQTT_KEEPALIVE CONFIG_MQTT_KEEPALIVE + +/**@brief Minimum mandatory size of fixed header. */ +#define MQTT_FIXED_HEADER_MIN_SIZE 2 + +/**@brief Maximum size of the fixed header. Remaining length size is 4 in this + * case. + */ +#define MQTT_FIXED_HEADER_MAX_SIZE 5 + +/**@brief MQTT Control Packet Types. */ +#define MQTT_PKT_TYPE_CONNECT 0x10 +#define MQTT_PKT_TYPE_CONNACK 0x20 +#define MQTT_PKT_TYPE_PUBLISH 0x30 +#define MQTT_PKT_TYPE_PUBACK 0x40 +#define MQTT_PKT_TYPE_PUBREC 0x50 +#define MQTT_PKT_TYPE_PUBREL 0x60 +#define MQTT_PKT_TYPE_PUBCOMP 0x70 +#define MQTT_PKT_TYPE_SUBSCRIBE 0x80 +#define MQTT_PKT_TYPE_SUBACK 0x90 +#define MQTT_PKT_TYPE_UNSUBSCRIBE 0xA0 +#define MQTT_PKT_TYPE_UNSUBACK 0xB0 +#define MQTT_PKT_TYPE_PINGREQ 0xC0 +#define MQTT_PKT_TYPE_PINGRSP 0xD0 +#define MQTT_PKT_TYPE_DISCONNECT 0xE0 + +/**@brief Masks for MQTT header flags. */ +#define MQTT_HEADER_DUP_MASK 0x08 +#define MQTT_HEADER_QOS_MASK 0x06 +#define MQTT_HEADER_RETAIN_MASK 0x01 + +/**@brief Masks for MQTT header flags. */ +#define MQTT_CONNECT_FLAG_CLEAN_SESSION 0x02 +#define MQTT_CONNECT_FLAG_WILL_TOPIC 0x04 +#define MQTT_CONNECT_FLAG_WILL_RETAIN 0x20 +#define MQTT_CONNECT_FLAG_PASSWORD 0x40 +#define MQTT_CONNECT_FLAG_USERNAME 0x80 + +#define MQTT_CONNACK_FLAG_SESSION_PRESENT 0x01 + +/**@brief Maximum payload size of MQTT packet. */ +#define MQTT_MAX_PAYLOAD_SIZE 0x0FFFFFFF + +/**@brief Computes total size needed to pack a UTF8 string. */ +#define GET_UT8STR_BUFFER_SIZE(STR) (sizeof(u16_t) + (STR)->size) + +/**@brief Computes total size needed to pack a binary stream. */ +#define GET_BINSTR_BUFFER_SIZE(STR) ((STR)->len) + +/**@brief Sets MQTT Client's state with one indicated in 'STATE'. */ +#define MQTT_SET_STATE(CLIENT, STATE) ((CLIENT)->internal.state |= (STATE)) + +/**@brief Sets MQTT Client's state exclusive to 'STATE'. */ +#define MQTT_SET_STATE_EXCLUSIVE(CLIENT, STATE) \ + ((CLIENT)->internal.state = (STATE)) + +/**@brief Verifies if MQTT Client's state is set with one indicated in 'STATE'. + */ +#define MQTT_HAS_STATE(CLIENT, STATE) ((CLIENT)->internal.state & (STATE)) + +/**@brief Reset 'STATE' in MQTT Client's state. */ +#define MQTT_RESET_STATE(CLIENT, STATE) ((CLIENT)->internal.state &= ~(STATE)) + +/**@brief Initialize MQTT Client's state. */ +#define MQTT_STATE_INIT(CLIENT) ((CLIENT)->internal.state = MQTT_STATE_IDLE) + +/**@brief Computes the first byte of MQTT message header based on message type, + * duplication flag, QoS and the retain flag. + */ +#define MQTT_MESSAGES_OPTIONS(TYPE, DUP, QOS, RETAIN) \ + (((TYPE) & 0xF0) | \ + (((DUP) << 3) & 0x08) | \ + (((QOS) << 1) & 0x06) | \ + ((RETAIN) & 0x01)) + +#define MQTT_MAX_LENGTH_BYTES 4 +#define MQTT_LENGTH_VALUE_MASK 0x7F +#define MQTT_LENGTH_CONTINUATION_BIT 0x80 +#define MQTT_LENGTH_SHIFT 7 + +/**@brief Check if the input pointer is NULL, if so it returns -EINVAL. */ +#define NULL_PARAM_CHECK(param) \ + do { \ + if ((param) == NULL) { \ + return -EINVAL; \ + } \ + } while (0) + +#define NULL_PARAM_CHECK_VOID(param) \ + do { \ + if ((param) == NULL) { \ + return; \ + } \ + } while (0) + +/** Buffer context to iterate over buffer. */ +struct buf_ctx { + u8_t *cur; + u8_t *end; +}; + +/**@brief MQTT States. */ +enum mqtt_state { + /** Idle state, implying the client entry in the table is unused/free. + */ + MQTT_STATE_IDLE = 0x00000000, + + /** TCP Connection has been requested, awaiting result of the request. + */ + MQTT_STATE_TCP_CONNECTING = 0x00000001, + + /** TCP Connection successfully established. */ + MQTT_STATE_TCP_CONNECTED = 0x00000002, + + /** MQTT Connection successful. */ + MQTT_STATE_CONNECTED = 0x00000004, + + /** TCP Disconnect has been requested, awaiting result of the request. + */ + MQTT_STATE_DISCONNECTING = 0x00000008 +}; + +/**@brief Notify application about MQTT event. + * + * @param[in] client Identifies the client for which event occurred. + * @param[in] evt MQTT event. + */ +void event_notify(struct mqtt_client *client, const struct mqtt_evt *evt); + +/**@brief Handles MQTT messages received from the peer. + * + * @param[in] client Identifies the client for which the data was received. + + * @return 0 if the procedure is successful, an error code otherwise. + */ +int mqtt_handle_rx(struct mqtt_client *client); + +/**@brief Constructs/encodes Connect packet. + * + * @param[in] client Identifies the client for which the procedure is requested. + * All information required for creating the packet like + * client id, clean session flag, retain session flag etc are + * assumed to be populated for the client instance when this + * procedure is requested. + * @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 connect_request_encode(const struct mqtt_client *client, + struct buf_ctx *buf); + +/**@brief Constructs/encodes Publish packet. + * + * @param[in] param Publish 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 publish_encode(const struct mqtt_publish_param *param, struct buf_ctx *buf); + +/**@brief Constructs/encodes Publish Ack packet. + * + * @param[in] param Publish Ack 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 publish_ack_encode(const struct mqtt_puback_param *param, + struct buf_ctx *buf); + +/**@brief Constructs/encodes Publish Receive packet. + * + * @param[in] param Publish Receive 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 publish_receive_encode(const struct mqtt_pubrec_param *param, + struct buf_ctx *buf); + +/**@brief Constructs/encodes Publish Release packet. + * + * @param[in] param Publish Release 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 publish_release_encode(const struct mqtt_pubrel_param *param, + struct buf_ctx *buf); + +/**@brief Constructs/encodes Publish Complete packet. + * + * @param[in] param Publish Complete 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 publish_complete_encode(const struct mqtt_pubcomp_param *param, + struct buf_ctx *buf); + +/**@brief Constructs/encodes Disconnect packet. + * + * @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 disconnect_encode(struct buf_ctx *buf); + +/**@brief Constructs/encodes Subscribe packet. + * + * @param[in] param Subscribe 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 subscribe_encode(const struct mqtt_subscription_list *param, + struct buf_ctx *buf); + +/**@brief Constructs/encodes Unsubscribe packet. + * + * @param[in] param Unsubscribe 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 unsubscribe_encode(const struct mqtt_subscription_list *param, + struct buf_ctx *buf); + +/**@brief Constructs/encodes Ping Request packet. + * + * @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 ping_request_encode(struct buf_ctx *buf); + +/**@brief Decode MQTT Packet Type and Length in the MQTT fixed header. + * + * @param[inout] buf A pointer to the buf_ctx structure containing current + * buffer position. + * @param[out] type_and_flags Message type and flags. + * @param[out] length Length of variable header and payload in the MQTT message. + * + * @return 0 if the procedure is successful, an error code otherwise. + */ +int fixed_header_decode(struct buf_ctx *buf, u8_t *type_and_flags, + u32_t *length); + +/**@brief Decode MQTT Connect Ack packet. + * + * @param[in] client 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 Connect Ack parameters. + * + * @return 0 if the procedure is successful, an error code otherwise. + */ +int connect_ack_decode(const struct mqtt_client *client, struct buf_ctx *buf, + struct mqtt_connack_param *param); + +/**@brief Decode MQTT Publish packet. + * + * @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 + * buffer position. + * @param[out] param Pointer to buffer for decoded Publish parameters. + * + * @return 0 if the procedure is successful, an error code otherwise. + */ +int publish_decode(u8_t flags, u32_t var_length, struct buf_ctx *buf, + struct mqtt_publish_param *param); + +/**@brief Decode MQTT Publish Ack packet. + * + * @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); + +/**@brief Decode MQTT Publish Receive packet. + * + * @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, + struct mqtt_pubrec_param *param); + +/**@brief Decode MQTT Publish Release packet. + * + * @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, + struct mqtt_pubrel_param *param); + +/**@brief Decode MQTT Publish Complete packet. + * + * @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, + struct mqtt_pubcomp_param *param); + +/**@brief Decode MQTT Subscribe packet. + * + * @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, + struct mqtt_suback_param *param); + +/**@brief Decode MQTT Unsubscribe packet. + * + * @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, + struct mqtt_unsuback_param *param); + +#ifdef __cplusplus +} +#endif + +#endif /* MQTT_INTERNAL_H_ */ diff --git a/subsys/net/lib/mqtt_sock/mqtt_os.h b/subsys/net/lib/mqtt_sock/mqtt_os.h new file mode 100644 index 0000000000000..708a9a4967da6 --- /dev/null +++ b/subsys/net/lib/mqtt_sock/mqtt_os.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @file mqtt_os.h + * + * @brief MQTT Client depends on certain OS specific functionality. The needed + * methods are mapped here and should be implemented based on OS in use. + * + * @details Memory management, mutex, logging and wall clock are the needed + * functionality for MQTT module. The needed interfaces are defined + * in the OS. OS specific port of the interface shall be provided. + * + */ + +#ifndef MQTT_OS_H_ +#define MQTT_OS_H_ + +#include +#include + +#include + +#include "mqtt_internal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/**@brief Method to get trace logs from the module. */ +#define MQTT_TRC(...) NET_DBG(__VA_ARGS__) + +/**@brief Method to error logs from the module. */ +#define MQTT_ERR(...) NET_ERR(__VA_ARGS__) + +/**@brief Initialize the mutex for the module, if any. + * + * @details This method is called during module initialization @ref mqtt_init. + */ +static inline void mqtt_mutex_init(struct mqtt_client *client) +{ + k_mutex_init(&client->internal.mutex); +} + +/**@brief Acquire lock on the module specific mutex, if any. + * + * @details This is assumed to be a blocking method until the acquisition + * of the mutex succeeds. + */ +static inline void mqtt_mutex_lock(struct mqtt_client *client) +{ + (void)k_mutex_lock(&client->internal.mutex, K_FOREVER); +} + +/**@brief Release the lock on the module specific mutex, if any. + */ +static inline void mqtt_mutex_unlock(struct mqtt_client *client) +{ + k_mutex_unlock(&client->internal.mutex); +} + +/**@brief Method to get the sys tick or a wall clock in millisecond resolution. + * + * @retval Current wall clock or sys tick value in milliseconds. + */ +static inline u32_t mqtt_sys_tick_in_ms_get(void) +{ + return k_uptime_get_32(); +} + +/**@brief Method to get elapsed time in milliseconds since the last activity. + * + * @param[in] last_activity The value since elapsed time is requested. + * + * @retval Time elapsed since last_activity time. + */ +static inline u32_t mqtt_elapsed_time_in_ms_get(u32_t last_activity) +{ + s32_t diff = k_uptime_get_32() - last_activity; + + if (diff < 0) { + return 0; + } + + return diff; +} + +#ifdef __cplusplus +} +#endif + +#endif /* MQTT_OS_H_ */ diff --git a/subsys/net/lib/mqtt_sock/mqtt_rx.c b/subsys/net/lib/mqtt_sock/mqtt_rx.c new file mode 100644 index 0000000000000..b87c97e0d9de2 --- /dev/null +++ b/subsys/net/lib/mqtt_sock/mqtt_rx.c @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define LOG_MODULE_NAME net_mqtt_rx +#define NET_LOG_LEVEL CONFIG_MQTT_LOG_LEVEL + +#include "mqtt_internal.h" +#include "mqtt_transport.h" +#include "mqtt_os.h" + +/** @file mqtt_rx.c + * + * @brief MQTT Received data handling. + */ + +static int mqtt_handle_packet(struct mqtt_client *client, + u8_t type_and_flags, + u32_t var_length, + struct buf_ctx *buf) +{ + int err_code = 0; + bool notify_event = true; + struct mqtt_evt evt; + + /* Success by default, overwritten in special cases. */ + evt.result = 0; + + switch (type_and_flags & 0xF0) { + case MQTT_PKT_TYPE_CONNACK: + MQTT_TRC("[CID %p]: Received MQTT_PKT_TYPE_CONNACK!", client); + + evt.type = MQTT_EVT_CONNACK; + err_code = connect_ack_decode(client, buf, &evt.param.connack); + if (err_code == 0) { + MQTT_TRC("[CID %p]: return_code: %d", client, + evt.param.connack.return_code); + + if (evt.param.connack.return_code == + MQTT_CONNECTION_ACCEPTED) { + /* Set state. */ + MQTT_SET_STATE(client, MQTT_STATE_CONNECTED); + } + + evt.result = evt.param.connack.return_code; + } else { + evt.result = err_code; + } + + break; + + case MQTT_PKT_TYPE_PUBLISH: + MQTT_TRC("[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); + evt.result = err_code; + + client->internal.remaining_payload = + evt.param.publish.message.payload.len; + + MQTT_TRC("PUB QoS:%02x, message len %08x, topic len %08x", + evt.param.publish.message.topic.qos, + evt.param.publish.message.payload.len, + evt.param.publish.message.topic.topic.size); + + break; + + case MQTT_PKT_TYPE_PUBACK: + MQTT_TRC("[CID %p]: Received MQTT_PKT_TYPE_PUBACK!", client); + + evt.type = MQTT_EVT_PUBACK; + err_code = publish_ack_decode(buf, &evt.param.puback); + evt.result = err_code; + break; + + case MQTT_PKT_TYPE_PUBREC: + MQTT_TRC("[CID %p]: Received MQTT_PKT_TYPE_PUBREC!", client); + + evt.type = MQTT_EVT_PUBREC; + err_code = publish_receive_decode(buf, &evt.param.pubrec); + evt.result = err_code; + break; + + case MQTT_PKT_TYPE_PUBREL: + MQTT_TRC("[CID %p]: Received MQTT_PKT_TYPE_PUBREL!", client); + + evt.type = MQTT_EVT_PUBREL; + err_code = publish_release_decode(buf, &evt.param.pubrel); + evt.result = err_code; + break; + + case MQTT_PKT_TYPE_PUBCOMP: + MQTT_TRC("[CID %p]: Received MQTT_PKT_TYPE_PUBCOMP!", client); + + evt.type = MQTT_EVT_PUBCOMP; + err_code = publish_complete_decode(buf, &evt.param.pubcomp); + evt.result = err_code; + break; + + case MQTT_PKT_TYPE_SUBACK: + MQTT_TRC("[CID %p]: Received MQTT_PKT_TYPE_SUBACK!", client); + + evt.type = MQTT_EVT_SUBACK; + err_code = subscribe_ack_decode(buf, &evt.param.suback); + evt.result = err_code; + break; + + case MQTT_PKT_TYPE_UNSUBACK: + MQTT_TRC("[CID %p]: Received MQTT_PKT_TYPE_UNSUBACK!", client); + + evt.type = MQTT_EVT_UNSUBACK; + err_code = unsubscribe_ack_decode(buf, &evt.param.unsuback); + evt.result = err_code; + break; + + case MQTT_PKT_TYPE_PINGRSP: + MQTT_TRC("[CID %p]: Received MQTT_PKT_TYPE_PINGRSP!", client); + + /* No notification of Ping response to application. */ + notify_event = false; + break; + + default: + /* Nothing to notify. */ + notify_event = false; + break; + } + + if (notify_event == true) { + event_notify(client, &evt); + } + + return err_code; +} + +static int mqtt_read_message_chunk(struct mqtt_client *client, + struct buf_ctx *buf, u32_t length) +{ + int remaining; + int len; + + /* Calculate how much data we need to read from the transport, + * given the already buffered data. + */ + remaining = length - (buf->end - buf->cur); + if (remaining <= 0) { + return 0; + } + + /* Check if read does not exceed the buffer. */ + if (buf->end + remaining > client->rx_buf + client->rx_buf_size) { + MQTT_ERR("[CID %p]: Buffer too small to receive the message", + client); + return -ENOMEM; + } + + len = mqtt_transport_read(client, buf->end, remaining); + if (len < 0) { + MQTT_TRC("[CID %p]: Transport read error: %d", client, len); + return len; + } + + if (len == 0) { + MQTT_TRC("[CID %p]: Connection closed.", client); + return -ENOTCONN; + } + + client->internal.rx_buf_datalen += len; + buf->end += len; + + if (len < remaining) { + MQTT_TRC("[CID %p]: Message partially received.", client); + return -EAGAIN; + } + + return 0; +} + +static int mqtt_read_publish_var_header(struct mqtt_client *client, + u8_t type_and_flags, + struct buf_ctx *buf) +{ + u8_t qos = (type_and_flags & MQTT_HEADER_QOS_MASK) >> 1; + int err_code; + u32_t variable_header_length; + + /* Read topic length field. */ + err_code = mqtt_read_message_chunk(client, buf, sizeof(u16_t)); + if (err_code < 0) { + return err_code; + } + + variable_header_length = *buf->cur << 8; /* MSB */ + variable_header_length |= *(buf->cur + 1); /* LSB */ + + /* Add two bytes for topic length field. */ + variable_header_length += sizeof(u16_t); + + /* Add two bytes for message_id, if needed. */ + if (qos > MQTT_QOS_0_AT_MOST_ONCE) { + variable_header_length += sizeof(u16_t); + } + + /* Now we can read the whole header. */ + err_code = mqtt_read_message_chunk(client, buf, + variable_header_length); + if (err_code < 0) { + return err_code; + } + + return 0; +} + +static int mqtt_read_and_parse_fixed_header(struct mqtt_client *client, + u8_t *type_and_flags, + u32_t *var_length, + struct buf_ctx *buf) +{ + /* Read the mandatory part of the fixed header in first iteration. */ + u8_t chunk_size = MQTT_FIXED_HEADER_MIN_SIZE; + int err_code; + + do { + err_code = mqtt_read_message_chunk(client, buf, chunk_size); + if (err_code < 0) { + return err_code; + } + + /* Reset to pointer to the beginning of the frame. */ + buf->cur = client->rx_buf; + chunk_size = 1; + + err_code = fixed_header_decode(buf, type_and_flags, var_length); + } while (err_code == -EAGAIN); + + return err_code; +} + +int mqtt_handle_rx(struct mqtt_client *client) +{ + int err_code; + u8_t type_and_flags; + u32_t var_length; + struct buf_ctx buf; + + buf.cur = client->rx_buf; + buf.end = client->rx_buf + client->internal.rx_buf_datalen; + + err_code = mqtt_read_and_parse_fixed_header(client, &type_and_flags, + &var_length, &buf); + if (err_code < 0) { + return (err_code == -EAGAIN) ? 0 : err_code; + } + + if ((type_and_flags & 0xF0) == MQTT_PKT_TYPE_PUBLISH) { + err_code = mqtt_read_publish_var_header(client, type_and_flags, + &buf); + } else { + err_code = mqtt_read_message_chunk(client, &buf, var_length); + } + + if (err_code < 0) { + return (err_code == -EAGAIN) ? 0 : err_code; + } + + /* At this point, packet is ready to be passed to the application. */ + err_code = mqtt_handle_packet(client, type_and_flags, var_length, &buf); + if (err_code < 0) { + return err_code; + } + + client->internal.rx_buf_datalen = 0; + + return 0; +} diff --git a/subsys/net/lib/mqtt_sock/mqtt_transport.c b/subsys/net/lib/mqtt_sock/mqtt_transport.c new file mode 100644 index 0000000000000..6d7cc7183c171 --- /dev/null +++ b/subsys/net/lib/mqtt_sock/mqtt_transport.c @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @file mqtt_transport.c + * + * @brief Internal functions to handle transport in MQTT module. + */ + +#include "mqtt_transport.h" + +/* Transport handler functions for TCP socket transport. */ +extern int mqtt_client_tcp_connect(struct mqtt_client *client); +extern int mqtt_client_tcp_write(struct mqtt_client *client, const u8_t *data, + u32_t datalen); +extern int mqtt_client_tcp_read(struct mqtt_client *client, u8_t *data, + u32_t buflen); +extern int mqtt_client_tcp_disconnect(struct mqtt_client *client); + +#if defined(CONFIG_MQTT_LIB_TLS) +/* Transport handler functions for TLS socket transport. */ +extern int mqtt_client_tls_connect(struct mqtt_client *client); +extern int mqtt_client_tls_write(struct mqtt_client *client, const u8_t *data, + u32_t datalen); +extern int mqtt_client_tls_read(struct mqtt_client *client, u8_t *data, + u32_t buflen); +extern int mqtt_client_tls_disconnect(struct mqtt_client *client); +#endif /* CONFIG_MQTT_LIB_TLS */ + +/**@brief Function pointer array for TCP/TLS transport handlers. */ +const struct transport_procedure transport_fn[MQTT_TRANSPORT_NUM] = { + { + mqtt_client_tcp_connect, + mqtt_client_tcp_write, + mqtt_client_tcp_read, + mqtt_client_tcp_disconnect, + }, +#if defined(CONFIG_MQTT_LIB_TLS) + { + mqtt_client_tls_connect, + mqtt_client_tls_write, + mqtt_client_tls_read, + mqtt_client_tls_disconnect, + } +#endif /* CONFIG_MQTT_LIB_TLS */ +}; + +int mqtt_transport_connect(struct mqtt_client *client) +{ + return transport_fn[client->transport.type].connect(client); +} + +int mqtt_transport_write(struct mqtt_client *client, const u8_t *data, + u32_t datalen) +{ + return transport_fn[client->transport.type].write(client, data, + datalen); +} + +int mqtt_transport_read(struct mqtt_client *client, u8_t *data, u32_t buflen) +{ + return transport_fn[client->transport.type].read(client, data, buflen); +} + +int mqtt_transport_disconnect(struct mqtt_client *client) +{ + return transport_fn[client->transport.type].disconnect(client); +} diff --git a/subsys/net/lib/mqtt_sock/mqtt_transport.h b/subsys/net/lib/mqtt_sock/mqtt_transport.h new file mode 100644 index 0000000000000..6f295a717af3f --- /dev/null +++ b/subsys/net/lib/mqtt_sock/mqtt_transport.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @file mqtt_transport.h + * + * @brief Internal functions to handle transport in MQTT module. + */ + +#ifndef MQTT_TRANSPORT_H_ +#define MQTT_TRANSPORT_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/**@brief Transport for handling transport connect procedure. */ +typedef int (*transport_connect_handler_t)(struct mqtt_client *client); + +/**@brief Transport write handler. */ +typedef int (*transport_write_handler_t)(struct mqtt_client *client, + const u8_t *data, u32_t datalen); + +/**@brief Transport read handler. */ +typedef int (*transport_read_handler_t)(struct mqtt_client *client, u8_t *data, + u32_t buflen); + +/**@brief Transport disconnect handler. */ +typedef int (*transport_disconnect_handler_t)(struct mqtt_client *client); + +/**@brief Transport procedure handlers. */ +struct transport_procedure { + /** Transport connect handler. Handles TCP connection callback based on + * type of transport. + */ + transport_connect_handler_t connect; + + /** Transport write handler. Handles transport write based on type of + * transport. + */ + transport_write_handler_t write; + + /** Transport read handler. Handles transport read based on type of + * transport. + */ + transport_read_handler_t read; + + /** Transport disconnect handler. Handles transport disconnection based + * on type of transport. + */ + transport_disconnect_handler_t disconnect; +}; + +/**@brief Handles TCP Connection Complete for configured transport. + * + * @param[in] client Identifies the client on which the procedure is requested. + * + * @retval 0 or an error code indicating reason for failure. + */ +int mqtt_transport_connect(struct mqtt_client *client); + +/**@brief Handles write requests on configured transport. + * + * @param[in] client Identifies the client on which the procedure is requested. + * @param[in] data Data to be written on the transport. + * @param[in] datalen Length of data to be written on the transport. + * + * @retval 0 or an error code indicating reason for failure. + */ +int mqtt_transport_write(struct mqtt_client *client, const u8_t *data, + u32_t datalen); + +/**@brief Handles read requests on configured transport. + * + * @param[in] client Identifies the client on which the procedure is requested. + * @param[in] data Pointer where read data is to be fetched. + * @param[in] buflen Size of memory provided for the operation. + * + * @retval Number of bytes read or an error code indicating reason for failure. + * 0 if connection was closed. + */ +int mqtt_transport_read(struct mqtt_client *client, u8_t *data, u32_t buflen); + +/**@brief Handles transport disconnection requests on configured transport. + * + * @param[in] client Identifies the client on which the procedure is requested. + * + * @retval 0 or an error code indicating reason for failure. + */ +int mqtt_transport_disconnect(struct mqtt_client *client); + +#ifdef __cplusplus +} +#endif + +#endif /* MQTT_TRANSPORT_H_ */ diff --git a/subsys/net/lib/mqtt_sock/mqtt_transport_socket_tcp.c b/subsys/net/lib/mqtt_sock/mqtt_transport_socket_tcp.c new file mode 100644 index 0000000000000..c51fd8b454a8d --- /dev/null +++ b/subsys/net/lib/mqtt_sock/mqtt_transport_socket_tcp.c @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @file mqtt_transport_socket_tcp.h + * + * @brief Internal functions to handle transport over TCP socket. + */ + +#define LOG_MODULE_NAME net_mqtt_sock_tcp +#define NET_LOG_LEVEL CONFIG_MQTT_LOG_LEVEL + +#include +#include +#include + +#include "mqtt_os.h" + +/**@brief Handles connect request for TCP socket transport. + * + * @param[in] client Identifies the client on which the procedure is requested. + * + * @retval 0 or an error code indicating reason for failure. + */ +int mqtt_client_tcp_connect(struct mqtt_client *client) +{ + const struct sockaddr *broker = client->broker; + int ret; + + client->transport.tcp.sock = socket(broker->sa_family, SOCK_STREAM, + IPPROTO_TCP); + if (client->transport.tcp.sock < 0) { + return -errno; + } + + MQTT_TRC("Created socket %d", client->transport.tcp.sock); + + size_t peer_addr_size = sizeof(struct sockaddr_in6); + + if (broker->sa_family == AF_INET) { + peer_addr_size = sizeof(struct sockaddr_in); + } + + ret = connect(client->transport.tcp.sock, client->broker, + peer_addr_size); + if (ret < 0) { + (void)close(client->transport.tcp.sock); + return -errno; + } + + MQTT_TRC("Connect completed"); + return 0; +} + +/**@brief Handles write requests on TCP socket transport. + * + * @param[in] client Identifies the client on which the procedure is requested. + * @param[in] data Data to be written on the transport. + * @param[in] datalen Length of data to be written on the transport. + * + * @retval 0 or an error code indicating reason for failure. + */ +int mqtt_client_tcp_write(struct mqtt_client *client, const u8_t *data, + u32_t datalen) +{ + u32_t offset = 0; + int ret; + + while (offset < datalen) { + ret = send(client->transport.tcp.sock, data + offset, + datalen - offset, 0); + if (ret < 0) { + return -errno; + } + + offset += ret; + } + + return 0; +} + +/**@brief Handles read requests on TCP socket transport. + * + * @param[in] client Identifies the client on which the procedure is requested. + * @param[in] data Pointer where read data is to be fetched. + * @param[in] buflen Size of memory provided for the operation. + * + * @retval Number of bytes read or an error code indicating reason for failure. + * 0 if connection was closed. + */ +int mqtt_client_tcp_read(struct mqtt_client *client, u8_t *data, u32_t buflen) +{ + int ret; + + ret = recv(client->transport.tcp.sock, data, buflen, MSG_DONTWAIT); + if (ret < 0) { + return -errno; + } + + return ret; +} + +/**@brief Handles transport disconnection requests on TCP socket transport. + * + * @param[in] client Identifies the client on which the procedure is requested. + * + * @retval 0 or an error code indicating reason for failure. + */ +int mqtt_client_tcp_disconnect(struct mqtt_client *client) +{ + int ret; + + MQTT_TRC("Closing socket %d", client->transport.tcp.sock); + + ret = close(client->transport.tcp.sock); + if (ret < 0) { + return -errno; + } + + return 0; +} diff --git a/subsys/net/lib/mqtt_sock/mqtt_transport_socket_tls.c b/subsys/net/lib/mqtt_sock/mqtt_transport_socket_tls.c new file mode 100644 index 0000000000000..862495e9df1aa --- /dev/null +++ b/subsys/net/lib/mqtt_sock/mqtt_transport_socket_tls.c @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @file mqtt_transport_socket_tls.h + * + * @brief Internal functions to handle transport over TLS socket. + */ + +#define LOG_MODULE_NAME net_mqtt_sock_tls +#define NET_LOG_LEVEL CONFIG_MQTT_LOG_LEVEL + +#include +#include +#include + +#include "mqtt_os.h" + +/**@brief Handles connect request for TLS socket transport. + * + * @param[in] client Identifies the client on which the procedure is requested. + * + * @retval 0 or an error code indicating reason for failure. + */ +int mqtt_client_tls_connect(struct mqtt_client *client) +{ + const struct sockaddr *broker = client->broker; + struct mqtt_sec_config *tls_config = &client->transport.tls.config; + int ret; + + client->transport.tls.sock = socket(broker->sa_family, + SOCK_STREAM, IPPROTO_TLS_1_2); + if (client->transport.tls.sock < 0) { + return -errno; + } + + MQTT_TRC("Created socket %d", client->transport.tls.sock); + + /* Set secure socket options. */ + ret = setsockopt(client->transport.tls.sock, SOL_TLS, TLS_PEER_VERIFY, + &tls_config->peer_verify, + sizeof(tls_config->peer_verify)); + if (ret < 0) { + goto error; + } + + if (tls_config->cipher_list != NULL && tls_config->cipher_count > 0) { + ret = setsockopt(client->transport.tls.sock, SOL_TLS, + TLS_CIPHERSUITE_LIST, tls_config->cipher_list, + sizeof(int) * tls_config->cipher_count); + if (ret < 0) { + goto error; + } + } + + if (tls_config->seg_tag_list != NULL && tls_config->sec_tag_count > 0) { + ret = setsockopt(client->transport.tls.sock, SOL_TLS, + TLS_SEC_TAG_LIST, tls_config->seg_tag_list, + sizeof(sec_tag_t) * tls_config->sec_tag_count); + if (ret < 0) { + goto error; + } + } + + if (tls_config->hostname) { + ret = setsockopt(client->transport.tls.sock, SOL_TLS, + TLS_HOSTNAME, tls_config->hostname, + strlen(tls_config->hostname)); + if (ret < 0) { + goto error; + } + } + + size_t peer_addr_size = sizeof(struct sockaddr_in6); + + if (broker->sa_family == AF_INET) { + peer_addr_size = sizeof(struct sockaddr_in); + } + + ret = connect(client->transport.tls.sock, client->broker, + peer_addr_size); + if (ret < 0) { + goto error; + } + + MQTT_TRC("Connect completed"); + return 0; + +error: + (void)close(client->transport.tls.sock); + return -errno; +} + +/**@brief Handles write requests on TLS socket transport. + * + * @param[in] client Identifies the client on which the procedure is requested. + * @param[in] data Data to be written on the transport. + * @param[in] datalen Length of data to be written on the transport. + * + * @retval 0 or an error code indicating reason for failure. + */ +int mqtt_client_tls_write(struct mqtt_client *client, const u8_t *data, + u32_t datalen) +{ + u32_t offset = 0; + int ret; + + while (offset < datalen) { + ret = send(client->transport.tls.sock, data + offset, + datalen - offset, 0); + if (ret < 0) { + return -errno; + } + + offset += ret; + } + + return 0; +} + +/**@brief Handles read requests on TLS socket transport. + * + * @param[in] client Identifies the client on which the procedure is requested. + * @param[in] data Pointer where read data is to be fetched. + * @param[in] buflen Size of memory provided for the operation. + * + * @retval Number of bytes read or an error code indicating reason for failure. + * 0 if connection was closed. + */ +int mqtt_client_tls_read(struct mqtt_client *client, u8_t *data, u32_t buflen) +{ + int ret; + + ret = recv(client->transport.tls.sock, data, buflen, MSG_DONTWAIT); + if (ret < 0) { + return -errno; + } + + return ret; +} + +/**@brief Handles transport disconnection requests on TLS socket transport. + * + * @param[in] client Identifies the client on which the procedure is requested. + * + * @retval 0 or an error code indicating reason for failure. + */ +int mqtt_client_tls_disconnect(struct mqtt_client *client) +{ + int ret; + + MQTT_TRC("Closing socket %d", client->transport.tls.sock); + ret = close(client->transport.tls.sock); + if (ret < 0) { + return -errno; + } + + return 0; +} diff --git a/tests/net/all/prj.conf b/tests/net/all/prj.conf index 8c84768dbc7ef..14edeecac3b93 100644 --- a/tests/net/all/prj.conf +++ b/tests/net/all/prj.conf @@ -219,9 +219,8 @@ CONFIG_COAP_LOG_LEVEL_DBG=y # MQTT CONFIG_MQTT_LIB=y -CONFIG_MQTT_MSG_MAX_SIZE=128 -CONFIG_MQTT_ADDITIONAL_BUFFER_CTR=1 -CONFIG_MQTT_SUBSCRIBE_MAX_TOPICS=2 +CONFIG_MQTT_KEEPALIVE=60 +CONFIG_MQTT_LIB_TLS=y # HTTP CONFIG_HTTP=y diff --git a/tests/net/lib/mqtt_packet/CMakeLists.txt b/tests/net/lib/mqtt_packet/CMakeLists.txt index 3e888b76a1cd9..fe68444b7e5bc 100644 --- a/tests/net/lib/mqtt_packet/CMakeLists.txt +++ b/tests/net/lib/mqtt_packet/CMakeLists.txt @@ -4,7 +4,7 @@ project(mqtt_packet) target_include_directories(app PRIVATE $ENV{ZEPHYR_BASE}/subsys/net/ip - $ENV{ZEPHYR_BASE}/subsys/net/lib/mqtt + $ENV{ZEPHYR_BASE}/subsys/net/lib/mqtt_sock ) FILE(GLOB app_sources src/*.c) target_sources(app PRIVATE ${app_sources}) diff --git a/tests/net/lib/mqtt_packet/README b/tests/net/lib/mqtt_packet/README index 982ae13c9141f..39abf382a0bcb 100644 --- a/tests/net/lib/mqtt_packet/README +++ b/tests/net/lib/mqtt_packet/README @@ -52,30 +52,28 @@ To exit from QEMU enter: 'CTRL+a, x' [QEMU] CPU: qemu32 tc_start() - MQTT Library test [PASS] 1 - CONNECT, new session, zeros -[PASS] 2 - CONNECT, new session, zeros and keep alive = 365 -[PASS] 3 - CONNECT, new session, will -[PASS] 4 - CONNECT, new session, will retain -[PASS] 5 - CONNECT, new session, will qos = 1 -[PASS] 6 - CONNECT, new session, will qos = 1, will retain -[PASS] 7 - CONNECT, new session, username and password -[PASS] 8 - DISCONNECT -[PASS] 9 - PUBLISH, qos = 0 -[PASS] 10 - PUBLISH, retain = 1 -[PASS] 11 - PUBLISH, retain = 1, qos = 1 -[PASS] 12 - PUBLISH, qos = 2 -[PASS] 13 - SUBSCRIBE, one topic, qos = 0 -[PASS] 14 - SUBSCRIBE, one topic, qos = 1 -[PASS] 15 - SUBSCRIBE, one topic, qos = 2 -[PASS] 16 - SUBACK, one topic, qos = 0 -[PASS] 17 - SUBACK, one topic, qos = 1 -[PASS] 18 - SUBACK, one topic, qos = 2 -[PASS] 19 - PINGREQ -[PASS] 20 - PINGRESP -[PASS] 21 - PUBACK -[PASS] 22 - PUBREC -[PASS] 23 - PUBREL -[PASS] 24 - PUBCOMP -[PASS] 25 - UNSUBACK +[PASS] 2 - CONNECT, new session, will +[PASS] 3 - CONNECT, new session, will retain +[PASS] 4 - CONNECT, new session, will qos = 1 +[PASS] 5 - CONNECT, new session, will qos = 1, will retain +[PASS] 6 - CONNECT, new session, username and password +[PASS] 7 - DISCONNECT +[PASS] 8 - PUBLISH, qos = 0 +[PASS] 9 - PUBLISH, retain = 1 +[PASS] 10 - PUBLISH, retain = 1, qos = 1 +[PASS] 11 - PUBLISH, qos = 2 +[PASS] 12 - SUBSCRIBE, one topic, qos = 0 +[PASS] 13 - SUBSCRIBE, one topic, qos = 1 +[PASS] 14 - SUBSCRIBE, one topic, qos = 2 +[PASS] 15 - SUBACK, one topic, qos = 0 +[PASS] 16 - SUBACK, one topic, qos = 1 +[PASS] 17 - SUBACK, one topic, qos = 2 +[PASS] 18 - PINGREQ +[PASS] 19 - PUBACK +[PASS] 20 - PUBREC +[PASS] 21 - PUBREL +[PASS] 22 - PUBCOMP +[PASS] 23 - UNSUBACK =================================================================== PASS - main. =================================================================== diff --git a/tests/net/lib/mqtt_packet/src/mqtt_packet.c b/tests/net/lib/mqtt_packet/src/mqtt_packet.c index 84178820ccc54..9b9f490113848 100644 --- a/tests/net/lib/mqtt_packet/src/mqtt_packet.c +++ b/tests/net/lib/mqtt_packet/src/mqtt_packet.c @@ -5,7 +5,7 @@ */ #include -#include +#include #include /* for ARRAY_SIZE */ #include @@ -15,13 +15,56 @@ #define TOPIC_LEN 7 #define WILL_TOPIC "quitting" #define WILL_TOPIC_LEN 8 +#define WILL_MSG "bye" +#define WILL_MSG_LEN 3 #define USERNAME "zephyr1" #define USERNAME_LEN 7 +#define PASSWORD "password" +#define PASSWORD_LEN 8 -/* MQTT messages in this test are under 256 bytes */ -#define BUF_SIZE 256 -static u8_t buf[BUF_SIZE]; -static u16_t buf_len; +#define BUFFER_SIZE 128 + +static u8_t rx_buffer[BUFFER_SIZE]; +static u8_t tx_buffer[BUFFER_SIZE]; +static struct mqtt_client client; + +static struct mqtt_topic topic_qos_0 = { + .qos = 0, + .topic.utf8 = TOPIC, + .topic.size = TOPIC_LEN +}; +static struct mqtt_topic topic_qos_1 = { + .qos = 1, + .topic.utf8 = TOPIC, + .topic.size = TOPIC_LEN +}; +static struct mqtt_topic topic_qos_2 = { + .qos = 2, + .topic.utf8 = TOPIC, + .topic.size = TOPIC_LEN +}; +static struct mqtt_topic will_topic_qos_0 = { + .qos = 0, + .topic.utf8 = WILL_TOPIC, + .topic.size = WILL_TOPIC_LEN +}; +static struct mqtt_topic will_topic_qos_1 = { + .qos = 1, + .topic.utf8 = WILL_TOPIC, + .topic.size = WILL_TOPIC_LEN +}; +static struct mqtt_utf8 will_msg = { + .utf8 = WILL_MSG, + .size = WILL_MSG_LEN +}; +static struct mqtt_utf8 username = { + .utf8 = USERNAME, + .size = USERNAME_LEN +}; +static struct mqtt_utf8 password = { + .utf8 = PASSWORD, + .size = PASSWORD_LEN +}; /** * @brief MQTT test structure @@ -31,9 +74,10 @@ struct mqtt_test { const char *test_name; /* cast to something like: - * struct mqtt_connect_msg *msg_connect = (struct mqtt_connect_msg *)msg + * struct mqtt_publish_param *msg_publish = + * (struct mqtt_publish_param *)ctx */ - void *msg; + void *ctx; /* pointer to the eval routine, for example: * eval_fcn = eval_msg_connect @@ -47,35 +91,6 @@ struct mqtt_test { u16_t expected_len; }; -#define MAX_TOPICS 4 - -/** - * @brief MQTT SUBSCRIBE msg - */ -struct msg_subscribe { - u16_t pkt_id; - int items; - const char *topics[MAX_TOPICS]; - enum mqtt_qos qos[MAX_TOPICS]; -}; - -/** - * @brief MQTT SUBACK msg - */ -struct msg_suback { - u16_t pkt_id; - int elements; - enum mqtt_qos qos[MAX_TOPICS]; -}; - -/** - * @brief MQTT msg with Packet Identifier as payload: - * PUBACK, PUBREC, PUBREL, PUBCOMP, UNSUBACK - */ -struct msg_pkt_id { - u16_t pkt_id; -}; - /** * @brief eval_msg_connect Evaluate the given mqtt_test against the * connect packing/unpacking routines. @@ -121,15 +136,6 @@ static int eval_msg_suback(struct mqtt_test *mqtt_test); */ static int eval_msg_pingreq(struct mqtt_test *mqtt_test); -/** - * @brief eval_msg_pingresp Evaluate the given mqtt_test against the - * pingresp packing/unpacking routines. - * @param [in] mqtt_test MQTT test structure - * @return TC_PASS on success - * @return TC_FAIL on error - */ -static int eval_msg_pingresp(struct mqtt_test *mqtt_test); - /** * @brief eval_msg_puback Evaluate the given mqtt_test against the * puback routines. @@ -188,75 +194,42 @@ static int eval_msg_disconnect(struct mqtt_test *mqtt_test); * @brief eval_buffers Evaluate if two given buffers are equal * @param [in] buf Input buffer 1, mostly used as the 'computed' * buffer - * @param [in] buf_len 'buf' length * @param [in] expected Expected buffer * @param [in] len 'expected' len * @return TC_PASS on success * @return TC_FAIL on error and prints both buffers */ -static int eval_buffers(u8_t *buf, u16_t buf_len, - u8_t *expected, u16_t len); +static int eval_buffers(const struct buf_ctx *buf, + const u8_t *expected, u16_t len); /** * @brief print_array Prints the array 'a' of 'size' elements * @param a The array * @param size Array's size */ -static void print_array(u8_t *a, u16_t size); - -/** - * @brief test_strlen Computes the C string length allowing NULL as - * input parameter - * @param [in] str Input string - * @return String length, assuming that strlen(NULL) is 0 - */ -static size_t test_strlen(const char *str); +static void print_array(const u8_t *a, u16_t size); /* * MQTT CONNECT msg: * Clean session: 1 Client id: [6] 'zephyr' Will flag: 0 * Will QoS: 0 Will retain: 0 Will topic: [0] - * Will msg: [0] Keep alive: 0 User name: [0] + * Will msg: [0] Keep alive: 60 User name: [0] * Password: [0] * * Message can be generated by the following command: - * mosquitto_sub -V mqttv311 -i zephyr -k 0 -t sensors + * mosquitto_sub -V mqttv311 -i zephyr -k 60 -t sensors */ static u8_t connect1[] = {0x10, 0x12, 0x00, 0x04, 0x4d, 0x51, 0x54, 0x54, - 0x04, 0x02, 0x00, 0x00, 0x00, 0x06, 0x7a, 0x65, - 0x70, 0x68, 0x79, 0x72}; - -static struct mqtt_connect_msg msg_connect1 = { - .clean_session = 1, .client_id = CLIENTID, - .client_id_len = CLIENTID_LEN, - .will_flag = 0, - .will_qos = 0, .will_retain = 0, .will_topic = NULL, - .will_msg = NULL, .will_msg_len = 0, - .keep_alive = 0, .user_name = NULL, - .password = NULL, .password_len = 0 -}; - -/* - * MQTT CONNECT msg: - * Same message as connect1, but change the keep alive parameter - * - * Message can be generated by the following command: - * mosquitto_sub -V mqttv311 -i zephyr -k 365 -t sensors - */ -static -u8_t connect2[] = {0x10, 0x12, 0x00, 0x04, 0x4d, 0x51, 0x54, 0x54, - 0x04, 0x02, 0x01, 0x6d, 0x00, 0x06, 0x7a, 0x65, - 0x70, 0x68, 0x79, 0x72}; - -static struct mqtt_connect_msg msg_connect2 = { - .clean_session = 1, .client_id = CLIENTID, - .client_id_len = CLIENTID_LEN, - .will_flag = 0, - .will_qos = 0, .will_retain = 0, .will_topic = NULL, - .will_msg = NULL, .will_msg_len = 0, - .keep_alive = 365, .user_name = NULL, - .password = NULL, .password_len = 0 + 0x04, 0x02, 0x00, 0x3c, 0x00, 0x06, 0x7a, 0x65, + 0x70, 0x68, 0x79, 0x72}; + +static struct mqtt_client client_connect1 = { + .clean_session = 1, .client_id.utf8 = CLIENTID, + .client_id.size = CLIENTID_LEN, + .will_retain = 0, .will_topic = NULL, + .will_message = NULL, .user_name = NULL, + .password = NULL }; /* @@ -266,26 +239,22 @@ static struct mqtt_connect_msg msg_connect2 = { * Will msg: [3] bye Keep alive: 0 * * Message can be generated by the following command: - * mosquitto_sub -V mqttv311 -i zephyr -k 0 -t sensors --will-topic quitting \ + * mosquitto_sub -V mqttv311 -i zephyr -k 60 -t sensors --will-topic quitting \ * --will-qos 0 --will-payload bye */ static -u8_t connect3[] = {0x10, 0x21, 0x00, 0x04, 0x4d, 0x51, 0x54, 0x54, - 0x04, 0x06, 0x00, 0x00, 0x00, 0x06, 0x7a, 0x65, - 0x70, 0x68, 0x79, 0x72, 0x00, 0x08, 0x71, 0x75, - 0x69, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x00, 0x03, - 0x62, 0x79, 0x65}; - -static struct mqtt_connect_msg msg_connect3 = { - .clean_session = 1, .client_id = CLIENTID, - .client_id_len = CLIENTID_LEN, - .will_flag = 1, - .will_qos = 0, .will_retain = 0, .will_topic = WILL_TOPIC, - .will_topic_len = WILL_TOPIC_LEN, - .will_msg = (u8_t *)"bye", - .will_msg_len = 3, - .keep_alive = 0, .user_name = NULL, - .password = NULL, .password_len = 0 +u8_t connect2[] = {0x10, 0x21, 0x00, 0x04, 0x4d, 0x51, 0x54, 0x54, + 0x04, 0x06, 0x00, 0x3c, 0x00, 0x06, 0x7a, 0x65, + 0x70, 0x68, 0x79, 0x72, 0x00, 0x08, 0x71, 0x75, + 0x69, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x00, 0x03, + 0x62, 0x79, 0x65}; + +static struct mqtt_client client_connect2 = { + .clean_session = 1, .client_id.utf8 = CLIENTID, + .client_id.size = CLIENTID_LEN, + .will_retain = 0, .will_topic = &will_topic_qos_0, + .will_message = &will_msg, .user_name = NULL, + .password = NULL }; /* @@ -293,26 +262,22 @@ static struct mqtt_connect_msg msg_connect3 = { * Same message as connect3, but set Will retain: 1 * * Message can be generated by the following command: - * mosquitto_sub -V mqttv311 -i zephyr -k 0 -t sensors --will-topic quitting \ + * mosquitto_sub -V mqttv311 -i zephyr -k 60 -t sensors --will-topic quitting \ * --will-qos 0 --will-payload bye --will-retain */ static -u8_t connect4[] = {0x10, 0x21, 0x00, 0x04, 0x4d, 0x51, 0x54, 0x54, - 0x04, 0x26, 0x00, 0x00, 0x00, 0x06, 0x7a, 0x65, - 0x70, 0x68, 0x79, 0x72, 0x00, 0x08, 0x71, 0x75, - 0x69, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x00, 0x03, - 0x62, 0x79, 0x65}; - -static struct mqtt_connect_msg msg_connect4 = { - .clean_session = 1, .client_id = CLIENTID, - .client_id_len = CLIENTID_LEN, - .will_flag = 1, - .will_qos = 0, .will_retain = 1, .will_topic = WILL_TOPIC, - .will_topic_len = WILL_TOPIC_LEN, - .will_msg = (u8_t *)"bye", - .will_msg_len = 3, - .keep_alive = 0, .user_name = NULL, - .password = NULL, .password_len = 0 +u8_t connect3[] = {0x10, 0x21, 0x00, 0x04, 0x4d, 0x51, 0x54, 0x54, + 0x04, 0x26, 0x00, 0x3c, 0x00, 0x06, 0x7a, 0x65, + 0x70, 0x68, 0x79, 0x72, 0x00, 0x08, 0x71, 0x75, + 0x69, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x00, 0x03, + 0x62, 0x79, 0x65}; + +static struct mqtt_client client_connect3 = { + .clean_session = 1, .client_id.utf8 = CLIENTID, + .client_id.size = CLIENTID_LEN, + .will_retain = 1, .will_topic = &will_topic_qos_0, + .will_message = &will_msg, .user_name = NULL, + .password = NULL }; /* @@ -320,26 +285,22 @@ static struct mqtt_connect_msg msg_connect4 = { * Same message as connect3, but set Will QoS: 1 * * Message can be generated by the following command: - * mosquitto_sub -V mqttv311 -i zephyr -k 0 -t sensors --will-topic quitting \ + * mosquitto_sub -V mqttv311 -i zephyr -k 60 -t sensors --will-topic quitting \ * --will-qos 1 --will-payload bye */ static -u8_t connect5[] = {0x10, 0x21, 0x00, 0x04, 0x4d, 0x51, 0x54, 0x54, - 0x04, 0x0e, 0x00, 0x00, 0x00, 0x06, 0x7a, 0x65, - 0x70, 0x68, 0x79, 0x72, 0x00, 0x08, 0x71, 0x75, - 0x69, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x00, 0x03, - 0x62, 0x79, 0x65}; - -static struct mqtt_connect_msg msg_connect5 = { - .clean_session = 1, .client_id = CLIENTID, - .client_id_len = CLIENTID_LEN, - .will_flag = 1, - .will_qos = 1, .will_retain = 0, .will_topic = WILL_TOPIC, - .will_topic_len = WILL_TOPIC_LEN, - .will_msg = (u8_t *)"bye", - .will_msg_len = 3, - .keep_alive = 0, .user_name = NULL, - .password = NULL, .password_len = 0 +u8_t connect4[] = {0x10, 0x21, 0x00, 0x04, 0x4d, 0x51, 0x54, 0x54, + 0x04, 0x0e, 0x00, 0x3c, 0x00, 0x06, 0x7a, 0x65, + 0x70, 0x68, 0x79, 0x72, 0x00, 0x08, 0x71, 0x75, + 0x69, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x00, 0x03, + 0x62, 0x79, 0x65}; + +static struct mqtt_client client_connect4 = { + .clean_session = 1, .client_id.utf8 = CLIENTID, + .client_id.size = CLIENTID_LEN, + .will_retain = 0, .will_topic = &will_topic_qos_1, + .will_message = &will_msg, .user_name = NULL, + .password = NULL }; /* @@ -347,58 +308,47 @@ static struct mqtt_connect_msg msg_connect5 = { * Same message as connect5, but set Will retain: 1 * * Message can be generated by the following command: - * mosquitto_sub -V mqttv311 -i zephyr -k 0 -t sensors --will-topic quitting \ + * mosquitto_sub -V mqttv311 -i zephyr -k 60 -t sensors --will-topic quitting \ * --will-qos 1 --will-payload bye --will-retain */ static -u8_t connect6[] = {0x10, 0x21, 0x00, 0x04, 0x4d, 0x51, 0x54, 0x54, - 0x04, 0x2e, 0x00, 0x00, 0x00, 0x06, 0x7a, 0x65, - 0x70, 0x68, 0x79, 0x72, 0x00, 0x08, 0x71, 0x75, - 0x69, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x00, 0x03, - 0x62, 0x79, 0x65}; - -static struct mqtt_connect_msg msg_connect6 = { - .clean_session = 1, .client_id = CLIENTID, - .client_id_len = CLIENTID_LEN, - .will_flag = 1, - .will_qos = 1, .will_retain = 1, .will_topic = WILL_TOPIC, - .will_topic_len = WILL_TOPIC_LEN, - .will_msg = (u8_t *)"bye", - .will_msg_len = 3, - .keep_alive = 0, .user_name = NULL, - .password = NULL, .password_len = 0 +u8_t connect5[] = {0x10, 0x21, 0x00, 0x04, 0x4d, 0x51, 0x54, 0x54, + 0x04, 0x2e, 0x00, 0x3c, 0x00, 0x06, 0x7a, 0x65, + 0x70, 0x68, 0x79, 0x72, 0x00, 0x08, 0x71, 0x75, + 0x69, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x00, 0x03, + 0x62, 0x79, 0x65}; + +static struct mqtt_client client_connect5 = { + .clean_session = 1, .client_id.utf8 = CLIENTID, + .client_id.size = CLIENTID_LEN, + .will_retain = 1, .will_topic = &will_topic_qos_1, + .will_message = &will_msg, .user_name = NULL, + .password = NULL }; - /* * MQTT CONNECT msg: * Same message as connect6, but set username: zephyr1 and password: password * * Message can be generated by the following command: - * mosquitto_sub -V mqttv311 -i zephyr -k 0 -t sensors --will-topic quitting \ + * mosquitto_sub -V mqttv311 -i zephyr -k 60 -t sensors --will-topic quitting \ * --will-qos 1 --will-payload bye --will-retain -u zephyr1 -P password */ static -u8_t connect7[] = {0x10, 0x34, 0x00, 0x04, 0x4d, 0x51, 0x54, 0x54, - 0x04, 0xee, 0x00, 0x00, 0x00, 0x06, 0x7a, 0x65, - 0x70, 0x68, 0x79, 0x72, 0x00, 0x08, 0x71, 0x75, - 0x69, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x00, 0x03, - 0x62, 0x79, 0x65, 0x00, 0x07, 0x7a, 0x65, 0x70, - 0x68, 0x79, 0x72, 0x31, 0x00, 0x08, 0x70, 0x61, - 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64}; - -static struct mqtt_connect_msg msg_connect7 = { - .clean_session = 1, .client_id = CLIENTID, - .client_id_len = CLIENTID_LEN, - .will_flag = 1, - .will_qos = 1, .will_retain = 1, .will_topic = WILL_TOPIC, - .will_topic_len = WILL_TOPIC_LEN, - .will_msg = (u8_t *)"bye", - .will_msg_len = 3, - .keep_alive = 0, .user_name = USERNAME, - .user_name_len = USERNAME_LEN, - .password = (u8_t *)"password", - .password_len = 8 +u8_t connect6[] = {0x10, 0x34, 0x00, 0x04, 0x4d, 0x51, 0x54, 0x54, + 0x04, 0xee, 0x00, 0x3c, 0x00, 0x06, 0x7a, 0x65, + 0x70, 0x68, 0x79, 0x72, 0x00, 0x08, 0x71, 0x75, + 0x69, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x00, 0x03, + 0x62, 0x79, 0x65, 0x00, 0x07, 0x7a, 0x65, 0x70, + 0x68, 0x79, 0x72, 0x31, 0x00, 0x08, 0x70, 0x61, + 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64}; + +static struct mqtt_client client_connect6 = { + .clean_session = 1, .client_id.utf8 = CLIENTID, + .client_id.size = CLIENTID_LEN, + .will_retain = 1, .will_topic = &will_topic_qos_1, + .will_message = &will_msg, .user_name = &username, + .password = &password }; static @@ -413,14 +363,15 @@ u8_t disconnect1[] = {0xe0, 0x00}; */ static u8_t publish1[] = {0x30, 0x0b, 0x00, 0x07, 0x73, 0x65, 0x6e, 0x73, - 0x6f, 0x72, 0x73, 0x4f, 0x4b}; - -static struct mqtt_publish_msg msg_publish1 = { - .dup = 0, .qos = 0, .retain = 0, .topic = TOPIC, - .topic_len = TOPIC_LEN, - .pkt_id = 0, - .msg = (u8_t *)"OK", - .msg_len = 2, + 0x6f, 0x72, 0x73, 0x4f, 0x4b}; + +static struct mqtt_publish_param msg_publish1 = { + .dup_flag = 0, .retain_flag = 0, .message_id = 0, + .message.topic.qos = 0, + .message.topic.topic.utf8 = TOPIC, + .message.topic.topic.size = TOPIC_LEN, + .message.payload.data = (u8_t *)"OK", + .message.payload.len = 2, }; /* @@ -432,14 +383,15 @@ static struct mqtt_publish_msg msg_publish1 = { */ static u8_t publish2[] = {0x31, 0x0b, 0x00, 0x07, 0x73, 0x65, 0x6e, 0x73, - 0x6f, 0x72, 0x73, 0x4f, 0x4b}; - -static struct mqtt_publish_msg msg_publish2 = { - .dup = 0, .qos = 0, .retain = 1, .topic = TOPIC, - .topic_len = TOPIC_LEN, - .pkt_id = 0, - .msg = (u8_t *)"OK", - .msg_len = 2 + 0x6f, 0x72, 0x73, 0x4f, 0x4b}; + +static struct mqtt_publish_param msg_publish2 = { + .dup_flag = 0, .retain_flag = 1, .message_id = 0, + .message.topic.qos = 0, + .message.topic.topic.utf8 = TOPIC, + .message.topic.topic.size = TOPIC_LEN, + .message.payload.data = (u8_t *)"OK", + .message.payload.len = 2, }; /* @@ -451,14 +403,15 @@ static struct mqtt_publish_msg msg_publish2 = { */ static u8_t publish3[] = {0x33, 0x0d, 0x00, 0x07, 0x73, 0x65, 0x6e, 0x73, - 0x6f, 0x72, 0x73, 0x00, 0x01, 0x4f, 0x4b}; - -static struct mqtt_publish_msg msg_publish3 = { - .dup = 0, .qos = 1, .retain = 1, .topic = TOPIC, - .topic_len = TOPIC_LEN, - .pkt_id = 1, - .msg = (u8_t *)"OK", - .msg_len = 2 + 0x6f, 0x72, 0x73, 0x00, 0x01, 0x4f, 0x4b}; + +static struct mqtt_publish_param msg_publish3 = { + .dup_flag = 0, .retain_flag = 1, .message_id = 1, + .message.topic.qos = 1, + .message.topic.topic.utf8 = TOPIC, + .message.topic.topic.size = TOPIC_LEN, + .message.payload.data = (u8_t *)"OK", + .message.payload.len = 2, }; /* @@ -470,13 +423,14 @@ static struct mqtt_publish_msg msg_publish3 = { */ static u8_t publish4[] = {0x34, 0x0d, 0x00, 0x07, 0x73, 0x65, 0x6e, 0x73, - 0x6f, 0x72, 0x73, 0x00, 0x01, 0x4f, 0x4b}; -static struct mqtt_publish_msg msg_publish4 = { - .dup = 0, .qos = 2, .retain = 0, .topic = TOPIC, - .topic_len = TOPIC_LEN, - .pkt_id = 1, - .msg = (u8_t *)"OK", - .msg_len = 2 + 0x6f, 0x72, 0x73, 0x00, 0x01, 0x4f, 0x4b}; +static struct mqtt_publish_param msg_publish4 = { + .dup_flag = 0, .retain_flag = 0, .message_id = 1, + .message.topic.qos = 2, + .message.topic.topic.utf8 = TOPIC, + .message.topic.topic.size = TOPIC_LEN, + .message.payload.data = (u8_t *)"OK", + .message.payload.len = 2, }; /* @@ -488,10 +442,9 @@ static struct mqtt_publish_msg msg_publish4 = { */ static u8_t subscribe1[] = {0x82, 0x0c, 0x00, 0x01, 0x00, 0x07, 0x73, 0x65, - 0x6e, 0x73, 0x6f, 0x72, 0x73, 0x00}; -static struct msg_subscribe msg_subscribe1 = { - .pkt_id = 1, .items = 1, - .topics = {"sensors"}, .qos = {MQTT_QoS0} + 0x6e, 0x73, 0x6f, 0x72, 0x73, 0x00}; +static struct mqtt_subscription_list msg_subscribe1 = { + .message_id = 1, .list_count = 1, .list = &topic_qos_0 }; /* @@ -503,10 +456,9 @@ static struct msg_subscribe msg_subscribe1 = { */ static u8_t subscribe2[] = {0x82, 0x0c, 0x00, 0x01, 0x00, 0x07, 0x73, 0x65, - 0x6e, 0x73, 0x6f, 0x72, 0x73, 0x01}; -static struct msg_subscribe msg_subscribe2 = { - .pkt_id = 1, .items = 1, - .topics = {"sensors"}, .qos = {MQTT_QoS1} + 0x6e, 0x73, 0x6f, 0x72, 0x73, 0x01}; +static struct mqtt_subscription_list msg_subscribe2 = { + .message_id = 1, .list_count = 1, .list = &topic_qos_1 }; /* @@ -518,10 +470,9 @@ static struct msg_subscribe msg_subscribe2 = { */ static u8_t subscribe3[] = {0x82, 0x0c, 0x00, 0x01, 0x00, 0x07, 0x73, 0x65, - 0x6e, 0x73, 0x6f, 0x72, 0x73, 0x02}; -static struct msg_subscribe msg_subscribe3 = { - .pkt_id = 1, .items = 1, - .topics = {"sensors"}, .qos = {MQTT_QoS2} + 0x6e, 0x73, 0x6f, 0x72, 0x73, 0x02}; +static struct mqtt_subscription_list msg_subscribe3 = { + .message_id = 1, .list_count = 1, .list = &topic_qos_2 }; /* @@ -533,8 +484,9 @@ static struct msg_subscribe msg_subscribe3 = { */ static u8_t suback1[] = {0x90, 0x03, 0x00, 0x01, 0x00}; -static struct msg_suback msg_suback1 = { - .pkt_id = 1, .elements = 1, .qos = {MQTT_QoS0} +static struct mqtt_suback_param msg_suback1 = { + .message_id = 1, .return_codes.len = 1, + .return_codes.data = (u8_t[]){ MQTT_SUBACK_SUCCESS_QoS_0 } }; /* @@ -546,8 +498,9 @@ static struct msg_suback msg_suback1 = { */ static u8_t suback2[] = {0x90, 0x03, 0x00, 0x01, 0x01}; -static struct msg_suback msg_suback2 = { - .pkt_id = 1, .elements = 1, .qos = {MQTT_QoS1} +static struct mqtt_suback_param msg_suback2 = { + .message_id = 1, .return_codes.len = 1, + .return_codes.data = (u8_t[]){ MQTT_SUBACK_SUCCESS_QoS_1 } }; /* @@ -559,143 +512,134 @@ static struct msg_suback msg_suback2 = { */ static u8_t suback3[] = {0x90, 0x03, 0x00, 0x01, 0x02}; -static struct msg_suback msg_suback3 = { - .pkt_id = 1, .elements = 1, .qos = {MQTT_QoS2} +static struct mqtt_suback_param msg_suback3 = { + .message_id = 1, .return_codes.len = 1, + .return_codes.data = (u8_t[]){ MQTT_SUBACK_SUCCESS_QoS_2 } }; static u8_t pingreq1[] = {0xc0, 0x00}; -static -u8_t pingresp1[] = {0xd0, 0x00}; - static u8_t puback1[] = {0x40, 0x02, 0x00, 0x01}; -static struct msg_pkt_id msg_puback1 = {.pkt_id = 1}; +static struct mqtt_puback_param msg_puback1 = {.message_id = 1}; static u8_t pubrec1[] = {0x50, 0x02, 0x00, 0x01}; -static struct msg_pkt_id msg_pubrec1 = {.pkt_id = 1}; +static struct mqtt_pubrec_param msg_pubrec1 = {.message_id = 1}; static u8_t pubrel1[] = {0x62, 0x02, 0x00, 0x01}; -static struct msg_pkt_id msg_pubrel1 = {.pkt_id = 1}; +static struct mqtt_pubrel_param msg_pubrel1 = {.message_id = 1}; static u8_t pubcomp1[] = {0x70, 0x02, 0x00, 0x01}; -static struct msg_pkt_id msg_pubcomp1 = {.pkt_id = 1}; +static struct mqtt_pubcomp_param msg_pubcomp1 = {.message_id = 1}; static u8_t unsuback1[] = {0xb0, 0x02, 0x00, 0x01}; -static struct msg_pkt_id msg_unsuback1 = {.pkt_id = 1}; +static struct mqtt_unsuback_param msg_unsuback1 = {.message_id = 1}; static struct mqtt_test mqtt_tests[] = { + {.test_name = "CONNECT, new session, zeros", - .msg = &msg_connect1, .eval_fcn = eval_msg_connect, + .ctx = &client_connect1, .eval_fcn = eval_msg_connect, .expected = connect1, .expected_len = sizeof(connect1)}, - {.test_name = "CONNECT, new session, zeros and keep alive = 365", - .msg = &msg_connect2, .eval_fcn = eval_msg_connect, - .expected = connect2, .expected_len = sizeof(connect2)}, - {.test_name = "CONNECT, new session, will", - .msg = &msg_connect3, .eval_fcn = eval_msg_connect, - .expected = connect3, .expected_len = sizeof(connect3)}, + .ctx = &client_connect2, .eval_fcn = eval_msg_connect, + .expected = connect2, .expected_len = sizeof(connect2)}, {.test_name = "CONNECT, new session, will retain", - .msg = &msg_connect4, .eval_fcn = eval_msg_connect, - .expected = connect4, .expected_len = sizeof(connect4)}, + .ctx = &client_connect3, .eval_fcn = eval_msg_connect, + .expected = connect3, .expected_len = sizeof(connect3)}, {.test_name = "CONNECT, new session, will qos = 1", - .msg = &msg_connect5, .eval_fcn = eval_msg_connect, - .expected = connect5, .expected_len = sizeof(connect5)}, + .ctx = &client_connect4, .eval_fcn = eval_msg_connect, + .expected = connect4, .expected_len = sizeof(connect4)}, {.test_name = "CONNECT, new session, will qos = 1, will retain", - .msg = &msg_connect6, .eval_fcn = eval_msg_connect, - .expected = connect6, .expected_len = sizeof(connect6)}, + .ctx = &client_connect5, .eval_fcn = eval_msg_connect, + .expected = connect5, .expected_len = sizeof(connect5)}, {.test_name = "CONNECT, new session, username and password", - .msg = &msg_connect7, .eval_fcn = eval_msg_connect, - .expected = connect7, .expected_len = sizeof(connect7)}, + .ctx = &client_connect6, .eval_fcn = eval_msg_connect, + .expected = connect6, .expected_len = sizeof(connect6)}, {.test_name = "DISCONNECT", - .msg = NULL, .eval_fcn = eval_msg_disconnect, + .ctx = NULL, .eval_fcn = eval_msg_disconnect, .expected = disconnect1, .expected_len = sizeof(disconnect1)}, {.test_name = "PUBLISH, qos = 0", - .msg = &msg_publish1, .eval_fcn = eval_msg_publish, + .ctx = &msg_publish1, .eval_fcn = eval_msg_publish, .expected = publish1, .expected_len = sizeof(publish1)}, {.test_name = "PUBLISH, retain = 1", - .msg = &msg_publish2, .eval_fcn = eval_msg_publish, + .ctx = &msg_publish2, .eval_fcn = eval_msg_publish, .expected = publish2, .expected_len = sizeof(publish2)}, {.test_name = "PUBLISH, retain = 1, qos = 1", - .msg = &msg_publish3, .eval_fcn = eval_msg_publish, + .ctx = &msg_publish3, .eval_fcn = eval_msg_publish, .expected = publish3, .expected_len = sizeof(publish3)}, {.test_name = "PUBLISH, qos = 2", - .msg = &msg_publish4, .eval_fcn = eval_msg_publish, + .ctx = &msg_publish4, .eval_fcn = eval_msg_publish, .expected = publish4, .expected_len = sizeof(publish4)}, {.test_name = "SUBSCRIBE, one topic, qos = 0", - .msg = &msg_subscribe1, .eval_fcn = eval_msg_subscribe, + .ctx = &msg_subscribe1, .eval_fcn = eval_msg_subscribe, .expected = subscribe1, .expected_len = sizeof(subscribe1)}, {.test_name = "SUBSCRIBE, one topic, qos = 1", - .msg = &msg_subscribe2, .eval_fcn = eval_msg_subscribe, + .ctx = &msg_subscribe2, .eval_fcn = eval_msg_subscribe, .expected = subscribe2, .expected_len = sizeof(subscribe2)}, {.test_name = "SUBSCRIBE, one topic, qos = 2", - .msg = &msg_subscribe3, .eval_fcn = eval_msg_subscribe, + .ctx = &msg_subscribe3, .eval_fcn = eval_msg_subscribe, .expected = subscribe3, .expected_len = sizeof(subscribe3)}, {.test_name = "SUBACK, one topic, qos = 0", - .msg = &msg_suback1, .eval_fcn = eval_msg_suback, + .ctx = &msg_suback1, .eval_fcn = eval_msg_suback, .expected = suback1, .expected_len = sizeof(suback1)}, {.test_name = "SUBACK, one topic, qos = 1", - .msg = &msg_suback2, .eval_fcn = eval_msg_suback, + .ctx = &msg_suback2, .eval_fcn = eval_msg_suback, .expected = suback2, .expected_len = sizeof(suback2)}, {.test_name = "SUBACK, one topic, qos = 2", - .msg = &msg_suback3, .eval_fcn = eval_msg_suback, + .ctx = &msg_suback3, .eval_fcn = eval_msg_suback, .expected = suback3, .expected_len = sizeof(suback3)}, {.test_name = "PINGREQ", - .msg = NULL, .eval_fcn = eval_msg_pingreq, + .ctx = NULL, .eval_fcn = eval_msg_pingreq, .expected = pingreq1, .expected_len = sizeof(pingreq1)}, - {.test_name = "PINGRESP", - .msg = NULL, .eval_fcn = eval_msg_pingresp, - .expected = pingresp1, .expected_len = sizeof(pingresp1)}, - {.test_name = "PUBACK", - .msg = &msg_puback1, .eval_fcn = eval_msg_puback, + .ctx = &msg_puback1, .eval_fcn = eval_msg_puback, .expected = puback1, .expected_len = sizeof(puback1)}, {.test_name = "PUBREC", - .msg = &msg_pubrec1, .eval_fcn = eval_msg_pubrec, + .ctx = &msg_pubrec1, .eval_fcn = eval_msg_pubrec, .expected = pubrec1, .expected_len = sizeof(pubrec1)}, {.test_name = "PUBREL", - .msg = &msg_pubrel1, .eval_fcn = eval_msg_pubrel, + .ctx = &msg_pubrel1, .eval_fcn = eval_msg_pubrel, .expected = pubrel1, .expected_len = sizeof(pubrel1)}, {.test_name = "PUBCOMP", - .msg = &msg_pubcomp1, .eval_fcn = eval_msg_pubcomp, + .ctx = &msg_pubcomp1, .eval_fcn = eval_msg_pubcomp, .expected = pubcomp1, .expected_len = sizeof(pubcomp1)}, {.test_name = "UNSUBACK", - .msg = &msg_unsuback1, .eval_fcn = eval_msg_unsuback, + .ctx = &msg_unsuback1, .eval_fcn = eval_msg_unsuback, .expected = unsuback1, .expected_len = sizeof(unsuback1)}, /* last test case, do not remove it */ {.test_name = NULL} }; -static void print_array(u8_t *a, u16_t size) +static void print_array(const u8_t *a, u16_t size) { u16_t i; @@ -709,24 +653,14 @@ static void print_array(u8_t *a, u16_t size) TC_PRINT("\n"); } -static size_t test_strlen(const char *str) -{ - if (str == NULL) { - return 0; - } - - return strlen(str); -} - static -int eval_buffers(u8_t *buf, u16_t buf_len, u8_t *expected, - u16_t len) +int eval_buffers(const struct buf_ctx *buf, const u8_t *expected, u16_t len) { - if (buf_len != len) { + if (buf->end - buf->cur != len) { goto exit_eval; } - if (memcmp(expected, buf, buf_len) != 0) { + if (memcmp(expected, buf->cur, buf->end - buf->cur) != 0) { goto exit_eval; } @@ -735,7 +669,7 @@ int eval_buffers(u8_t *buf, u16_t buf_len, u8_t *expected, exit_eval: TC_PRINT("FAIL\n"); TC_PRINT("Computed:"); - print_array(buf, buf_len); + print_array(buf->cur, buf->end - buf->cur); TC_PRINT("Expected:"); print_array(expected, len); @@ -744,262 +678,371 @@ int eval_buffers(u8_t *buf, u16_t buf_len, u8_t *expected, static int eval_msg_connect(struct mqtt_test *mqtt_test) { - struct mqtt_connect_msg *msg; - struct mqtt_connect_msg msg2; + struct mqtt_client *test_client; int rc; + struct buf_ctx buf; + + test_client = (struct mqtt_client *)mqtt_test->ctx; - msg = (struct mqtt_connect_msg *)mqtt_test->msg; + client.clean_session = test_client->clean_session; + client.client_id = test_client->client_id; + client.will_topic = test_client->will_topic; + client.will_retain = test_client->will_retain; + client.will_message = test_client->will_message; + client.user_name = test_client->user_name; + client.password = test_client->password; - rc = mqtt_pack_connect(buf, &buf_len, sizeof(buf), msg); + buf.cur = client.tx_buf; + buf.end = client.tx_buf + client.tx_buf_size; - /**TESTPOINTS: Check eval_msg_connect functions*/ - zassert_false(rc, "mqtt_pack_connect failed"); + rc = connect_request_encode(&client, &buf); - rc = eval_buffers(buf, buf_len, - mqtt_test->expected, mqtt_test->expected_len); + /**TESTPOINTS: Check connect_request_encode functions*/ + zassert_false(rc, "connect_request_encode failed"); + + rc = eval_buffers(&buf, mqtt_test->expected, mqtt_test->expected_len); zassert_false(rc, "eval_buffers failed"); - rc = mqtt_unpack_connect(buf, buf_len, &msg2); + return TC_PASS; +} - zassert_false(rc, "mqtt_unpack_connect failed"); +static int eval_msg_disconnect(struct mqtt_test *mqtt_test) +{ + int rc; + struct buf_ctx buf; - zassert_equal(msg->clean_session, msg2.clean_session, - "clean session check failed"); + buf.cur = client.tx_buf; + buf.end = client.tx_buf + client.tx_buf_size; - zassert_equal(test_strlen(msg->client_id), msg2.client_id_len, - "client ID length check failed"); + rc = disconnect_encode(&buf); - zassert_false(memcmp(msg->client_id, msg2.client_id, - msg2.client_id_len), - "client ID check failed"); + /**TESTPOINTS: Check disconnect_encode functions*/ + zassert_false(rc, "disconnect_encode failed"); - zassert_equal(msg->will_flag, msg2.will_flag, - "will flag error"); + rc = eval_buffers(&buf, mqtt_test->expected, mqtt_test->expected_len); - zassert_equal(msg->will_qos, msg2.will_qos, - "will QoS error"); + zassert_false(rc, "eval_buffers failed"); - zassert_equal(msg->will_retain, msg2.will_retain, - "will retain error"); + return TC_PASS; +} - zassert_equal(test_strlen(msg->will_topic), msg2.will_topic_len, - "will topic length error"); +static int eval_msg_publish(struct mqtt_test *mqtt_test) +{ + struct mqtt_publish_param *param = + (struct mqtt_publish_param *)mqtt_test->ctx; + struct mqtt_publish_param dec_param; + int rc; + u8_t type_and_flags; + u32_t length; + struct buf_ctx buf; - zassert_false(memcmp(msg->will_topic, msg2.will_topic, - msg2.will_topic_len), - "will topic error"); + memset(&dec_param, 0, sizeof(dec_param)); - zassert_equal(msg->will_msg_len, msg2.will_msg_len, - "will msg len error"); + buf.cur = client.tx_buf; + buf.end = client.tx_buf + client.tx_buf_size; - zassert_false(memcmp(msg->will_msg, msg2.will_msg, msg2.will_msg_len), - "will msg error"); + rc = publish_encode(param, &buf); - zassert_equal(msg->keep_alive, msg2.keep_alive, - "keep alive error"); + /* Payload is not copied, copy it manually just after the header.*/ + memcpy(buf.end, param->message.payload.data, + param->message.payload.len); + buf.end += param->message.payload.len; - zassert_equal(test_strlen(msg->user_name), msg2.user_name_len, - "username length error"); + /**TESTPOINT: Check publish_encode function*/ + zassert_false(rc, "publish_encode failed"); - zassert_false(memcmp(msg->user_name, msg2.user_name, - msg2.user_name_len), - "username error"); + rc = eval_buffers(&buf, mqtt_test->expected, mqtt_test->expected_len); - zassert_equal(msg->password_len, msg2.password_len, - "password length error"); + zassert_false(rc, "eval_buffers failed"); - zassert_false(memcmp(msg->password, msg2.password, msg2.password_len), - "password error"); + rc = fixed_header_decode(&buf, &type_and_flags, &length); + + zassert_false(rc, "fixed_header_decode failed"); + + rc = publish_decode(type_and_flags, length, &buf, &dec_param); + + /**TESTPOINT: Check publish_decode function*/ + zassert_false(rc, "publish_decode failed"); + + zassert_equal(dec_param.message_id, param->message_id, + "message_id error"); + zassert_equal(dec_param.dup_flag, param->dup_flag, + "dup flag error"); + zassert_equal(dec_param.retain_flag, param->retain_flag, + "retain flag error"); + zassert_equal(dec_param.message.topic.qos, param->message.topic.qos, + "topic qos error"); + zassert_equal(dec_param.message.topic.topic.size, + param->message.topic.topic.size, + "topic len error"); + if (memcmp(dec_param.message.topic.topic.utf8, + param->message.topic.topic.utf8, + dec_param.message.topic.topic.size) != 0) { + zassert_unreachable("topic content error"); + } + zassert_equal(dec_param.message.payload.len, + param->message.payload.len, + "payload len error"); return TC_PASS; } -static int eval_msg_disconnect(struct mqtt_test *mqtt_test) +static int eval_msg_subscribe(struct mqtt_test *mqtt_test) { + struct mqtt_subscription_list *param = + (struct mqtt_subscription_list *)mqtt_test->ctx; int rc; + struct buf_ctx buf; - rc = mqtt_pack_disconnect(buf, &buf_len, sizeof(buf)); + buf.cur = client.tx_buf; + buf.end = client.tx_buf + client.tx_buf_size; - /**TESTPOINTS: Check eval_msg_disconnect functions*/ - zassert_false(rc, "mqtt_pack_disconnect failed"); + rc = subscribe_encode(param, &buf); - rc = eval_buffers(buf, buf_len, - mqtt_test->expected, mqtt_test->expected_len); + /**TESTPOINT: Check subscribe_encode function*/ + zassert_false(rc, "subscribe_encode failed"); - zassert_false(rc, "eval_buffers failed"); + return eval_buffers(&buf, mqtt_test->expected, mqtt_test->expected_len); +} + +static int eval_msg_suback(struct mqtt_test *mqtt_test) +{ + struct mqtt_suback_param *param = + (struct mqtt_suback_param *)mqtt_test->ctx; + struct mqtt_suback_param dec_param; + + int rc; + u8_t type_and_flags; + u32_t length; + struct buf_ctx buf; + + buf.cur = mqtt_test->expected; + buf.end = mqtt_test->expected + mqtt_test->expected_len; + + memset(&dec_param, 0, sizeof(dec_param)); + + rc = fixed_header_decode(&buf, &type_and_flags, &length); + + zassert_false(rc, "fixed_header_decode failed"); - rc = mqtt_unpack_disconnect(buf, buf_len); + rc = subscribe_ack_decode(&buf, &dec_param); - zassert_false(rc, "mqtt_unpack_disconnect failed"); + /**TESTPOINT: Check subscribe_ack_decode function*/ + zassert_false(rc, "subscribe_ack_decode failed"); + + zassert_equal(dec_param.message_id, param->message_id, + "packet identifier error"); + zassert_equal(dec_param.return_codes.len, + param->return_codes.len, + "topic count error"); + if (memcmp(dec_param.return_codes.data, param->return_codes.data, + dec_param.return_codes.len) != 0) { + zassert_unreachable("subscribe result error"); + } return TC_PASS; } -static int eval_msg_publish(struct mqtt_test *mqtt_test) +static int eval_msg_pingreq(struct mqtt_test *mqtt_test) { - struct mqtt_publish_msg *msg; int rc; + struct buf_ctx buf; + + buf.cur = client.tx_buf; + buf.end = client.tx_buf + client.tx_buf_size; - msg = (struct mqtt_publish_msg *)mqtt_test->msg; + rc = ping_request_encode(&buf); - rc = mqtt_pack_publish(buf, &buf_len, sizeof(buf), msg); + /**TESTPOINTS: Check eval_msg_pingreq functions*/ + zassert_false(rc, "ping_request_encode failed"); + + rc = eval_buffers(&buf, mqtt_test->expected, mqtt_test->expected_len); - /**TESTPOINT: Check eval_msg_publish function*/ - zassert_false(rc, "mqtt_pack_publish failed"); + zassert_false(rc, "eval_buffers failed"); - return eval_buffers(buf, buf_len, - mqtt_test->expected, mqtt_test->expected_len); + return TC_PASS; } -static int eval_msg_subscribe(struct mqtt_test *mqtt_test) +static int eval_msg_puback(struct mqtt_test *mqtt_test) { - struct msg_subscribe *msg = (struct msg_subscribe *)mqtt_test->msg; + struct mqtt_puback_param *param = + (struct mqtt_puback_param *)mqtt_test->ctx; + struct mqtt_puback_param dec_param; int rc; + u8_t type_and_flags; + u32_t length; + struct buf_ctx buf; - rc = mqtt_pack_subscribe(buf, &buf_len, sizeof(buf), msg->pkt_id, - msg->items, msg->topics, msg->qos); + memset(&dec_param, 0, sizeof(dec_param)); - /**TESTPOINT: Check eval_msg_subscribe function*/ - zassert_false(rc, "mqtt_pack_subscribe failed"); + buf.cur = client.tx_buf; + buf.end = client.tx_buf + client.tx_buf_size; - return eval_buffers(buf, buf_len, - mqtt_test->expected, mqtt_test->expected_len); -} + rc = publish_ack_encode(param, &buf); -static int eval_msg_suback(struct mqtt_test *mqtt_test) -{ - struct msg_suback *msg = (struct msg_suback *)mqtt_test->msg; - int rc; + /**TESTPOINTS: Check publish_ack_encode functions*/ + zassert_false(rc, "publish_ack_encode failed"); - rc = mqtt_pack_suback(buf, &buf_len, sizeof(buf), - msg->pkt_id, msg->elements, msg->qos); + rc = eval_buffers(&buf, mqtt_test->expected, mqtt_test->expected_len); - /**TESTPOINT: Check eval_msg_suback function*/ - zassert_false(rc, "mqtt_pack_suback failed"); + zassert_false(rc, "eval_buffers failed"); + + rc = fixed_header_decode(&buf, &type_and_flags, &length); + + zassert_false(rc, "fixed_header_decode failed"); + + rc = publish_ack_decode(&buf, &dec_param); + + zassert_false(rc, "publish_ack_decode failed"); - return eval_buffers(buf, buf_len, - mqtt_test->expected, mqtt_test->expected_len); + zassert_equal(dec_param.message_id, param->message_id, + "packet identifier error"); + + return TC_PASS; } -static int eval_msg_pingreq(struct mqtt_test *mqtt_test) +static int eval_msg_pubcomp(struct mqtt_test *mqtt_test) { + struct mqtt_pubcomp_param *param = + (struct mqtt_pubcomp_param *)mqtt_test->ctx; + struct mqtt_pubcomp_param dec_param; int rc; + u32_t length; + u8_t type_and_flags; + struct buf_ctx buf; - rc = mqtt_pack_pingreq(buf, &buf_len, sizeof(buf)); + memset(&dec_param, 0, sizeof(dec_param)); - /**TESTPOINTS: Check eval_msg_pingreq functions*/ - zassert_false(rc, "mqtt_pack_pingreq failed"); + buf.cur = client.tx_buf; + buf.end = client.tx_buf + client.tx_buf_size; - rc = eval_buffers(buf, buf_len, - mqtt_test->expected, mqtt_test->expected_len); + rc = publish_complete_encode(param, &buf); + + /**TESTPOINTS: Check publish_complete_encode functions*/ + zassert_false(rc, "publish_complete_encode failed"); + + rc = eval_buffers(&buf, mqtt_test->expected, mqtt_test->expected_len); zassert_false(rc, "eval_buffers failed"); - rc = mqtt_unpack_pingreq(buf, buf_len); + rc = fixed_header_decode(&buf, &type_and_flags, &length); + + zassert_false(rc, "fixed_header_decode failed"); + + rc = publish_complete_decode(&buf, &dec_param); + + zassert_false(rc, "publish_complete_decode failed"); - zassert_false(rc, "mqtt_unpack_pingreq failed"); + zassert_equal(dec_param.message_id, param->message_id, + "packet identifier error"); return TC_PASS; } -static int eval_msg_pingresp(struct mqtt_test *mqtt_test) +static int eval_msg_pubrec(struct mqtt_test *mqtt_test) { + struct mqtt_pubrec_param *param = + (struct mqtt_pubrec_param *)mqtt_test->ctx; + struct mqtt_pubrec_param dec_param; int rc; + u32_t length; + u8_t type_and_flags; + struct buf_ctx buf; + + memset(&dec_param, 0, sizeof(dec_param)); + + buf.cur = client.tx_buf; + buf.end = client.tx_buf + client.tx_buf_size; - rc = mqtt_pack_pingresp(buf, &buf_len, sizeof(buf)); + rc = publish_receive_encode(param, &buf); - /**TESTPOINTS: Check evam_msg_pingresp functions*/ - zassert_false(rc, "mqtt_pack_pingresp failed"); + /**TESTPOINTS: Check publish_receive_encode functions*/ + zassert_false(rc, "publish_receive_encode failed"); - rc = eval_buffers(buf, buf_len, - mqtt_test->expected, mqtt_test->expected_len); + rc = eval_buffers(&buf, mqtt_test->expected, mqtt_test->expected_len); zassert_false(rc, "eval_buffers failed"); - rc = mqtt_unpack_pingresp(buf, buf_len); + rc = fixed_header_decode(&buf, &type_and_flags, &length); + + zassert_false(rc, "fixed_header_decode failed"); + + rc = publish_receive_decode(&buf, &dec_param); - zassert_false(rc, "mqtt_unpack_pingresp failed"); + zassert_false(rc, "publish_receive_decode failed"); + + zassert_equal(dec_param.message_id, param->message_id, + "packet identifier error"); return TC_PASS; } -static -int eval_msg_packet_id(struct mqtt_test *mqtt_test, enum mqtt_packet type) +static int eval_msg_pubrel(struct mqtt_test *mqtt_test) { - int (*pack)(u8_t *, u16_t *, u16_t, u16_t) = NULL; - int (*unpack)(u8_t *, u16_t, u16_t *) = NULL; - struct msg_pkt_id *msg = (struct msg_pkt_id *)mqtt_test->msg; - u16_t pkt_id; + struct mqtt_pubrel_param *param = + (struct mqtt_pubrel_param *)mqtt_test->ctx; + struct mqtt_pubrel_param dec_param; int rc; + u32_t length; + u8_t type_and_flags; + struct buf_ctx buf; - switch (type) { - case MQTT_PUBACK: - pack = mqtt_pack_puback; - unpack = mqtt_unpack_puback; - break; - case MQTT_PUBCOMP: - pack = mqtt_pack_pubcomp; - unpack = mqtt_unpack_pubcomp; - break; - case MQTT_PUBREC: - pack = mqtt_pack_pubrec; - unpack = mqtt_unpack_pubrec; - break; - case MQTT_PUBREL: - pack = mqtt_pack_pubrel; - unpack = mqtt_unpack_pubrel; - break; - case MQTT_UNSUBACK: - pack = mqtt_pack_unsuback; - unpack = mqtt_unpack_unsuback; - break; - default: - return TC_FAIL; - } + memset(&dec_param, 0, sizeof(dec_param)); + + buf.cur = client.tx_buf; + buf.end = client.tx_buf + client.tx_buf_size; - rc = pack(buf, &buf_len, sizeof(buf), msg->pkt_id); + rc = publish_release_encode(param, &buf); - /**TESTPOINTS: Check eval_msg_packet_id functions*/ - zassert_false(rc, "pack failed"); + /**TESTPOINTS: Check publish_release_encode functions*/ + zassert_false(rc, "publish_release_encode failed"); - rc = eval_buffers(buf, buf_len, - mqtt_test->expected, mqtt_test->expected_len); + rc = eval_buffers(&buf, mqtt_test->expected, mqtt_test->expected_len); zassert_false(rc, "eval_buffers failed"); - rc = unpack(buf, buf_len, &pkt_id); + rc = fixed_header_decode(&buf, &type_and_flags, &length); - zassert_false(rc, "unpack failed"); + zassert_false(rc, "fixed_header_decode failed"); - zassert_equal(pkt_id, msg->pkt_id, "packet identifier error"); + rc = publish_release_decode(&buf, &dec_param); + + zassert_false(rc, "publish_release_decode failed"); + + zassert_equal(dec_param.message_id, param->message_id, + "packet identifier error"); return TC_PASS; } -static int eval_msg_puback(struct mqtt_test *mqtt_test) +static int eval_msg_unsuback(struct mqtt_test *mqtt_test) { - return eval_msg_packet_id(mqtt_test, MQTT_PUBACK); -} + struct mqtt_unsuback_param *param = + (struct mqtt_unsuback_param *)mqtt_test->ctx; + struct mqtt_unsuback_param dec_param; + int rc; + u32_t length; + u8_t type_and_flags; + struct buf_ctx buf; -static int eval_msg_pubcomp(struct mqtt_test *mqtt_test) -{ - return eval_msg_packet_id(mqtt_test, MQTT_PUBCOMP); -} + memset(&dec_param, 0, sizeof(dec_param)); -static int eval_msg_pubrec(struct mqtt_test *mqtt_test) -{ - return eval_msg_packet_id(mqtt_test, MQTT_PUBREC); -} + buf.cur = mqtt_test->expected; + buf.end = mqtt_test->expected + mqtt_test->expected_len; -static int eval_msg_pubrel(struct mqtt_test *mqtt_test) -{ - return eval_msg_packet_id(mqtt_test, MQTT_PUBREL); -} + rc = fixed_header_decode(&buf, &type_and_flags, &length); -static int eval_msg_unsuback(struct mqtt_test *mqtt_test) -{ - return eval_msg_packet_id(mqtt_test, MQTT_UNSUBACK); + zassert_false(rc, "fixed_header_decode failed"); + + rc = unsubscribe_ack_decode(&buf, &dec_param); + + zassert_false(rc, "unsubscribe_ack_decode failed"); + + zassert_equal(dec_param.message_id, param->message_id, + "packet identifier error"); + + return TC_PASS; } void test_mqtt_packet(void) @@ -1009,6 +1052,13 @@ void test_mqtt_packet(void) int rc; int i; + mqtt_client_init(&client); + client.protocol_version = MQTT_VERSION_3_1_1; + client.rx_buf = rx_buffer; + client.rx_buf_size = sizeof(rx_buffer); + client.tx_buf = tx_buffer; + client.tx_buf_size = sizeof(tx_buffer); + i = 0; do { struct mqtt_test *test = &mqtt_tests[i]; @@ -1026,8 +1076,9 @@ void test_mqtt_packet(void) i++; } while (1); -} + mqtt_abort(&client); +} void test_main(void) { diff --git a/tests/net/lib/mqtt_publisher/CMakeLists.txt b/tests/net/lib/mqtt_publisher/CMakeLists.txt index e376dc5df1aea..fa5879dab3a5b 100644 --- a/tests/net/lib/mqtt_publisher/CMakeLists.txt +++ b/tests/net/lib/mqtt_publisher/CMakeLists.txt @@ -4,7 +4,6 @@ project(mqtt_publisher) target_include_directories(app PRIVATE $ENV{ZEPHYR_BASE}/subsys/net/ip - $ENV{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_publisher/prj_tls.conf b/tests/net/lib/mqtt_publisher/prj_tls.conf index ab5aa470efa0c..5a975f4ce4c0b 100644 --- a/tests/net/lib/mqtt_publisher/prj_tls.conf +++ b/tests/net/lib/mqtt_publisher/prj_tls.conf @@ -24,6 +24,7 @@ CONFIG_NET_IPV6=y # Enable the MQTT Lib CONFIG_MQTT_LIB=y CONFIG_MQTT_LIB_TLS=y +CONFIG_NET_SOCKETS_SOCKOPT_TLS=y CONFIG_NET_CONFIG_SETTINGS=y CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1" @@ -32,7 +33,7 @@ CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::2" CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.168.1.101" CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.168.1.10" -CONFIG_MAIN_STACK_SIZE=2048 +CONFIG_MAIN_STACK_SIZE=4096 # For IPv6 CONFIG_NET_BUF_DATA_SIZE=256 @@ -41,6 +42,5 @@ CONFIG_MBEDTLS=y CONFIG_MBEDTLS_BUILTIN=y CONFIG_MBEDTLS_ENABLE_HEAP=y CONFIG_MBEDTLS_HEAP_SIZE=30000 -CONFIG_MBEDTLS_CFG_FILE="config-mini-tls1_2.h" CONFIG_ZTEST=y diff --git a/tests/net/lib/mqtt_publisher/src/main.c b/tests/net/lib/mqtt_publisher/src/main.c index aa142ff621f24..38eb76767a842 100644 --- a/tests/net/lib/mqtt_publisher/src/main.c +++ b/tests/net/lib/mqtt_publisher/src/main.c @@ -15,7 +15,6 @@ extern void test_mqtt_disconnect(void); void test_main(void) { ztest_test_suite(mqtt_test, - ztest_unit_test(test_mqtt_init), ztest_unit_test(test_mqtt_connect), ztest_unit_test(test_mqtt_pingreq), ztest_unit_test(test_mqtt_publish), diff --git a/tests/net/lib/mqtt_publisher/src/test_mqtt_publish.c b/tests/net/lib/mqtt_publisher/src/test_mqtt_publish.c index 7631eaf2b1dee..680f1410bd91d 100644 --- a/tests/net/lib/mqtt_publisher/src/test_mqtt_publish.c +++ b/tests/net/lib/mqtt_publisher/src/test_mqtt_publish.c @@ -8,155 +8,142 @@ #define NET_LOG_LEVEL LOG_LEVEL_WRN #include +#include #include -#include -#include -#include - #include #include #include "config.h" -/* Container for some structures used by the MQTT publisher app. */ -struct mqtt_client_ctx { - /** - * The connect message structure is only used during the connect - * stage. Developers must set some msg properties before calling the - * mqtt_tx_connect routine. See below. - */ - struct mqtt_connect_msg connect_msg; - /** - * This is the message that will be received by the server - * (MQTT broker). - */ - struct mqtt_publish_msg pub_msg; - - /** - * This is the MQTT application context variable. - */ - struct mqtt_ctx mqtt_ctx; - - /** - * This variable will be passed to the connect callback, declared inside - * the mqtt context struct. If not used, it could be set to NULL. - */ - void *connect_data; - - /** - * This variable will be passed to the disconnect callback, declared - * inside the mqtt context struct. If not used, it could be set to NULL. - */ - void *disconnect_data; - - /** - * This variable will be passed to the publish_tx callback, declared - * inside the mqtt context struct. If not used, it could be set to NULL. - */ - void *publish_data; -}; - /* This is mqtt payload message. */ char payload[] = "DOORS:OPEN_QoSx"; -/* The mqtt client struct */ -static struct mqtt_client_ctx client_ctx; - -/* The signature of this routine must match the connect callback declared at - * the mqtt.h header. - */ -static void connect_cb(struct mqtt_ctx *mqtt_ctx) -{ - struct mqtt_client_ctx *client_ctx; +#define BUFFER_SIZE 128 - client_ctx = CONTAINER_OF(mqtt_ctx, struct mqtt_client_ctx, mqtt_ctx); +static u8_t rx_buffer[BUFFER_SIZE]; +static u8_t tx_buffer[BUFFER_SIZE]; +static struct mqtt_client client_ctx; +static struct sockaddr broker; +static struct pollfd fds[1]; +static int nfds; +static bool connected; - TC_PRINT("[%s:%d]", __func__, __LINE__); +static void broker_init(void) +{ +#if defined(CONFIG_NET_IPV6) + struct sockaddr_in6 *broker6 = net_sin6(&broker); + + broker6->sin6_family = AF_INET6; + broker6->sin6_port = htons(SERVER_PORT); + inet_pton(AF_INET6, SERVER_ADDR, &broker6->sin6_addr); +#else + struct sockaddr_in *broker4 = net_sin(&broker); + + broker4->sin_family = AF_INET; + broker4->sin_port = htons(SERVER_PORT); + inet_pton(AF_INET, SERVER_ADDR, &broker4->sin_addr); +#endif +} - if (client_ctx->connect_data) { - TC_PRINT(" user_data: %s", - (const char *)client_ctx->connect_data); +static void prepare_fds(struct mqtt_client *client) +{ + if (client->transport.type == MQTT_TRANSPORT_NON_SECURE) { + fds[0].fd = client->transport.tcp.sock; } - TC_PRINT("\n"); + fds[0].events = ZSOCK_POLLIN; + nfds = 1; } -/* The signature of this routine must match the disconnect callback declared at - * the mqtt.h header. - */ -static void disconnect_cb(struct mqtt_ctx *mqtt_ctx) +static void clear_fds(void) { - struct mqtt_client_ctx *client_ctx; - - client_ctx = CONTAINER_OF(mqtt_ctx, struct mqtt_client_ctx, mqtt_ctx); - - TC_PRINT("[%s:%d]", __func__, __LINE__); + nfds = 0; +} - if (client_ctx->disconnect_data) { - TC_PRINT(" user_data: %s", - (const char *)client_ctx->disconnect_data); +static void wait(int timeout) +{ + if (nfds > 0) { + if (poll(fds, nfds, timeout) < 0) { + TC_PRINT("poll error: %d\n", errno); + } } - - TC_PRINT("\n"); } -/** - * The signature of this routine must match the publish_tx callback declared at - * the mqtt.h header. - * - * NOTE: we have two callbacks for MQTT Publish related stuff: - * - publish_tx, for publishers - * - publish_rx, for subscribers - * - * Applications must keep a "message database" with pkt_id's. So far, this is - * not implemented here. For example, if we receive a PUBREC message with an - * unknown pkt_id, this routine must return an error, for example -EINVAL or - * any negative value. - */ -static int publish_cb(struct mqtt_ctx *mqtt_ctx, u16_t pkt_id, - enum mqtt_packet type) +void mqtt_evt_handler(struct mqtt_client *const client, + const struct mqtt_evt *evt) { - struct mqtt_client_ctx *client_ctx; - const char *str; - int rc = 0; + int err; + + switch (evt->type) { + case MQTT_EVT_CONNACK: + if (evt->result != 0) { + TC_PRINT("MQTT connect failed %d\n", evt->result); + break; + } - client_ctx = CONTAINER_OF(mqtt_ctx, struct mqtt_client_ctx, mqtt_ctx); + connected = true; + TC_PRINT("[%s:%d] MQTT_EVT_CONNACK: Connected!\n", + __func__, __LINE__); - switch (type) { - case MQTT_PUBACK: - str = "MQTT_PUBACK"; break; - case MQTT_PUBCOMP: - str = "MQTT_PUBCOMP"; + + case MQTT_EVT_DISCONNECT: + TC_PRINT("[%s:%d] MQTT_EVT_DISCONNECT: disconnected %d\n", + __func__, __LINE__, evt->result); + + connected = false; + clear_fds(); + break; - case MQTT_PUBREC: - str = "MQTT_PUBREC"; + + case MQTT_EVT_PUBACK: + if (evt->result != 0) { + TC_PRINT("MQTT PUBACK error %d\n", evt->result); + break; + } + + TC_PRINT("[%s:%d] MQTT_EVT_PUBACK packet id: %u\n", + __func__, __LINE__, evt->param.puback.message_id); + break; - default: - rc = -EINVAL; - str = "Invalid MQTT packet"; - } - TC_PRINT("[%s:%d] <%s> packet id: %u", __func__, __LINE__, str, pkt_id); + case MQTT_EVT_PUBREC: + if (evt->result != 0) { + TC_PRINT("MQTT PUBREC error %d\n", evt->result); + break; + } - if (client_ctx->publish_data) { - TC_PRINT(", user_data: %s", - (const char *)client_ctx->publish_data); - } + TC_PRINT("[%s:%d] MQTT_EVT_PUBREC packet id: %u\n", + __func__, __LINE__, evt->param.pubrec.message_id); - TC_PRINT("\n"); + const struct mqtt_pubrel_param rel_param = { + .message_id = evt->param.pubrec.message_id + }; - return rc; -} + err = mqtt_publish_qos2_release(client, &rel_param); + if (err != 0) { + TC_PRINT("Failed to send MQTT PUBREL: %d\n", + err); + } -/** - * The signature of this routine must match the malformed callback declared at - * the mqtt.h header. - */ -static void malformed_cb(struct mqtt_ctx *mqtt_ctx, u16_t pkt_type) -{ - TC_PRINT("[%s:%d] pkt_type: %u\n", __func__, __LINE__, pkt_type); + break; + + case MQTT_EVT_PUBCOMP: + if (evt->result != 0) { + TC_PRINT("MQTT PUBCOMP error %d\n", evt->result); + break; + } + + TC_PRINT("[%s:%d] MQTT_EVT_PUBCOMP packet id: %u\n", + __func__, __LINE__, evt->param.pubcomp.message_id); + + break; + + default: + TC_PRINT("[%s:%d] Invalid MQTT packet\n", __func__, __LINE__); + break; + } } static char *get_mqtt_payload(enum mqtt_qos qos) @@ -171,102 +158,76 @@ static char *get_mqtt_topic(void) return "sensors"; } -static void prepare_mqtt_publish_msg(struct mqtt_publish_msg *pub_msg, - enum mqtt_qos qos) +static void client_init(struct mqtt_client *client) { - /* MQTT message payload may be anything, we we use C strings */ - pub_msg->msg = (u8_t *)get_mqtt_payload(qos); - /* Payload's length */ - pub_msg->msg_len = (u16_t)strlen((char *)client_ctx.pub_msg.msg); - /* MQTT Quality of Service */ - pub_msg->qos = qos; - /* Message's topic */ - pub_msg->topic = get_mqtt_topic(); - pub_msg->topic_len = strlen(client_ctx.pub_msg.topic); - /* Packet Identifier, always use different values */ - pub_msg->pkt_id = sys_rand32_get(); + mqtt_client_init(client); + + broker_init(); + + /* MQTT client configuration */ + client->broker = &broker; + client->evt_cb = mqtt_evt_handler; + client->client_id.utf8 = (uint8_t *)MQTT_CLIENTID; + client->client_id.size = strlen(MQTT_CLIENTID); + client->password = NULL; + client->user_name = NULL; + client->protocol_version = MQTT_VERSION_3_1_1; + client->transport.type = MQTT_TRANSPORT_NON_SECURE; + + client->rx_buf = rx_buffer; + client->rx_buf_size = sizeof(rx_buffer); + client->tx_buf = tx_buffer; + client->tx_buf_size = sizeof(tx_buffer); } -#define RC_STR(rc) ((rc) == 0 ? "OK" : "ERROR") - -#define PRINT_RESULT(func, rc) \ - TC_PRINT("[%s:%d] %s: %d <%s>\n", __func__, __LINE__, \ - (func), rc, RC_STR(rc)) +static int publish(enum mqtt_qos qos) +{ + struct mqtt_publish_param param; + + param.message.topic.qos = qos; + param.message.topic.topic.utf8 = (uint8_t *)get_mqtt_topic(); + param.message.topic.topic.size = + strlen(param.message.topic.topic.utf8); + param.message.payload.data = get_mqtt_payload(qos); + param.message.payload.len = + strlen(param.message.payload.data); + param.message_id = sys_rand32_get(); + param.dup_flag = 0; + param.retain_flag = 0; + + return mqtt_publish(&client_ctx, ¶m); +} /* In this routine we block until the connected variable is 1 */ -static int try_to_connect(struct mqtt_client_ctx *client_ctx) +static int try_to_connect(struct mqtt_client *client) { - int i = 0; + int rc, i = 0; + + while (i++ < APP_CONNECT_TRIES && !connected) { - while (i++ < APP_CONNECT_TRIES && !client_ctx->mqtt_ctx.connected) { - int rc; + client_init(&client_ctx); - rc = mqtt_tx_connect(&client_ctx->mqtt_ctx, - &client_ctx->connect_msg); - k_sleep(APP_SLEEP_MSECS); + rc = mqtt_connect(client); if (rc != 0) { + k_sleep(APP_SLEEP_MSECS); continue; } - } - - if (client_ctx->mqtt_ctx.connected) { - return TC_PASS; - } - - return TC_FAIL; -} - -static int init_network(void) -{ - int rc; - - /* Set everything to 0 and later just assign the required fields. */ - (void)memset(&client_ctx, 0x00, sizeof(client_ctx)); - - /* connect, disconnect and malformed may be set to NULL */ - client_ctx.mqtt_ctx.connect = connect_cb; - client_ctx.mqtt_ctx.disconnect = disconnect_cb; - client_ctx.mqtt_ctx.malformed = malformed_cb; + prepare_fds(client); - client_ctx.mqtt_ctx.net_init_timeout = APP_NET_INIT_TIMEOUT; - client_ctx.mqtt_ctx.net_timeout = APP_TX_RX_TIMEOUT; + wait(APP_SLEEP_MSECS); + mqtt_input(client); - client_ctx.mqtt_ctx.peer_addr_str = SERVER_ADDR; - client_ctx.mqtt_ctx.peer_port = SERVER_PORT; - - /* Publisher apps TX the MQTT PUBLISH msg */ - client_ctx.mqtt_ctx.publish_tx = publish_cb; - - /* The connect message will be sent to the MQTT server (broker). - * If clean_session here is 0, the mqtt_ctx clean_session variable - * will be set to 0 also. Please don't do that, set always to 1. - * Clean session = 0 is not yet supported. - */ - client_ctx.connect_msg.client_id = MQTT_CLIENTID; - client_ctx.connect_msg.client_id_len = strlen(MQTT_CLIENTID); - client_ctx.connect_msg.clean_session = 1; - - client_ctx.connect_data = "CONNECTED"; - client_ctx.disconnect_data = "DISCONNECTED"; - client_ctx.publish_data = "PUBLISH"; - - rc = mqtt_init(&client_ctx.mqtt_ctx, MQTT_APP_PUBLISHER); - if (rc != 0) { - goto exit_app; + if (!connected) { + mqtt_abort(client); + } } - rc = mqtt_connect(&client_ctx.mqtt_ctx); - if (rc != 0) { - goto exit_app; + if (connected) { + return 0; } - return TC_PASS; - -exit_app: - mqtt_close(&client_ctx.mqtt_ctx); - - return TC_FAIL; + return -EINVAL; } static int test_connect(void) @@ -279,19 +240,20 @@ static int test_connect(void) } return TC_PASS; - } static int test_pingreq(void) { int rc; - rc = mqtt_tx_pingreq(&client_ctx.mqtt_ctx); - k_sleep(APP_SLEEP_MSECS); + rc = mqtt_ping(&client_ctx); if (rc != 0) { return TC_FAIL; } + wait(APP_SLEEP_MSECS); + mqtt_input(&client_ctx); + return TC_PASS; } @@ -299,13 +261,20 @@ static int test_publish(enum mqtt_qos qos) { int rc; - prepare_mqtt_publish_msg(&client_ctx.pub_msg, qos); - rc = mqtt_tx_publish(&client_ctx.mqtt_ctx, &client_ctx.pub_msg); - k_sleep(APP_SLEEP_MSECS); + rc = publish(qos); if (rc != 0) { return TC_FAIL; } + wait(APP_SLEEP_MSECS); + mqtt_input(&client_ctx); + + /* Second input handle for expected Publish Complete response. */ + if (qos == MQTT_QOS_2_EXACTLY_ONCE) { + wait(APP_SLEEP_MSECS); + mqtt_input(&client_ctx); + } + return TC_PASS; } @@ -313,17 +282,15 @@ static int test_disconnect(void) { int rc; - rc = mqtt_tx_disconnect(&client_ctx.mqtt_ctx); + rc = mqtt_disconnect(&client_ctx); if (rc != 0) { return TC_FAIL; } - return TC_PASS; -} + wait(APP_SLEEP_MSECS); + mqtt_input(&client_ctx); -void test_mqtt_init(void) -{ - zassert_true(init_network() == TC_PASS, NULL); + return TC_PASS; } void test_mqtt_connect(void) @@ -338,9 +305,9 @@ void test_mqtt_pingreq(void) void test_mqtt_publish(void) { - zassert_true(test_publish(MQTT_QoS0) == TC_PASS, NULL); - zassert_true(test_publish(MQTT_QoS1) == TC_PASS, NULL); - zassert_true(test_publish(MQTT_QoS2) == TC_PASS, NULL); + zassert_true(test_publish(MQTT_QOS_0_AT_MOST_ONCE) == TC_PASS, NULL); + zassert_true(test_publish(MQTT_QOS_1_AT_LEAST_ONCE) == TC_PASS, NULL); + zassert_true(test_publish(MQTT_QOS_2_EXACTLY_ONCE) == TC_PASS, NULL); } void test_mqtt_disconnect(void) diff --git a/tests/net/lib/mqtt_pubsub/CMakeLists.txt b/tests/net/lib/mqtt_pubsub/CMakeLists.txt new file mode 100644 index 0000000000000..122dc8b45cc70 --- /dev/null +++ b/tests/net/lib/mqtt_pubsub/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.8.2) +include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE) +project(mqtt_subscriber) + +target_include_directories(app PRIVATE + $ENV{ZEPHYR_BASE}/subsys/net/ip + ) +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/net/lib/mqtt_pubsub/prj.conf b/tests/net/lib/mqtt_pubsub/prj.conf new file mode 100644 index 0000000000000..368a9c283ddb3 --- /dev/null +++ b/tests/net/lib/mqtt_pubsub/prj.conf @@ -0,0 +1,34 @@ +CONFIG_NETWORKING=y +CONFIG_NET_TCP=y +CONFIG_ENTROPY_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_NET_LOG=y +CONFIG_INIT_STACKS=y + +CONFIG_NET_IPV6_RA_RDNSS=y +CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=3 +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=2 + +CONFIG_STDOUT_CONSOLE=y + +# Enable IPv6 support +CONFIG_NET_IPV6=n +# Enable IPv4 support +CONFIG_NET_IPV4=y + +# Enable the MQTT Lib +CONFIG_MQTT_LIB=y + +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1" +CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::2" + +CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1" +CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.0.2.2" + +CONFIG_MAIN_STACK_SIZE=2048 + +# For IPv6 +CONFIG_NET_BUF_DATA_SIZE=256 + +CONFIG_ZTEST=y diff --git a/tests/net/lib/mqtt_pubsub/src/config.h b/tests/net/lib/mqtt_pubsub/src/config.h new file mode 100644 index 0000000000000..798341a9286b8 --- /dev/null +++ b/tests/net/lib/mqtt_pubsub/src/config.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __CONFIG_H__ +#define __CONFIG_H__ + +#ifdef CONFIG_NET_CONFIG_SETTINGS +#ifdef CONFIG_NET_IPV6 +#define ZEPHYR_ADDR CONFIG_NET_CONFIG_MY_IPV6_ADDR +#define SERVER_ADDR CONFIG_NET_CONFIG_PEER_IPV6_ADDR +#else +#define ZEPHYR_ADDR CONFIG_NET_CONFIG_MY_IPV4_ADDR +#define SERVER_ADDR CONFIG_NET_CONFIG_PEER_IPV4_ADDR +#endif +#else +#ifdef CONFIG_NET_IPV6 +#define ZEPHYR_ADDR "2001:db8::1" +#define SERVER_ADDR "2001:db8::2" +#else +#define ZEPHYR_ADDR "192.168.1.101" +#define SERVER_ADDR "192.168.1.10" +#endif +#endif + +#define SERVER_PORT 1883 + +#define APP_SLEEP_MSECS 500 +#define APP_TX_RX_TIMEOUT 300 +#define APP_NET_INIT_TIMEOUT 1000 + +#define APP_CONNECT_TRIES 10 + +#define APP_MAX_ITERATIONS 100 + +#define MQTT_CLIENTID "zephyr_pubsub" + +#endif diff --git a/tests/net/lib/mqtt_pubsub/src/main.c b/tests/net/lib/mqtt_pubsub/src/main.c new file mode 100644 index 0000000000000..380823abe4da8 --- /dev/null +++ b/tests/net/lib/mqtt_pubsub/src/main.c @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +extern void test_mqtt_connect(void); +extern void test_mqtt_subscribe(void); +extern void test_mqtt_publish_short(void); +extern void test_mqtt_publish_long(void); +extern void test_mqtt_unsubscribe(void); +extern void test_mqtt_disconnect(void); + +void test_main(void) +{ + ztest_test_suite(mqtt_test, + ztest_unit_test(test_mqtt_connect), + ztest_unit_test(test_mqtt_subscribe), + ztest_unit_test(test_mqtt_publish_short), + ztest_unit_test(test_mqtt_publish_long), + ztest_unit_test(test_mqtt_unsubscribe), + ztest_unit_test(test_mqtt_disconnect)); + ztest_run_test_suite(mqtt_test); +} diff --git a/tests/net/lib/mqtt_pubsub/src/test_mqtt_pubsub.c b/tests/net/lib/mqtt_pubsub/src/test_mqtt_pubsub.c new file mode 100644 index 0000000000000..fcadcde9292e5 --- /dev/null +++ b/tests/net/lib/mqtt_pubsub/src/test_mqtt_pubsub.c @@ -0,0 +1,462 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define LOG_MODULE_NAME net_test +#define NET_LOG_LEVEL LOG_LEVEL_WRN + +#include +#include +#include + +#include +#include + +#include "../../mqtt_pubsub/src/config.h" + +#define BUFFER_SIZE 32 + +static u8_t rx_buffer[BUFFER_SIZE]; +static u8_t tx_buffer[BUFFER_SIZE]; +static struct mqtt_client client_ctx; +static struct sockaddr broker; +static struct pollfd fds[1]; +static int nfds; +static bool connected; +static int payload_left; +static const u8_t *payload; + +static const u8_t payload_short[] = "Short payload"; + +/* Generated by http://www.lipsum.com/ + * 2 paragraphs, 171 words, 1210 bytes of Lorem Ipsum + */ +static const u8_t payload_long[] = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse " + "eleifend odio sit amet scelerisque tincidunt. In orci ligula, egestas " + "ut neque sit amet, fringilla malesuada dolor. Fusce rhoncus nunc in " + "lacus tincidunt lobortis. Donec aliquam lectus gravida fermentum " + "egestas. Curabitur eu ex pretium, dapibus massa in, pretium tellus. " + "Suspendisse ac efficitur magna, ut convallis nisl. Duis sed sapien " + "odio. Aliquam efficitur sed tellus sit amet eleifend. Sed facilisis " + "ligula aliquam erat ornare hendrerit. Aenean tincidunt nunc a nulla " + "vestibulum mollis. Vivamus fringilla euismod nisi sit amet malesuada. " + "Vivamus consequat ultricies metus sed feugiat. Aenean malesuada " + "cursus sem." + "\n" + "Vestibulum tempor diam et aliquam tristique. Nullam condimentum felis " + "et convallis finibus. Aliquam erat volutpat. Nam et blandit tortor. " + "Nullam pharetra arcu aliquam, suscipit mi eu, faucibus lacus. " + "Pellentesque eleifend nulla sit amet purus maximus, et elementum " + "lectus fringilla. Praesent consectetur diam eget tellus molestie " + "tempor. Class aptent taciti sociosqu ad litora torquent per conubia " + "nostra, per inceptos himenaeos. Nam tincidunt urna augue, nec cursus " + "arcu eleifend nec. Donec semper tellus in leo nullam." + "\n"; + +static void broker_init(void) +{ +#if defined(CONFIG_NET_IPV6) + struct sockaddr_in6 *broker6 = net_sin6(&broker); + + broker6->sin6_family = AF_INET6; + broker6->sin6_port = htons(SERVER_PORT); + inet_pton(AF_INET6, SERVER_ADDR, &broker6->sin6_addr); +#else + struct sockaddr_in *broker4 = net_sin(&broker); + + broker4->sin_family = AF_INET; + broker4->sin_port = htons(SERVER_PORT); + inet_pton(AF_INET, SERVER_ADDR, &broker4->sin_addr); +#endif +} + +static void prepare_fds(struct mqtt_client *client) +{ + if (client->transport.type == MQTT_TRANSPORT_NON_SECURE) { + fds[0].fd = client->transport.tcp.sock; + } + + fds[0].events = ZSOCK_POLLIN; + nfds = 1; +} + +static void clear_fds(void) +{ + nfds = 0; +} + +static void wait(int timeout) +{ + if (nfds > 0) { + if (poll(fds, nfds, timeout) < 0) { + TC_PRINT("poll error: %d\n", errno); + } + } +} + +void publish_handler(struct mqtt_client *const client, + const struct mqtt_evt *evt) +{ + int rc; + u8_t buf[16]; + u32_t offset = 0; + + if (evt->result != 0) { + TC_PRINT("MQTT PUBLISH error: %d\n", evt->result); + goto error; + } + + if (payload_left != evt->param.publish.message.payload.len) { + TC_PRINT("Invalid payload length: %d\n", + evt->param.publish.message.payload.len); + goto error; + } + + while (payload_left > 0) { + wait(APP_SLEEP_MSECS); + rc = mqtt_read_publish_payload(client, buf, sizeof(buf)); + if (rc <= 0 && rc != -EAGAIN) { + TC_PRINT("Failed to receive payload, err: %d\n", -rc); + goto error; + } + + if (memcmp(payload + offset, buf, rc) != 0) { + TC_PRINT("Invalid payload content\n"); + goto error; + } + + payload_left -= rc; + offset += rc; + } + + return; + +error: + payload_left = -1; +} + +void mqtt_evt_handler(struct mqtt_client *const client, + const struct mqtt_evt *evt) +{ + int err; + + switch (evt->type) { + case MQTT_EVT_CONNACK: + if (evt->result != 0) { + TC_PRINT("MQTT connect failed %d\n", evt->result); + break; + } + + connected = true; + TC_PRINT("[%s:%d] MQTT_EVT_CONNACK: Connected!\n", + __func__, __LINE__); + + break; + + case MQTT_EVT_DISCONNECT: + TC_PRINT("[%s:%d] MQTT_EVT_DISCONNECT: disconnected %d\n", + __func__, __LINE__, evt->result); + + connected = false; + payload_left = -1; + clear_fds(); + + break; + + case MQTT_EVT_PUBLISH: + publish_handler(client, evt); + + break; + + case MQTT_EVT_PUBACK: + if (evt->result != 0) { + TC_PRINT("MQTT PUBACK error %d\n", evt->result); + payload_left = -1; + break; + } + + TC_PRINT("[%s:%d] MQTT_EVT_PUBACK packet id: %u\n", + __func__, __LINE__, evt->param.puback.message_id); + + break; + + case MQTT_EVT_PUBREC: + if (evt->result != 0) { + TC_PRINT("MQTT PUBREC error %d\n", evt->result); + break; + } + + TC_PRINT("[%s:%d] MQTT_EVT_PUBREC packet id: %u\n", + __func__, __LINE__, evt->param.pubrec.message_id); + + const struct mqtt_pubrel_param rel_param = { + .message_id = evt->param.pubrec.message_id + }; + + err = mqtt_publish_qos2_release(client, &rel_param); + if (err != 0) { + TC_PRINT("Failed to send MQTT PUBREL: %d\n", + err); + } + + break; + + case MQTT_EVT_PUBCOMP: + if (evt->result != 0) { + TC_PRINT("MQTT PUBCOMP error %d\n", evt->result); + break; + } + + TC_PRINT("[%s:%d] MQTT_EVT_PUBCOMP packet id: %u\n", + __func__, __LINE__, evt->param.pubcomp.message_id); + + break; + + case MQTT_EVT_SUBACK: + if (evt->result != 0) { + TC_PRINT("MQTT SUBACK error %d\n", evt->result); + break; + } + + + TC_PRINT("[%s:%d] items: %d packet id: %u\n", __func__, + __LINE__, evt->param.suback.return_codes.len, + evt->param.suback.message_id); + + + break; + + case MQTT_EVT_UNSUBACK: + if (evt->result != 0) { + TC_PRINT("MQTT UNSUBACK error %d\n", evt->result); + break; + } + + TC_PRINT("[%s:%d] packet id: %u\n", __func__, __LINE__, + evt->param.unsuback.message_id); + + break; + + default: + TC_PRINT("[%s:%d] Invalid MQTT packet\n", __func__, __LINE__); + break; + } +} + +static char *get_mqtt_topic(void) +{ + return "sensors"; +} + +static void client_init(struct mqtt_client *client) +{ + mqtt_client_init(client); + + broker_init(); + + /* MQTT client configuration */ + client->broker = &broker; + client->evt_cb = mqtt_evt_handler; + client->client_id.utf8 = (u8_t *)MQTT_CLIENTID; + client->client_id.size = strlen(MQTT_CLIENTID); + client->password = NULL; + client->user_name = NULL; + client->protocol_version = MQTT_VERSION_3_1_1; + client->transport.type = MQTT_TRANSPORT_NON_SECURE; + + client->rx_buf = rx_buffer; + client->rx_buf_size = sizeof(rx_buffer); + client->tx_buf = tx_buffer; + client->tx_buf_size = sizeof(tx_buffer); +} + +/* In this routine we block until the connected variable is 1 */ +static int try_to_connect(struct mqtt_client *client) +{ + int rc, i = 0; + + while (i++ < APP_CONNECT_TRIES && !connected) { + + client_init(&client_ctx); + + rc = mqtt_connect(client); + if (rc != 0) { + k_sleep(APP_SLEEP_MSECS); + continue; + } + + prepare_fds(client); + + wait(APP_SLEEP_MSECS); + mqtt_input(client); + + if (!connected) { + mqtt_abort(client); + } + } + + if (connected) { + return 0; + } + + return -EINVAL; +} + +static int test_connect(void) +{ + int rc; + + rc = try_to_connect(&client_ctx); + if (rc != 0) { + return TC_FAIL; + } + + return TC_PASS; +} + +static int test_subscribe(void) +{ + int rc; + struct mqtt_topic topic; + struct mqtt_subscription_list sub; + + topic.topic.utf8 = get_mqtt_topic(); + topic.topic.size = strlen(topic.topic.utf8); + topic.qos = MQTT_QOS_1_AT_LEAST_ONCE; + sub.list = &topic; + sub.list_count = 1; + sub.message_id = sys_rand32_get(); + + rc = mqtt_subscribe(&client_ctx, &sub); + if (rc != 0) { + return TC_FAIL; + } + + wait(APP_SLEEP_MSECS); + rc = mqtt_input(&client_ctx); + if (rc != 0) { + return TC_FAIL; + } + + return TC_PASS; +} + +static int test_publish(enum mqtt_qos qos) +{ + int rc; + struct mqtt_publish_param param; + + payload_left = strlen(payload); + + param.message.topic.qos = qos; + param.message.topic.topic.utf8 = (u8_t *)get_mqtt_topic(); + param.message.topic.topic.size = + strlen(param.message.topic.topic.utf8); + param.message.payload.data = (u8_t *)payload; + param.message.payload.len = payload_left; + param.message_id = sys_rand32_get(); + param.dup_flag = 0; + param.retain_flag = 0; + + rc = mqtt_publish(&client_ctx, ¶m); + if (rc != 0) { + return TC_FAIL; + } + + while (payload_left > 0) { + wait(APP_SLEEP_MSECS); + rc = mqtt_input(&client_ctx); + if (rc != 0) { + return TC_FAIL; + } + } + + if (payload_left != 0) { + return TC_FAIL; + } + + return TC_PASS; +} + +static int test_unsubscribe(void) +{ + int rc; + struct mqtt_topic topic; + struct mqtt_subscription_list unsub; + + topic.topic.utf8 = get_mqtt_topic(); + topic.topic.size = strlen(topic.topic.utf8); + unsub.list = &topic; + unsub.list_count = 1; + unsub.message_id = sys_rand32_get(); + + rc = mqtt_unsubscribe(&client_ctx, &unsub); + if (rc != 0) { + return TC_FAIL; + } + + wait(APP_SLEEP_MSECS); + rc = mqtt_input(&client_ctx); + if (rc != 0) { + return TC_FAIL; + } + + return TC_PASS; +} + +static int test_disconnect(void) +{ + int rc; + + rc = mqtt_disconnect(&client_ctx); + if (rc != 0) { + return TC_FAIL; + } + + wait(APP_SLEEP_MSECS); + rc = mqtt_input(&client_ctx); + if (rc != 0) { + return TC_FAIL; + } + + if (connected) { + return TC_FAIL; + } + + return TC_PASS; +} + +void test_mqtt_connect(void) +{ + zassert_true(test_connect() == TC_PASS, NULL); +} + +void test_mqtt_subscribe(void) +{ + zassert_true(test_subscribe() == TC_PASS, NULL); +} + +void test_mqtt_publish_short(void) +{ + payload = payload_short; + zassert_true(test_publish(MQTT_QOS_0_AT_MOST_ONCE) == TC_PASS, NULL); +} + +void test_mqtt_publish_long(void) +{ + payload = payload_long; + zassert_true(test_publish(MQTT_QOS_1_AT_LEAST_ONCE) == TC_PASS, NULL); +} + +void test_mqtt_unsubscribe(void) +{ + zassert_true(test_unsubscribe() == TC_PASS, NULL); +} + +void test_mqtt_disconnect(void) +{ + zassert_true(test_disconnect() == TC_PASS, NULL); +} diff --git a/tests/net/lib/mqtt_pubsub/testcase.yaml b/tests/net/lib/mqtt_pubsub/testcase.yaml new file mode 100644 index 0000000000000..660adf5c3a64f --- /dev/null +++ b/tests/net/lib/mqtt_pubsub/testcase.yaml @@ -0,0 +1,8 @@ +common: + depends_on: netif + platform_whitelist: native_posix qemu_x86 qemu_cortex_m3 +tests: + net.mqtt.pubsub: + min_ram: 16 + tags: net mqtt + harness: net diff --git a/tests/net/lib/mqtt_subscriber/CMakeLists.txt b/tests/net/lib/mqtt_subscriber/CMakeLists.txt index ba919694eb9f2..122dc8b45cc70 100644 --- a/tests/net/lib/mqtt_subscriber/CMakeLists.txt +++ b/tests/net/lib/mqtt_subscriber/CMakeLists.txt @@ -4,7 +4,6 @@ project(mqtt_subscriber) target_include_directories(app PRIVATE $ENV{ZEPHYR_BASE}/subsys/net/ip - $ENV{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_subscriber/src/main.c b/tests/net/lib/mqtt_subscriber/src/main.c index 25a3e87ae1e4d..96839d0a0af07 100644 --- a/tests/net/lib/mqtt_subscriber/src/main.c +++ b/tests/net/lib/mqtt_subscriber/src/main.c @@ -15,7 +15,6 @@ extern void test_mqtt_disconnect(void); void test_main(void) { ztest_test_suite(mqtt_test, - ztest_unit_test(test_mqtt_init), ztest_unit_test(test_mqtt_connect), ztest_unit_test(test_mqtt_subscribe), ztest_unit_test(test_mqtt_unsubscribe), diff --git a/tests/net/lib/mqtt_subscriber/src/test_mqtt_subscribe.c b/tests/net/lib/mqtt_subscriber/src/test_mqtt_subscribe.c index fcecd25cd0ff7..6270d1eb19b57 100644 --- a/tests/net/lib/mqtt_subscriber/src/test_mqtt_subscribe.c +++ b/tests/net/lib/mqtt_subscriber/src/test_mqtt_subscribe.c @@ -8,303 +8,223 @@ #define NET_LOG_LEVEL LOG_LEVEL_WRN #include +#include #include -#include -#include - -#include #include #include #include "config.h" -/* Container for some structures used by the MQTT subscriber app. */ -struct mqtt_client_ctx { - /** - * The connect message structure is only used during the connect - * stage. Developers must set some msg properties before calling the - * mqtt_tx_connect routine. See below. - */ - struct mqtt_connect_msg connect_msg; - /** - * This is the message that will be received by the server - * (MQTT broker). - */ - struct mqtt_publish_msg pub_msg; - - /** - * This is the MQTT application context variable. - */ - struct mqtt_ctx mqtt_ctx; - - /** - * This variable will be passed to the connect callback, declared inside - * the mqtt context struct. If not used, it could be set to NULL. - */ - void *connect_data; - - /** - * This variable will be passed to the disconnect callback, declared - * inside the mqtt context struct. If not used, it could be set to NULL. - */ - void *disconnect_data; - - /** - * This variable will be passed to the subscribe_tx callback, declared - * inside the mqtt context struct. If not used, it could be set to NULL. - */ - void *subscribe_data; - - /** - * This variable will be passed to the unsubscribe_tx callback, declared - * inside the mqtt context struct. If not used, it could be set to NULL. - */ - void *unsubscribe_data; -}; - -/* The mqtt client struct */ -static struct mqtt_client_ctx client_ctx; - -/* The signature of this routine must match the connect callback declared at - * the mqtt.h header. - */ -static void connect_cb(struct mqtt_ctx *mqtt_ctx) -{ - struct mqtt_client_ctx *client_ctx; +#define BUFFER_SIZE 128 - client_ctx = CONTAINER_OF(mqtt_ctx, struct mqtt_client_ctx, mqtt_ctx); +static u8_t rx_buffer[BUFFER_SIZE]; +static u8_t tx_buffer[BUFFER_SIZE]; +static struct mqtt_client client_ctx; +static struct sockaddr broker; +static struct pollfd fds[1]; +static int nfds; +static bool connected; - printk("[%s:%d]", __func__, __LINE__); +static void broker_init(void) +{ +#if defined(CONFIG_NET_IPV6) + struct sockaddr_in6 *broker6 = net_sin6(&broker); + + broker6->sin6_family = AF_INET6; + broker6->sin6_port = htons(SERVER_PORT); + inet_pton(AF_INET6, SERVER_ADDR, &broker6->sin6_addr); +#else + struct sockaddr_in *broker4 = net_sin(&broker); + + broker4->sin_family = AF_INET; + broker4->sin_port = htons(SERVER_PORT); + inet_pton(AF_INET, SERVER_ADDR, &broker4->sin_addr); +#endif +} - if (client_ctx->connect_data) { - printk(" user_data: %s", - (const char *)client_ctx->connect_data); +static void prepare_fds(struct mqtt_client *client) +{ + if (client->transport.type == MQTT_TRANSPORT_NON_SECURE) { + fds[0].fd = client->transport.tcp.sock; } - printk("\n"); + fds[0].events = ZSOCK_POLLIN; + nfds = 1; } -/* The signature of this routine must match the disconnect callback declared at - * the mqtt.h header. - */ -static void disconnect_cb(struct mqtt_ctx *mqtt_ctx) +static void clear_fds(void) { - struct mqtt_client_ctx *client_ctx; - - client_ctx = CONTAINER_OF(mqtt_ctx, struct mqtt_client_ctx, mqtt_ctx); - - printk("[%s:%d]", __func__, __LINE__); + nfds = 0; +} - if (client_ctx->disconnect_data) { - printk(" user_data: %s", - (const char *)client_ctx->disconnect_data); +static void wait(int timeout) +{ + if (nfds > 0) { + if (poll(fds, nfds, timeout) < 0) { + TC_PRINT("poll error: %d\n", errno); + } } - - printk("\n"); } -/** - * The signature of this routine must match the publish_rx callback declared at - * the mqtt.h header. - * - * NOTE: we have two callbacks for MQTT Publish related stuff: - * - publish_tx, for publishers - * - publish_rx, for subscribers - * - * Applications must keep a "message database" with pkt_id's. So far, this is - * not implemented here. For example, if we receive a PUBREC message with an - * unknown pkt_id, this routine must return an error, for example -EINVAL or - * any negative value. - */ -static int publish_rx_cb(struct mqtt_ctx *mqtt_ctx, struct mqtt_publish_msg - *msg, u16_t pkt_id, enum mqtt_packet type) +void mqtt_evt_handler(struct mqtt_client *const client, + const struct mqtt_evt *evt) { - struct mqtt_client_ctx *client_ctx; - const char *str; - int rc = 0; + int err; + + switch (evt->type) { + case MQTT_EVT_CONNACK: + if (evt->result != 0) { + TC_PRINT("MQTT connect failed %d\n", evt->result); + break; + } - client_ctx = CONTAINER_OF(mqtt_ctx, struct mqtt_client_ctx, mqtt_ctx); + connected = true; + TC_PRINT("[%s:%d] MQTT_EVT_CONNACK: Connected!\n", + __func__, __LINE__); - switch (type) { - case MQTT_PUBLISH: - str = "MQTT_PUBLISH"; - printk("[%s:%d] <%s> msg: %s", __func__, __LINE__, - str, msg->msg); break; - case MQTT_PUBREL: - str = "MQTT_PUBREL"; - printk("[%s:%d] <%s> packet id: %u", __func__, __LINE__, - str, pkt_id); - return 0; - default: - rc = -EINVAL; - str = "Invalid MQTT packet"; - } - if (client_ctx->subscribe_data) { - printk(", user_data: %s", - (const char *)client_ctx->subscribe_data); - } + case MQTT_EVT_DISCONNECT: + TC_PRINT("[%s:%d] MQTT_EVT_DISCONNECT: disconnected %d\n", + __func__, __LINE__, evt->result); - printk("\n"); + connected = false; + clear_fds(); - return rc; -} + break; -/** - * The signature of this routine must match the subscribe callback declared at - * the mqtt.h header. - */ -static int subscriber_cb(struct mqtt_ctx *mqtt_ctx, u16_t pkt_id, - u8_t items, enum mqtt_qos qos[]) -{ - struct mqtt_client_ctx *client_ctx; + case MQTT_EVT_PUBACK: + if (evt->result != 0) { + TC_PRINT("MQTT PUBACK error %d\n", evt->result); + break; + } - client_ctx = CONTAINER_OF(mqtt_ctx, struct mqtt_client_ctx, mqtt_ctx); + TC_PRINT("[%s:%d] MQTT_EVT_PUBACK packet id: %u\n", + __func__, __LINE__, evt->param.puback.message_id); - printk("[%s:%d] items: %d packet id: %u", __func__, __LINE__, - items, pkt_id); + break; - if (client_ctx->subscribe_data) { - printk(" user_data: %s", - (const char *)client_ctx->subscribe_data); - } + case MQTT_EVT_PUBREC: + if (evt->result != 0) { + TC_PRINT("MQTT PUBREC error %d\n", evt->result); + break; + } - printk("\n"); + TC_PRINT("[%s:%d] MQTT_EVT_PUBREC packet id: %u\n", + __func__, __LINE__, evt->param.pubrec.message_id); - return 0; -} + const struct mqtt_pubrel_param rel_param = { + .message_id = evt->param.pubrec.message_id + }; -/** - * The signature of this routine must match the unsubscribe callback declared at - * the mqtt.h header. - */ -static int unsubscribe_cb(struct mqtt_ctx *mqtt_ctx, u16_t pkt_id) -{ - struct mqtt_client_ctx *client_ctx; + err = mqtt_publish_qos2_release(client, &rel_param); + if (err != 0) { + TC_PRINT("Failed to send MQTT PUBREL: %d\n", + err); + } - client_ctx = CONTAINER_OF(mqtt_ctx, struct mqtt_client_ctx, mqtt_ctx); + break; - printk("[%s:%d] packet id: %u", __func__, __LINE__, pkt_id); + case MQTT_EVT_PUBCOMP: + if (evt->result != 0) { + TC_PRINT("MQTT PUBCOMP error %d\n", evt->result); + break; + } - if (client_ctx->unsubscribe_data) { - printk(" user_data: %s", - (const char *)client_ctx->unsubscribe_data); - } + TC_PRINT("[%s:%d] MQTT_EVT_PUBCOMP packet id: %u\n", + __func__, __LINE__, evt->param.pubcomp.message_id); - printk("\n"); + break; - return 0; -} + case MQTT_EVT_SUBACK: + if (evt->result != 0) { + TC_PRINT("MQTT SUBACK error %d\n", evt->result); + break; + } -/** - * The signature of this routine must match the malformed callback declared at - * the mqtt.h header. - */ -static void malformed_cb(struct mqtt_ctx *mqtt_ctx, u16_t pkt_type) -{ - printk("[%s:%d] pkt_type: %u\n", __func__, __LINE__, pkt_type); -} -static char *get_mqtt_topic(void) -{ - return "sensors"; -} + TC_PRINT("[%s:%d] items: %d packet id: %u\n", __func__, + __LINE__, evt->param.suback.return_codes.len, + evt->param.suback.message_id); -#define RC_STR(rc) ((rc) == 0 ? "OK" : "ERROR") -#define PRINT_RESULT(func, rc) \ - printk("[%s:%d] %s: %d <%s>\n", __func__, __LINE__, \ - (func), rc, RC_STR(rc)) + break; -/* In this routine we block until the connected variable is 1 */ -static int try_to_connect(struct mqtt_client_ctx *client_ctx) -{ - int i = 0; + case MQTT_EVT_UNSUBACK: + if (evt->result != 0) { + TC_PRINT("MQTT UNSUBACK error %d\n", evt->result); + break; + } - while (i++ < APP_CONNECT_TRIES && !client_ctx->mqtt_ctx.connected) { - int rc; + TC_PRINT("[%s:%d] packet id: %u\n", __func__, __LINE__, + evt->param.unsuback.message_id); - rc = mqtt_tx_connect(&client_ctx->mqtt_ctx, - &client_ctx->connect_msg); - k_sleep(APP_SLEEP_MSECS); - if (rc != 0) { - continue; - } - } + break; - if (client_ctx->mqtt_ctx.connected) { - return TC_PASS; + default: + TC_PRINT("[%s:%d] Invalid MQTT packet\n", __func__, __LINE__); + break; } - - return TC_FAIL; } -static int init_network(void) +static char *get_mqtt_topic(void) { - int rc; - - /* Set everything to 0 and later just assign the required fields. */ - (void)memset(&client_ctx, 0x00, sizeof(client_ctx)); - - /* connect, disconnect and malformed may be set to NULL */ - client_ctx.mqtt_ctx.connect = connect_cb; - - client_ctx.mqtt_ctx.disconnect = disconnect_cb; - - client_ctx.mqtt_ctx.malformed = malformed_cb; - - client_ctx.mqtt_ctx.subscribe = subscriber_cb; + return "sensors"; +} - client_ctx.mqtt_ctx.unsubscribe = unsubscribe_cb; +static void client_init(struct mqtt_client *client) +{ + mqtt_client_init(client); + + broker_init(); + + /* MQTT client configuration */ + client->broker = &broker; + client->evt_cb = mqtt_evt_handler; + client->client_id.utf8 = (uint8_t *)MQTT_CLIENTID; + client->client_id.size = strlen(MQTT_CLIENTID); + client->password = NULL; + client->user_name = NULL; + client->protocol_version = MQTT_VERSION_3_1_1; + client->transport.type = MQTT_TRANSPORT_NON_SECURE; + + client->rx_buf = rx_buffer; + client->rx_buf_size = sizeof(rx_buffer); + client->tx_buf = tx_buffer; + client->tx_buf_size = sizeof(tx_buffer); +} - client_ctx.mqtt_ctx.net_init_timeout = APP_NET_INIT_TIMEOUT; +/* In this routine we block until the connected variable is 1 */ +static int try_to_connect(struct mqtt_client *client) +{ + int rc, i = 0; - client_ctx.mqtt_ctx.net_timeout = APP_TX_RX_TIMEOUT; + while (i++ < APP_CONNECT_TRIES && !connected) { - client_ctx.mqtt_ctx.peer_addr_str = SERVER_ADDR; + client_init(&client_ctx); - client_ctx.mqtt_ctx.peer_port = SERVER_PORT; + rc = mqtt_connect(client); + if (rc != 0) { + k_sleep(APP_SLEEP_MSECS); + continue; + } - /* Publisher apps TX the MQTT PUBLISH msg */ - client_ctx.mqtt_ctx.publish_rx = publish_rx_cb; + prepare_fds(client); - rc = mqtt_init(&client_ctx.mqtt_ctx, MQTT_APP_SUBSCRIBER); - if (rc != 0) { - goto exit_app; - } + wait(APP_SLEEP_MSECS); + mqtt_input(client); - /* The connect message will be sent to the MQTT server (broker). - * If clean_session here is 0, the mqtt_ctx clean_session variable - * will be set to 0 also. Please don't do that, set always to 1. - * Clean session = 0 is not yet supported. - */ - client_ctx.connect_msg.client_id = MQTT_CLIENTID; - client_ctx.connect_msg.client_id_len = strlen(MQTT_CLIENTID); - client_ctx.connect_msg.clean_session = 1; - - client_ctx.connect_data = "CONNECTED"; - client_ctx.disconnect_data = "DISCONNECTED"; - client_ctx.subscribe_data = "SUBSCRIBE"; - client_ctx.unsubscribe_data = "UNSUBSCRIBE"; - - rc = mqtt_init(&client_ctx.mqtt_ctx, MQTT_APP_SUBSCRIBER); - if (rc != 0) { - goto exit_app; + if (!connected) { + mqtt_abort(client); + } } - rc = mqtt_connect(&client_ctx.mqtt_ctx); - if (!rc) { - goto exit_app; + if (connected) { + return 0; } - return TC_PASS; - -exit_app: - mqtt_close(&client_ctx.mqtt_ctx); - - return TC_FAIL; + return -EINVAL; } static int test_connect(void) @@ -322,33 +242,47 @@ static int test_connect(void) static int test_subscribe(void) { int rc; - const char *topic_sub = get_mqtt_topic(); - u16_t pkt_id_sub = sys_rand32_get(); - static enum mqtt_qos mqtt_qos_sub[1]; + struct mqtt_topic topic; + struct mqtt_subscription_list sub; - rc = mqtt_tx_subscribe(&client_ctx.mqtt_ctx, pkt_id_sub, 1, - &topic_sub, mqtt_qos_sub); - k_sleep(APP_SLEEP_MSECS); + topic.topic.utf8 = get_mqtt_topic(); + topic.topic.size = strlen(topic.topic.utf8); + topic.qos = MQTT_QOS_1_AT_LEAST_ONCE; + sub.list = &topic; + sub.list_count = 1; + sub.message_id = sys_rand32_get(); + + rc = mqtt_subscribe(&client_ctx, &sub); if (rc != 0) { return TC_FAIL; } + wait(APP_SLEEP_MSECS); + mqtt_input(&client_ctx); + return TC_PASS; } static int test_unsubscribe(void) { int rc; - const char *topic_sub = get_mqtt_topic(); - u16_t pkt_id_unsub = sys_rand32_get(); + struct mqtt_topic topic; + struct mqtt_subscription_list unsub; + + topic.topic.utf8 = get_mqtt_topic(); + topic.topic.size = strlen(topic.topic.utf8); + unsub.list = &topic; + unsub.list_count = 1; + unsub.message_id = sys_rand32_get(); - rc = mqtt_tx_unsubscribe(&client_ctx.mqtt_ctx, pkt_id_unsub, - 1, &topic_sub); - k_sleep(APP_SLEEP_MSECS); + rc = mqtt_unsubscribe(&client_ctx, &unsub); if (rc != 0) { return TC_FAIL; } + wait(APP_SLEEP_MSECS); + mqtt_input(&client_ctx); + return TC_PASS; } @@ -356,17 +290,15 @@ static int test_disconnect(void) { int rc; - rc = mqtt_tx_disconnect(&client_ctx.mqtt_ctx); + rc = mqtt_disconnect(&client_ctx); if (rc != 0) { return TC_FAIL; } - return TC_PASS; -} + wait(APP_SLEEP_MSECS); + mqtt_input(&client_ctx); -void test_mqtt_init(void) -{ - zassert_true(init_network() == TC_PASS, NULL); + return TC_PASS; } void test_mqtt_connect(void)