/*
 * FreeRTOS+TCP <DEVELOPMENT BRANCH>
 * Copyright (C) 2022 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * 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://aws.amazon.com/freertos
 * http://www.FreeRTOS.org
 */

/**
 * @file FreeRTOS_IPv6_Sockets.c
 * @brief Implements the Sockets API based on Berkeley sockets for the FreeRTOS+TCP network stack.
 *        Sockets are used by the application processes to interact with the IP-task which in turn
 *        interacts with the hardware.
 */

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

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

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

/* *INDENT-OFF* */
#if( ipconfigUSE_IPv6 != 0 )
/* *INDENT-ON* */

#if ( ipconfigUSE_TCP == 1 )

/**
 * @brief Called by pxTCPSocketLookup(), this function will check if a socket
 *        is connected to a remote IP-address. It will be called from a loop
 *        iterating through all sockets.
 * @param[in] pxSocket The socket to be inspected.
 * @param[in] pxAddress The IPv4/IPv6 address.
 * @return The socket in case it is connected to the remote IP-address.
 */
    FreeRTOS_Socket_t * pxTCPSocketLookup_IPv6( FreeRTOS_Socket_t * pxSocket,
                                                const IPv46_Address_t * pxAddress )
    {
        FreeRTOS_Socket_t * pxResult = NULL;

        if( ( pxSocket != NULL ) && ( pxAddress != NULL ) )
        {
            if( pxSocket->bits.bIsIPv6 != pdFALSE_UNSIGNED )
            {
                if( pxAddress->xIs_IPv6 != pdFALSE )
                {
                    if( memcmp( pxSocket->u.xTCP.xRemoteIP.xIP_IPv6.ucBytes, pxAddress->xIPAddress.xIP_IPv6.ucBytes, ipSIZE_OF_IPv6_ADDRESS ) == 0 )
                    {
                        /* For sockets not in listening mode, find a match with
                         * uxLocalPort, ulRemoteIP AND uxRemotePort. */
                        pxResult = pxSocket;
                    }
                }
            }
            else
            {
                if( pxAddress->xIs_IPv6 == pdFALSE )
                {
                    if( pxSocket->u.xTCP.xRemoteIP.ulIP_IPv4 == pxAddress->xIPAddress.ulIP_IPv4 )
                    {
                        /* For sockets not in listening mode, find a match with
                         * uxLocalPort, ulRemoteIP AND uxRemotePort. */
                        pxResult = pxSocket;
                    }
                }
            }
        }

        return pxResult;
    }

#endif /* if ( ( ipconfigUSE_TCP == 1 ) */

/**
 * @brief Called by prvSendUDPPacket(), this function will UDP packet
 *        fields and IPv6 address for the packet to be send.
 * @param[in] pxNetworkBuffer  The packet to be sent.
 * @param[in] pxDestinationAddress The IPv4 socket address.
 * @return  Returns NULL, always.
 */
void * xSend_UDP_Update_IPv6( NetworkBufferDescriptor_t * pxNetworkBuffer,
                              const struct freertos_sockaddr * pxDestinationAddress )
{
    /* MISRA Ref 11.3.1 [Misaligned access] */
    /* More details at: https://github.com/FreeRTOS/FreeRTOS-Plus-TCP/blob/main/MISRA.md#rule-113 */
    /* coverity[misra_c_2012_rule_11_3_violation] */
    UDPPacket_IPv6_t * pxUDPPacket_IPv6 = ( ( UDPPacket_IPv6_t * ) pxNetworkBuffer->pucEthernetBuffer );

    pxNetworkBuffer->xIPAddress.ulIP_IPv4 = 0U;

    configASSERT( pxDestinationAddress != NULL );
    ( void ) memcpy( pxUDPPacket_IPv6->xIPHeader.xDestinationAddress.ucBytes, pxDestinationAddress->sin_address.xIP_IPv6.ucBytes, ipSIZE_OF_IPv6_ADDRESS );
    ( void ) memcpy( pxNetworkBuffer->xIPAddress.xIP_IPv6.ucBytes, pxDestinationAddress->sin_address.xIP_IPv6.ucBytes, ipSIZE_OF_IPv6_ADDRESS );
    pxUDPPacket_IPv6->xEthernetHeader.usFrameType = ipIPv6_FRAME_TYPE;

    return NULL;
}

/**
 * @brief Called by FreeRTOS_recvfrom(), this function will update socket
 *        address with IPv6 address from the packet received.
 * @param[in] pxNetworkBuffer  The packet received.
 * @param[in] pxSourceAddress The IPv4 socket address.
 * @return The Payload Offset.
 */
