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

/**
 * @file core_mqtt_stubs.h
 * @brief Stub functions to interact with queues.
 */

#include "core_mqtt.h"
#include "core_mqtt_state.h"
#include <string.h>


static bool isValidIncomingMqttPacket( uint8_t packetType )
{
    bool isValid = false;

    switch( packetType )
    {
        case MQTT_PACKET_TYPE_PUBACK:
        case MQTT_PACKET_TYPE_PUBCOMP:
        case MQTT_PACKET_TYPE_SUBACK:
        case MQTT_PACKET_TYPE_UNSUBACK:
        case MQTT_PACKET_TYPE_CONNACK:
        case MQTT_PACKET_TYPE_PUBLISH:
        case MQTT_PACKET_TYPE_PUBREC:
        case MQTT_PACKET_TYPE_PUBREL:
        case MQTT_PACKET_TYPE_PINGRESP:
            isValid = true;
            break;

        default:
            break;
    }

    return isValid;
}

MQTTStatus_t MQTT_Init( MQTTContext_t * pContext,
                        const TransportInterface_t * pTransportInterface,
                        MQTTGetCurrentTimeFunc_t getTimeFunction,
                        MQTTEventCallback_t userCallback,
                        const MQTTFixedBuffer_t * pNetworkBuffer )
{
    MQTTStatus_t status;

    __CPROVER_assert( pContext != NULL,
                      "MQTT Context is not NULL." );

    ( void ) memset( pContext, 0x00, sizeof( MQTTContext_t ) );

    pContext->connectStatus = MQTTNotConnected;
    pContext->transportInterface = *pTransportInterface;
    pContext->getTime = getTimeFunction;
    pContext->appCallback = userCallback;
    pContext->networkBuffer = *pNetworkBuffer;

    /* Zero is not a valid packet ID per MQTT spec. Start from 1. */
    pContext->nextPacketId = 1;

    return status;
}

MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * pContext )
{
    MQTTStatus_t status;
    MQTTPacketInfo_t * pPacketInfo;
    MQTTDeserializedInfo_t * pDeserializedInfo;
    size_t remainingLength;
    MQTTPublishInfo_t * pPublishInfo;
    uint16_t topicNameLength;
    size_t payloadLength;
    static bool terminate = false;

    /* These constants are used to limit the range of variable length fields in
     * an MQTT packet. This will improve the proof run time. */
    static const uint8_t maxRemainingLength = 64U;
    static const uint8_t subAckMinRemainingLength = 2U;
    static const uint8_t maxTopicNameLength = 32U;
    static const uint8_t maxPayloadLength = 64U;

    __CPROVER_assert( pContext != NULL,
                      "MQTT Context is not NULL." );

    /* Only one packet per MQTT_ProcessLoop is received for the proof and it will be enough
     * to prove the memory safety. The second invocation of MQTT_ProcessLoop returns without
     * invoking the appCallback. */
    if( terminate == false )
    {
        pPacketInfo = malloc( sizeof( MQTTPacketInfo_t ) );
        __CPROVER_assume( pPacketInfo != NULL );
        __CPROVER_assume( isValidIncomingMqttPacket( pPacketInfo->type ) );
        __CPROVER_assume( remainingLength < maxRemainingLength );

        /* SUBACK codes will be after 2 bytes. */
        if( pPacketInfo->type == MQTT_PACKET_TYPE_SUBACK )
        {
            __CPROVER_assume( remainingLength > subAckMinRemainingLength );
        }

        pPacketInfo->pRemainingData = malloc( remainingLength );
        __CPROVER_assume( pPacketInfo->pRemainingData != NULL );
        pPacketInfo->remainingLength = remainingLength;

        pDeserializedInfo = malloc( sizeof( MQTTDeserializedInfo_t ) );
        __CPROVER_assume( pDeserializedInfo != NULL );

        if( pPacketInfo->type == MQTT_PACKET_TYPE_PUBLISH )
        {
            pPublishInfo = malloc( sizeof( MQTTPublishInfo_t ) );
            __CPROVER_assume( pPublishInfo != NULL );

            __CPROVER_assume( topicNameLength < maxTopicNameLength );
            pPublishInfo->pTopicName = malloc( topicNameLength );
            __CPROVER_assume( pPublishInfo->pTopicName != NULL );
            pPublishInfo->topicNameLength = topicNameLength;

            __CPROVER_assume( payloadLength < maxPayloadLength );
            pPublishInfo->pPayload = malloc( payloadLength );
            pPublishInfo->payloadLength = ( pPublishInfo->pPayload != NULL ) ? payloadLength : 0;

            pDeserializedInfo->pPublishInfo = pPublishInfo;
        }

        __CPROVER_assume( pDeserializedInfo->packetIdentifier > MQTT_PACKET_ID_INVALID );

        /* Invoke event callback. */
        pContext->appCallback( pContext,
                               pPacketInfo,
                               pDeserializedInfo );

        terminate = true;
    }

    __CPROVER_assume( ( status >= MQTTSuccess && status <= MQTTKeepAliveTimeout ) );

    return status;
}

MQTTStatus_t MQTT_Publish( MQTTContext_t * pContext,
                           const MQTTPublishInfo_t * pPublishInfo,
                           uint16_t packetId )
{
    MQTTStatus_t status;

    __CPROVER_assert( pContext != NULL,
                      "MQTT Context is not NULL." );
    __CPROVER_assert( pPublishInfo != NULL,
                      "Publish Info is not NULL." );
    __CPROVER_assume( ( status >= MQTTSuccess && status <= MQTTKeepAliveTimeout ) );

    return status;
}

uint16_t MQTT_PublishToResend( const MQTTContext_t * pMqttContext,
                               MQTTStateCursor_t * pCursor )
{
    uint16_t packetId;
    static bool terminate = false;

    __CPROVER_assert( pMqttContext != NULL,
                      "MQTT Context is not NULL." );
    __CPROVER_assert( pCursor != NULL,
                      "MQTT State Cursor is not NULL." );

    if( terminate == true )
    {
        packetId = MQTT_PACKET_ID_INVALID;
    }
    else
    {
        #ifdef MAX_PACKET_ID
            /* Limit the packet Ids so that the range of packet ids so that the
             * probability of finding a matching packet in the pending acks is high. */
            __CPROVER_assume( packetId < MAX_PACKET_ID );
        #endif
    }

    terminate = true;

    return packetId;
}