/*
 * 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 common functions for plain text, basic TLS, and mutual
 * authentication HTTPS demos. Aside from the difference in security level during
 * connect, the three demos perform the same interaction with a HTTPS host. The
 * demo creates a singe application task that loops through a set of examples
 * demonstrating how to the connect to the server, send a few types of requests to
 * the server (GET, HEAD, PUT, POST), and disconnect from the server again. All
 * of the responses and their associated HTTPS response status codes are printed
 * to the console.
 *
 * The plain text HTTP demo does not authenticate the server nor the client. The
 * basic TLS HTTPS demo builds on top of the plain text demo, adding broker
 * authentication and encryption. The mutual authentication HTTPS demo builds on
 * top of the basic TLS demo, enabling both server and client authentication.
 *
 * For more information regarding the HTTPS library and the demo, please refer to:
 * https://www.freertos.org/https/index.html
 */

/* 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_https_client.h"
#include "platform/iot_network_freertos.h"

/* HTTPS Demo Select */
#include "demo_config.h"

/* Select HTTPS profile based on the setting in demo_config.h */
#if ( democonfigPROFILE_USE_AWS_IOT == 1 )
    #include "aws_iot_demo_profile.h"
#else
    #include "https_demo_profile.h"
#endif

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

/*
 * Set the connection profile based on settings in demo_config.h. For more
 * information on each variable, please refer to the respective *_profile.h
 * file in FreeRTOS-Labs\Demo\FreeRTOS-IoT-Libraries-LTS-Beta1\include.
 *
 * Note that if you are running the https_tls_mutual_auth demo please make sure
 * to visit the following link for setup:
 * https://www.freertos.org/https/preconfiguredexamplesMA.html
 */
#if defined( AWS_IOT_DEMO_PROFILE_H )
    #define httpsexampleHTTPS_SERVER_ADDRESS        awsiotdemoprofileAWS_ENDPOINT
    #define httpsexampleHTTPS_SERVER_PORT           awsiotdemoprofileAWS_HTTPS_PORT
    #define httpsexampleHTTPS_SERVER_CERTIFICATE    awsiotdemoprofileAWS_CERTIFICATE_PEM
    #define httpsexampleCLIENT_CERTIFICATE_PEM      awsiotdemoprofileCLIENT_CERTIFICATE_PEM
    #define httpsexampleCLIENT_PRIVATE_KEY_PEM      awsiotdemoprofileCLIENT_PRIVATE_KEY_PEM
#elif defined( HTTPS_DEMO_PROFILE_H )
    #define httpsexampleHTTPS_SERVER_ADDRESS        httpsdemoprofileSERVER_ADDRESS
    #define httpsexampleHTTPS_SERVER_PORT           httpsdemoprofileSERVER_PORT
    #define httpsexampleHTTPS_SERVER_CERTIFICATE    httpsdemoprofileSERVER_CERTIFICATE_PEM
#endif

/*
 * Each preconfigured host supports different request paths. In this demo, we
 * are using httpbin and AWS IoT.
 *
 * In the demos that uses httpbin.org see http://httpbin.org/#/HTTP_Methods for
 * details on supported REST API.
 **/
#if defined( AWS_IOT_DEMO_PROFILE_H )
    #define httpsexampleHTTPS_POST_PATH    "/topics/" awsiotdemoprofileCLIENT_IDENTIFIER
#elif defined( HTTPS_DEMO_PROFILE_H )
    #define httpsexampleHTTPS_GET_PATH     "/ip"
    #define httpsexampleHTTPS_HEAD_PATH    "/ip"
    #define httpsexampleHTTPS_PUT_PATH     "/put"
    #define httpsexampleHTTPS_POST_PATH    "/post"
#endif

/**
 * @brief The length in bytes of the user buffer to store the HTTPS Client library
 * connection context.
 *
 * The minimum size can be found in @ref connectionUserBufferMinimumSize.
 */
#define httpsexampleCONNECTION_USER_BUFFER_LENGTH    ( 512 )

