/*
 * FreeRTOS 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.
 *
 * http://aws.amazon.com/freertos
 * http://www.FreeRTOS.org
 */

/**
 * @file iot_mqtt_internal.h
 * @brief Internal header of MQTT library. This header should not be included in
 * typical application code.
 */

#ifndef IOT_MQTT_INTERNAL_H_
#define IOT_MQTT_INTERNAL_H_

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

/* Linear containers (lists and queues) include. */
#include "iot_linear_containers.h"

/* MQTT include. */
#include "iot_mqtt.h"

/* Task pool include. */
#include "iot_taskpool.h"

/**
 * @def IotMqtt_Assert( expression )
 * @brief Assertion macro for the MQTT library.
 *
 * Set @ref IOT_MQTT_ENABLE_ASSERTS to `1` to enable assertions in the MQTT
 * library.
 *
 * @param[in] expression Expression to be evaluated.
 */
#if IOT_MQTT_ENABLE_ASSERTS == 1
    #ifndef IotMqtt_Assert
        #include <assert.h>
        #define IotMqtt_Assert( expression )    assert( expression )
    #endif
#else
    #define IotMqtt_Assert( expression )
#endif

/* Configure logs for MQTT functions. */
#ifdef IOT_LOG_LEVEL_MQTT
    #define LIBRARY_LOG_LEVEL        IOT_LOG_LEVEL_MQTT
#else
    #ifdef IOT_LOG_LEVEL_GLOBAL
        #define LIBRARY_LOG_LEVEL    IOT_LOG_LEVEL_GLOBAL
    #else
        #define LIBRARY_LOG_LEVEL    IOT_LOG_NONE
    #endif
#endif

#define LIBRARY_LOG_NAME    ( "MQTT" )
#include "iot_logging_setup.h"

/*
 * Provide default values for undefined memory allocation functions based on
 * the usage of dynamic memory allocation.
 */
#if IOT_STATIC_MEMORY_ONLY == 1
    #include "private/iot_static_memory.h"

/**
 * @brief Allocate an #_mqttConnection_t. This function should have the same
 * signature as [malloc]
 * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html).
 */
    void * IotMqtt_MallocConnection( size_t size );

/**
 * @brief Free an #_mqttConnection_t. This function should have the same
 * signature as [free]
 * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html).
 */
    void IotMqtt_FreeConnection( void * ptr );

/**
 * @brief Allocate memory for an MQTT packet. This function should have the
 * same signature as [malloc]
 * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html).
 */
    #define IotMqtt_MallocMessage    Iot_MallocMessageBuffer

/**
 * @brief Free an MQTT packet. This function should have the same signature
 * as [free]
 * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html).
 */
    #define IotMqtt_FreeMessage      Iot_FreeMessageBuffer

/**
 * @brief Allocate an #_mqttOperation_t. This function should have the same
 * signature as [malloc]
 * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html).
 */
    void * IotMqtt_MallocOperation( size_t size );

/**
 * @brief Free an #_mqttOperation_t. This function should have the same
 * signature as [free]
 * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html).
 */
    void IotMqtt_FreeOperation( void * ptr );

/**
 * @brief Allocate an #_mqttSubscription_t. This function should have the
 * same signature as [malloc]
 * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html).
 */
    void * IotMqtt_MallocSubscription( size_t size );

/**
 * @brief Free an #_mqttSubscription_t. This function should have the same
 * signature as [free]
 * (http://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html).
 */
    void IotMqtt_FreeSubscription( void * ptr );
#else /* if IOT_STATIC_MEMORY_ONLY == 1 */
    #include <stdlib.h>

    #ifndef IotMqtt_MallocConnection
        #define IotMqtt_MallocConnection    malloc
    #endif

    #ifndef IotMqtt_FreeConnection
        #define IotMqtt_FreeConnection    free
    #endif

    #ifndef IotMqtt_MallocMessage
        #define IotMqtt_MallocMessage    malloc
    #endif

    #ifndef IotMqtt_FreeMessage
        #define IotMqtt_FreeMessage    free
    #endif

    #ifndef IotMqtt_MallocOperation
        #define IotMqtt_MallocOperation    malloc
    #endif

    #ifndef IotMqtt_FreeOperation
        #define IotMqtt_FreeOperation    free
    #endif

    #ifndef IotMqtt_MallocSubscription
        #define IotMqtt_MallocSubscription    malloc
    #endif

    #ifndef IotMqtt_FreeSubscription
        #define IotMqtt_FreeSubscription    free
    #endif
#endif /* if IOT_STATIC_MEMORY_ONLY == 1 */

/**
 * @cond DOXYGEN_IGNORE
 * Doxygen should ignore this section.
 *
 * Provide default values for undefined configuration constants.
 */
