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

/*
 * This file contains the code necessary to make a mutally authenticated connection,
 * using MQTT, with credentials managed by PKCS #11.
 */

/* Standard includes. */
#include <string.h>
#include <stdio.h>

/* Kernel includes. */
#include "FreeRTOS.h"
#include "task.h"

/* FreeRTOS+TCP includes. */
#include "FreeRTOS_IP.h"

/* IoT SDK includes. */
#include "iot_mqtt.h"
#include "platform/iot_network_freertos.h"

/* Select MQTT profile */
#include "aws_iot_demo_profile.h"

/* Preprocessor check for configuration */
#include "aws_iot_setup_check.h"


/*
 * Set connection profile based on the setting in demo_config.h. For more
 * information on each variable, please refer to the respective *_profile.h
 * file in FreeRTOS-Plus\Demo\FreeRTOS-IoT-Libraries-LTS-Beta1\include.
 *
 * Note that if you are running mqtt_tls_mutual_auth demo please make sure to
 * visit the following link for setup:
 * https://www.freertos.org/mqtt/preconfiguredexamplesMA.html
 */
#define mqttexampleBROKER_ENDPOINT           awsiotdemoprofileAWS_ENDPOINT
#define mqttexampleBROKER_PORT               awsiotdemoprofileAWS_MQTT_PORT
#define mqttexampleBROKER_CERTIFICATE_PEM    awsiotdemoprofileAWS_CERTIFICATE_PEM
#define mqttexampleBROKER_CERTIFICATE_PEM_SIZE \
    sizeof( awsiotdemoprofileAWS_CERTIFICATE_PEM )
#define mqttexampleCLIENT_IDENTIFIER         awsiotdemoprofileCLIENT_IDENTIFIER

/**
 * @brief The keep-alive interval used for this example.
 *
 * An MQTT ping request will be sent periodically at this interval.
 */
#define mqttexampleKEEP_ALIVE_SECONDS        ( 60 )

/**
 * @brief The timeout for MQTT operations in this example.
 */
#define mqttexampleMQTT_TIMEOUT_MS           ( 5000 )

/**
 * @brief The topic to subscribe and publish to in the example.
 *
 * The topic starts with the client identifier to ensure that each demo interacts
 * with a unique topic.
 */
#define mqttexampleTOPIC                     mqttexampleCLIENT_IDENTIFIER "/example/topic"

/**
 * @brief The MQTT message published in this example.
 */
#define mqttexampleMESSAGE                   "Hello World!"

/**
 * @brief Parameters to control the retry behaviour in case a QoS1 publish
 * message gets lost.
 *
 * Retry every minutes up to a maximum of 5 retries.
 */
#define mqttexamplePUBLISH_RETRY_MS          ( 1000 )
#define mqttexamplePUBLISH_RETRY_LIMIT       ( 5 )

/**
 * @brief The bit which is set in the demo task's notification value from the
 * disconnect callback to inform the demo task about the MQTT disconnect.
 */
#define mqttexampleDISCONNECTED_BIT          ( 1UL << 0UL )

/**
 * @brief The bit which is set in the demo task's notification value from the
 * publish callback to inform the demo task about the message received from the
 * MQTT broker.
 */
#define mqttexampleMESSAGE_RECEIVED_BIT      ( 1UL << 1UL )

/*-----------------------------------------------------------*/

/**
 * @brief The task used to demonstrate the MQTT API.
 *
 * @param[in] pvParameters Parameters as passed at the time of task creation. Not
 * used in this example.
 */
static void prvMQTTDemoTask( void * pvParameters );

/**
 * @brief The callback invoked by the MQTT library when the MQTT connection gets
 * disconnected.
 *
 * @param[in] pvCallbackContext Callback context as provided at the time of
 * connect.
 * @param[in] pxCallbackParams Contains the reason why the MQTT connection was
 * disconnected.
 */
