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

/*
 * This file is part of the demo project that shows use of the coreSNTP library to create
 * an SNTP client (daemon) task for synchronizing system time with internet time and
 * maintaining Coordinated Universal Time (UTC) (or wall-clock time) in the system.
 *
 * This file contains the SNTP client (daemon) task as well as functionality for
 * maintaining wall-clock or UTC time in RAM. The SNTP client periodically synchronizes
 * system clock with SNTP/NTP server(s). Any other task running an application in the
 * system can query the system time. For an example of an application task querying time
 * from the system, refer to the SampleAppTask.c file in this project.
 *
 * This demo shows how the coreSNTP library can be used to communicate with SNTP/NTP
 * servers in a mutually authenticated through the use of symmetric-key based AES-128-CMAC
 * algorithm. To run this demo with an SNTP/NTP server in authenticated mode, the AES-128-CMAC
 * symmetric key needs to be pre-shared between the client (i.e. this demo) and the server.
 *
 * !!!Note!!!:
 * Even though this demo shows the use of AES-128-CMAC, a symmetric-key cryptographic based
 * solution, for authenticating SNTP communication between the demo (SNTP client) and
 * SNTP/NTP server, we instead RECOMMEND that production devices use the most secure authentication
 * mechanism alternative available with the Network Time Security (NTS) protocol, an asymmetric-key
 * cryptographic protocol. For more information, refer to the NTS specification here:
 * https://datatracker.ietf.org/doc/html/rfc8915
 */

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

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

/* Demo include. */
#include "common_demo_include.h"

/* SNTP library include. */
#include "core_sntp_client.h"

/* Synchronization primitive include. */
#include "semphr.h"

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

/* PKCS11 includes. */
#include "core_pki_utils.h"
#include "core_pkcs11_config.h"
#include "core_pkcs11.h"

/* Backoff Algorithm include. */
#include "backoff_algorithm.h"

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

/* Compile time error for undefined configs. */

#ifndef democonfigLIST_OF_TIME_SERVERS
    #error "Define the democonfigLIST_OF_TIME_SERVERS config by following the instructions in demo_config.h file."
#endif

#ifndef democonfigLIST_OF_AUTHENTICATION_SYMMETRIC_KEYS
    #error "Define the democonfigLIST_OF_AUTHENTICATION_SYMMETRIC_KEYS config by following the instructions in demo_config.h file."
#endif

#ifndef democonfigLIST_OF_AUTHENTICATION_KEY_IDS
    #error "Define the democonfigLIST_OF_AUTHENTICATION_KEY_IDS config by following the instructions in demo_config.h file."
#endif

#ifndef democonfigSNTP_CLIENT_POLLING_INTERVAL_SECONDS
    #error "Define the democonfigSNTP_CLIENT_POLLING_INTERVAL_SECONDS config by following instructions in demo_config.h file."
#endif

#ifndef democonfigSYSTEM_START_YEAR
    #error "Define the democonfigSYSTEM_START_YEAR config by following instructions in demo_config.h file."
#endif

/*-----------------------------------------------------------*/
/* Default values for timeout configurations . */

#ifndef democonfigSERVER_RESPONSE_TIMEOUT_MS
    #define democonfigSERVER_RESPONSE_TIMEOUT_MS    ( 5000 )
#endif

#ifndef democonfigSEND_TIME_REQUEST_TIMEOUT_MS
    #define democonfigSEND_TIME_REQUEST_TIMEOUT_MS    ( 50 )
#endif

#ifndef democonfigRECEIVE_SERVER_RESPONSE_BLOCK_TIME_MS
    #define democonfigRECEIVE_SERVER_RESPONSE_BLOCK_TIME_MS    ( 200 )
#endif

/**
 * @brief The size for network buffer that is allocated for initializing the coreSNTP library in the
 * demo.
 *
 * @note The size of the buffer MUST be large enough to hold an entire SNTP packet, which includes the standard SNTP
 * packet data of 48 bytes and authentication data for security mechanism, if used, in communication with time server.
 */
#define SNTP_CONTEXT_NETWORK_BUFFER_SIZE        ( SNTP_PACKET_BASE_SIZE )

/**
 * @brief The constant for storing the number of milliseconds per FreeRTOS tick in the system.
 * @note This value represents the time duration per tick from the perspective of the
 * of Windows Simulator based FreeRTOS system that carries lagging clock drift in relation to
 * internet time or UTC time. Thus, the actual time duration value per tick of the system will be
 * larger from the perspective of internet time.
 */
#define MILLISECONDS_PER_TICK                   ( 1000 / configTICK_RATE_HZ )

/**
 * @brief The fixed size of the key for the AES-128-CMAC algorithm used for authenticating communication
 * between the time server and the client.
 */
#define AES_CMAC_AUTHENTICATION_KEY_SIZE        ( 16 )

/**
 * @brief The size of the "Key Identifier" field in the SNTP packet when symmetric key authentication mode as
 * security mechanism in communicating with time server.
 *
 * The "Key Identifier" field appears immediately after the 48 bytes of standard SNTP packet created by the coreSNTP
 * library. For more information, refer to the SNTPv4 specification: https://datatracker.ietf.org/doc/html/rfc4330#page-8
 *
 * @note This demo uses the "Key Identifier" field to communicate with time servers that support authentication mechanism.
 * This field is stored with the Key ID of the AES-128-CMAC based authentication key stored in the time server.
 */
#define SNTP_PACKET_SYMMETRIC_KEY_ID_LENGTH     4

/**
 * @brief The offset for the starting byte of the "Key Identifier" field in an SNTPv4/NTPv4 packet.
 * This field is only used when symmetric key authentication mode is used for communicating with time server.
 *
 * For more information of the SNTP packet format, refer to the SNTPv4 specification
 * https://datatracker.ietf.org/doc/html/rfc4330#page-8
 */
#define SNTP_PACKET_SYMMETRIC_KEY_ID_OFFSET     SNTP_PACKET_BASE_SIZE

/**
 * @brief The total size of an SNTP packet (which remains same for both client request and server response in SNTP communication)
 * when using symmetric key based authentication mechanism.
 *
 * This value includes size of the 48 bytes of standard SNTP packet, and the "Key Identifier" and "Message Digest" fields
 * that are used for authentication information in SNTP communication between client and server.
 *
 * For more information of the SNTP packet format, refer to the SNTPv4 specification
 * https://datatracker.ietf.org/doc/html/rfc4330#page-8
 */
#define SNTP_PACKET_AUTHENTICATED_MODE_SIZE     ( SNTP_PACKET_BASE_SIZE + SNTP_PACKET_SYMMETRIC_KEY_ID_LENGTH + pkcs11AES_CMAC_SIGNATURE_LENGTH )

/**
 * @brief The maximum poll period that the SNTP client can use as back-off on receiving a rejection from a time server.
 *
 * @note This demo performs back-off in polling rate from time server ONLY for the case when a single time server being
 * is configured through the democonfigLIST_OF_TIME_SERVERS macro.
 * This is because when more than one time server is configured, the coreSNTP library automatically handles the case
 * of server rejection of time request by rotating to the next configured server for subsequent time polling requests.
 */
#define SNTP_DEMO_POLL_MAX_BACKOFF_DELAY_SEC    UINT16_MAX

/**
 * @brief The maximum number of times of retrying time requests at exponentially backed-off polling frequency
 * from a server that rejects time requests.
 *
 * @note This macro is only relevant for the case when a single time server is configured in
 * the demo through, democonfigLIST_OF_TIME_SERVERS.
 */
#define SNTP_DEMO_MAX_SERVER_BACKOFF_RETRIES    10

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

/**
 * @brief The definition of the @ref NetworkContext_t structure for the demo.
 * The structure wraps a FreeRTOS+TCP socket that is used for UDP communication
 * with time servers.
 *
 * @note The context is used in the @ref UdpTransportInterface_t interface required
 * by the coreSNTP library.
 */
struct NetworkContext
{
    Socket_t socket;
};

/**
 * @brief The definition of the @ref SntpAuthContext_t structure for the demo.
 * This structure represents the symmetric key for the AES-128-CMAC algorithm based
 * authentication mechanism shown in the demo for securing SNTP communication
 * between client and time server.
 *
 * @note The context is used in the @ref SntpAuthInterface_t interface required
 * by the coreSNTP library for enabling authentication.
 */
struct SntpAuthContext
{
    const char * pServer;
    int32_t keyId;
    uint8_t pAuthKey[ AES_CMAC_AUTHENTICATION_KEY_SIZE ];
};

