/*
 * coreMQTT v2.3.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_state_utest.c
 * @brief Unit tests for functions in core_mqtt_state.h.
 */
#include <string.h>
#include "unity.h"

#include "core_mqtt_state.h"

#define MQTT_PACKET_ID_INVALID         ( ( uint16_t ) 0U )
#define  MQTT_STATE_ARRAY_MAX_COUNT    10

/* ============================   UNITY FIXTURES ============================ */
void setUp( void )
{
}

/* called before each testcase */
void tearDown( void )
{
}

/* called at the beginning of the whole suite */
void suiteSetUp()
{
}

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

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

static int32_t transportRecvSuccess( NetworkContext_t * pNetworkContext,
                                     void * pBuffer,
                                     size_t bytesToRead )
{
    ( void ) pNetworkContext;
    ( void ) pBuffer;
    return bytesToRead;
}

static int32_t transportSendSuccess( NetworkContext_t * pNetworkContext,
                                     const void * pBuffer,
                                     size_t bytesToWrite )
{
    ( void ) pNetworkContext;
    ( void ) pBuffer;
    return bytesToWrite;
}

/**
 * @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 0;
}

/**
 * @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;
}

static void resetPublishRecords( MQTTContext_t * pMqttContext )
{
    uint32_t i = 0;

    for( ; i < MQTT_STATE_ARRAY_MAX_COUNT; i++ )
    {
        pMqttContext->outgoingPublishRecords[ i ].packetId = MQTT_PACKET_ID_INVALID;
        pMqttContext->outgoingPublishRecords[ i ].qos = MQTTQoS0;
        pMqttContext->outgoingPublishRecords[ i ].publishState = MQTTStateNull;
        pMqttContext->incomingPublishRecords[ i ].packetId = MQTT_PACKET_ID_INVALID;
        pMqttContext->incomingPublishRecords[ i ].qos = MQTTQoS0;
        pMqttContext->incomingPublishRecords[ i ].publishState = MQTTStateNull;
    }
}

static void addToRecord( MQTTPubAckInfo_t * records,
                         size_t index,
                         uint16_t packetId,
                         MQTTQoS_t qos,
                         MQTTPublishState_t state )
{
    records[ index ].packetId = packetId;
    records[ index ].qos = qos;
    records[ index ].publishState = state;
}

static void fillRecord( MQTTPubAckInfo_t * records,
                        uint16_t startingId,
                        MQTTQoS_t qos,
                        MQTTPublishState_t state )
{
    uint32_t i;

    for( i = 0; i < MQTT_STATE_ARRAY_MAX_COUNT; i++ )
    {
        records[ i ].packetId = startingId + i;
        records[ i ].qos = qos;
        records[ i ].publishState = state;
    }
}

static void validateRecordAt( MQTTPubAckInfo_t * records,
                              size_t index,
                              uint16_t packetId,
                              MQTTQoS_t qos,
                              MQTTPublishState_t state )
{
    TEST_ASSERT_EQUAL( packetId, records[ index ].packetId );
    TEST_ASSERT_EQUAL( qos, records[ index ].qos );
    TEST_ASSERT_EQUAL( state, records[ index ].publishState );
}

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

void test_MQTT_ReserveState( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTStatus_t status;
    const uint16_t PACKET_ID = 1;
    const uint16_t PACKET_ID2 = 2;
    const uint16_t PACKET_ID3 = 3;
    const size_t index = MQTT_STATE_ARRAY_MAX_COUNT / 2;
    TransportInterface_t transport;
    MQTTFixedBuffer_t networkBuffer = { 0 };

    transport.recv = transportRecvSuccess;
    transport.send = transportSendSuccess;

    MQTTPubAckInfo_t incomingRecords[ MQTT_STATE_ARRAY_MAX_COUNT ] = { 0 };
    MQTTPubAckInfo_t outgoingRecords[ MQTT_STATE_ARRAY_MAX_COUNT ] = { 0 };

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

    status = MQTT_InitStatefulQoS( &mqttContext,
                                   outgoingRecords, MQTT_STATE_ARRAY_MAX_COUNT,
                                   incomingRecords, MQTT_STATE_ARRAY_MAX_COUNT );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );

    /* QoS 0 returns success. */
    TEST_ASSERT_EQUAL( MQTTSuccess, MQTT_ReserveState( NULL, MQTT_PACKET_ID_INVALID, MQTTQoS0 ) );

    /* Test for bad parameters */
    status = MQTT_ReserveState( &mqttContext, MQTT_PACKET_ID_INVALID, MQTTQoS1 );
    TEST_ASSERT_EQUAL( MQTTBadParameter, status );
    status = MQTT_ReserveState( NULL, PACKET_ID, MQTTQoS1 );
    TEST_ASSERT_EQUAL( MQTTBadParameter, status );

    /* Test for collisions. */
    mqttContext.outgoingPublishRecords[ 0 ].packetId = PACKET_ID;
    mqttContext.outgoingPublishRecords[ 0 ].qos = MQTTQoS1;
    mqttContext.outgoingPublishRecords[ 0 ].publishState = MQTTPublishSend;

    status = MQTT_ReserveState( &mqttContext, PACKET_ID, MQTTQoS1 );
    TEST_ASSERT_EQUAL( MQTTStateCollision, status );


    /* Test for no memory. */
    fillRecord( mqttContext.outgoingPublishRecords, 2, MQTTQoS1, MQTTPublishSend );
    status = MQTT_ReserveState( &mqttContext, PACKET_ID, MQTTQoS1 );
    TEST_ASSERT_EQUAL( MQTTNoMemory, status );

    /* Success. */
    resetPublishRecords( &mqttContext );
    status = MQTT_ReserveState( &mqttContext, PACKET_ID, MQTTQoS1 );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    /* Reserve uses first available entry. */
    TEST_ASSERT_EQUAL( PACKET_ID, mqttContext.outgoingPublishRecords[ 0 ].packetId );
    TEST_ASSERT_EQUAL( MQTTQoS1, mqttContext.outgoingPublishRecords[ 0 ].qos );
    TEST_ASSERT_EQUAL( MQTTPublishSend, mqttContext.outgoingPublishRecords[ 0 ].publishState );

    /* Success.
     * Add record after the highest non empty index.
     * Already an entry exists at index 0. Adding 1 more entry at index 5.
     * The new index used should be 6. */
    addToRecord( mqttContext.outgoingPublishRecords, index, PACKET_ID2, MQTTQoS2, MQTTPubRelSend );
    status = MQTT_ReserveState( &mqttContext, PACKET_ID3, MQTTQoS1 );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    TEST_ASSERT_EQUAL( PACKET_ID3, mqttContext.outgoingPublishRecords[ index + 1 ].packetId );
    TEST_ASSERT_EQUAL( MQTTQoS1, mqttContext.outgoingPublishRecords[ index + 1 ].qos );
    TEST_ASSERT_EQUAL( MQTTPublishSend, mqttContext.outgoingPublishRecords[ index + 1 ].publishState );
}

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