#ifndef AWS_IOT_MQTT_ENABLE_METRICS
    #define AWS_IOT_MQTT_ENABLE_METRICS             ( 1 )
#endif
#ifndef IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES
    #define IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES    ( 0 )
#endif
#ifndef IOT_MQTT_RESPONSE_WAIT_MS
    #define IOT_MQTT_RESPONSE_WAIT_MS               ( 1000 )
#endif
#ifndef IOT_MQTT_RETRY_MS_CEILING
    #define IOT_MQTT_RETRY_MS_CEILING               ( 60000 )
#endif
/** @endcond */

/**
 * @brief Marks the empty statement of an `else` branch.
 *
 * Does nothing, but allows test coverage to detect branches not taken. By default,
 * this is defined to nothing. When running code coverage testing, this is defined
 * to an assembly NOP.
 */
#ifndef EMPTY_ELSE_MARKER
    #define EMPTY_ELSE_MARKER
#endif

/*
 * Constants related to limits defined in AWS Service Limits.
 *
 * For details, see
 * https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html
 *
 * Used to validate parameters if when connecting to an AWS IoT MQTT server.
 */
#define AWS_IOT_MQTT_SERVER_MIN_KEEPALIVE                      ( 30 )   /**< @brief Minumum keep-alive interval accepted by AWS IoT. */
#define AWS_IOT_MQTT_SERVER_MAX_KEEPALIVE                      ( 1200 ) /**< @brief Maximum keep-alive interval accepted by AWS IoT. */
#define AWS_IOT_MQTT_SERVER_MAX_CLIENTID                       ( 128 )  /**< @brief Maximum length of client identifier accepted by AWS IoT. */
#define AWS_IOT_MQTT_SERVER_MAX_TOPIC_LENGTH                   ( 256 )  /**< @brief Maximum length of topic names or filters accepted by AWS IoT. */
#define AWS_IOT_MQTT_SERVER_MAX_TOPIC_FILTERS_PER_SUBSCRIBE    ( 8 )    /**< @brief Maximum number of topic filters in a single SUBSCRIBE packet. */

/*
 * MQTT control packet type and flags. Always the first byte of an MQTT
 * packet.
 *
 * For details, see
 * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/csprd02/mqtt-v3.1.1-csprd02.html#_Toc385349757
 */
#define MQTT_PACKET_TYPE_CONNECT                               ( ( uint8_t ) 0x10U ) /**< @brief CONNECT (client-to-server). */
#define MQTT_PACKET_TYPE_CONNACK                               ( ( uint8_t ) 0x20U ) /**< @brief CONNACK (server-to-client). */
#define MQTT_PACKET_TYPE_PUBLISH                               ( ( uint8_t ) 0x30U ) /**< @brief PUBLISH (bi-directional). */
#define MQTT_PACKET_TYPE_PUBACK                                ( ( uint8_t ) 0x40U ) /**< @brief PUBACK (server-to-client). */
#define MQTT_PACKET_TYPE_SUBSCRIBE                             ( ( uint8_t ) 0x82U ) /**< @brief SUBSCRIBE (client-to-server). */
#define MQTT_PACKET_TYPE_SUBACK                                ( ( uint8_t ) 0x90U ) /**< @brief SUBACK (server-to-client). */
#define MQTT_PACKET_TYPE_UNSUBSCRIBE                           ( ( uint8_t ) 0xa2U ) /**< @brief UNSUBSCRIBE (client-to-server). */
#define MQTT_PACKET_TYPE_UNSUBACK                              ( ( uint8_t ) 0xb0U ) /**< @brief UNSUBACK (server-to-client). */
#define MQTT_PACKET_TYPE_PINGREQ                               ( ( uint8_t ) 0xc0U ) /**< @brief PINGREQ (client-to-server). */
#define MQTT_PACKET_TYPE_PINGRESP                              ( ( uint8_t ) 0xd0U ) /**< @brief PINGRESP (server-to-client). */
#define MQTT_PACKET_TYPE_DISCONNECT                            ( ( uint8_t ) 0xe0U ) /**< @brief DISCONNECT (client-to-server). */

/**
 * @brief A value that represents an invalid remaining length.
 *
 * This value is greater than what is allowed by the MQTT specification.
 */
#define MQTT_REMAINING_LENGTH_INVALID                          ( ( size_t ) 268435456 )

/*---------------------- MQTT internal data structures ----------------------*/

/**
 * @brief Represents an MQTT connection.
 */