/**
 * @brief Structure aggregating state variables for RAM-based wall-clock time
 * in Coordinated Universal Time (UTC) for system.
 *
 * @note This demo uses the following mathematical model to represent current
 * time in RAM.
 *
 *  BaseTime = Time set at boot or the last synchronized time
 *  Slew Rate = Number of milliseconds to adjust per system time second
 *  No. of ticks since last SNTP sync = Current FreeRTOS Tick Count -
 *                                      Tick count at last SNTP sync
 *
 *  Time Elapsed since last SNTP sync = No. of ticks since last SNTP sync
 *                                                    x
 *                                      Number of milliseconds per FreeRTOS tick
 *
 *  Slew Adjustment = Slew Rate x Time Elapsed since last SNTP sync
 *
 *  Current Time = Base Time +
 *                 Time Elapsed since last SNTP sync +
 *                 Slew Adjustment
 */
typedef struct SystemClock
{
    UTCTime_t baseTime;
    TickType_t lastSyncTickCount;
    uint32_t pollPeriod;
    uint64_t slewRate; /* Milliseconds/Seconds */
    bool firstTimeSyncDone;
} SystemClock_t;

/**
 * @brief Shared global system clock object for representing UTC/wall-clock
 * time in system.
 */
static SystemClock_t systemClock;

/**
 * @brief Mutex for protecting access to the shared memory of the
 * system clock parameters.
 */
static SemaphoreHandle_t xMutex = NULL;

/*
 * @brief Stores the configured time servers in an array.
 */
static const char * pTimeServers[] = { democonfigLIST_OF_TIME_SERVERS };
const size_t numOfServers = sizeof( pTimeServers ) / sizeof( char * );

/**
 * @brief Stores the list of configured AES-128-CMAC symmetric keys for authentication
 * mechanism for corresponding time servers in democonfigLIST_OF_TIME_SERVERS.
 */
static const char * pAESCMACAuthKeys[] = { democonfigLIST_OF_AUTHENTICATION_SYMMETRIC_KEYS };

/**
 * @brief Stores list of Key IDs corresponding to the authentication keys configured
 * in democonfigLIST_OF_TIME_SERVERS.
 */
static const int32_t pAuthKeyIds[] = { democonfigLIST_OF_AUTHENTICATION_KEY_IDS };

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

/**
 * @brief Utility function to convert the passed year to UNIX time representation
 * of seconds since 1st Jan 1970 00h:00m:00s seconds to 1st Jan 00h:00m:00s of the
 * the passed year.
 *
 * This utility does account for leap years.
 *
 * @param[in] The year to translate.
 */
static uint32_t translateYearToUnixSeconds( uint16_t year );

/**
 * @brief Calculates the current time in the system.
 * It calculates the current time as:
 *
 *   BaseTime = Time set at device boot or the last synchronized time
 *   SlewRate = Number of milliseconds to adjust per system time second
 *
 *   Current Time = Base Time +
 *                  Time since last SNTP Synchronization +
 *                  Slew Adjustment (if slew rate > 0) for time period since
 *                  last SNTP synchronization
 *
 * @param[in] pBaseTime The base time in the system clock parameters.
 * @param[in] lastSyncTickCount The tick count at the last time synchronization
 * with a time server.
 * @param[in] slewRate The slew rate as seconds of clock adjustment per FreeRTOS
 * system time second.
 * @param[out] pCurrentTime This will be populated with the calculated current
 * UTC time in the system.
 */
static void calculateCurrentTime( UTCTime_t * pBaseTime,
                                  TickType_t lastSyncTickCount,
                                  uint64_t slewRate,
                                  UTCTime_t * pCurrentTime );

/**
 * @brief Initializes the SNTP context for the SNTP client task.
 * This function generates an array of the configured time servers, creates a FreeRTOS UDP socket
 * for the UDP transport interface and initializes the passed SNTP context by calling the
 * Sntp_Init() API of the coreSNTP library.
 *
 * @param[in, out] pContext The memory for the SNTP client context that will be initialized with
 * Sntp_Init API.
 * @param[in] pTimeServers The list of time servers configured through the democonfigLIST_OF_TIME_SERVERS
 * macro in demo_config.h.
 * @param[in] numOfServers The number of time servers configured in democonfigLIST_OF_TIME_SERVERS.
 * @param[in] pContextBuffer The allocated network buffer that will be initialized in the SNTP context.
 * @param[in] pUdpContext The memory for the network context for the UDP transport interface that will
 * be passed to the SNTP client context. This will be filled with a UDP context created by this function.
 *
 * @return Returns `true` if initialization of SNTP client context is successful; otherwise `false`.
 */
static bool initializeSntpClient( SntpContext_t * pContext,
                                  const char ** pTimeServers,
                                  size_t numOfServers,
                                  uint8_t * pContextBuffer,
                                  size_t contextBufferSize,
                                  NetworkContext_t * pUdpContext,
                                  SntpAuthContext_t * pAuthContext );

/**
 * @brief The demo implementation of the @ref SntpResolveDns_t interface to
 * allow the coreSNTP library to resolve DNS name of a time server being
 * used for requesting time from.
 *
 * @param[in] pTimeServer The time-server whose IPv4 address is to be resolved.
 * @param[out] pIpV4Addr This is filled with the resolved IPv4 address of
 * @p pTimeServer.
 */
static bool resolveDns( const SntpServerInfo_t * pServerAddr,
                        uint32_t * pIpV4Addr );

/**
 * @brief The demo implementation of the @ref UdpTransportSendTo_t function
 * of the UDP transport interface to allow the coreSNTP library to perform
 * network operation of sending time request over UDP to the provided time server.
 *
 * @param[in] pNetworkContext This will be the NetworkContext_t context object
 * representing the FreeRTOS UDP socket to use for network send operation.
 * @param[in] serverAddr The IPv4 address of the time server.
 * @param[in] serverPort The port of the server to send data to.
 * @param[in] pBuffer The demo-supplied network buffer of size, SNTP_CONTEXT_NETWORK_BUFFER_SIZE,
 * containing the data to send over the network.
 * @param[in] bytesToSend The size of data in @p pBuffer to send.
 *
 * @return Returns the return code of FreeRTOS UDP send API, FreeRTOS_sendto, which returns
 * 0 for error or timeout OR the number of bytes sent over the network.
 */
static int32_t UdpTransport_Send( NetworkContext_t * pNetworkContext,
                                  uint32_t serverAddr,
                                  uint16_t serverPort,
                                  const void * pBuffer,
                                  uint16_t bytesToSend );

/**
 * @brief The demo implementation of the @ref UdpTransportRecvFrom_t function
 * of the UDP transport interface to allow the coreSNTP library to perform
 * network operation of reading expected time response over UDP from
 * provided time server.
 *
 * @param[in] pNetworkContext This will be the NetworkContext_t context object
 * representing the FreeRTOS UDP socket to use for network read operation.
 * @param[in] pTimeServer The IPv4 address of the time server to receive data from.
 * @param[in] serverPort The port of the server to receive data from.
 * @param[out] pBuffer The demo-supplied network buffer of size, SNTP_CONTEXT_NETWORK_BUFFER_SIZE,
 * that will be filled with data received from the network.
 * @param[in] bytesToRecv The expected number of bytes to receive from the network
 * for the server response server.
 *
 * @return Returns one of the following:
 * - 0 for timeout in receiving any data from the network (by translating the
 * -pdFREERTOS_ERRNO_EWOULDBLOCK return code from FreeRTOS_recvfrom API )
 *                         OR
 * - The number of bytes read from the network.
 */
static int32_t UdpTransport_Recv( NetworkContext_t * pNetworkContext,
                                  uint32_t serverAddr,
                                  uint16_t serverPort,
                                  void * pBuffer,
                                  uint16_t bytesToRecv );

/**
 * @brief The demo implementation of the @ref SntpGetTime_t interface
 * for obtaining system clock time for the coreSNTP library.
 *
 * @param[out] pTime This will be populated with the current time from
 * the system.
 */
static void sntpClient_GetTime( SntpTimestamp_t * pCurrentTime );