void test_MQTT_RemoveStateRecord_ContextNULL( void )
{
    MQTTStatus_t status;

    status = MQTT_RemoveStateRecord( NULL, 0 );

    TEST_ASSERT_EQUAL( MQTTBadParameter, status );
}

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

void test_MQTT_RemoveStateRecord_RecordsNULL( void )
{
    MQTTStatus_t status;
    MQTTContext_t context;

    memset( &context, 0, sizeof( MQTTContext_t ) );

    status = MQTT_RemoveStateRecord( &context, 0 );

    TEST_ASSERT_EQUAL( MQTTBadParameter, status );
}

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

void test_MQTT_RemoveStateRecord_NoRecordFound( void )
{
    MQTTStatus_t status;
    MQTTContext_t context;
    MQTTPubAckInfo_t outgoingRecords[ 5 ];

    memset( &context, 0, sizeof( MQTTContext_t ) );

    context.outgoingPublishRecords = outgoingRecords;
    context.outgoingPublishRecordMaxCount = 5;

    memset( context.outgoingPublishRecords, 0, sizeof( outgoingRecords ) );

    /* Any non-zero packet ID. */
    status = MQTT_RemoveStateRecord( &context, 12U );

    TEST_ASSERT_EQUAL( MQTTBadParameter, status );
}

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

void test_MQTT_RemoveStateRecord_RecordFoundIncorrectQoS( void )
{
    MQTTStatus_t status;
    MQTTContext_t context;
    MQTTPubAckInfo_t outgoingRecords[ 5 ];
    const uint16_t packetID = 12;
    /* Any state except null state. */
    const MQTTPublishState_t state = MQTTPubRelSend;

    memset( &context, 0, sizeof( MQTTContext_t ) );

    context.outgoingPublishRecords = outgoingRecords;
    context.outgoingPublishRecordMaxCount = 5;

    memset( context.outgoingPublishRecords, 0, sizeof( outgoingRecords ) );

    context.outgoingPublishRecords[ 0 ].packetId = packetID;
    context.outgoingPublishRecords[ 0 ].publishState = state;
    context.outgoingPublishRecords[ 0 ].qos = MQTTQoS0;

    /* Any non-zero packet ID. */
    status = MQTT_RemoveStateRecord( &context, packetID );

    TEST_ASSERT_EQUAL( MQTTBadParameter, status );
}

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

void test_MQTT_RemoveStateRecord_RemoveQoS1Record( void )
{
    MQTTStatus_t status;
    MQTTContext_t context;
    MQTTPubAckInfo_t outgoingRecords[ 5 ];
    const uint16_t packetID = 12;
    /* Any state except null state. */
    const MQTTPublishState_t state = MQTTPubRelSend;

    memset( &context, 0, sizeof( MQTTContext_t ) );

    context.outgoingPublishRecords = outgoingRecords;
    context.outgoingPublishRecordMaxCount = 5;

    memset( context.outgoingPublishRecords, 0, sizeof( outgoingRecords ) );

    context.outgoingPublishRecords[ 1 ].packetId = packetID;
    context.outgoingPublishRecords[ 1 ].publishState = state;
    context.outgoingPublishRecords[ 1 ].qos = MQTTQoS1;

    /* Any non-zero packet ID. */
    status = MQTT_RemoveStateRecord( &context, packetID );

    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    TEST_ASSERT_EQUAL( context.outgoingPublishRecords[ 1 ].packetId, MQTT_PACKET_ID_INVALID );
    TEST_ASSERT_EQUAL( context.outgoingPublishRecords[ 1 ].publishState, MQTTStateNull );
    TEST_ASSERT_EQUAL( context.outgoingPublishRecords[ 1 ].qos, MQTTQoS0 );
}

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

void test_MQTT_RemoveStateRecord_RemoveQoS2Record( void )
{
    MQTTStatus_t status;
    MQTTContext_t context;
    MQTTPubAckInfo_t outgoingRecords[ 5 ];
    const uint16_t packetID = 12;
    /* Any state except null state. */
    const MQTTPublishState_t state = MQTTPubRelSend;

    memset( &context, 0, sizeof( MQTTContext_t ) );

    context.outgoingPublishRecords = outgoingRecords;
    context.outgoingPublishRecordMaxCount = 5;

    memset( context.outgoingPublishRecords, 0, sizeof( outgoingRecords ) );

    context.outgoingPublishRecords[ 1 ].packetId = packetID;
    context.outgoingPublishRecords[ 1 ].publishState = state;
    context.outgoingPublishRecords[ 1 ].qos = MQTTQoS2;

    /* Any non-zero packet ID. */
    status = MQTT_RemoveStateRecord( &context, packetID );

    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    TEST_ASSERT_EQUAL( context.outgoingPublishRecords[ 1 ].packetId, MQTT_PACKET_ID_INVALID );
    TEST_ASSERT_EQUAL( context.outgoingPublishRecords[ 1 ].publishState, MQTTStateNull );
    TEST_ASSERT_EQUAL( context.outgoingPublishRecords[ 1 ].qos, MQTTQoS0 );
}

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