typedef struct _mqttConnection
{
    bool awsIotMqttMode;                             /**< @brief Specifies if this connection is to an AWS IoT MQTT server. */
    bool ownNetworkConnection;                       /**< @brief Whether this MQTT connection owns its network connection. */
    void * pNetworkConnection;                       /**< @brief References the transport-layer network connection. */
    const IotNetworkInterface_t * pNetworkInterface; /**< @brief Network interface provided to @ref mqtt_function_connect. */
    IotMqttCallbackInfo_t disconnectCallback;        /**< @brief A function to invoke when this connection is disconnected. */

    #if IOT_MQTT_ENABLE_SERIALIZER_OVERRIDES == 1
        const IotMqttSerializer_t * pSerializer; /**< @brief MQTT packet serializer overrides. */
    #endif

    bool disconnected;                           /**< @brief Tracks if this connection has been disconnected. */
    IotMutex_t referencesMutex;                  /**< @brief Recursive mutex. Grants access to connection state and operation lists. */
    int32_t references;                          /**< @brief Counts callbacks and operations using this connection. */
    IotListDouble_t pendingProcessing;           /**< @brief List of operations waiting to be processed by a task pool routine. */
    IotListDouble_t pendingResponse;             /**< @brief List of processed operations awaiting a server response. */

    IotListDouble_t subscriptionList;            /**< @brief Holds subscriptions associated with this connection. */
    IotMutex_t subscriptionMutex;                /**< @brief Grants exclusive access to the subscription list. */

    bool keepAliveFailure;                       /**< @brief Failure flag for keep-alive operation. */
    uint32_t keepAliveMs;                        /**< @brief Keep-alive interval in milliseconds. Its max value (per spec) is 65,535,000. */
    uint32_t nextKeepAliveMs;                    /**< @brief Relative delay for next keep-alive job. */
    IotTaskPoolJobStorage_t keepAliveJobStorage; /**< @brief Task pool job for processing this connection's keep-alive. */
    IotTaskPoolJob_t keepAliveJob;               /**< @brief Task pool job for processing this connection's keep-alive. */
    uint8_t * pPingreqPacket;                    /**< @brief An MQTT PINGREQ packet, allocated if keep-alive is active. */
    size_t pingreqPacketSize;                    /**< @brief The size of an allocated PINGREQ packet. */
} _mqttConnection_t;

/**
 * @brief Represents a subscription stored in an MQTT connection.
 */
typedef struct _mqttSubscription
{
    IotLink_t link;     /**< @brief List link member. */

    int32_t references; /**< @brief How many subscription callbacks are using this subscription. */

    /**
     * @brief Tracks whether @ref mqtt_function_unsubscribe has been called for
     * this subscription.
     *
     * If there are active subscription callbacks, @ref mqtt_function_unsubscribe
     * cannot remove this subscription. Instead, it will set this flag, which
     * schedules the removal of this subscription once all subscription callbacks
     * terminate.
     */
    bool unsubscribed;

    struct
    {
        uint16_t identifier;        /**< @brief Packet identifier. */
        size_t order;               /**< @brief Order in the packet's list of subscriptions. */
    } packetInfo;                   /**< @brief Information about the SUBSCRIBE packet that registered this subscription. */

    IotMqttCallbackInfo_t callback; /**< @brief Callback information for this subscription. */

    uint16_t topicFilterLength;     /**< @brief Length of #_mqttSubscription_t.pTopicFilter. */
    char pTopicFilter[];            /**< @brief The subscription topic filter. */
} _mqttSubscription_t;

/**
 * @brief Internal structure representing a single MQTT operation, such as
 * CONNECT, SUBSCRIBE, PUBLISH, etc.
 *
 * Queues of these structures keeps track of all in-progress MQTT operations.
 */
typedef struct _mqttOperation
{
    /* Pointers to neighboring queue elements. */
    IotLink_t link;                      /**< @brief List link member. */

    bool incomingPublish;                /**< @brief Set to true if this operation an incoming PUBLISH. */
    _mqttConnection_t * pMqttConnection; /**< @brief MQTT connection associated with this operation. */

    IotTaskPoolJobStorage_t jobStorage;  /**< @brief Task pool job storage associated with this operation. */
    IotTaskPoolJob_t job;                /**< @brief Task pool job associated with this operation. */

    union
    {
        /* If incomingPublish is false, this struct is valid. */
        struct
        {
            /* Basic operation information. */
            int32_t jobReference;        /**< @brief Tracks if a job is using this operation. Must always be 0, 1, or 2. */
            IotMqttOperationType_t type; /**< @brief What operation this structure represents. */
            uint32_t flags;              /**< @brief Flags passed to the function that created this operation. */
            uint16_t packetIdentifier;   /**< @brief The packet identifier used with this operation. */

            /* Serialized packet and size. */
            uint8_t * pMqttPacket;           /**< @brief The MQTT packet to send over the network. */
            uint8_t * pPacketIdentifierHigh; /**< @brief The location of the high byte of the packet identifier in the MQTT packet. */
            size_t packetSize;               /**< @brief Size of `pMqttPacket`. */

            /* How to notify of an operation's completion. */
            union
            {
                IotSemaphore_t waitSemaphore;   /**< @brief Semaphore to be used with @ref mqtt_function_wait. */
                IotMqttCallbackInfo_t callback; /**< @brief User-provided callback function and parameter. */
            } notify;                           /**< @brief How to notify of this operation's completion. */
            IotMqttError_t status;              /**< @brief Result of this operation. This is reported once a response is received. */

            struct
            {
                uint32_t count;
                uint32_t limit;
                uint32_t nextPeriod;
            } retry;
        } operation;

        /* If incomingPublish is true, this struct is valid. */
        struct
        {
            IotMqttPublishInfo_t publishInfo; /**< @brief Deserialized PUBLISH. */
            const void * pReceivedData;       /**< @brief Any buffer associated with this PUBLISH that should be freed. */
        } publish;
    } u;                                      /**< @brief Valid member depends on _mqttOperation_t.incomingPublish. */
} _mqttOperation_t;

