/*
 * 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
 *
 */

/* WinPCap includes. */
#include "pcap.h"
#include "remote-ext.h"

/* uIP includes. */
#include "net/uip.h"
#include "net/uip_arp.h"
#include "net/clock-arch.h"

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

/*
 * Query the computer the simulation is being executed on to find the network
 * interfaces it has installed.
 */
static pcap_if_t * prvPrintAvailableNetworkInterfaces( void );

/*
 * Open the network interface.  The number of the interface to be opened is set
 * by the configNETWORK_INTERFACE_TO_USE constant in FreeRTOSConfig.h.
 */
static void prvOpenSelectedNetworkInterface( pcap_if_t * pxAllNetworkInterfaces );

/*
 * Configure the capture filter to allow blocking reads, and to filter out
 * packets that are not of interest to this demo.
 */
static void prvConfigureCaptureBehaviour( void );

pcap_t * pxOpenedInterfaceHandle = NULL;
LARGE_INTEGER freq, sys_start_time;

#define archNUM_BUFFERS            5
#define archNUM_BUFFER_POINTERS    ( archNUM_BUFFERS - 1 )

static void prvInterruptSimulator( void * pvParameters );

static unsigned char ucEthernetBuffer[ archNUM_BUFFERS ][ UIP_CONF_BUFFER_SIZE ];
static unsigned char * pucEthernetBufferPointers[ archNUM_BUFFER_POINTERS ];

static long lLengthOfDataInBuffer[ archNUM_BUFFER_POINTERS ] = { 0 };
static unsigned char ucNextBufferToFill = 0U, ucNextBufferToProcess = 0U;

unsigned char * uip_buf = NULL;
char cErrorBuffer[ PCAP_ERRBUF_SIZE ];

void vNetifTx( void )
{
    pcap_sendpacket( pxOpenedInterfaceHandle, uip_buf, uip_len );
    pcap_sendpacket( pxOpenedInterfaceHandle, uip_buf, uip_len );
}
/*-----------------------------------------------------------*/

UBaseType_t uxNetifRx( void )
{
    UBaseType_t xDataLen;
    unsigned char * pucTemp;

    /* Check there is really data available. */
    xDataLen = lLengthOfDataInBuffer[ ucNextBufferToProcess ];

    if( xDataLen != 0L )
    {
        /* The buffer pointed to by uip_buf is going to change.  Remember which
         * buffer uip_buf is currently pointing to. */
        pucTemp = uip_buf;

        /* Point uip_buf at the next buffer that contains data. */
        uip_buf = pucEthernetBufferPointers[ ucNextBufferToProcess ];

        /* The buffer pointed to by
         * pucEthernetBufferPointeres[ ucNextBufferToProcess ] is now in use by
         * uip_buf, but the buffer uip_buf was pointing to on entry to this
         * function is free.  Set
         * pucEthernetBufferPointeres[ ucNextBufferToProcess ] to the free
         * buffer. */
        pucEthernetBufferPointers[ ucNextBufferToProcess ] = pucTemp;
        lLengthOfDataInBuffer[ ucNextBufferToProcess ] = 0L;

        ucNextBufferToProcess++;

        if( ucNextBufferToProcess >= archNUM_BUFFER_POINTERS )
        {
            ucNextBufferToProcess = 0L;
        }
    }

    return xDataLen;
}
/*-----------------------------------------------------------*/

BaseType_t xNetifInit( void )
{
    BaseType_t x;
    pcap_if_t * pxAllNetworkInterfaces;

    /* Allocate a free buffer to each buffer pointer. */
    for( x = 0; x < sizeof( pucEthernetBufferPointers ) / sizeof( unsigned char * ); x++ )
    {
        pucEthernetBufferPointers[ x ] = &( ucEthernetBuffer[ x ][ 0 ] );
    }

    /* Start with uip_buf pointing to a buffer that is not referenced from the
     * pucEthernetBufferPointers[] array. */
    uip_buf = &( ucEthernetBuffer[ archNUM_BUFFERS - 1 ][ 0 ] );

    /* Query the computer the simulation is being executed on to find the
     * network interfaces it has installed. */
    pxAllNetworkInterfaces = prvPrintAvailableNetworkInterfaces();

    /* Open the network interface.  The number of the interface to be opened is
     * set by the configNETWORK_INTERFACE_TO_USE constant in FreeRTOSConfig.h.
     * Calling this function will set the pxOpenedInterfaceHandle variable.  If,
     * after calling this function, pxOpenedInterfaceHandle is equal to NULL, then
     * the interface could not be opened. */
    if( pxAllNetworkInterfaces != NULL )
    {
        prvOpenSelectedNetworkInterface( pxAllNetworkInterfaces );
    }

    return x;
}
/*-----------------------------------------------------------*/