void test_MQTT_ReserveState_compactRecords( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTStatus_t status;
    const uint16_t PACKET_ID = 1;
    const uint16_t PACKET_ID2 = 2;

    TransportInterface_t transport;
    MQTTFixedBuffer_t networkBuffer = { 0 };

    transport.recv = transportRecvSuccess;
    transport.send = transportSendSuccess;

    MQTTPubAckInfo_t incomingRecords[ MQTT_STATE_ARRAY_MAX_COUNT ] = { 0 };
    MQTTPubAckInfo_t outgoingRecords[ MQTT_STATE_ARRAY_MAX_COUNT ] = { 0 };

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

    status = MQTT_InitStatefulQoS( &mqttContext,
                                   outgoingRecords, MQTT_STATE_ARRAY_MAX_COUNT,
                                   incomingRecords, MQTT_STATE_ARRAY_MAX_COUNT );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );

    /* Consider the state of the array with 2 states. 1 indicates a non empty
     * spot and 0 an empty spot. Size of the array is 10.
     * Pre condition - 0 0 0 0 0 0 0 0 0 1.
     * Add an element will try to compact the array and the resulting state
     * should be - 1 1 0 0 0 0 0 0 0 0. */
    addToRecord( mqttContext.outgoingPublishRecords,
                 MQTT_STATE_ARRAY_MAX_COUNT - 1,
                 PACKET_ID,
                 MQTTQoS1,
                 MQTTPubRelSend );
    status = MQTT_ReserveState( &mqttContext, PACKET_ID2, MQTTQoS1 );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    /* The existing record should be at index 0. */
    validateRecordAt( mqttContext.outgoingPublishRecords,
                      0,
                      PACKET_ID,
                      MQTTQoS1,
                      MQTTPubRelSend );
    /* New record should be added to index 1. */
    validateRecordAt( mqttContext.outgoingPublishRecords,
                      1,
                      PACKET_ID2,
                      MQTTQoS1,
                      MQTTPublishSend );

    /* One free spot.
     * Pre condition - 1 1 1 0 1 1 1 1 1 1.
     * Add an element will try to compact the array and the resulting state
     * should be - 1 1 1 1 1 1 1 1 1 1. */
    fillRecord( mqttContext.outgoingPublishRecords, PACKET_ID2 + 1, MQTTQoS2, MQTTPubRelSend );
    /* Invalid record at index 3. */
    mqttContext.outgoingPublishRecords[ 3 ].packetId = MQTT_PACKET_ID_INVALID;
    status = MQTT_ReserveState( &mqttContext, PACKET_ID, MQTTQoS1 );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    /* The new record should be added to the end. */
    validateRecordAt( mqttContext.outgoingPublishRecords,
                      MQTT_STATE_ARRAY_MAX_COUNT - 1,
                      PACKET_ID,
                      MQTTQoS1,
                      MQTTPublishSend );
    /* Any new add should result in no memory error. */
    status = MQTT_ReserveState( &mqttContext, PACKET_ID2, MQTTQoS1 );
    TEST_ASSERT_EQUAL( MQTTNoMemory, status );

    /* Alternate free spots.
     * Pre condition - 1 0 1 0 1 0 1 0 1 0.
     * Add an element will skip to compact the array and the resulting state
     * should be - 1 0 1 0 1 0 1 0 1 1. */
    fillRecord( mqttContext.outgoingPublishRecords, PACKET_ID2 + 1, MQTTQoS2, MQTTPubRelSend );
    /* Invalidate record at alternate indexes starting from 1. */
    mqttContext.outgoingPublishRecords[ 1 ].packetId = MQTT_PACKET_ID_INVALID;
    mqttContext.outgoingPublishRecords[ 3 ].packetId = MQTT_PACKET_ID_INVALID;
    mqttContext.outgoingPublishRecords[ 5 ].packetId = MQTT_PACKET_ID_INVALID;
    mqttContext.outgoingPublishRecords[ 7 ].packetId = MQTT_PACKET_ID_INVALID;
    mqttContext.outgoingPublishRecords[ 9 ].packetId = MQTT_PACKET_ID_INVALID;
    status = MQTT_ReserveState( &mqttContext, PACKET_ID, MQTTQoS1 );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    /* The new record should be added to the end. */
    validateRecordAt( mqttContext.outgoingPublishRecords,
                      MQTT_STATE_ARRAY_MAX_COUNT - 1,
                      PACKET_ID,
                      MQTTQoS1,
                      MQTTPublishSend );

    /* Array is in state 1 0 1 0 1 0 1 0 1 1.
     * Adding one more element should result in array in state
     * 1 1 1 1 1 1 1 0 0 0. */
    status = MQTT_ReserveState( &mqttContext, PACKET_ID2, MQTTQoS1 );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    validateRecordAt( mqttContext.outgoingPublishRecords, 6, PACKET_ID2, MQTTQoS1, MQTTPublishSend );
    /* Remaining records should be invalid. */
    TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 7 ].packetId );
    TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 8 ].packetId );
    TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 9 ].packetId );

    /* Free spots only in the beginning.
     * Pre condition - 0 0 0 0 0 1 1 1 1 1.
     * Add an element will compact the array and the resulting state
     * should be - 1 1 1 1 1 1 0 0 0 0. */
    fillRecord( mqttContext.outgoingPublishRecords, PACKET_ID2 + 1, MQTTQoS2, MQTTPubRelSend );
    /* Clear record from 0 to 4. */
    ( void ) memset( &mqttContext.outgoingPublishRecords[ 0 ], 0x00, 5 * sizeof( MQTTPubAckInfo_t ) );

    /* Adding one element should result in array in state
     * 1 1 1 1 1 1 0 0 0 0. */
    status = MQTT_ReserveState( &mqttContext, PACKET_ID2, MQTTQoS1 );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    validateRecordAt( mqttContext.outgoingPublishRecords, 5, PACKET_ID2, MQTTQoS1, MQTTPublishSend );
    /* Remaining records should be cleared. */
    TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 6 ].packetId );
    TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 7 ].packetId );
    TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 8 ].packetId );
    TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 9 ].packetId );

    /* Fragmented array.
     * Pre condition - 1 0 0 1 1 1 1 0 0 1.
     * Add an element will compact the array and the resulting state
     * should be - 1 1 1 1 1 1 1 0 0 0. */
    fillRecord( mqttContext.outgoingPublishRecords, PACKET_ID2 + 1, MQTTQoS2, MQTTPubRelSend );
    /* Clear record at index 1,2,7 and 8. */
    mqttContext.outgoingPublishRecords[ 1 ].packetId = MQTT_PACKET_ID_INVALID;
    mqttContext.outgoingPublishRecords[ 2 ].packetId = MQTT_PACKET_ID_INVALID;
    mqttContext.outgoingPublishRecords[ 7 ].packetId = MQTT_PACKET_ID_INVALID;
    mqttContext.outgoingPublishRecords[ 8 ].packetId = MQTT_PACKET_ID_INVALID;
    status = MQTT_ReserveState( &mqttContext, PACKET_ID2, MQTTQoS1 );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    validateRecordAt( mqttContext.outgoingPublishRecords, 6, PACKET_ID2, MQTTQoS1, MQTTPublishSend );
    /* Remaining records should be cleared. */
    TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 7 ].packetId );
    TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 8 ].packetId );
    TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 9 ].packetId );

    /* Fragmented array.
     * Pre condition - 1 0 0 0 0 0 0 0 0 1.
     * Add an element will compact the array and the resulting state
     * should be - 1 1 1 0 0 0 0 0 0 0. */
    resetPublishRecords( &mqttContext );
    addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRecPending );
    addToRecord( mqttContext.outgoingPublishRecords, 9, PACKET_ID2 + 1, MQTTQoS2, MQTTPubCompPending );
    status = MQTT_ReserveState( &mqttContext, PACKET_ID2, MQTTQoS1 );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    validateRecordAt( mqttContext.outgoingPublishRecords, 2, PACKET_ID2, MQTTQoS1, MQTTPublishSend );
    /* Validate existing records. */
    validateRecordAt( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRecPending );
    validateRecordAt( mqttContext.outgoingPublishRecords, 1, PACKET_ID2 + 1, MQTTQoS2, MQTTPubCompPending );
}

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

