/*
 * coreMQTT v2.1.1
 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * 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 core_mqtt_utest.c
 * @brief Unit tests for functions in core_mqtt.h.
 */
#include <string.h>
#include <stdbool.h>

#include "unity.h"

/* Include paths for public enums, structures, and macros. */
#include "core_mqtt.h"

#include "mock_core_mqtt_serializer.h"
#include "mock_core_mqtt_state.h"

/**
 * @brief MQTT client identifier.
 */
#define MQTT_CLIENT_IDENTIFIER                 "testclient"

/**
 * @brief A valid starting packet ID per MQTT spec. Start from 1.
 */
#define MQTT_FIRST_VALID_PACKET_ID             ( 1 )

/**
 * @brief A PINGREQ packet is always 2 bytes in size, defined by MQTT 3.1.1 spec.
 */
#define MQTT_PACKET_PINGREQ_SIZE               ( 2U )

/**
 * @brief A packet type not handled by MQTT_ProcessLoop.
 */
#define MQTT_PACKET_TYPE_INVALID               ( 0U )

/**
 * @brief Number of milliseconds in a second.
 */
#define MQTT_ONE_SECOND_TO_MS                  ( 1000U )

/**
 * @brief Length of the MQTT network buffer.
 */
#define MQTT_TEST_BUFFER_LENGTH                ( 128 )

/**
 * @brief Sample keep-alive interval that should be greater than 0.
 */
#define MQTT_SAMPLE_KEEPALIVE_INTERVAL_S       ( 1U )

/**
 * @brief Length of time spent for single test case with
 * multiple iterations spent in the process loop for coverage.
 */
#define MQTT_SAMPLE_PROCESS_LOOP_TIMEOUT_MS    ( 1U )

/**
 * @brief Zero timeout in the process loop implies one iteration.
 */
#define MQTT_NO_TIMEOUT_MS                     ( 0U )

/**
 * @brief Sample length of remaining serialized data.
 */
#define MQTT_SAMPLE_REMAINING_LENGTH           ( 64 )

/**
 * @brief Subtract this value from max value of global entry time
 * for the timer overflow test.
 */
#define MQTT_OVERFLOW_OFFSET                   ( 3 )

/**
 * @brief The number of times the "getTime()" function is called
 * within a single iteration of the #MQTT_ProcessLoop.
 *
 * This constant is used for the timer overflow test which checks
 * that the API can support normal behavior even if the timer
 * overflows.
 *
 * @note Currently, there are 6 calls within a single iteration.
 * This can change when the implementation changes which would be
 * caught through unit test failure.
 */
#define MQTT_TIMER_CALLS_PER_ITERATION         ( 6 )

/**
 * @brief Timeout for the timer overflow test.
 */
#define MQTT_TIMER_OVERFLOW_TIMEOUT_MS         ( 10 )

/**
 * @brief A sample network context that we set to NULL.
 */
#define MQTT_SAMPLE_NETWORK_CONTEXT            ( NULL )

/**
 * @brief Sample topic filter to subscribe to.
 */
#define MQTT_SAMPLE_TOPIC_FILTER               "iot"

/**
 * @brief Length of sample topic filter.
 */
#define MQTT_SAMPLE_TOPIC_FILTER_LENGTH        ( sizeof( MQTT_SAMPLE_TOPIC_FILTER ) - 1 )

/**
 * @brief Sample topic filter to subscribe to.
 */
#define MQTT_SAMPLE_TOPIC_FILTER1              "TopicFilter2"

/**
 * @brief Length of sample topic filter.
 */
#define MQTT_SAMPLE_TOPIC_FILTER_LENGTH1       ( sizeof( MQTT_SAMPLE_TOPIC_FILTER1 ) - 1 )

/**
 * @brief Sample topic filter to subscribe to.
 */
#define MQTT_SAMPLE_TOPIC_FILTER2              "SomeTopic"

/**
 * @brief Length of sample topic filter.
 */
#define MQTT_SAMPLE_TOPIC_FILTER_LENGTH2       ( sizeof( MQTT_SAMPLE_TOPIC_FILTER2 ) - 1 )

/**
 * @brief Sample topic filter to subscribe to.
 */
#define MQTT_SAMPLE_TOPIC_FILTER3              "iotTopicFilter"

/**
 * @brief Length of sample topic filter.
 */
#define MQTT_SAMPLE_TOPIC_FILTER_LENGTH3       ( sizeof( MQTT_SAMPLE_TOPIC_FILTER3 ) - 1 )

/**
 * @brief Return values of mocked calls in MQTT_ProcessLoop(). Used by
 * `expectProcessLoopCalls`
 */
typedef struct ProcessLoopReturns
{
    MQTTStatus_t deserializeStatus;           /**< @brief Status after deserializing incoming packet. */
    MQTTPublishState_t stateAfterDeserialize; /**< @brief Publish state after deserializing incoming packet. */
    MQTTStatus_t updateStateStatus;           /**< @brief Status after updating publish state. */
    MQTTStatus_t serializeStatus;             /**< @brief Status after serializing a publish ack to send. */
    MQTTPublishState_t stateAfterSerialize;   /**< @brief Publish state after serializing an ack to send. */
    MQTTStatus_t processLoopStatus;           /**< @brief Return value of the process loop. */
    bool incomingPublish;                     /**< @brief Whether the incoming packet is a publish. */
    MQTTPublishInfo_t * pPubInfo;             /**< @brief Publish information to be returned by the deserializer. */
    uint32_t timeoutMs;                       /**< @brief The timeout value to call MQTT_ProcessLoop API with. */
} ProcessLoopReturns_t;

/**
 * @brief The packet type to be received by the process loop.
 * IMPORTANT: Make sure this is set before calling expectProcessLoopCalls(...).
 */
static uint8_t currentPacketType = MQTT_PACKET_TYPE_INVALID;

/**
 * @brief The return value of modifyIncomingPacket(...) CMock callback that
 * replaces a call to MQTT_GetIncomingPacketTypeAndLength.
 * IMPORTANT: Make sure this is set before calling expectProcessLoopCalls(...).
 */
static MQTTStatus_t modifyIncomingPacketStatus = MQTTSuccess;

/**
 * @brief Time at the beginning of each test. Note that this is not updated with
 * a real clock. Instead, we simply increment this variable.
 */
static uint32_t globalEntryTime = 0;

/**
 * @brief A static buffer used by the MQTT library for storing packet data.
 */
static uint8_t mqttBuffer[ MQTT_TEST_BUFFER_LENGTH ] = { 0 };

/**
 * @brief A flag to indicate whether event callback is called from
 * MQTT_ProcessLoop.
 */
static bool isEventCallbackInvoked = false;
static bool receiveOnce = false;

static const uint8_t SubscribeHeader[] =
{
    MQTT_PACKET_TYPE_SUBSCRIBE,                  /* Subscribe header. */
    0x10, 0x10,                                  /* Packet Length. */
    ( MQTT_FIRST_VALID_PACKET_ID >> 8 ),
    ( ( MQTT_FIRST_VALID_PACKET_ID ) & 0x00ffU ) /* Packet ID. */
};
static const size_t SubscribeHeaderLength = 5U;

static const uint8_t UnsubscribeHeader[] =
{
    MQTT_PACKET_TYPE_UNSUBSCRIBE,                /* Subscribe header. */
    0x12, 0x15,                                  /* Packet Length. */
    ( MQTT_FIRST_VALID_PACKET_ID >> 8 ),
    ( ( MQTT_FIRST_VALID_PACKET_ID ) & 0x00ffU ) /* Packet ID. */
};
static const size_t UnsubscribeHeaderLength = 5U;

/* ============================   UNITY FIXTURES ============================ */

/* Called before each test method. */
void setUp()
{
    memset( mqttBuffer, 0x0, sizeof( mqttBuffer ) );
    MQTT_State_strerror_IgnoreAndReturn( "DUMMY_MQTT_STATE" );

    globalEntryTime = 0;
}

/* Called after each test method. */
void tearDown()
{
}

/* Called at the beginning of the whole suite. */
void suiteSetUp()
{
    receiveOnce = 0;
}

/* Called at the end of the whole suite. */
int suiteTearDown( int numFailures )
{
    return numFailures;
}


/* ========================================================================== */

/**
 * @brief Mock successful transport send, and write data into buffer for
 * verification.
 */
static int32_t mockSend( NetworkContext_t * pNetworkContext,
                         const void * pMessage,
                         size_t bytesToSend )
{
    const uint8_t * buffer = ( const uint8_t * ) pMessage;
    /* Treat network context as pointer to buffer for mocking. */
    uint8_t * mockNetwork = *( pNetworkContext->buffer );
    size_t bytesSent = 0;

    while( bytesSent++ < bytesToSend )
    {
        /* Write single byte and advance buffer. */
        *mockNetwork++ = *buffer++;
    }

    /* Move stream by bytes sent. */
    *( pNetworkContext->buffer ) = mockNetwork;

    return bytesToSend;
}

/**
 * @brief Initialize pNetworkBuffer using static buffer.
 *
 * @param[in] pNetworkBuffer Network buffer provided for the context.
 */
static void setupNetworkBuffer( MQTTFixedBuffer_t * const pNetworkBuffer )
{
    pNetworkBuffer->pBuffer = mqttBuffer;
    pNetworkBuffer->size = MQTT_TEST_BUFFER_LENGTH;
}

/**
 * @brief Mocked MQTT event callback.
 *
 * @param[in] pContext MQTT context pointer.
 * @param[in] pPacketInfo Packet Info pointer for the incoming packet.
 * @param[in] pDeserializedInfo Deserialized information from the incoming packet.
 */
static void eventCallback( MQTTContext_t * pContext,
                           MQTTPacketInfo_t * pPacketInfo,
                           MQTTDeserializedInfo_t * pDeserializedInfo )
{
    ( void ) pContext;
    ( void ) pPacketInfo;
    ( void ) pDeserializedInfo;

    /* Update the global state to indicate that event callback is invoked. */
    isEventCallbackInvoked = true;
}

/**
 * @brief A mocked timer query function that increments on every call. This
 * guarantees that only a single iteration runs in the ProcessLoop for ease
 * of testing.
 */
static uint32_t getTime( void )
{
    return globalEntryTime++;
}

/**
 * @brief A mocked timer function that could be used on a device with no system
 * time.
 */
static uint32_t getTimeDummy( void )
{
    return 0;
}

/**
 * @brief Mocked successful transport writev.
 *
 * @param[in] tcpSocket TCP socket.
 * @param[in] pMessage vectors to send
 * @param[in] bytesToWrite number of vectors
 *
 * @return Number of bytes sent; negative value on error;
 * 0 for timeout or 0 bytes sent.
 */
static int32_t transportWritevSuccess( NetworkContext_t * pNetworkContext,
                                       TransportOutVector_t * pIoVectorIterator,
                                       size_t vectorsToBeSent )
{
    TEST_ASSERT_EQUAL( MQTT_SAMPLE_NETWORK_CONTEXT, pNetworkContext );
    int32_t bytesToWrite = 0;
    size_t i;

    for( i = 0; i < vectorsToBeSent; ++i )
    {
        bytesToWrite += pIoVectorIterator->iov_len;
        pIoVectorIterator++;
    }

    return bytesToWrite;
}

static void verifyEncodedTopicString( TransportOutVector_t * pIoVectorIterator,
                                      char * pTopicFilter,
                                      size_t topicFilterLength,
                                      MQTTQoS_t topicQos,
                                      int32_t * pBytesToWrite )
{
    /* Encoded topic length. */
    uint8_t serializedLength[ 2 ];
    const uint8_t * buffer;
    size_t length;

    serializedLength[ 0 ] = ( uint8_t ) ( topicFilterLength >> 8 );
    serializedLength[ 1 ] = ( uint8_t ) ( topicFilterLength & 0x00ffU );

    buffer = pIoVectorIterator[ 0 ].iov_base;
    length = pIoVectorIterator[ 0 ].iov_len;

    TEST_ASSERT_EQUAL( 2U, length );
    TEST_ASSERT_EQUAL_UINT8_ARRAY( serializedLength, buffer, 2U );
    ( *pBytesToWrite ) += length;

    buffer = pIoVectorIterator[ 1 ].iov_base;
    length = pIoVectorIterator[ 1 ].iov_len;

    /* Encoded topic string. */
    TEST_ASSERT_EQUAL( topicFilterLength, length );
    TEST_ASSERT_EQUAL_UINT8_ARRAY( pTopicFilter, buffer, topicFilterLength );
    ( *pBytesToWrite ) += length;

    buffer = pIoVectorIterator[ 2 ].iov_base;
    length = pIoVectorIterator[ 2 ].iov_len;

    /* Encoded QoS. */
    TEST_ASSERT_EQUAL( 1U, length );
    TEST_ASSERT_EQUAL_UINT8( topicQos, *buffer );
    ( *pBytesToWrite ) += length;
}

static void verifyEncodedTopicStringUnsubscribe( TransportOutVector_t * pIoVectorIterator,
                                                 char * pTopicFilter,
                                                 size_t topicFilterLength,
                                                 int32_t * pBytesToWrite )
{
    /* Encoded topic length. */
    uint8_t serializedLength[ 2 ];
    const uint8_t * buffer;
    size_t length;

    serializedLength[ 0 ] = ( uint8_t ) ( topicFilterLength >> 8 );
    serializedLength[ 1 ] = ( uint8_t ) ( topicFilterLength & 0x00ffU );

    buffer = pIoVectorIterator[ 0 ].iov_base;
    length = pIoVectorIterator[ 0 ].iov_len;

    TEST_ASSERT_EQUAL( 2U, length );
    TEST_ASSERT_EQUAL_UINT8_ARRAY( serializedLength, buffer, 2U );
    ( *pBytesToWrite ) += length;

    buffer = pIoVectorIterator[ 1 ].iov_base;
    length = pIoVectorIterator[ 1 ].iov_len;

    /* Encoded topic string. */
    TEST_ASSERT_EQUAL( topicFilterLength, length );
    TEST_ASSERT_EQUAL_UINT8_ARRAY( pTopicFilter, buffer, topicFilterLength );
    ( *pBytesToWrite ) += length;
}

/**
 * @brief Mocked transport writev to verify correct subscription
 * packet.
 *
 * @param[in] tcpSocket TCP socket.
 * @param[in] pMessage vectors to send
 * @param[in] bytesToWrite number of vectors
 *
 * @return Number of bytes sent; negative value on error;
 * 0 for timeout or 0 bytes sent.
 */
static int32_t transportWritevSubscribeSuccess( NetworkContext_t * pNetworkContext,
                                                TransportOutVector_t * pIoVectorIterator,
                                                size_t vectorsToBeSent )
{
    TEST_ASSERT_EQUAL( MQTT_SAMPLE_NETWORK_CONTEXT, pNetworkContext );
    int32_t bytesToWrite = 0;
    static int writeCount = 0;
    const uint8_t * buffer;
    size_t length;

    /* The header. */
    if( writeCount == 0 )
    {
        TEST_ASSERT_EQUAL( 4, vectorsToBeSent );

        buffer = pIoVectorIterator->iov_base;
        length = pIoVectorIterator->iov_len;

        TEST_ASSERT_EQUAL( length, SubscribeHeaderLength );
        TEST_ASSERT_EQUAL_UINT8_ARRAY( ( uint8_t * ) SubscribeHeader, buffer, SubscribeHeaderLength );

        bytesToWrite += length;
        pIoVectorIterator++;

        /* First topic filter. */
        verifyEncodedTopicString( pIoVectorIterator,
                                  MQTT_SAMPLE_TOPIC_FILTER,
                                  MQTT_SAMPLE_TOPIC_FILTER_LENGTH,
                                  MQTTQoS1,
                                  &bytesToWrite );

        writeCount++;

        pIoVectorIterator += 4;
    }
    else if( writeCount == 1 )
    {
        TEST_ASSERT_EQUAL( 6, vectorsToBeSent );

        /* Second topic filter. */
        verifyEncodedTopicString( pIoVectorIterator,
                                  MQTT_SAMPLE_TOPIC_FILTER1,
                                  MQTT_SAMPLE_TOPIC_FILTER_LENGTH1,
                                  MQTTQoS2,
                                  &bytesToWrite );

        pIoVectorIterator += 3;

        /* Third topic filter. */
        verifyEncodedTopicString( pIoVectorIterator,
                                  MQTT_SAMPLE_TOPIC_FILTER2,
                                  MQTT_SAMPLE_TOPIC_FILTER_LENGTH2,
                                  MQTTQoS0,
                                  &bytesToWrite );

        writeCount++;
    }
    else if( writeCount == 2 )
    {
        TEST_ASSERT_EQUAL( 3, vectorsToBeSent );

        /* Fourth topic filter. */
        verifyEncodedTopicString( pIoVectorIterator,
                                  MQTT_SAMPLE_TOPIC_FILTER3,
                                  MQTT_SAMPLE_TOPIC_FILTER_LENGTH3,
                                  MQTTQoS1,
                                  &bytesToWrite );

        writeCount++;
    }
    else
    {
        bytesToWrite = -1;
    }

    return bytesToWrite;
}

/**
 * @brief Mocked transport writev to verify correct subscription
 * packet.
 *
 * @param[in] tcpSocket TCP socket.
 * @param[in] pMessage vectors to send
 * @param[in] bytesToWrite number of vectors
 *
 * @return Number of bytes sent; negative value on error;
 * 0 for timeout or 0 bytes sent.
 */
static int32_t transportWritevUnsubscribeSuccess( NetworkContext_t * pNetworkContext,
                                                  TransportOutVector_t * pIoVectorIterator,
                                                  size_t vectorsToBeSent )
{
    TEST_ASSERT_EQUAL( MQTT_SAMPLE_NETWORK_CONTEXT, pNetworkContext );
    int32_t bytesToWrite = 0;
    static int writeCount = 0;
    const uint8_t * buffer;
    size_t length;

    /* The header. */
    if( writeCount == 0 )
    {
        TEST_ASSERT_EQUAL( 5, vectorsToBeSent );

        buffer = pIoVectorIterator->iov_base;
        length = pIoVectorIterator->iov_len;

        TEST_ASSERT_EQUAL( length, UnsubscribeHeaderLength );
        TEST_ASSERT_EQUAL_UINT8_ARRAY( ( uint8_t * ) UnsubscribeHeader, buffer, UnsubscribeHeaderLength );

        bytesToWrite += length;
        pIoVectorIterator++;

        /* First topic filter. */
        verifyEncodedTopicStringUnsubscribe( pIoVectorIterator,
                                             MQTT_SAMPLE_TOPIC_FILTER,
                                             MQTT_SAMPLE_TOPIC_FILTER_LENGTH,
                                             &bytesToWrite );

        pIoVectorIterator += 2;

        /* Second topic filter. */
        verifyEncodedTopicStringUnsubscribe( pIoVectorIterator,
                                             MQTT_SAMPLE_TOPIC_FILTER1,
                                             MQTT_SAMPLE_TOPIC_FILTER_LENGTH1,
                                             &bytesToWrite );

        writeCount++;
    }
    else if( writeCount == 1 )
    {
        TEST_ASSERT_EQUAL( 4, vectorsToBeSent );

        /* Third topic filter. */
        verifyEncodedTopicStringUnsubscribe( pIoVectorIterator,
                                             MQTT_SAMPLE_TOPIC_FILTER2,
                                             MQTT_SAMPLE_TOPIC_FILTER_LENGTH2,
                                             &bytesToWrite );

        pIoVectorIterator += 2;

        /* Fourth topic filter. */
        verifyEncodedTopicStringUnsubscribe( pIoVectorIterator,
                                             MQTT_SAMPLE_TOPIC_FILTER3,
                                             MQTT_SAMPLE_TOPIC_FILTER_LENGTH3,
                                             &bytesToWrite );

        writeCount++;
    }
    else
    {
        bytesToWrite = -1;
    }

    return bytesToWrite;
}

/**
 * @brief Mocked failed transport writev.
 *
 * @param[in] tcpSocket TCP socket.
 * @param[in] pMessage vectors to send
 * @param[in] bytesToWrite number of vectors
 *
 * @return Number of bytes sent; negative value on error;
 * 0 for timeout or 0 bytes sent.
 */
static int32_t transportWritevFail( NetworkContext_t * pNetworkContext,
                                    TransportOutVector_t * pIoVectorIterator,
                                    size_t vectorsToBeSent )
{
    TEST_ASSERT_EQUAL( MQTT_SAMPLE_NETWORK_CONTEXT, pNetworkContext );
    int32_t bytesToWrite = 0;
    size_t i;

    for( i = 0; i < vectorsToBeSent; ++i )
    {
        bytesToWrite += pIoVectorIterator->iov_len;
        pIoVectorIterator++;
    }

    return bytesToWrite + 3;
}

static int32_t transportWritevError( NetworkContext_t * pNetworkContext,
                                     TransportOutVector_t * pIoVectorIterator,
                                     size_t vectorsToBeSent )
{
    ( void ) vectorsToBeSent;
    ( void ) pIoVectorIterator;

    TEST_ASSERT_EQUAL( MQTT_SAMPLE_NETWORK_CONTEXT, pNetworkContext );

    return -1;
}

/**
 * @brief Mocked successful transport send.
 *
 * @param[in] tcpSocket TCP socket.
 * @param[in] pMessage Data to send.
 * @param[in] bytesToWrite Length of data to send.
 *
 * @return Number of bytes sent; negative value on error;
 * 0 for timeout or 0 bytes sent.
 */
static int32_t transportSendSuccess( NetworkContext_t * pNetworkContext,
                                     const void * pBuffer,
                                     size_t bytesToWrite )
{
    TEST_ASSERT_EQUAL( MQTT_SAMPLE_NETWORK_CONTEXT, pNetworkContext );
    ( void ) pBuffer;
    return bytesToWrite;
}

/**
 * @brief Mocked failed transport send.
 */
static int32_t transportSendFailure( NetworkContext_t * pNetworkContext,
                                     const void * pBuffer,
                                     size_t bytesToWrite )
{
    ( void ) pNetworkContext;
    ( void ) pBuffer;
    ( void ) bytesToWrite;
    return -1;
}

/**
 * @brief Mocked transport send that always returns 0 bytes sent.
 */
static int32_t transportSendNoBytes( NetworkContext_t * pNetworkContext,
                                     const void * pBuffer,
                                     size_t bytesToWrite )
{
    ( void ) pNetworkContext;
    ( void ) pBuffer;
    ( void ) bytesToWrite;
    return 0;
}

/**
 * @brief Mocked transport send that succeeds then fails.
 */
static int32_t transportSendSucceedThenFail( NetworkContext_t * pNetworkContext,
                                             const void * pMessage,
                                             size_t bytesToSend )
{
    int32_t retVal = bytesToSend;
    static int counter = 0;

    ( void ) pNetworkContext;
    ( void ) pMessage;

    if( counter++ )
    {
        retVal = -1;
        counter = 0;
    }

    return retVal;
}

/**
 * @brief Mocked transport send that only sends half of the bytes.
 */