/**
 * @brief Represents an MQTT packet received from the network.
 *
 * This struct is used to hold parameters for the deserializers so that all
 * deserializers have the same function signature.
 */
typedef struct _mqttPacket
{
    union
    {
        /**
         * @brief (Input) MQTT connection associated with this packet. Only used
         * when deserializing SUBACKs.
         */
        _mqttConnection_t * pMqttConnection;

        /**
         * @brief (Output) Operation representing an incoming PUBLISH. Only used
         * when deserializing PUBLISHes.
         */
        _mqttOperation_t * pIncomingPublish;
    } u;                       /**< @brief Valid member depends on packet being decoded. */

    uint8_t * pRemainingData;  /**< @brief (Input) The remaining data in MQTT packet. */
    size_t remainingLength;    /**< @brief (Input) Length of the remaining data in the MQTT packet. */
    uint16_t packetIdentifier; /**< @brief (Output) MQTT packet identifier. */
    uint8_t type;              /**< @brief (Input) A value identifying the packet type. */
} _mqttPacket_t;

/*-------------------- MQTT struct validation functions ---------------------*/

/**
 * @brief Check that an #IotMqttConnectInfo_t is valid.
 *
 * @param[in] pConnectInfo The #IotMqttConnectInfo_t to validate.
 *
 * @return `true` if `pConnectInfo` is valid; `false` otherwise.
 */
bool _IotMqtt_ValidateConnect( const IotMqttConnectInfo_t * pConnectInfo );

/**
 * @brief Check that an #IotMqttPublishInfo_t is valid.
 *
 * @param[in] awsIotMqttMode Specifies if this PUBLISH packet is being sent to
 * an AWS IoT MQTT server.
 * @param[in] pPublishInfo The #IotMqttPublishInfo_t to validate.
 *
 * @return `true` if `pPublishInfo` is valid; `false` otherwise.
 */
bool _IotMqtt_ValidatePublish( bool awsIotMqttMode,
                               const IotMqttPublishInfo_t * pPublishInfo );

/**
 * @brief Check that an #IotMqttOperation_t is valid and waitable.
 *
 * @param[in] operation The #IotMqttOperation_t to validate.
 *
 * @return `true` if `operation` is valid; `false` otherwise.
 */
bool _IotMqtt_ValidateOperation( IotMqttOperation_t operation );

/**
 * @brief Check that a list of #IotMqttSubscription_t is valid.
 *
 * @param[in] operation Either #IOT_MQTT_SUBSCRIBE or #IOT_MQTT_UNSUBSCRIBE.
 * Some parameters are not validated for #IOT_MQTT_UNSUBSCRIBE.
 * @param[in] awsIotMqttMode Specifies if this SUBSCRIBE packet is being sent to
 * an AWS IoT MQTT server.
 * @param[in] pListStart First element of the list to validate.
 * @param[in] listSize Number of elements in the subscription list.
 *
 * @return `true` if every element in the list is valid; `false` otherwise.
 */
bool _IotMqtt_ValidateSubscriptionList( IotMqttOperationType_t operation,
                                        bool awsIotMqttMode,
                                        const IotMqttSubscription_t * pListStart,
                                        size_t listSize );

/*-------------------- MQTT packet serializer functions ---------------------*/

/**
 * @brief Get the MQTT packet type from a stream of bytes off the network.
 *
 * @param[in] pNetworkConnection Reference to the network connection.
 * @param[in] pNetworkInterface Function pointers used to interact with the
 * network.
 *
 * @return One of the server-to-client MQTT packet types.
 *
 * @note This function is only used for incoming packets, and may not work
 * correctly for outgoing packets.
 */
uint8_t _IotMqtt_GetPacketType( void * pNetworkConnection,
                                const IotNetworkInterface_t * pNetworkInterface );

/**
 * @brief Get the remaining length from a stream of bytes off the network.
 *
 * @param[in] pNetworkConnection Reference to the network connection.
 * @param[in] pNetworkInterface Function pointers used to interact with the
 * network.
 *
 * @return The remaining length; #MQTT_REMAINING_LENGTH_INVALID on error.
 */