void test_MQTT_CalculateStatePublish( void )
{
    /* QoS 0. */
    TEST_ASSERT_EQUAL( MQTTPublishDone, MQTT_CalculateStatePublish( MQTT_SEND, MQTTQoS0 ) );
    TEST_ASSERT_EQUAL( MQTTPublishDone, MQTT_CalculateStatePublish( MQTT_RECEIVE, MQTTQoS0 ) );

    /* QoS 1. */
    TEST_ASSERT_EQUAL( MQTTPubAckPending, MQTT_CalculateStatePublish( MQTT_SEND, MQTTQoS1 ) );
    TEST_ASSERT_EQUAL( MQTTPubAckSend, MQTT_CalculateStatePublish( MQTT_RECEIVE, MQTTQoS1 ) );

    /* QoS 2. */
    TEST_ASSERT_EQUAL( MQTTPubRecPending, MQTT_CalculateStatePublish( MQTT_SEND, MQTTQoS2 ) );
    TEST_ASSERT_EQUAL( MQTTPubRecSend, MQTT_CalculateStatePublish( MQTT_RECEIVE, MQTTQoS2 ) );

    /* Invalid QoS. */
    TEST_ASSERT_EQUAL( MQTTStateNull, MQTT_CalculateStatePublish( MQTT_SEND, 3 ) );
}

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

void test_MQTT_UpdateStatePublish( void )
{
    MQTTContext_t mqttContext = { 0 };
    const uint16_t PACKET_ID = 1;
    MQTTStateOperation_t operation = MQTT_SEND;
    MQTTQoS_t qos = MQTTQoS0;
    MQTTPublishState_t state;
    MQTTStatus_t status;

    TransportInterface_t transport;
    MQTTFixedBuffer_t networkBuffer = { 0 };

    transport.recv = transportRecvSuccess;
    transport.send = transportSendSuccess;

    MQTTPubAckInfo_t incomingRecords[ MQTT_STATE_ARRAY_MAX_COUNT ] = { 0 };
    MQTTPubAckInfo_t outgoingRecords[ MQTT_STATE_ARRAY_MAX_COUNT ] = { 0 };

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

    status = MQTT_InitStatefulQoS( &mqttContext,
                                   outgoingRecords, MQTT_STATE_ARRAY_MAX_COUNT,
                                   incomingRecords, MQTT_STATE_ARRAY_MAX_COUNT );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );

    /* QoS 0. */
    status = MQTT_UpdateStatePublish( &mqttContext, 0, operation, qos, &state );
    TEST_ASSERT_EQUAL( MQTTPublishDone, state );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );

    /* Invalid parameters. */
    /* Invalid ID. */
    qos = MQTTQoS1;
    status = MQTT_UpdateStatePublish( &mqttContext, 0, operation, qos, &state );
    TEST_ASSERT_EQUAL( MQTTBadParameter, status );
    /* NULL context. */
    status = MQTT_UpdateStatePublish( NULL, PACKET_ID, operation, qos, &state );
    TEST_ASSERT_EQUAL( MQTTBadParameter, status );
    /* NULL new state. */
    status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, NULL );
    TEST_ASSERT_EQUAL( MQTTBadParameter, status );
    /* No record found. */
    status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state );
    TEST_ASSERT_EQUAL( MQTTBadParameter, status );
    /* QoS mismatch. */
    addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPublishSend );
    status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state );
    TEST_ASSERT_EQUAL( MQTTBadParameter, status );

    /* Invalid state transition. */
    addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS1, MQTTPubRelPending );
    status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state );
    TEST_ASSERT_EQUAL( MQTTIllegalState, status );

    /* Invalid QoS. */
    operation = MQTT_SEND;
    addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, 3, MQTTPublishSend );
    status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, 3, &state );
    TEST_ASSERT_EQUAL( MQTTIllegalState, status );
    operation = MQTT_RECEIVE;
    status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, 3, &state );
    TEST_ASSERT_EQUAL( MQTTIllegalState, status );

    /* Invalid current state. */
    operation = MQTT_SEND;
    addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, qos, MQTTStateNull );
    status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state );
    TEST_ASSERT_EQUAL( MQTTIllegalState, status );

    /* Collision. */
    operation = MQTT_RECEIVE;
    addToRecord( mqttContext.incomingPublishRecords, 0, PACKET_ID, MQTTQoS1, MQTTPubAckSend );
    status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state );
    TEST_ASSERT_EQUAL( MQTTStateCollision, status );

    /* No memory. */
    operation = MQTT_RECEIVE;
    fillRecord( mqttContext.incomingPublishRecords, 2, MQTTQoS1, MQTTPublishSend );
    status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state );
    TEST_ASSERT_EQUAL( MQTTNoMemory, status );

    resetPublishRecords( &mqttContext );

    /* QoS 1. */
    qos = MQTTQoS1;
    /* Send. */
    operation = MQTT_SEND;
    addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS1, MQTTPublishSend );
    status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    TEST_ASSERT_EQUAL( MQTTPubAckPending, state );
    TEST_ASSERT_EQUAL( MQTTPubAckPending, mqttContext.outgoingPublishRecords[ 0 ].publishState );
    /* Resend when record already exists. */
    status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    TEST_ASSERT_EQUAL( MQTTPubAckPending, state );
    TEST_ASSERT_EQUAL( MQTTPubAckPending, mqttContext.outgoingPublishRecords[ 0 ].publishState );
    /* Receive. */
    operation = MQTT_RECEIVE;
    status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    TEST_ASSERT_EQUAL( MQTTPubAckSend, state );
    TEST_ASSERT_EQUAL( MQTTPubAckSend, mqttContext.incomingPublishRecords[ 0 ].publishState );
    /* Receive duplicate incoming publish. */
    status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state );
    TEST_ASSERT_EQUAL( MQTTStateCollision, status );
    TEST_ASSERT_EQUAL( MQTTPubAckSend, state );
    TEST_ASSERT_EQUAL( MQTTPubAckSend, mqttContext.incomingPublishRecords[ 0 ].publishState );

    resetPublishRecords( &mqttContext );

    /* QoS 2. */
    qos = MQTTQoS2;
    /* Send. */
    operation = MQTT_SEND;
    addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPublishSend );
    status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    TEST_ASSERT_EQUAL( MQTTPubRecPending, state );
    TEST_ASSERT_EQUAL( MQTTPubRecPending, mqttContext.outgoingPublishRecords[ 0 ].publishState );
    /* Resend when record already exists. */
    status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    TEST_ASSERT_EQUAL( MQTTPubRecPending, state );
    TEST_ASSERT_EQUAL( MQTTPubRecPending, mqttContext.outgoingPublishRecords[ 0 ].publishState );
    /* Receive. */
    operation = MQTT_RECEIVE;
    status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    TEST_ASSERT_EQUAL( MQTTPubRecSend, state );
    TEST_ASSERT_EQUAL( MQTTPubRecSend, mqttContext.incomingPublishRecords[ 0 ].publishState );
    /* Receive incoming publish when the packet record is in state #MQTTPubRecSend. */
    status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state );
    TEST_ASSERT_EQUAL( MQTTStateCollision, status );
    TEST_ASSERT_EQUAL( MQTTPubRecSend, state );
    TEST_ASSERT_EQUAL( MQTTPubRecSend, mqttContext.incomingPublishRecords[ 0 ].publishState );
    /* Receive incoming publish when the packet record is in state #MQTTPubRelPending. */
    addToRecord( mqttContext.incomingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRelPending );
    status = MQTT_UpdateStatePublish( &mqttContext, PACKET_ID, operation, qos, &state );
    TEST_ASSERT_EQUAL( MQTTStateCollision, status );
    /* The returned state will always be #MQTTPubRecSend as a PUBREC need to be sent. */
    TEST_ASSERT_EQUAL( MQTTPubRecSend, state );
    TEST_ASSERT_EQUAL( MQTTPubRelPending, mqttContext.incomingPublishRecords[ 0 ].publishState );
}

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

