/*
 * FreeRTOS V202212.00
 * 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.
 *
 * https://www.FreeRTOS.org
 * https://github.com/FreeRTOS
 *
 */


/******************************************************************************
*
* See the following web page for essential TwoEchoClient.c usage and
* configuration details:
* https://www.FreeRTOS.org/FreeRTOS-Plus/FreeRTOS_Plus_UDP/Embedded_Ethernet_Examples/Common_Echo_Clients.shtml
*
******************************************************************************/


/* Standard includes. */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

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

/* FreeRTOS+UDP includes. */
#include "FreeRTOS_UDP_IP.h"
#include "FreeRTOS_Sockets.h"

/* Small delay used between attempts to obtain a zero copy buffer. */
#define echoTINY_DELAY    ( ( TickType_t ) 2 )

/* The echo tasks create a socket, send out a number of echo requests
 * (listening for each echo reply), then close the socket again before
 * starting over.  This delay is used between each iteration to ensure the
 * network does not get too congested. */
#define echoLOOP_DELAY    ( ( TickType_t ) 250 / portTICK_RATE_MS )

#if ipconfigINCLUDE_EXAMPLE_FREERTOS_PLUS_TRACE_CALLS == 1

/* When the trace recorder code is included user events are generated to
 * mark the sending and receiving of the echoed data (only in the zero copy
 * task. */
    #define echoMARK_SEND_IN_TRACE_BUFFER( x )    vTraceUserEvent( x )
    traceLabel xZeroCopySendEvent, xZeroCopyReceiveEvent;

#else

/* When the trace recorder code is not included just #define away the call
 * to post the user event. */
    #define echoMARK_SEND_IN_TRACE_BUFFER( x )
    #define xZeroCopySendEvent       0
    #define xZeroCopyReceiveEvent    0
#endif

/* The echo server is assumed to be on port 7, which is the standard echo
 * protocol port. */
#define echoECHO_PORT    ( 7 )

/*
 * Uses a socket to send data to, then receive data from, the standard echo
 * port number 7.  prvEchoClientTask() uses the standard interface.
 * prvZeroCopyEchoClientTask() uses the zero copy interface.
 */
static void prvEchoClientTask( void * pvParameters );
static void prvZeroCopyEchoClientTask( void * pvParameters );

/* The receive timeout is set shorter when the windows simulator is used
 * because simulated time is slower than real time. */
#ifdef _WINDOWS_
    const TickType_t xReceiveTimeOut = 50 / portTICK_RATE_MS;
#else
    const TickType_t xReceiveTimeOut = 500 / portTICK_RATE_MS;
#endif

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

void vStartEchoClientTasks( uint16_t usTaskStackSize,
                            UBaseType_t uxTaskPriority )
{
    /* Create the echo client task that does not use the zero copy interface. */
    xTaskCreate( prvEchoClientTask, /* The function that implements the task. */
                 "Echo0",           /* Just a text name for the task to aid debugging. */
                 usTaskStackSize,   /* The stack size is defined in FreeRTOSIPConfig.h. */
                 NULL,              /* The task parameter, not used in this case. */
                 uxTaskPriority,    /* The priority assigned to the task is defined in FreeRTOSConfig.h. */
                 NULL );            /* The task handle is not used. */

    /* Create the echo client task that does use the zero copy interface. */
    xTaskCreate( prvZeroCopyEchoClientTask, /* The function that implements the task. */
                 "Echo1",                   /* Just a text name for the task to aid debugging. */
                 usTaskStackSize,           /* The stack size is defined in FreeRTOSIPConfig.h. */
                 NULL,                      /* The task parameter, not used in this case. */
                 uxTaskPriority,            /* The priority assigned to the task is defined in FreeRTOSConfig.h. */
                 NULL );                    /* The task handle is not used. */
}
/*-----------------------------------------------------------*/