/**
 * @brief The demo implementation of the @ref SntpSetTime_t interface
 * for correcting the system clock time based on the  time received
 * from the server response and the clock-offset value calculated by
 * the coreSNTP library.
 *
 * @note This demo uses a combination of "step" AND "slew" methodology
 * for system clock correction.
 * 1. "Step" correction is ALWAYS used to immediately correct the system clock
 *    to match server time on every successful time synchronization with a
 *    time server (that occurs periodically on the poll interval gaps).
 *
 * 2. "Slew" correction approach is used for compensating system clock drift
 *    during the poll interval period between time synchronization attempts with
 *    time server(s) when latest time server is not known. The "slew rate" is
 *    calculated ONLY once on the occasion of the second successful time
 *    synchronization with a time server. This is because the demo initializes
 *    system time with (the first second of) the democonfigSYSTEM_START_YEAR
 *    configuration, and thus, the the actual system clock drift over a period
 *    of time can be calculated only AFTER the demo system time has been synchronized
 *    with server time once. Thus, after the first time period of poll interval has
 *    transpired, the system clock drift is calculated correctly on the subsequent
 *    successful time synchronization with a time server.
 *
 * @note The above system clock correction algorithm is just one example of a correction
 * approach. It can be modified to suit your application needs. For example, your
 * application can use ONLY the "step" correction methodology for simplicity of system clock
 * time calculation logic if the application is not sensitive to abrupt time changes
 * (that occur at the instances of periodic time synchronization attempts). In such a case,
 * the Sntp_CalculatePollInterval() API of coreSNTP library can be used to calculate
 * the optimum time polling period for your application based on the factors of your
 * system's clock drift rate and the maximum clock drift tolerable by your application.
 *
 *
 * @param[in] pTimeServer The time server from whom the time has been received.
 * @param[in] pServerTime The most recent time of the server, @p pTimeServer, sent in its
 * time response.
 * @param[in] clockOffsetMs The value, in milliseconds, of system clock offset relative
 * to the server time calculated by the coreSNTP library. If the value is positive, then
 * the system is BEHIND the server time, and a "slew" clock correction approach is used in
 * this demo. If the value is negative, then the system time is AHEAD of the server time,
 * and a "step" clock correction approach is used in this demo.
 * @param[in] leapSecondInfo This indicates whether there is an upcoming leap second insertion
 * or deletion (according to astronomical time) the last minute of the end of the month that the
 * system time needs to adjust for. Leap second adjustment is valuable for applications that
 * require non-abrupt increment of time for use cases like logging. This demo DOES NOT showcase
 * leap second adjustment in system clock.
 */
static void sntpClient_SetTime( const SntpServerInfo_t * pTimeServer,
                                const SntpTimestamp_t * pServerTime,
                                int64_t clockOffsetMs,
                                SntpLeapSecondInfo_t leapSecondInfo );

/**
 * @brief Utility function to create a PKCS11 session and a PKCS11 object, and obtain the PKCS11
 * global function list for performing 128 bit AES-CMAC operations.
 * This function is called by the definitions of both the authentication interface functions,
 * @ref SntpGenerateAuthCode_t and @ref SntpValidateServerAuth_t, in this demo for the
 * AES-CMAC operations.
 *
 * @param[in] pAuthContext The context representing the symmetric key for the AES-CMAC operation.
 * @param[out] pPkcs11Session This is populated with the created PKCS11 session.
 * @param[out] pFunctionList This is populated with the function list obtained from the created
 * PKCS11 session.
 * @param[out] pCmacKey This is populated with the created PKCS11 object handle for AES-CMAC
 * operations.
 *
 * @return The return status code of PKCS11 API calls.
 */
static CK_RV setupPkcs11ObjectForAesCmac( const SntpAuthContext_t * pAuthContext,
                                          CK_SESSION_HANDLE * pPkcs11Session,
                                          CK_FUNCTION_LIST_PTR * pFunctionList,
                                          CK_OBJECT_HANDLE * pCmacKey );

/**
 * @brief Utility function for filling the authentication context with the time server and
 * its associated authentication key information from the democonfigLIST_OF_AUTHENTICATION_SYMMETRIC_KEYS
 * and democonfigLIST_OF_AUTHENTICATION_KEY_IDS configuration lists.
 *
 * The authentication context represents the server and its authentication key information being
 * used by the SNTP client at a time for time query. This function is called to update the context
 * at the time of SNTP client initialization as well as whenever the SNTP client rotates the time
 * server of use.
 *
 * @param[in] pServer The time server whose information is filled in the context.
 * @param[out] pAuthContext The authentication context to update with information about the @p pServer.
 */
static void populateAuthContextForServer( const char * pServer,
                                          SntpAuthContext_t * pAuthContext );

/**
 * @brief The demo implementation of the @ref SntpGenerateClientAuth_t function of the authentication
 * interface required by the coreSNTP library to execute functionality of generating client-side
 * message authentication code and appending it to the time request before sending to a time server.
 *
 * This function first determines whether the passed time server has an authentication key configured
 * in the demo. If the time server supports authentication, the function utilizes the corePKCS11 library
 * to generate the client authentication code as a signature using the AES-128-CMAC algorithm, and append
 * it to the passed SNTP request packet buffer, @p pRequestBuffer.
 *
 * @note If the time server supports authentication, this function writes the "Key Identifier" and "Message
 * Digest" fields of an SNTP packet.
 *
 * @param[in, out] pAuthContext The authentication context representing the time server and its authentication
 * credentials. If the coreSNTP library rotated the time server of use, then this function updates the context
 * to carry authentication information for the new server.
 * @param[in] pTimeServer The current time server being used for sending time queries by the SNTP client.
 * This is used to determine whether the @p pAuthContext carries stale information of a previously used server,
 * and thus, needs to be updated with information of the current server, @p pTimeServer.
 * @param[in, out] pRequestBuffer The buffer representing the SNTP request packet, which is already populated with
 * the standard 48 bytes of packet data. If the time server supports authentication, then the 48 bytes of data
 * and the authentication key are used to generated AES-128-CMAC signature, and the "Key Identifier" and "Message
 * Digest" fields of the packet are filled in the buffer.
 * @param[in] bufferSize The total buffer size of the @p pRequestBuffer for the SNTP request packet.
 * @param[out] pAuthCodeSize This will be populated with the total bytes for authentication data written to the
 * @p pRequestBuffer when
 *
 * @return Returns one of the following:
 * - SntpSuccess if EITHER no authentication key information has been configured for the time
 * server, and thus, no AES-CMAC operation was performed OR the time server supports authentication and
 * the corePKCS11 operations are successful in generating and appending authentication information to the
 * @p pRequestBuffer.
 * - SntpErrorAuthFailure if there is failure in PKCS#11 operations in generating and appending the AES-128-CMAC
 * signature as the authentication code to the @p pRequestBuffer.
 */
static SntpStatus_t addClientAuthCode( SntpAuthContext_t * pAuthContext,
                                       const SntpServerInfo_t * pTimeServer,
                                       void * pRequestBuffer,
                                       uint16_t bufferSize,
                                       uint16_t * pAuthCodeSize );


/**
 * @brief The demo implementation of the @ref SntpValidateServerAuth_t function of the authentication
 * interface required by the coreSNTP library to execute validation of server as the source of the
 * received SNTP response by verifying the authentication information present in the packet.
 *
 * This function first checks whether the passed time server has authentication key information configured
 * in the demo to determine if the server supports authentication. If the time server supports authentication,
 * the function utilizes the corePKCS11 library to verify the AES-128-CMAC signature in the packet, @p pResponseData
 * that represents the server authentication code.
 *
 * @param[in] pAuthContext The authentication context representing the time server of use and its authentication
 * credentials.
 * @param[in] pTimeServer The current time server of use from which the response data, @p pResponseData has been
 * received by the SNTP client. This SHOULD match the time server information carried by the authentication context.
 * @param[in] pResponseData The buffer representing the SNTP response packet, received from the server, @p pTimeServer,
 * which contains the server authentication code, if the server supports authentication. The authentication code, if present,
 * is verified using corePKCS11 to be the expected AES-128-CMAC signature using the standard 48 bytes of SNTP packet data
 * present in the buffer and the secret symmetric key configured for the server.
 * @param[in] responseSize The total buffer size of the @p pResponseData for the SNTP response packet.
 *
 * @return Returns one of the following:
 * - SntpSuccess if EITHER no authentication key information has been configured for the time server, and thus,
 * no AES-CMAC validation operation is performed OR the time server supports authentication and the authentication
 * code has been successfully validated @p pBuffer.
 * - SntpErrorAuthFailure if there is internal failure in PKCS#11 operations in validating the server authentication code as
 * as the AES-128-CMAC for the information present in the response packet, @p pResponseData.
 * - SntpServerNotAuthenticated if the server is not validated from the response due to the authentication code not matching
 * the expected AES-128-CMAC signature.
 */
