/*
 * IoT MQTT V2.1.1
 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * @file iot_mqtt.h
 * @brief User-facing functions of the MQTT 3.1.1 library.
 */

#ifndef IOT_MQTT_H_
#define IOT_MQTT_H_

/* The config header is always included first. */
#include "iot_config.h"

/* MQTT types include. */
#include "types/iot_mqtt_types.h"

/*------------------------- MQTT library functions --------------------------*/

/**
 * @functionspage{mqtt,MQTT library}
 * - @functionname{mqtt_function_init}
 * - @functionname{mqtt_function_cleanup}
 * - @functionname{mqtt_function_receivecallback}
 * - @functionname{mqtt_function_connect}
 * - @functionname{mqtt_function_disconnect}
 * - @functionname{mqtt_function_subscribeasync}
 * - @functionname{mqtt_function_subscribesync}
 * - @functionname{mqtt_function_unsubscribeasync}
 * - @functionname{mqtt_function_unsubscribesync}
 * - @functionname{mqtt_function_publishasync}
 * - @functionname{mqtt_function_publishsync}
 * - @functionname{mqtt_function_wait}
 * - @functionname{mqtt_function_strerror}
 * - @functionname{mqtt_function_operationtype}
 * - @functionname{mqtt_function_issubscribed}
 */

/**
 * @functionpage{IotMqtt_Init,mqtt,init}
 * @functionpage{IotMqtt_Cleanup,mqtt,cleanup}
 * @functionpage{IotMqtt_ReceiveCallback,mqtt,receivecallback}
 * @functionpage{IotMqtt_Connect,mqtt,connect}
 * @functionpage{IotMqtt_Disconnect,mqtt,disconnect}
 * @functionpage{IotMqtt_SubscribeAsync,mqtt,subscribeasync}
 * @functionpage{IotMqtt_SubscribeSync,mqtt,subscribesync}
 * @functionpage{IotMqtt_UnsubscribeAsync,mqtt,unsubscribeasync}
 * @functionpage{IotMqtt_UnsubscribeSync,mqtt,unsubscribesync}
 * @functionpage{IotMqtt_PublishAsync,mqtt,publishasync}
 * @functionpage{IotMqtt_PublishSync,mqtt,publishsync}
 * @functionpage{IotMqtt_Wait,mqtt,wait}
 * @functionpage{IotMqtt_strerror,mqtt,strerror}
 * @functionpage{IotMqtt_OperationType,mqtt,operationtype}
 * @functionpage{IotMqtt_IsSubscribed,mqtt,issubscribed}
 */

/**
 * @brief One-time initialization function for the MQTT library.
 *
 * This function performs setup of the MQTT library. <b>It must be called
 * once (and only once) before calling any other MQTT function.</b> Calling this
 * function more than once without first calling @ref mqtt_function_cleanup
 * may result in a crash.
 *
 * @return One of the following:
 * - #IOT_MQTT_SUCCESS
 * - #IOT_MQTT_NOT_INITIALIZED
 *
 * @warning No thread-safety guarantees are provided for this function.
 *
 * @see @ref mqtt_function_cleanup
 */
/* @[declare_mqtt_init] */
IotMqttError_t IotMqtt_Init( void );
/* @[declare_mqtt_init] */

/**
 * @brief One-time deinitialization function for the MQTT library.
 *
 * This function frees resources taken in @ref mqtt_function_init. It should be
 * called after [closing all MQTT connections](@ref mqtt_function_disconnect) to
 * clean up the MQTT library. After this function returns, @ref mqtt_function_init
 * must be called again before calling any other MQTT function.
 *
 * @warning No thread-safety guarantees are provided for this function. Do not
 * call this function if any MQTT connections are open!
 *
 * @see @ref mqtt_function_init
 */
/* @[declare_mqtt_cleanup] */
void IotMqtt_Cleanup( void );
/* @[declare_mqtt_cleanup] */

/**
 * @brief Network receive callback for the MQTT library.
 *
 * This function should be called by the system whenever data is available for
 * the MQTT library.
 *
 * @param[in] pNetworkConnection The network connection associated with the MQTT
 * connection, passed by the network stack.
 * @param[in] pReceiveContext A pointer to the MQTT connection handle for which
 * the packet was received.
 */
/* @[declare_mqtt_receivecallback] */
void IotMqtt_ReceiveCallback( IotNetworkConnection_t pNetworkConnection,
                              void * pReceiveContext );
/* @[declare_mqtt_receivecallback] */