static pcap_if_t * prvPrintAvailableNetworkInterfaces( void )
{
    pcap_if_t * pxAllNetworkInterfaces = NULL, * xInterface;
    long lInterfaceNumber = 1;

    if( pcap_findalldevs_ex( PCAP_SRC_IF_STRING, NULL, &pxAllNetworkInterfaces, cErrorBuffer ) == -1 )
    {
        printf( "\r\nCould not obtain a list of network interfaces\r\n%s\r\n", cErrorBuffer );
        pxAllNetworkInterfaces = NULL;
    }

    if( pxAllNetworkInterfaces != NULL )
    {
        /* Print out the list of network interfaces.  The first in the list
         * is interface '1', not interface '0'. */
        for( xInterface = pxAllNetworkInterfaces; xInterface != NULL; xInterface = xInterface->next )
        {
            printf( "%d. %s", lInterfaceNumber, xInterface->name );

            if( xInterface->description != NULL )
            {
                printf( " (%s)\r\n", xInterface->description );
            }
            else
            {
                printf( " (No description available)\r\n" );
            }

            lInterfaceNumber++;
        }
    }

    if( lInterfaceNumber == 1 )
    {
        /* The interface number was never incremented, so the above for() loop
         * did not execute meaning no interfaces were found. */
        printf( " \r\nNo network interfaces were found.\r\n" );
        pxAllNetworkInterfaces = NULL;
    }

    printf( "\r\nThe interface that will be opened is set by configNETWORK_INTERFACE_TO_USE which should be defined in FreeRTOSConfig.h\r\n" );
    printf( "Attempting to open interface number %d.\r\n", configNETWORK_INTERFACE_TO_USE );

    if( ( configNETWORK_INTERFACE_TO_USE < 1L ) || ( configNETWORK_INTERFACE_TO_USE > lInterfaceNumber ) )
    {
        printf( "\r\nconfigNETWORK_INTERFACE_TO_USE is not in the valid range.\r\n" );

        if( pxAllNetworkInterfaces != NULL )
        {
            /* Free the device list, as no devices are going to be opened. */
            pcap_freealldevs( pxAllNetworkInterfaces );
            pxAllNetworkInterfaces = NULL;
        }
    }

    return pxAllNetworkInterfaces;
}
/*-----------------------------------------------------------*/

static void prvOpenSelectedNetworkInterface( pcap_if_t * pxAllNetworkInterfaces )
{
    pcap_if_t * xInterface;
    long x;

    /* Walk the list of devices until the selected device is located. */
    xInterface = pxAllNetworkInterfaces;

    for( x = 0L; x < ( configNETWORK_INTERFACE_TO_USE - 1L ); x++ )
    {
        xInterface = xInterface->next;
    }

    /* Open the selected interface. */
    pxOpenedInterfaceHandle = pcap_open( xInterface->name,          /* The name of the selected interface. */
                                         UIP_CONF_BUFFER_SIZE,      /* The size of the packet to capture. */
                                         PCAP_OPENFLAG_PROMISCUOUS, /* Open in promiscuous mode as the MAC and
                                                                     * IP address is going to be "simulated", and
                                                                     * not be the real MAC and IP address.  This allows
                                                                     * traffic to the simulated IP address to be routed
                                                                     * to uIP, and traffic to the real IP address to be
                                                                     * routed to the Windows TCP/IP stack. */
                                         0xfffffffL,                /* The read time out.  This is going to block
                                                                     * until data is available. */
                                         NULL,                      /* No authentication is required as this is
                                                                     * not a remote capture session. */
                                         cErrorBuffer
                                         );

    if( pxOpenedInterfaceHandle == NULL )
    {
        printf( "\r\n%s is not supported by WinPcap and cannot be opened\r\n", xInterface->name );
    }
    else
    {
        /* Configure the capture filter to allow blocking reads, and to filter
         * out packets that are not of interest to this demo. */
        prvConfigureCaptureBehaviour();
    }

    /* The device list is no longer required. */
    pcap_freealldevs( pxAllNetworkInterfaces );
}
/*-----------------------------------------------------------*/