static SntpStatus_t validateServerAuth( SntpAuthContext_t * pAuthContext,
                                        const SntpServerInfo_t * pTimeServer,
                                        const void * pResponseData,
                                        size_t responseSize );

/**
 * @brief Generates a random number using PKCS#11.
 *
 * @note It is RECOMMENDED to generate a random number for the call to Sntp_SendTimeRequest API
 * of coreSNTP library to protect against server response spoofing attacks from "network off-path"
 * attackers.
 *
 * @return The generated random number.
 */
static uint32_t generateRandomNumber();

/**
 * @brief Utility to create a new FreeRTOS UDP socket and bind a random
 * port to it.
 * A random port is used for the created UDP socket as a protection mechanism
 * against spoofing attacks from malicious actors that are off the network
 * path of the client-server communication.
 *
 * @param[out] This will be populated with a new FreeRTOS UDP socket
 * that is bound to a random port.
 *
 * @return Returns #true for successful creation of UDP socket; #false
 * otherwise for failure.
 */
static bool createUdpSocket( Socket_t * pSocket );

/**
 * @brief Utility to close the passed FreeRTOS UDP socket.
 *
 * @param pSocket The UDP socket to close.
 */
static void closeUdpSocket( Socket_t * pSocket );

/**
 * @brief Utility to calculate new poll period with exponential backoff and jitter
 * algorithm.
 *
 * @note The demo applies time polling frequency backoff only when a single time server
 * is configured, through the democonfigLIST_OF_SERVERS macro, and the single server
 * rejects time requests.
 *
 * @param[in, out] pContext The context representing the back-off parameters. This
 * context is initialized by the function whenever the caller indicates it with the
 * @p shouldInitializeContext flag.
 * @param[in] shouldInitializeContext Flag to indicate if the passed context should be
 * initialized to start a new sequence of backed-off time request retries.
 * @param[in] minPollPeriod The minimum poll period
 * @param[in] pPollPeriod The new calculated poll period.
 *
 * @return Return #true if a new poll interval is calculated to retry time request
 * from the server; #false otherwise to indicate exhaustion of time request retry attempts
 * with the server.
 */
static bool calculateBackoffForNextPoll( BackoffAlgorithmContext_t * pContext,
                                         bool shouldInitializeContext,
                                         uint32_t minPollPeriod,
                                         uint32_t * pPollPeriod );

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

static uint32_t translateYearToUnixSeconds( uint16_t year )
{
    configASSERT( year >= 1970 );

    uint32_t numOfDaysSince1970 = ( year - 1970 ) * 365;

    /* Calculate the extra days in leap years (for February 29) over the time
    * period from 1st Jan 1970 to 1st Jan of the passed year.
    * By subtracting from the year 1969, the extra day in 1972 is covered. */
    numOfDaysSince1970 += ( ( year - 1969 ) / 4 );

    return( numOfDaysSince1970 * 24 * 3600 );
}

void calculateCurrentTime( UTCTime_t * pBaseTime,
                           TickType_t lastSyncTickCount,
                           uint64_t slewRate,
                           UTCTime_t * pCurrentTime )
{
    uint64_t msElapsedSinceLastSync = 0;
    TickType_t ticksElapsedSinceLastSync = xTaskGetTickCount() - lastSyncTickCount;

    /* Calculate time elapsed since last synchronization according to the number
     * of system ticks passed. */
    msElapsedSinceLastSync = ticksElapsedSinceLastSync * MILLISECONDS_PER_TICK;

    /* If slew rate is set, then apply the slew-based clock adjustment for the elapsed time. */
    if( slewRate > 0 )
    {
        /* Slew Adjustment = Slew Rate ( Milliseconds/seconds )
         *                                      x
         *                   No. of seconds since last synchronization. */
        msElapsedSinceLastSync += slewRate * ( msElapsedSinceLastSync / 1000 );
    }

    /* Set the current UTC time in the output parameter. */
    if( msElapsedSinceLastSync >= 1000 )
    {
        pCurrentTime->secs = pBaseTime->secs + msElapsedSinceLastSync / 1000;
        pCurrentTime->msecs = msElapsedSinceLastSync % 1000;
    }
    else
    {
        pCurrentTime->secs = pBaseTime->secs;
        pCurrentTime->msecs = msElapsedSinceLastSync;
    }
}

/********************** DNS Resolution Interface *******************************/
static bool resolveDns( const SntpServerInfo_t * pServerAddr,
                        uint32_t * pIpV4Addr )
{
    uint32_t resolvedAddr = 0;
    bool status = false;

    resolvedAddr = FreeRTOS_gethostbyname( pServerAddr->pServerName );

    /* Set the output parameter if DNS look up succeeded. */
    if( resolvedAddr != 0 )
    {
        /* DNS Look up succeeded. */
        status = true;

        *pIpV4Addr = FreeRTOS_ntohl( resolvedAddr );

        #if defined( LIBRARY_LOG_LEVEL ) && ( LIBRARY_LOG_LEVEL != LOG_NONE )
            uint8_t stringAddr[ 16 ];
            FreeRTOS_inet_ntoa( resolvedAddr, stringAddr );
            LogInfo( ( "Resolved time server as %s", stringAddr ) );
        #endif
    }

    return status;
}

/********************** UDP Interface definition *******************************/
int32_t UdpTransport_Send( NetworkContext_t * pNetworkContext,
                           uint32_t serverAddr,
                           uint16_t serverPort,
                           const void * pBuffer,
                           uint16_t bytesToSend )
{
    struct freertos_sockaddr destinationAddress;
    int32_t bytesSent;

    #if defined( ipconfigIPv4_BACKWARD_COMPATIBLE ) && ( ipconfigIPv4_BACKWARD_COMPATIBLE == 0 )
    {
        destinationAddress.sin_address.ulIP_IPv4 = FreeRTOS_htonl( serverAddr );
    }
    #else
    {
        destinationAddress.sin_addr = FreeRTOS_htonl( serverAddr );
    }
    #endif /* defined( ipconfigIPv4_BACKWARD_COMPATIBLE ) && ( ipconfigIPv4_BACKWARD_COMPATIBLE == 0 ) */

    destinationAddress.sin_port = FreeRTOS_htons( serverPort );
    destinationAddress.sin_family = FREERTOS_AF_INET;

    /* Send the buffer with ulFlags set to 0, so the FREERTOS_ZERO_COPY bit
     * is clear. */
    bytesSent = FreeRTOS_sendto( /* The socket being send to. */
        pNetworkContext->socket,
        /* The data being sent. */
        pBuffer,
        /* The length of the data being sent. */
        bytesToSend,
        /* ulFlags with the FREERTOS_ZERO_COPY bit clear. */
        0,
        /* Where the data is being sent. */
        &destinationAddress,
        /* Not used but should be set as shown. */
        sizeof( destinationAddress )
        );

    return bytesSent;
}