/**
 * @brief Establish a new MQTT connection.
 *
 * This function opens a connection between a new MQTT client and an MQTT server
 * (also called a <i>broker</i>). MQTT connections are established on top of transport
 * layer protocols (such as TCP/IP), and optionally, application layer security
 * protocols (such as TLS). The MQTT packet that establishes a connection is called
 * the MQTT CONNECT packet. After @ref mqtt_function_init, this function must be
 * called before any other MQTT library function.
 *
 * If [pConnectInfo->cleanSession](@ref IotMqttConnectInfo_t.cleanSession) is `true`,
 * this function establishes a clean MQTT session. Subscriptions and unacknowledged
 * PUBLISH messages will be discarded when the connection is closed.
 *
 * If [pConnectInfo->cleanSession](@ref IotMqttConnectInfo_t.cleanSession) is `false`,
 * this function establishes (or re-establishes) a persistent MQTT session. The parameters
 * [pConnectInfo->pPreviousSubscriptions](@ref IotMqttConnectInfo_t.pPreviousSubscriptions)
 * and [pConnectInfo->previousSubscriptionCount](@ref IotMqttConnectInfo_t.previousSubscriptionCount)
 * may be used to restore subscriptions present in a re-established persistent session.
 * Any restored subscriptions <b>MUST</b> have been present in the persistent session;
 * <b>this function does not send an MQTT SUBSCRIBE packet!</b>
 *
 * [pConnectInfo->pPreviousSubscriptions](@ref IotMqttConnectInfo_t.pPreviousSubscriptions)
 * and [pConnectInfo->previousSubscriptionCount](@ref IotMqttConnectInfo_t.previousSubscriptionCount) can
 * also be used to pass a list of subscriptions to be stored locally without a SUBSCRIBE packet being
 * sent to the broker. These subscriptions are useful to invoke application level callbacks for messages received
 * on unsolicited topics from the broker.
 *
 * This MQTT library is network agnostic, meaning it has no knowledge of the
 * underlying network protocol carrying the MQTT packets. It interacts with the
 * network through a network abstraction layer, allowing it to be used with many
 * different network stacks. The network abstraction layer is established
 * per-connection, allowing every #IotMqttConnection_t to use a different network
 * stack. The parameter `pNetworkInterface` sets up the network abstraction layer
 * for an MQTT connection; see the documentation on #IotMqttNetworkInfo_t for details
 * on its members.
 *
 * The `pConnectInfo` parameter provides the contents of the MQTT CONNECT packet.
 * Most members [are defined by the MQTT spec.](@ref IotMqttConnectInfo_t). The
 * [pConnectInfo->pWillInfo](@ref IotMqttConnectInfo_t.pWillInfo) member provides
 * information on a Last Will and Testament (LWT) message to be published if the
 * MQTT connection is closed without [sending a DISCONNECT packet]
 * (@ref mqtt_function_disconnect). Unlike other PUBLISH
 * messages, a LWT message payload is limited to 65535 bytes in length. Additionally,
 * the retry [interval](@ref IotMqttPublishInfo_t.retryMs) and [limit]
 * (@ref IotMqttPublishInfo_t.retryLimit) members of #IotMqttPublishInfo_t
 * are ignored for LWT messages. The LWT message is optional; `pWillInfo` may be NULL.
 *
 * MQTT keep-alive is configured by @ref IotMqttConnectInfo_t.keepAliveSeconds.
 * Keep-alive is used to detect half-open or otherwise unusable network connections.
 * An MQTT client will send periodic ping requests (PINGREQ) to the server if the
 * connection is idle. The MQTT server must respond to ping requests with a ping
 * response (PINGRESP). The standard does not specify how long the server has to
 * respond to a ping request, noting only a "reasonable amount of time". In this library,
 * the amount of time a server has to respond to a ping request is set with
 * @ref IOT_MQTT_RESPONSE_WAIT_MS.
 *
 * Unlike @ref mqtt_function_publishasync, @ref mqtt_function_subscribeasync, and
 * @ref mqtt_function_unsubscribeasync, this function is always blocking. Additionally,
 * because the MQTT connection acknowledgement packet (CONNACK packet) does not
 * contain any information on <i>which</i> CONNECT packet it acknowledges, only one
 * CONNECT operation may be in progress at any time. This means that parallel
 * threads making calls to @ref mqtt_function_connect will be serialized to send
 * their CONNECT packets one-by-one.
 *
 * @param[in] pNetworkInfo Information on the transport-layer network connection
 * to use with the MQTT connection.
 * @param[in] pConnectInfo MQTT connection setup parameters.
 * @param[in] timeoutMs If the MQTT server does not accept the connection within
 * this timeout in milliseconds, this function returns #IOT_MQTT_TIMEOUT.
 * @param[out] pMqttConnection Set to a newly-initialized MQTT connection handle
 * if this function succeeds.
 *
 * @return One of the following:
 * - #IOT_MQTT_SUCCESS
 * - #IOT_MQTT_NOT_INITIALIZED
 * - #IOT_MQTT_BAD_PARAMETER
 * - #IOT_MQTT_NO_MEMORY
 * - #IOT_MQTT_NETWORK_ERROR
 * - #IOT_MQTT_SCHEDULING_ERROR
 * - #IOT_MQTT_BAD_RESPONSE
 * - #IOT_MQTT_TIMEOUT
 * - #IOT_MQTT_SERVER_REFUSED
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Callback function to receive messages from the broker on an unsolicited topic.
 * void unsolicitedMessageCallback( void * pArgument, IotMqttCallbackParam_t * pPublish );
 *
 * // Parameters to MQTT connect.
 * IotMqttConnection_t mqttConnection = IOT_MQTT_CONNECTION_INITIALIZER;
 * IotMqttNetworkInfo_t networkInfo = IOT_MQTT_NETWORK_INFO_INITIALIZER;
 * IotMqttConnectInfo_t connectInfo = IOT_MQTT_CONNECT_INFO_INITIALIZER;
 * IotMqttPublishInfo_t willInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER;
 *
 * // A local subscription to receive messages from the broker on an unsolicited topic.
 * IotMqttSubscription_t subscription = IOT_MQTT_SUBSCRIPTION_INITIALIZER;
 *
 * // Example network abstraction types.
 * IotNetworkServerInfo_t serverInfo = { ... };
 * IotNetworkCredentials_t credentialInfo = { ... };
 * IotNetworkInterface_t networkInterface = { ... };
 *
 * // Example using a generic network implementation.
 * networkInfo.createNetworkConnection = true;
 * networkInfo.u.setup.pNetworkServerInfo = &serverInfo;
 * networkInfo.u.setup.pNetworkCredentialInfo = &credentialInfo;
 * networkInfo.pNetworkInterface = &networkInterface;
 *
 * // Set the members of the connection info (password and username not used).
 * connectInfo.cleanSession = true;
 * connectInfo.keepAliveSeconds = 30;
 * connectInfo.pClientIdentifier = "uniqueclientidentifier";
 * connectInfo.clientIdentifierLength = 22;
 *
 * // Set the members of the will info (retain and retry not used).
 * willInfo.qos = IOT_MQTT_QOS_1;
 * willInfo.pTopicName = "will/topic/name";
 * willInfo.topicNameLength = ( uint16_t ) strlen( willInfo.pTopicName );
 * willInfo.pPayload = "MQTT client unexpectedly disconnected.";
 * willInfo.payloadLength = strlen( willInfo.pPayload );
 *
 * // Set the pointer to the will info.
 * connectInfo.pWillInfo = &willInfo;
 *
 * // [Optional] Set a local subscription to receive the broker messages on an unsolicited topic.
 * subscription.qos = IOT_MQTT_QOS_0;
 * subscription.pTopicFilter = "some/unsolicited/topic";
 * subscription.topicLength = ( uint16_t ) strlen( subscription.pTopicFilter );
 * subscription.callback.function = unsolicitedMessageCallback;
 * connectInfo.pPreviousSubscriptions = &subscription;
 * connectInfo.previousSubscriptionCount = 1;
 *
 *
 * // Call CONNECT with a 5 second block time. Should return
 * // IOT_MQTT_SUCCESS when successful.
 * IotMqttError_t result = IotMqtt_Connect( &networkInfo,
 *                                          &connectInfo,
 *                                          5000,
 *                                          &mqttConnection );
 *
 * if( result == IOT_MQTT_SUCCESS )
 * {
 *     // Do something with the MQTT connection...
 *
 *     // Clean up and close the MQTT connection once it's no longer needed.
 *     IotMqtt_Disconnect( mqttConnection, 0 );
 * }
 * @endcode
 */