static int32_t transportWritevPartialByte( NetworkContext_t * pNetworkContext,
                                           TransportOutVector_t * pIoVectorIterator,
                                           size_t vectorsToBeSent )
{
    int32_t bytesToWrite = 0;
    size_t i;

    ( void ) pNetworkContext;

    for( i = 0; i < vectorsToBeSent; ++i )
    {
        bytesToWrite += pIoVectorIterator->iov_len;
        pIoVectorIterator++;
    }

    if( bytesToWrite > 1 )
    {
        bytesToWrite /= 2;
    }

    return bytesToWrite;
}

/**
 * @brief Mocked successful transport read.
 *
 * @param[in] tcpSocket TCP socket.
 * @param[out] pBuffer Buffer for receiving data.
 * @param[in] bytesToRead Size of pBuffer.
 *
 * @return Number of bytes received; negative value on error.
 */
static int32_t transportRecvSuccess( NetworkContext_t * pNetworkContext,
                                     void * pBuffer,
                                     size_t bytesToRead )
{
    TEST_ASSERT_EQUAL( MQTT_SAMPLE_NETWORK_CONTEXT, pNetworkContext );
    ( void ) pBuffer;
    return bytesToRead;
}

static int32_t transportRecvOneSuccessOneFail( NetworkContext_t * pNetworkContext,
                                               void * pBuffer,
                                               size_t bytesToRead )
{
    TEST_ASSERT_EQUAL( MQTT_SAMPLE_NETWORK_CONTEXT, pNetworkContext );
    ( void ) pBuffer;

    if( receiveOnce == false )
    {
        receiveOnce = true;
        return bytesToRead;
    }
    else
    {
        return -1;
    }
}

/**
 * @brief Mocked failed transport read.
 */
static int32_t transportRecvFailure( NetworkContext_t * pNetworkContext,
                                     void * pBuffer,
                                     size_t bytesToRead )
{
    ( void ) pNetworkContext;
    ( void ) pBuffer;
    ( void ) bytesToRead;
    return -1;
}

/**
 * @brief Mocked transport reading one byte at a time.
 */
static int32_t transportRecvOneByte( NetworkContext_t * pNetworkContext,
                                     void * pBuffer,
                                     size_t bytesToRead )
{
    ( void ) pNetworkContext;
    ( void ) pBuffer;
    ( void ) bytesToRead;
    return 1;
}

/**
 * @brief Mocked transport returning zero bytes to simulate reception
 * of no data over network.
 */
static int32_t transportRecvNoData( NetworkContext_t * pNetworkContext,
                                    void * pBuffer,
                                    size_t bytesToRead )
{
    ( void ) pNetworkContext;
    ( void ) pBuffer;
    ( void ) bytesToRead;
    return 0;
}

/**
 * @brief Initialize the transport interface with the mocked functions for
 * send and receive.
 *
 * @brief param[in] pTransport The transport interface to use with the context.
 */
static void setupTransportInterface( TransportInterface_t * pTransport )
{
    pTransport->pNetworkContext = MQTT_SAMPLE_NETWORK_CONTEXT;
    pTransport->send = transportSendSuccess;
    pTransport->recv = transportRecvSuccess;
    pTransport->writev = transportWritevSuccess;
}

/**
 * @brief Initialize pSubscribeInfo using test-defined macros.
 *
 * @param[in] pSubscribeInfo Pointer to MQTT subscription info.
 */
static void setupSubscriptionInfo( MQTTSubscribeInfo_t * pSubscribeInfo )
{
    pSubscribeInfo->qos = MQTTQoS1;
    pSubscribeInfo->pTopicFilter = MQTT_SAMPLE_TOPIC_FILTER;
    pSubscribeInfo->topicFilterLength = MQTT_SAMPLE_TOPIC_FILTER_LENGTH;
}

/**
 * @brief Zero out a #ProcessLoopReturns_t.
 *
 * @param[in] pExpectParams Pointer to struct to reset.
 */
static void resetProcessLoopParams( ProcessLoopReturns_t * pExpectParams )
{
    pExpectParams->deserializeStatus = MQTTSuccess;
    pExpectParams->stateAfterDeserialize = MQTTStateNull;
    pExpectParams->updateStateStatus = MQTTSuccess;
    pExpectParams->serializeStatus = MQTTSuccess;
    pExpectParams->stateAfterSerialize = MQTTStateNull;
    pExpectParams->processLoopStatus = MQTTSuccess;
    pExpectParams->incomingPublish = false;
    pExpectParams->pPubInfo = NULL;
    pExpectParams->timeoutMs = MQTT_NO_TIMEOUT_MS;
}

/**
 * @brief create default context
 *
 * @param[out] context to initialize
 */
static void setUPContext( MQTTContext_t * mqttContext )
{
    MQTTStatus_t mqttStatus;
    static TransportInterface_t transport = { 0 };
    static MQTTFixedBuffer_t networkBuffer = { 0 };
    static MQTTPubAckInfo_t incomingRecords[ 10 ] = { 0 };
    static MQTTPubAckInfo_t outgoingRecords[ 10 ] = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    memset( mqttContext, 0x0, sizeof( MQTTContext_t ) );

    mqttStatus = MQTT_Init( mqttContext,
                            &transport,
                            getTime,
                            eventCallback,
                            &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    mqttStatus = MQTT_InitStatefulQoS( mqttContext,
                                       outgoingRecords,
                                       10,
                                       incomingRecords,
                                       10 );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
}

/**
 * @brief This helper function is used to expect any calls from the process loop
 * to mocked functions belonging to an external header file. Its parameters
 * are used to provide return values for these mocked functions.
 */
static void expectProcessLoopCalls( MQTTContext_t * const pContext,
                                    ProcessLoopReturns_t * pExpectParams )
{
    MQTTStatus_t mqttStatus = MQTTSuccess;
    MQTTPacketInfo_t incomingPacket = { 0 };
    size_t pingreqSize = MQTT_PACKET_PINGREQ_SIZE;
    bool expectMoreCalls = true;
    /* Copy values passed in the parameter struct. */
    MQTTStatus_t deserializeStatus = pExpectParams->deserializeStatus;
    MQTTPublishState_t stateAfterDeserialize = pExpectParams->stateAfterDeserialize;
    MQTTStatus_t updateStateStatus = pExpectParams->updateStateStatus;
    MQTTStatus_t serializeStatus = pExpectParams->serializeStatus;
    MQTTPublishState_t stateAfterSerialize = pExpectParams->stateAfterSerialize;
    MQTTStatus_t processLoopStatus = pExpectParams->processLoopStatus;
    bool incomingPublish = pExpectParams->incomingPublish;
    MQTTPublishInfo_t * pPubInfo = pExpectParams->pPubInfo;
    uint32_t packetTxTimeoutMs = 0U;

    /* Modify incoming packet depending on type to be tested. */
    incomingPacket.type = currentPacketType;
    incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    incomingPacket.headerLength = MQTT_SAMPLE_REMAINING_LENGTH;

    MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );

    /* More calls are expected only with the following packet types. */
    if( ( currentPacketType != MQTT_PACKET_TYPE_PUBLISH ) &&
        ( currentPacketType != MQTT_PACKET_TYPE_PUBACK ) &&
        ( currentPacketType != MQTT_PACKET_TYPE_PUBREC ) &&
        ( currentPacketType != MQTT_PACKET_TYPE_PUBREL ) &&
        ( currentPacketType != MQTT_PACKET_TYPE_PUBCOMP ) &&
        ( currentPacketType != MQTT_PACKET_TYPE_PINGRESP ) &&
        ( currentPacketType != MQTT_PACKET_TYPE_SUBACK ) &&
        ( currentPacketType != MQTT_PACKET_TYPE_UNSUBACK ) )
    {
        expectMoreCalls = false;
    }

    /* When no data is available, the process loop tries to send a PINGREQ. */
    if( modifyIncomingPacketStatus == MQTTNoDataAvailable )
    {
        packetTxTimeoutMs = 1000U * ( uint32_t ) pContext->keepAliveIntervalSec;

        if( PACKET_TX_TIMEOUT_MS < packetTxTimeoutMs )
        {
            packetTxTimeoutMs = PACKET_TX_TIMEOUT_MS;
        }

        if( pContext->waitingForPingResp == false )
        {
            if( ( ( packetTxTimeoutMs != 0U ) &&
                  ( ( globalEntryTime - pContext->lastPacketTxTime ) >= packetTxTimeoutMs ) ) ||
                ( ( PACKET_RX_TIMEOUT_MS != 0U ) &&
                  ( ( globalEntryTime - pContext->lastPacketRxTime ) >= PACKET_RX_TIMEOUT_MS ) ) )
            {
                MQTT_GetPingreqPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
                /* Replace pointer parameter being passed to the method. */
                MQTT_GetPingreqPacketSize_ReturnThruPtr_pPacketSize( &pingreqSize );
                MQTT_SerializePingreq_ExpectAnyArgsAndReturn( serializeStatus );
            }
        }

        expectMoreCalls = false;
    }

    /* Deserialize based on the packet type (PUB or ACK) being received. */
    if( expectMoreCalls )
    {
        if( incomingPublish )
        {
            MQTT_DeserializePublish_ExpectAnyArgsAndReturn( deserializeStatus );

            if( pPubInfo != NULL )
            {
                MQTT_DeserializePublish_ReturnThruPtr_pPublishInfo( pPubInfo );
            }
        }
        else
        {
            MQTT_DeserializeAck_ExpectAnyArgsAndReturn( deserializeStatus );
        }

        if( ( deserializeStatus != MQTTSuccess ) ||
            ( currentPacketType == MQTT_PACKET_TYPE_PINGRESP ) ||
            ( currentPacketType == MQTT_PACKET_TYPE_SUBACK ) ||
            ( currentPacketType == MQTT_PACKET_TYPE_UNSUBACK ) )
        {
            expectMoreCalls = false;
        }
    }

    /* Update state based on the packet type (PUB or ACK) being received. */
    if( expectMoreCalls )
    {
        if( incomingPublish )
        {
            MQTT_UpdateStatePublish_ExpectAnyArgsAndReturn( updateStateStatus );
            MQTT_UpdateStatePublish_ReturnThruPtr_pNewState( &stateAfterDeserialize );
        }
        else
        {
            MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( updateStateStatus );
            MQTT_UpdateStateAck_ReturnThruPtr_pNewState( &stateAfterDeserialize );
        }

        if( stateAfterDeserialize == MQTTPublishDone )
        {
            expectMoreCalls = false;
        }
        else
        {
            switch( updateStateStatus )
            {
                case MQTTSuccess:
                    expectMoreCalls = true;
                    break;

                case MQTTStateCollision:
                    /* Execution will continue regardless of the dup flag. */
                    expectMoreCalls = true;
                    MQTT_CalculateStatePublish_ExpectAnyArgsAndReturn( stateAfterDeserialize );
                    break;

                default:
                    expectMoreCalls = false;
            }
        }
    }

    /* Serialize the packet to be sent in response to the received packet. */
    if( expectMoreCalls )
    {
        MQTT_SerializeAck_ExpectAnyArgsAndReturn( serializeStatus );

        if( serializeStatus != MQTTSuccess )
        {
            expectMoreCalls = false;
        }
    }

    /* Update the state based on the sent packet. */
    if( expectMoreCalls )
    {
        MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( ( stateAfterSerialize == MQTTStateNull ) ?
                                                    MQTTIllegalState : MQTTSuccess );
        MQTT_UpdateStateAck_ReturnThruPtr_pNewState( &stateAfterSerialize );
    }

    /* Expect the above calls when running MQTT_ProcessLoop. */

    mqttStatus = MQTT_ProcessLoop( pContext );
    TEST_ASSERT_EQUAL( processLoopStatus, mqttStatus );

    /* Any final assertions to end the test. */
    if( mqttStatus == MQTTSuccess )
    {
        if( currentPacketType == MQTT_PACKET_TYPE_PUBLISH )
        {
            TEST_ASSERT_TRUE( pContext->controlPacketSent );
        }

        if( currentPacketType == MQTT_PACKET_TYPE_PINGRESP )
        {
            TEST_ASSERT_FALSE( pContext->waitingForPingResp );
        }
    }
}

/* ========================================================================== */

/**
 * @brief Test that MQTT_Init is able to update the context object correctly.
 */
void test_MQTT_Init_Happy_Path( void )
{
    MQTTStatus_t mqttStatus = { 0 };
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
    TEST_ASSERT_EQUAL( MQTTNotConnected, context.connectStatus );
    TEST_ASSERT_EQUAL( MQTT_FIRST_VALID_PACKET_ID, context.nextPacketId );
    TEST_ASSERT_EQUAL_PTR( getTime, context.getTime );
    TEST_ASSERT_EQUAL_PTR( eventCallback, context.appCallback );
    /* These Unity assertions take pointers and compare their contents. */
    TEST_ASSERT_EQUAL_MEMORY( &transport, &context.transportInterface, sizeof( transport ) );
    TEST_ASSERT_EQUAL_MEMORY( &networkBuffer, &context.networkBuffer, sizeof( networkBuffer ) );
}

/**
 * @brief Test that any NULL parameter causes MQTT_Init to return MQTTBadParameter.
 */
void test_MQTT_Init_Invalid_Params( void )
{
    MQTTStatus_t mqttStatus = { 0 };
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    /* Check that MQTTBadParameter is returned if any NULL parameters are passed. */
    mqttStatus = MQTT_Init( NULL, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );

    mqttStatus = MQTT_Init( &context, NULL, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );

    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, NULL );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );

    /* Test if NULL is passed for any of the function pointers. */
    mqttStatus = MQTT_Init( &context, &transport, NULL, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );

    mqttStatus = MQTT_Init( &context, &transport, getTime, NULL, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );

    transport.recv = NULL;
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );

    transport.recv = transportRecvSuccess;
    transport.send = NULL;
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );
}

/* ========================================================================== */

static uint8_t * MQTT_SerializeConnectFixedHeader_cb( uint8_t * pIndex,
                                                      const MQTTConnectInfo_t * pConnectInfo,
                                                      const MQTTPublishInfo_t * pWillInfo,
                                                      size_t remainingLength,
                                                      int numcallbacks )
{
    ( void ) pConnectInfo;
    ( void ) pWillInfo;
    ( void ) remainingLength;
    ( void ) numcallbacks;

    return pIndex;
}

/**
 * @brief Test MQTT_Connect, except for receiving the CONNACK.
 */
void test_MQTT_Connect_sendConnect_writev_error( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTConnectInfo_t connectInfo = { 0 };
    uint32_t timeout = 2;
    bool sessionPresent;
    MQTTStatus_t status;
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    size_t remainingLength;
    size_t packetSize;

    setupTransportInterface( &transport );
    transport.writev = transportWritevError;
    setupNetworkBuffer( &networkBuffer );

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Test network send failure from timeout in calling transport send. */
    mqttContext.transportInterface.send = transportSendNoBytes; /* Use mock send that always returns zero bytes. */
    MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb );
    MQTT_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetConnectPacketSize_IgnoreArg_pPacketSize();
    MQTT_GetConnectPacketSize_IgnoreArg_pRemainingLength();
    MQTT_GetConnectPacketSize_ReturnThruPtr_pPacketSize( &packetSize );
    MQTT_GetConnectPacketSize_ReturnThruPtr_pRemainingLength( &remainingLength );

    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent );

    TEST_ASSERT_EQUAL_INT( MQTTSendFailed, status );
}

/**
 * @brief Test MQTT_Connect, except for receiving the CONNACK.
 */
void test_MQTT_Connect_sendConnect1( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTConnectInfo_t connectInfo = { 0 };
    uint32_t timeout = 2;
    bool sessionPresent;
    MQTTStatus_t status;
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    size_t remainingLength;
    size_t packetSize;

    setupTransportInterface( &transport );
    transport.writev = transportWritevFail;
    setupNetworkBuffer( &networkBuffer );

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Test network send failure from timeout in calling transport send. */
    mqttContext.transportInterface.send = transportSendNoBytes; /* Use mock send that always returns zero bytes. */
    MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb );
    MQTT_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetConnectPacketSize_IgnoreArg_pPacketSize();
    MQTT_GetConnectPacketSize_IgnoreArg_pRemainingLength();
    MQTT_GetConnectPacketSize_ReturnThruPtr_pPacketSize( &packetSize );
    MQTT_GetConnectPacketSize_ReturnThruPtr_pRemainingLength( &remainingLength );

    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent );

    TEST_ASSERT_EQUAL_INT( MQTTSendFailed, status );
}

/**
 * @brief Test MQTT_Connect, except for receiving the CONNACK.
 */
void test_MQTT_Connect_WillInfoWrong( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTConnectInfo_t connectInfo = { 0 };
    uint32_t timeout = 2;
    bool sessionPresent;
    MQTTStatus_t status;
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    size_t remainingLength;
    size_t packetSize;
    MQTTPublishInfo_t willInfo;

    setupTransportInterface( &transport );
    transport.writev = transportWritevFail;
    setupNetworkBuffer( &networkBuffer );

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &willInfo, 0, sizeof( MQTTPublishInfo_t ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Test network send failure from timeout in calling transport send. */
    mqttContext.transportInterface.send = transportSendNoBytes; /* Use mock send that always returns zero bytes. */
    MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb );
    MQTT_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetConnectPacketSize_IgnoreArg_pPacketSize();
    MQTT_GetConnectPacketSize_IgnoreArg_pRemainingLength();
    MQTT_GetConnectPacketSize_ReturnThruPtr_pPacketSize( &packetSize );
    MQTT_GetConnectPacketSize_ReturnThruPtr_pRemainingLength( &remainingLength );

    status = MQTT_Connect( &mqttContext, &connectInfo, &willInfo, timeout, &sessionPresent );

    TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status );
}

/**
 * @brief Test MQTT_Connect, except for receiving the CONNACK.
 */
void test_MQTT_Connect_sendConnect2( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTConnectInfo_t connectInfo = { 0 };
    uint32_t timeout = 2;
    bool sessionPresent;
    MQTTStatus_t status;
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    size_t remainingLength;
    size_t packetSize;

    setupTransportInterface( &transport );
    transport.writev = transportWritevFail;
    setupNetworkBuffer( &networkBuffer );

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Transport send failed when sending CONNECT. */

    /* Choose 10 bytes variable header + 1 byte payload for the remaining
     * length of the CONNECT. The packet size needs to be nonzero for this test
     * as that is the amount of bytes used in the call to send the packet. */
    packetSize = 13;
    remainingLength = 11;
    mqttContext.transportInterface.send = transportSendFailure;
    MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb );
    MQTT_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetConnectPacketSize_IgnoreArg_pPacketSize();
    MQTT_GetConnectPacketSize_IgnoreArg_pRemainingLength();
    MQTT_GetConnectPacketSize_ReturnThruPtr_pPacketSize( &packetSize );
    MQTT_GetConnectPacketSize_ReturnThruPtr_pRemainingLength( &remainingLength );
    MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess );

    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent );

    TEST_ASSERT_EQUAL_INT( MQTTSendFailed, status );
}

/**
 * @brief Test MQTT_Connect, except for receiving the CONNACK.
 */
void test_MQTT_Connect_ProperWIllInfo( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTConnectInfo_t connectInfo = { 0 };
    uint32_t timeout = 2;
    bool sessionPresent;
    MQTTStatus_t status;
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    size_t remainingLength;
    size_t packetSize;
    MQTTPublishInfo_t willInfo;

    setupTransportInterface( &transport );
    transport.writev = transportWritevFail;
    setupNetworkBuffer( &networkBuffer );

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &willInfo, 0, sizeof( MQTTPublishInfo_t ) );

    willInfo.pTopicName = "MQTTTopic";
    willInfo.topicNameLength = strlen( willInfo.pTopicName );
    willInfo.pPayload = "MQTTPayload";
    willInfo.payloadLength = strlen( willInfo.pPayload );

    connectInfo.pUserName = "MQTTUser";
    connectInfo.userNameLength = strlen( connectInfo.pUserName );
    connectInfo.pPassword = "NotSafePassword";
    connectInfo.passwordLength = strlen( connectInfo.pPassword );

    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Transport send failed when sending CONNECT. */

    /* Choose 10 bytes variable header + 1 byte payload for the remaining
     * length of the CONNECT. The packet size needs to be nonzero for this test
     * as that is the amount of bytes used in the call to send the packet. */
    packetSize = 13;
    remainingLength = 11;
    mqttContext.transportInterface.send = transportSendFailure;
    MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb );
    MQTT_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetConnectPacketSize_IgnoreArg_pPacketSize();
    MQTT_GetConnectPacketSize_IgnoreArg_pRemainingLength();
    MQTT_GetConnectPacketSize_ReturnThruPtr_pPacketSize( &packetSize );
    MQTT_GetConnectPacketSize_ReturnThruPtr_pRemainingLength( &remainingLength );
    MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess );

    status = MQTT_Connect( &mqttContext, &connectInfo, &willInfo, timeout, &sessionPresent );

    TEST_ASSERT_EQUAL_INT( MQTTSendFailed, status );
}

/**
 * @brief Test MQTT_Connect, with no payload in will message.
 */
void test_MQTT_Connect_ProperWIllInfoWithNoPayload( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTConnectInfo_t connectInfo = { 0 };
    uint32_t timeout = 2;
    bool sessionPresent;
    MQTTStatus_t status;
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    size_t remainingLength;
    size_t packetSize;
    MQTTPublishInfo_t willInfo;

    setupTransportInterface( &transport );
    transport.writev = transportWritevFail;
    setupNetworkBuffer( &networkBuffer );

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &willInfo, 0, sizeof( MQTTPublishInfo_t ) );

    willInfo.pTopicName = "MQTTTopic";
    willInfo.topicNameLength = strlen( willInfo.pTopicName );

    connectInfo.pUserName = "MQTTUser";
    connectInfo.userNameLength = strlen( connectInfo.pUserName );
    connectInfo.pPassword = "NotSafePassword";
    connectInfo.passwordLength = strlen( connectInfo.pPassword );

    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Transport send failed when sending CONNECT. */

    /* Choose 10 bytes variable header + 1 byte payload for the remaining
     * length of the CONNECT. The packet size needs to be nonzero for this test
     * as that is the amount of bytes used in the call to send the packet. */
    packetSize = 13;
    remainingLength = 11;
    mqttContext.transportInterface.send = transportSendFailure;
    MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb );
    MQTT_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetConnectPacketSize_IgnoreArg_pPacketSize();
    MQTT_GetConnectPacketSize_IgnoreArg_pRemainingLength();
    MQTT_GetConnectPacketSize_ReturnThruPtr_pPacketSize( &packetSize );
    MQTT_GetConnectPacketSize_ReturnThruPtr_pRemainingLength( &remainingLength );
    MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess );

    status = MQTT_Connect( &mqttContext, &connectInfo, &willInfo, timeout, &sessionPresent );

    TEST_ASSERT_EQUAL_INT( MQTTSendFailed, status );
}

/**
 * @brief Test MQTT_Connect, with payload in will message but the length is
 * zero.
 */