static int32_t UdpTransport_Recv( NetworkContext_t * pNetworkContext,
                                  uint32_t serverAddr,
                                  uint16_t serverPort,
                                  void * pBuffer,
                                  uint16_t bytesToRecv )
{
    struct freertos_sockaddr sourceAddress;
    int32_t bytesReceived;
    socklen_t addressLength = sizeof( struct freertos_sockaddr );

    /* Receive into the buffer with ulFlags set to 0, so the FREERTOS_ZERO_COPY bit
     * is clear. */
    bytesReceived = FreeRTOS_recvfrom( /* The socket data is being received on. */
        pNetworkContext->socket,

        /* The buffer into which received data will be
         * copied. */
        pBuffer,

        /* The length of the buffer into which data will be
         * copied. */
        bytesToRecv,
        /* ulFlags with the FREERTOS_ZERO_COPY bit clear. */
        0,
        /* Will get set to the source of the received data. */
        &sourceAddress,
        /* Not used but should be set as shown. */
        &addressLength
        );

    /* If data is received from the network, discard the data if  received from a different source than
     * the server. */
    #if defined( ipconfigIPv4_BACKWARD_COMPATIBLE ) && ( ipconfigIPv4_BACKWARD_COMPATIBLE == 0 )
        if( ( bytesReceived > 0 ) && ( ( FreeRTOS_ntohl( sourceAddress.sin_address.ulIP_IPv4 ) != serverAddr ) ||
                                       ( FreeRTOS_ntohs( sourceAddress.sin_port ) != serverPort ) ) )
    #else
        if( ( bytesReceived > 0 ) && ( ( FreeRTOS_ntohl( sourceAddress.sin_addr ) != serverAddr ) ||
                                       ( FreeRTOS_ntohs( sourceAddress.sin_port ) != serverPort ) ) )
    #endif /* defined( ipconfigIPv4_BACKWARD_COMPATIBLE ) && ( ipconfigIPv4_BACKWARD_COMPATIBLE == 0 ) */
    {
        bytesReceived = 0;

        #if defined( LIBRARY_LOG_LEVEL ) && ( LIBRARY_LOG_LEVEL != LOG_NONE )
            /* Convert the IP address of the sender's address to string for logging. */
            char stringAddr[ 16 ];

        #if defined( ipconfigIPv4_BACKWARD_COMPATIBLE ) && ( ipconfigIPv4_BACKWARD_COMPATIBLE == 0 )
            {
                FreeRTOS_inet_ntoa( sourceAddress.sin_address.ulIP_IPv4, stringAddr );
            }
        #else
            {
                FreeRTOS_inet_ntoa( sourceAddress.sin_addr, stringAddr );
            }
        #endif /* defined( ipconfigIPv4_BACKWARD_COMPATIBLE ) && ( ipconfigIPv4_BACKWARD_COMPATIBLE == 0 ) */

        /* Log about reception of packet from unexpected sender. */
        LogWarn( ( "Received UDP packet from unexpected source: Addr=%s Port=%u",
                   stringAddr, FreeRTOS_ntohs( sourceAddress.sin_port ) ) );
        #endif /* if defined( LIBRARY_LOG_LEVEL ) && ( LIBRARY_LOG_LEVEL != LOG_NONE ) */
    }

    /* Translate the return code of timeout to the UDP transport interface expected
     * code to indicate read retry. */
    else if( bytesReceived == -pdFREERTOS_ERRNO_EWOULDBLOCK )
    {
        bytesReceived = 0;
    }

    return bytesReceived;
}


/**************************** Time Interfaces ************************************************/
static void sntpClient_GetTime( SntpTimestamp_t * pCurrentTime )
{
    UTCTime_t currentTime;
    uint64_t ntpSecs;

    /* Obtain mutex for accessing system clock variables */
    xSemaphoreTake( xMutex, portMAX_DELAY );

    calculateCurrentTime( &systemClock.baseTime,
                          systemClock.lastSyncTickCount,
                          systemClock.slewRate,
                          &currentTime );

    /* Release mutex. */
    xSemaphoreGive( xMutex );

    /* Convert UTC time from UNIX timescale to SNTP timestamp format. */
    ntpSecs = currentTime.secs + SNTP_TIME_AT_UNIX_EPOCH_SECS;

    /* Support case of SNTP timestamp rollover on 7 February 2036 when
     * converting from UNIX time to SNTP timestamp. */
    if( ntpSecs > UINT32_MAX )
    {
        /* Subtract an extra second as timestamp 0 represents the epoch for
         * NTP era 1. */
        pCurrentTime->seconds = ntpSecs - UINT32_MAX - 1;
    }
    else
    {
        pCurrentTime->seconds = ntpSecs;
    }

    pCurrentTime->fractions = MILLISECONDS_TO_SNTP_FRACTIONS( currentTime.msecs );
}

static void sntpClient_SetTime( const SntpServerInfo_t * pTimeServer,
                                const SntpTimestamp_t * pServerTime,
                                int64_t clockOffsetMs,
                                SntpLeapSecondInfo_t leapSecondInfo )
{
    /* Note: This demo DOES NOT show adjustment of leap second in system time,
     * if an upcoming leap second adjustment is mentioned in server response.
     * Leap second adjustment occurs at low frequency (only for the last minute of June
     * or December) and can be useful for applications that require smooth system
     * time continuum ALWAYS including the time of the leap second adjustment.
     *
     * For more information on leap seconds, refer to
     * https://www.nist.gov/pml/time-and-frequency-division/leap-seconds-faqs.
     */
    ( void ) leapSecondInfo;

    LogInfo( ( "Received time from time server: %s", pTimeServer->pServerName ) );

    /* Obtain the mutext for accessing system clock variables. */
    xSemaphoreTake( xMutex, portMAX_DELAY );

    /* Always correct the system base time on receiving time from server.*/
    SntpStatus_t status;
    uint32_t unixSecs;
    uint32_t unixMicroSecs;

    /* Convert server time from NTP timestamp to UNIX format. */
    status = Sntp_ConvertToUnixTime( pServerTime,
                                     &unixSecs,
                                     &unixMicroSecs );
    configASSERT( status == SntpSuccess );

    /* Always correct the base time of the system clock as the time received from the server. */
    systemClock.baseTime.secs = unixSecs;
    systemClock.baseTime.msecs = unixMicroSecs / 1000;

    /* Set the clock adjustment "slew" rate of system clock if it wasn't set already and this is NOT
     * the first clock synchronization since device boot-up. */
    if( ( systemClock.firstTimeSyncDone == true ) && ( systemClock.slewRate == 0 ) )
    {
        /* We will use a "slew" correction approach to compensate for system clock
         * drift over poll interval period that exists between consecutive time synchronizations
         * with time server. */

        /* Calculate the "slew" rate for system clock as milliseconds of adjustment needed per second. */
        systemClock.slewRate = clockOffsetMs / systemClock.pollPeriod;
    }

    /* Set the system clock flag that indicates completion of the first time synchronization since device boot-up. */
    if( systemClock.firstTimeSyncDone == false )
    {
        systemClock.firstTimeSyncDone = true;
    }

    /* Store the tick count of the current time synchronization in the system clock. */
    systemClock.lastSyncTickCount = xTaskGetTickCount();

    xSemaphoreGive( xMutex );
}

/**************************** Authentication Utilities and Interface Functions ***********************************************/
static void populateAuthContextForServer( const char * pServer,
                                          SntpAuthContext_t * pAuthContext )

{
    size_t index = 0;

    /* Look for the server in the list of configured servers to determine the
     * index position of the server so that the server's corresponding information of credentials
     * can be found from democonfigLIST_OF_AUTHENTICATION_SYMMETRIC_KEYS and democonfigLIST_OF_AUTHENTICATION_KEY_IDS
     * lists. */
    for( index = 0; index < numOfServers; index++ )
    {
        if( ( strlen( pServer ) == strlen( pTimeServers[ index ] ) ) && ( strncmp( pServer, pTimeServers[ index ], strlen( pServer ) ) == 0 ) )
        {
            /* The records for server in the demo configuration lists have been found. */
            break;
        }
    }

    /* Make sure that record for server has been found. */
    configASSERT( index != numOfServers );

    /* Fill the time server in the authentication context. */
    pAuthContext->pServer = pServer;

    /* Determine if the time server has been configured to use authentication mechanism. */
    if( ( pAESCMACAuthKeys[ index ] != NULL ) && ( pAuthKeyIds[ index ] != -1 ) )
    {
        const char * pKeyHexString = pAESCMACAuthKeys[ index ];

        /* Verify that the configured authentication key is 128 bits or 16 bytes in size. As
         * the input format is a hex string, the string length should be 32 bytes. */
        configASSERT( strlen( pKeyHexString ) == 2 * AES_CMAC_AUTHENTICATION_KEY_SIZE );

        /* Set the key ID in the context. */
        pAuthContext->keyId = pAuthKeyIds[ index ];

        /* Store the configured AES-128-CMAC key for authentication in the SntpAuthContext_t context
         * after converting it from hex string to binary. */
        for( index = 0; index < strlen( pKeyHexString ); index += 2 )
        {
            char byteString[ 3 ] = { pKeyHexString[ index ], pKeyHexString[ index + 1 ], '\0' };
            uint8_t byteVal = strtoul( byteString, NULL, 16 );
            pAuthContext->pAuthKey[ index / 2 ] = byteVal;
        }
    }
    else
    {
        /* No key information has been configured for the time server. Thus, communication with the time
         * server will not use authentication mechanism. */
        memset( pAuthContext->pAuthKey, 0, sizeof( pAuthContext->pAuthKey ) );
        pAuthContext->keyId = -1;
    }
}