/* @[declare_mqtt_connect] */
IotMqttError_t IotMqtt_Connect( const IotMqttNetworkInfo_t * pNetworkInfo,
                                const IotMqttConnectInfo_t * pConnectInfo,
                                uint32_t timeoutMs,
                                IotMqttConnection_t * const pMqttConnection );
/* @[declare_mqtt_connect] */

/**
 * @brief Closes an MQTT connection and frees resources.
 *
 * This function closes an MQTT connection and should only be called once
 * the MQTT connection is no longer needed. Its exact behavior depends on the
 * `flags` parameter.
 *
 * Normally, `flags` should be `0`. This gracefully shuts down an MQTT
 * connection by sending an MQTT DISCONNECT packet. Any [network close function]
 * (@ref IotNetworkInterface_t::close) provided [when the connection was established]
 * (@ref mqtt_function_connect) will also be called. Note that because the MQTT server
 * will not acknowledge a DISCONNECT packet, the client has no way of knowing if
 * the server received the DISCONNECT packet. In the case where the DISCONNECT
 * packet is lost in transport, any Last Will and Testament (LWT) message established
 * with the connection may be published. However, if the DISCONNECT reaches the
 * MQTT server, the LWT message will be discarded and not published.
 *
 * Should the underlying network connection become unusable, this function should
 * be called with `flags` set to #IOT_MQTT_FLAG_CLEANUP_ONLY. In this case, no
 * DISCONNECT packet will be sent, though the [network close function](@ref IotNetworkInterface_t::close)
 * will still be called. This function will only free the resources used by the MQTT
 * connection; it still must be called even if the network is offline to avoid leaking
 * resources.
 *
 * @ref mqtt_function_disconnect modifies `mqttConnection`, so it shouldn't
 * be used after calling this function.
 *
 * @param[in] mqttConnection The MQTT connection to close and clean up.
 * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags.
 */
/* @[declare_mqtt_disconnect] */
void IotMqtt_Disconnect( IotMqttConnection_t mqttConnection,
                         uint32_t flags );
/* @[declare_mqtt_disconnect] */