size_t _IotMqtt_GetRemainingLength( void * pNetworkConnection,
                                    const IotNetworkInterface_t * pNetworkInterface );

/**
 * @brief Generate a CONNECT packet from the given parameters.
 *
 * @param[in] pConnectInfo User-provided CONNECT information.
 * @param[out] pConnectPacket Where the CONNECT packet is written.
 * @param[out] pPacketSize Size of the packet written to `pConnectPacket`.
 *
 * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY.
 */
IotMqttError_t _IotMqtt_SerializeConnect( const IotMqttConnectInfo_t * pConnectInfo,
                                          uint8_t ** pConnectPacket,
                                          size_t * pPacketSize );

/**
 * @brief Deserialize a CONNACK packet.
 *
 * Converts the packet from a stream of bytes to an #IotMqttError_t. Also
 * prints out debug log messages about the packet.
 *
 * @param[in,out] pConnack Pointer to an MQTT packet struct representing a CONNACK.
 *
 * @return #IOT_MQTT_SUCCESS if CONNACK specifies that CONNECT was accepted;
 * #IOT_MQTT_SERVER_REFUSED if CONNACK specifies that CONNECT was rejected;
 * #IOT_MQTT_BAD_RESPONSE if the CONNACK packet doesn't follow MQTT spec.
 */
IotMqttError_t _IotMqtt_DeserializeConnack( _mqttPacket_t * pConnack );

/**
 * @brief Generate a PUBLISH packet from the given parameters.
 *
 * @param[in] pPublishInfo User-provided PUBLISH information.
 * @param[out] pPublishPacket Where the PUBLISH packet is written.
 * @param[out] pPacketSize Size of the packet written to `pPublishPacket`.
 * @param[out] pPacketIdentifier The packet identifier generated for this PUBLISH.
 * @param[out] pPacketIdentifierHigh Where the high byte of the packet identifier
 * is written.
 *
 * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY.
 */
IotMqttError_t _IotMqtt_SerializePublish( const IotMqttPublishInfo_t * pPublishInfo,
                                          uint8_t ** pPublishPacket,
                                          size_t * pPacketSize,
                                          uint16_t * pPacketIdentifier,
                                          uint8_t ** pPacketIdentifierHigh );

/**
 * @brief Set the DUP bit in a QoS 1 PUBLISH packet.
 *
 * @param[in] pPublishPacket Pointer to the PUBLISH packet to modify.
 * @param[in] pPacketIdentifierHigh The high byte of any packet identifier to modify.
 * @param[out] pNewPacketIdentifier Since AWS IoT does not support the DUP flag,
 * a new packet identifier is generated and should be written here. This parameter
 * is only used when connected to an AWS IoT MQTT server.
 *
 * @note See #IotMqttPublishInfo_t for caveats with retransmission to the
 * AWS IoT MQTT server.
 */
void _IotMqtt_PublishSetDup( uint8_t * pPublishPacket,
                             uint8_t * pPacketIdentifierHigh,
                             uint16_t * pNewPacketIdentifier );

/**
 * @brief Deserialize a PUBLISH packet received from the server.
 *
 * Converts the packet from a stream of bytes to an #IotMqttPublishInfo_t and
 * extracts the packet identifier. Also prints out debug log messages about the
 * packet.
 *
 * @param[in,out] pPublish Pointer to an MQTT packet struct representing a PUBLISH.
 *
 * @return #IOT_MQTT_SUCCESS if PUBLISH is valid; #IOT_MQTT_BAD_RESPONSE
 * if the PUBLISH packet doesn't follow MQTT spec.
 */
IotMqttError_t _IotMqtt_DeserializePublish( _mqttPacket_t * pPublish );

/**
 * @brief Generate a PUBACK packet for the given packet identifier.
 *
 * @param[in] packetIdentifier The packet identifier to place in PUBACK.
 * @param[out] pPubackPacket Where the PUBACK packet is written.
 * @param[out] pPacketSize Size of the packet written to `pPubackPacket`.
 *
 * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY.
 */
IotMqttError_t _IotMqtt_SerializePuback( uint16_t packetIdentifier,
                                         uint8_t ** pPubackPacket,
                                         size_t * pPacketSize );

/**
 * @brief Deserialize a PUBACK packet.
 *
 * Converts the packet from a stream of bytes to an #IotMqttError_t and extracts
 * the packet identifier. Also prints out debug log messages about the packet.
 *
 * @param[in,out] pPuback Pointer to an MQTT packet struct representing a PUBACK.
 *
 * @return #IOT_MQTT_SUCCESS if PUBACK is valid; #IOT_MQTT_BAD_RESPONSE
 * if the PUBACK packet doesn't follow MQTT spec.
 */