static CK_RV setupPkcs11ObjectForAesCmac( const SntpAuthContext_t * pAuthContext,
                                          CK_SESSION_HANDLE * pPkcs11Session,
                                          CK_FUNCTION_LIST_PTR * pFunctionList,
                                          CK_OBJECT_HANDLE * pCmacKey )
{
    CK_RV result;

    static CK_BYTE label[] = pkcs11configLABEL_CMAC_KEY;
    static CK_KEY_TYPE cmacKeyType = CKK_AES;
    static CK_OBJECT_CLASS cmacKeyClass = CKO_SECRET_KEY;
    static CK_BBOOL trueObject = CK_TRUE;

    static CK_ATTRIBUTE aes_cmac_template[] =
    {
        { CKA_CLASS,    &cmacKeyClass, sizeof( CK_OBJECT_CLASS ) },
        { CKA_KEY_TYPE, &cmacKeyType,  sizeof( CK_KEY_TYPE )     },
        { CKA_LABEL,    label,         sizeof( label ) - 1       },
        { CKA_TOKEN,    &trueObject,   sizeof( CK_BBOOL )        },
        { CKA_SIGN,     &trueObject,   sizeof( CK_BBOOL )        },
        { CKA_VERIFY,   &trueObject,   sizeof( CK_BBOOL )        },
        { CKA_VALUE,    NULL,          0                         }
    };

    /* Update the attributes array with the key of AES-CMAC operation. */
    aes_cmac_template[ 6 ].pValue = pAuthContext->pAuthKey;
    aes_cmac_template[ 6 ].ulValueLen = sizeof( pAuthContext->pAuthKey );

    result = xInitializePkcs11Session( pPkcs11Session );

    if( result != CKR_OK )
    {
        LogError( ( "Failed to open PKCS #11 session." ) );
    }

    if( result == CKR_OK )
    {
        result = C_GetFunctionList( pFunctionList );

        if( result != CKR_OK )
        {
            LogError( ( "Failed to get PKCS #11 function list." ) );
        }
    }

    if( result == CKR_OK )
    {
        /* Create the template objects */
        result = ( *pFunctionList )->C_CreateObject( *pPkcs11Session,
                                                     ( CK_ATTRIBUTE_PTR ) &aes_cmac_template,
                                                     sizeof( aes_cmac_template ) / sizeof( CK_ATTRIBUTE ),
                                                     pCmacKey );

        if( result != CKR_OK )
        {
            LogError( ( "Failed to create AES CMAC object." ) );
        }

        configASSERT( *pCmacKey != CK_INVALID_HANDLE );
    }

    return result;
}

SntpStatus_t addClientAuthCode( SntpAuthContext_t * pAuthContext,
                                const SntpServerInfo_t * pTimeServer,
                                void * pRequestBuffer,
                                uint16_t bufferSize,
                                uint16_t * pAuthCodeSize )
{
    CK_RV result = CKR_OK;
    CK_FUNCTION_LIST_PTR functionList;
    CK_SESSION_HANDLE pkcs11Session = 0;

    CK_OBJECT_HANDLE cMacKey;
    size_t macBytesWritten = pkcs11AES_CMAC_SIGNATURE_LENGTH;

    CK_MECHANISM mechanism =
    {
        CKM_AES_CMAC, NULL_PTR, 0
    };

    /* Determine whether the authentication context information needs to be updated to match
     * the passed time server that is to be used for querying time.
     * Note: The coreSNTP library will rotate the time server of use for communication to the next
     * in the list if either the time server rejects a time request OR times out in its response to the
     * time request. In such a case of rotating time server, the application (or user of the coreSNTP
     * library) is required to necessary updates to the authentication context to reflect the new
     * time server being used for SNTP communication by the SNTP client.*/
    if( ( strlen( pTimeServer->pServerName ) != strlen( pAuthContext->pServer ) ) ||
        ( strncmp( pTimeServer->pServerName, pAuthContext->pServer, strlen( pAuthContext->pServer ) ) != 0 ) )
    {
        /* Update the authentication context to represent the new time server of usage for
         *  time requests. */
        populateAuthContextForServer( pTimeServer->pServerName, pAuthContext );
    }

    /* Check if the time server supports AES-128-CMAC authentication scheme in communication.
     * If the time server supports authentication, then proceed with operation of generating client
     * authentication code from the SNTP request packet and appending it to the request buffer.  */
    if( pAuthContext->keyId != -1 )
    {
        /* Ensure that the buffer is large enough to hold the "Key Identifier" and "Message Digest" fields
         * for authentication information of the SNTP time request packet. */
        configASSERT( bufferSize >= SNTP_PACKET_AUTHENTICATED_MODE_SIZE );

        result = setupPkcs11ObjectForAesCmac( pAuthContext,
                                              &pkcs11Session,
                                              &functionList,
                                              &cMacKey );

        if( result == CKR_OK )
        {
            /* Test SignInit and Sign */
            result = functionList->C_SignInit( pkcs11Session, &mechanism, cMacKey );

            if( result != CKR_OK )
            {
                LogError( ( "Failed to C_SignInit AES CMAC." ) );
            }
        }

        /* Append the Key ID of the signing key before appending the signature to the buffer. */
        *( uint32_t * ) ( ( uint8_t * ) pRequestBuffer + SNTP_PACKET_SYMMETRIC_KEY_ID_OFFSET ) = FreeRTOS_htonl( pAuthContext->keyId );

        /* Generate the authentication code as the signature of the time request packet
         * with the configured key. */
        if( result == CKR_OK )
        {
            result = functionList->C_Sign( pkcs11Session,
                                           ( CK_BYTE_PTR ) pRequestBuffer,
                                           SNTP_PACKET_BASE_SIZE,
                                           ( CK_BYTE_PTR ) pRequestBuffer + SNTP_PACKET_BASE_SIZE + SNTP_PACKET_SYMMETRIC_KEY_ID_LENGTH,
                                           &macBytesWritten );

            if( result != CKR_OK )
            {
                LogError( ( "Failed to generate client auth code: Failed to generate AES-128-CMAC signature of SNTP request packet." ) );
            }
        }

        /* Close the PKCS #11 session as the AES-CMAC operation is completed. */
        if( result == CKR_OK )
        {
            result = functionList->C_CloseSession( pkcs11Session );
            configASSERT( result == CKR_OK );

            result = functionList->C_Finalize( NULL );
            configASSERT( result == CKR_OK );
        }

        if( result == CKR_OK )
        {
            *pAuthCodeSize = SNTP_PACKET_SYMMETRIC_KEY_ID_LENGTH + pkcs11AES_CMAC_SIGNATURE_LENGTH;
        }
    }
    else
    {
        /* Server has not been configured with authentication key information, thus, no data was appended to the
         * request packet buffer. */
        *pAuthCodeSize = 0;
    }

    return ( result == CKR_OK ) ? SntpSuccess : SntpErrorAuthFailure;
}