/**
 * @brief Subscribes to the given array of topic filters and optionally
 * receive an asynchronous notification when the subscribe completes.
 *
 * This function sends an MQTT SUBSCRIBE packet to the server. A SUBSCRIBE
 * packet notifies the server to send any matching PUBLISH messages to this client.
 * A single SUBSCRIBE packet may carry more than one topic filter, hence the
 * parameters to this function include an array of [subscriptions]
 * (@ref IotMqttSubscription_t).
 *
 * An MQTT subscription has two pieces:
 * 1. The subscription topic filter registered with the MQTT server. The MQTT
 * SUBSCRIBE packet sent from this client to server notifies the server to send
 * messages matching the given topic filters to this client.
 * 2. The [callback function](@ref IotMqttCallbackInfo_t.function) that this
 * client will invoke when an incoming message is received. The callback function
 * notifies applications of an incoming PUBLISH message.
 *
 * The helper function @ref mqtt_function_issubscribed can be used to check if a
 * [callback function](@ref IotMqttCallbackInfo_t.function) is registered for
 * a particular topic filter.
 *
 * To modify an already-registered subscription callback, call this function with
 * a new `pSubscriptionList`. Any topic filters in `pSubscriptionList` that already
 * have a registered callback will be replaced with the new values in `pSubscriptionList`.
 *
 * @attention QoS 2 subscriptions are currently unsupported. Only 0 or 1 are valid
 * for subscription QoS.
 *
 * @param[in] mqttConnection The MQTT connection to use for the subscription.
 * @param[in] pSubscriptionList Pointer to the first element in the array of
 * subscriptions.
 * @param[in] subscriptionCount The number of elements in pSubscriptionList.
 * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags.
 * @param[in] pCallbackInfo Asynchronous notification of this function's completion (`NULL` to disable).
 * @param[out] pSubscribeOperation Set to a handle by which this operation may be
 * referenced after this function returns. This reference is invalidated once
 * the subscription operation completes.
 *
 * @return This function will return #IOT_MQTT_STATUS_PENDING upon success.
 * @return Upon completion of the subscription (either through an
 * #IotMqttCallbackInfo_t or @ref mqtt_function_wait), the status will be one of:
 * - #IOT_MQTT_SUCCESS
 * - #IOT_MQTT_NETWORK_ERROR
 * - #IOT_MQTT_SCHEDULING_ERROR
 * - #IOT_MQTT_BAD_RESPONSE
 * - #IOT_MQTT_SERVER_REFUSED
 * @return If this function fails before queuing a subscribe operation, it will return
 * one of:
 * - #IOT_MQTT_NOT_INITIALIZED
 * - #IOT_MQTT_BAD_PARAMETER
 * - #IOT_MQTT_NO_MEMORY
 *
 * @see @ref mqtt_function_subscribesync for a blocking variant of this function.
 * @see @ref mqtt_function_unsubscribeasync for the function that removes subscriptions.
 *
 * <b>Example</b>
 * @code{c}
 * #define NUMBER_OF_SUBSCRIPTIONS ...
 *
 * // Subscription callback function.
 * void subscriptionCallback( void * pArgument, IotMqttCallbackParam_t * pPublish );
 *
 * // An initialized and connected MQTT connection.
 * IotMqttConnection_t mqttConnection;
 *
 * // Subscription information.
 * pSubscriptions[ NUMBER_OF_SUBSCRIPTIONS ] = { IOT_MQTT_SUBSCRIPTION_INITIALIZER };
 * IotMqttOperation_t lastOperation = IOT_MQTT_OPERATION_INITIALIZER;
 *
 * // Set the subscription information.
 * for( int i = 0; i < NUMBER_OF_SUBSCRIPTIONS; i++ )
 * {
 *     pSubscriptions[ i ].qos = IOT_MQTT_QOS_1;
 *     pSubscriptions[ i ].pTopicFilter = "some/topic/filter";
 *     pSubscriptions[ i ].topicLength = ( uint16_t ) strlen( pSubscriptions[ i ].pTopicFilter );
 *     pSubscriptions[ i ].callback.function = subscriptionCallback;
 * }
 *
 * IotMqttError_t result = IotMqtt_SubscribeAsync( mqttConnection,
 *                                                 pSubscriptions,
 *                                                 NUMBER_OF_SUBSCRIPTIONS,
 *                                                 IOT_MQTT_FLAG_WAITABLE,
 *                                                 NULL,
 *                                                 &lastOperation );
 *
 * // Subscribe returns IOT_MQTT_STATUS_PENDING when successful. Wait up to
 * // 5 seconds for the operation to complete.
 * if( result == IOT_MQTT_STATUS_PENDING )
 * {
 *     result = IotMqtt_Wait( subscriptionRef, 5000 );
 * }
 *
 * // Check that the subscriptions were successful.
 * if( result == IOT_MQTT_SUCCESS )
 * {
 *     // Wait for messages on the subscription topic filters...
 *
 *     // Unsubscribe once the subscriptions are no longer needed.
 *     result = IotMqtt_UnsubscribeAsync( mqttConnection,
 *                                        pSubscriptions,
 *                                        NUMBER_OF_SUBSCRIPTIONS,
 *                                        IOT_MQTT_FLAG_WAITABLE,
 *                                        NULL,
 *                                        &lastOperation );
 *
 *     // UNSUBSCRIBE returns IOT_MQTT_STATUS_PENDING when successful.
 *     // Wait up to 5 seconds for the operation to complete.
 *     if( result == IOT_MQTT_STATUS_PENDING )
 *     {
 *         result = IotMqtt_Wait( lastOperation, 5000 );
 *     }
 * }
 * // Check which subscriptions were rejected by the server.
 * else if( result == IOT_MQTT_SERVER_REFUSED )
 * {
 *     for( int i = 0; i < NUMBER_OF_SUBSCRIPTIONS; i++ )
 *     {
 *         if( IotMqtt_IsSubscribed( mqttConnection,
 *                                   pSubscriptions[ i ].pTopicFilter,
 *                                   pSubscriptions[ i ].topicFilterLength,
 *                                   NULL ) == false )
 *         {
 *             // This subscription was rejected.
 *         }
 *     }
 * }
 * @endcode
 */