size_t xRecv_Update_IPv6( const NetworkBufferDescriptor_t * pxNetworkBuffer,
                          struct freertos_sockaddr * pxSourceAddress )
{
    /* MISRA Ref 11.3.1 [Misaligned access] */
    /* More details at: https://github.com/FreeRTOS/FreeRTOS-Plus-TCP/blob/main/MISRA.md#rule-113 */
    /* coverity[misra_c_2012_rule_11_3_violation] */
    const UDPPacket_IPv6_t * pxUDPPacketV6 = ( ( const UDPPacket_IPv6_t * ) pxNetworkBuffer->pucEthernetBuffer );
    size_t uxPayloadOffset = 0;

    if( pxUDPPacketV6->xEthernetHeader.usFrameType == ipIPv6_FRAME_TYPE )
    {
        if( pxSourceAddress != NULL )
        {
            ( void ) memcpy( ( void * ) pxSourceAddress->sin_address.xIP_IPv6.ucBytes,
                             ( const void * ) pxUDPPacketV6->xIPHeader.xSourceAddress.ucBytes,
                             ipSIZE_OF_IPv6_ADDRESS );
            pxSourceAddress->sin_family = ( uint8_t ) FREERTOS_AF_INET6;
            pxSourceAddress->sin_port = pxNetworkBuffer->usPort;
        }

        uxPayloadOffset = ipUDP_PAYLOAD_OFFSET_IPv6;
    }

    return uxPayloadOffset;
}


/**
 * @brief Converts a 4 bit (nibble) value to a readable hex character, e.g. 14 becomes 'e'.
 * @param usValue  The value to be converted, must be between 0 and 15.
 * @return The character, between '0' and '9', or between 'a' and 'f'.
 */
char cHexToChar( uint16_t usValue )
{
    char cReturn = '0';

    if( usValue <= 9U )
    {
        cReturn = ( char ) ( cReturn + usValue );
    }
    else if( usValue <= 15U )
    {
        cReturn = 'a';
        cReturn = ( char ) ( cReturn + ( usValue - ( uint16_t ) 10 ) );
    }
    else
    {
        /* The value passed to 'usValue' has been and-ed with 0x0f,
         * so this else clause should never be reached. */
        configASSERT( 0 == 1 );
    }

    return cReturn;
}

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

/**
 * @brief Convert a short numeric value to a hex string of at most 4 characters.
 *        The resulting string is **not** null-terminated. The resulting string
 *        will not have leading zero's, except when 'usValue' equals zero.
 * @param[in] pcBuffer  The buffer to which the string is written.
 * @param[in] uxBufferSize  The size of the buffer pointed to by 'pcBuffer'.
 * @param[in] usValue  The 16-bit value to be converted.
 * @return The number of bytes written to 'pcBuffer'.
 */
socklen_t uxHexPrintShort( char * pcBuffer,
                           size_t uxBufferSize,
                           uint16_t usValue )
{
    const size_t uxNibbleCount = 4U;
    size_t uxNibble;
    socklen_t uxIndex = 0U;
    uint16_t usShifter = usValue;
    BaseType_t xHadNonZero = pdFALSE;

    for( uxNibble = 0; uxNibble < uxNibbleCount; uxNibble++ )
    {
        uint16_t usNibble = ( usShifter >> 12 ) & 0x0FU;

        if( usNibble != 0U )
        {
            xHadNonZero = pdTRUE;
        }

        if( ( xHadNonZero != pdFALSE ) || ( uxNibble == ( uxNibbleCount - 1U ) ) )
        {
            if( uxIndex >= ( uxBufferSize - 1U ) )
            {
                break;
            }

            pcBuffer[ uxIndex ] = cHexToChar( usNibble );
            uxIndex++;
        }

        usShifter = ( uint16_t ) ( usShifter << 4 );
    }

    return uxIndex;
}
/*-----------------------------------------------------------*/

/**
 * @brief Scan the binary IPv6 address and find the longest train of consecutive zero's.
 *        The result of this search will be stored in 'xZeroStart' and 'xZeroLength'.
 * @param pxSet the set of parameters as used by FreeRTOS_inet_ntop6().
 */