void test_MQTT_CalculateStateAck( void )
{
    MQTTPubAckType_t ack;
    MQTTQoS_t qos;
    MQTTStateOperation_t opType;

    /* Invalid qos. */
    qos = MQTTQoS0;
    ack = MQTTPuback;
    opType = MQTT_SEND;
    TEST_ASSERT_EQUAL( MQTTStateNull, MQTT_CalculateStateAck( ack, opType, qos ) );
    qos = MQTTQoS2;
    TEST_ASSERT_EQUAL( MQTTStateNull, MQTT_CalculateStateAck( ack, opType, qos ) );
    qos = MQTTQoS0;
    ack = MQTTPubrec;
    TEST_ASSERT_EQUAL( MQTTStateNull, MQTT_CalculateStateAck( ack, opType, qos ) );
    qos = MQTTQoS1;
    TEST_ASSERT_EQUAL( MQTTStateNull, MQTT_CalculateStateAck( ack, opType, qos ) );

    /* Invalid ack type. */
    ack = MQTTPubcomp + 1;
    TEST_ASSERT_EQUAL( MQTTStateNull, MQTT_CalculateStateAck( ack, opType, qos ) );

    /* PUBACK */
    ack = MQTTPuback;
    qos = MQTTQoS1;
    opType = MQTT_SEND;
    TEST_ASSERT_EQUAL( MQTTPublishDone, MQTT_CalculateStateAck( ack, opType, qos ) );
    opType = MQTT_RECEIVE;
    TEST_ASSERT_EQUAL( MQTTPublishDone, MQTT_CalculateStateAck( ack, opType, qos ) );

    /* QoS 2 tests. */
    qos = MQTTQoS2;

    /* PUBREC */
    ack = MQTTPubrec;
    /* Send */
    opType = MQTT_SEND;
    TEST_ASSERT_EQUAL( MQTTPubRelPending, MQTT_CalculateStateAck( ack, opType, qos ) );
    /* Receive */
    opType = MQTT_RECEIVE;
    TEST_ASSERT_EQUAL( MQTTPubRelSend, MQTT_CalculateStateAck( ack, opType, qos ) );

    /* PUBREL */
    ack = MQTTPubrel;
    /* Send */
    opType = MQTT_SEND;
    TEST_ASSERT_EQUAL( MQTTPubCompPending, MQTT_CalculateStateAck( ack, opType, qos ) );
    /* Receive */
    opType = MQTT_RECEIVE;
    TEST_ASSERT_EQUAL( MQTTPubCompSend, MQTT_CalculateStateAck( ack, opType, qos ) );

    /* PUBCOMP */
    ack = MQTTPubcomp;
    TEST_ASSERT_EQUAL( MQTTPublishDone, MQTT_CalculateStateAck( ack, opType, qos ) );
    opType = MQTT_SEND;
    TEST_ASSERT_EQUAL( MQTTPublishDone, MQTT_CalculateStateAck( ack, opType, qos ) );
}

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