void test_MQTT_Connect_ProperWIllInfoWithPayloadButZeroLength( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTConnectInfo_t connectInfo = { 0 };
    uint32_t timeout = 2;
    bool sessionPresent;
    MQTTStatus_t status;
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    size_t remainingLength;
    size_t packetSize;
    MQTTPublishInfo_t willInfo;

    setupTransportInterface( &transport );
    transport.writev = transportWritevFail;
    setupNetworkBuffer( &networkBuffer );

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &willInfo, 0, sizeof( MQTTPublishInfo_t ) );

    willInfo.pTopicName = "MQTTTopic";
    willInfo.topicNameLength = strlen( willInfo.pTopicName );
    willInfo.pPayload = "Payload";
    willInfo.payloadLength = 0;

    connectInfo.pUserName = "MQTTUser";
    connectInfo.userNameLength = strlen( connectInfo.pUserName );
    connectInfo.pPassword = "NotSafePassword";
    connectInfo.passwordLength = strlen( connectInfo.pPassword );

    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Transport send failed when sending CONNECT. */

    /* Choose 10 bytes variable header + 1 byte payload for the remaining
     * length of the CONNECT. The packet size needs to be nonzero for this test
     * as that is the amount of bytes used in the call to send the packet. */
    packetSize = 13;
    remainingLength = 11;
    mqttContext.transportInterface.send = transportSendFailure;
    MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb );
    MQTT_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetConnectPacketSize_IgnoreArg_pPacketSize();
    MQTT_GetConnectPacketSize_IgnoreArg_pRemainingLength();
    MQTT_GetConnectPacketSize_ReturnThruPtr_pPacketSize( &packetSize );
    MQTT_GetConnectPacketSize_ReturnThruPtr_pRemainingLength( &remainingLength );
    MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess );

    status = MQTT_Connect( &mqttContext, &connectInfo, &willInfo, timeout, &sessionPresent );

    TEST_ASSERT_EQUAL_INT( MQTTSendFailed, status );
}

/**
 * @brief Test MQTT_Connect, except for receiving the CONNACK.
 */
void test_MQTT_Connect_sendConnect3( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTConnectInfo_t connectInfo = { 0 };
    uint32_t timeout = 2;
    bool sessionPresent;
    MQTTStatus_t status;
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Empty connect info fails. */
    MQTT_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTBadParameter );
    memset( &connectInfo, 0x0, sizeof( connectInfo ) );

    status = MQTT_Connect( &mqttContext,
                           &connectInfo,
                           NULL,
                           timeout,
                           &sessionPresent );

    TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status );
}

/**
 * @brief Test MQTT_Connect, except for receiving the CONNACK.
 */
void test_MQTT_Connect_sendConnect4( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTConnectInfo_t connectInfo = { 0 };
    uint32_t timeout = 2;
    bool sessionPresent;
    MQTTStatus_t status;
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Check parameters */
    status = MQTT_Connect( NULL, &connectInfo, NULL, timeout, &sessionPresent );
    TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status );

    status = MQTT_Connect( &mqttContext, NULL, NULL, timeout, &sessionPresent );
    TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status );

    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, NULL );
    TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status );
}

/**
 * @brief Test MQTT_Connect, except for receiving the CONNACK.
 */
void test_MQTT_Connect_sendConnect5( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTConnectInfo_t connectInfo = { 0 };
    uint32_t timeout = 2;
    bool sessionPresent;
    MQTTStatus_t status;
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    size_t remainingLength;
    size_t packetSize;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Send the CONNECT successfully. This provides branch coverage for sendPacket. */
    mqttContext.transportInterface.send = transportSendSuccess;
    MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb );
    MQTT_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetConnectPacketSize_ReturnThruPtr_pPacketSize( &packetSize );
    MQTT_GetConnectPacketSize_ReturnThruPtr_pRemainingLength( &remainingLength );

    /* We know the send was successful if MQTT_GetIncomingPacketTypeAndLength()
     * is called. */
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTRecvFailed );

    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent );

    TEST_ASSERT_EQUAL_INT( MQTTRecvFailed, status );
}

/**
 * @brief Test MQTT_Connect, except for receiving the CONNACK.
 */
void test_MQTT_Connect_sendConnect6( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTConnectInfo_t connectInfo = { 0 };
    uint32_t timeout = 2;
    bool sessionPresent;
    MQTTStatus_t status;
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    connectInfo.pClientIdentifier = MQTT_CLIENT_IDENTIFIER;
    connectInfo.clientIdentifierLength = sizeof( MQTT_CLIENT_IDENTIFIER ) - 1;

    MQTT_GetConnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb );
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( 2 );

    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent );

    TEST_ASSERT_EQUAL_INT( MQTTNoMemory, status );
}

/**
 * @brief Test CONNACK reception in MQTT_Connect.
 */
void test_MQTT_Connect_receiveConnack( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTConnectInfo_t connectInfo = { 0 };
    uint32_t timeout = 0;
    bool sessionPresent, sessionPresentExpected;
    MQTTStatus_t status;
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTPacketInfo_t incomingPacket = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    transport.recv = transportRecvFailure;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Everything before receiving the CONNACK should succeed. */
    MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess );
    MQTT_GetConnectPacketSize_IgnoreAndReturn( MQTTSuccess );

    /* Nothing received from transport interface. Set timeout to 2 for branch coverage. */
    timeout = 2;
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTNoDataAvailable );
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTNoDataAvailable );
    MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb );
    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent );
    TEST_ASSERT_EQUAL_INT( MQTTNoDataAvailable, status );

    /* Did not receive a CONNACK. */
    incomingPacket.type = MQTT_PACKET_TYPE_PINGRESP;
    incomingPacket.remainingLength = 0;
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent );
    TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status );

    /* Transport receive failure when receiving rest of packet. */
    incomingPacket.type = MQTT_PACKET_TYPE_CONNACK;
    incomingPacket.remainingLength = 2;
    timeout = 2;
    mqttContext.transportInterface.recv = transportRecvFailure;
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent );
    TEST_ASSERT_EQUAL_INT( MQTTRecvFailed, status );

    /* Bad response when deserializing CONNACK. */
    mqttContext.transportInterface.recv = transportRecvSuccess;
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTBadResponse );
    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent );
    TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status );

    /* Test case when broker sends session present flag in response to a
     * clean session connection request. */
    mqttContext.transportInterface.recv = transportRecvSuccess;
    connectInfo.cleanSession = true;
    sessionPresentExpected = true;
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_DeserializeAck_ReturnThruPtr_pSessionPresent( &sessionPresentExpected );
    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent );
    TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status );
}

/**
 * @brief Test CONNACK reception in MQTT_Connect.
 */
void test_MQTT_Connect_receiveConnack_retries( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTConnectInfo_t connectInfo = { 0 };
    bool sessionPresent;
    MQTTStatus_t status;
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTPacketInfo_t incomingPacket = { 0 };

    /* Same set of tests with retries. MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT is 2*/
    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    transport.recv = transportRecvFailure;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Everything before receiving the CONNACK should succeed. */
    MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess );
    MQTT_GetConnectPacketSize_IgnoreAndReturn( MQTTSuccess );

    /* Test with retries. MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT is 2.
     * Nothing received from transport interface. */
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTNoDataAvailable );
    /* 2 retries. */
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTNoDataAvailable );
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTNoDataAvailable );
    MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb );
    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, 0U, &sessionPresent );
    TEST_ASSERT_EQUAL_INT( MQTTNoDataAvailable, status );

    /* Did not receive a CONNACK. */
    incomingPacket.type = MQTT_PACKET_TYPE_PINGRESP;
    incomingPacket.remainingLength = 0;
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, 0U, &sessionPresent );
    TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status );

    /* Transport receive failure when receiving rest of packet. */
    incomingPacket.type = MQTT_PACKET_TYPE_CONNACK;
    incomingPacket.remainingLength = 2;
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, 0U, &sessionPresent );
    TEST_ASSERT_EQUAL_INT( MQTTRecvFailed, status );

    /* Bad response when deserializing CONNACK. */
    mqttContext.transportInterface.recv = transportRecvSuccess;
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTBadResponse );
    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, 0U, &sessionPresent );
    TEST_ASSERT_EQUAL_INT( MQTTBadResponse, status );
}

/**
 * @brief Test error cases for MQTT_Connect when a timeout occurs or the packet
 * needs to be discarded in MQTT_Connect.
 */
void test_MQTT_Connect_partial_receive( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTConnectInfo_t connectInfo = { 0 };
    uint32_t timeout = 0;
    bool sessionPresent;
    MQTTStatus_t status;
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTPacketInfo_t incomingPacket = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    transport.recv = transportRecvNoData;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Everything before receiving the CONNACK should succeed. */
    MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess );
    MQTT_GetConnectPacketSize_IgnoreAndReturn( MQTTSuccess );
    incomingPacket.type = MQTT_PACKET_TYPE_CONNACK;
    incomingPacket.remainingLength = 2;

    /* Timeout in receiving entire packet, for branch coverage. This is due to the fact that the mocked
     * receive function always returns 0 bytes read. */
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb );
    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent );
    TEST_ASSERT_EQUAL_INT( MQTTRecvFailed, status );

    /* Update to use mock receive function that receives one byte at a time for the
     * rest of the test. */
    mqttContext.transportInterface.recv = transportRecvOneByte;
    timeout = 10;

    /* Not enough space for packet, discard it. */
    mqttContext.networkBuffer.size = 2;
    incomingPacket.remainingLength = 3;
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent );
    TEST_ASSERT_EQUAL_INT( MQTTNoDataAvailable, status );

    /* Discard packet, no timeout provided. This should fail since multiple
     * iterations of the discard loop are required to discard the packet, but only
     * one will run. */
    mqttContext.transportInterface.recv = transportRecvSuccess;
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, 0, &sessionPresent );
    TEST_ASSERT_EQUAL_INT( MQTTRecvFailed, status );

    /* Timeout while discarding packet. */
    /* (Mocked) read only one byte at a time to ensure timeout will occur. */
    mqttContext.transportInterface.recv = transportRecvOneByte;
    incomingPacket.remainingLength = 20;
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent );
    TEST_ASSERT_EQUAL_INT( MQTTRecvFailed, status );

    /* Receive failure while discarding packet. */
    mqttContext.transportInterface.recv = transportRecvFailure;
    /* Test with dummy get time function to make sure there are no infinite loops. */
    mqttContext.getTime = getTimeDummy;
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, MQTT_NO_TIMEOUT_MS, &sessionPresent );
    TEST_ASSERT_EQUAL_INT( MQTTRecvFailed, status );
}

/**
 * @brief Test resend of pending acks in MQTT_Connect.
 */
void test_MQTT_Connect_resendPendingAcks( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTConnectInfo_t connectInfo = { 0 };
    uint32_t timeout = 2;
    bool sessionPresent, sessionPresentResult;
    MQTTStatus_t status;
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTPacketInfo_t incomingPacket = { 0 };
    uint16_t packetIdentifier = 1;
    MQTTPublishState_t pubRelState = MQTTPubRelSend;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &connectInfo, 0x00, sizeof( connectInfo ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess );
    MQTT_GetConnectPacketSize_IgnoreAndReturn( MQTTSuccess );
    connectInfo.keepAliveSeconds = MQTT_SAMPLE_KEEPALIVE_INTERVAL_S;

    /* Test 1. No packets to resend reestablishing a session. */
    /* successful receive CONNACK packet. */
    incomingPacket.type = MQTT_PACKET_TYPE_CONNACK;
    incomingPacket.remainingLength = 2;
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    /* Return with a session present flag. */
    sessionPresent = true;
    MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_DeserializeAck_ReturnThruPtr_pSessionPresent( &sessionPresent );
    /* No packets to resend. */
    MQTT_PubrelToResend_ExpectAnyArgsAndReturn( MQTT_PACKET_TYPE_INVALID );
    MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb );
    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresentResult );
    TEST_ASSERT_EQUAL_INT( MQTTSuccess, status );
    TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus );
    TEST_ASSERT_EQUAL_INT( connectInfo.keepAliveSeconds, mqttContext.keepAliveIntervalSec );
    TEST_ASSERT_TRUE( sessionPresentResult );

    /* Test 2. One packet found in ack pending state, but Sending ack failed. */
    sessionPresentResult = false;
    mqttContext.connectStatus = MQTTNotConnected;
    mqttContext.keepAliveIntervalSec = 0;
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_DeserializeAck_ReturnThruPtr_pSessionPresent( &sessionPresent );
    MQTT_PubrelToResend_ExpectAnyArgsAndReturn( packetIdentifier );
    MQTT_PubrelToResend_ReturnThruPtr_pState( &pubRelState );
    /* Serialize Ack failure. */
    MQTT_SerializeAck_ExpectAnyArgsAndReturn( MQTTBadParameter );
    MQTT_PubrelToResend_ExpectAnyArgsAndReturn( MQTT_PACKET_TYPE_INVALID );
    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresentResult );
    TEST_ASSERT_EQUAL_INT( MQTTSendFailed, status );
    TEST_ASSERT_EQUAL_INT( MQTTNotConnected, mqttContext.connectStatus );
    TEST_ASSERT_TRUE( sessionPresentResult );

    /* Test 3. One packet found in ack pending state, Sent
     * PUBREL successfully. */
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_DeserializeAck_ReturnThruPtr_pSessionPresent( &sessionPresent );
    MQTT_PubrelToResend_ExpectAnyArgsAndReturn( packetIdentifier );
    MQTT_PubrelToResend_ReturnThruPtr_pState( &pubRelState );
    /* Serialize Ack successful. */
    MQTT_SerializeAck_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( MQTTSuccess );
    /* Query for any remaining packets pending to ack. */
    MQTT_PubrelToResend_ExpectAnyArgsAndReturn( MQTT_PACKET_ID_INVALID );
    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent );
    TEST_ASSERT_EQUAL_INT( MQTTSuccess, status );
    TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus );
    TEST_ASSERT_EQUAL_INT( connectInfo.keepAliveSeconds, mqttContext.keepAliveIntervalSec );

    /* Test 4. Three packets found in ack pending state. Sent PUBREL successfully
     * for first and failed for second and no attempt for third. */
    mqttContext.keepAliveIntervalSec = 0;
    mqttContext.connectStatus = MQTTNotConnected;
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_DeserializeAck_ReturnThruPtr_pSessionPresent( &sessionPresent );
    /* First packet. */
    MQTT_PubrelToResend_ExpectAnyArgsAndReturn( packetIdentifier );
    MQTT_PubrelToResend_ReturnThruPtr_pState( &pubRelState );
    /* Serialize Ack successful. */
    MQTT_SerializeAck_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( MQTTSuccess );
    /* Second packet. */
    MQTT_PubrelToResend_ExpectAnyArgsAndReturn( packetIdentifier + 1 );
    MQTT_PubrelToResend_ReturnThruPtr_pState( &pubRelState );
    /* Serialize Ack failed. */
    MQTT_SerializeAck_ExpectAnyArgsAndReturn( MQTTBadParameter );
    /* Query for any remaining packets pending to ack. */
    MQTT_PubrelToResend_ExpectAnyArgsAndReturn( packetIdentifier + 2 );
    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent );
    TEST_ASSERT_EQUAL_INT( MQTTSendFailed, status );
    TEST_ASSERT_EQUAL_INT( MQTTNotConnected, mqttContext.connectStatus );

    /* Test 5. Two packets found in ack pending state. Sent PUBREL successfully
     * for first and failed for second. */
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_DeserializeAck_ReturnThruPtr_pSessionPresent( &sessionPresent );
    /* First packet. */
    MQTT_PubrelToResend_ExpectAnyArgsAndReturn( packetIdentifier );
    MQTT_PubrelToResend_ReturnThruPtr_pState( &pubRelState );
    /* Serialize Ack successful. */
    MQTT_SerializeAck_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( MQTTSuccess );
    /* Second packet. */
    MQTT_PubrelToResend_ExpectAnyArgsAndReturn( packetIdentifier + 1 );
    MQTT_PubrelToResend_ReturnThruPtr_pState( &pubRelState );
    /* Serialize Ack successful. */
    MQTT_SerializeAck_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( MQTTSuccess );
    /* Query for any remaining packets pending to ack. */
    MQTT_PubrelToResend_ExpectAnyArgsAndReturn( MQTT_PACKET_ID_INVALID );
    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent );
    TEST_ASSERT_EQUAL_INT( MQTTSuccess, status );
    TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus );
    TEST_ASSERT_EQUAL_INT( connectInfo.keepAliveSeconds, mqttContext.keepAliveIntervalSec );
}

/**
 * @brief Test success case for MQTT_Connect().
 */

#define MQTT_STATE_ARRAY_MAX_COUNT    1

void test_MQTT_Connect_happy_path1()
{
    MQTTContext_t mqttContext = { 0 };
    MQTTConnectInfo_t connectInfo = { 0 };
    bool sessionPresent;
    MQTTStatus_t status;
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTPacketInfo_t incomingPacket = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );


    /* CONNACK receive with timeoutMs=0. Retry logic will be used. */
    mqttContext.connectStatus = MQTTNotConnected;
    mqttContext.keepAliveIntervalSec = 0;
    sessionPresent = false;
    incomingPacket.type = MQTT_PACKET_TYPE_CONNACK;
    incomingPacket.remainingLength = 2;
    MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess );
    MQTT_GetConnectPacketSize_IgnoreAndReturn( MQTTSuccess );
    MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb );
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    MQTT_DeserializeAck_IgnoreAndReturn( MQTTSuccess );
    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, 0U, &sessionPresent );
    TEST_ASSERT_EQUAL_INT( MQTTSuccess, status );
    TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus );
    TEST_ASSERT_EQUAL_INT( connectInfo.keepAliveSeconds, mqttContext.keepAliveIntervalSec );
    TEST_ASSERT_FALSE( mqttContext.waitingForPingResp );
}

/**
 * @brief Test success case for MQTT_Connect().
 */
void test_MQTT_Connect_happy_path2()
{
    MQTTContext_t mqttContext = { 0 };
    MQTTConnectInfo_t connectInfo = { 0 };
    uint32_t timeout = 2;
    bool sessionPresent;
    MQTTStatus_t status;
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTPacketInfo_t incomingPacket = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    connectInfo.keepAliveSeconds = MQTT_SAMPLE_KEEPALIVE_INTERVAL_S;
    MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess );
    MQTT_GetConnectPacketSize_IgnoreAndReturn( MQTTSuccess );
    MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb );
    /* Success. */
    incomingPacket.type = MQTT_PACKET_TYPE_CONNACK;
    incomingPacket.remainingLength = 2;
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    MQTT_DeserializeAck_IgnoreAndReturn( MQTTSuccess );
    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent );
    TEST_ASSERT_EQUAL_INT( MQTTSuccess, status );
    TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus );
    TEST_ASSERT_EQUAL_INT( connectInfo.keepAliveSeconds, mqttContext.keepAliveIntervalSec );
}

/**
 * @brief Test success case for MQTT_Connect().
 */
void test_MQTT_Connect_happy_path3()
{
    MQTTContext_t mqttContext = { 0 };
    MQTTConnectInfo_t connectInfo = { 0 };
    MQTTPublishInfo_t willInfo = { 0 };
    uint32_t timeout = 2;
    bool sessionPresent;
    MQTTStatus_t status;
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTPacketInfo_t incomingPacket = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &willInfo, 0x0, sizeof( MQTTPublishInfo_t ) );
    memset( &connectInfo, 0x0, sizeof( MQTTConnectInfo_t ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* With non-NULL Will. */
    mqttContext.connectStatus = MQTTNotConnected;
    mqttContext.keepAliveIntervalSec = 0;

    willInfo.pTopicName = "test";
    willInfo.topicNameLength = 4;
    willInfo.pPayload = "Payload";
    willInfo.payloadLength = 7;
    incomingPacket.type = MQTT_PACKET_TYPE_CONNACK;
    MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess );
    MQTT_GetConnectPacketSize_IgnoreAndReturn( MQTTSuccess );
    MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb );
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess );

    status = MQTT_Connect( &mqttContext, &connectInfo, &willInfo, timeout, &sessionPresent );

    TEST_ASSERT_EQUAL_INT( MQTTSuccess, status );
    TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus );
    TEST_ASSERT_EQUAL_INT( connectInfo.keepAliveSeconds, mqttContext.keepAliveIntervalSec );
}

/**
 * @brief Test success case for MQTT_Connect().
 */
void test_MQTT_Connect_happy_path4()
{
    MQTTContext_t mqttContext = { 0 };
    MQTTConnectInfo_t connectInfo = { 0 };
    uint32_t timeout = 2;
    bool sessionPresent;
    bool sessionPresentExpected;
    MQTTStatus_t status;
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTPacketInfo_t incomingPacket = { 0 };
    MQTTPubAckInfo_t incomingRecords[ 10 ] = { 0 };
    MQTTPubAckInfo_t outgoingRecords[ 10 ] = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );
    MQTT_InitStatefulQoS( &mqttContext,
                          outgoingRecords, 10,
                          incomingRecords, 10 );

    /* Request to establish a clean session. */
    mqttContext.connectStatus = MQTTNotConnected;
    mqttContext.keepAliveIntervalSec = 0;
    connectInfo.cleanSession = true;
    sessionPresentExpected = false;

    /* Populate some state records to make sure they are cleared since a clean session
     * will be established. */
    mqttContext.outgoingPublishRecords[ 0 ].packetId = 1;
    mqttContext.outgoingPublishRecords[ 0 ].qos = MQTTQoS2;
    mqttContext.outgoingPublishRecords[ 0 ].publishState = MQTTPublishSend;
    mqttContext.incomingPublishRecords[ MQTT_STATE_ARRAY_MAX_COUNT - 1 ].packetId = 1;
    mqttContext.networkBuffer.size = 5000;

    incomingPacket.type = MQTT_PACKET_TYPE_CONNACK;
    incomingPacket.remainingLength = 2;

    /* Set ping response flag to true to ensure it will be cleared. */
    mqttContext.waitingForPingResp = true;
    MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess );
    MQTT_GetConnectPacketSize_IgnoreAndReturn( MQTTSuccess );
    MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb );
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_DeserializeAck_ReturnThruPtr_pSessionPresent( &sessionPresentExpected );
    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent );
    TEST_ASSERT_EQUAL_INT( MQTTSuccess, status );
    TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus );
    TEST_ASSERT_EQUAL_INT( connectInfo.keepAliveSeconds, mqttContext.keepAliveIntervalSec );
    TEST_ASSERT_FALSE( mqttContext.waitingForPingResp );
    TEST_ASSERT_FALSE( sessionPresent );
    /* Test old records were cleared. */
    TEST_ASSERT_EQUAL_MEMORY( &outgoingRecords,
                              mqttContext.outgoingPublishRecords,
                              sizeof( outgoingRecords ) );
    TEST_ASSERT_EQUAL_MEMORY( &incomingRecords,
                              mqttContext.incomingPublishRecords,
                              sizeof( incomingRecords ) );
}

/**
 * @brief Test success case for MQTT_Connect().
 */
