/*
 * FreeRTOS Secure Sockets V1.1.9
 * 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://aws.amazon.com/freertos
 * http://www.FreeRTOS.org
 */

/* Define _SECURE_SOCKETS_WRAPPER_NOT_REDEFINE to prevent secure sockets functions
 * from redefining in iot_secure_sockets_wrapper_metrics.h */
#define _SECURE_SOCKETS_WRAPPER_NOT_REDEFINE

/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "FreeRTOSIPConfig.h"
#include "list.h"
#include "semphr.h"
#include "FreeRTOS_IP.h"
#include "FreeRTOS_Sockets.h"
#include "iot_secure_sockets.h"
#include "iot_tls.h"
#include "task.h"
#include "iot_pkcs11.h"
#include "iot_crypto.h"

#undef _SECURE_SOCKETS_WRAPPER_NOT_REDEFINE

/* Internal context structure. */
typedef struct SSOCKETContext
{
    Socket_t xSocket;
    char * pcDestination;
    void * pvTLSContext;
    BaseType_t xRequireTLS;
    BaseType_t xSendFlags;
    BaseType_t xRecvFlags;
    char * pcServerCertificate;
    uint32_t ulServerCertificateLength;
    char ** ppcAlpnProtocols;
    uint32_t ulAlpnProtocolsCount;
    BaseType_t xConnectAttempted;
} SSOCKETContext_t, * SSOCKETContextPtr_t;

/*
 * Helper routines.
 */

/*
 * @brief Network send callback.
 */
static BaseType_t prvNetworkSend( void * pvContext,
                                  const unsigned char * pucData,
                                  size_t xDataLength )
{
    SSOCKETContextPtr_t pxContext = ( SSOCKETContextPtr_t ) pvContext; /*lint !e9087 cast used for portability. */

    return FreeRTOS_send( pxContext->xSocket, pucData, xDataLength, pxContext->xSendFlags );
}
/*-----------------------------------------------------------*/

/*
 * @brief Network receive callback.
 */
static BaseType_t prvNetworkRecv( void * pvContext,
                                  unsigned char * pucReceiveBuffer,
                                  size_t xReceiveLength )
{
    SSOCKETContextPtr_t pxContext = ( SSOCKETContextPtr_t ) pvContext; /*lint !e9087 cast used for portability. */

    return FreeRTOS_recv( pxContext->xSocket, pucReceiveBuffer, xReceiveLength, pxContext->xRecvFlags );
}
/*-----------------------------------------------------------*/

/*
 * Interface routines.
 */

int32_t SOCKETS_Close( Socket_t xSocket )
{
    SSOCKETContextPtr_t pxContext = ( SSOCKETContextPtr_t ) xSocket; /*lint !e9087 cast used for portability. */
    uint32_t ulProtocol;
    int32_t lReturn;

    if( ( xSocket != SOCKETS_INVALID_SOCKET ) && ( NULL != pxContext ) )
    {
        /* Clean-up destination string. */
        if( NULL != pxContext->pcDestination )
        {
            vPortFree( pxContext->pcDestination );
        }

        /* Clean-up server certificate. */
        if( NULL != pxContext->pcServerCertificate )
        {
            vPortFree( pxContext->pcServerCertificate );
        }

        /* Clean-up application protocol array. */
        if( NULL != pxContext->ppcAlpnProtocols )
        {
            for( ulProtocol = 0;
                 ulProtocol < pxContext->ulAlpnProtocolsCount;
                 ulProtocol++ )
            {
                if( NULL != pxContext->ppcAlpnProtocols[ ulProtocol ] )
                {
                    vPortFree( pxContext->ppcAlpnProtocols[ ulProtocol ] );
                }
            }

            vPortFree( pxContext->ppcAlpnProtocols );
        }

        /* Clean-up TLS context. */
        if( pdTRUE == pxContext->xRequireTLS )
        {
            TLS_Cleanup( pxContext->pvTLSContext );
        }

        /* Close the underlying socket handle. */
        ( void ) FreeRTOS_closesocket( pxContext->xSocket );

        /* Free the context. */
        vPortFree( pxContext );
        lReturn = SOCKETS_ERROR_NONE;
    }
    else
    {
        lReturn = SOCKETS_EINVAL;
    }

    return lReturn;
}
/*-----------------------------------------------------------*/