SntpStatus_t validateServerAuth( SntpAuthContext_t * pAuthContext,
                                 const SntpServerInfo_t * pTimeServer,
                                 const void * pResponseData,
                                 uint16_t responseSize )
{
    CK_RV result = CKR_OK;
    CK_FUNCTION_LIST_PTR functionList;
    CK_SESSION_HANDLE pkcs11Session = 0;
    SntpStatus_t returnStatus = SntpSuccess;

    CK_OBJECT_HANDLE cMacKey;
    size_t macBytesWritten = pkcs11AES_CMAC_SIGNATURE_LENGTH;

    CK_MECHANISM mechanism =
    {
        CKM_AES_CMAC, NULL_PTR, 0
    };

    /* The time server information in the authentication context, managed by this demo application, and the
     * pTimeServer parameter, passed by the coreSNTP library, MUST be the same.
     * Note: The addClientAuthCode() function
     * is responsible for updating the authentication context to represent the time server being currently used
     * by the coreSNTP library for time querying. */
    configASSERT( ( strlen( pTimeServer->pServerName ) == strlen( pAuthContext->pServer ) ) &&
                  ( strncmp( pTimeServer->pServerName, pAuthContext->pServer, strlen( pAuthContext->pServer ) ) == 0 ) );

    /* Check if the time server supports AES-128-CMAC authentication scheme in communication.
     * If the time server supports authentication, then proceed with operation of validating server
     * from the authentication code in the response payload.  */
    if( pAuthContext->keyId != -1 )
    {
        /* As the server supports authentication mode of communication, the server response size
         * SHOULD contain the authentication code. */
        configASSERT( responseSize >= ( SNTP_PACKET_AUTHENTICATED_MODE_SIZE ) );

        result = setupPkcs11ObjectForAesCmac( pAuthContext,
                                              &pkcs11Session,
                                              &functionList,
                                              &cMacKey );

        if( result == CKR_OK )
        {
            /* Test SignInit and Sign */
            result = functionList->C_VerifyInit( pkcs11Session, &mechanism, cMacKey );

            if( result != CKR_OK )
            {
                returnStatus = SntpErrorAuthFailure;
                LogError( ( "Failed to C_VerifyInit AES CMAC." ) );
            }
        }
        else
        {
            returnStatus = SntpErrorAuthFailure;
        }

        /* Generate the authentication code as the signature of the time request packet
         * with the configured key. */
        if( result == CKR_OK )
        {
            result = functionList->C_Verify( pkcs11Session,
                                             ( CK_BYTE_PTR ) pResponseData,
                                             SNTP_PACKET_BASE_SIZE,
                                             ( CK_BYTE_PTR ) pResponseData + SNTP_PACKET_SYMMETRIC_KEY_ID_OFFSET + SNTP_PACKET_SYMMETRIC_KEY_ID_LENGTH,
                                             pkcs11AES_CMAC_SIGNATURE_LENGTH );

            if( result != CKR_OK )
            {
                returnStatus = SntpServerNotAuthenticated;
                LogError( ( "Server cannot be validated from received response: AES-128-CMAC signature in response packet does not match expected." ) );
            }
        }

        /* Close the PKCS #11 session as the AES-CMAC operation is completed. */
        if( result == CKR_OK )
        {
            result = functionList->C_CloseSession( pkcs11Session );
            configASSERT( result == CKR_OK );

            result = functionList->C_Finalize( NULL );
            configASSERT( result == CKR_OK );
        }
    }

    return returnStatus;
}


/*************************************************************************************/

static uint32_t generateRandomNumber()
{
    CK_RV pkcs11Status = CKR_OK;
    CK_FUNCTION_LIST_PTR pFunctionList = NULL;
    CK_SESSION_HANDLE session = CK_INVALID_HANDLE;
    uint32_t randomNum = 0;

    /* Get list of functions supported by the PKCS #11 port. */
    pkcs11Status = C_GetFunctionList( &pFunctionList );

    if( pkcs11Status != CKR_OK )
    {
        configASSERT( pFunctionList != NULL );
        LogError( ( "Failed to generate random number. "
                    "PKCS #11 API, C_GetFunctionList, failed." ) );
    }

    if( pkcs11Status == CKR_OK )
    {
        /* Initialize PKCS #11 module and create a new session. */
        pkcs11Status = xInitializePkcs11Session( &session );

        if( pkcs11Status != CKR_OK )
        {
            configASSERT( session != CK_INVALID_HANDLE );

            LogError( ( "Failed to generate random number. "
                        "Failed to initialize PKCS #11 session." ) );
        }
    }

    if( pkcs11Status == CKR_OK )
    {
        if( pFunctionList->C_GenerateRandom( session,
                                             &randomNum,
                                             sizeof( randomNum ) ) != CKR_OK )
        {
            LogError( ( "Failed to generate random number. "
                        "PKCS #11 API, C_GenerateRandom, failed to generate random number." ) );
        }
    }

    if( pkcs11Status == CKR_OK )
    {
        if( pFunctionList->C_CloseSession( session ) != CKR_OK )
        {
            LogError( ( " Failed to close PKCS #11 session after generating random number." ) );
        }
    }

    return randomNum;
}


/*************************************************************************************/

void initializeSystemClock( void )
{
    /* On boot-up initialize the system time as the first second in the configured year. */
    int64_t startupTimeInUnixSecs = translateYearToUnixSeconds( democonfigSYSTEM_START_YEAR );

    systemClock.baseTime.secs = startupTimeInUnixSecs;
    systemClock.baseTime.msecs = 0;

    LogInfo( ( "System time has been initialized to the year %u", democonfigSYSTEM_START_YEAR ) );
    printTime( &systemClock.baseTime );

    /* Initialize semaphore for guarding access to system clock variables. */
    xMutex = xSemaphoreCreateMutex();
    configASSERT( xMutex );

    /* Clear the first time sync completed flag of the system clock object so that a "step" correction
     * of system time is utilized for the first time synchronization from a time server. */
    systemClock.firstTimeSyncDone = false;
}

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

static bool initializeSntpClient( SntpContext_t * pContext,
                                  const char ** pTimeServers,
                                  size_t numOfServers,
                                  uint8_t * pContextBuffer,
                                  size_t contextBufferSize,
                                  NetworkContext_t * pUdpContext,
                                  SntpAuthContext_t * pAuthContext )
{
    bool initStatus = false;

    /* Populate the list of time servers. */
    SntpServerInfo_t * pServers = pvPortMalloc( sizeof( SntpServerInfo_t ) * numOfServers );

    if( pServers == NULL )
    {
        LogError( ( "Unable to initialize SNTP client: Malloc failed for memory of configured time servers." ) );
    }
    else
    {
        UdpTransportInterface_t udpTransportIntf;
        SntpAuthenticationInterface_t symmetricKeyAuthIntf;

        for( uint8_t index = 0; index < numOfServers; index++ )
        {
            pServers[ index ].pServerName = pTimeServers[ index ];
            pServers[ index ].port = SNTP_DEFAULT_SERVER_PORT;
        }

        /* Set the UDP transport interface object. */
        udpTransportIntf.pUserContext = pUdpContext;
        udpTransportIntf.sendTo = UdpTransport_Send;
        udpTransportIntf.recvFrom = UdpTransport_Recv;

        /* Set the authentication interface object. */
        symmetricKeyAuthIntf.pAuthContext = pAuthContext;
        symmetricKeyAuthIntf.generateClientAuth = addClientAuthCode;
        symmetricKeyAuthIntf.validateServerAuth = validateServerAuth;

        /* Initialize context. */
        Sntp_Init( pContext,
                   pServers,
                   numOfServers,
                   democonfigSERVER_RESPONSE_TIMEOUT_MS,
                   pContextBuffer,
                   contextBufferSize,
                   resolveDns,
                   sntpClient_GetTime,
                   sntpClient_SetTime,
                   &udpTransportIntf,
                   &symmetricKeyAuthIntf );

        initStatus = true;
    }

    return initStatus;
}

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

static bool createUdpSocket( Socket_t * pSocket )
{
    bool status = false;
    struct freertos_sockaddr bindAddress;

    configASSERT( pSocket != NULL );

    /* Call the FreeRTOS+TCP API to create a UDP socket. */
    *pSocket = FreeRTOS_socket( FREERTOS_AF_INET,
                                FREERTOS_SOCK_DGRAM,
                                FREERTOS_IPPROTO_UDP );

    /* Check the socket was created successfully. */
    if( *pSocket == FREERTOS_INVALID_SOCKET )
    {
        /* There was insufficient FreeRTOS heap memory available for the socket
         * to be created. */
        LogError( ( "Failed to create UDP socket for SNTP client due to insufficient memory." ) );
    }
    else
    {
        /* Use a random UDP port for SNTP communication with server for protection against
         * spoofing vulnerability from "network off-path" attackers. */
        uint16_t randomPort = ( generateRandomNumber() % UINT16_MAX );
        bindAddress.sin_port = FreeRTOS_htons( randomPort );
        bindAddress.sin_family = FREERTOS_AF_INET;

        if( FreeRTOS_bind( *pSocket, &bindAddress, sizeof( bindAddress ) ) == 0 )
        {
            /* The bind was successful. */
            LogDebug( ( "UDP socket has been bound to port %u", randomPort ) );
            status = true;
        }
        else
        {
            LogError( ( "Failed to bind UDP socket to port %u", randomPort ) );
        }
    }

    return status;
}

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

static void closeUdpSocket( Socket_t * pSocket )
{
    bool status = false;
    struct freertos_sockaddr bindAddress;

    configASSERT( pSocket != NULL );

    FreeRTOS_shutdown( *pSocket, FREERTOS_SHUT_RDWR );

    /* Close the socket again. */
    FreeRTOS_closesocket( *pSocket );
}

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