void prv_ntop6_search_zeros( struct sNTOP6_Set * pxSet )
{
    BaseType_t xIndex = 0;            /* The index in the IPv6 address: 0..7. */
    BaseType_t xCurStart = 0;         /* The position of the first zero found so far. */
    BaseType_t xCurLength = 0;        /* The number of zero's seen so far. */
    const BaseType_t xShortCount = 8; /* An IPv6 address consists of 8 shorts. */

    /* Default: when xZeroStart is negative, it won't match with any xIndex. */
    pxSet->xZeroStart = -1;

    /* Look for the longest train of zero's 0:0:0:... */
    for( ; xIndex < xShortCount; xIndex++ )
    {
        uint16_t usValue = pxSet->pusAddress[ xIndex ];

        if( usValue == 0U )
        {
            if( xCurLength == 0 )
            {
                /* Remember the position of the first zero. */
                xCurStart = xIndex;
            }

            /* Count consecutive zeros. */
            xCurLength++;
        }

        if( ( usValue != 0U ) || ( xIndex == ( xShortCount - 1 ) ) )
        {
            /* Has a longer train of zero's been found? */
            if( ( xCurLength > 1 ) && ( pxSet->xZeroLength < xCurLength ) )
            {
                /* Remember the number of consecutive zeros. */
                pxSet->xZeroLength = xCurLength;
                /* Remember the index of the first zero found. */
                pxSet->xZeroStart = xCurStart;
            }

            /* Reset the counter of consecutive zeros. */
            xCurLength = 0;
        }
    }
}
/*-----------------------------------------------------------*/

/**
 * @brief The location is now at the longest train of zero's. Two colons have to
 *        be printed without a numeric value, e.g. "ff02::1".
 * @param pcDestination the output buffer where the colons will be printed.
 * @param uxSize the remaining length of the output buffer.
 * @param pxSet the set of parameters as used by FreeRTOS_inet_ntop6().
 * @return pdPASS in case the output buffer is big enough to contain the colons.
 * @note uxSize must be at least 2, enough to print "::". The string will get
 *       null-terminated later on.
 */
static BaseType_t prv_ntop6_write_zeros( char * pcDestination,
                                         size_t uxSize,
                                         struct sNTOP6_Set * pxSet )
{
    BaseType_t xReturn = pdPASS;
    const BaseType_t xShortCount = 8; /* An IPv6 address consists of 8 shorts. */

    if( pxSet->uxTargetIndex <= ( uxSize - 1U ) )
    {
        pcDestination[ pxSet->uxTargetIndex ] = ':';
        pxSet->uxTargetIndex++;

        if( ( pxSet->xIndex + pxSet->xZeroLength ) == xShortCount )
        {
            /* Reached the last index, write a second ":". */
            if( pxSet->uxTargetIndex <= ( uxSize - 1U ) )
            {
                pcDestination[ pxSet->uxTargetIndex ] = ':';
                pxSet->uxTargetIndex++;
            }
            else
            {
                /* Can not write the second colon. */
                xReturn = pdFAIL;
            }
        }
        else
        {
            /* Otherwise the function prv_ntop6_write_short() will places the second colon. */
        }
    }
    else
    {
        /* Can not write the first colon. */
        xReturn = pdFAIL;
    }

    return xReturn;
}
/*-----------------------------------------------------------*/

/**
 * @brief Write a short value, as a hex number with at most 4 characters. E.g. the
 *        value 15 will be printed as "f".
 * @param pcDestination the output buffer where the hex number is to be printed.
 * @param uxSize the remaining length of the output buffer.
 * @param pxSet the set of parameters as used by FreeRTOS_inet_ntop6().
 * @return pdPASS in case the output buffer is big enough to contain the string.
 * @note uxSize must be at least 4, enough to print "abcd". The string will get
 *       null-terminated later on.
 */
static BaseType_t prv_ntop6_write_short( char * pcDestination,
                                         size_t uxSize,
                                         struct sNTOP6_Set * pxSet )
{
    socklen_t uxLength;
    BaseType_t xReturn = pdPASS;
    const size_t uxBytesPerShortValue = 4U;

    if( pxSet->xIndex > 0 )
    {
        if( pxSet->uxTargetIndex >= ( uxSize - 1U ) )
        {
            xReturn = pdFAIL;
        }
        else
        {
            pcDestination[ pxSet->uxTargetIndex ] = ':';
            pxSet->uxTargetIndex++;
        }
    }

    if( xReturn == pdPASS )
    {
        /* If there is enough space to write a short. */
        if( pxSet->uxTargetIndex <= ( uxSize - uxBytesPerShortValue ) )
        {
            /* Write hex value of short. at most 4 + 1 bytes. */
            uxLength = uxHexPrintShort( &( pcDestination[ pxSet->uxTargetIndex ] ),
                                        uxBytesPerShortValue + 1U,
                                        FreeRTOS_ntohs( pxSet->pusAddress[ pxSet->xIndex ] ) );

            /* uxLength will be non zero and positive always. */
            pxSet->uxTargetIndex += uxLength;
        }
        else
        {
            xReturn = pdFAIL;
        }
    }