static void prvExample_OnDisconnect( void * pvCallbackContext,
                                     IotMqttCallbackParam_t * pxCallbackParams );

/**
 * @brief The callback invoked by the MQTT library when a message is received on
 * a subscribed topic from the MQTT broker.
 *
 * @param[in] pvCallbackContext Callback context as provided at the time of
 * subscribe.
 * @param[in] pxCallbackParams Contain the details about the received message -
 * topic on which the message was received, the received message.
 */
static void prvExample_OnMessageReceived( void * pvCallbackContext,
                                          IotMqttCallbackParam_t * pxCallbackParams );

/**
 * @brief Connects to the MQTT broker as specified in mqttexampleBROKER_ENDPOINT
 * and mqttexampleBROKER_PORT.
 */
static void prvMQTTConnect( void );

/**
 * @brief Subscribes to the topic as specified in mqttexampleTOPIC.
 */
static void prvMQTTSubscribe( void );

/**
 * @brief Publishes a messages mqttexampleMESSAGE on mqttexampleTOPIC topic.
 */
static void prvMQTTPublish( void );

/**
 * @brief Unsubscribes from the mqttexampleTOPIC topic.
 */
static void prvMQTTUnsubscribe( void );

/**
 * @brief Disconnects from the MQTT broker gracefully by sending an MQTT
 * DISCONNECT message.
 */
static void prvMQTTDisconnect( void );

/**
 * @brief Initialises the IoT libraries used by this demo.
 */
static void prvInitialiseLibraries( void );

/**
 * @brief The MQTT connection handle used in this example.
 */
static IotMqttConnection_t xMQTTConnection = NULL;

/*-----------------------------------------------------------*/

static const struct IotNetworkServerInfo xMQTTBrokerInfo =
{
    .pHostName = mqttexampleBROKER_ENDPOINT,
    .port      = mqttexampleBROKER_PORT
};

static struct IotNetworkCredentials xNetworkSecurityCredentials =
{
    /* Optional TLS extensions. For this demo, they are disabled. */
    .pAlpnProtos       = NULL,
    .maxFragmentLength = 0,

    /* SNI is enabled by default. */
    .disableSni        = false,

    /* Provide the certificate for validating the server. Only required for
     * demos using TLS. */
    .pRootCa           = mqttexampleBROKER_CERTIFICATE_PEM,
    .rootCaSize        = mqttexampleBROKER_CERTIFICATE_PEM_SIZE,
};

static IotMqttNetworkInfo_t xNetworkInfo =
{
    /* No connection to the MQTT broker has been established yet and we want to
     * establish a new connection. */
    .createNetworkConnection        = true,
    .u.setup.pNetworkServerInfo     = &( xMQTTBrokerInfo ),

    /* Set the TLS credentials for the new MQTT connection. */
    .u.setup.pNetworkCredentialInfo = &xNetworkSecurityCredentials,
    /* Use FreeRTOS+TCP network interface. */
    .pNetworkInterface              = IOT_NETWORK_INTERFACE_FREERTOS,

    /* Setup the callback which is called when the MQTT connection is
     * disconnected. The task handle is passed as the callback context which
     * is used by the callback to send a task notification to this task.*/
    .disconnectCallback.function    = prvExample_OnDisconnect
};