int32_t SOCKETS_Connect( Socket_t xSocket,
                         SocketsSockaddr_t * pxAddress,
                         Socklen_t xAddressLength )
{
    int32_t lStatus = SOCKETS_ERROR_NONE;
    SSOCKETContextPtr_t pxContext = ( SSOCKETContextPtr_t ) xSocket; /*lint !e9087 cast used for portability. */
    TLSParams_t xTLSParams = { 0 };
    struct freertos_sockaddr xTempAddress = { 0 };

    if( ( pxContext != ( SSOCKETContextPtr_t ) SOCKETS_INVALID_SOCKET ) && ( pxAddress != NULL ) )
    {
        /* A connection was attempted. If this function fails, then the socket is invalid and the user
         * must call SOCKETS_Close(), on this socket, and SOCKETS_Socket() to get a new socket. */
        pxContext->xConnectAttempted = pdTRUE;

        /* Connect the wrapped socket. */
        xTempAddress.sin_addr = pxAddress->ulAddress;
        xTempAddress.sin_family = pxAddress->ucSocketDomain;
        xTempAddress.sin_len = ( uint8_t ) sizeof( xTempAddress );
        xTempAddress.sin_port = pxAddress->usPort;
        lStatus = FreeRTOS_connect( pxContext->xSocket, &xTempAddress, xAddressLength );

        /* Negotiate TLS if requested. */
        if( ( SOCKETS_ERROR_NONE == lStatus ) && ( pdTRUE == pxContext->xRequireTLS ) )
        {
            xTLSParams.ulSize = sizeof( xTLSParams );
            xTLSParams.pcDestination = pxContext->pcDestination;
            xTLSParams.pcServerCertificate = pxContext->pcServerCertificate;
            xTLSParams.ulServerCertificateLength = pxContext->ulServerCertificateLength;
            xTLSParams.ppcAlpnProtocols = ( const char ** ) pxContext->ppcAlpnProtocols;
            xTLSParams.ulAlpnProtocolsCount = pxContext->ulAlpnProtocolsCount;
            xTLSParams.pvCallerContext = pxContext;
            xTLSParams.pxNetworkRecv = prvNetworkRecv;
            xTLSParams.pxNetworkSend = prvNetworkSend;
            lStatus = TLS_Init( &pxContext->pvTLSContext, &xTLSParams );

            if( SOCKETS_ERROR_NONE == lStatus )
            {
                lStatus = TLS_Connect( pxContext->pvTLSContext );

                if( lStatus < 0 )
                {
                    lStatus = SOCKETS_TLS_HANDSHAKE_ERROR;
                }
            }
        }
    }
    else
    {
        lStatus = SOCKETS_SOCKET_ERROR;
    }

    return lStatus;
}
/*-----------------------------------------------------------*/

uint32_t SOCKETS_GetHostByName( const char * pcHostName )
{
    return FreeRTOS_gethostbyname( pcHostName );
}
/*-----------------------------------------------------------*/

int32_t SOCKETS_Recv( Socket_t xSocket,
                      void * pvBuffer,
                      size_t xBufferLength,
                      uint32_t ulFlags )
{
    int32_t lStatus = SOCKETS_SOCKET_ERROR;
    SSOCKETContextPtr_t pxContext = ( SSOCKETContextPtr_t ) xSocket; /*lint !e9087 cast used for portability. */

    if( ( xSocket != SOCKETS_INVALID_SOCKET ) &&
        ( pvBuffer != NULL ) )
    {
        pxContext->xRecvFlags = ( BaseType_t ) ulFlags;

        if( pdTRUE == pxContext->xRequireTLS )
        {
            /* Receive through TLS pipe, if negotiated. */
            lStatus = TLS_Recv( pxContext->pvTLSContext, pvBuffer, xBufferLength );
        }
        else
        {
            /* Receive unencrypted. */
            lStatus = prvNetworkRecv( pxContext, pvBuffer, xBufferLength );
        }
    }
    else
    {
        lStatus = SOCKETS_EINVAL;
    }

    return lStatus;
}
/*-----------------------------------------------------------*/