void test_MQTT_Connect_happy_path5()
{
    MQTTContext_t mqttContext = { 0 };
    MQTTConnectInfo_t connectInfo = { 0 };
    uint32_t timeout = 2;
    bool sessionPresent, sessionPresentExpected;
    MQTTStatus_t status;
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTPacketInfo_t incomingPacket = { 0 };


    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Request to establish a session if present and session present is received
     * from broker. */
    mqttContext.connectStatus = MQTTNotConnected;
    mqttContext.keepAliveIntervalSec = 0;
    mqttContext.waitingForPingResp = true;
    connectInfo.cleanSession = false;
    sessionPresentExpected = true;

    incomingPacket.type = MQTT_PACKET_TYPE_CONNACK;

    MQTT_SerializeConnect_IgnoreAndReturn( MQTTSuccess );
    MQTT_GetConnectPacketSize_IgnoreAndReturn( MQTTSuccess );
    MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb );
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_DeserializeAck_ReturnThruPtr_pSessionPresent( &sessionPresentExpected );
    MQTT_PubrelToResend_ExpectAnyArgsAndReturn( MQTT_PACKET_TYPE_INVALID );

    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, timeout, &sessionPresent );

    TEST_ASSERT_EQUAL_INT( MQTTSuccess, status );
    TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus );
    TEST_ASSERT_EQUAL_INT( connectInfo.keepAliveSeconds, mqttContext.keepAliveIntervalSec );
    TEST_ASSERT_TRUE( sessionPresent );
    TEST_ASSERT_FALSE( mqttContext.waitingForPingResp );
}

/**
 * @brief Test success case for MQTT_Connect().
 */
void test_MQTT_Connect_happy_path6()
{
    MQTTContext_t mqttContext = { 0 };
    MQTTConnectInfo_t connectInfo = { 0 };
    bool sessionPresent = false;
    MQTTStatus_t status;
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTPacketInfo_t incomingPacket = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* CONNACK receive with timeoutMs=0. Retry logic will  be used.
     * #MQTTNoDataAvailable for first #MQTT_GetIncomingPacketTypeAndLength
     * and success in the second time. */
    mqttContext.connectStatus = MQTTNotConnected;
    mqttContext.keepAliveIntervalSec = 0;
    incomingPacket.type = MQTT_PACKET_TYPE_CONNACK;
    incomingPacket.remainingLength = 2;

    MQTT_GetConnectPacketSize_IgnoreAndReturn( MQTTSuccess );
    MQTT_SerializeConnectFixedHeader_Stub( MQTT_SerializeConnectFixedHeader_cb );
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTNoDataAvailable );
    MQTT_GetIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    MQTT_DeserializeAck_IgnoreAndReturn( MQTTSuccess );

    status = MQTT_Connect( &mqttContext, &connectInfo, NULL, 0U, &sessionPresent );

    TEST_ASSERT_EQUAL_INT( MQTTSuccess, status );
    TEST_ASSERT_EQUAL_INT( MQTTConnected, mqttContext.connectStatus );
    TEST_ASSERT_EQUAL_INT( connectInfo.keepAliveSeconds, mqttContext.keepAliveIntervalSec );
    TEST_ASSERT_FALSE( mqttContext.waitingForPingResp );
}

/* ========================================================================== */

/**
 * @brief Test that MQTT_Publish works as intended.
 */
void test_MQTT_Publish( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTPublishInfo_t publishInfo = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t status;

    const uint16_t PACKET_ID = 1;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    transport.send = transportSendFailure;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &publishInfo, 0x0, sizeof( publishInfo ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Verify parameters. */
    status = MQTT_Publish( NULL, &publishInfo, PACKET_ID );
    TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status );
}

/**
 * @brief Test that MQTT_Publish works as intended.
 */
void test_MQTT_Publish2( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTPublishInfo_t publishInfo = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t status;

    const uint16_t PACKET_ID = 1;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    transport.send = transportSendFailure;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &publishInfo, 0x0, sizeof( publishInfo ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    status = MQTT_Publish( &mqttContext, NULL, PACKET_ID );
    TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status );
}

/**
 * @brief Test that MQTT_Publish works as intended.
 */
void test_MQTT_Publish3( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTPublishInfo_t publishInfo = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t status;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    transport.send = transportSendFailure;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &publishInfo, 0x0, sizeof( publishInfo ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    publishInfo.qos = MQTTQoS1;
    status = MQTT_Publish( &mqttContext, &publishInfo, 0 );
    TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status );
}

/**
 * @brief Test that MQTT_Publish works as intended.
 */
void test_MQTT_Publish4( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTPublishInfo_t publishInfo = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t status;

    const uint16_t PACKET_ID = 1;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    transport.send = transportSendFailure;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &publishInfo, 0x0, sizeof( publishInfo ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    publishInfo.payloadLength = 1;
    publishInfo.pPayload = NULL;
    status = MQTT_Publish( &mqttContext, &publishInfo, PACKET_ID );
    TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status );
}

/**
 * @brief Test that MQTT_Publish works as intended.
 */
void test_MQTT_Publish5( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTPublishInfo_t publishInfo = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t status;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    transport.send = transportSendFailure;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &publishInfo, 0x0, sizeof( publishInfo ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Bad Parameter when getting packet size. */
    publishInfo.qos = MQTTQoS0;
    MQTT_GetPublishPacketSize_ExpectAnyArgsAndReturn( MQTTBadParameter );
    status = MQTT_Publish( &mqttContext, &publishInfo, 0 );
    TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status );
}

/**
 * @brief Test that MQTT_Publish works as intended.
 */
void test_MQTT_Publish6( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTPublishInfo_t publishInfo = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t status;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    transport.send = transportSendFailure;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &publishInfo, 0x0, sizeof( publishInfo ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Always return success from now on. */
    MQTT_GetPublishPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_SerializePublishHeaderWithoutTopic_ExpectAnyArgsAndReturn( MQTTNoMemory );
    status = MQTT_Publish( &mqttContext, &publishInfo, 0 );
    TEST_ASSERT_EQUAL_INT( MQTTNoMemory, status );
}

/**
 * @brief Test that MQTT_Publish works as intended.
 */
void test_MQTT_Publish_DuplicatePublish( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTPublishInfo_t publishInfo = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t status;
    MQTTPubAckInfo_t outgoingPublishRecord[ 10 ];

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    transport.send = transportSendFailure;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &publishInfo, 0x0, sizeof( publishInfo ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Always return success from now on. */
    MQTT_GetPublishPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_SerializePublishHeaderWithoutTopic_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_ReserveState_ExpectAnyArgsAndReturn( MQTTStateCollision );
    MQTT_UpdateStatePublish_ExpectAnyArgsAndReturn( MQTTSuccess );

    mqttContext.outgoingPublishRecordMaxCount = 10;
    mqttContext.outgoingPublishRecords = outgoingPublishRecord;

    publishInfo.qos = MQTTQoS1;
    publishInfo.dup = true;
    publishInfo.pPayload = "TestPublish";
    publishInfo.payloadLength = strlen( publishInfo.pPayload );
    publishInfo.pTopicName = "TestTopic";
    publishInfo.topicNameLength = strlen( publishInfo.pTopicName );

    status = MQTT_Publish( &mqttContext, &publishInfo, 10 );

    TEST_ASSERT_EQUAL_INT( MQTTSuccess, status );
}

/**
 * @brief Test that MQTT_Publish works as intended.
 */
void test_MQTT_Publish_DuplicatePublish_UpdateFailed( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTPublishInfo_t publishInfo = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t status;
    MQTTPubAckInfo_t outgoingPublishRecord[ 10 ];

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    transport.send = transportSendFailure;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &publishInfo, 0x0, sizeof( publishInfo ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Always return success from now on. */
    MQTT_GetPublishPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_SerializePublishHeaderWithoutTopic_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_ReserveState_ExpectAnyArgsAndReturn( MQTTStateCollision );
    MQTT_UpdateStatePublish_ExpectAnyArgsAndReturn( MQTTSendFailed );

    mqttContext.outgoingPublishRecordMaxCount = 10;
    mqttContext.outgoingPublishRecords = outgoingPublishRecord;

    publishInfo.qos = MQTTQoS1;
    publishInfo.dup = true;
    publishInfo.pPayload = "TestPublish";
    publishInfo.payloadLength = strlen( publishInfo.pPayload );
    publishInfo.pTopicName = "TestTopic";
    publishInfo.topicNameLength = strlen( publishInfo.pTopicName );

    status = MQTT_Publish( &mqttContext, &publishInfo, 10 );

    TEST_ASSERT_EQUAL_INT( MQTTSendFailed, status );
}

/**
 * @brief Test that MQTT_Publish works as intended.
 */
void test_MQTT_Publish_WriteVSendsPartialBytes( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTPublishInfo_t publishInfo = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t status;
    MQTTPubAckInfo_t outgoingPublishRecord[ 10 ];
    size_t headerLen = 5;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    transport.writev = transportWritevPartialByte;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &publishInfo, 0x0, sizeof( publishInfo ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Always return success from now on. */
    MQTT_GetPublishPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_SerializePublishHeaderWithoutTopic_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_SerializePublishHeaderWithoutTopic_ReturnThruPtr_headerSize( &headerLen );
    MQTT_ReserveState_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_UpdateStatePublish_ExpectAnyArgsAndReturn( MQTTSuccess );

    mqttContext.outgoingPublishRecordMaxCount = 10;
    mqttContext.outgoingPublishRecords = outgoingPublishRecord;

    publishInfo.qos = MQTTQoS1;
    publishInfo.dup = false;
    publishInfo.pPayload = "TestPublish";
    publishInfo.payloadLength = strlen( publishInfo.pPayload );
    publishInfo.pTopicName = "TestTopic";
    publishInfo.topicNameLength = strlen( publishInfo.pTopicName );

    status = MQTT_Publish( &mqttContext, &publishInfo, 10 );

    TEST_ASSERT_EQUAL_INT( MQTTSuccess, status );
}

/**
 * @brief Test that MQTT_Publish works as intended.
 */
void test_MQTT_Publish7( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTPublishInfo_t publishInfo = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t status;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    transport.writev = transportWritevFail;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &publishInfo, 0x0, sizeof( publishInfo ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    MQTT_GetPublishPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_SerializePublishHeaderWithoutTopic_ExpectAnyArgsAndReturn( MQTTSuccess );

    /* We need sendPacket to be called with at least 1 byte to send, so that
     * it can return failure. This argument is the output of serializing the
     * publish header. */
    publishInfo.pPayload = "Test";
    publishInfo.payloadLength = 4;
    status = MQTT_Publish( &mqttContext, &publishInfo, 0 );
    TEST_ASSERT_EQUAL_INT( MQTTSendFailed, status );
}

/**
 * @brief Test that MQTT_Publish works as intended.
 */
void test_MQTT_Publish8( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTPublishInfo_t publishInfo = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t status;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    transport.writev = transportWritevFail;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &publishInfo, 0x0, sizeof( publishInfo ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* We want to test the first call to sendPacket within sendPublish succeeding,
     * and the second one failing. */
    mqttContext.transportInterface.send = transportSendSucceedThenFail;
    MQTT_GetPublishPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_SerializePublishHeaderWithoutTopic_ExpectAnyArgsAndReturn( MQTTSuccess );
    publishInfo.pPayload = "Test";
    publishInfo.payloadLength = 4;
    status = MQTT_Publish( &mqttContext, &publishInfo, 0 );
    TEST_ASSERT_EQUAL_INT( MQTTSendFailed, status );
}

/**
 * @brief Test that MQTT_Publish works as intended.
 */
void test_MQTT_Publish9( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTPublishInfo_t publishInfo = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t status;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    transport.send = transportSendFailure;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &publishInfo, 0x0, sizeof( publishInfo ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    MQTT_GetPublishPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_SerializePublishHeaderWithoutTopic_ExpectAnyArgsAndReturn( MQTTSuccess );
    mqttContext.transportInterface.send = transportSendSuccess;
    status = MQTT_Publish( &mqttContext, &publishInfo, 0 );
    TEST_ASSERT_EQUAL_INT( MQTTSuccess, status );
}

/**
 * @brief Test that MQTT_Publish works as intended.
 */
void test_MQTT_Publish10( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTPublishInfo_t publishInfo = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t status;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    transport.send = transportSendFailure;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &publishInfo, 0x0, sizeof( publishInfo ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Test that sending a publish without a payload succeeds. */
    publishInfo.pPayload = NULL;
    publishInfo.payloadLength = 0;
    MQTT_GetPublishPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_SerializePublishHeaderWithoutTopic_ExpectAnyArgsAndReturn( MQTTSuccess );
    status = MQTT_Publish( &mqttContext, &publishInfo, 0 );
    TEST_ASSERT_EQUAL_INT( MQTTSuccess, status );
}

/**
 * @brief Test that MQTT_Publish works as intended.
 */
void test_MQTT_Publish11( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTPublishInfo_t publishInfo = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t status;
    const uint16_t PACKET_ID = 1;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    transport.send = transportSendFailure;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &publishInfo, 0x0, sizeof( publishInfo ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Restore the test payload and length. */
    publishInfo.pPayload = "Test";
    publishInfo.payloadLength = 4;
    /* Now for non zero QoS, which uses state engine. */
    publishInfo.qos = MQTTQoS2;

    status = MQTT_Publish( &mqttContext, &publishInfo, PACKET_ID );

    TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status );
}

/**
 * @brief Test that MQTT_Publish works as intended.
 */
void test_MQTT_Publish12( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTPublishInfo_t publishInfo = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t status;
    MQTTPublishState_t expectedState = { 0 };

    MQTTPubAckInfo_t incomingRecords = { 0 };
    MQTTPubAckInfo_t outgoingRecords = { 0 };

    const uint16_t PACKET_ID = 1;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    transport.send = transportSendFailure;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &publishInfo, 0x0, sizeof( publishInfo ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    publishInfo.qos = MQTTQoS1;

    MQTT_InitStatefulQoS( &mqttContext,
                          &outgoingRecords, 4,
                          &incomingRecords, 4 );

    mqttContext.outgoingPublishRecords[ 0 ].packetId = 1;
    mqttContext.outgoingPublishRecords[ 0 ].qos = MQTTQoS2;
    mqttContext.outgoingPublishRecords[ 0 ].publishState = MQTTPublishSend;
    mqttContext.outgoingPublishRecords->packetId = 1;

    mqttContext.outgoingPublishRecords->qos = MQTTQoS2;

    expectedState = MQTTPublishSend;

    MQTT_GetPublishPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_SerializePublishHeaderWithoutTopic_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_ReserveState_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_UpdateStatePublish_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_UpdateStatePublish_ReturnThruPtr_pNewState( &expectedState );

    status = MQTT_Publish( &mqttContext, &publishInfo, PACKET_ID );

    TEST_ASSERT_EQUAL_INT( MQTTSuccess, status );
}

/**
 * @brief Test that MQTT_Publish works as intended.
 */
void test_MQTT_Publish13( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTPublishInfo_t publishInfo = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t status;
    MQTTPubAckInfo_t incomingRecords = { 0 };
    MQTTPubAckInfo_t outgoingRecords = { 0 };

    const uint16_t PACKET_ID = 1;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    transport.send = transportSendFailure;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &publishInfo, 0x0, sizeof( publishInfo ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    MQTT_InitStatefulQoS( &mqttContext,
                          &outgoingRecords, 4,
                          &incomingRecords, 4 );

    publishInfo.qos = MQTTQoS1;

    MQTT_GetPublishPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_SerializePublishHeaderWithoutTopic_ExpectAnyArgsAndReturn( MQTTSuccess );
    /* Duplicate publish. dup flag is not marked by application. */
    MQTT_ReserveState_ExpectAnyArgsAndReturn( MQTTStateCollision );
    status = MQTT_Publish( &mqttContext, &publishInfo, PACKET_ID );
    TEST_ASSERT_EQUAL_INT( MQTTStateCollision, status );
}

/**
 * @brief Test that MQTT_Publish works as intended.
 */
void test_MQTT_Publish14( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTPublishInfo_t publishInfo = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t status;

    const uint16_t PACKET_ID = 1;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    transport.send = transportSendFailure;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &publishInfo, 0x0, sizeof( publishInfo ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Duplicate publish. dup flag is marked by application. */
    publishInfo.dup = true;

    MQTT_GetPublishPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_SerializePublishHeaderWithoutTopic_ExpectAnyArgsAndReturn( MQTTSuccess );

    status = MQTT_Publish( &mqttContext, &publishInfo, PACKET_ID );
    TEST_ASSERT_EQUAL_INT( MQTTSuccess, status );
}

/**
 * @brief Test that MQTT_Publish works as intended.
 */
void test_MQTT_Publish15( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTPublishInfo_t publishInfo = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t status;
    const uint16_t PACKET_ID = 1;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    transport.writev = transportWritevSuccess;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    memset( &publishInfo, 0x0, sizeof( publishInfo ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Duplicate publish. dup flag is marked by application.
     * State record is not present. */
    publishInfo.dup = true;
    MQTT_GetPublishPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_SerializePublishHeaderWithoutTopic_ExpectAnyArgsAndReturn( MQTTSuccess );

    status = MQTT_Publish( &mqttContext, &publishInfo, PACKET_ID );

    TEST_ASSERT_EQUAL_INT( MQTTSuccess, status );
}

/**
 * @brief Test that verifies that the MQTT_Publish API detects a timeout
 * and returns failure when the transport send function is unable to send any data
 * over the network.
 */
void test_MQTT_Publish_Send_Timeout( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTPublishInfo_t publishInfo = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t status;

    setupNetworkBuffer( &networkBuffer );
    setupTransportInterface( &transport );
    transport.writev = transportWritevFail;

    /* Set the transport send function to the mock that always returns zero
     * bytes for the test. */
    transport.send = transportSendNoBytes;

    /* Initialize the MQTT context. */
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Setup for making sure that the test results in calling sendPacket function
     * where calls to transport send function are made (repeatedly to send packet
     * over the network).*/
    memset( &publishInfo, 0, sizeof( MQTTPublishInfo_t ) );
    publishInfo.pPayload = "Test";
    publishInfo.payloadLength = 4;
    MQTT_GetPublishPacketSize_IgnoreAndReturn( MQTTSuccess );
    MQTT_SerializePublishHeaderWithoutTopic_ExpectAnyArgsAndReturn( MQTTSuccess );

    /* Call the API function under test and expect that it detects a timeout in sending
     * MQTT packet over the network. */
    status = MQTT_Publish( &mqttContext, &publishInfo, 0 );
    TEST_ASSERT_EQUAL_INT( MQTTSendFailed, status );
}

/* ========================================================================== */

/**
 * @brief Test that MQTT_Disconnect works as intended.
 */
void test_MQTT_Disconnect1( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTStatus_t status;
    uint8_t buffer[ 10 ];
    uint8_t * bufPtr = buffer;
    NetworkContext_t networkContext = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    networkContext.buffer = &bufPtr;
    transport.pNetworkContext = &networkContext;
    transport.recv = transportRecvSuccess;
    transport.send = transportSendFailure;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );
    mqttContext.connectStatus = MQTTConnected;

    /* Verify parameters. */
    status = MQTT_Disconnect( NULL );
    TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status );
}

/**
 * @brief Test that MQTT_Disconnect works as intended.
 */
void test_MQTT_Disconnect2( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTStatus_t status;
    uint8_t buffer[ 10 ];
    uint8_t * bufPtr = buffer;
    NetworkContext_t networkContext = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    size_t disconnectSize = 2;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    networkContext.buffer = &bufPtr;
    transport.pNetworkContext = &networkContext;
    transport.recv = transportRecvSuccess;
    transport.send = transportSendFailure;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );
    mqttContext.connectStatus = MQTTConnected;

    /* Send failure with network error. */
    MQTT_GetDisconnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetDisconnectPacketSize_ReturnThruPtr_pPacketSize( &disconnectSize );
    MQTT_SerializeDisconnect_ExpectAnyArgsAndReturn( MQTTSuccess );
    status = MQTT_Disconnect( &mqttContext );
    TEST_ASSERT_EQUAL( MQTTSendFailed, status );
}

/**
 * @brief Test that MQTT_Disconnect works as intended.
 */
void test_MQTT_Disconnect3( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTStatus_t status;
    uint8_t buffer[ 10 ];
    uint8_t * bufPtr = buffer;
    NetworkContext_t networkContext = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    size_t disconnectSize = 2;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    networkContext.buffer = &bufPtr;
    transport.pNetworkContext = &networkContext;
    transport.recv = transportRecvSuccess;
    transport.send = transportSendFailure;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );
    mqttContext.connectStatus = MQTTConnected;

    /* Send failure with timeout in calling transport send. */
    transport.send = transportSendNoBytes;
    MQTT_GetDisconnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetDisconnectPacketSize_ReturnThruPtr_pPacketSize( &disconnectSize );
    MQTT_SerializeDisconnect_ExpectAnyArgsAndReturn( MQTTSuccess );
    status = MQTT_Disconnect( &mqttContext );
    TEST_ASSERT_EQUAL( MQTTSendFailed, status );
}

MQTTStatus_t MQTT_SerializeDisconnect_stub( const MQTTFixedBuffer_t * pFixedBuffer,
                                            int numcalls )
{
    ( void ) numcalls;

    pFixedBuffer->pBuffer[ 0 ] = MQTT_PACKET_TYPE_DISCONNECT;
    pFixedBuffer->pBuffer[ 1 ] = 0;

    return MQTTSuccess;
}

/**
 * @brief Test that MQTT_Disconnect works as intended.
 */
void test_MQTT_Disconnect4( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTStatus_t status;
    uint8_t buffer[ 10 ];
    uint8_t * bufPtr = buffer;
    NetworkContext_t networkContext = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    size_t disconnectSize = 2;

    /* Fill the buffer with garbage data. */
    memset( mqttBuffer, 0xAB, MQTT_TEST_BUFFER_LENGTH );

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    networkContext.buffer = &bufPtr;
    transport.pNetworkContext = &networkContext;
    transport.recv = transportRecvSuccess;
    transport.send = transportSendFailure;
    transport.writev = NULL;

    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );
    mqttContext.connectStatus = MQTTConnected;

    /* Successful send. */
    mqttContext.transportInterface.send = mockSend;
    MQTT_GetDisconnectPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetDisconnectPacketSize_ReturnThruPtr_pPacketSize( &disconnectSize );
    MQTT_SerializeDisconnect_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_SerializeDisconnect_Stub( MQTT_SerializeDisconnect_stub );
    /* Write a disconnect packet into the buffer. */
    mqttBuffer[ 0 ] = MQTT_PACKET_TYPE_DISCONNECT;

    status = MQTT_Disconnect( &mqttContext );

    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    TEST_ASSERT_EQUAL( MQTTNotConnected, mqttContext.connectStatus );
    /* At disconnect, the buffer is cleared of any pending packets. */
    TEST_ASSERT_EACH_EQUAL_UINT8( 0, mqttBuffer, MQTT_TEST_BUFFER_LENGTH );
}
/* ========================================================================== */

/**
 * @brief Test that MQTT_GetPacketId works as intended.
 */
void test_MQTT_GetPacketId( void )
{
    MQTTContext_t mqttContext = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    uint16_t packetId;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    memset( &mqttContext, 0x0, sizeof( mqttContext ) );
    MQTT_Init( &mqttContext, &transport, getTime, eventCallback, &networkBuffer );

    /* Verify parameters. */
    packetId = MQTT_GetPacketId( NULL );
    TEST_ASSERT_EQUAL_INT( 0, packetId );

    packetId = MQTT_GetPacketId( &mqttContext );
    TEST_ASSERT_EQUAL_INT( 1, packetId );
    TEST_ASSERT_EQUAL_INT( 2, mqttContext.nextPacketId );

    mqttContext.nextPacketId = UINT16_MAX;
    packetId = MQTT_GetPacketId( &mqttContext );
    TEST_ASSERT_EQUAL_INT( UINT16_MAX, packetId );
    TEST_ASSERT_EQUAL_INT( 1, mqttContext.nextPacketId );
}

/* ========================================================================== */

/**
 * @brief Test that NULL pContext causes MQTT_ProcessLoop to return MQTTBadParameter.
 */
void test_MQTT_ProcessLoop_Invalid_Params( void )
{
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t mqttStatus = MQTT_ProcessLoop( NULL );

    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );

    setupTransportInterface( &transport );
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    /* Get time function cannot be NULL. */
    context.getTime = NULL;
    mqttStatus = MQTT_ProcessLoop( &context );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );
    /* Restore the time function for the next test. */
    context.getTime = getTime;

    /* The fixed network buffer cannot be NULL. */
    context.networkBuffer.pBuffer = NULL;

    mqttStatus = MQTT_ProcessLoop( &context );

    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );
}

void test_MQTT_ProcessLoop_discardPacket( void )
{
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTPacketInfo_t incomingPacket = { 0 };
    MQTTStatus_t mqttStatus;

    setupNetworkBuffer( &networkBuffer );
    setupTransportInterface( &transport );

    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    context.networkBuffer.size = 20;

    incomingPacket.type = currentPacketType;
    incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    incomingPacket.headerLength = MQTT_SAMPLE_REMAINING_LENGTH;

    MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );

    mqttStatus = MQTT_ProcessLoop( &context );

    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
}

void test_MQTT_ProcessLoop_IncomingBufferNotInit( void )
{
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t mqttStatus;
    MQTTPacketInfo_t incomingPacket = { 0 };
    MQTTPublishInfo_t publishInfo = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );

    /* First byte shows that it is a publish type packet. */
    mqttBuffer[ 0 ] = MQTT_PACKET_TYPE_PUBLISH;
    /* Length of the packet. */
    mqttBuffer[ 1 ] = 20;

    incomingPacket.type = MQTT_PACKET_TYPE_PUBLISH;
    incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    incomingPacket.headerLength = MQTT_SAMPLE_REMAINING_LENGTH;

    MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );

    /* Assume QoS = 1. */
    publishInfo.qos = MQTTQoS1;

    MQTT_DeserializePublish_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_DeserializePublish_ReturnThruPtr_pPublishInfo( &publishInfo );

    mqttStatus = MQTT_ProcessLoop( &context );

    TEST_ASSERT_EQUAL( MQTTRecvFailed, mqttStatus );
}

void test_MQTT_ProcessLoop_IncomingBufferNotInitQoS0( void )
{
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t mqttStatus;
    MQTTPacketInfo_t incomingPacket = { 0 };
    MQTTPublishInfo_t publishInfo = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    isEventCallbackInvoked = false;

    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );

    /* First byte shows that it is a publish type packet. */
    mqttBuffer[ 0 ] = MQTT_PACKET_TYPE_PUBLISH;
    /* Length of the packet. */
    mqttBuffer[ 1 ] = 20;

    incomingPacket.type = MQTT_PACKET_TYPE_PUBLISH;
    incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    incomingPacket.headerLength = MQTT_SAMPLE_REMAINING_LENGTH;

    MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );

    /* Assume QoS = 1. */
    publishInfo.qos = MQTTQoS0;

    MQTT_DeserializePublish_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_DeserializePublish_ReturnThruPtr_pPublishInfo( &publishInfo );

    MQTT_UpdateStatePublish_ExpectAnyArgsAndReturn( MQTTSuccess );

    mqttStatus = MQTT_ProcessLoop( &context );

    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
    TEST_ASSERT_EQUAL( true, isEventCallbackInvoked );
}