void test_MQTT_UpdateStateAck( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTPubAckType_t ack = MQTTPuback;
    MQTTStateOperation_t operation = MQTT_RECEIVE;
    MQTTPublishState_t state = MQTTStateNull;
    MQTTStatus_t status;

    const uint16_t PACKET_ID = 1;
    TransportInterface_t transport;
    MQTTFixedBuffer_t networkBuffer = { 0 };

    transport.recv = transportRecvSuccess;
    transport.send = transportSendSuccess;

    MQTTPubAckInfo_t incomingRecords[ MQTT_STATE_ARRAY_MAX_COUNT ] = { 0 };
    MQTTPubAckInfo_t outgoingRecords[ MQTT_STATE_ARRAY_MAX_COUNT ] = { 0 };

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

    status = MQTT_InitStatefulQoS( &mqttContext,
                                   outgoingRecords, MQTT_STATE_ARRAY_MAX_COUNT,
                                   incomingRecords, MQTT_STATE_ARRAY_MAX_COUNT );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );

    /* NULL parameters. */
    status = MQTT_UpdateStateAck( NULL, PACKET_ID, ack, operation, &state );
    TEST_ASSERT_EQUAL( MQTTBadParameter, status );
    status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, NULL );
    TEST_ASSERT_EQUAL( MQTTBadParameter, status );
    /* Invalid packet ID. */
    status = MQTT_UpdateStateAck( &mqttContext, 0, ack, operation, &state );
    TEST_ASSERT_EQUAL( MQTTBadParameter, status );
    /* No matching record found. */
    status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state );
    TEST_ASSERT_EQUAL( MQTTBadResponse, status );

    /* Invalid transitions. */
    /* Invalid transition from #MQTTPubRelPending. */
    addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRelPending );
    ack = MQTTPubrel;
    operation = MQTT_SEND;
    status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state );
    TEST_ASSERT_EQUAL( MQTTIllegalState, status );
    /* Invalid transition from #MQTTPubCompSend. */
    addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubCompSend );
    status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state );
    TEST_ASSERT_EQUAL( MQTTIllegalState, status );
    /* Invalid transition from #MQTTPubCompPending. */
    addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubCompPending );
    ack = MQTTPubrec;
    operation = MQTT_RECEIVE;
    status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state );
    TEST_ASSERT_EQUAL( MQTTIllegalState, status );
    /* Invalid transition from #MQTTPubRecPending. */
    addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRecPending );
    ack = MQTTPubcomp;
    status = MQTT_UpdateStateAck( &mqttContext, 1, ack, operation, &state );
    TEST_ASSERT_EQUAL( MQTTIllegalState, status );

    /* Invalid ack type. */
    status = MQTT_UpdateStateAck( &mqttContext, 1, MQTTPubcomp + 1, operation, &state );
    TEST_ASSERT_EQUAL( MQTTBadParameter, status );

    /* Invalid current state. */
    addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPublishDone );
    status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state );
    TEST_ASSERT_EQUAL( MQTTIllegalState, status );
    addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPublishSend );
    status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state );
    TEST_ASSERT_EQUAL( MQTTIllegalState, status );
    addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTStateNull );
    status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state );
    TEST_ASSERT_EQUAL( MQTTIllegalState, status );

    resetPublishRecords( &mqttContext );

    /* QoS 1, receive PUBACK for outgoing publish. */
    addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS1, MQTTPubAckPending );
    operation = MQTT_RECEIVE;
    ack = MQTTPuback;
    status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    TEST_ASSERT_EQUAL( MQTTPublishDone, state );

    /* Test for deletion. */
    TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 0 ].packetId );
    /* Send PUBACK for incoming publish. */
    operation = MQTT_SEND;
    addToRecord( mqttContext.incomingPublishRecords, 0, PACKET_ID, MQTTQoS1, MQTTPubAckSend );
    status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    TEST_ASSERT_EQUAL( MQTTPublishDone, state );

    resetPublishRecords( &mqttContext );

    /* QoS 2, PUBREL. */
    /* Outgoing. */
    addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRelSend );
    operation = MQTT_SEND;
    ack = MQTTPubrel;
    status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    TEST_ASSERT_EQUAL( MQTTPubCompPending, state );
    /* Outgoing. Resend PUBREL when record in state #MQTTPubCompPending. */
    operation = MQTT_SEND;
    ack = MQTTPubrel;
    status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    TEST_ASSERT_EQUAL( MQTTPubCompPending, state );
    /* Incoming. */
    addToRecord( mqttContext.incomingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRelPending );
    operation = MQTT_RECEIVE;
    status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    TEST_ASSERT_EQUAL( MQTTPubCompSend, state );
    /* Test for update. */
    TEST_ASSERT_EQUAL( MQTTPubCompSend, mqttContext.incomingPublishRecords[ 0 ].publishState );
    /* Incoming. Duplicate PUBREL is received when record is in state #MQTTPubRelPending. */
    status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    TEST_ASSERT_EQUAL( MQTTPubCompSend, state );

    /* QoS 2, PUBREC. */
    /* Outgoing. */
    addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRecPending );
    operation = MQTT_RECEIVE;
    ack = MQTTPubrec;
    status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    TEST_ASSERT_EQUAL( MQTTPubRelSend, state );

    /* Receiving a PUBREC will move the record to the end.
     * In this case, only one record exists, no moving is required. */
    TEST_ASSERT_EQUAL( PACKET_ID, mqttContext.outgoingPublishRecords[ 0 ].packetId );
    TEST_ASSERT_EQUAL( MQTTQoS2, mqttContext.outgoingPublishRecords[ 0 ].qos );
    TEST_ASSERT_EQUAL( MQTTPubRelSend, mqttContext.outgoingPublishRecords[ 0 ].publishState );

    /* Outgoing.
     * Test if the record moves to the end of the records when PUBREC is
     * received. */
    resetPublishRecords( &mqttContext );
    addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRecPending );
    addToRecord( mqttContext.outgoingPublishRecords, 1, PACKET_ID + 1, MQTTQoS2, MQTTPubRelSend );
    status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    TEST_ASSERT_EQUAL( MQTTPubRelSend, state );

    /* Receiving a PUBREC will move the record to the end.
     * In this case, the record will be moved to index 2. */
    TEST_ASSERT_EQUAL( PACKET_ID, mqttContext.outgoingPublishRecords[ 2 ].packetId );
    TEST_ASSERT_EQUAL( MQTTQoS2, mqttContext.outgoingPublishRecords[ 2 ].qos );
    TEST_ASSERT_EQUAL( MQTTPubRelSend, mqttContext.outgoingPublishRecords[ 2 ].publishState );
    /* Record at the current index will be marked as invalid. */
    TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, mqttContext.outgoingPublishRecords[ 0 ].packetId );

    /* Incoming. */
    addToRecord( mqttContext.incomingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRecSend );
    operation = MQTT_SEND;
    status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    TEST_ASSERT_EQUAL( MQTTPubRelPending, state );
    /* Incoming. Duplicate publish received and record is in state #MQTTPubRelPending. */
    addToRecord( mqttContext.incomingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubRelPending );
    status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    TEST_ASSERT_EQUAL( MQTTPubRelPending, state );

    /* QoS 2, PUBCOMP. */
    /* Outgoing. */
    addToRecord( mqttContext.outgoingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubCompPending );
    operation = MQTT_RECEIVE;
    ack = MQTTPubcomp;
    status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    TEST_ASSERT_EQUAL( MQTTPublishDone, state );
    /* Incoming. */
    addToRecord( mqttContext.incomingPublishRecords, 0, PACKET_ID, MQTTQoS2, MQTTPubCompSend );
    operation = MQTT_SEND;
    status = MQTT_UpdateStateAck( &mqttContext, PACKET_ID, ack, operation, &state );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );
    TEST_ASSERT_EQUAL( MQTTPublishDone, state );
}

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