int32_t SOCKETS_Send( Socket_t xSocket,
                      const void * pvBuffer,
                      size_t xDataLength,
                      uint32_t ulFlags )
{
    int32_t lStatus = SOCKETS_SOCKET_ERROR;
    SSOCKETContextPtr_t pxContext = ( SSOCKETContextPtr_t ) xSocket; /*lint !e9087 cast used for portability. */

    if( ( xSocket != SOCKETS_INVALID_SOCKET ) &&
        ( pvBuffer != NULL ) )
    {
        pxContext->xSendFlags = ( BaseType_t ) ulFlags;

        if( pdTRUE == pxContext->xRequireTLS )
        {
            /* Send through TLS pipe, if negotiated. */
            lStatus = TLS_Send( pxContext->pvTLSContext, pvBuffer, xDataLength );
        }
        else
        {
            /* Send unencrypted. */
            lStatus = prvNetworkSend( pxContext, pvBuffer, xDataLength );
        }
    }
    else
    {
        lStatus = SOCKETS_EINVAL;
    }

    return lStatus;
}
/*-----------------------------------------------------------*/

int32_t SOCKETS_SetSockOpt( Socket_t xSocket,
                            int32_t lLevel,
                            int32_t lOptionName,
                            const void * pvOptionValue,
                            size_t xOptionLength )
{
    int32_t lStatus = SOCKETS_ERROR_NONE;
    TickType_t xTimeout;
    SSOCKETContextPtr_t pxContext = ( SSOCKETContextPtr_t ) xSocket; /*lint !e9087 cast used for portability. */
    char ** ppcAlpnIn = ( char ** ) pvOptionValue;
    size_t xLength = 0;
    uint32_t ulProtocol;

    if( ( xSocket != SOCKETS_INVALID_SOCKET ) && ( xSocket != NULL ) )
    {
        switch( lOptionName )
        {
            case SOCKETS_SO_SERVER_NAME_INDICATION:

                /* Do not set the SNI options if the socket is possibly already connected. */
                if( pxContext->xConnectAttempted == pdTRUE )
                {
                    lStatus = SOCKETS_EISCONN;
                }

                /* Non-NULL destination string indicates that SNI extension should
                 * be used during TLS negotiation. */
                else if( NULL == ( pxContext->pcDestination =
                                       ( char * ) pvPortMalloc( 1U + xOptionLength ) ) )
                {
                    lStatus = SOCKETS_ENOMEM;
                }
                else
                {
                    memcpy( pxContext->pcDestination, pvOptionValue, xOptionLength );
                    pxContext->pcDestination[ xOptionLength ] = '\0';
                }

                break;

            case SOCKETS_SO_TRUSTED_SERVER_CERTIFICATE:

                /* Do not set the trusted server certificate if the socket is possibly already connected. */
                if( pxContext->xConnectAttempted == pdTRUE )
                {
                    lStatus = SOCKETS_EISCONN;
                }

                /* Non-NULL server certificate field indicates that the default trust
                 * list should not be used. */
                else if( NULL == ( pxContext->pcServerCertificate =
                                       ( char * ) pvPortMalloc( xOptionLength ) ) )
                {
                    lStatus = SOCKETS_ENOMEM;
                }
                else
                {
                    memcpy( pxContext->pcServerCertificate, pvOptionValue, xOptionLength );
                    pxContext->ulServerCertificateLength = xOptionLength;
                }

                break;

            case SOCKETS_SO_REQUIRE_TLS:

                /* Do not set the TLS option if the socket is possibly already connected. */
                if( pxContext->xConnectAttempted == pdTRUE )
                {
                    lStatus = SOCKETS_EISCONN;
                }
                else
                {
                    pxContext->xRequireTLS = pdTRUE;
                }

                break;

            case SOCKETS_SO_ALPN_PROTOCOLS:

                /* Do not set the ALPN option if the socket is already connected. */
                if( pxContext->xConnectAttempted == pdTRUE )
                {
                    lStatus = SOCKETS_EISCONN;
                    break;
                }

                /* Allocate a sufficiently long array of pointers. */
                pxContext->ulAlpnProtocolsCount = 1 + xOptionLength;

                if( NULL == ( pxContext->ppcAlpnProtocols =
                                  ( char ** ) pvPortMalloc( pxContext->ulAlpnProtocolsCount * sizeof( char * ) ) ) )
                {
                    lStatus = SOCKETS_ENOMEM;
                }
                else
                {
                    /* Zero out the pointers. */
                    memset( pxContext->ppcAlpnProtocols,
                            0,
                            pxContext->ulAlpnProtocolsCount * sizeof( char * ) );
                }

                /* Copy each protocol string. */
                for( ulProtocol = 0;
                     ( ulProtocol < pxContext->ulAlpnProtocolsCount - 1 ) &&
                     ( pdFREERTOS_ERRNO_NONE == lStatus );
                     ulProtocol++ )
                {
                    xLength = strlen( ppcAlpnIn[ ulProtocol ] );

                    if( NULL == ( pxContext->ppcAlpnProtocols[ ulProtocol ] =
                                      ( char * ) pvPortMalloc( 1 + xLength ) ) )
                    {
                        lStatus = SOCKETS_ENOMEM;
                    }
                    else
                    {
                        memcpy( pxContext->ppcAlpnProtocols[ ulProtocol ],
                                ppcAlpnIn[ ulProtocol ],
                                xLength );
                        pxContext->ppcAlpnProtocols[ ulProtocol ][ xLength ] = '\0';
                    }
                }

                break;

            case SOCKETS_SO_NONBLOCK:
                xTimeout = 0;

                /* Non-blocking connect is not supported.  Socket may be set to nonblocking
                 * only after a connection is made. */
                if( pdTRUE == pxContext->xConnectAttempted )
                {
                    lStatus = FreeRTOS_setsockopt( pxContext->xSocket,
                                                   lLevel,
                                                   SOCKETS_SO_RCVTIMEO,
                                                   &xTimeout,
                                                   sizeof( xTimeout ) );

                    if( lStatus == SOCKETS_ERROR_NONE )
                    {
                        lStatus = FreeRTOS_setsockopt( pxContext->xSocket,
                                                       lLevel,
                                                       SOCKETS_SO_SNDTIMEO,
                                                       &xTimeout,
                                                       sizeof( xTimeout ) );
                    }
                }
                else
                {
                    lStatus = SOCKETS_EISCONN;
                }

                break;

            case SOCKETS_SO_RCVTIMEO:
            case SOCKETS_SO_SNDTIMEO:
                /* Comply with Berkeley standard - a 0 timeout is wait forever. */
                xTimeout = *( ( const TickType_t * ) pvOptionValue ); /*lint !e9087 pvOptionValue passed should be of TickType_t */

                if( xTimeout == 0U )
                {
                    xTimeout = portMAX_DELAY;
                }

                lStatus = FreeRTOS_setsockopt( pxContext->xSocket,
                                               lLevel,
                                               lOptionName,
                                               &xTimeout,
                                               xOptionLength );
                break;

            default:
                lStatus = FreeRTOS_setsockopt( pxContext->xSocket,
                                               lLevel,
                                               lOptionName,
                                               pvOptionValue,
                                               xOptionLength );
                break;
        }
    }
    else
    {
        lStatus = SOCKETS_EINVAL;
    }

    return lStatus;
}
/*-----------------------------------------------------------*/