void test_MQTT_ProcessLoop_HandleKeepAlive( void )
{
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t mqttStatus;

    setupTransportInterface( &transport );
    transport.recv = transportRecvNoData;
    setupNetworkBuffer( &networkBuffer );

    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );

    /* Set this value such that the library will be forced to send keep alive. */
    context.waitingForPingResp = true;
    /* Update the time so that there is timeout. */
    globalEntryTime = MQTT_PINGRESP_TIMEOUT_MS;
    mqttStatus = MQTT_ProcessLoop( &context );

    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
}

void test_MQTT_ProcessLoop_HandleKeepAlive1( void )
{
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t mqttStatus;
    size_t pingreqSize = MQTT_PACKET_PINGREQ_SIZE;

    setupTransportInterface( &transport );
    transport.recv = transportRecvNoData;
    setupNetworkBuffer( &networkBuffer );

    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );

    /* Verify MQTTSuccess is returned. */
    MQTT_GetPingreqPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetPingreqPacketSize_ReturnThruPtr_pPacketSize( &pingreqSize );
    MQTT_SerializePingreq_ExpectAnyArgsAndReturn( MQTTSuccess );

    /* Set this value such that the library will be forced to send keep alive. */
    context.waitingForPingResp = false;
    context.keepAliveIntervalSec = 1;
    /* Update the time so that there is timeout. */
    globalEntryTime = MQTT_PINGRESP_TIMEOUT_MS;
    mqttStatus = MQTT_ProcessLoop( &context );

    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
}

void test_MQTT_ProcessLoop_HandleKeepAlive2( void )
{
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t mqttStatus;

    setupTransportInterface( &transport );
    transport.recv = transportRecvNoData;
    setupNetworkBuffer( &networkBuffer );

    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );

    /* Set this value such that the library will be forced to send keep alive. */
    context.waitingForPingResp = false;
    context.keepAliveIntervalSec = 1;

    /* Update the time so that timeout does not occur and the ping is not sent. */
    globalEntryTime = 0;

    mqttStatus = MQTT_ProcessLoop( &context );

    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
}

void test_MQTT_ProcessLoop_RecvFailed( void )
{
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTStatus_t mqttStatus;

    setupTransportInterface( &transport );
    transport.recv = transportRecvFailure;
    setupNetworkBuffer( &networkBuffer );

    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );

    mqttStatus = MQTT_ProcessLoop( &context );

    TEST_ASSERT_EQUAL( MQTTRecvFailed, mqttStatus );
}

void test_MQTT_ReceiveLoop_KeepAliveACK( void )
{
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTPacketInfo_t incomingPacket = { 0 };
    MQTTStatus_t mqttStatus;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    context.networkBuffer.size = MQTT_TEST_BUFFER_LENGTH;

    incomingPacket.type = MQTT_PACKET_TYPE_PINGRESP;
    incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    incomingPacket.headerLength = MQTT_SAMPLE_REMAINING_LENGTH;

    MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );

    MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess );

    mqttStatus = MQTT_ReceiveLoop( &context );

    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
}

void test_MQTT_ProcessLoop_discardPacket_second_recv_fail( void )
{
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTPacketInfo_t incomingPacket = { 0 };
    MQTTStatus_t mqttStatus;

    setupTransportInterface( &transport );
    transport.recv = transportRecvOneSuccessOneFail;
    setupNetworkBuffer( &networkBuffer );

    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    context.networkBuffer.size = 20;

    incomingPacket.type = currentPacketType;
    incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    incomingPacket.headerLength = MQTT_SAMPLE_REMAINING_LENGTH;

    MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );

    mqttStatus = MQTT_ProcessLoop( &context );

    TEST_ASSERT_EQUAL( MQTTRecvFailed, mqttStatus );
}

/**
 * @brief This test case covers one call to the private method,
 * handleIncomingPublish(...),
 * that result in the process loop returning successfully.
 */
void test_MQTT_ProcessLoop_handleIncomingPublish_Happy_Path1( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    ProcessLoopReturns_t expectParams = { 0 };
    MQTTPubAckInfo_t pIncomingCallback[ 10 ];

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    mqttStatus = MQTT_InitStatefulQoS( &context, NULL, 0, pIncomingCallback, 10 );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    modifyIncomingPacketStatus = MQTTSuccess;

    /* Assume QoS = 1 so that a PUBACK will be sent after receiving PUBLISH.
     * That is, expectProcessLoopCalls will take on the following parameters:
     * incomingPublish=true and stateAfterDeserialize=MQTTPubAckSend. */
    currentPacketType = MQTT_PACKET_TYPE_PUBLISH;
    /* Set expected return values during the loop. */
    resetProcessLoopParams( &expectParams );
    expectParams.stateAfterDeserialize = MQTTPubAckSend;
    expectParams.stateAfterSerialize = MQTTPublishDone;
    expectParams.incomingPublish = true;
    expectProcessLoopCalls( &context, &expectParams );
}

/**
 * @brief This test case covers one call to the private method,
 * handleIncomingPublish(...),
 * that result in the process loop returning successfully.
 */
void test_MQTT_ProcessLoop_handleIncomingPublish_Happy_Path2( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    ProcessLoopReturns_t expectParams = { 0 };
    MQTTPubAckInfo_t pIncomingPublish[ 10 ];

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    mqttStatus = MQTT_InitStatefulQoS( &context, NULL, 0, pIncomingPublish, 10 );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    modifyIncomingPacketStatus = MQTTSuccess;

    /* Assume QoS = 2 so that a PUBREC will be sent after receiving PUBLISH.
     * That is, expectProcessLoopCalls will take on the following parameters:
     * incomingPublish=true and stateAfterDeserialize=MQTTPubRecSend. */
    currentPacketType = MQTT_PACKET_TYPE_PUBLISH;
    /* Set expected return values during the loop. */
    resetProcessLoopParams( &expectParams );
    expectParams.stateAfterDeserialize = MQTTPubRecSend;
    expectParams.stateAfterSerialize = MQTTPubRelPending;
    expectParams.incomingPublish = true;
    expectProcessLoopCalls( &context, &expectParams );
}

/**
 * @brief This test case covers one call to the private method,
 * handleIncomingPublish(...),
 * that result in the process loop returning successfully.
 */
void test_MQTT_ProcessLoop_handleIncomingPublish_Happy_Path3( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTPublishInfo_t pubInfo = { 0 };
    ProcessLoopReturns_t expectParams = { 0 };
    MQTTPubAckInfo_t incomingRecords = { 0 };
    MQTTPubAckInfo_t outgoingRecords = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    mqttStatus = MQTT_Init( &context,
                            &transport,
                            getTime,
                            eventCallback,
                            &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    MQTT_InitStatefulQoS( &context,
                          &outgoingRecords, 4,
                          &incomingRecords, 4 );

    modifyIncomingPacketStatus = MQTTSuccess;

    /* Duplicate QoS1 publish received.
     * expectProcessLoopCalls will take on the following parameters:
     * incomingPublish=true, stateAfterDeserialize=MQTTPubAckSend,
     * updateStateStatus=MQTTStateCollision and pPubInfo is passed with
     * dup flag set. The event callback should not be invoked. */
    currentPacketType = MQTT_PACKET_TYPE_PUBLISH;
    pubInfo.dup = true;
    pubInfo.qos = MQTTQoS1;
    isEventCallbackInvoked = false;
    /* Set expected return values during the loop. */
    resetProcessLoopParams( &expectParams );
    expectParams.stateAfterDeserialize = MQTTPubAckSend;
    expectParams.updateStateStatus = MQTTStateCollision;
    expectParams.stateAfterSerialize = MQTTPublishDone;
    expectParams.deserializeStatus = MQTTSuccess;
    expectParams.incomingPublish = true;
    expectParams.pPubInfo = &pubInfo;
    expectProcessLoopCalls( &context, &expectParams );
    TEST_ASSERT_FALSE( isEventCallbackInvoked );
}

/**
 * @brief This test case covers one call to the private method,
 * handleIncomingPublish(...),
 * that result in the process loop returning successfully.
 */
void test_MQTT_ProcessLoop_handleIncomingPublish_Happy_Path4( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    ProcessLoopReturns_t expectParams = { 0 };
    MQTTPubAckInfo_t pIncomingPublish[ 10 ];

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    mqttStatus = MQTT_InitStatefulQoS( &context, NULL, 0, pIncomingPublish, 10 );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    modifyIncomingPacketStatus = MQTTSuccess;

    /* Duplicate QoS2 publish received.
     * expectProcessLoopCalls will take on the following parameters:
     * incomingPublish=true, stateAfterDeserialize=MQTTPubRecSend,
     * updateStateStatus=MQTTStateCollision and pPubInfo is passed with
     * dup flag set. The event callback should not be invoked. */
    currentPacketType = MQTT_PACKET_TYPE_PUBLISH;
    isEventCallbackInvoked = false;
    /* Only changes are for QoS 2, no need to reset. */
    expectParams.stateAfterDeserialize = MQTTPubRecSend;
    expectParams.stateAfterSerialize = MQTTPubRelPending;
    expectParams.incomingPublish = true;
    expectParams.updateStateStatus = MQTTStateCollision;
    expectProcessLoopCalls( &context, &expectParams );
    TEST_ASSERT_FALSE( isEventCallbackInvoked );
}

/**
 * @brief This test case covers one call to the private method,
 * handleIncomingPublish(...),
 * that result in the process loop returning successfully.
 */
void test_MQTT_ProcessLoop_handleIncomingPublish_Happy_Path5( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    ProcessLoopReturns_t expectParams = { 0 };
    MQTTPubAckInfo_t pIncomingPublish[ 10 ];

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    mqttStatus = MQTT_InitStatefulQoS( &context, NULL, 0, pIncomingPublish, 10 );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    modifyIncomingPacketStatus = MQTTSuccess;

    /* A publish is received when already a state record exists, but dup
     * flag is not set. */
    currentPacketType = MQTT_PACKET_TYPE_PUBLISH;
    isEventCallbackInvoked = false;
    expectParams.incomingPublish = true;
    expectParams.updateStateStatus = MQTTStateCollision;
    expectParams.stateAfterDeserialize = MQTTPubAckSend;
    expectParams.stateAfterSerialize = MQTTPublishDone;
    /* No loop statuses have changed from before. */
    expectProcessLoopCalls( &context, &expectParams );
    TEST_ASSERT_FALSE( isEventCallbackInvoked );
}

/**
 * @brief This test case covers one call to the private method,
 * handleIncomingPublish(...),
 * that result in the process loop returning successfully.
 */
void test_MQTT_ProcessLoop_handleIncomingPublish_Happy_Path6( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    ProcessLoopReturns_t expectParams = { 0 };
    MQTTPubAckInfo_t pIncomingPublish[ 10 ];

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    mqttStatus = MQTT_InitStatefulQoS( &context, NULL, 0, pIncomingPublish, 10 );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    modifyIncomingPacketStatus = MQTTSuccess;

    /* Duplicate QoS2 publish received with no collision.
     * expectProcessLoopCalls will take on the following parameters:
     * incomingPublish=true, stateAfterDeserialize=MQTTPubRecSend,
     * updateStateStatus=MQTTSuccess and pPubInfo is passed with
     * dup flag set. The event callback should be invoked. */
    currentPacketType = MQTT_PACKET_TYPE_PUBLISH;
    isEventCallbackInvoked = false;
    expectParams.incomingPublish = true;
    /* The only expect parameter to change is the update status. */
    expectParams.updateStateStatus = MQTTSuccess;
    expectParams.stateAfterDeserialize = MQTTPubAckSend;
    expectParams.stateAfterSerialize = MQTTPublishDone;
    expectProcessLoopCalls( &context, &expectParams );
    TEST_ASSERT_TRUE( isEventCallbackInvoked );
}

/**
 * @brief This test case covers all calls to the private method,
 * handleIncomingPublish(...),
 * that result in the process loop returning an error.
 */
void test_MQTT_ProcessLoop_handleIncomingPublish_Error_Paths( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTPublishInfo_t publishInfo = { 0 };
    ProcessLoopReturns_t expectParams = { 0 };
    MQTTPubAckInfo_t incomingRecords = { 0 };
    MQTTPubAckInfo_t outgoingRecords = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    MQTT_InitStatefulQoS( &context,
                          &outgoingRecords, 4,
                          &incomingRecords, 4 );

    modifyIncomingPacketStatus = MQTTSuccess;

    /* Verify that an error is propagated when deserialization fails by returning
     * MQTTBadResponse. Any parameters beyond that are actually irrelevant
     * because they are only used as return values for non-expected calls. */
    currentPacketType = MQTT_PACKET_TYPE_PUBLISH;
    /* Set expected return values during the loop. */
    resetProcessLoopParams( &expectParams );
    expectParams.deserializeStatus = MQTTBadResponse;
    expectParams.updateStateStatus = MQTTIllegalState;
    expectParams.serializeStatus = MQTTBadResponse;
    expectParams.processLoopStatus = MQTTBadResponse;
    expectParams.incomingPublish = true;
    expectProcessLoopCalls( &context, &expectParams );

    /* A publish is received and dup flag is set, but state update failed. */
    publishInfo.dup = true;
    publishInfo.qos = MQTTQoS2;
    currentPacketType = MQTT_PACKET_TYPE_PUBLISH;
    isEventCallbackInvoked = false;
    resetProcessLoopParams( &expectParams );
    expectParams.stateAfterDeserialize = MQTTPubAckSend;
    expectParams.updateStateStatus = MQTTIllegalState;
    expectParams.processLoopStatus = MQTTIllegalState;
    expectParams.incomingPublish = true;
    expectParams.pPubInfo = &publishInfo;
    /* The other loop parameter fields are irrelevant. */
    expectProcessLoopCalls( &context, &expectParams );
    TEST_ASSERT_FALSE( isEventCallbackInvoked );
}

/**
 * @brief This test checks that the ProcessLoop API function is able to
 * support receiving an entire incoming MQTT packet over the network when
 * the transport recv function only reads less than requested bytes at a
 * time, and the timeout passed to the API is "0ms".
 */
void test_MQTT_ProcessLoop_Zero_Duration_And_Partial_Network_Read( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    ProcessLoopReturns_t expectParams = { 0 };

    setupNetworkBuffer( &networkBuffer );

    transport.send = transportSendSuccess;

    /* Set the transport recv function for the test to the mock function that represents
     * partial read of data from network (i.e. less than requested number of bytes)
     * at a time. */
    transport.recv = transportRecvOneByte;

    /* Initialize the context. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    /* Set flag required for configuring behavior of expectProcessLoopCalls()
     * helper function. */
    modifyIncomingPacketStatus = MQTTNoDataAvailable;

    /* Test the ProcessLoop() call with zero duration timeout to verify that it
     * will be able to support reading the packet over network over multiple calls to
     * the transport receive function. */
    expectParams.timeoutMs = MQTT_NO_TIMEOUT_MS;

    /* Test with an incoming PUBLISH packet whose payload is read only one byte
     * per call to the transport recv function. */
    currentPacketType = MQTT_PACKET_TYPE_PUBLISH;
    /* Set expected return values during the loop. */
    resetProcessLoopParams( &expectParams );
    expectParams.stateAfterDeserialize = MQTTPubAckSend;
    expectParams.stateAfterSerialize = MQTTPublishDone;
    expectParams.incomingPublish = true;
    expectParams.processLoopStatus = MQTTNeedMoreBytes;
    expectParams.updateStateStatus = MQTTNeedMoreBytes;
    expectProcessLoopCalls( &context, &expectParams );
}

/**
 * @brief This test case covers all calls to the private method,
 * handleIncomingAck(...),
 * that result in the process loop returning successfully.
 */
void test_MQTT_ProcessLoop_handleIncomingAck_Happy_Paths( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    ProcessLoopReturns_t expectParams = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    modifyIncomingPacketStatus = MQTTSuccess;

    /* Mock the receiving of a PUBACK packet type and expect the appropriate
     * calls made from the process loop. */
    currentPacketType = MQTT_PACKET_TYPE_PUBACK;
    /* Set expected return values in the loop. */
    resetProcessLoopParams( &expectParams );
    expectParams.stateAfterDeserialize = MQTTPublishDone;
    expectParams.stateAfterSerialize = MQTTPublishDone;
    expectProcessLoopCalls( &context, &expectParams );

    /* Mock the receiving of a PUBREC packet type and expect the appropriate
     * calls made from the process loop. */
    currentPacketType = MQTT_PACKET_TYPE_PUBREC;
    /* Set expected return values in the loop. */
    resetProcessLoopParams( &expectParams );
    expectParams.stateAfterDeserialize = MQTTPubRelSend;
    expectParams.stateAfterSerialize = MQTTPubCompPending;
    expectProcessLoopCalls( &context, &expectParams );

    /* Mock the receiving of a PUBREL packet type and expect the appropriate
     * calls made from the process loop. */
    currentPacketType = MQTT_PACKET_TYPE_PUBREL;
    /* Set expected return values in the loop. */
    resetProcessLoopParams( &expectParams );
    expectParams.stateAfterDeserialize = MQTTPubCompSend;
    expectParams.stateAfterSerialize = MQTTPublishDone;
    expectProcessLoopCalls( &context, &expectParams );

    /* Duplicate PUBREL, but no record exists. */
    currentPacketType = MQTT_PACKET_TYPE_PUBREL;
    /* Set expected return values in the loop. */
    resetProcessLoopParams( &expectParams );
    expectParams.stateAfterDeserialize = MQTTStateNull;
    expectParams.updateStateStatus = MQTTBadParameter;
    expectParams.processLoopStatus = MQTTBadParameter;
    expectProcessLoopCalls( &context, &expectParams );

    /* Mock the receiving of a PUBCOMP packet type and expect the appropriate
     * calls made from the process loop. */
    currentPacketType = MQTT_PACKET_TYPE_PUBCOMP;
    /* Set expected return values in the loop. */
    resetProcessLoopParams( &expectParams );
    expectParams.stateAfterDeserialize = MQTTPublishDone;
    expectParams.stateAfterSerialize = MQTTPublishDone;
    expectProcessLoopCalls( &context, &expectParams );

    /* Mock the receiving of a PINGRESP packet type and expect the appropriate
     * calls made from the process loop. */
    currentPacketType = MQTT_PACKET_TYPE_PINGRESP;
    /* Set expected return values in the loop. */
    resetProcessLoopParams( &expectParams );
    expectProcessLoopCalls( &context, &expectParams );

    /* Mock the receiving of a SUBACK packet type and expect the appropriate
     * calls made from the process loop. */
    currentPacketType = MQTT_PACKET_TYPE_SUBACK;
    /* Set expected return values in the loop. */
    resetProcessLoopParams( &expectParams );
    expectProcessLoopCalls( &context, &expectParams );

    /* Verify that process loop is still successful when SUBACK indicates a
     * server refusal. */
    currentPacketType = MQTT_PACKET_TYPE_SUBACK;
    isEventCallbackInvoked = false;
    /* Set expected return values in the loop. */
    resetProcessLoopParams( &expectParams );
    expectParams.deserializeStatus = MQTTServerRefused;
    expectProcessLoopCalls( &context, &expectParams );
    TEST_ASSERT_TRUE( isEventCallbackInvoked );

    /* Mock the receiving of an UNSUBACK packet type and expect the appropriate
     * calls made from the process loop. */
    currentPacketType = MQTT_PACKET_TYPE_UNSUBACK;
    /* Set expected return values in the loop. */
    resetProcessLoopParams( &expectParams );
    expectProcessLoopCalls( &context, &expectParams );
}

/**
 * @brief This test case covers all calls to the private method,
 * handleIncomingAck(...),
 * that result in the process loop returning an error.
 */
void test_MQTT_ProcessLoop_handleIncomingAck_Error_Paths( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    ProcessLoopReturns_t expectParams = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    modifyIncomingPacketStatus = MQTTSuccess;

    /* Verify that MQTTBadResponse is propagated when deserialization fails upon
     * receiving an unknown packet type. */
    currentPacketType = MQTT_PACKET_TYPE_INVALID;
    /* Set expected return values in the loop. */
    resetProcessLoopParams( &expectParams );
    expectParams.deserializeStatus = MQTTBadResponse;
    expectParams.processLoopStatus = MQTTBadResponse;
    expectProcessLoopCalls( &context, &expectParams );

    /* Verify that MQTTSendFailed is propagated when receiving a PUBREC
     * then failing when serializing a PUBREL to send in response. */
    currentPacketType = MQTT_PACKET_TYPE_PUBREC;
    /* Set expected return values in the loop. */
    resetProcessLoopParams( &expectParams );
    expectParams.stateAfterDeserialize = MQTTPubRelSend;
    expectParams.serializeStatus = MQTTNoMemory;
    expectParams.stateAfterSerialize = MQTTStateNull;
    expectParams.processLoopStatus = MQTTSendFailed;
    expectProcessLoopCalls( &context, &expectParams );

    /* Verify that MQTTBadResponse is propagated when deserialization fails upon
     * receiving a PUBACK. */
    currentPacketType = MQTT_PACKET_TYPE_PUBACK;
    /* Set expected return values in the loop. */
    resetProcessLoopParams( &expectParams );
    expectParams.deserializeStatus = MQTTBadResponse;
    expectParams.stateAfterDeserialize = MQTTStateNull;
    expectParams.processLoopStatus = MQTTBadResponse;
    /* The other loop parameter fields are irrelevant. */
    expectProcessLoopCalls( &context, &expectParams );

    /* Verify that MQTTBadResponse is propagated when deserialization fails upon
     * receiving a PINGRESP. */
    currentPacketType = MQTT_PACKET_TYPE_PINGRESP;
    /* Set expected return values in the loop. */
    resetProcessLoopParams( &expectParams );
    expectParams.deserializeStatus = MQTTBadResponse;
    expectParams.stateAfterDeserialize = MQTTStateNull;
    expectParams.processLoopStatus = MQTTBadResponse;
    /* The other loop parameter fields are irrelevant. */
    expectProcessLoopCalls( &context, &expectParams );

    /* Verify that MQTTBadResponse is propagated when deserialization fails upon
     * receiving a SUBACK. */
    currentPacketType = MQTT_PACKET_TYPE_SUBACK;
    /* Set expected return values in the loop. */
    resetProcessLoopParams( &expectParams );
    expectParams.deserializeStatus = MQTTBadResponse;
    expectParams.processLoopStatus = MQTTBadResponse;
    /* The other loop parameter fields are irrelevant. */
    expectProcessLoopCalls( &context, &expectParams );

    /* Verify that MQTTIllegalState is returned if MQTT_UpdateStateAck(...)
     * provides an unknown state such as MQTTStateNull to sendPublishAcks(...). */
    currentPacketType = MQTT_PACKET_TYPE_PUBREC;
    /* Set expected return values in the loop. */
    resetProcessLoopParams( &expectParams );
    expectParams.stateAfterDeserialize = MQTTPubRelSend;
    expectParams.stateAfterSerialize = MQTTStateNull;
    expectParams.processLoopStatus = MQTTIllegalState;
    expectProcessLoopCalls( &context, &expectParams );
}

/**
 * @brief This test case covers all calls to the private method,
 * handleKeepAlive(...),
 * that result in the process loop returning successfully.
 */
void test_MQTT_ProcessLoop_handleKeepAlive_Happy_Paths1( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    ProcessLoopReturns_t expectParams = { 0 };

    resetProcessLoopParams( &expectParams );
    setupNetworkBuffer( &networkBuffer );
    setupTransportInterface( &transport );

    modifyIncomingPacketStatus = MQTTNoDataAvailable;
    globalEntryTime = MQTT_ONE_SECOND_TO_MS;

    /* Coverage for the branch path where keep alive interval is 0. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    context.waitingForPingResp = false;
    context.keepAliveIntervalSec = 0;
    expectParams.incomingPublish = false;
    expectParams.updateStateStatus = MQTTSuccess;
    expectParams.processLoopStatus = MQTTSuccess;
    expectParams.stateAfterDeserialize = MQTTPubRelSend;
    /* Set expected return values in the loop. All success. */
    MQTTPacketInfo_t incomingPacket = { 0 };
    /* Modify incoming packet depending on type to be tested. */
    incomingPacket.type = currentPacketType;
    incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    incomingPacket.headerLength = MQTT_SAMPLE_REMAINING_LENGTH;


    MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess );

    MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( expectParams.updateStateStatus );
    MQTT_UpdateStateAck_ReturnThruPtr_pNewState( &expectParams.stateAfterDeserialize );

    MQTT_SerializeAck_ExpectAnyArgsAndReturn( MQTTSuccess );

    MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( expectParams.updateStateStatus );
    MQTT_UpdateStateAck_ReturnThruPtr_pNewState( &expectParams.stateAfterDeserialize );

    mqttStatus = MQTT_ProcessLoop( &context );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
}