IotMqttError_t _IotMqtt_DeserializePuback( _mqttPacket_t * pPuback );

/**
 * @brief Generate a SUBSCRIBE packet from the given parameters.
 *
 * @param[in] pSubscriptionList User-provided array of subscriptions.
 * @param[in] subscriptionCount Size of `pSubscriptionList`.
 * @param[out] pSubscribePacket Where the SUBSCRIBE packet is written.
 * @param[out] pPacketSize Size of the packet written to `pSubscribePacket`.
 * @param[out] pPacketIdentifier The packet identifier generated for this SUBSCRIBE.
 *
 * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY.
 */
IotMqttError_t _IotMqtt_SerializeSubscribe( const IotMqttSubscription_t * pSubscriptionList,
                                            size_t subscriptionCount,
                                            uint8_t ** pSubscribePacket,
                                            size_t * pPacketSize,
                                            uint16_t * pPacketIdentifier );

/**
 * @brief Deserialize a SUBACK packet.
 *
 * Converts the packet from a stream of bytes to an #IotMqttError_t and extracts
 * the packet identifier. Also prints out debug log messages about the packet.
 *
 * @param[in,out] pSuback Pointer to an MQTT packet struct representing a SUBACK.
 *
 * @return #IOT_MQTT_SUCCESS if SUBACK is valid; #IOT_MQTT_BAD_RESPONSE
 * if the SUBACK packet doesn't follow MQTT spec.
 */
IotMqttError_t _IotMqtt_DeserializeSuback( _mqttPacket_t * pSuback );

/**
 * @brief Generate an UNSUBSCRIBE packet from the given parameters.
 *
 * @param[in] pSubscriptionList User-provided array of subscriptions to remove.
 * @param[in] subscriptionCount Size of `pSubscriptionList`.
 * @param[out] pUnsubscribePacket Where the UNSUBSCRIBE packet is written.
 * @param[out] pPacketSize Size of the packet written to `pUnsubscribePacket`.
 * @param[out] pPacketIdentifier The packet identifier generated for this UNSUBSCRIBE.
 *
 * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY.
 */
IotMqttError_t _IotMqtt_SerializeUnsubscribe( const IotMqttSubscription_t * pSubscriptionList,
                                              size_t subscriptionCount,
                                              uint8_t ** pUnsubscribePacket,
                                              size_t * pPacketSize,
                                              uint16_t * pPacketIdentifier );

/**
 * @brief Deserialize a UNSUBACK packet.
 *
 * Converts the packet from a stream of bytes to an #IotMqttError_t and extracts
 * the packet identifier. Also prints out debug log messages about the packet.
 *
 * @param[in,out] pUnsuback Pointer to an MQTT packet struct representing an UNSUBACK.
 *
 * @return #IOT_MQTT_SUCCESS if UNSUBACK is valid; #IOT_MQTT_BAD_RESPONSE
 * if the UNSUBACK packet doesn't follow MQTT spec.
 */
IotMqttError_t _IotMqtt_DeserializeUnsuback( _mqttPacket_t * pUnsuback );

/**
 * @brief Generate a PINGREQ packet.
 *
 * @param[out] pPingreqPacket Where the PINGREQ packet is written.
 * @param[out] pPacketSize Size of the packet written to `pPingreqPacket`.
 *
 * @return Always returns #IOT_MQTT_SUCCESS.
 */
IotMqttError_t _IotMqtt_SerializePingreq( uint8_t ** pPingreqPacket,
                                          size_t * pPacketSize );

/**
 * @brief Deserialize a PINGRESP packet.
 *
 * Converts the packet from a stream of bytes to an #IotMqttError_t. Also
 * prints out debug log messages about the packet.
 *
 * @param[in,out] pPingresp Pointer to an MQTT packet struct representing a PINGRESP.
 *
 * @return #IOT_MQTT_SUCCESS if PINGRESP is valid; #IOT_MQTT_BAD_RESPONSE
 * if the PINGRESP packet doesn't follow MQTT spec.
 */
IotMqttError_t _IotMqtt_DeserializePingresp( _mqttPacket_t * pPingresp );

/**
 * @brief Generate a DISCONNECT packet.
 *
 * @param[out] pDisconnectPacket Where the DISCONNECT packet is written.
 * @param[out] pPacketSize Size of the packet written to `pDisconnectPacket`.
 *
 * @return Always returns #IOT_MQTT_SUCCESS.
 */
IotMqttError_t _IotMqtt_SerializeDisconnect( uint8_t ** pDisconnectPacket,
                                             size_t * pPacketSize );

/**
 * @brief Free a packet generated by the serializer.
 *
 * @param[in] pPacket The packet to free.
 */
void _IotMqtt_FreePacket( uint8_t * pPacket );

/*-------------------- MQTT operation record functions ----------------------*/