int32_t SOCKETS_Shutdown( Socket_t xSocket,
                          uint32_t ulHow )
{
    int32_t lReturn;
    SSOCKETContextPtr_t pxContext = ( SSOCKETContextPtr_t ) xSocket; /*lint !e9087 cast used for portability. */

    if( ( xSocket != SOCKETS_INVALID_SOCKET ) && ( xSocket != NULL ) )
    {
        lReturn = FreeRTOS_shutdown( pxContext->xSocket, ( BaseType_t ) ulHow );
    }
    else
    {
        lReturn = SOCKETS_EINVAL;
    }

    return lReturn;
}
/*-----------------------------------------------------------*/

Socket_t SOCKETS_Socket( int32_t lDomain,
                         int32_t lType,
                         int32_t lProtocol )
{
    SSOCKETContextPtr_t pxContext = NULL;
    Socket_t xSocket;

    /* Ensure that only supported values are supplied. */
    configASSERT( lDomain == SOCKETS_AF_INET );
    configASSERT( lType == SOCKETS_SOCK_STREAM );
    configASSERT( lProtocol == SOCKETS_IPPROTO_TCP );

    /* Create the wrapped socket. */
    xSocket = FreeRTOS_socket( lDomain, lType, lProtocol );

    if( xSocket != FREERTOS_INVALID_SOCKET )
    {
        /* Allocate the internal context structure. */
        if( NULL == ( pxContext = pvPortMalloc( sizeof( SSOCKETContext_t ) ) ) )
        {
            /* Need to close socket. */
            ( void ) FreeRTOS_closesocket( xSocket );
            pxContext = ( SSOCKETContextPtr_t ) SOCKETS_INVALID_SOCKET;
        }
        else
        {
            memset( pxContext, 0, sizeof( SSOCKETContext_t ) );
            pxContext->xSocket = xSocket;
        }
    }
    else
    {
        pxContext = ( SSOCKETContextPtr_t ) SOCKETS_INVALID_SOCKET;
    }

    return ( Socket_t ) pxContext;
}
/*-----------------------------------------------------------*/