/**
 * @brief This test case covers all calls to the private method,
 * handleKeepAlive(...),
 * that result in the process loop returning successfully.
 */
void test_MQTT_ProcessLoop_handleKeepAlive_Happy_Paths2( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    ProcessLoopReturns_t expectParams = { 0 };

    setupNetworkBuffer( &networkBuffer );
    setupTransportInterface( &transport );
    resetProcessLoopParams( &expectParams );

    modifyIncomingPacketStatus = MQTTNoDataAvailable;
    globalEntryTime = MQTT_ONE_SECOND_TO_MS;

    /* Coverage for the branch path where keep alive interval is 0. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    /* Coverage for the branch path where keep alive interval is greater than 0,
     * and the interval has not expired. PINGREQ should not be sent. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
    context.keepAliveIntervalSec = MQTT_SAMPLE_KEEPALIVE_INTERVAL_S;
    context.lastPacketTxTime = getTime();
    /* Set expected return values in the loop. All success. */

    MQTTPacketInfo_t incomingPacket = { 0 };
    /* Modify incoming packet depending on type to be tested. */
    incomingPacket.type = currentPacketType;
    incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    incomingPacket.headerLength = MQTT_SAMPLE_REMAINING_LENGTH;

    MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess );

    MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( expectParams.updateStateStatus );
    MQTT_UpdateStateAck_ReturnThruPtr_pNewState( &expectParams.stateAfterDeserialize );

    mqttStatus = MQTT_ProcessLoop( &context );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
}

/**
 * @brief This test case covers all calls to the private method,
 * handleKeepAlive(...),
 * that result in the process loop returning successfully.
 */
void test_MQTT_ProcessLoop_handleKeepAlive_Happy_Paths3( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    ProcessLoopReturns_t expectParams = { 0 };

    resetProcessLoopParams( &expectParams );
    setupNetworkBuffer( &networkBuffer );
    setupTransportInterface( &transport );

    modifyIncomingPacketStatus = MQTTNoDataAvailable;
    globalEntryTime = MQTT_ONE_SECOND_TO_MS;

    /* Coverage for the branch path where keep alive interval is 0. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    /* Coverage for the branch path where PINGRESP timeout interval hasn't expired. */
    context.waitingForPingResp = true;
    context.keepAliveIntervalSec = MQTT_SAMPLE_KEEPALIVE_INTERVAL_S;
    context.lastPacketTxTime = MQTT_ONE_SECOND_TO_MS;
    context.pingReqSendTimeMs = MQTT_ONE_SECOND_TO_MS;
    /* Set expected return values in the loop. All success. */


    MQTTPacketInfo_t incomingPacket = { 0 };
    /* Modify incoming packet depending on type to be tested. */
    incomingPacket.type = currentPacketType;
    incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    incomingPacket.headerLength = MQTT_SAMPLE_REMAINING_LENGTH;

    MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess );

    MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( expectParams.updateStateStatus );
    MQTT_UpdateStateAck_ReturnThruPtr_pNewState( &expectParams.stateAfterDeserialize );

    mqttStatus = MQTT_ProcessLoop( &context );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
}

/**
 * @brief This test case covers all calls to the private method,
 * handleKeepAlive(...),
 * that result in the process loop returning successfully.
 */
void test_MQTT_ProcessLoop_handleKeepAlive_Happy_Paths4( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    ProcessLoopReturns_t expectParams = { 0 };

    resetProcessLoopParams( &expectParams );
    setupNetworkBuffer( &networkBuffer );
    setupTransportInterface( &transport );

    modifyIncomingPacketStatus = MQTTNoDataAvailable;
    globalEntryTime = MQTT_ONE_SECOND_TO_MS;

    /* Coverage for the branch path where keep alive interval is 0. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    context.waitingForPingResp = false;
    context.keepAliveIntervalSec = MQTT_SAMPLE_KEEPALIVE_INTERVAL_S;
    context.lastPacketTxTime = 0;
    /* Set expected return values in the loop. All success. */

    MQTTPacketInfo_t incomingPacket = { 0 };
    /* Modify incoming packet depending on type to be tested. */
    incomingPacket.type = currentPacketType;
    incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    incomingPacket.headerLength = MQTT_SAMPLE_REMAINING_LENGTH;

    MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );

    MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTSuccess );


    MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( expectParams.updateStateStatus );
    MQTT_UpdateStateAck_ReturnThruPtr_pNewState( &expectParams.stateAfterDeserialize );

    mqttStatus = MQTT_ProcessLoop( &context );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
}

/**
 * @brief This test case covers all calls to the private method,
 * handleKeepAlive(...),
 * that result in the process loop returning an error.
 */
void test_MQTT_ProcessLoop_handleKeepAlive_Error_Paths1( void )
{
    MQTTStatus_t mqttStatus = { 0 };
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    ProcessLoopReturns_t expectParams = { 0 };

    setupNetworkBuffer( &networkBuffer );
    setupTransportInterface( &transport );
    transport.writev = transportWritevSuccess;
    transport.recv = transportRecvNoData;

    globalEntryTime = MQTT_PINGRESP_TIMEOUT_MS + 1;

    /* Coverage for the branch path where PINGRESP timeout interval has expired. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    context.keepAliveIntervalSec = MQTT_SAMPLE_KEEPALIVE_INTERVAL_S;
    context.lastPacketTxTime = 0;
    context.lastPacketRxTime = 0;
    context.pingReqSendTimeMs = 0;
    context.waitingForPingResp = true;

    /* Set expected return values in the loop. */
    resetProcessLoopParams( &expectParams );
    expectParams.processLoopStatus = MQTTKeepAliveTimeout;

    mqttStatus = MQTT_ProcessLoop( &context );
    TEST_ASSERT_EQUAL( MQTTKeepAliveTimeout, mqttStatus );
}

/**
 * @brief This test case covers all calls to the private method,
 * handleKeepAlive(...),
 * that result in the process loop returning an error.
 */
void test_MQTT_ProcessLoop_handleKeepAlive_Error_Paths2( void )
{
    MQTTStatus_t mqttStatus = { 0 };
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    ProcessLoopReturns_t expectParams = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    modifyIncomingPacketStatus = MQTTNoDataAvailable;
    globalEntryTime = MQTT_PINGRESP_TIMEOUT_MS + 1;

    /* Coverage for the branch path where PINGRESP timeout interval has expired. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    context.keepAliveIntervalSec = PACKET_TX_TIMEOUT_MS + 1;
    context.lastPacketTxTime = 0;
    context.lastPacketRxTime = 0;
    context.pingReqSendTimeMs = 0;
    context.waitingForPingResp = false;
    /* Set expected return values in the loop. */
    resetProcessLoopParams( &expectParams );

    expectParams.processLoopStatus = MQTTKeepAliveTimeout;

    MQTTPacketInfo_t incomingPacket = { 0 };
    /* Modify incoming packet depending on type to be tested. */
    currentPacketType = MQTT_PACKET_TYPE_PINGRESP;
    incomingPacket.type = currentPacketType;
    incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    incomingPacket.headerLength = MQTT_SAMPLE_REMAINING_LENGTH;

    MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTNoDataAvailable );
    MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );

    mqttStatus = MQTT_ProcessLoop( &context );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
}

/**
 * @brief This test case covers all calls to the private method,
 * handleKeepAlive(...),
 * that result in the process loop returning an error.
 */
void test_MQTT_ProcessLoop_handleKeepAlive_Error_Paths3( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    ProcessLoopReturns_t expectParams = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    transport.recv = transportRecvNoData;

    modifyIncomingPacketStatus = MQTTNoDataAvailable;
    globalEntryTime = MQTT_PINGRESP_TIMEOUT_MS + 1;

    /* Coverage for the branch path where PINGRESP timeout interval has expired. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    globalEntryTime = PACKET_RX_TIMEOUT_MS + 1;
    context.keepAliveIntervalSec = 0;
    context.lastPacketTxTime = 0;
    context.lastPacketRxTime = 0;
    context.pingReqSendTimeMs = 0;
    context.waitingForPingResp = false;
    /* Set expected return values in the loop. */
    resetProcessLoopParams( &expectParams );

    MQTT_GetPingreqPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_SerializePingreq_ExpectAnyArgsAndReturn( MQTTSuccess );

    mqttStatus = MQTT_ProcessLoop( &context );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
}

/**
 * @brief This test case covers all calls to the private method,
 * handleKeepAlive(...),
 * that result in the process loop returning an error.
 */
void test_MQTT_ProcessLoop_handleKeepAlive_Error_Paths4( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    ProcessLoopReturns_t expectParams = { 0 };

    setupTransportInterface( &transport );
    transport.recv = transportRecvNoData;

    setupNetworkBuffer( &networkBuffer );

    modifyIncomingPacketStatus = MQTTNoDataAvailable;
    globalEntryTime = MQTT_PINGRESP_TIMEOUT_MS + 1;

    /* Coverage for the branch path where PINGRESP timeout interval has expired. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    globalEntryTime = PACKET_RX_TIMEOUT_MS + 1;
    context.keepAliveIntervalSec = ( PACKET_TX_TIMEOUT_MS / 1000 ) + 1U;
    context.lastPacketTxTime = 0;
    context.lastPacketRxTime = 0;
    context.pingReqSendTimeMs = 0;
    context.waitingForPingResp = false;

    /* Set the index to non-zero value to show that there is some data in the buffer
     * to be processed. */
    context.index = 12;

    /* Set expected return values in the loop. */
    resetProcessLoopParams( &expectParams );

    MQTTPacketInfo_t incomingPacket = { 0 };
    /* Modify incoming packet depending on type to be tested. */
    currentPacketType = MQTT_PACKET_TYPE_PINGRESP;
    incomingPacket.type = currentPacketType;
    incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    incomingPacket.headerLength = MQTT_SAMPLE_REMAINING_LENGTH;

    MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTNeedMoreBytes );
    MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );

    MQTT_GetPingreqPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_SerializePingreq_ExpectAnyArgsAndReturn( MQTTSuccess );

    mqttStatus = MQTT_ProcessLoop( &context );
    TEST_ASSERT_EQUAL( MQTTNeedMoreBytes, mqttStatus );
}

/**
 * @brief This test case covers all calls to the private method,
 * handleKeepAlive(...),
 * that result in the process loop returning an error.
 */
void test_MQTT_ProcessLoop_handleKeepAlive_Error_Paths5( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    ProcessLoopReturns_t expectParams = { 0 };

    setupTransportInterface( &transport );
    transport.recv = transportRecvNoData;

    setupNetworkBuffer( &networkBuffer );

    modifyIncomingPacketStatus = MQTTNoDataAvailable;
    globalEntryTime = MQTT_PINGRESP_TIMEOUT_MS + 1;

    /* Coverage for the branch path where PINGRESP timeout interval has expired. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    globalEntryTime = PACKET_RX_TIMEOUT_MS - 1U;
    context.keepAliveIntervalSec = ( PACKET_TX_TIMEOUT_MS / 1000 ) + 1U;
    context.lastPacketTxTime = 0;
    context.lastPacketRxTime = 0;
    context.pingReqSendTimeMs = 0;
    context.waitingForPingResp = false;

    /* Set the index to non-zero value to show that there is some data in the buffer
     * to be processed. */
    context.index = 12;

    /* Set expected return values in the loop. */
    resetProcessLoopParams( &expectParams );

    MQTTPacketInfo_t incomingPacket = { 0 };
    /* Modify incoming packet depending on type to be tested. */
    currentPacketType = MQTT_PACKET_TYPE_PINGRESP;
    incomingPacket.type = currentPacketType;
    incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    incomingPacket.headerLength = MQTT_SAMPLE_REMAINING_LENGTH;

    MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTNeedMoreBytes );
    MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );

    mqttStatus = MQTT_ProcessLoop( &context );
    TEST_ASSERT_EQUAL( MQTTNeedMoreBytes, mqttStatus );
}

/**
 * @brief This test mocks a failing transport receive and runs multiple
 * iterations of the process loop, resulting in returning MQTTRecvFailed.
 */
void test_MQTT_ProcessLoop_Receive_Failed( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTPacketInfo_t incomingPacket = { 0 };

    setupNetworkBuffer( &networkBuffer );
    setupTransportInterface( &transport );
    transport.writev = transportWritevFail;

    mqttStatus = MQTT_Init( &context, &transport,
                            getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
    incomingPacket.type = MQTT_PACKET_TYPE_PUBCOMP;

    MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    MQTT_DeserializeAck_ExpectAnyArgsAndReturn( MQTTRecvFailed );

    mqttStatus = MQTT_ProcessLoop( &context );

    TEST_ASSERT_EQUAL( MQTTRecvFailed, mqttStatus );
}

/**
 * @brief Set the initial entry time close to the maximum value, causing
 * an overflow. This test then checks that the process loop still runs for the
 * expected number of iterations in spite of this.
 */
void test_MQTT_ProcessLoop_Timer_Overflow( void )
{
    MQTTStatus_t mqttStatus = { 0 };
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTPacketInfo_t incomingPacket = { 0 };
    MQTTPublishState_t publishState = MQTTPubAckSend;
    MQTTPublishState_t ackState = MQTTPublishDone;
    MQTTPubAckInfo_t incomingPublishRecords[ 10 ];

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    networkBuffer.size = 1000;
    incomingPacket.type = MQTT_PACKET_TYPE_PUBLISH;
    incomingPacket.remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;

    globalEntryTime = UINT32_MAX - MQTT_OVERFLOW_OFFSET;

    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    mqttStatus = MQTT_InitStatefulQoS( &context, NULL, 0, incomingPublishRecords, 10 );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_ProcessIncomingPacketTypeAndLength_ReturnThruPtr_pIncomingPacket( &incomingPacket );
    /* Assume QoS = 1 so that a PUBACK will be sent after receiving PUBLISH. */
    MQTT_DeserializePublish_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_UpdateStatePublish_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_UpdateStatePublish_ReturnThruPtr_pNewState( &publishState );
    MQTT_SerializeAck_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_UpdateStateAck_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_UpdateStateAck_ReturnThruPtr_pNewState( &ackState );

    mqttStatus = MQTT_ProcessLoop( &context );

    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
}

/* ========================================================================== */

/**
 * @brief Test that MQTT_ReceiveLoop() works as intended. Since the only difference
 * between this and the process loop is keep alive, we only need to test the
 * differences for coverage.
 */
void test_MQTT_ReceiveLoop( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    /* Verify that a NULL Context returns an error. */
    mqttStatus = MQTT_ReceiveLoop( NULL );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );

    /* Verify that a NULL time function returns an error. */
    context.getTime = NULL;
    mqttStatus = MQTT_ReceiveLoop( &context );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );
    context.getTime = getTime;

    /* Verify that a null fixed network buffer returns an error. */
    context.networkBuffer.pBuffer = NULL;
    mqttStatus = MQTT_ReceiveLoop( &context );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );
    setupNetworkBuffer( &( context.networkBuffer ) );

    MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTBadResponse );
    /* Error case, for branch coverage. */
    mqttStatus = MQTT_ReceiveLoop( &context );
    TEST_ASSERT_EQUAL( MQTTBadResponse, mqttStatus );

    /* This will cover the case when there is data available in the buffer to be processed but
     * no additional bytes have been received by the transport interface. */
    context.transportInterface.recv = transportRecvNoData;
    MQTT_ProcessIncomingPacketTypeAndLength_ExpectAnyArgsAndReturn( MQTTBadResponse );
    /* Error case, for branch coverage. */
    mqttStatus = MQTT_ReceiveLoop( &context );
    TEST_ASSERT_EQUAL( MQTTBadResponse, mqttStatus );

    /* Reset the index to clear the buffer of any remaining data. */
    context.index = 0;
    /* Keep Alive should not trigger.*/
    context.keepAliveIntervalSec = 1;
    mqttStatus = MQTT_ReceiveLoop( &context );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
    TEST_ASSERT_FALSE( context.controlPacketSent );

    /* Test with a dummy getTime to ensure there's no infinite loops. */
    context.getTime = getTimeDummy;
    mqttStatus = MQTT_ReceiveLoop( &context );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
    context.getTime = getTime;

    /* Receive a PINGRESP. */
    mqttStatus = MQTT_ReceiveLoop( &context );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
}

/* ========================================================================== */

/**
 * @brief This test case verifies that MQTT_Subscribe returns MQTTBadParameter
 * with an invalid parameter. This test case also gives us coverage over
 * the private method, validateSubscribeUnsubscribeParams(...).
 */
void test_MQTT_Subscribe_invalid_params( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    MQTTSubscribeInfo_t subscribeInfo = { 0 };

    /* Call subscribe with a NULL context. */
    mqttStatus = MQTT_Subscribe( NULL, &subscribeInfo, 1, MQTT_FIRST_VALID_PACKET_ID );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );

    /* Call subscribe with a NULL subscription list. */
    mqttStatus = MQTT_Subscribe( &context, NULL, 1, MQTT_FIRST_VALID_PACKET_ID );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );

    /* Call subscribe with 0 subscriptions. */
    mqttStatus = MQTT_Subscribe( &context, &subscribeInfo, 0, MQTT_FIRST_VALID_PACKET_ID );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );

    /* Packet ID cannot be 0 per MQTT 3.1.1 spec. */
    mqttStatus = MQTT_Subscribe( &context, &subscribeInfo, 1, 0 );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );

    /* Incoming publish records NULL but QoS > 0. */
    subscribeInfo.qos = MQTTQoS1;
    mqttStatus = MQTT_Subscribe( &context, &subscribeInfo, 1, 10 );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );
}

static uint8_t * MQTT_SerializeSubscribedHeader_cb( size_t remainingLength,
                                                    uint8_t * pIndex,
                                                    uint16_t packetId,
                                                    int numcallbacks )
{
    ( void ) remainingLength;
    ( void ) pIndex;
    ( void ) packetId;
    ( void ) numcallbacks;

    return pIndex;
}