/* @[declare_mqtt_subscribeasync] */
IotMqttError_t IotMqtt_SubscribeAsync( IotMqttConnection_t mqttConnection,
                                       const IotMqttSubscription_t * pSubscriptionList,
                                       size_t subscriptionCount,
                                       uint32_t flags,
                                       const IotMqttCallbackInfo_t * pCallbackInfo,
                                       IotMqttOperation_t * const pSubscribeOperation );
/* @[declare_mqtt_subscribeasync] */

/**
 * @brief Subscribes to the given array of topic filters with a timeout.
 *
 * This function sends an MQTT SUBSCRIBE packet to the server, then waits for
 * a server response to the packet. Internally, this function is a call to @ref
 * mqtt_function_subscribeasync followed by @ref mqtt_function_wait. See @ref
 * mqtt_function_subscribeasync for more information about the MQTT SUBSCRIBE operation.
 *
 * @attention QoS 2 subscriptions are currently unsupported. Only 0 or 1 are valid
 * for subscription QoS.
 *
 * @param[in] mqttConnection The MQTT connection to use for the subscription.
 * @param[in] pSubscriptionList Pointer to the first element in the array of
 * subscriptions.
 * @param[in] subscriptionCount The number of elements in pSubscriptionList.
 * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags.
 * Currently, flags are ignored by this function; this parameter is for
 * future-compatibility.
 * @param[in] timeoutMs If the MQTT server does not acknowledge the subscriptions within
 * this timeout in milliseconds, this function returns #IOT_MQTT_TIMEOUT.
 *
 * @return One of the following:
 * - #IOT_MQTT_SUCCESS
 * - #IOT_MQTT_NOT_INITIALIZED
 * - #IOT_MQTT_BAD_PARAMETER
 * - #IOT_MQTT_NO_MEMORY
 * - #IOT_MQTT_NETWORK_ERROR
 * - #IOT_MQTT_SCHEDULING_ERROR
 * - #IOT_MQTT_BAD_RESPONSE
 * - #IOT_MQTT_TIMEOUT
 * - #IOT_MQTT_SERVER_REFUSED
 */
/* @[declare_mqtt_subscribesync] */
IotMqttError_t IotMqtt_SubscribeSync( IotMqttConnection_t mqttConnection,
                                      const IotMqttSubscription_t * pSubscriptionList,
                                      size_t subscriptionCount,
                                      uint32_t flags,
                                      uint32_t timeoutMs );
/* @[declare_mqtt_subscribesync] */

/**
 * @brief Unsubscribes from the given array of topic filters and optionally
 * receive an asynchronous notification when the unsubscribe completes.
 *
 * This function sends an MQTT UNSUBSCRIBE packet to the server. An UNSUBSCRIBE
 * packet removes registered topic filters from the server. After unsubscribing,
 * the server will no longer send messages on these topic filters to the client.
 *
 * Corresponding [subscription callback functions](@ref IotMqttCallbackInfo_t.function)
 * are also removed from the MQTT connection. These subscription callback functions
 * will be removed even if the MQTT UNSUBSCRIBE packet fails to send.
 *
 * @param[in] mqttConnection The MQTT connection used for the subscription.
 * @param[in] pSubscriptionList Pointer to the first element in the array of
 * subscriptions.
 * @param[in] subscriptionCount The number of elements in pSubscriptionList.
 * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags.
 * @param[in] pCallbackInfo Asynchronous notification of this function's completion (`NULL` to disable).
 * @param[out] pUnsubscribeOperation Set to a handle by which this operation may be
 * referenced after this function returns. This reference is invalidated once
 * the unsubscribe operation completes.
 *
 * @return This function will return #IOT_MQTT_STATUS_PENDING upon success.
 * @return Upon completion of the unsubscribe (either through an
 * #IotMqttCallbackInfo_t or @ref mqtt_function_wait), the status will be one of:
 * - #IOT_MQTT_SUCCESS
 * - #IOT_MQTT_NETWORK_ERROR
 * - #IOT_MQTT_SCHEDULING_ERROR
 * - #IOT_MQTT_BAD_RESPONSE
 * @return If this function fails before queuing an unsubscribe operation, it will return
 * one of:
 * - #IOT_MQTT_NOT_INITIALIZED
 * - #IOT_MQTT_BAD_PARAMETER
 * - #IOT_MQTT_NO_MEMORY
 *
 * @see @ref mqtt_function_unsubscribesync for a blocking variant of this function.
 * @see @ref mqtt_function_subscribeasync for the function that adds subscriptions.
 */
/* @[declare_mqtt_unsubscribeasync] */
IotMqttError_t IotMqtt_UnsubscribeAsync( IotMqttConnection_t mqttConnection,
                                         const IotMqttSubscription_t * pSubscriptionList,
                                         size_t subscriptionCount,
                                         uint32_t flags,
                                         const IotMqttCallbackInfo_t * pCallbackInfo,
                                         IotMqttOperation_t * const pUnsubscribeOperation );