static void prvConfigureCaptureBehaviour( void )
{
    struct bpf_program xFilterCode;
    const long lMinBytesToCopy = 10L, lBlocking = 0L;
    unsigned long ulNetMask;

    /* Unblock a read as soon as anything is received. */
    pcap_setmintocopy( pxOpenedInterfaceHandle, lMinBytesToCopy );

    /* Allow blocking. */
    pcap_setnonblock( pxOpenedInterfaceHandle, lBlocking, cErrorBuffer );

    /* Set up a filter so only the packets of interest are passed to the uIP
     * stack.  cErrorBuffer is used for convenience to create the string.  Don't
     * confuse this with an error message. */
    sprintf( cErrorBuffer, "broadcast or multicast or host %d.%d.%d.%d", configIP_ADDR0, configIP_ADDR1, configIP_ADDR2, configIP_ADDR3 );

    ulNetMask = ( configNET_MASK3 << 24UL ) | ( configNET_MASK2 << 16UL ) | ( configNET_MASK1 << 8L ) | configNET_MASK0;

    if( pcap_compile( pxOpenedInterfaceHandle, &xFilterCode, cErrorBuffer, 1, ulNetMask ) < 0 )
    {
        printf( "\r\nThe packet filter string is invalid\r\n" );
    }
    else
    {
        if( pcap_setfilter( pxOpenedInterfaceHandle, &xFilterCode ) < 0 )
        {
            printf( "\r\nAn error occurred setting the packet filter.\r\n" );
        }
    }

    /* Create a task that simulates an interrupt in a real system.  This will
     * block waiting for packets, then send a message to the uIP task when data
     * is available. */
    xTaskCreate( prvInterruptSimulator, ( signed char * ) "MAC_ISR", configMINIMAL_STACK_SIZE, NULL, ( ipconfigIP_TASK_PRIORITY - 1 ), NULL );
}
/*-----------------------------------------------------------*/

static void prvInterruptSimulator( void * pvParameters )
{
    static struct pcap_pkthdr * pxHeader;
    const unsigned char * pucPacketData;
    extern QueueHandle_t xEMACEventQueue;
    const unsigned long ulRxEvent = uipETHERNET_RX_EVENT;
    long lResult;

    /* Just to kill the compiler warning. */
    ( void ) pvParameters;

    for( ; ; )
    {
        /* Get the next packet. */
        lResult = pcap_next_ex( pxOpenedInterfaceHandle, &pxHeader, &pucPacketData );

        if( lResult )
        {
            /* Is the next buffer into which data should be placed free? */
            if( lLengthOfDataInBuffer[ ucNextBufferToFill ] == 0L )
            {
                /* Copy the data from the captured packet into the buffer. */
                memcpy( pucEthernetBufferPointers[ ucNextBufferToFill ], pucPacketData, pxHeader->len );

                /* Note the amount of data that was copied. */
                lLengthOfDataInBuffer[ ucNextBufferToFill ] = pxHeader->len;

                /* Move onto the next buffer, wrapping around if necessary. */
                ucNextBufferToFill++;

                if( ucNextBufferToFill >= archNUM_BUFFER_POINTERS )
                {
                    ucNextBufferToFill = 0U;
                }

                /* Data was received and stored.  Send a message to the uIP task
                 * to let it know. */
                xQueueSendToBack( xEMACEventQueue, &ulRxEvent, portMAX_DELAY );
            }
        }
    }
}