static void prvEchoClientTask( void * pvParameters )
{
    xSocket_t xSocket;
    struct freertos_sockaddr xEchoServerAddress;
    char cTxString[ 25 ], cRxString[ 25 ]; /* Make sure the stack is large enough to hold these.  Turn on stack overflow checking during debug to be sure. */
    int32_t lLoopCount = 0UL;
    const int32_t lMaxLoopCount = 50;
    volatile uint32_t ulRxCount = 0UL, ulTxCount = 0UL;
    uint32_t xAddressLength = sizeof( xEchoServerAddress );

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

    /* Echo requests are sent to the echo server.  The address of the echo
     * server is configured by the constants configECHO_SERVER_ADDR0 to
     * configECHO_SERVER_ADDR3 in FreeRTOSConfig.h. */
    xEchoServerAddress.sin_port = FreeRTOS_htons( echoECHO_PORT );

    #if defined( ipconfigIPv4_BACKWARD_COMPATIBLE ) && ( ipconfigIPv4_BACKWARD_COMPATIBLE == 0 )
    {
        xEchoServerAddress.sin_address.ulIP_IPv4 = FreeRTOS_inet_addr_quick( configECHO_SERVER_ADDR0,
                                                                             configECHO_SERVER_ADDR1,
                                                                             configECHO_SERVER_ADDR2,
                                                                             configECHO_SERVER_ADDR3 );
    }
    #else
    {
        xEchoServerAddress.sin_addr = FreeRTOS_inet_addr_quick( configECHO_SERVER_ADDR0,
                                                                configECHO_SERVER_ADDR1,
                                                                configECHO_SERVER_ADDR2,
                                                                configECHO_SERVER_ADDR3 );
    }
    #endif /* defined( ipconfigIPv4_BACKWARD_COMPATIBLE ) && ( ipconfigIPv4_BACKWARD_COMPATIBLE == 0 ) */

    xEchoServerAddress.sin_family = FREERTOS_AF_INET;

    for( ; ; )
    {
        /* Create a socket. */
        xSocket = FreeRTOS_socket( FREERTOS_AF_INET, FREERTOS_SOCK_DGRAM, FREERTOS_IPPROTO_UDP );
        configASSERT( xSocket != FREERTOS_INVALID_SOCKET );

        /* Set a time out so a missing reply does not cause the task to block
         * indefinitely. */
        FreeRTOS_setsockopt( xSocket, 0, FREERTOS_SO_RCVTIMEO, &xReceiveTimeOut, sizeof( xReceiveTimeOut ) );

        /* Send a number of echo requests. */
        for( lLoopCount = 0; lLoopCount < lMaxLoopCount; lLoopCount++ )
        {
            /* Create the string that is sent to the echo server. */
            sprintf( cTxString, "Message number %u\r\n", ( unsigned int ) ulTxCount );

            /* Send the string to the socket.  ulFlags is set to 0, so the zero
             * copy interface is not used.  That means the data from cTxString is
             * copied into a network buffer inside FreeRTOS_sendto(), and cTxString
             * can be reused as soon as FreeRTOS_sendto() has returned.  1 is added
             * to ensure the NULL string terminator is sent as part of the message. */
            FreeRTOS_sendto( xSocket,                 /* The socket being sent to. */
                             ( void * ) cTxString,    /* The data being sent. */
                             strlen( cTxString ) + 1, /* The length of the data being sent. */
                             0,                       /* ulFlags with the FREERTOS_ZERO_COPY bit clear. */
                             &xEchoServerAddress,     /* The destination address. */
                             sizeof( xEchoServerAddress ) );

            /* Keep a count of how many echo requests have been transmitted so
             * it can be compared to the number of echo replies received.  It would
             * be expected to loose at least one to an ARP message the first time
             * the	connection is created. */
            ulTxCount++;

            /* Receive data echoed back to the socket.  ulFlags is zero, so the
             * zero copy option is not being used and the received data will be
             * copied into the buffer pointed to by cRxString.  xAddressLength is
             * not actually used (at the time of writing this comment, anyway) by
             * FreeRTOS_recvfrom(), but is set appropriately in case future
             * versions do use it. */
            memset( ( void * ) cRxString, 0x00, sizeof( cRxString ) );
            FreeRTOS_recvfrom( xSocket,             /* The socket being received from. */
                               cRxString,           /* The buffer into which the received data will be written. */
                               sizeof( cRxString ), /* The size of the buffer provided to receive the data. */
                               0,                   /* ulFlags with the FREERTOS_ZERO_COPY bit clear. */
                               &xEchoServerAddress, /* The address from where the data was sent (the source address). */
                               &xAddressLength );

            /* Compare the transmitted string to the received string. */
            if( strcmp( cRxString, cTxString ) == 0 )
            {
                /* The echo reply was received without error. */
                ulRxCount++;
            }
        }

        /* Pause for a short while to ensure the network is not too
         * congested. */
        vTaskDelay( echoLOOP_DELAY );

        /* Close this socket before looping back to create another. */
        FreeRTOS_closesocket( xSocket );
    }
}
/*-----------------------------------------------------------*/