BaseType_t SOCKETS_Init( void )
{
    /* Empty initialization for this port. */
    return pdPASS;
}
/*-----------------------------------------------------------*/

/**
 * @brief Create a static PKCS #11 crypto session handle to share across socket
 * and FreeRTOS+TCP threads. Assume that two or more threads may race to be the
 * first to initialize the static and handle that case accordingly.
 */
static CK_RV prvSocketsGetCryptoSession( SemaphoreHandle_t * pxSessionLock,
                                         CK_SESSION_HANDLE * pxSession,
                                         CK_FUNCTION_LIST_PTR_PTR ppxFunctionList )
{
    CK_RV xResult = CKR_OK;
    CK_C_GetFunctionList pxCkGetFunctionList = NULL;
    static CK_SESSION_HANDLE xPkcs11Session = 0;
    static CK_FUNCTION_LIST_PTR pxPkcs11FunctionList = NULL;
    static StaticSemaphore_t xStaticSemaphore;
    static SemaphoreHandle_t xSessionLock = NULL;
    CK_ULONG ulCount = 0;
    CK_SLOT_ID * pxSlotIds = NULL;

    /* Check if one-time initialization of the lock is needed.*/
    portENTER_CRITICAL();

    if( NULL == xSessionLock )
    {
        xSessionLock = xSemaphoreCreateMutexStatic( &xStaticSemaphore );
    }

    *pxSessionLock = xSessionLock;
    portEXIT_CRITICAL();

    /* Check if one-time initialization of the crypto handle is needed.*/
    xSemaphoreTake( xSessionLock, portMAX_DELAY );

    if( 0 == xPkcs11Session )
    {
        /* One-time initialization. */

        /* Ensure that the PKCS#11 module is initialized. We don't keep the
         * scheduler stopped here, since we don't want to make assumptions about hardware
         * requirements for accessing a crypto module. */

        pxCkGetFunctionList = C_GetFunctionList;
        xResult = pxCkGetFunctionList( &pxPkcs11FunctionList );

        if( CKR_OK == xResult )
        {
            xResult = xInitializePkcs11Token();
        }

        /* Get the crypto token slot count. */
        if( CKR_OK == xResult )
        {
            xResult = pxPkcs11FunctionList->C_GetSlotList( CK_TRUE,
                                                           NULL,
                                                           &ulCount );
        }

        /* Allocate memory to store the token slots. */
        if( CKR_OK == xResult )
        {
            pxSlotIds = ( CK_SLOT_ID * ) pvPortMalloc( sizeof( CK_SLOT_ID ) * ulCount );

            if( NULL == pxSlotIds )
            {
                xResult = CKR_HOST_MEMORY;
            }
        }

        /* Get all of the available private key slot identities. */
        if( CKR_OK == xResult )
        {
            xResult = pxPkcs11FunctionList->C_GetSlotList( CK_TRUE,
                                                           pxSlotIds,
                                                           &ulCount );
        }

        /* Start a session with the PKCS#11 module. */
        if( CKR_OK == xResult )
        {
            xResult = pxPkcs11FunctionList->C_OpenSession( pxSlotIds[ 0 ],
                                                           CKF_SERIAL_SESSION,
                                                           NULL,
                                                           NULL,
                                                           &xPkcs11Session );
        }
    }

    *pxSession = xPkcs11Session;
    *ppxFunctionList = pxPkcs11FunctionList;
    xSemaphoreGive( xSessionLock );

    if( NULL != pxSlotIds )
    {
        vPortFree( pxSlotIds );
    }

    return xResult;
}
/*-----------------------------------------------------------*/