/* @[declare_mqtt_unsubscribeasync] */

/**
 * @brief Unsubscribes from a given array of topic filters with a timeout.
 *
 * This function sends an MQTT UNSUBSCRIBE packet to the server, then waits
 * for a server response to the packet. Internally, this function is a call to
 * @ref mqtt_function_unsubscribeasync followed by @ref mqtt_function_wait. See @ref
 * mqtt_function_unsubscribeasync for more information about the MQTT UNSUBSCRIBE
 * operation.
 *
 * @param[in] mqttConnection The MQTT connection used for the subscription.
 * @param[in] pSubscriptionList Pointer to the first element in the array of
 * subscriptions.
 * @param[in] subscriptionCount The number of elements in pSubscriptionList.
 * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags.
 * Flags are currently ignored but reserved for future use.
 * @param[in] timeoutMs If the MQTT server does not acknowledge the UNSUBSCRIBE within
 * this timeout in milliseconds, this function returns #IOT_MQTT_TIMEOUT.
 *
 * @return One of the following:
 * - #IOT_MQTT_SUCCESS
 * - #IOT_MQTT_NOT_INITIALIZED
 * - #IOT_MQTT_BAD_PARAMETER
 * - #IOT_MQTT_NO_MEMORY
 * - #IOT_MQTT_NETWORK_ERROR
 * - #IOT_MQTT_SCHEDULING_ERROR
 * - #IOT_MQTT_BAD_RESPONSE
 */
/* @[declare_mqtt_unsubscribesync] */
IotMqttError_t IotMqtt_UnsubscribeSync( IotMqttConnection_t mqttConnection,
                                        const IotMqttSubscription_t * pSubscriptionList,
                                        size_t subscriptionCount,
                                        uint32_t flags,
                                        uint32_t timeoutMs );
/* @[declare_mqtt_unsubscribesync] */

/**
 * @brief Publishes a message to the given topic name and optionally
 * receive an asynchronous notification when the publish completes.
 *
 * This function sends an MQTT PUBLISH packet to the server. A PUBLISH packet
 * contains a payload and a topic name. Any clients with a subscription on a
 * topic filter matching the PUBLISH topic name will receive a copy of the
 * PUBLISH packet from the server.
 *
 * If a PUBLISH packet fails to reach the server and it is not a QoS 0 message,
 * it will be retransmitted. See #IotMqttPublishInfo_t for a description
 * of the retransmission strategy.
 *
 * @attention QoS 2 messages are currently unsupported. Only 0 or 1 are valid
 * for message QoS.
 *
 * @param[in] mqttConnection The MQTT connection to use for the publish.
 * @param[in] pPublishInfo MQTT publish parameters.
 * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags.
 * @param[in] pCallbackInfo Asynchronous notification of this function's completion (`NULL` to disable).
 * @param[out] pPublishOperation Set to a handle by which this operation may be
 * referenced after this function returns. This reference is invalidated once
 * the publish operation completes.
 *
 * @return This function will return #IOT_MQTT_STATUS_PENDING upon success for
 * QoS 1 publishes. For a QoS 0 publish it returns #IOT_MQTT_SUCCESS upon
 * success.
 * @return Upon completion of a QoS 1 publish (either through an
 * #IotMqttCallbackInfo_t or @ref mqtt_function_wait), the status will be one of:
 * - #IOT_MQTT_SUCCESS
 * - #IOT_MQTT_NETWORK_ERROR
 * - #IOT_MQTT_SCHEDULING_ERROR
 * - #IOT_MQTT_BAD_RESPONSE
 * - #IOT_MQTT_RETRY_NO_RESPONSE (if [pPublishInfo->retryMs](@ref IotMqttPublishInfo_t.retryMs)
 * and [pPublishInfo->retryLimit](@ref IotMqttPublishInfo_t.retryLimit) were set).
 * @return If this function fails before queuing an publish operation (regardless
 * of QoS), it will return one of:
 * - #IOT_MQTT_NOT_INITIALIZED
 * - #IOT_MQTT_BAD_PARAMETER
 * - #IOT_MQTT_NO_MEMORY
 *
 * @note The parameters `pCallbackInfo` and `pPublishOperation` should only be used for QoS
 * 1 publishes. For QoS 0, they should both be `NULL`.
 *
 * @see @ref mqtt_function_publishsync for a blocking variant of this function.
 *
 * <b>Example</b>
 * @code{c}
 * // An initialized and connected MQTT connection.
 * IotMqttConnection_t mqttConnection;
 *
 * // Publish information.
 * IotMqttPublishInfo_t publishInfo = IOT_MQTT_PUBLISH_INFO_INITIALIZER;
 *
 * // Set the publish information. QoS 0 example (retain not used):
 * publishInfo.qos = IOT_MQTT_QOS_0;
 * publishInfo.pTopicName = "some/topic/name";
 * publishInfo.topicNameLength = ( uint16_t ) strlen( publishInfo.pTopicName );
 * publishInfo.pPayload = "payload";
 * publishInfo.payloadLength = strlen( publishInfo.pPayload );
 *
 * // QoS 0 publish should return IOT_MQTT_SUCCESS upon success.
 * IotMqttError_t qos0Result = IotMqtt_PublishAsync( mqttConnection,
 *                                                   &publishInfo,
 *                                                   0,
 *                                                   NULL,
 *                                                   NULL );
 *
 * // QoS 1 with retry example (using same topic name and payload as QoS 0 example):
 * IotMqttOperation_t qos1Operation = IOT_MQTT_OPERATION_INITIALIZER;
 * publishInfo.qos = IOT_MQTT_QOS_1;
 * publishInfo.retryMs = 1000; // Retry if no response is received in 1 second.
 * publishInfo.retryLimit = 5; // Retry up to 5 times.
 *
 * // QoS 1 publish should return IOT_MQTT_STATUS_PENDING upon success.
 * IotMqttError_t qos1Result = IotMqtt_PublishAsync( mqttConnection,
 *                                                   &publishInfo,
 *                                                   IOT_MQTT_FLAG_WAITABLE,
 *                                                   NULL,
 *                                                   &qos1Operation );
 *
 * // Wait up to 5 seconds for the publish to complete.
 * if( qos1Result == IOT_MQTT_STATUS_PENDING )
 * {
 *     qos1Result = IotMqtt_Wait( qos1Operation, 5000 );
 * }
 * @endcode
 */