static const IotMqttConnectInfo_t xConnectInfo =
{
    /* Set this flag to true if connecting to the AWS IoT MQTT broker. */
    .awsIotMqttMode            = true,

    /* Start with a clean session i.e. direct the MQTT broker to discard any
     * previous session data. Also, establishing a connection with clean session
     * will ensure that the broker does not store any data when this client
     * gets disconnected. */
    .cleanSession              = true,

    /* Since we are starting with a clean session, there are no previous
     * subscriptions to be restored. */
    .pPreviousSubscriptions    = NULL,
    .previousSubscriptionCount = 0,

    /* We do not want to publish Last Will and Testament (LWT) message if the
     * client gets disconnected. */
    .pWillInfo                 = NULL,

    /* Send an MQTT PING request every minute to keep the connection open if
     * there is no other MQTT traffic. */
    .keepAliveSeconds          = mqttexampleKEEP_ALIVE_SECONDS,

    /* The client identifier is used to uniquely identify this MQTT client to
     * the MQTT broker.  In a production device the identifier can be something
     * unique, such as a device serial number. */
    .pClientIdentifier         = mqttexampleCLIENT_IDENTIFIER,
    .clientIdentifierLength    = ( uint16_t ) sizeof( mqttexampleCLIENT_IDENTIFIER ) - 1,

    /* This example does not authenticate the client and therefore username and
     * password fields are not used. */
    .pUserName                 = NULL,
    .userNameLength            = 0,
    .pPassword                 = NULL,
    .passwordLength            = 0
};
/*-----------------------------------------------------------*/


void vStartPKCSMutualAuthDemo( void )
{
    TickType_t xShortDelay = ( TickType_t ) pdMS_TO_TICKS( ( TickType_t ) 500 );

    /* Wait a short time to allow receipt of the ARP replies. */
    vTaskDelay( xShortDelay );

    /* This example uses a single application task, which in turn is used to
     * connect, subscribe, publish, unsubscribe and disconnect from the MQTT
     * broker. */
    xTaskCreate( prvMQTTDemoTask,          /* Function that implements the task. */
                 "MQTTDemo",               /* Text name for the task - only used for debugging. */
                 configMINIMAL_STACK_SIZE, /* Size of stack (in words, not bytes) to allocate for the task. */
                 NULL,                     /* Task parameter - not used in this case. */
                 tskIDLE_PRIORITY,         /* Task priority, must be between 0 and configMAX_PRIORITIES - 1. */
                 NULL );                   /* Used to pass out a handle to the created task - not used in this case. */
}
/*-----------------------------------------------------------*/