/**
 * @brief The length in bytes of the user buffer to store the HTTPS Client library
 * request context.
 *
 * The minimum size can be found in @ref requestUserBufferMinimumSize.
 */
#define httpsexampleREQUEST_USER_BUFFER_LENGTH       ( 512 )

/**
 * @brief The length in bytes of the user buffer to store the HTTPS Client library
 * response context.
 */
#define httpsexampleRESPONSE_USER_BUFFER_LENGTH      ( 512 )

/**
 * @brief Some text to send as the request body for a PUT and POST request in
 * this examples.
 */
#define httpsexampleREQUEST_BODY_TEXT                "Hello, world!"

/**
 * @brief The length in bytes of the buffer used to receive the response body.
 */
#define httpsexampleRESPONSE_BODY_BUFFER_LENGTH      ( 512 )

/**
 * @brief Timeout in ms for HTTPS operations in this example.
 */
#define httpsexampleHTTPS_TIMEOUT_MS                 ( 5000 )

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

/**
 * @brief The HTTPS connection handle used in this example.
 */
static IotHttpsConnectionHandle_t xHTTPSConnection = IOT_HTTPS_CONNECTION_HANDLE_INITIALIZER;

/**
 * @brief A buffer used to store the HTTPS Client library's connection context.
 */
static uint8_t ucHTTPSConnectionUserBuffer[ httpsexampleCONNECTION_USER_BUFFER_LENGTH ] = { 0 };

/**
 * @brief A buffer used to store the HTTPS Client library's request context and
 * some headers.
 */
static uint8_t ucHTTPSRequestUserBuffer[ httpsexampleREQUEST_USER_BUFFER_LENGTH ] = { 0 };

/**
 * @brief A buffer used to store the HTTPS Client library's response context and
 * some headers.
 */
static uint8_t ucHTTPSResponseUserBuffer[ httpsexampleRESPONSE_USER_BUFFER_LENGTH ] = { 0 };

/**
 * @brief The request body.
 */
static char cHTTPSRequestBodyBuffer[] = httpsexampleREQUEST_BODY_TEXT;

/**
 * @brief A buffer used to receive the HTTPS Client library's response body.
 */
static uint8_t ucHTTPSResponseBodyBuffer[ httpsexampleRESPONSE_BODY_BUFFER_LENGTH ] = { 0 };

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

/**
 * @brief Connects to the HTTPS server as specified in httpsexampleHTTPS_SERVER_ADDRESS
 * and httpsexampleHTTPS_SERVER_PORT.
 */
static void prvHTTPSConnect( void );

/**
 * @brief Creates and sends an HTTPS request to the HTTPS server specified in
 * httpsexampleHTTPS_SERVER_ADDRESS and httpsexampleHTTPS_SERVER_PORT.
 *
 * @param[in] xMethod The HTTPS method to use. Please #IotHttpsMethod_t for
 *            possible parameters.
 * @param[in] pcPath The path for the HTTPS request.
 * @param[in] ulPathLength The length of the input pcPath.
 */
static void prvHTTPSRequest( IotHttpsMethod_t xMethod,
                             const char * pcPath,
                             uint32_t ulPathLength );

/**
 * @brief Disconnects from the HTTPS server.
 */
static void prvHTTPSDisconnect( void );

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