/* @[declare_mqtt_publishasync] */
IotMqttError_t IotMqtt_PublishAsync( IotMqttConnection_t mqttConnection,
                                     const IotMqttPublishInfo_t * pPublishInfo,
                                     uint32_t flags,
                                     const IotMqttCallbackInfo_t * pCallbackInfo,
                                     IotMqttOperation_t * const pPublishOperation );
/* @[declare_mqtt_publishasync] */

/**
 * @brief Publish a message to the given topic name with a timeout.
 *
 * This function sends an MQTT PUBLISH packet to the server, then waits for
 * a server response to the packet. Internally, this function is a call to @ref
 * mqtt_function_publishasync followed by @ref mqtt_function_wait. See @ref
 * mqtt_function_publishasync for more information about the MQTT PUBLISH operation.
 *
 * @attention QoS 2 messages are currently unsupported. Only 0 or 1 are valid
 * for message QoS.
 *
 * @param[in] mqttConnection The MQTT connection to use for the publish.
 * @param[in] pPublishInfo MQTT publish parameters.
 * @param[in] flags Flags which modify the behavior of this function. See @ref mqtt_constants_flags.
 * Currently, flags are ignored by this function; this parameter is for
 * future-compatibility.
 * @param[in] timeoutMs If the MQTT server does not acknowledge a QoS 1 PUBLISH
 * within this timeout in milliseconds, this function returns #IOT_MQTT_TIMEOUT.
 * This parameter is ignored for QoS 0 PUBLISH messages.
 *
 * @return One of the following:
 * - #IOT_MQTT_SUCCESS
 * - #IOT_MQTT_NOT_INITIALIZED
 * - #IOT_MQTT_BAD_PARAMETER
 * - #IOT_MQTT_NO_MEMORY
 * - #IOT_MQTT_NETWORK_ERROR
 * - #IOT_MQTT_SCHEDULING_ERROR
 * - #IOT_MQTT_BAD_RESPONSE
 * - #IOT_MQTT_RETRY_NO_RESPONSE (if [pPublishInfo->retryMs](@ref IotMqttPublishInfo_t.retryMs)
 * and [pPublishInfo->retryLimit](@ref IotMqttPublishInfo_t.retryLimit) were set).
 */
/* @[declare_mqtt_publishsync] */
IotMqttError_t IotMqtt_PublishSync( IotMqttConnection_t mqttConnection,
                                    const IotMqttPublishInfo_t * pPublishInfo,
                                    uint32_t flags,
                                    uint32_t timeoutMs );
/* @[declare_mqtt_publishsync] */

/**
 * @brief Waits for an operation to complete.
 *
 * This function blocks to wait for a [subscribe](@ref mqtt_function_subscribeasync),
 * [unsubscribe](@ref mqtt_function_unsubscribeasync), or [publish]
 * (@ref mqtt_function_publishasync) to complete. These operations are by default
 * asynchronous; the function calls queue an operation for processing, and a
 * callback is invoked once the operation is complete.
 *
 * To use this function, the flag #IOT_MQTT_FLAG_WAITABLE must have been
 * set in the operation's function call. Additionally, this function must always
 * be called with any waitable operation to clean up resources.
 *
 * Regardless of its return value, this function always clean up resources used
 * by the waitable operation. This means `reference` is invalidated as soon as
 * this function returns, even if it returns #IOT_MQTT_TIMEOUT or another error.
 *
 * @param[in] operation Reference to the operation to wait for. The flag
 * #IOT_MQTT_FLAG_WAITABLE must have been set for this operation.
 * @param[in] timeoutMs How many milliseconds to wait before returning
 * #IOT_MQTT_TIMEOUT.
 *
 * @return The return value of this function depends on the MQTT operation associated
 * with `reference`. See #IotMqttError_t for possible return values.
 *
 * <b>Example</b>
 * @code{c}
 * // Operation reference and timeout.
 * IotMqttOperation_t publishOperation = IOT_MQTT_OPERATION_INITIALIZER;
 * uint32_t timeoutMs = 5000; // 5 seconds
 *
 * // MQTT operation to wait for.
 * IotMqttError_t result = IotMqtt_PublishAsync( mqttConnection,
 *                                               &publishInfo,
 *                                               IOT_MQTT_FLAG_WAITABLE,
 *                                               NULL,
 *                                               &publishOperation );
 *
 * // Publish should have returned IOT_MQTT_STATUS_PENDING. The call to wait
 * // returns once the result of the publish is available or the timeout expires.
 * if( result == IOT_MQTT_STATUS_PENDING )
 * {
 *     result = IotMqtt_Wait( publishOperation, timeoutMs );
 *
 *     // After the call to wait, the result of the publish is known
 *     // (not IOT_MQTT_STATUS_PENDING).
 *     assert( result != IOT_MQTT_STATUS_PENDING );
 * }
 * @endcode
 */