    return xReturn;
}
/*-----------------------------------------------------------*/

/**
 * @brief This function converts a binary IPv6 address to a human readable notation.
 *
 * @param[in] pvSource The binary address, 16 bytes long..
 * @param[out] pcDestination The human-readable ( hexadecimal ) notation of the
 *                            address.
 * @param[in] uxSize The size of pvDestination. A value of 40 is recommended.
 *
 * @return pdPASS if the translation was successful or else pdFAIL.
 */
const char * FreeRTOS_inet_ntop6( const void * pvSource,
                                  char * pcDestination,
                                  socklen_t uxSize )
{
    const char * pcReturn;  /* The return value, which is either 'pcDestination' or NULL. */
    struct sNTOP6_Set xSet; /* A set of values for easy exchange with the helper functions prv_ntop6_xxx(). */

    ( void ) memset( &( xSet ), 0, sizeof( xSet ) );

    xSet.pusAddress = pvSource;

    if( uxSize < 3U )
    {
        /* Can not even print :: */
    }
    else
    {
        prv_ntop6_search_zeros( &( xSet ) );

        while( xSet.xIndex < 8 )
        {
            if( xSet.xIndex == xSet.xZeroStart )
            {
                if( prv_ntop6_write_zeros( pcDestination, uxSize, &( xSet ) ) == pdFAIL )
                {
                    break;
                }

                xSet.xIndex += xSet.xZeroLength;
            }
            else
            {
                if( prv_ntop6_write_short( pcDestination, uxSize, &( xSet ) ) == pdFAIL )
                {
                    break;
                }

                xSet.xIndex++;
            }
        }
    }

    if( xSet.xIndex < 8 )
    {
        /* Didn't reach the last nibble: clear the string. */
        pcReturn = NULL;
    }
    else
    {
        pcDestination[ xSet.uxTargetIndex ] = '\0';
        pcReturn = pcDestination;
    }

    return pcReturn;
}
/*-----------------------------------------------------------*/

/**
 * @brief Converting a readable IPv6 address to its binary form, add one nibble.
 *
 * @param[in] pxSet  A set of variables describing the conversion.
 * @param[in] ucNew  The hex value, between 0 and 15
 * @param[in] ch  The character, such as '5', 'f', or ':'.
 *
 * @return pdTRUE when the nibble was added, otherwise pdFALSE.
 */
static BaseType_t prv_inet_pton6_add_nibble( struct sPTON6_Set * pxSet,
                                             uint8_t ucNew,
                                             char ch )
{
    BaseType_t xReturn = pdPASS;

    if( ucNew != ( uint8_t ) socketINVALID_HEX_CHAR )
    {
        /* Shift in 4 bits. */
        pxSet->ulValue <<= 4;
        pxSet->ulValue |= ( uint32_t ) ucNew;

        /* Remember that ulValue is valid now. */
        pxSet->xHadDigit = pdTRUE;

        /* Check if the number is not becoming larger than 16 bits. */
        if( pxSet->ulValue > 0xffffU )
        {
            /* The highest nibble has already been set,
             * an overflow would occur.  Break out of the for-loop. */
            xReturn = pdFAIL;
        }
    }
    else if( ch == ':' )
    {
        if( pxSet->xHadDigit == pdFALSE )
        {
            /* A "::" sequence has been received. Check if it is not a third colon. */
            if( pxSet->xColon >= 0 )
            {
                xReturn = pdFAIL;
            }
            else
            {
                /* Two or more zero's are expected, starting at position 'xColon'. */
                pxSet->xColon = pxSet->xTargetIndex;
            }
        }
        else
        {
            if( pxSet->xTargetIndex <= pxSet->xHighestIndex )
            {
                /* Store a short value at position 'xTargetIndex'. */
                pxSet->pucTarget[ pxSet->xTargetIndex ] = ( uint8_t ) ( ( pxSet->ulValue >> 8 ) & 0xffU );
                pxSet->pucTarget[ pxSet->xTargetIndex + 1 ] = ( uint8_t ) ( pxSet->ulValue & 0xffU );
                pxSet->xTargetIndex += 2;
                pxSet->xHadDigit = pdFALSE;
                pxSet->ulValue = 0U;
            }
            else
            {
                xReturn = pdFAIL;
            }
        }
    }
    else
    {
        /* When an IPv4 address or rubbish is provided, this statement will be reached. */
        xReturn = pdFAIL;
    }

    return xReturn;
}
/*-----------------------------------------------------------*/