static void prvMQTTDemoTask( void * pvParameters )
{
    uint32_t ulNotificationValue = 0, ulPublishCount;
    const uint32_t ulMaxPublishCount = 5UL;
    const TickType_t xNoDelay = ( TickType_t ) 0;

    /* Remove compiler warnings about unused parameters. */
    ( void ) pvParameters;

    /* One time initialisation of the libraries used by this demo. */
    prvInitialiseLibraries();

    for( ; ; )
    {
        /* Notifications are used to send events from the callback functions to this
         * task.  Don't expect any notifications to be pending at the beginning of the
         * loop. */
        configASSERT( ulTaskNotifyTake( pdTRUE, xNoDelay ) == 0 );

        /****************************** Connect. ******************************/

        /* Establish a connection to the MQTT broker. This example connects to
         * the MQTT broker as specified by the compile time constants
         * mqttexampleBROKER_ENDPOINT and mqttexampleBROKER_PORT.
         * Please change it to the MQTT broker you want to connect to. */
        configPRINTF( ( "Attempt to connect to %s:%d\r\n", mqttexampleBROKER_ENDPOINT, mqttexampleBROKER_PORT ) );
        prvMQTTConnect();
        configPRINTF( ( "Connected to %s:%d\r\n", mqttexampleBROKER_ENDPOINT, mqttexampleBROKER_PORT ) );


        /**************************** Subscribe. ******************************/

        /* The client is now connected to the broker. Subscribe to the topic
         * as specified by the mqttexampleTOPIC compile time constant.  This
         * client will then publish to the same topic it subscribed to, so will
         * expect all the messages it sends to the broker to be sent back to it
         * from the broker. */
        configPRINTF( ( "Attempt to subscribed to the topic %s\r\n", mqttexampleTOPIC ) );
        prvMQTTSubscribe();
        configPRINTF( ( "Subscribed to the topic %s\r\n", mqttexampleTOPIC ) );


        /*********************** Publish ulMaxPublishCount messages. **********/

        /* Publish a few messages while connected. */
        for( ulPublishCount = 0; ulPublishCount < ulMaxPublishCount; ulPublishCount++ )
        {
            /* Publish a message on the topic specified by the mqttexampleTOPIC
             * compile time constant. */
            configPRINTF( ( "Publish %s on the topic %s\r\n", mqttexampleMESSAGE, mqttexampleTOPIC ) );
            prvMQTTPublish();
            configPRINTF( ( "Published %s on the topic %s\r\n", mqttexampleMESSAGE, mqttexampleTOPIC ) );

            /* Since we are subscribed to the same topic as we published on, we
             * will get the same message back from the MQTT broker. Wait for the
             * message to be received which is signaled to us by the publish
             * callback (prvExample_OnMessageReceived) setting the
             * mqttexampleMESSAGE_RECEIVED_BIT bit in this task's notification
             * value. Note the bit is then cleared in the task's notification value
             * to ensure the bit being set can be detected on the next iteration. */
            xTaskNotifyWait( 0UL,                             /* Don't clear any bits on entry. */
                             mqttexampleMESSAGE_RECEIVED_BIT, /* Clear bit on exit. */
                             &( ulNotificationValue ),        /* Obtain the notification value. */
                             pdMS_TO_TICKS( mqttexampleMQTT_TIMEOUT_MS ) );
            configASSERT( ( ulNotificationValue & mqttexampleMESSAGE_RECEIVED_BIT ) == mqttexampleMESSAGE_RECEIVED_BIT );
        }

        /******************* Unsubscribe and Disconnect. **********************/

        /* Unsubscribe from the topic mqttexampleTOPIC and disconnect
         * gracefully. */
        prvMQTTUnsubscribe();
        prvMQTTDisconnect();
        configPRINTF( ( "Disconnected from %s:%d\r\n\r\n", mqttexampleBROKER_ENDPOINT, mqttexampleBROKER_PORT ) );

        /* Wait for the disconnect operation to complete which is signaled to us
         * by the disconnect callback (prvExample_OnDisconnect)by setting
         * the mqttexampleDISCONNECTED_BIT bit in this task's notification value.
         * Note the bit is cleared in the task's notification value again to ensure
         * it being set can be detected again on the next iteration. */
        xTaskNotifyWait( 0UL,                         /* Don't clear any bits on entry. */
                         mqttexampleDISCONNECTED_BIT, /* Clear bit on exit. */
                         &( ulNotificationValue ),    /* Obtain the notification value. */
                         pdMS_TO_TICKS( mqttexampleMQTT_TIMEOUT_MS ) );
        configASSERT( ( ulNotificationValue & mqttexampleDISCONNECTED_BIT ) == mqttexampleDISCONNECTED_BIT );

        /* Delay between iterations to avoid broker throttling. */
        configPRINTF( ( "prvMQTTDemoTask() completed an iteration successfully. Total free heap is %u\r\n", xPortGetFreeHeapSize() ) );
        configPRINTF( ( "Demo completed successfully.\r\n" ) );
        configPRINTF( ( "Short delay before starting the next iteration.... \r\n\r\n" ) );

        vTaskDelay( pdMS_TO_TICKS( mqttexampleMQTT_TIMEOUT_MS ) );
    }
}
/*-----------------------------------------------------------*/

static void prvExample_OnDisconnect( void * pvCallbackContext,
                                     IotMqttCallbackParam_t * pxCallbackParams )
{
    TaskHandle_t xDemoTaskHandle = ( TaskHandle_t ) pvCallbackContext;

    /* Ensure that we initiated the disconnect. */
    configASSERT( pxCallbackParams->u.disconnectReason == IOT_MQTT_DISCONNECT_CALLED );

    /* Inform the demo task about the disconnect. */
    xTaskNotify( xDemoTaskHandle,
                 mqttexampleDISCONNECTED_BIT,
                 eSetBits /* Set the mqttexampleDISCONNECTED_BIT in the demo task's notification value. */
                 );
}
/*-----------------------------------------------------------*/