/**
 * @brief Create a record for a new in-progress MQTT operation.
 *
 * @param[in] pMqttConnection The MQTT connection to associate with the operation.
 * @param[in] flags Flags variable passed to a user-facing MQTT function.
 * @param[in] pCallbackInfo User-provided callback function and parameter.
 * @param[out] pNewOperation Set to point to the new operation on success.
 *
 * @return #IOT_MQTT_SUCCESS, #IOT_MQTT_BAD_PARAMETER, or #IOT_MQTT_NO_MEMORY.
 */
IotMqttError_t _IotMqtt_CreateOperation( _mqttConnection_t * pMqttConnection,
                                         uint32_t flags,
                                         const IotMqttCallbackInfo_t * pCallbackInfo,
                                         _mqttOperation_t ** pNewOperation );

/**
 * @brief Decrement the job reference count of an MQTT operation and optionally
 * cancel its job.
 *
 * Checks if the operation may be destroyed afterwards.
 *
 * @param[in] pOperation The MQTT operation with the job to cancel.
 * @param[in] cancelJob Whether to attempt cancellation of the operation's job.
 *
 * @return `true` if the the operation may be safely destroyed; `false` otherwise.
 */
bool _IotMqtt_DecrementOperationReferences( _mqttOperation_t * pOperation,
                                            bool cancelJob );

/**
 * @brief Free resources used to record an MQTT operation. This is called when
 * the operation completes.
 *
 * @param[in] pOperation The operation which completed.
 */
void _IotMqtt_DestroyOperation( _mqttOperation_t * pOperation );

/**
 * @brief Task pool routine for processing an MQTT connection's keep-alive.
 *
 * @param[in] pTaskPool Pointer to the system task pool.
 * @param[in] pKeepAliveJob Pointer the an MQTT connection's keep-alive job.
 * @param[in] pContext Pointer to an MQTT connection, passed as an opaque context.
 */
void _IotMqtt_ProcessKeepAlive( IotTaskPool_t pTaskPool,
                                IotTaskPoolJob_t pKeepAliveJob,
                                void * pContext );

/**
 * @brief Task pool routine for processing an incoming PUBLISH message.
 *
 * @param[in] pTaskPool Pointer to the system task pool.
 * @param[in] pPublishJob Pointer to the incoming PUBLISH operation's job.
 * @param[in] pContext Pointer to the incoming PUBLISH operation, passed as an
 * opaque context.
 */
void _IotMqtt_ProcessIncomingPublish( IotTaskPool_t pTaskPool,
                                      IotTaskPoolJob_t pPublishJob,
                                      void * pContext );

/**
 * @brief Task pool routine for processing an MQTT operation to send.
 *
 * @param[in] pTaskPool Pointer to the system task pool.
 * @param[in] pSendJob Pointer to an operation's job.
 * @param[in] pContext Pointer to the operation to send, passed as an opaque
 * context.
 */
void _IotMqtt_ProcessSend( IotTaskPool_t pTaskPool,
                           IotTaskPoolJob_t pSendJob,
                           void * pContext );

/**
 * @brief Task pool routine for processing a completed MQTT operation.
 *
 * @param[in] pTaskPool Pointer to the system task pool.
 * @param[in] pOperationJob Pointer to the completed operation's job.
 * @param[in] pContext Pointer to the completed operation, passed as an opaque
 * context.
 */
void _IotMqtt_ProcessCompletedOperation( IotTaskPool_t pTaskPool,
                                         IotTaskPoolJob_t pOperationJob,
                                         void * pContext );

/**
 * @brief Schedule an operation for immediate processing.
 *
 * @param[in] pOperation The operation to schedule.
 * @param[in] jobRoutine The routine to run for the job. Must be either
 * #_IotMqtt_ProcessSend, #_IotMqtt_ProcessCompletedOperation, or
 * #_IotMqtt_ProcessIncomingPublish.
 * @param[in] delay A delay before the operation job should be executed. Pass
 * `0` to execute ASAP.
 *
 * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_SCHEDULING_ERROR.
 */
IotMqttError_t _IotMqtt_ScheduleOperation( _mqttOperation_t * pOperation,
                                           IotTaskPoolRoutine_t jobRoutine,
                                           uint32_t delay );

/**
 * @brief Search a list of MQTT operations pending responses using an operation
 * name and packet identifier. Removes a matching operation from the list if found.
 *
 * @param[in] pMqttConnection The connection associated with the operation.
 * @param[in] type The operation type to look for.
 * @param[in] pPacketIdentifier A packet identifier to match. Pass `NULL` to ignore.
 *
 * @return Pointer to any matching operation; `NULL` if no match was found.
 */
_mqttOperation_t * _IotMqtt_FindOperation( _mqttConnection_t * pMqttConnection,
                                           IotMqttOperationType_t type,
                                           const uint16_t * pPacketIdentifier );