static const IotHttpsConnectionInfo_t xConnectionInfo =
{
    /* No connection to the HTTPS server has been established yet and we want to
     * establish a new connection. */
    .pAddress             = httpsexampleHTTPS_SERVER_ADDRESS,
    .addressLen           = sizeof( httpsexampleHTTPS_SERVER_ADDRESS ) - 1,
    .port                 = httpsexampleHTTPS_SERVER_PORT,
    .userBuffer.pBuffer   = ucHTTPSConnectionUserBuffer,
    .userBuffer.bufferLen = sizeof( ucHTTPSConnectionUserBuffer ),

    /* Use FreeRTOS+TCP network. */
    .pNetworkInterface    = IOT_NETWORK_INTERFACE_FREERTOS,

    /* The HTTPS Client library uses TLS by default as indicated by the "S"
    * postfixed to "HTTP" in the name of the library and its types and
    * functions. There are no configurations in the flags to enable TLS. */
    .flags                = 0,

    /* Optional TLS extensions. For this demo, they are disabled. */
    .pAlpnProtocols       = NULL,
    .alpnProtocolsLen     = 0,

    /* Provide the certificate for authenticating the server. */
    #if ( democonfigENABLE_TLS == 1 )
        .pCaCert          = httpsexampleHTTPS_SERVER_CERTIFICATE,
        .caCertLen        = sizeof( httpsexampleHTTPS_SERVER_CERTIFICATE ),
    #else
        .pCaCert          = NULL,
        .caCertLen        = 0,
    #endif

    /* The HTTPS server at httpbin.org:443 does not require client certificates,
     * but AWS IoT does.
     * If the server were to require a client certificate, the following members
     * need to be set. */
    #if ( democonfigENABLE_MUTUAL_AUTH == 1 )
        .pClientCert      = httpsexampleCLIENT_CERTIFICATE_PEM,
        .clientCertLen    = sizeof( httpsexampleCLIENT_CERTIFICATE_PEM ),
        .pPrivateKey      = httpsexampleCLIENT_PRIVATE_KEY_PEM,
        .privateKeyLen    = sizeof( httpsexampleCLIENT_PRIVATE_KEY_PEM )
    #else
           .pClientCert   = NULL,
        .clientCertLen    = 0,
        .pPrivateKey      = NULL,
        .privateKeyLen    = 0
                            #endif
};
/*-----------------------------------------------------------*/