static void prvExample_OnMessageReceived( void * pvCallbackContext,
                                          IotMqttCallbackParam_t * pxCallbackParams )
{
    TaskHandle_t xDemoTaskHandle = ( TaskHandle_t ) pvCallbackContext;

    /* Ensure the message is received on the expected topic. */
    configASSERT( pxCallbackParams->u.message.info.topicNameLength == strlen( mqttexampleTOPIC ) );
    configASSERT( strncmp( pxCallbackParams->u.message.info.pTopicName,
                           mqttexampleTOPIC,
                           strlen( mqttexampleTOPIC ) ) == 0 );

    /* Ensure the message itself is as expected. */
    configASSERT( pxCallbackParams->u.message.info.payloadLength == strlen( mqttexampleMESSAGE ) );
    configASSERT( strncmp( pxCallbackParams->u.message.info.pPayload,
                           mqttexampleMESSAGE,
                           strlen( mqttexampleMESSAGE ) ) == 0 );

    /* Ensure the message Quality of Service (QoS) is as expected. */
    configASSERT( pxCallbackParams->u.message.info.qos == IOT_MQTT_QOS_1 );

    /* So as not to worry about string lengths the print message uses the
     * consts rather than the data from the message, but the asserts above have
     * already checked the two are equal. */
    configPRINTF( ( "Received %s on the topic %s\r\n", mqttexampleMESSAGE, mqttexampleTOPIC ) );

    /* Inform the demo task about the message received from the MQTT broker by
     * setting the mqttexampleMESSAGE_RECEIVED_BIT bit in the task's notification
     * value. */
    xTaskNotify( xDemoTaskHandle,
                 mqttexampleMESSAGE_RECEIVED_BIT,
                 eSetBits /* Set the mqttexampleMESSAGE_RECEIVED_BIT in the demo task's notification value. */
                 );
}
/*-----------------------------------------------------------*/

static void prvMQTTConnect( void )
{
    IotMqttError_t xResult;

    /* Set the context to pass into the disconnect callback function. */
    xNetworkInfo.disconnectCallback.pCallbackContext = ( void * ) xTaskGetCurrentTaskHandle();

    /* Establish the connection to the MQTT broker - It is a blocking call and
     * will return only when connection is complete or a timeout occurs.  The
     * network and connection structures are declared and initialised at the top
     * of this file. */
    xResult = IotMqtt_Connect( &( xNetworkInfo ),
                               &( xConnectInfo ),
                               mqttexampleMQTT_TIMEOUT_MS,
                               &( xMQTTConnection ) );
    configASSERT( xResult == IOT_MQTT_SUCCESS );
}
/*-----------------------------------------------------------*/

static void prvMQTTDisconnect( void )
{
    /* Send a MQTT DISCONNECT packet to the MQTT broker to do a graceful
     * disconnect. */
    IotMqtt_Disconnect( xMQTTConnection,
                        0 /* flags - 0 means a graceful disconnect by sending MQTT DISCONNECT. */
                        );
}
/*-----------------------------------------------------------*/

static void prvMQTTSubscribe( void )
{
    IotMqttError_t xResult;
    IotMqttSubscription_t xMQTTSubscription;

    /* Subscribe to the mqttexampleTOPIC topic filter. The task handle is passed
     * as the callback context, which is then used by the callback to send a task
     * notification to this task.*/
    xMQTTSubscription.qos = IOT_MQTT_QOS_1;
    xMQTTSubscription.pTopicFilter = mqttexampleTOPIC;
    xMQTTSubscription.topicFilterLength = ( uint16_t ) strlen( mqttexampleTOPIC );
    xMQTTSubscription.callback.pCallbackContext = ( void * ) xTaskGetCurrentTaskHandle();
    xMQTTSubscription.callback.function = prvExample_OnMessageReceived;

    /* Use the synchronous API to subscribe - It is a blocking call and only
     * returns when the subscribe operation is complete or a timeout occurs. */
    xResult = IotMqtt_SubscribeSync( xMQTTConnection,
                                     &( xMQTTSubscription ),
                                     1, /* We are subscribing to one topic filter. */
                                     0, /* flags - currently ignored. */
                                     mqttexampleMQTT_TIMEOUT_MS );
    configASSERT( xResult == IOT_MQTT_SUCCESS );
}
/*-----------------------------------------------------------*/