/* @[declare_mqtt_wait] */
IotMqttError_t IotMqtt_Wait( IotMqttOperation_t operation,
                             uint32_t timeoutMs );
/* @[declare_mqtt_wait] */

/*-------------------------- MQTT helper functions --------------------------*/

/**
 * @brief Returns a string that describes an #IotMqttError_t.
 *
 * Like the POSIX `strerror`, this function returns a string describing a
 * return code. In this case, the return code is an MQTT library error code,
 * `status`.
 *
 * The string returned by this function <b>MUST</b> be treated as read-only: any
 * attempt to modify its contents may result in a crash. Therefore, this function
 * is limited to usage in logging.
 *
 * @param[in] status The status to describe.
 *
 * @return A read-only string that describes `status`.
 *
 * @warning The string returned by this function must never be modified.
 */
/* @[declare_mqtt_strerror] */
const char * IotMqtt_strerror( IotMqttError_t status );
/* @[declare_mqtt_strerror] */

/**
 * @brief Returns a string that describes an #IotMqttOperationType_t.
 *
 * This function returns a string describing an MQTT operation type, `operation`.
 *
 * The string returned by this function <b>MUST</b> be treated as read-only: any
 * attempt to modify its contents may result in a crash. Therefore, this function
 * is limited to usage in logging.
 *
 * @param[in] operation The operation to describe.
 *
 * @return A read-only string that describes `operation`.
 *
 * @warning The string returned by this function must never be modified.
 */
/* @[declare_mqtt_operationtype] */
const char * IotMqtt_OperationType( IotMqttOperationType_t operation );
/* @[declare_mqtt_operationtype] */

/**
 * @brief Check if an MQTT connection has a subscription for a topic filter.
 *
 * This function checks whether an MQTT connection `mqttConnection` has a
 * subscription callback registered for a topic filter `pTopicFilter`. If a
 * subscription callback is found, its details are copied into the output parameter
 * `pCurrentSubscription`. This subscription callback will be invoked for incoming
 * PUBLISH messages on `pTopicFilter`.
 *
 * <b>The check for a matching subscription is only performed client-side</b>;
 * therefore, this function should not be relied upon for perfect accuracy. For
 * example, this function may return an incorrect result if the MQTT server
 * crashes and drops subscriptions without informing the client.
 *
 * Note that an MQTT connection's subscriptions might change between the time this
 * function checks the subscription list and its caller tests the return value.
 * This function certainly should not be used concurrently with any pending SUBSCRIBE
 * or UNSUBSCRIBE operations.
 *
 * One suitable use of this function is to check <i>which</i> subscriptions were rejected
 * if @ref mqtt_function_subscribeasync returns #IOT_MQTT_SERVER_REFUSED; that return
 * code only means that <i>at least one</i> subscription was rejected.
 *
 * @param[in] mqttConnection The MQTT connection to check.
 * @param[in] pTopicFilter The topic filter to check.
 * @param[in] topicFilterLength Length of `pTopicFilter`.
 * @param[out] pCurrentSubscription If a subscription is found, its details are
 * copied here. This output parameter is only valid if this function returns `true` (`NULL` to disable).
 *
 * @return `true` if a subscription was found; `false` otherwise.
 *
 * @note The subscription QoS is not stored by the MQTT library; therefore,
 * `pCurrentSubscription->qos` will always be set to #IOT_MQTT_QOS_0.
 */
/* @[declare_mqtt_issubscribed] */
bool IotMqtt_IsSubscribed( IotMqttConnection_t mqttConnection,
                           const char * pTopicFilter,
                           uint16_t topicFilterLength,
                           IotMqttSubscription_t * const pCurrentSubscription );
/* @[declare_mqtt_issubscribed] */

/**
 * @cond DOXYGEN_IGNORE
 * Doxygen should ignore this section.
 *
 * Backwards compatibility macros for previous function names.
 */
#define IotMqtt_Subscribe           IotMqtt_SubscribeAsync
#define IotMqtt_TimedSubscribe      IotMqtt_SubscribeSync
#define IotMqtt_Unsubscribe         IotMqtt_UnsubscribeAsync
#define IotMqtt_TimedUnsubscribe    IotMqtt_UnsubscribeSync
#define IotMqtt_Publish             IotMqtt_PublishAsync
#define IotMqtt_TimedPublish        IotMqtt_PublishSync
/** @endcond */

#endif /* ifndef IOT_MQTT_H_ */