/**
 * @brief Notify of a completed MQTT operation.
 *
 * @param[in] pOperation The MQTT operation which completed.
 *
 * Depending on the parameters passed to a user-facing MQTT function, the
 * notification will cause @ref mqtt_function_wait to return or invoke a
 * user-provided callback.
 */
void _IotMqtt_Notify( _mqttOperation_t * pOperation );

/*----------------- MQTT subscription management functions ------------------*/

/**
 * @brief Add an array of subscriptions to the subscription manager.
 *
 * @param[in] pMqttConnection The MQTT connection associated with the subscriptions.
 * @param[in] subscribePacketIdentifier Packet identifier for the subscriptions'
 * SUBSCRIBE packet.
 * @param[in] pSubscriptionList The first element in the array.
 * @param[in] subscriptionCount Number of elements in `pSubscriptionList`.
 *
 * @return #IOT_MQTT_SUCCESS or #IOT_MQTT_NO_MEMORY.
 */
IotMqttError_t _IotMqtt_AddSubscriptions( _mqttConnection_t * pMqttConnection,
                                          uint16_t subscribePacketIdentifier,
                                          const IotMqttSubscription_t * pSubscriptionList,
                                          size_t subscriptionCount );

/**
 * @brief Process a received PUBLISH from the server, invoking any subscription
 * callbacks that have a matching topic filter.
 *
 * @param[in] pMqttConnection The MQTT connection associated with the received
 * PUBLISH.
 * @param[in] pCallbackParam The parameter to pass to a PUBLISH callback.
 */
void _IotMqtt_InvokeSubscriptionCallback( _mqttConnection_t * pMqttConnection,
                                          IotMqttCallbackParam_t * pCallbackParam );

/**
 * @brief Remove a single subscription from the subscription manager by
 * packetIdentifier and order.
 *
 * @param[in] pMqttConnection The MQTT connection associated with the subscriptions.
 * @param[in] packetIdentifier The packet identifier associated with the subscription's
 * SUBSCRIBE packet.
 * @param[in] order The order of the subscription in the SUBSCRIBE packet.
 * Pass `-1` to ignore order and remove all subscriptions for `packetIdentifier`.
 */
void _IotMqtt_RemoveSubscriptionByPacket( _mqttConnection_t * pMqttConnection,
                                          uint16_t packetIdentifier,
                                          int32_t order );

/**
 * @brief Remove an array of subscriptions from the subscription manager by
 * topic filter.
 *
 * @param[in] pMqttConnection The MQTT connection associated with the subscriptions.
 * @param[in] pSubscriptionList The first element in the array.
 * @param[in] subscriptionCount Number of elements in `pSubscriptionList`.
 */
void _IotMqtt_RemoveSubscriptionByTopicFilter( _mqttConnection_t * pMqttConnection,
                                               const IotMqttSubscription_t * pSubscriptionList,
                                               size_t subscriptionCount );

/*------------------ MQTT connection management functions -------------------*/

/**
 * @brief Attempt to increment the reference count of an MQTT connection.
 *
 * @param[in] pMqttConnection The referenced MQTT connection.
 *
 * @return `true` if the reference count was incremented; `false` otherwise. The
 * reference count will not be incremented for a disconnected connection.
 */
bool _IotMqtt_IncrementConnectionReferences( _mqttConnection_t * pMqttConnection );

/**
 * @brief Decrement the reference count of an MQTT connection.
 *
 * Also destroys an unreferenced MQTT connection.
 *
 * @param[in] pMqttConnection The referenced MQTT connection.
 */
void _IotMqtt_DecrementConnectionReferences( _mqttConnection_t * pMqttConnection );

/**
 * @brief Read the next available byte on a network connection.
 *
 * @param[in] pNetworkConnection Reference to the network connection.
 * @param[in] pNetworkInterface Function pointers used to interact with the
 * network.
 * @param[out] pIncomingByte The byte read from the network.
 *
 * @return `true` if a byte was successfully received from the network; `false`
 * otherwise.
 */
bool _IotMqtt_GetNextByte( void * pNetworkConnection,
                           const IotNetworkInterface_t * pNetworkInterface,
                           uint8_t * pIncomingByte );

/**
 * @brief Closes the network connection associated with an MQTT connection.
 *
 * A network disconnect function must be set in the network interface for the
 * network connection to be closed.
 *
 * @param[in] disconnectReason A reason to pass to the connection's disconnect
 * callback.
 * @param[in] pMqttConnection The MQTT connection with the network connection
 * to close.
 */
void _IotMqtt_CloseNetworkConnection( IotMqttDisconnectReason_t disconnectReason,
                                      _mqttConnection_t * pMqttConnection );

#endif /* ifndef IOT_MQTT_INTERNAL_H_ */