static void prvMQTTPublish( void )
{
    IotMqttError_t xResult;
    IotMqttPublishInfo_t xMQTTPublishInfo;

    /* Publish a message with QoS1 on the mqttexampleTOPIC topic. Since we are
     * subscribed to the same topic, the MQTT broker will send the same message
     * back to us. It is verified in the publish callback. */
    xMQTTPublishInfo.qos = IOT_MQTT_QOS_1;
    xMQTTPublishInfo.retain = false;
    xMQTTPublishInfo.pTopicName = mqttexampleTOPIC;
    xMQTTPublishInfo.topicNameLength = ( uint16_t ) strlen( mqttexampleTOPIC );
    xMQTTPublishInfo.pPayload = mqttexampleMESSAGE;
    xMQTTPublishInfo.payloadLength = strlen( mqttexampleMESSAGE );
    xMQTTPublishInfo.retryMs = mqttexamplePUBLISH_RETRY_MS;
    xMQTTPublishInfo.retryLimit = mqttexamplePUBLISH_RETRY_LIMIT;

    /* Use the synchronous API to publish - It is a blocking call and only
     * returns when the publish operation is complete or a timeout occurs. */
    xResult = IotMqtt_PublishSync( xMQTTConnection,
                                   &( xMQTTPublishInfo ),
                                   0, /* flags - currently ignored. */
                                   mqttexampleMQTT_TIMEOUT_MS );
    configASSERT( xResult == IOT_MQTT_SUCCESS );
}
/*-----------------------------------------------------------*/

static void prvMQTTUnsubscribe( void )
{
    IotMqttError_t xResult;
    IotMqttSubscription_t xMQTTSubscription;

    /* Unsubscribe from the mqttexampleTOPIC topic filter. */
    xMQTTSubscription.pTopicFilter = mqttexampleTOPIC;
    xMQTTSubscription.topicFilterLength = ( uint16_t ) strlen( mqttexampleTOPIC );

    /* The following members of the IotMqttSubscription_t are ignored by the
     * unsubscribe operation. Just initialize them to avoid "use of uninitialized
     * variable" warnings. */
    xMQTTSubscription.qos = IOT_MQTT_QOS_1;
    xMQTTSubscription.callback.pCallbackContext = NULL;
    xMQTTSubscription.callback.function = NULL;

    /* Use the synchronous API to unsubscribe - It is a blocking call and only
     * returns when the unsubscribe operation is complete or a timeout occurs. */
    xResult = IotMqtt_UnsubscribeSync( xMQTTConnection,
                                       &( xMQTTSubscription ),
                                       1, /* We are unsubscribing from one topic filter. */
                                       0, /* flags - currently ignored. */
                                       mqttexampleMQTT_TIMEOUT_MS );
    configASSERT( xResult == IOT_MQTT_SUCCESS );
}
/*-----------------------------------------------------------*/

static void prvInitialiseLibraries( void )
{
    IotMqttError_t xResult;
    IotNetworkError_t xNetworkResult; /*_RB_ Are three types necessary, one for each lib? */

    /* Initialize the network stack abstraction for FreeRTOS. */
    xNetworkResult = IotNetworkFreeRTOS_Init();
    configASSERT( xNetworkResult == IOT_NETWORK_SUCCESS );

    /* MQTT library must be initialized before it can be used. This is just one
     * time initialization. */
    xResult = IotMqtt_Init();
    configASSERT( xResult == IOT_MQTT_SUCCESS );
}
/*-----------------------------------------------------------*/