void test_MQTT_AckToResend( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTStateCursor_t cursor = MQTT_STATE_CURSOR_INITIALIZER;
    MQTTPublishState_t state = MQTTStateNull;
    uint16_t packetId;
    const uint16_t PACKET_ID = 1;
    const uint16_t PACKET_ID2 = 2;
    const uint16_t PACKET_ID3 = 3;
    const uint16_t PACKET_ID4 = 4;
    const size_t index = 0;
    const size_t index2 = 1;
    const size_t index3 = MQTT_STATE_ARRAY_MAX_COUNT / 2;
    const size_t index4 = index3 + 2;

    MQTTPubAckInfo_t incomingRecords[ MQTT_STATE_ARRAY_MAX_COUNT ] = { 0 };
    MQTTPubAckInfo_t outgoingRecords[ MQTT_STATE_ARRAY_MAX_COUNT ] = { 0 };
    MQTTStatus_t status;
    TransportInterface_t transport;
    MQTTFixedBuffer_t networkBuffer = { 0 };

    transport.recv = transportRecvSuccess;
    transport.send = transportSendSuccess;

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

    status = MQTT_InitStatefulQoS( &mqttContext,
                                   outgoingRecords, MQTT_STATE_ARRAY_MAX_COUNT,
                                   incomingRecords, MQTT_STATE_ARRAY_MAX_COUNT );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );

    /* Invalid parameters. */
    packetId = MQTT_PubrelToResend( NULL, &cursor, &state );
    TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId );
    packetId = MQTT_PubrelToResend( &mqttContext, NULL, &state );
    TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId );
    packetId = MQTT_PubrelToResend( &mqttContext, &cursor, NULL );
    TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId );

    /* No packet exists. */
    cursor = MQTT_STATE_CURSOR_INITIALIZER;
    packetId = MQTT_PubrelToResend( &mqttContext, &cursor, &state );
    TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId );
    TEST_ASSERT_EQUAL( MQTTStateNull, state );
    TEST_ASSERT_EQUAL( MQTT_STATE_ARRAY_MAX_COUNT, cursor );

    /* No packet exists in state #MQTTPubCompPending or #MQTTPubCompPending states. */
    cursor = MQTT_STATE_CURSOR_INITIALIZER;
    addToRecord( mqttContext.outgoingPublishRecords, index, PACKET_ID3, MQTTQoS2, MQTTPubRelPending );
    addToRecord( mqttContext.outgoingPublishRecords, index2, PACKET_ID4, MQTTQoS2, MQTTPubCompSend );
    packetId = MQTT_PubrelToResend( &mqttContext, &cursor, &state );
    TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId );
    TEST_ASSERT_EQUAL( MQTTStateNull, state );
    TEST_ASSERT_EQUAL( MQTT_STATE_ARRAY_MAX_COUNT, cursor );

    /* Add a record in #MQTTPubCompPending state. */
    cursor = MQTT_STATE_CURSOR_INITIALIZER;
    addToRecord( mqttContext.outgoingPublishRecords, index3, PACKET_ID, MQTTQoS2, MQTTPubCompPending );
    packetId = MQTT_PubrelToResend( &mqttContext, &cursor, &state );
    TEST_ASSERT_EQUAL( PACKET_ID, packetId );
    TEST_ASSERT_EQUAL( index3 + 1, cursor );
    TEST_ASSERT_EQUAL( MQTTPubRelSend, state );

    /* Add another record in #MQTTPubCompPending state. */
    addToRecord( mqttContext.outgoingPublishRecords, index4, PACKET_ID2, MQTTQoS2, MQTTPubCompPending );
    packetId = MQTT_PubrelToResend( &mqttContext, &cursor, &state );
    TEST_ASSERT_EQUAL( PACKET_ID2, packetId );
    TEST_ASSERT_EQUAL( index4 + 1, cursor );
    TEST_ASSERT_EQUAL( MQTTPubRelSend, state );

    /* Add another record in #MQTTPubRelSend state. */
    addToRecord( mqttContext.outgoingPublishRecords, index4 + 1, PACKET_ID2 + 1, MQTTQoS2, MQTTPubRelSend );
    packetId = MQTT_PubrelToResend( &mqttContext, &cursor, &state );
    TEST_ASSERT_EQUAL( PACKET_ID2 + 1, packetId );
    TEST_ASSERT_EQUAL( index4 + 2, cursor );
    TEST_ASSERT_EQUAL( MQTTPubRelSend, state );

    /* Only one record in #MQTTPubRelSend state. */
    resetPublishRecords( &mqttContext );
    cursor = MQTT_STATE_CURSOR_INITIALIZER;
    addToRecord( mqttContext.outgoingPublishRecords, index3, PACKET_ID, MQTTQoS2, MQTTPubRelSend );
    packetId = MQTT_PubrelToResend( &mqttContext, &cursor, &state );
    TEST_ASSERT_EQUAL( PACKET_ID, packetId );
    TEST_ASSERT_EQUAL( index3 + 1, cursor );
    TEST_ASSERT_EQUAL( MQTTPubRelSend, state );

    /* Further search should be return no valid packets. */
    state = MQTTStateNull;
    packetId = MQTT_PubrelToResend( &mqttContext, &cursor, &state );
    TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId );
    TEST_ASSERT_EQUAL( MQTTStateNull, state );
    TEST_ASSERT_EQUAL( MQTT_STATE_ARRAY_MAX_COUNT, cursor );
}