/*uint32_t ulRand( void ) __attribute__ ((deprecated)) */
#if 0

/* Why is this function between #if 0? Because the function ulRand()
 * is now reprecated. The function is still shown here for users who
 * are looking for it.
 * It is deprecated because a return value of 0 would mean that the
 * RND module is broken. This was confusing because 0 is also a
 * possible valid random number. */
    uint32_t ulRand( void )
    {
        uint32_t ulNumber = 0uL;
        BaseType_t xResult = xApplicationGetRandomNumber( &( ulNumber ) );

        if( xResult == pdFALSE )
        {
            ulNumber = 0uL;
        }
        else
        {
            /* Function succeeded, a random number will be returned. */
        }

        return ulNumber;
    }
#endif /* 0 */

BaseType_t xApplicationGetRandomNumber( uint32_t * pulNumber )
{
    CK_RV xResult = 0;
    SemaphoreHandle_t xSessionLock = NULL;
    CK_SESSION_HANDLE xPkcs11Session = 0;
    CK_FUNCTION_LIST_PTR pxPkcs11FunctionList = NULL;
    uint32_t ulRandomValue = 0;
    BaseType_t xReturn; /* Return pdTRUE if successful */

    xResult = prvSocketsGetCryptoSession( &xSessionLock,
                                          &xPkcs11Session,
                                          &pxPkcs11FunctionList );

    if( 0 == xResult )
    {
        /* Request a sequence of cryptographically random byte values using
         * PKCS#11. */
        xResult = pxPkcs11FunctionList->C_GenerateRandom( xPkcs11Session,
                                                          ( CK_BYTE_PTR ) &ulRandomValue,
                                                          sizeof( ulRandomValue ) );
    }

    /* Check if any of the API calls failed. */
    if( 0 == xResult )
    {
        xReturn = pdTRUE;
        *( pulNumber ) = ulRandomValue;
    }
    else
    {
        xReturn = pdFALSE;
        *( pulNumber ) = 0uL;
    }

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

/**
 * @brief Generate a TCP Initial Sequence Number that is reasonably difficult
 * to predict, per https://tools.ietf.org/html/rfc6528.
 */
uint32_t ulApplicationGetNextSequenceNumber( uint32_t ulSourceAddress,
                                             uint16_t usSourcePort,
                                             uint32_t ulDestinationAddress,
                                             uint16_t usDestinationPort )
{
    CK_RV xResult = CKR_OK;
    SemaphoreHandle_t xSessionLock = NULL;
    CK_SESSION_HANDLE xPkcs11Session = 0;
    CK_FUNCTION_LIST_PTR pxPkcs11FunctionList = NULL;
    CK_MECHANISM xMechSha256 = { 0 };
    uint8_t ucSha256Result[ cryptoSHA256_DIGEST_BYTES ];
    CK_ULONG ulLength = sizeof( ucSha256Result );
    uint32_t ulNextSequenceNumber = 0;
    static uint64_t ullKey;
    static CK_BBOOL xKeyIsInitialized = CK_FALSE;

    /* Acquire a crypto session handle. */
    xResult = prvSocketsGetCryptoSession( &xSessionLock,
                                          &xPkcs11Session,
                                          &pxPkcs11FunctionList );

    if( CKR_OK == xResult )
    {
        xSemaphoreTake( xSessionLock, portMAX_DELAY );

        if( CK_FALSE == xKeyIsInitialized )
        {
            /* One-time initialization, per boot, of the random seed. */
            xResult = pxPkcs11FunctionList->C_GenerateRandom( xPkcs11Session,
                                                              ( CK_BYTE_PTR ) &ullKey,
                                                              sizeof( ullKey ) );

            if( xResult == CKR_OK )
            {
                xKeyIsInitialized = CK_TRUE;
            }
        }

        xSemaphoreGive( xSessionLock );
    }

    /* Lock the shared crypto session. */
    xSemaphoreTake( xSessionLock, portMAX_DELAY );

    /* Start a hash. */
    if( CKR_OK == xResult )
    {
        xMechSha256.mechanism = CKM_SHA256;
        xResult = pxPkcs11FunctionList->C_DigestInit( xPkcs11Session, &xMechSha256 );
    }

    /* Hash the seed. */
    if( CKR_OK == xResult )
    {
        xResult = pxPkcs11FunctionList->C_DigestUpdate( xPkcs11Session,
                                                        ( CK_BYTE_PTR ) &ullKey,
                                                        sizeof( ullKey ) );
    }

    /* Hash the source address. */
    if( CKR_OK == xResult )
    {
        xResult = pxPkcs11FunctionList->C_DigestUpdate( xPkcs11Session,
                                                        ( CK_BYTE_PTR ) &ulSourceAddress,
                                                        sizeof( ulSourceAddress ) );
    }

    /* Hash the source port. */
    if( CKR_OK == xResult )
    {
        xResult = pxPkcs11FunctionList->C_DigestUpdate( xPkcs11Session,
                                                        ( CK_BYTE_PTR ) &usSourcePort,
                                                        sizeof( usSourcePort ) );
    }

    /* Hash the destination address. */
    if( CKR_OK == xResult )
    {
        xResult = pxPkcs11FunctionList->C_DigestUpdate( xPkcs11Session,
                                                        ( CK_BYTE_PTR ) &ulDestinationAddress,
                                                        sizeof( ulDestinationAddress ) );
    }

    /* Hash the destination port. */
    if( CKR_OK == xResult )
    {
        xResult = pxPkcs11FunctionList->C_DigestUpdate( xPkcs11Session,
                                                        ( CK_BYTE_PTR ) &usDestinationPort,
                                                        sizeof( usDestinationPort ) );
    }

    /* Get the hash. */
    if( CKR_OK == xResult )
    {
        xResult = pxPkcs11FunctionList->C_DigestFinal( xPkcs11Session,
                                                       ucSha256Result,
                                                       &ulLength );
    }

    xSemaphoreGive( xSessionLock );

    /* Use the first four bytes of the hash result as the starting point for
     * all initial sequence numbers for connections based on the input 4-tuple. */
    if( CKR_OK == xResult )
    {
        memcpy( &ulNextSequenceNumber,
                ucSha256Result,
                sizeof( ulNextSequenceNumber ) );

        /* Add the tick count of four-tick intervals. In theory, per the RFC
         * (see above), this approach still allows server equipment to optimize
         * handling of connections from the same device that haven't fully timed out. */
        ulNextSequenceNumber += xTaskGetTickCount() / 4;
    }

    return ulNextSequenceNumber;
}
/*-----------------------------------------------------------*/