static uint8_t * MQTT_SerializeSubscribedHeader_cb1( size_t remainingLength,
                                                     uint8_t * pIndex,
                                                     uint16_t packetId,
                                                     int numcallbacks )
{
    ( void ) remainingLength;
    ( void ) pIndex;
    ( void ) packetId;
    ( void ) numcallbacks;

    return pIndex + 5;
}

static uint8_t * MQTT_SerializeSubscribedHeader_cb2( size_t remainingLength,
                                                     uint8_t * pIndex,
                                                     uint16_t packetId,
                                                     int numcallbacks )
{
    ( void ) remainingLength;
    ( void ) pIndex;
    ( void ) packetId;
    ( void ) numcallbacks;

    memcpy( pIndex, SubscribeHeader, SubscribeHeaderLength );

    return pIndex + SubscribeHeaderLength;
}

static uint8_t * MQTT_SerializeUnsubscribedHeader_cb2( size_t remainingLength,
                                                       uint8_t * pIndex,
                                                       uint16_t packetId,
                                                       int numcallbacks )
{
    ( void ) remainingLength;
    ( void ) pIndex;
    ( void ) packetId;
    ( void ) numcallbacks;

    memcpy( pIndex, UnsubscribeHeader, UnsubscribeHeaderLength );

    return pIndex + UnsubscribeHeaderLength;
}

/**
 * @brief This test case verifies that MQTT_Subscribe returns successfully
 * when valid parameters are passed and all bytes are sent.
 */
void test_MQTT_Subscribe_happy_path( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTSubscribeInfo_t subscribeInfo = { 0 };
    size_t remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    size_t packetSize = MQTT_SAMPLE_REMAINING_LENGTH;
    MQTTPubAckInfo_t incomingRecords = { 0 };
    MQTTPubAckInfo_t outgoingRecords = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    setupSubscriptionInfo( &subscribeInfo );

    /* Initialize context. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    mqttStatus = MQTT_InitStatefulQoS( &context,
                                       &outgoingRecords, 4,
                                       &incomingRecords, 4 );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    /* Verify MQTTSuccess is returned with the following mocks. */
    MQTT_GetSubscribePacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetSubscribePacketSize_ReturnThruPtr_pPacketSize( &packetSize );
    MQTT_GetSubscribePacketSize_ReturnThruPtr_pRemainingLength( &remainingLength );
    MQTT_SerializeSubscribeHeader_Stub( MQTT_SerializeSubscribedHeader_cb );

    /* Expect the above calls when running MQTT_Subscribe. */
    mqttStatus = MQTT_Subscribe( &context, &subscribeInfo, 1, MQTT_FIRST_VALID_PACKET_ID );

    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
}

/**
 * @brief This test case verifies that MQTT_Subscribe returns successfully
 * when valid parameters are passed and all bytes are sent.
 */
void test_MQTT_Subscribe_happy_path1( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTSubscribeInfo_t subscribeInfo[ 2 ];
    size_t remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    size_t packetSize = MQTT_SAMPLE_REMAINING_LENGTH;
    MQTTPubAckInfo_t incomingRecords = { 0 };
    MQTTPubAckInfo_t outgoingRecords = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    setupSubscriptionInfo( &subscribeInfo[ 0 ] );

    /* Initialize context. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    mqttStatus = MQTT_InitStatefulQoS( &context,
                                       &outgoingRecords, 4,
                                       &incomingRecords, 4 );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    /* Verify MQTTSuccess is returned with the following mocks. */
    MQTT_GetSubscribePacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetSubscribePacketSize_ReturnThruPtr_pPacketSize( &packetSize );
    MQTT_GetSubscribePacketSize_ReturnThruPtr_pRemainingLength( &remainingLength );
    MQTT_SerializeSubscribeHeader_Stub( MQTT_SerializeSubscribedHeader_cb );

    /* Expect the above calls when running MQTT_Subscribe. */
    mqttStatus = MQTT_Subscribe( &context, subscribeInfo, 1, MQTT_FIRST_VALID_PACKET_ID );

    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
}

/**
 * @brief This test case verifies that MQTT_Subscribe returns successfully
 * when valid parameters are passed and all bytes are sent.
 */
void test_MQTT_Subscribe_happy_path2( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTSubscribeInfo_t subscribeInfo[ 2 ];
    size_t remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    size_t packetSize = MQTT_SAMPLE_REMAINING_LENGTH;
    MQTTPubAckInfo_t incomingRecords = { 0 };
    MQTTPubAckInfo_t outgoingRecords = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    setupSubscriptionInfo( &subscribeInfo[ 0 ] );
    setupSubscriptionInfo( &subscribeInfo[ 1 ] );

    /* Initialize context. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    mqttStatus = MQTT_InitStatefulQoS( &context,
                                       &outgoingRecords, 4,
                                       &incomingRecords, 4 );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    /* Verify MQTTSuccess is returned with the following mocks. */
    MQTT_GetSubscribePacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetSubscribePacketSize_ReturnThruPtr_pPacketSize( &packetSize );
    MQTT_GetSubscribePacketSize_ReturnThruPtr_pRemainingLength( &remainingLength );
    MQTT_SerializeSubscribeHeader_Stub( MQTT_SerializeSubscribedHeader_cb1 );

    /* Expect the above calls when running MQTT_Subscribe. */
    mqttStatus = MQTT_Subscribe( &context, subscribeInfo, 2, MQTT_FIRST_VALID_PACKET_ID );

    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
}

void test_MQTT_Subscribe_MultipleSubscriptions( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTSubscribeInfo_t subscribeInfo[ 5 ];
    size_t remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    size_t packetSize = MQTT_SAMPLE_REMAINING_LENGTH;
    MQTTPubAckInfo_t incomingRecords = { 0 };
    MQTTPubAckInfo_t outgoingRecords = { 0 };

    TEST_ASSERT_EQUAL_MESSAGE( 6U, MQTT_SUB_UNSUB_MAX_VECTORS,
                               "This test is configured to work with MQTT_SUB_UNSUB_MAX_VECTORS defined as 6." );

    setupTransportInterface( &transport );
    transport.writev = transportWritevSubscribeSuccess;

    setupNetworkBuffer( &networkBuffer );

    subscribeInfo[ 0 ].qos = MQTTQoS1;
    subscribeInfo[ 0 ].pTopicFilter = MQTT_SAMPLE_TOPIC_FILTER;
    subscribeInfo[ 0 ].topicFilterLength = MQTT_SAMPLE_TOPIC_FILTER_LENGTH;

    subscribeInfo[ 1 ].qos = MQTTQoS2;
    subscribeInfo[ 1 ].pTopicFilter = MQTT_SAMPLE_TOPIC_FILTER1;
    subscribeInfo[ 1 ].topicFilterLength = MQTT_SAMPLE_TOPIC_FILTER_LENGTH1;

    subscribeInfo[ 2 ].qos = MQTTQoS0;
    subscribeInfo[ 2 ].pTopicFilter = MQTT_SAMPLE_TOPIC_FILTER2;
    subscribeInfo[ 2 ].topicFilterLength = MQTT_SAMPLE_TOPIC_FILTER_LENGTH2;

    subscribeInfo[ 3 ].qos = MQTTQoS1;
    subscribeInfo[ 3 ].pTopicFilter = MQTT_SAMPLE_TOPIC_FILTER3;
    subscribeInfo[ 3 ].topicFilterLength = MQTT_SAMPLE_TOPIC_FILTER_LENGTH3;

    /* Initialize context. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    mqttStatus = MQTT_InitStatefulQoS( &context,
                                       &outgoingRecords, 4,
                                       &incomingRecords, 4 );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    /* Verify MQTTSuccess is returned with the following mocks. */
    MQTT_GetSubscribePacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetSubscribePacketSize_ReturnThruPtr_pPacketSize( &packetSize );
    MQTT_GetSubscribePacketSize_ReturnThruPtr_pRemainingLength( &remainingLength );
    MQTT_SerializeSubscribeHeader_Stub( MQTT_SerializeSubscribedHeader_cb2 );

    /* Expect the above calls when running MQTT_Subscribe. */
    mqttStatus = MQTT_Subscribe( &context, subscribeInfo, 4, MQTT_FIRST_VALID_PACKET_ID );

    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
}

/**
 * @brief This test case verifies that MQTT_Subscribe returns MQTTSendFailed
 * if transport interface send returns an error.
 */
void test_MQTT_Subscribe_error_paths1( void )
{
    MQTTStatus_t mqttStatus = { 0 };
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTSubscribeInfo_t subscribeInfo = { 0 };
    size_t remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    size_t packetSize = MQTT_SAMPLE_REMAINING_LENGTH;

    /* Verify that an error is propagated when transport interface returns an error. */
    setupNetworkBuffer( &networkBuffer );
    setupSubscriptionInfo( &subscribeInfo );
    subscribeInfo.qos = MQTTQoS0;
    setupTransportInterface( &transport );
    transport.send = transportSendFailure;
    transport.writev = transportWritevFail;

    /* Initialize context. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
    /* Verify MQTTSendFailed is propagated when transport interface returns an error. */
    MQTT_GetSubscribePacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetSubscribePacketSize_ReturnThruPtr_pPacketSize( &packetSize );
    MQTT_GetSubscribePacketSize_ReturnThruPtr_pRemainingLength( &remainingLength );
    MQTT_SerializeSubscribeHeader_Stub( MQTT_SerializeSubscribedHeader_cb );
    /* Expect the above calls when running MQTT_Subscribe. */
    mqttStatus = MQTT_Subscribe( &context, &subscribeInfo, 1, MQTT_FIRST_VALID_PACKET_ID );
    TEST_ASSERT_EQUAL( MQTTSendFailed, mqttStatus );
}

/**
 * @brief This test case verifies that MQTT_Subscribe returns MQTTSendFailed
 * if transport interface send returns an error.
 */
void test_MQTT_Subscribe_error_paths2( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTSubscribeInfo_t subscribeInfo = { 0 };
    size_t remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    size_t packetSize = MQTT_SAMPLE_REMAINING_LENGTH;

    /* Verify that an error is propagated when transport interface returns an error. */
    setupNetworkBuffer( &networkBuffer );
    setupSubscriptionInfo( &subscribeInfo );
    subscribeInfo.qos = MQTTQoS0;
    setupTransportInterface( &transport );
    transport.writev = NULL;
    /* Case when there is timeout in sending data through transport send. */
    transport.send = transportSendNoBytes; /* Use the mock function that returns zero bytes sent. */

    /* Initialize context. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    MQTT_GetSubscribePacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetSubscribePacketSize_ReturnThruPtr_pPacketSize( &packetSize );
    MQTT_GetSubscribePacketSize_ReturnThruPtr_pRemainingLength( &remainingLength );
    MQTT_SerializeSubscribeHeader_Stub( MQTT_SerializeSubscribedHeader_cb );
    mqttStatus = MQTT_Subscribe( &context, &subscribeInfo, 1, MQTT_FIRST_VALID_PACKET_ID );
    TEST_ASSERT_EQUAL( MQTTSendFailed, mqttStatus );
}

/* ========================================================================== */

/**
 * @brief This test case verifies that MQTT_Unsubscribe returns MQTTBadParameter
 * with an invalid parameter. This test case also gives us coverage over
 * the private method, validateSubscribeUnsubscribeParams(...).
 */
void test_MQTT_Unsubscribe_invalid_params( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    MQTTSubscribeInfo_t subscribeInfo = { 0 };

    /* Call subscribe with a NULL context. */
    mqttStatus = MQTT_Unsubscribe( NULL, &subscribeInfo, 1, MQTT_FIRST_VALID_PACKET_ID );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );

    /* Call subscribe with a NULL subscription list. */
    mqttStatus = MQTT_Unsubscribe( &context, NULL, 1, MQTT_FIRST_VALID_PACKET_ID );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );

    /* Call subscribe with 0 subscriptions. */
    mqttStatus = MQTT_Unsubscribe( &context, &subscribeInfo, 0, MQTT_FIRST_VALID_PACKET_ID );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );

    /* Packet ID cannot be 0 per MQTT 3.1.1 spec. */
    mqttStatus = MQTT_Unsubscribe( &context, &subscribeInfo, 1, 0 );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );
}

static uint8_t * MQTT_SerializeUnsubscribeHeader_cb( size_t remainingLength,
                                                     uint8_t * pIndex,
                                                     uint16_t packetId,
                                                     int numcallbacks )
{
    ( void ) remainingLength;
    ( void ) pIndex;
    ( void ) packetId;
    ( void ) numcallbacks;

    return pIndex;
}

/**
 * @brief This test case verifies that MQTT_Unsubscribe returns successfully
 * when valid parameters are passed and all bytes are sent.
 */
void test_MQTT_Unsubscribe_happy_path( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTSubscribeInfo_t subscribeInfo = { 0 };
    size_t remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    size_t packetSize = MQTT_SAMPLE_REMAINING_LENGTH;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    setupSubscriptionInfo( &subscribeInfo );
    subscribeInfo.qos = MQTTQoS0;

    /* Initialize context. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
    /* Verify MQTTSuccess is returned with the following mocks. */
    MQTT_SerializeUnsubscribeHeader_Stub( MQTT_SerializeUnsubscribeHeader_cb );
    MQTT_GetUnsubscribePacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetUnsubscribePacketSize_ReturnThruPtr_pPacketSize( &packetSize );
    MQTT_GetUnsubscribePacketSize_ReturnThruPtr_pRemainingLength( &remainingLength );

    /* Expect the above calls when running MQTT_Unsubscribe. */
    mqttStatus = MQTT_Unsubscribe( &context, &subscribeInfo, 1, MQTT_FIRST_VALID_PACKET_ID );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
}

/**
 * @brief This test case verifies that MQTT_Subscribe returns successfully
 * when valid parameters are passed and all bytes are sent.
 */
void test_MQTT_Unsubscribe_happy_path1( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTSubscribeInfo_t subscribeInfo[ 2 ];
    size_t remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    size_t packetSize = MQTT_SAMPLE_REMAINING_LENGTH;
    MQTTPubAckInfo_t incomingRecords = { 0 };
    MQTTPubAckInfo_t outgoingRecords = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    setupSubscriptionInfo( &subscribeInfo[ 0 ] );

    /* Initialize context. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    mqttStatus = MQTT_InitStatefulQoS( &context,
                                       &outgoingRecords, 4,
                                       &incomingRecords, 4 );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    /* Verify MQTTSuccess is returned with the following mocks. */
    MQTT_GetUnsubscribePacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetUnsubscribePacketSize_ReturnThruPtr_pPacketSize( &packetSize );
    MQTT_GetUnsubscribePacketSize_ReturnThruPtr_pRemainingLength( &remainingLength );
    MQTT_SerializeUnsubscribeHeader_Stub( MQTT_SerializeSubscribedHeader_cb );

    /* Expect the above calls when running MQTT_Subscribe. */
    mqttStatus = MQTT_Unsubscribe( &context, subscribeInfo, 1, MQTT_FIRST_VALID_PACKET_ID );

    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
}

/**
 * @brief This test case verifies that MQTT_Subscribe returns successfully
 * when valid parameters are passed and all bytes are sent.
 */
void test_MQTT_unsubscribe_happy_path2( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTSubscribeInfo_t subscribeInfo[ 3 ];
    size_t remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    size_t packetSize = MQTT_SAMPLE_REMAINING_LENGTH;
    MQTTPubAckInfo_t incomingRecords = { 0 };
    MQTTPubAckInfo_t outgoingRecords = { 0 };

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    setupSubscriptionInfo( &subscribeInfo[ 0 ] );
    setupSubscriptionInfo( &subscribeInfo[ 1 ] );
    setupSubscriptionInfo( &subscribeInfo[ 2 ] );

    /* Initialize context. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    mqttStatus = MQTT_InitStatefulQoS( &context,
                                       &outgoingRecords, 4,
                                       &incomingRecords, 4 );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    /* Verify MQTTSuccess is returned with the following mocks. */
    MQTT_GetUnsubscribePacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetUnsubscribePacketSize_ReturnThruPtr_pPacketSize( &packetSize );
    MQTT_GetUnsubscribePacketSize_ReturnThruPtr_pRemainingLength( &remainingLength );
    MQTT_SerializeUnsubscribeHeader_Stub( MQTT_SerializeSubscribedHeader_cb1 );

    /* Expect the above calls when running MQTT_Subscribe. */
    mqttStatus = MQTT_Unsubscribe( &context, subscribeInfo, 3, MQTT_FIRST_VALID_PACKET_ID );

    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
}

void test_MQTT_Unsubscribe_MultipleSubscriptions( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTSubscribeInfo_t subscribeInfo[ 5 ];
    size_t remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    size_t packetSize = MQTT_SAMPLE_REMAINING_LENGTH;
    MQTTPubAckInfo_t incomingRecords = { 0 };
    MQTTPubAckInfo_t outgoingRecords = { 0 };

    TEST_ASSERT_EQUAL_MESSAGE( 6U, MQTT_SUB_UNSUB_MAX_VECTORS,
                               "This test is configured to work with MQTT_SUB_UNSUB_MAX_VECTORS defined as 6." );

    setupTransportInterface( &transport );
    transport.writev = transportWritevUnsubscribeSuccess;

    setupNetworkBuffer( &networkBuffer );

    subscribeInfo[ 0 ].qos = MQTTQoS1;
    subscribeInfo[ 0 ].pTopicFilter = MQTT_SAMPLE_TOPIC_FILTER;
    subscribeInfo[ 0 ].topicFilterLength = MQTT_SAMPLE_TOPIC_FILTER_LENGTH;

    subscribeInfo[ 1 ].qos = MQTTQoS2;
    subscribeInfo[ 1 ].pTopicFilter = MQTT_SAMPLE_TOPIC_FILTER1;
    subscribeInfo[ 1 ].topicFilterLength = MQTT_SAMPLE_TOPIC_FILTER_LENGTH1;

    subscribeInfo[ 2 ].qos = MQTTQoS0;
    subscribeInfo[ 2 ].pTopicFilter = MQTT_SAMPLE_TOPIC_FILTER2;
    subscribeInfo[ 2 ].topicFilterLength = MQTT_SAMPLE_TOPIC_FILTER_LENGTH2;

    subscribeInfo[ 3 ].qos = MQTTQoS1;
    subscribeInfo[ 3 ].pTopicFilter = MQTT_SAMPLE_TOPIC_FILTER3;
    subscribeInfo[ 3 ].topicFilterLength = MQTT_SAMPLE_TOPIC_FILTER_LENGTH3;

    /* Initialize context. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    mqttStatus = MQTT_InitStatefulQoS( &context,
                                       &outgoingRecords, 4,
                                       &incomingRecords, 4 );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    /* Verify MQTTSuccess is returned with the following mocks. */
    MQTT_GetUnsubscribePacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetUnsubscribePacketSize_ReturnThruPtr_pPacketSize( &packetSize );
    MQTT_GetUnsubscribePacketSize_ReturnThruPtr_pRemainingLength( &remainingLength );
    MQTT_SerializeUnsubscribeHeader_Stub( MQTT_SerializeUnsubscribedHeader_cb2 );

    /* Expect the above calls when running MQTT_Subscribe. */
    mqttStatus = MQTT_Unsubscribe( &context, subscribeInfo, 4, MQTT_FIRST_VALID_PACKET_ID );

    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
}

/**
 * @brief This test case verifies that MQTT_Unsubscribe returns MQTTSendFailed
 * if transport interface send returns an error.
 */
void test_MQTT_Unsubscribe_error_path1( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTSubscribeInfo_t subscribeInfo = { 0 };
    size_t remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    size_t packetSize = MQTT_SAMPLE_REMAINING_LENGTH;

    /* Verify that an error is propagated when transport interface returns an error. */
    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );
    setupSubscriptionInfo( &subscribeInfo );
    subscribeInfo.qos = MQTTQoS0;

    transport.send = transportSendFailure;
    transport.recv = transportRecvFailure;
    transport.writev = transportWritevFail;

    /* Initialize context. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
    MQTT_SerializeUnsubscribeHeader_Stub( MQTT_SerializeUnsubscribeHeader_cb );
    /* Verify MQTTSendFailed is propagated when transport interface returns an error. */
    MQTT_GetUnsubscribePacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetUnsubscribePacketSize_ReturnThruPtr_pPacketSize( &packetSize );
    MQTT_GetUnsubscribePacketSize_ReturnThruPtr_pRemainingLength( &remainingLength );
    /* Expect the above calls when running MQTT_Unsubscribe. */
    mqttStatus = MQTT_Unsubscribe( &context, &subscribeInfo, 1, MQTT_FIRST_VALID_PACKET_ID );
    TEST_ASSERT_EQUAL( MQTTSendFailed, mqttStatus );

    /* Case when there is timeout in sending data through transport send. */
}

/**
 * @brief This test case verifies that MQTT_Unsubscribe returns MQTTSendFailed
 * if transport interface send returns an error.
 */
void test_MQTT_Unsubscribe_error_path2( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    MQTTSubscribeInfo_t subscribeInfo = { 0 };
    size_t remainingLength = MQTT_SAMPLE_REMAINING_LENGTH;
    size_t packetSize = MQTT_SAMPLE_REMAINING_LENGTH;

    setupNetworkBuffer( &networkBuffer );
    setupSubscriptionInfo( &subscribeInfo );
    subscribeInfo.qos = MQTTQoS0;

    /* Verify that an error is propagated when transport interface returns an error. */
    setupTransportInterface( &transport );
    transport.send = transportSendFailure;
    transport.recv = transportRecvFailure;
    transport.writev = transportWritevFail;

    /* Initialize context. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    MQTT_SerializeUnsubscribeHeader_Stub( MQTT_SerializeUnsubscribeHeader_cb );
    transport.send = transportSendNoBytes; /* Use the mock function that returns zero bytes sent. */
    MQTT_GetUnsubscribePacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetUnsubscribePacketSize_ReturnThruPtr_pPacketSize( &packetSize );
    MQTT_GetUnsubscribePacketSize_ReturnThruPtr_pRemainingLength( &remainingLength );
    /* Expect the above calls when running MQTT_Unsubscribe. */
    mqttStatus = MQTT_Unsubscribe( &context, &subscribeInfo, 1, MQTT_FIRST_VALID_PACKET_ID );
    TEST_ASSERT_EQUAL( MQTTSendFailed, mqttStatus );
}

/* ========================================================================== */

/**
 * @brief This test case verifies that MQTT_Ping returns MQTTBadParameter
 * with context parameter is NULL.
 */
void test_MQTT_Ping_invalid_params( void )
{
    MQTTStatus_t mqttStatus;

    /* Call ping with a NULL context. */
    mqttStatus = MQTT_Ping( NULL );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );
}

/**
 * @brief This test case verifies that MQTT_Ping returns successfully
 * when valid parameters are passed and all bytes are sent.
 */
void test_MQTT_Ping_happy_path( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    size_t pingreqSize = MQTT_PACKET_PINGREQ_SIZE;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    /* Initialize context. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
    /* Verify MQTTSuccess is returned. */
    MQTT_GetPingreqPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetPingreqPacketSize_ReturnThruPtr_pPacketSize( &pingreqSize );
    MQTT_SerializePingreq_ExpectAnyArgsAndReturn( MQTTSuccess );
    /* Expect the above calls when running MQTT_Ping. */
    mqttStatus = MQTT_Ping( &context );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );

    TEST_ASSERT_EQUAL( context.lastPacketTxTime, context.pingReqSendTimeMs );
    TEST_ASSERT_TRUE( context.waitingForPingResp );
}