void test_MQTT_PublishToResend( void )
{
    MQTTContext_t mqttContext = { 0 };
    MQTTStateCursor_t cursor = MQTT_STATE_CURSOR_INITIALIZER;
    uint16_t packetId;
    const uint16_t PACKET_ID = 1;
    const uint16_t PACKET_ID2 = 2;
    const uint16_t PACKET_ID3 = 3;
    const uint16_t PACKET_ID4 = 4;
    const size_t index = 0;
    const size_t index2 = 1;
    const size_t index3 = MQTT_STATE_ARRAY_MAX_COUNT / 2;
    const size_t index4 = index3 + 2;

    MQTTPubAckInfo_t incomingRecords[ MQTT_STATE_ARRAY_MAX_COUNT ] = { 0 };
    MQTTPubAckInfo_t outgoingRecords[ MQTT_STATE_ARRAY_MAX_COUNT ] = { 0 };
    MQTTStatus_t status;
    TransportInterface_t transport = { 0 };

    transport.recv = transportRecvSuccess;
    transport.send = transportSendSuccess;

    MQTTFixedBuffer_t networkBuffer = { 0 };

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

    status = MQTT_InitStatefulQoS( &mqttContext,
                                   outgoingRecords, MQTT_STATE_ARRAY_MAX_COUNT,
                                   incomingRecords, MQTT_STATE_ARRAY_MAX_COUNT );
    TEST_ASSERT_EQUAL( MQTTSuccess, status );


    /* Invalid parameters. */
    packetId = MQTT_PublishToResend( NULL, &cursor );
    TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId );
    packetId = MQTT_PublishToResend( &mqttContext, NULL );
    TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId );

    /* No packet exists. */
    cursor = MQTT_STATE_CURSOR_INITIALIZER;
    packetId = MQTT_PublishToResend( &mqttContext, &cursor );
    TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId );
    TEST_ASSERT_EQUAL( MQTT_STATE_ARRAY_MAX_COUNT, cursor );

    /* No packet exists in state #MQTTPublishSend, #MQTTPubAckPending and
     * #MQTTPubRecPending states. */
    cursor = MQTT_STATE_CURSOR_INITIALIZER;
    addToRecord( mqttContext.outgoingPublishRecords, index, PACKET_ID3, MQTTQoS2, MQTTPubCompPending );
    addToRecord( mqttContext.outgoingPublishRecords, index2, PACKET_ID4, MQTTQoS2, MQTTPubRelSend );
    packetId = MQTT_PublishToResend( &mqttContext, &cursor );
    TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId );
    TEST_ASSERT_EQUAL( MQTT_STATE_ARRAY_MAX_COUNT, cursor );

    /* Add a record in #MQTTPublishSend state. */
    cursor = MQTT_STATE_CURSOR_INITIALIZER;
    addToRecord( mqttContext.outgoingPublishRecords, index3, PACKET_ID, MQTTQoS2, MQTTPublishSend );
    packetId = MQTT_PublishToResend( &mqttContext, &cursor );
    TEST_ASSERT_EQUAL( PACKET_ID, packetId );
    TEST_ASSERT_EQUAL( index3 + 1, cursor );

    /* Add another record in #MQTTPubAckPending state. */
    addToRecord( mqttContext.outgoingPublishRecords, index4, PACKET_ID2, MQTTQoS1, MQTTPubAckPending );
    packetId = MQTT_PublishToResend( &mqttContext, &cursor );
    TEST_ASSERT_EQUAL( PACKET_ID2, packetId );
    TEST_ASSERT_EQUAL( index4 + 1, cursor );

    /* Add another record in #MQTTPubRecPending state. */
    addToRecord( mqttContext.outgoingPublishRecords, index4 + 1, PACKET_ID2 + 1, MQTTQoS2, MQTTPubRecPending );
    packetId = MQTT_PublishToResend( &mqttContext, &cursor );
    TEST_ASSERT_EQUAL( PACKET_ID2 + 1, packetId );
    TEST_ASSERT_EQUAL( index4 + 2, cursor );

    /* Further search should find no packets. */
    packetId = MQTT_PublishToResend( &mqttContext, &cursor );
    TEST_ASSERT_EQUAL( MQTT_PACKET_ID_INVALID, packetId );
    TEST_ASSERT_EQUAL( MQTT_STATE_ARRAY_MAX_COUNT, cursor );
}

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

void test_MQTT_State_strerror( void )
{
    MQTTPublishState_t state;
    const char * str = NULL;

    state = MQTTStateNull;
    str = MQTT_State_strerror( state );
    TEST_ASSERT_EQUAL_STRING( "MQTTStateNull", str );

    state = MQTTPublishSend;
    str = MQTT_State_strerror( state );
    TEST_ASSERT_EQUAL_STRING( "MQTTPublishSend", str );

    state = MQTTPubAckSend;
    str = MQTT_State_strerror( state );
    TEST_ASSERT_EQUAL_STRING( "MQTTPubAckSend", str );

    state = MQTTPubRecSend;
    str = MQTT_State_strerror( state );
    TEST_ASSERT_EQUAL_STRING( "MQTTPubRecSend", str );

    state = MQTTPubRelSend;
    str = MQTT_State_strerror( state );
    TEST_ASSERT_EQUAL_STRING( "MQTTPubRelSend", str );

    state = MQTTPubCompSend;
    str = MQTT_State_strerror( state );
    TEST_ASSERT_EQUAL_STRING( "MQTTPubCompSend", str );

    state = MQTTPubAckPending;
    str = MQTT_State_strerror( state );
    TEST_ASSERT_EQUAL_STRING( "MQTTPubAckPending", str );

    state = MQTTPubRecPending;
    str = MQTT_State_strerror( state );
    TEST_ASSERT_EQUAL_STRING( "MQTTPubRecPending", str );

    state = MQTTPubRelPending;
    str = MQTT_State_strerror( state );
    TEST_ASSERT_EQUAL_STRING( "MQTTPubRelPending", str );

    state = MQTTPubCompPending;
    str = MQTT_State_strerror( state );
    TEST_ASSERT_EQUAL_STRING( "MQTTPubCompPending", str );

    state = MQTTPublishDone;
    str = MQTT_State_strerror( state );
    TEST_ASSERT_EQUAL_STRING( "MQTTPublishDone", str );

    state = MQTTPublishDone + 1;
    str = MQTT_State_strerror( state );
    TEST_ASSERT_EQUAL_STRING( "Invalid MQTT State", str );
}

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