void vStartSimpleHTTPSDemo( void )
{
    /* This example uses a single application task, which in turn is used to
    * connect, send a few requests, and disconnect from the HTTPS server. */
    xTaskCreate( prvHTTPSDemoTask,         /* Function that implements the task. */
                 "HTTPSDemo",              /* Text name for the task - only used for debugging. */
                 democonfigDEMO_STACKSIZE, /* 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 prvHTTPSDemoTask( void * pvParameters )
{
    /* Remove compiler warnings about unused parameters. */
    ( void ) pvParameters;

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

    for( ; ; )
    {
        /****************************** Connect. ******************************/

        /* Establish a connection to the HTTPS server. This example connects to
         * the HTTPS server as specified in httpsexampleHTTPS_SERVER_ADDRESS and
         * httpsexampleHTTPS_SERVER_PORT at the top of this file. Please change
         * it to the HTTPS server you want to connect to. */
        configPRINTF( ( "Attempt to connect to %s:%d\r\n", httpsexampleHTTPS_SERVER_ADDRESS, httpsexampleHTTPS_SERVER_PORT ) );
        prvHTTPSConnect();
        configPRINTF( ( "Connected to %s:%d\r\n", httpsexampleHTTPS_SERVER_ADDRESS, httpsexampleHTTPS_SERVER_PORT ) );

        /*********************** Send HTTPS requests. ************************/

        /* The client is now connected to the server. This example will send a
         * GET, HEAD, PUT, and POST request. For AWS IoT profile, the example will
         * only send a POST request.
         **/
        #if defined( httpsexampleHTTPS_GET_PATH )
            configPRINTF( ( "Sending HTTPS GET request...\r\n" ) );
            prvHTTPSRequest( IOT_HTTPS_METHOD_GET,
                             httpsexampleHTTPS_GET_PATH,
                             sizeof( httpsexampleHTTPS_GET_PATH ) - 1 );
        #endif
        #if defined( httpsexampleHTTPS_HEAD_PATH )
            configPRINTF( ( "Sending HTTPS HEAD request...\r\n" ) );
            prvHTTPSRequest( IOT_HTTPS_METHOD_HEAD,
                             httpsexampleHTTPS_HEAD_PATH,
                             sizeof( httpsexampleHTTPS_HEAD_PATH ) - 1 );
        #endif
        #if defined( httpsexampleHTTPS_PUT_PATH )
            configPRINTF( ( "Sending HTTPS PUT request...\r\n" ) );
            prvHTTPSRequest( IOT_HTTPS_METHOD_PUT,
                             httpsexampleHTTPS_PUT_PATH,
                             sizeof( httpsexampleHTTPS_PUT_PATH ) - 1 );
        #endif
        #if defined( httpsexampleHTTPS_POST_PATH )
            configPRINTF( ( "Sending HTTPS POST request...\r\n" ) );
            prvHTTPSRequest( IOT_HTTPS_METHOD_POST,
                             httpsexampleHTTPS_POST_PATH,
                             sizeof( httpsexampleHTTPS_POST_PATH ) - 1 );
        #endif

        /**************************** Disconnect. *****************************/

        prvHTTPSDisconnect();
        configPRINTF( ( "Disconnected from %s:%d\r\n\r\n", httpsexampleHTTPS_SERVER_ADDRESS, httpsexampleHTTPS_SERVER_PORT ) );

        /* Wait between iterations to avoid server throttling. */

        configPRINTF( ( "prvHTTPSDemoTask() 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( httpsexampleHTTPS_TIMEOUT_MS ) );
    }
}

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

static void prvHTTPSConnect( void )
{
    IotHttpsReturnCode_t xHTTPSClientResult;

    /* Establish the connection to the HTTPS server - It is a blocking call and
     * will return only when the connection is complete or a timeout occurs. */
    xHTTPSClientResult = IotHttpsClient_Connect( &( xHTTPSConnection ),
                                                 &( xConnectionInfo ) );
    configASSERT( xHTTPSClientResult == IOT_HTTPS_OK );
}

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

static void prvHTTPSRequest( IotHttpsMethod_t xMethod,
                             const char * pcPath,
                             uint32_t ulPathLength )
{
    IotHttpsReturnCode_t xHTTPSClientResult;
    IotHttpsRequestInfo_t xHTTPSRequestInfo = IOT_HTTPS_REQUEST_INFO_INITIALIZER;
    IotHttpsResponseInfo_t xHTTPSResponseInfo = IOT_HTTPS_RESPONSE_INFO_INITIALIZER;
    IotHttpsRequestHandle_t xHTTPSRequest = IOT_HTTPS_REQUEST_HANDLE_INITIALIZER;
    IotHttpsResponseHandle_t xHTTPSResponse = IOT_HTTPS_RESPONSE_HANDLE_INITIALIZER;
    IotHttpsSyncInfo_t xHTTPSSyncRequestInfo = IOT_HTTPS_SYNC_INFO_INITIALIZER;
    IotHttpsSyncInfo_t xHTTPSSyncResponseInfo = IOT_HTTPS_SYNC_INFO_INITIALIZER;
    uint16_t usResponseStatus = 0;

    configASSERT( pcPath != NULL );

    /************************** HTTPS request setup. ***************************/

    if( ( xMethod == IOT_HTTPS_METHOD_PUT ) || ( xMethod == IOT_HTTPS_METHOD_POST ) )
    {
        xHTTPSSyncRequestInfo.pBody = ( uint8_t * ) cHTTPSRequestBodyBuffer;
        xHTTPSSyncRequestInfo.bodyLen = sizeof( cHTTPSRequestBodyBuffer );
    }
    else
    {
        xHTTPSSyncRequestInfo.pBody = NULL;
        xHTTPSSyncRequestInfo.bodyLen = 0;
    }

    xHTTPSRequestInfo.pHost = httpsexampleHTTPS_SERVER_ADDRESS;
    xHTTPSRequestInfo.hostLen = sizeof( httpsexampleHTTPS_SERVER_ADDRESS ) - 1;
    xHTTPSRequestInfo.pPath = pcPath;
    xHTTPSRequestInfo.pathLen = ulPathLength;
    xHTTPSRequestInfo.method = xMethod;
    xHTTPSRequestInfo.isNonPersistent = false;
    xHTTPSRequestInfo.userBuffer.pBuffer = ucHTTPSRequestUserBuffer;
    xHTTPSRequestInfo.userBuffer.bufferLen = sizeof( ucHTTPSRequestUserBuffer ) - 1;
    xHTTPSRequestInfo.isAsync = false;
    xHTTPSRequestInfo.u.pSyncInfo = &xHTTPSSyncRequestInfo;

    xHTTPSClientResult = IotHttpsClient_InitializeRequest( &( xHTTPSRequest ),
                                                           &( xHTTPSRequestInfo ) );
    configASSERT( xHTTPSClientResult == IOT_HTTPS_OK );

    /************************** HTTPS response setup. **************************/

    memset( ucHTTPSResponseBodyBuffer, 0, sizeof( ucHTTPSResponseBodyBuffer ) );

    if( xMethod == IOT_HTTPS_METHOD_HEAD )
    {
        xHTTPSSyncResponseInfo.pBody = NULL;
        xHTTPSSyncResponseInfo.bodyLen = 0;
    }
    else
    {
        xHTTPSSyncResponseInfo.pBody = ucHTTPSResponseBodyBuffer;
        xHTTPSSyncResponseInfo.bodyLen = sizeof( ucHTTPSResponseBodyBuffer );
    }

    xHTTPSResponseInfo.userBuffer.pBuffer = ucHTTPSResponseUserBuffer;
    xHTTPSResponseInfo.userBuffer.bufferLen = sizeof( ucHTTPSResponseUserBuffer );
    xHTTPSResponseInfo.pSyncInfo = &xHTTPSSyncResponseInfo;

    /*************************** Send HTTPS request. ***************************/

    /* This synchronous send function blocks until the full response is received
     * from the network. */
    xHTTPSClientResult = IotHttpsClient_SendSync( xHTTPSConnection,
                                                  xHTTPSRequest,
                                                  &( xHTTPSResponse ),
                                                  &( xHTTPSResponseInfo ),
                                                  httpsexampleHTTPS_TIMEOUT_MS );
    configASSERT( xHTTPSClientResult == IOT_HTTPS_OK );

    /* The response status is only available if the httpsexampleRESPONSE_USER_BUFFER
     * is large enough to fit not only the HTTPS Client response context, but
     * also the Status-Line of the response. The Status-Line and the response
     * headers are stored in the provided ucHTTPSResponseUserBuffer right after
     * the HTTPS Client response context. */
    xHTTPSClientResult = IotHttpsClient_ReadResponseStatus( xHTTPSResponse,
                                                            &usResponseStatus );
    configASSERT( xHTTPSClientResult == IOT_HTTPS_OK );

    configPRINTF( ( "Response status: %d\r\n", usResponseStatus ) );
    configPRINTF( ( "Response body: \r\n%s\r\n", ucHTTPSResponseBodyBuffer ) );

    /* The response body may be too large for the print buffer. These extra
     * carriage returns and newlines help with clobbering. */
    configPRINTF( ( "\r\n\r\n" ) );
}

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

static void prvHTTPSDisconnect( void )
{
    IotHttpsReturnCode_t xHTTPSClientResult;

    /* The application must always explicitly disconnect from the server with
     * this API if the last request sent on the connection was a persistent
     * request. */
    xHTTPSClientResult = IotHttpsClient_Disconnect( xHTTPSConnection );
    configASSERT( xHTTPSClientResult == IOT_HTTPS_OK );
}


static void prvInitialiseLibraries( void )
{
    IotHttpsReturnCode_t xHTTPSClientResult;
    IotNetworkError_t xNetworkResult;

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

    /* HTTPS Client library must be initialized before it can be used. This is
     * just one time initialization. */
    xHTTPSClientResult = IotHttpsClient_Init();
    configASSERT( xHTTPSClientResult == IOT_HTTPS_OK );
}