/**
 * @brief Convert an ASCII character to its corresponding hexadecimal value.
 *        A :: block was found, now fill in the zero's.
 * @param[in] pxSet  A set of variables describing the conversion.
 */
static void prv_inet_pton6_set_zeros( struct sPTON6_Set * pxSet )
{
    /* The number of bytes that were written after the :: */
    const BaseType_t xCount = pxSet->xTargetIndex - pxSet->xColon;
    const BaseType_t xTopIndex = ( BaseType_t ) ipSIZE_OF_IPv6_ADDRESS;
    BaseType_t xIndex;
    BaseType_t xTarget = xTopIndex - 1;
    BaseType_t xSource = pxSet->xColon + ( xCount - 1 );

    /* Inserting 'xCount' zero's. */
    for( xIndex = 0; xIndex < xCount; xIndex++ )
    {
        pxSet->pucTarget[ xTarget ] = pxSet->pucTarget[ xSource ];
        pxSet->pucTarget[ xSource ] = 0;
        xTarget--;
        xSource--;
    }

    pxSet->xTargetIndex = ( BaseType_t ) ipSIZE_OF_IPv6_ADDRESS;
}
/*-----------------------------------------------------------*/

/**
 * @brief Convert an IPv6 address in hexadecimal notation to a binary format of 16 bytes.
 *
 * @param[in] pcSource The address in hexadecimal notation.
 * @param[out] pvDestination The address in binary format, 16 bytes long.
 *
 * @return The 32-bit representation of IP(v4) address.
 */
BaseType_t FreeRTOS_inet_pton6( const char * pcSource,
                                void * pvDestination )
{
    char ch;
    uint8_t ucNew;
    BaseType_t xResult;
    struct sPTON6_Set xSet;

    const char * pcIterator = pcSource;

    ( void ) memset( &( xSet ), 0, sizeof( xSet ) );
    xSet.xColon = -1;
    xSet.pucTarget = pvDestination;

    ( void ) memset( xSet.pucTarget, 0, ipSIZE_OF_IPv6_ADDRESS );

    xResult = 0;

    /* Leading :: requires some special handling. */
    if( strcmp( pcIterator, "::" ) == 0 )
    {
        xResult = 1;
    }
    else
    {
        if( pcIterator[ 0 ] == ':' )
        {
            pcIterator++;
        }

        /* The last bytes will be written at position 14 and 15. */
        xSet.xHighestIndex = ( BaseType_t ) ipSIZE_OF_IPv6_ADDRESS;
        xSet.xHighestIndex -= ( BaseType_t ) sizeof( uint16_t );

        /* The value in ulValue is not yet valid. */
        xSet.xHadDigit = pdFALSE;
        xSet.ulValue = 0U;

        for( ; ; )
        {
            ch = *( pcIterator );
            pcIterator++;

            if( ch == ( char ) '\0' )
            {
                /* The string is parsed now.
                 * Store the last short, if present. */
                if( ( xSet.xHadDigit != pdFALSE ) &&
                    ( xSet.xTargetIndex <= xSet.xHighestIndex ) )
                {
                    /* Add the last value seen, network byte order ( MSB first ). */
                    xSet.pucTarget[ xSet.xTargetIndex ] = ( uint8_t ) ( ( xSet.ulValue >> 8 ) & 0xffU );
                    xSet.pucTarget[ xSet.xTargetIndex + 1 ] = ( uint8_t ) ( xSet.ulValue & 0xffU );
                    xSet.xTargetIndex += 2;
                }

                /* Break out of the for-ever loop. */
                break;
            }

            /* Convert from a readable character to a hex value. */
            ucNew = ucASCIIToHex( ch );
            /* See if this is a digit or a colon. */
            xResult = prv_inet_pton6_add_nibble( &( xSet ), ucNew, ch );

            if( xResult == pdFALSE )
            {
                /* The new character was not accepted. */
                break;
            }
        } /* for( ;; ) */

        if( xSet.xColon >= 0 )
        {
            /* The address contains a block of zero. */
            prv_inet_pton6_set_zeros( &( xSet ) );
        }

        if( xSet.xTargetIndex == ( BaseType_t ) ipSIZE_OF_IPv6_ADDRESS )
        {
            xResult = 1;
        }
    }

    if( xResult != 1 )
    {
        xSet.pucTarget = ( uint8_t * ) pvDestination;
        ( void ) memset( xSet.pucTarget, 0, ipSIZE_OF_IPv6_ADDRESS );
    }

    return xResult;
}

/*-----------------------------------------------------------*/
/* *INDENT-OFF* */
#endif /* ipconfigUSE_IPv6 != 0 */
/* *INDENT-ON* */