static void prvZeroCopyEchoClientTask( void * pvParameters )
{
    xSocket_t xSocket;
    struct freertos_sockaddr xEchoServerAddress;
    static char cTxString[ 40 ];
    int32_t lLoopCount = 0UL;
    volatile uint32_t ulRxCount = 0UL, ulTxCount = 0UL;
    uint32_t xAddressLength = sizeof( xEchoServerAddress );
    int32_t lReturned;
    uint8_t * pucUDPPayloadBuffer;

    const int32_t lMaxLoopCount = 50;
    const char * const pcStringToSend = "Zero copy message number";
/* The buffer is large enough to hold the string, a number, and the string terminator. */
    const size_t xBufferLength = strlen( pcStringToSend ) + 15;

    #if ipconfigINCLUDE_EXAMPLE_FREERTOS_PLUS_TRACE_CALLS == 1
    {
        /* When the trace recorder code is included user events are generated to
         * mark the sending and receiving of the echoed data (only in the zero copy
         * task). */
        xZeroCopySendEvent = xTraceOpenLabel( "ZeroCopyTx" );
        xZeroCopyReceiveEvent = xTraceOpenLabel( "ZeroCopyRx" );
    }
    #endif /* ipconfigINCLUDE_EXAMPLE_FREERTOS_PLUS_TRACE_CALLS */

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

    /* Delay for a little while to ensure the task is out of synch with the
     * other echo task implemented above. */
    vTaskDelay( echoLOOP_DELAY >> 1 );

    /* Echo requests are sent to the echo server.  The address of the echo
     * server is configured by the constants configECHO_SERVER_ADDR0 to
     * configECHO_SERVER_ADDR3 in FreeRTOSConfig.h. */
    xEchoServerAddress.sin_port = FreeRTOS_htons( echoECHO_PORT );

    #if defined( ipconfigIPv4_BACKWARD_COMPATIBLE ) && ( ipconfigIPv4_BACKWARD_COMPATIBLE == 0 )
    {
        xEchoServerAddress.sin_address.ulIP_IPv4 = FreeRTOS_inet_addr_quick( configECHO_SERVER_ADDR0,
                                                                             configECHO_SERVER_ADDR1,
                                                                             configECHO_SERVER_ADDR2,
                                                                             configECHO_SERVER_ADDR3 );
    }
    #else
    {
        xEchoServerAddress.sin_addr = FreeRTOS_inet_addr_quick( configECHO_SERVER_ADDR0,
                                                                configECHO_SERVER_ADDR1,
                                                                configECHO_SERVER_ADDR2,
                                                                configECHO_SERVER_ADDR3 );
    }
    #endif /* defined( ipconfigIPv4_BACKWARD_COMPATIBLE ) && ( ipconfigIPv4_BACKWARD_COMPATIBLE == 0 ) */

    xEchoServerAddress.sin_family = FREERTOS_AF_INET;

    for( ; ; )
    {
        /* Create a socket. */
        xSocket = FreeRTOS_socket( FREERTOS_AF_INET, FREERTOS_SOCK_DGRAM, FREERTOS_IPPROTO_UDP );
        configASSERT( xSocket != FREERTOS_INVALID_SOCKET );

        /* Set a time out so a missing reply does not cause the task to block
         * indefinitely. */
        FreeRTOS_setsockopt( xSocket, 0, FREERTOS_SO_RCVTIMEO, &xReceiveTimeOut, sizeof( xReceiveTimeOut ) );

        /* Send a number of echo requests. */
        for( lLoopCount = 0; lLoopCount < lMaxLoopCount; lLoopCount++ )
        {
            /* This task is going to send using the zero copy interface.  The
             * data being sent is therefore written directly into a buffer that is
             * passed by reference into the FreeRTOS_sendto() function.  First
             * obtain a buffer of adequate size from the IP stack.  Although a max
             * delay is used, the actual delay will be capped to
             * ipconfigMAX_SEND_BLOCK_TIME_TICKS, hence the test to ensure a buffer
             * was actually obtained. */
            pucUDPPayloadBuffer = ( uint8_t * ) FreeRTOS_GetUDPPayloadBuffer( xBufferLength, portMAX_DELAY );

            if( pucUDPPayloadBuffer != NULL )
            {
                /* A buffer was successfully obtained.  Create the string that is
                 * sent to the echo server.  Note the string is written directly
                 * into the buffer obtained from the IP stack. */
                sprintf( ( char * ) pucUDPPayloadBuffer, "%s %u\r\n", "Zero copy message number", ( unsigned int ) ulTxCount );

                /* Also copy the string into a local buffer so it can be compared
                 * with the string that is later received back from the echo server. */
                strcpy( cTxString, ( char * ) pucUDPPayloadBuffer );

                /* Pass the buffer into the send function.  ulFlags has the
                 * FREERTOS_ZERO_COPY bit set so the IP stack will take control of
                 * the	buffer, rather than copy data out of the buffer. */
                echoMARK_SEND_IN_TRACE_BUFFER( xZeroCopySendEvent );
                lReturned = FreeRTOS_sendto( xSocket,                        /* The socket being sent to. */
                                             ( void * ) pucUDPPayloadBuffer, /* The buffer being passed into the IP stack. */
                                             strlen( cTxString ) + 1,        /* The length of the data being sent.  Plus 1 to ensure the null terminator is part of the data. */
                                             FREERTOS_ZERO_COPY,             /* ulFlags with the zero copy bit is set. */
                                             &xEchoServerAddress,            /* Where the data is being sent. */
                                             sizeof( xEchoServerAddress ) );

                if( lReturned == 0 )
                {
                    /* The send operation failed, so this task is still
                     * responsible	for the buffer obtained from the IP stack.  To
                     * ensure the buffer is not lost it must either be used again,
                     * or, as in this case, returned to the IP stack using
                     * FreeRTOS_ReleaseUDPPayloadBuffer().  pucUDPPayloadBuffer can
                     * be safely re-used to receive from the socket below once the
                     * buffer has been returned to the stack. */
                    FreeRTOS_ReleaseUDPPayloadBuffer( ( void * ) pucUDPPayloadBuffer );
                }
                else
                {
                    /* The send was successful so the IP stack is now managing
                     * the	buffer pointed to by pucUDPPayloadBuffer, and the IP
                     * stack will return the buffer once it has been sent.
                     * pucUDPPayloadBuffer can	be safely re-used to receive from
                     * the socket below. */
                }

                /* Keep a count of how many echo requests have been transmitted
                 * so it can be compared to the number of echo replies received.
                 * It would be expected to loose at least one to an ARP message the
                 * first time the connection is created. */
                ulTxCount++;

                /* Receive data on the socket.  ulFlags has the zero copy bit set
                 * (FREERTOS_ZERO_COPY) indicating to the stack that a reference to
                 * the	received data should be passed out to this task using the
                 * second parameter to the FreeRTOS_recvfrom() call.  When this is
                 * done the IP stack is no longer responsible for releasing the
                 * buffer, and	the task *must* return the buffer to the stack when
                 * it is no longer	needed.  By default the receive block time is
                 * portMAX_DELAY. */
                echoMARK_SEND_IN_TRACE_BUFFER( xZeroCopyReceiveEvent );
                lReturned = FreeRTOS_recvfrom( xSocket,                         /* The socket to receive from. */
                                               ( void * ) &pucUDPPayloadBuffer, /* pucUDPPayloadBuffer will be set to point to the buffer that already contains the received data. */
                                               0,                               /* Ignored because the zero copy interface is being used. */
                                               FREERTOS_ZERO_COPY,              /* ulFlags with the FREERTOS_ZERO_COPY bit set. */
                                               &xEchoServerAddress,             /* The address from which the data was sent. */
                                               &xAddressLength );

                if( lReturned > 0 )
                {
                    /* Compare the string sent to the echo server with the string
                     * received back from the echo server. */
                    if( strcmp( ( char * ) pucUDPPayloadBuffer, cTxString ) == 0 )
                    {
                        /* The strings matched. */
                        ulRxCount++;
                    }

                    /* The buffer that contains the data passed out of the stack
                     * must* be returned to the stack. */
                    FreeRTOS_ReleaseUDPPayloadBuffer( pucUDPPayloadBuffer );
                }
            }
        }

        /* Pause for a short while to ensure the network is not too
         * congested. */
        vTaskDelay( echoLOOP_DELAY );

        /* Close this socket before looping back to create another. */
        FreeRTOS_closesocket( xSocket );
    }
}
/*-----------------------------------------------------------*/
