text: /*
   * FreeRTOS V202104.00
   * Copyright (C) 2021 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 an SNTP/NTP servers. 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.
   *
   * !!! NOTE !!!
   * This SNTP demo does not authenticate the server nor the client.
   * Hence, this demo should not be used as production ready code.
   */
  
  /* 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"
  
  /*-----------------------------------------------------------*/
  
  /* 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 democonfigDESIRED_CLOCK_ACCURACY_MS
      #error "Define the democonfigDESIRED_CLOCK_ACCURACY_MS config by following instructions in demo_config.h file."
  #endif
  
  #ifndef democonfigSYSTEM_CLOCK_TOLERANCE_PPM
      #error "Define the democonfigSYSTEM_CLOCK_TOLERANCE_PPM 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 configuration . */
  
  #ifndef democonfigSERVER_RESPONSE_TIMEOUT_MS
      #define democonfigSERVER_RESPONSE_TIMEOUT_MS    ( 5000 )
  #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 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 seconds 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;
      uint32_t slewRate; /* Seconds/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;
  static StaticSemaphore_t xSemaphoreMutex;
  
  
  /*
   * @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 seconds 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,
                                    uint32_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,
                                    size_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,
                                    size_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 either the "slew" OR "step" methodology of system
   * clock correction based on the use-case:
   * 1. "Step" correction is used if:
   *   - System time is ahead of server time so that system time is immediately
   *     corrected instead of potentially receding back in time with a "slew"
   *     correction approach.
   *                                      OR
   *   - It is the first time synchronization for the system since boot-up. Using
   *     "step" approach immediately corrects the system if it is far away from the
   *     server time on device startup instead of slowly correcting over time with
   *     the "slew" approach.
   *
   * 2. The "slew" correction approach is used for all cases other than the above
   *    as they represent regular time synchronization during device runtime where
   *    the system time may have drifted behind the server time, and can be corrected
   *    gradually over the SNTP client's polling interval period.
   *
   * @note The above system clock correction algorithm is just one example of a correction
   * approach. It can be modified to suit your application needs. Examples include:
   * - Always using "slew" correction if the device is always within a small time offset from
   *   server and your application is sensitive to non-abrupt changes in time (that could occur
   *   with "step" approach) for use-cases like logging events in correct order
   * - Always using a "step" approach for a simplicity if your application is not sensitive to
   *   abrupt changes/progress in time.
   */
  static void sntpClient_SetTime( const SntpServerInfo_t * pTimeServer,
                                  const SntpTimestamp_t * pServerTime,
                                  int32_t clockOffsetSec,
                                  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 bool 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,
                                         size_t bufferSize,
                                         size_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();
  
  /*------------------------------------------------------------------------------*/
  
  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,
                             uint32_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 )
      {
          msElapsedSinceLastSync += ( uint64_t ) slewRate * msElapsedSinceLastSync;
      }
  
      /* 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 = 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,
                             size_t bytesToSend )
  {
      struct freertos_sockaddr destinationAddress;
      int32_t bytesSent;
  
      destinationAddress.sin_addr = serverAddr;
      destinationAddress.sin_port = FreeRTOS_htons( serverPort );
  
      /* 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,
                                    size_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( ( bytesReceived > 0 ) && ( ( sourceAddress.sin_addr != serverAddr ) ||
                                     ( FreeRTOS_ntohs( sourceAddress.sin_port ) != serverPort ) ) )
      {
          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 ];
              FreeRTOS_inet_ntoa( sourceAddress.sin_addr, stringAddr );
  
              /* 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
      }
  
      /* 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,
                                  int32_t clockOffsetSec,
                                  SntpLeapSecondInfo_t leapSecondInfo )
  {
      /* TODO - Handle leap second. */
      ( void ) leapSecondInfo;
  
      ( void ) pTimeServer;
  
      /* Obtain the mutext for accessing system clock variables. */
      xSemaphoreTake( xMutex, portMAX_DELAY );
  
      /* Use "step" approach if:
       * The system clock has drifted ahead of server time.
       *                         OR
       * This is the first time synchronization with NTP server since device boot-up.
       */
      if( ( clockOffsetSec < 0 ) || ( systemClock.firstTimeSyncDone == false ) )
      {
          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 );
  
          /* Immediately correct the base time of the system clock as server time. */
          systemClock.baseTime.secs = unixSecs;
          systemClock.baseTime.msecs = unixMicroSecs / 1000;
  
          /* Reset slew rate to zero as the time has been immediately corrected to server time. */
          systemClock.slewRate = 0;
  
          /* Store the tick count of the current time synchronization in the system clock. */
          systemClock.lastSyncTickCount = xTaskGetTickCount();
  
          /* Set the system clock flag that indicates completion of the first time synchronization since device boot-up. */
          if( systemClock.firstTimeSyncDone == false )
          {
              systemClock.firstTimeSyncDone = true;
          }
      }
  
      /* As the system clock is behind server time, we will use a "slew" approach to gradually
       * correct system time over the poll interval period. */
      else
      {
          /* Update the base time based on the previous slew rate and the time period transpired
           * since last time synchronization. */
          calculateCurrentTime( &systemClock.baseTime,
                                systemClock.lastSyncTickCount,
                                systemClock.slewRate,
                                &systemClock.baseTime );
  
          /* Calculate the new slew rate as offset in seconds of adjustment per second. */
          systemClock.slewRate = clockOffsetSec / systemClock.pollPeriod;
  
          /* Store the tick count of the current time synchronization in the system clock. */
          systemClock.lastSyncTickCount = xTaskGetTickCount();
      }
  
      xSemaphoreGive( xMutex );
  }
  
  /**************************** Authentication Utilities and Interface Functions ***********************************************/
  static bool 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,
                                  size_t bufferSize,
                                  size_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,
                                   size_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 = xSemaphoreCreateMutexStatic( &xSemaphoreMutex );
      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
      {
          for( uint8_t index = 0; index < numOfServers; index++ )
          {
              pServers[ index ].pServerName = pTimeServers[ index ];
              pServers[ index ].port = SNTP_DEFAULT_SERVER_PORT;
          }
  
          LogInfo( ( "Calculated poll interval: %lus", systemClock.pollPeriod ) );
  
          /* Create a UDP socket for network I/O with server. */
          pUdpContext->socket = FreeRTOS_socket( FREERTOS_AF_INET,
                                                 FREERTOS_SOCK_DGRAM,
                                                 FREERTOS_IPPROTO_UDP );
  
          /* Check the socket was created successfully. */
          /* TODO - Consider using random port assigned by FreeRTOS for better protection */
          /* against "off-path" attacker. */
          if( pUdpContext->socket == 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
          {
              struct freertos_sockaddr bindAddress;
              UdpTransportInterface_t udpTransportIntf;
              SntpAuthenticationInterface_t symmetricKeyAuthIntf;
  
              bindAddress.sin_port = FreeRTOS_htons( SNTP_DEFAULT_SERVER_PORT );
  
              if( FreeRTOS_bind( pUdpContext->socket, &bindAddress, sizeof( bindAddress ) ) == 0 )
              {
                  /* The bind was successful. */
                  LogDebug( ( "UDP socket has been bound to port %lu", 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;
  }
  
  /*-----------------------------------------------------------*/
  
  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;
  
      /* 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;
  
          /* Calculate Poll interval of SNTP client based on desired accuracy and clock tolerance of the system. */
          status = Sntp_CalculatePollInterval( democonfigSYSTEM_CLOCK_TOLERANCE_PPM,
                                               democonfigDESIRED_CLOCK_ACCURACY_MS,
                                               &systemClock.pollPeriod );
          configASSERT( status == SntpSuccess );
  
          LogInfo( ( "Initialized SNTP Client context. Starting the SNTP client loop for time synchronization every %lu seconds",
                     systemClock.pollPeriod ) );
  
          /* SNTP Client loop of sending and receiving SNTP packets for time synchronization at poll intervals */
          while( 1 )
          {
              status = Sntp_SendTimeRequest( &clientContext, generateRandomNumber() );
  
              /*configASSERT( status == SntpSuccess ); */
              if( status != SntpSuccess )
              {
                  continue;
              }
  
              /* Wait till the server response is not received. */
              do
              {
                  /* Attempt to receive server response each time for 200 ms. */
                  status = Sntp_ReceiveTimeResponse( &clientContext, 200 );
              } while( status == SntpNoResponseReceived );
  
              /* 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 );
  }
  
  /*-----------------------------------------------------------*/
  