static bool calculateBackoffForNextPoll( BackoffAlgorithmContext_t * pBackoffContext,
                                         bool shouldInitializeContext,
                                         uint32_t minPollPeriod,
                                         uint32_t * pPollPeriod )
{
    uint16_t newPollPeriod = 0U;
    BackoffAlgorithmStatus_t status;

    configASSERT( pBackoffContext != NULL );
    configASSERT( pPollPeriod != NULL );

    if( shouldInitializeContext == true )
    {
        /* Initialize reconnect attempts and interval.*/
        BackoffAlgorithm_InitializeParams( &pBackoffContext,
                                           minPollPeriod,
                                           SNTP_DEMO_POLL_MAX_BACKOFF_DELAY_SEC,
                                           SNTP_DEMO_MAX_SERVER_BACKOFF_RETRIES );
    }

    /* Generate a random number and calculate the new backoff poll period to wait before the next
     * time poll attempt. */
    status = BackoffAlgorithm_GetNextBackoff( &pBackoffContext, generateRandomNumber(), &newPollPeriod );

    if( status == BackoffAlgorithmRetriesExhausted )
    {
        LogError( ( "All backed-off attempts of polling time server have expired: MaxAttempts=%d",
                    SNTP_DEMO_MAX_SERVER_BACKOFF_RETRIES ) );
    }
    else
    {
        /* Store the calculated backoff period as the new poll period. */
        *pPollPeriod = newPollPeriod;
    }

    return( status == BackoffAlgorithmSuccess );
}

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

void sntpTask( void * pParameters )
{
    SntpContext_t clientContext;
    bool initStatus = false;
    CK_RV pkcs11Status;

    /* Validate that the configured lists of time servers, authentication keys and key IDs
     * are of the same length. */
    configASSERT( numOfServers == ( sizeof( pAESCMACAuthKeys ) / sizeof( pAESCMACAuthKeys[ 0 ] ) ) );
    configASSERT( numOfServers == sizeof( pAuthKeyIds ) / sizeof( pAuthKeyIds[ 0 ] ) );

    /* Variable representing the SNTP client context. */
    static SntpContext_t context;

    /* Memory for the SNTP packet buffer in the SNTP context. */
    static uint8_t contextBuffer[ SNTP_PACKET_AUTHENTICATED_MODE_SIZE ];

    /* Memory for the network context representing the UDP socket that will be
     * passed to the SNTP client context. */
    static NetworkContext_t udpContext;

    /* Initialize PKCS11 module for cryptographic operations of AES-128-CMAC show
     * shown in this demo for authentication mechanism in SNTP communication with server. */
    pkcs11Status = xInitializePKCS11();
    configASSERT( pkcs11Status == CKR_OK );

    /* Memory for authentication context that will be passed to  the SNTP client context through
     * the authentication interface. This represents a combination of the time server and
     * its authentication key information that will be utilized for authentication communication
     * between client and server, if the server supports authentication. */
    static SntpAuthContext_t authContext;

    /* Context used for calculating backoff that is applied to polling interval when the configured
     * time server rejects time request.
     * Note: Backoff is applied to polling interval ONLY when a single server is configured in the demo
     * because in the case of multiple server configurations, the coreSNTP library handles server
     * rejection by rotating server. */
    static BackoffAlgorithmContext_t backoffContext;

    /* Initialize the authentication context for information for the first time server and its
     * keys configured in the demo. */
    populateAuthContextForServer( pTimeServers[ 0 ], &authContext );

    initStatus = initializeSntpClient( &clientContext,
                                       pTimeServers,
                                       numOfServers,
                                       contextBuffer,
                                       sizeof( contextBuffer ),
                                       &udpContext,
                                       &authContext );

    if( initStatus == true )
    {
        SntpStatus_t status;
        bool backoffModeFlag = false;

        /* Set the polling interval for periodic time synchronization attempts by the SNTP client. */
        systemClock.pollPeriod = democonfigSNTP_CLIENT_POLLING_INTERVAL_SECONDS;

        LogDebug( ( "Minimum SNTP client polling interval calculated as %lus", systemClock.pollPeriod ) );

        LogInfo( ( "Initialized SNTP Client context. Starting SNTP client loop to poll time every %lu seconds",
                   systemClock.pollPeriod ) );

        /* The loop of the SNTP Client task that synchronizes system time with a time server (in the configured list of time servers)
         * periodically at intervals of polling period. Each iteration of time synchronization is performed by calling the coreSNTP
         * APIs for sending time request to the server and receiving time response from the server. */
        while( 1 )
        {
            LogInfo( ( "---------STARTING DEMO---------\r\n" ) );
            bool socketStatus = false;

            /* For security, this demo keeps a UDP socket open only for one iteration of SNTP request-response cycle.
             * There is a security risk of a UDP socket being flooded with invalid or malicious server response packets
             * when a UDP socket is kept open across multiple time polling cycles. In such a scenario where the UDP
             * socket buffer has received multiple server response packets from a single time request, the extraneous
             * server response present in the UDP socket buffer will prevent the SNTP client application from correctly
             * reading network data of server responses that correspond to future time requests.
             * By closing the UDP socket after receiving the first acceptable server response (within the server response
             * timeout window), any extraneous or malicious server response packets for the same time request will be
             * ignored by the demo. */

            /* Wait for Networking */
            if( xPlatformIsNetworkUp() == pdFALSE )
            {
                LogInfo( ( "Waiting for the network link up event..." ) );

                while( xPlatformIsNetworkUp() == pdFALSE )
                {
                    vTaskDelay( pdMS_TO_TICKS( 1000U ) );
                }
            }

            /* Create a UDP socket for the current iteration of time polling. */
            socketStatus = createUdpSocket( &udpContext.socket );
            configASSERT( socketStatus == true );

            status = Sntp_SendTimeRequest( &clientContext, generateRandomNumber(), democonfigSEND_TIME_REQUEST_TIMEOUT_MS );
            configASSERT( status == SntpSuccess );

            /* Wait for server response for a maximum time of server response timeout. */
            do
            {
                /* Attempt to receive server response each time for a smaller block time
                 * than the total duration for the server response to time out. */
                status = Sntp_ReceiveTimeResponse( &clientContext, democonfigRECEIVE_SERVER_RESPONSE_BLOCK_TIME_MS );
            } while( status == SntpNoResponseReceived );

            /* Close the UDP socket irrespective of whether a server response is received. */
            closeUdpSocket( &udpContext.socket );

            /* Apply back-off delay before the next poll iteration if the demo has been configured with only
             * a single time server. */
            if( ( status == SntpRejectedResponse ) && ( numOfServers == 1 ) )
            {
                bool backoffStatus = false;

                /* Determine if this is the first back-off attempt we are making since the most recent server rejection
                 * for time request. */
                bool firstBackoffAttempt = false;

                if( backoffModeFlag == false )
                {
                    firstBackoffAttempt = true;

                    /* Set the flag to indicate we are in back-off retry mode for requesting time from the server. */
                    backoffModeFlag = true;
                }

                LogInfo( ( "The single configured time server, %s, rejected time request. Backing-off before ",
                           "next time poll....", strlen( pTimeServers[ 0 ] ) ) );

                /* Add exponential back-off to polling period. */
                backoffStatus = calculateBackoffForNextPoll( &backoffContext,
                                                             firstBackoffAttempt,
                                                             systemClock.pollPeriod,
                                                             &systemClock.pollPeriod );
                configASSERT( backoffStatus == true );

                /* Wait for the increased poll interval before retrying request for time from server. */
                vTaskDelay( pdMS_TO_TICKS( systemClock.pollPeriod * 1000 ) );
            }
            else
            {
                /* Reset flag to indicate that we are not backing-off for the next time poll. */
                backoffModeFlag = false;

                /* Wait for the poll interval period before the next iteration of time synchronization. */
                vTaskDelay( pdMS_TO_TICKS( systemClock.pollPeriod * 1000 ) );
            }
        }
    }
    else
    {
        configASSERT( false );

        /* Terminate the task as the SNTP client failed to be run. */
        LogError( ( "Failed to initialize SNTP client. Terminating SNTP client task.." ) );

        vTaskDelete( NULL );
    }
}

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

void systemGetWallClockTime( UTCTime_t * pTime )
{
    TickType_t xTickCount = 0;
    uint32_t ulTimeMs = 0UL;

    /* Obtain the mutext for accessing system clock variables. */
    xSemaphoreTake( xMutex, portMAX_DELAY );

    /* Calculate the current RAM-based time using a mathematical formula using
     * system clock state parameters and the time transpired since last synchronization. */
    calculateCurrentTime( &systemClock.baseTime,
                          systemClock.lastSyncTickCount,
                          systemClock.slewRate,
                          pTime );

    xSemaphoreGive( xMutex );
}

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