/**
 * @brief This test case verifies that MQTT_Ping returns MQTTSendFailed
 * if transport interface send returns an error.
 */
void test_MQTT_Ping_error_path( void )
{
    MQTTStatus_t mqttStatus;
    MQTTContext_t context = { 0 };
    TransportInterface_t transport = { 0 };
    MQTTFixedBuffer_t networkBuffer = { 0 };
    size_t pingreqSize = MQTT_PACKET_PINGREQ_SIZE;

    setupTransportInterface( &transport );
    setupNetworkBuffer( &networkBuffer );

    /* Test a network error is returned from sending the PING packet over the
     * transport send . */
    transport.send = transportSendFailure;
    transport.recv = transportRecvFailure;

    /* Initialize context. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
    /* Verify MQTTSendFailed is propagated when transport interface returns an error. */
    MQTT_GetPingreqPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetPingreqPacketSize_ReturnThruPtr_pPacketSize( &pingreqSize );
    MQTT_SerializePingreq_ExpectAnyArgsAndReturn( MQTTSuccess );
    /* Expect the above calls when running MQTT_Ping. */
    mqttStatus = MQTT_Ping( &context );
    TEST_ASSERT_EQUAL( MQTTSendFailed, mqttStatus );

    /* Case when there is timeout in sending data through transport send. */
    transport.recv = transportRecvSuccess;
    transport.send = transportSendNoBytes; /* Use the mock function that returns zero bytes sent. */
    /* Initialize context. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
    MQTT_GetPingreqPacketSize_ExpectAnyArgsAndReturn( MQTTSuccess );
    MQTT_GetPingreqPacketSize_ReturnThruPtr_pPacketSize( &pingreqSize );
    MQTT_SerializePingreq_ExpectAnyArgsAndReturn( MQTTSuccess );
    /* Verify that the API returns failure. */
    mqttStatus = MQTT_Ping( &context );
    TEST_ASSERT_EQUAL( MQTTSendFailed, mqttStatus );


    /* Initialize context. */
    mqttStatus = MQTT_Init( &context, &transport, getTime, eventCallback, &networkBuffer );
    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
    /* Verify MQTTBadParameter is propagated when getting PINGREQ packet size fails. */
    MQTT_GetPingreqPacketSize_ExpectAnyArgsAndReturn( MQTTBadParameter );
    MQTT_GetPingreqPacketSize_ReturnThruPtr_pPacketSize( &pingreqSize );
    /* Expect the above calls when running MQTT_Ping. */
    mqttStatus = MQTT_Ping( &context );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );
}

/* ========================================================================== */

/**
 * @brief Test MQTT_MatchTopic for invalid input parameters.
 */
void test_MQTT_MatchTopic_InvalidInput( void )
{
    bool matchResult = false;

    /* NULL topic name. */
    TEST_ASSERT_EQUAL( MQTTBadParameter, MQTT_MatchTopic( NULL,
                                                          MQTT_SAMPLE_TOPIC_FILTER_LENGTH,
                                                          MQTT_SAMPLE_TOPIC_FILTER,
                                                          MQTT_SAMPLE_TOPIC_FILTER_LENGTH,
                                                          &matchResult ) );
    TEST_ASSERT_EQUAL( false, matchResult );

    /* Invalid topic name length. */
    TEST_ASSERT_EQUAL( MQTTBadParameter, MQTT_MatchTopic( MQTT_SAMPLE_TOPIC_FILTER,
                                                          0u,
                                                          MQTT_SAMPLE_TOPIC_FILTER,
                                                          MQTT_SAMPLE_TOPIC_FILTER_LENGTH,
                                                          &matchResult ) );
    TEST_ASSERT_EQUAL( false, matchResult );

    /* NULL topic filter. */
    TEST_ASSERT_EQUAL( MQTTBadParameter, MQTT_MatchTopic( MQTT_SAMPLE_TOPIC_FILTER,
                                                          MQTT_SAMPLE_TOPIC_FILTER_LENGTH,
                                                          NULL,
                                                          MQTT_SAMPLE_TOPIC_FILTER_LENGTH,
                                                          &matchResult ) );
    TEST_ASSERT_EQUAL( false, matchResult );

    /* Invalid topic filter length. */
    TEST_ASSERT_EQUAL( MQTTBadParameter, MQTT_MatchTopic( MQTT_SAMPLE_TOPIC_FILTER,
                                                          MQTT_SAMPLE_TOPIC_FILTER_LENGTH,
                                                          MQTT_SAMPLE_TOPIC_FILTER,
                                                          0u,
                                                          &matchResult ) );
    TEST_ASSERT_EQUAL( false, matchResult );

    /* Invalid output parameter. */
    TEST_ASSERT_EQUAL( MQTTBadParameter, MQTT_MatchTopic( MQTT_SAMPLE_TOPIC_FILTER,
                                                          MQTT_SAMPLE_TOPIC_FILTER_LENGTH,
                                                          MQTT_SAMPLE_TOPIC_FILTER,
                                                          MQTT_SAMPLE_TOPIC_FILTER_LENGTH,
                                                          NULL ) );
}

/**
 * @brief Verifies that MQTT_MatchTopic is able to determine an exact match between the
 * topic name and topic filter.
 */
void test_MQTT_MatchTopic_ExactMatch( void )
{
    const char * pTopicFilter = NULL;
    const char * pTopicName = NULL;
    bool matchResult = false;

    /* Test for topic filter and topic name having exact match. */
    pTopicName = "/test/match";
    pTopicFilter = "/test/match";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( true, matchResult );

    /* Test for exact match when topic name and filter start with '$' .*/
    pTopicName = "$///";
    pTopicFilter = "$///";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( true, matchResult );

    /* Test for no match (with no wildcard in the topic filter). */
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     MQTT_SAMPLE_TOPIC_FILTER,
                                                     MQTT_SAMPLE_TOPIC_FILTER_LENGTH,
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( false, matchResult );

    /* Edge case tests (for branch coverage) to match at end with no wildcards. */
    pTopicName = "/test/match/";
    pTopicFilter = "/test/match/a";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( false, matchResult );

    pTopicName = "a";
    pTopicFilter = "a/";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( false, matchResult );

    /* Edge case test (for branch coverage) when topic name has more levels
     * than topic filter. */
    pTopicName = "test/match";
    pTopicFilter = "test";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( false, matchResult );
}

/**
 * @brief Verifies that MQTT_MatchTopic meets the MQTT 3.1.1 specification of all
 * cases of matching topic filters that contain the single-level '+' wildcard.
 */
void test_MQTT_MatchTopic_Wildcard_SingleLevel_Match_Cases( void )
{
    const char * pTopicName = NULL;
    const char * pTopicFilter = NULL;
    bool matchResult = false;

    /* Nominal case of topic filter ending with '+' .*/
    pTopicName = "/test/match/level1";
    pTopicFilter = "/test/match/+";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( true, matchResult );

    /* Test for match with a topic name starting with '$' .*/
    pTopicName = "$test/match/level1";
    pTopicFilter = "$test/match/+";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( true, matchResult );

    /* Test with '+' as the topic filter. */
    pTopicName = "test";
    pTopicFilter = "+";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( true, matchResult );

    /* Test that '+' in topic filter matches topic name containing consecutive
     * level separators, "//" in the corresponding level. */
    pTopicName = "/test//level1";
    pTopicFilter = "/test/+/level1";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( true, matchResult );

    /* Test with multiple placements of wildcard in topic filter. */
    pTopicName = "/test/match/level1";
    pTopicFilter = "/+/match/+";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( true, matchResult );

    pTopicName = "/test/match/level1";
    pTopicFilter = "+/+/+/+";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( true, matchResult );

    pTopicName = "/test///level1";
    pTopicFilter = "/test/+/+/level1";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( true, matchResult );

    /* Test that match fails when topic name has more levels than topic filter. */
    pTopicName = "/test/match/level1/level2";
    pTopicFilter = "/test/match/+";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( false, matchResult );

    pTopicName = "/";
    pTopicFilter = "+";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( false, matchResult );

    /* Edge case where filter ending with '/+' matches topic ending with '/'. */
    pTopicName = "/test/match/";
    pTopicFilter = "/test/match/+";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( true, matchResult );
}

/**
 * @brief Verifies that MQTT_MatchTopic meets the MQTT 3.1.1 specification for
 * cases of where topic filter containing '+' wildcard do not match topic name.
 */
void test_MQTT_MatchTopic_Wildcard_SingleLevel_No_Match_Cases( void )
{
    const char * pTopicName = NULL;
    const char * pTopicFilter = NULL;
    bool matchResult = false;

    /* Edge case where filter ending with '/+' should not match a topic ending with
     * at parent level. */
    pTopicName = "/test/match";
    pTopicFilter = "/test/match/+";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( false, matchResult );

    /* Edge case where topic filter starts with '+' and topic name starts with '$'. */
    pTopicName = "$/test/match";
    pTopicFilter = "+/test/match";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( false, matchResult );

    /* Topic name matches all characters with topic filter, but topic filter is invalid. */
    pTopicName = "test/match/level";
    pTopicFilter = "test/match/level+";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( false, matchResult );

    /* Invalid topic filter where non-starting '+' is not placed after '/'.*/
    pTopicName = "test/match/level1";
    pTopicFilter = "test/match/level+";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( false, matchResult );

    /* Invalid topic filter where intermediate '+' is not followed by '/'.*/
    pTopicName = "test/match/level";
    pTopicFilter = "test/+?level";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( false, matchResult );
}

/**
 * @brief Verifies that MQTT_MatchTopic meets the MQTT 3.1.1 specification of all
 * cases of matching topic filters that contain the multi-level '#' wildcard.
 */
void test_MQTT_MatchTopic_Wildcard_MultiLevel_Match_Cases( void )
{
    const char * pTopicName = NULL;
    const char * pTopicFilter = NULL;
    bool matchResult = false;

    /* Match topic filter ending with '#' with a single level .*/
    pTopicName = "/test/match/level1";
    pTopicFilter = "/test/match/#";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( true, matchResult );

    /* Match topic filter ending with '#' with multiple levels in topic name.*/
    pTopicName = "/test/match/level1/level2/level3";
    pTopicFilter = "/test/match/#";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( true, matchResult );

    pTopicName = "/test/match/level1/level2/level3";
    pTopicFilter = "/#";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( true, matchResult );

    /* Match when topic filter is "#" */
    pTopicName = "test/match/level";
    pTopicFilter = "#";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( true, matchResult );

    /* Test for match with a topic name starting with '$' .*/
    pTopicName = "$test/match/level1";
    pTopicFilter = "$test/match/#";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( true, matchResult );

    /* Match '#' with topic name ending at the parent level. */
    pTopicName = "/test/match";
    pTopicFilter = "/test/match/#";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( true, matchResult );

    /* Edge case where filter ending with '/#' matches topic ending with '/'. */
    pTopicName = "/test/match/";
    pTopicFilter = "/test/match/#";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( true, matchResult );

    /* Test for topic filters containing both '+' and '#' wildcard characters. */
    pTopicName = "/test/match";
    pTopicFilter = "+/test/match/#";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( true, matchResult );

    pTopicName = "/test/match/level";
    pTopicFilter = "+/+/+/#";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( true, matchResult );
}

/**
 * @brief Verifies that MQTT_MatchTopic meets the MQTT 3.1.1 specification for
 * cases of where topic filter containing '#' wildcard do not match topic name.
 */
void test_MQTT_MatchTopic_Wildcard_MultiLevel_No_Match_Cases( void )
{
    const char * pTopicName = NULL;
    const char * pTopicFilter = NULL;
    bool matchResult = false;

    /* Edge case where topic filter starts with '#' and topic name starts with '$'. */
    pTopicName = "$/test/match";
    pTopicFilter = "#";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( false, matchResult );

    /* Invalid topic filter where non-starting '#' is not placed after '/'.*/
    pTopicName = "test/match/level1";
    pTopicFilter = "test/match/level#";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( false, matchResult );

    /* Topic name matches all characters with topic filter, but topic filter is invalid. */
    pTopicName = "test/match/level";
    pTopicFilter = "test/match/level?#";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( false, matchResult );

    pTopicName = "test/match/level";
    pTopicFilter = "test/match/level#";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( false, matchResult );

    /* Invalid topic filters that contain '#' at a non-ending position .*/
    pTopicName = "test/match/level1/level2";
    pTopicFilter = "test/match/#/level2";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( false, matchResult );

    pTopicName = "test/match/level2";
    pTopicFilter = "#/match/level2";
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_MatchTopic( pTopicName,
                                                     strlen( pTopicName ),
                                                     pTopicFilter,
                                                     strlen( pTopicFilter ),
                                                     &matchResult ) );
    TEST_ASSERT_EQUAL( false, matchResult );
}

/* ========================================================================== */

/**
 * @brief Tests that MQTT_GetSubAckStatusCodes works as expected in parsing the
 * payload information of a SUBACK packet.
 */
void test_MQTT_GetSubAckStatusCodes( void )
{
    MQTTPacketInfo_t mqttPacketInfo = { 0 };
    size_t payloadSize;
    uint8_t * pPayloadStart;
    MQTTStatus_t status = MQTTSuccess;
    uint8_t buffer[ 10 ] = { 0 };

    buffer[ 0 ] = 0;
    buffer[ 1 ] = 1;
    buffer[ 2 ] = 0x00;
    buffer[ 3 ] = 0x01;
    buffer[ 4 ] = 0x02;
    buffer[ 5 ] = 0x80;

    /* Process a valid SUBACK packet containing whole range of server response codes. */
    mqttPacketInfo.type = MQTT_PACKET_TYPE_SUBACK;
    mqttPacketInfo.pRemainingData = buffer;
    mqttPacketInfo.remainingLength = 6;
    status = MQTT_GetSubAckStatusCodes( &mqttPacketInfo, &pPayloadStart, &payloadSize );
    TEST_ASSERT_EQUAL_INT( MQTTSuccess, status );
    TEST_ASSERT_EQUAL_PTR( &buffer[ 2 ], pPayloadStart );
    TEST_ASSERT_EQUAL_INT( MQTTSubAckSuccessQos0, pPayloadStart[ 0 ] );
    TEST_ASSERT_EQUAL_INT( MQTTSubAckSuccessQos1, pPayloadStart[ 1 ] );
    TEST_ASSERT_EQUAL_INT( MQTTSubAckSuccessQos2, pPayloadStart[ 2 ] );
    TEST_ASSERT_EQUAL_INT( MQTTSubAckFailure, pPayloadStart[ 3 ] );
    TEST_ASSERT_EQUAL_INT( 4, payloadSize );

    /* Packet is NULL. */
    status = MQTT_GetSubAckStatusCodes( NULL, &pPayloadStart, &payloadSize );
    TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status );

    /* Output parameter, pPayloadStart, is NULL. */
    status = MQTT_GetSubAckStatusCodes( &mqttPacketInfo, NULL, &payloadSize );
    TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status );

    /* Output parameter, pPayloadSize, is NULL. */
    status = MQTT_GetSubAckStatusCodes( &mqttPacketInfo, &pPayloadStart, NULL );
    TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status );

    /* Remaining Data is NULL. */
    mqttPacketInfo.pRemainingData = NULL;
    status = MQTT_GetSubAckStatusCodes( &mqttPacketInfo, &pPayloadStart, &payloadSize );
    TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status );

    /* non-SUBACK packet type. */
    mqttPacketInfo.type = MQTT_PACKET_TYPE_CONNACK;
    mqttPacketInfo.pRemainingData = buffer;
    status = MQTT_GetSubAckStatusCodes( &mqttPacketInfo, &pPayloadStart, &payloadSize );
    TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status );

    /* Invalid remaining length value in packet. */
    mqttPacketInfo.remainingLength = 0;
    mqttPacketInfo.type = MQTT_PACKET_TYPE_SUBACK;
    status = MQTT_GetSubAckStatusCodes( &mqttPacketInfo, &pPayloadStart, &payloadSize );
    TEST_ASSERT_EQUAL_INT( MQTTBadParameter, status );
}

/* ========================================================================== */

/**
 * @brief Test MQTT_Status_strerror returns correct strings.
 */
void test_MQTT_Status_strerror( void )
{
    MQTTStatus_t status;
    const char * str = NULL;

    status = MQTTSuccess;
    str = MQTT_Status_strerror( status );
    TEST_ASSERT_EQUAL_STRING( "MQTTSuccess", str );

    status = MQTTBadParameter;
    str = MQTT_Status_strerror( status );
    TEST_ASSERT_EQUAL_STRING( "MQTTBadParameter", str );

    status = MQTTNoMemory;
    str = MQTT_Status_strerror( status );
    TEST_ASSERT_EQUAL_STRING( "MQTTNoMemory", str );

    status = MQTTSendFailed;
    str = MQTT_Status_strerror( status );
    TEST_ASSERT_EQUAL_STRING( "MQTTSendFailed", str );

    status = MQTTRecvFailed;
    str = MQTT_Status_strerror( status );
    TEST_ASSERT_EQUAL_STRING( "MQTTRecvFailed", str );

    status = MQTTBadResponse;
    str = MQTT_Status_strerror( status );
    TEST_ASSERT_EQUAL_STRING( "MQTTBadResponse", str );

    status = MQTTServerRefused;
    str = MQTT_Status_strerror( status );
    TEST_ASSERT_EQUAL_STRING( "MQTTServerRefused", str );

    status = MQTTNoDataAvailable;
    str = MQTT_Status_strerror( status );
    TEST_ASSERT_EQUAL_STRING( "MQTTNoDataAvailable", str );

    status = MQTTIllegalState;
    str = MQTT_Status_strerror( status );
    TEST_ASSERT_EQUAL_STRING( "MQTTIllegalState", str );

    status = MQTTStateCollision;
    str = MQTT_Status_strerror( status );
    TEST_ASSERT_EQUAL_STRING( "MQTTStateCollision", str );

    status = MQTTKeepAliveTimeout;
    str = MQTT_Status_strerror( status );
    TEST_ASSERT_EQUAL_STRING( "MQTTKeepAliveTimeout", str );

    status = MQTTKeepAliveTimeout + 1;
    str = MQTT_Status_strerror( status );
    TEST_ASSERT_EQUAL_STRING( "Invalid MQTT Status code", str );
}

/* ========================================================================== */
void test_MQTT_GetPacketId_NULL_Context( void )
{
    uint16_t packetId = 0U;

    packetId = MQTT_GetPacketId( NULL );

    TEST_ASSERT_EQUAL( 0, packetId );
}
/* ========================================================================== */

void test_MQTT_GetPacketId_happy_path( void )
{
    uint16_t packetId = 0U;
    MQTTContext_t mqttContext = { 0 };

    mqttContext.nextPacketId = 5;

    packetId = MQTT_GetPacketId( &mqttContext );

    TEST_ASSERT_EQUAL( 5, packetId );
    TEST_ASSERT_EQUAL( 6, mqttContext.nextPacketId );
}

/* ========================================================================== */
void test_MQTT_GetPacketId_happy_path_uint16_max( void )
{
    uint16_t packetId = 0U;
    MQTTContext_t mqttContext = { 0 };

    mqttContext.nextPacketId = UINT16_MAX;

    packetId = MQTT_GetPacketId( &mqttContext );

    TEST_ASSERT_EQUAL( UINT16_MAX, packetId );
    TEST_ASSERT_EQUAL( 1, mqttContext.nextPacketId );
}
/* ========================================================================== */

void test_MQTT_CancelCallback_null_context( void )
{
    uint16_t packetId = 0U;
    MQTTStatus_t mqttStatus;

    mqttStatus = MQTT_CancelCallback( NULL, packetId );

    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );
}
/* ========================================================================== */

void test_MQTT_CancelCallback_null_outgoingPublishRecords( void )
{
    uint16_t packetId = 0U;
    MQTTStatus_t mqttStatus;
    MQTTContext_t mqttContext = { 0 };

    mqttContext.outgoingPublishRecords = NULL;

    mqttStatus = MQTT_CancelCallback( &mqttContext, packetId );

    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );
}
/* ========================================================================== */

void test_MQTT_CancelCallback_happy_path( void )
{
    uint16_t packetId = 0U;
    MQTTStatus_t mqttStatus;
    MQTTContext_t mqttContext = { 0 };

    mqttContext.outgoingPublishRecords = NULL;
    setUPContext( &mqttContext );

    MQTT_RemoveStateRecord_ExpectAndReturn( &mqttContext, packetId, MQTTSuccess );

    mqttStatus = MQTT_CancelCallback( &mqttContext, packetId );

    TEST_ASSERT_EQUAL( MQTTSuccess, mqttStatus );
}
/* ========================================================================== */

void test_MQTT_InitStatefulQoS_fail_null_context( void )
{
    MQTTStatus_t mqttStatus;
    MQTTPubAckInfo_t pOutgoingPublishRecords[ 10 ] = { 0 };
    MQTTPubAckInfo_t pIncomingPublishRecords[ 10 ] = { 0 };

    mqttStatus = MQTT_InitStatefulQoS( NULL,
                                       pOutgoingPublishRecords,
                                       10,
                                       pIncomingPublishRecords,
                                       10 );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );
}
/* ========================================================================== */

void test_MQTT_InitStatefulQoS_zero_outgoing_size( void )
{
    MQTTStatus_t mqttStatus;
    MQTTPubAckInfo_t pOutgoingPublishRecords[ 10 ] = { 0 };
    MQTTPubAckInfo_t pIncomingPublishRecords[ 10 ] = { 0 };

    MQTTContext_t mqttContext = { 0 };

    mqttStatus = MQTT_InitStatefulQoS( &mqttContext,
                                       pOutgoingPublishRecords,
                                       0,
                                       pIncomingPublishRecords,
                                       10 );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );
}
/* ========================================================================== */
void test_MQTT_InitStatefulQoS_zero_incoming_size( void )
{
    MQTTStatus_t mqttStatus;
    MQTTPubAckInfo_t pOutgoingPublishRecords[ 10 ] = { 0 };
    MQTTPubAckInfo_t pIncomingPublishRecords[ 10 ] = { 0 };

    MQTTContext_t mqttContext = { 0 };

    mqttStatus = MQTT_InitStatefulQoS( &mqttContext,
                                       pOutgoingPublishRecords,
                                       10,
                                       pIncomingPublishRecords,
                                       0 );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );
}
/* ========================================================================== */

void test_MQTT_InitStatefulQoS_callback_is_null( void )
{
    MQTTStatus_t mqttStatus;
    MQTTPubAckInfo_t pOutgoingPublishRecords[ 10 ] = { 0 };
    MQTTPubAckInfo_t pIncomingPublishRecords[ 10 ] = { 0 };

    MQTTContext_t mqttContext = { 0 };

    mqttContext.appCallback = NULL;

    mqttStatus = MQTT_InitStatefulQoS( &mqttContext,
                                       pOutgoingPublishRecords,
                                       10,
                                       pIncomingPublishRecords,
                                       10 );
    TEST_ASSERT_EQUAL( MQTTBadParameter, mqttStatus );
}
/* ========================================================================== */
