/* * coreSNTP v1.2.0 * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * SPDX-License-Identifier: MIT * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * @file core_sntp_serializer.c * @brief Implementation of the Serializer API of the coreSNTP library. */ /* Standard includes. */ #include #include #include /* Include API header. */ #include "core_sntp_serializer.h" #include "core_sntp_config_defaults.h" /** * @brief The version of SNTP supported by the coreSNTP library by complying * with the SNTPv4 specification defined in [RFC 4330](https://tools.ietf.org/html/rfc4330). */ #define SNTP_VERSION ( 4U ) /** * @brief The bit mask for the "Mode" information in the first byte of an SNTP packet. * The "Mode" field occupies bits 0-2 of the byte. * @note Refer to the [RFC 4330 Section 4](https://tools.ietf.org/html/rfc4330#section-4) * for more information. */ #define SNTP_MODE_BITS_MASK ( 0x07U ) /** * @brief The value indicating a "client" in the "Mode" field of an SNTP packet. * @note Refer to the [RFC 4330 Section 4](https://tools.ietf.org/html/rfc4330#section-4) * for more information. */ #define SNTP_MODE_CLIENT ( 3U ) /** * @brief The value indicating a "server" in the "Mode" field of an SNTP packet. * @note Refer to the [RFC 4330 Section 4](https://tools.ietf.org/html/rfc4330#section-4) * for more information. */ #define SNTP_MODE_SERVER ( 4U ) /** * @brief The position of the least significant bit of the "Leap Indicator" field * in first byte of an SNTP packet. The "Leap Indicator" field occupies bits 6-7 of the byte. * @note Refer to the [RFC 4330 Section 4](https://tools.ietf.org/html/rfc4330#section-4) * for more information. */ #define SNTP_LEAP_INDICATOR_LSB_POSITION ( 6 ) /** * @brief Value of Stratum field in SNTP packet representing a Kiss-o'-Death message * from server. */ #define SNTP_KISS_OF_DEATH_STRATUM ( 0U ) /** * @brief The position of least significant bit of the "Version" information * in the first byte of an SNTP packet. "Version" field occupies bits 3-5 of * the byte. * @note Refer to the [RFC 4330 Section 4](https://tools.ietf.org/html/rfc4330#section-4) * for more information. */ #define SNTP_VERSION_LSB_POSITION ( 3 ) /** * @brief The integer value of the Kiss-o'-Death ASCII code, "DENY", used * for comparison with data in an SNTP response. * @note Refer to [RFC 4330 Section 8](https://tools.ietf.org/html/rfc4330#section-8) * for more information. */ #define KOD_CODE_DENY_UINT_VALUE ( 0x44454e59U ) /** * @brief The integer value of the Kiss-o'-Death ASCII code, "RSTR", used * for comparison with data in an SNTP response. * @note Refer to [RFC 4330 Section 8](https://tools.ietf.org/html/rfc4330#section-8) * for more information. */ #define KOD_CODE_RSTR_UINT_VALUE ( 0x52535452U ) /** * @brief The integer value of the Kiss-o'-Death ASCII code, "RATE", used * for comparison with data in an SNTP response. * @note Refer to [RFC 4330 Section 8](https://tools.ietf.org/html/rfc4330#section-8) * for more information. */ #define KOD_CODE_RATE_UINT_VALUE ( 0x52415445U ) /** * @brief Macro to represent exactly half of the total number of seconds in an NTP era. * As 32 bits are used to represent the "seconds" part of an SNTP timestamp, the half of * the total range of seconds in an NTP era is 2^31 seconds, which represents ~68 years. * * @note This macro represents the edge case of calculating the client system clock-offset * relative to server time as the library ASSUMES that the client and server times are within * 2^31 seconds apart in duration. This is done to support calculating clock-offset for the * cases when server and client systems are in adjacent NTP eras, which can occur when NTP time * wraps around in 2036, and the relative NTP era presence of client and server times is * determined based on comparing first order difference values between all possible NTP era * configurations of the systems. */ #define CLOCK_OFFSET_MAX_TIME_DIFFERENCE ( ( ( ( int64_t ) INT32_MAX + 1 ) * 1000 ) ) /** * @brief Macro to represent the total number of milliseconds that are represented in an * NTP era period. This macro represents a duration of ~136 years. * * @note Rationale for calculation: The "seconds" part of an NTP timestamp is represented in * unsigned 32 bit width, thus, the total range of seconds it represents is 2^32, * i.e. (UINT32_MAX + 1). */ #define TOTAL_MILLISECONDS_IN_NTP_ERA ( ( ( int64_t ) UINT32_MAX + 1 ) * 1000 ) /** * @brief Structure representing an SNTP packet header. * For more information on SNTP packet format, refer to * [RFC 4330 Section 4](https://tools.ietf.org/html/rfc4330#section-4). * * @note This does not include extension fields for authentication data * for secure SNTP communication. Authentication data follows the * packet header represented by this structure. */ typedef struct SntpPacket { /** * @brief Bits 6-7 leap indicator, bits 3-5 are version number, bits 0-2 are mode */ uint8_t leapVersionMode; /** * @brief stratum */ uint8_t stratum; /** * @brief poll interval */ uint8_t poll; /** * @brief precision */ uint8_t precision; /** * @brief root delay */ uint32_t rootDelay; /** * @brief root dispersion */ uint32_t rootDispersion; /** * @brief reference ID */ uint32_t refId; /** * @brief reference time */ SntpTimestamp_t refTime; /** * @brief origin timestamp */ SntpTimestamp_t originTime; /** * @brief receive timestamp */ SntpTimestamp_t receiveTime; /** * @brief transmit timestamp */ SntpTimestamp_t transmitTime; } SntpPacket_t; /** * @brief Utility macro to fill 32-bit integer in word-sized * memory in network byte (or Big Endian) order. * * @param[out] pWordMemory Pointer to the word-sized memory in which * the 32-bit integer will be filled. * @param[in] data The 32-bit integer to fill in the @p wordMemory * in network byte order. * * @note This utility ensures that data is filled in memory * in expected network byte order, as an assignment operation * (like *pWordMemory = word) can cause undesired side-effect * of network-byte ordering getting reversed on Little Endian platforms. */ static void fillWordMemoryInNetworkOrder( uint32_t * pWordMemory, uint32_t data ) { assert( pWordMemory != NULL ); *( ( uint8_t * ) pWordMemory ) = ( uint8_t ) ( data >> 24 ); *( ( uint8_t * ) pWordMemory + 1 ) = ( uint8_t ) ( ( data >> 16 ) & 0x000000FFU ); *( ( uint8_t * ) pWordMemory + 2 ) = ( uint8_t ) ( ( data >> 8 ) & 0x000000FFU ); *( ( uint8_t * ) pWordMemory + 3 ) = ( uint8_t ) ( ( data ) & 0x000000FFU ); } /** * @brief Utility macro to generate a 32-bit integer from memory containing * integer in network (or Big Endian) byte order. * @param[in] ptr Pointer to the memory containing 32-bit integer in network * byte order. * * @return The host representation of the 32-bit integer in the passed word * memory. */ static uint32_t readWordFromNetworkByteOrderMemory( const uint32_t * ptr ) { const uint8_t * pMemStartByte = ( const uint8_t * ) ptr; assert( ptr != NULL ); return ( uint32_t ) ( ( ( uint32_t ) *( pMemStartByte ) << 24 ) | ( 0x00FF0000U & ( ( uint32_t ) *( pMemStartByte + 1 ) << 16 ) ) | ( 0x0000FF00U & ( ( uint32_t ) *( pMemStartByte + 2 ) << 8 ) ) | ( ( uint32_t ) *( pMemStartByte + 3 ) ) ); } /** * @brief Utility to return absolute (or positively signed) value of an signed * 64 bit integer. * * @param[in] value The integer to return the absolute value of. * * @return The absolute value of @p value. */ static int64_t absoluteOf( int64_t value ) { return ( value >= 0 ) ? value : ( ( int64_t ) 0 - value ); } /** * @brief Utility to determine whether a timestamp represents a zero * timestamp value. * * @note This utility is used to determine whether a timestamp value is * invalid. According to the SNTPv4 specification, a zero timestamp value * is considered invalid. * * @param[in] pTime The timestamp whose value is to be inspected for * zero value. * * @return `true` if the timestamp is zero; otherwise `false`. */ static bool isZeroTimestamp( const SntpTimestamp_t * pTime ) { bool isSecondsZero = ( pTime->seconds == 0U ) ? true : false; bool isFractionsZero = ( pTime->fractions == 0U ) ? true : false; return( isSecondsZero && isFractionsZero ); } /** * @brief Utility to convert the "fractions" part of an SNTP timestamp to milliseconds * duration of time. * @param[in] fractions The fractions value. * * @return The milliseconds equivalent of the @p fractions value. */ static uint32_t fractionsToMs( uint32_t fractions ) { return( fractions / ( 1000U * SNTP_FRACTION_VALUE_PER_MICROSECOND ) ); } /** * @brief Utility to safely calculate difference between server and client timestamps and * return the difference in the resolution of milliseconds as a signed 64 bit integer. * The calculated value represents the effective subtraction as ( @p serverTimeSec - @p clientTimeSec ). * * @note This utility SUPPORTS the cases of server and client timestamps being in different NTP eras, * and ASSUMES that the server and client systems are within 68 years of each other. * To handle the case of different NTP eras, this function calculates difference values for all * possible combinations of NTP eras of server and client times (i.e. 1. both timestamps in same era, * 2. server timestamp one era ahead, and 3. client timestamp being one era ahead), and determines * the NTP era configuration by choosing the difference value of the smallest absolute value. * * @param[in] pServerTime The server timestamp. * @param[in] pClientTime The client timestamp. * * @return The calculated difference between server and client times as a signed 64 bit integer. */ static int64_t safeTimeDifference( const SntpTimestamp_t * pServerTime, const SntpTimestamp_t * pClientTime ) { int64_t eraAdjustedDiff = 0; /* Convert the timestamps into 64 bit signed integer values of milliseconds. */ int64_t serverTime = ( ( int64_t ) pServerTime->seconds * 1000 ) + ( int64_t ) fractionsToMs( pServerTime->fractions ); int64_t clientTime = ( ( int64_t ) pClientTime->seconds * 1000 ) + ( int64_t ) fractionsToMs( pClientTime->fractions ); /* The difference between the 2 timestamps is calculated by determining the whether the timestamps * are present in the same NTP era or adjacent NTP eras (i.e. the NTP timestamp overflow case). */ /* First, calculate the first order time difference assuming that server and client times * are in the same NTP era. */ int64_t diffWithNoEraAdjustment = serverTime - clientTime; /* Store the absolute value of the time difference which will be used for comparison with * different cases of relative NTP era configuration of client and server times. */ int64_t absSameEraDiff = absoluteOf( diffWithNoEraAdjustment ); /* If the absolute difference value is 2^31 seconds, it means that the server and client times are * away by exactly half the range of SNTP timestamp "second" values representable in unsigned 32 bits. * In such a case, irrespective of whether the 2 systems exist in the same or adjacent NTP eras, the * time difference calculated between the systems will ALWAYS yield the same value when viewed from * all NTP era configurations of the times. * For such a case, we will ASSUME that the server time is AHEAD of client time, and thus, generate * a positive clock-offset value. */ if( absSameEraDiff == CLOCK_OFFSET_MAX_TIME_DIFFERENCE ) { /* It does not matter whether server and client are in the same era for this * special case as the difference value for both same and adjacent eras will yield * the same absolute value of 2^31.*/ eraAdjustedDiff = CLOCK_OFFSET_MAX_TIME_DIFFERENCE; } else { /* Determine if server time belongs to an NTP era different than the client time, and accordingly * choose the 64 bit representation of the first order difference to account for the era. * The logic for determining the relative era presence of the timestamps is by calculating the * first order difference (of "Server Time - Client Time") for all the 3 different era combinations * (1. both timestamps in same era, 2. server time one era ahead, 3. client time one era ahead) * and choosing the NTP era configuration that has the smallest first order difference value. */ int64_t diffWithServerEraAdjustment = serverTime + TOTAL_MILLISECONDS_IN_NTP_ERA - clientTime; /* This helps determine whether server is an * era ahead of client time. */ int64_t diffWithClientEraAdjustment = serverTime - ( TOTAL_MILLISECONDS_IN_NTP_ERA + clientTime ); /* This helps determine whether server is an * era behind of client time. */ /* Store the absolute value equivalents of all the time difference configurations * for easier comparison to smallest value from them. */ int64_t absServerEraAheadDiff = absoluteOf( diffWithServerEraAdjustment ); int64_t absClientEraAheadDiff = absoluteOf( diffWithClientEraAdjustment ); /* Determine the correct relative era of client and server times by checking which era * configuration of difference value represents the least difference. */ if( ( absSameEraDiff <= absServerEraAheadDiff ) && ( absSameEraDiff <= absClientEraAheadDiff ) ) { /* Both server and client times are in the same era. */ eraAdjustedDiff = diffWithNoEraAdjustment; } /* Check if server time is an NTP era ahead of client time. */ else if( absServerEraAheadDiff < absSameEraDiff ) { /* Server time is in NTP era 1 while client time is in NTP era 0. */ eraAdjustedDiff = diffWithServerEraAdjustment; } /* Now, we know that the client time is an era ahead of server time. */ else { /* Server time is in NTP era 0 while client time is in NTP era 1. */ eraAdjustedDiff = diffWithClientEraAdjustment; } } return eraAdjustedDiff; } /** * @brief Utility to calculate clock offset of system relative to the * server using the on-wire protocol specified in the NTPv4 specification. * For more information on on-wire protocol, refer to * [RFC 5905 Section 8](https://tools.ietf.org/html/rfc5905#section-8). * * @note The following diagram explains the calculation of the clock * offset: * * T2 T3 * --------------------------------- <----- *SNTP/NTP server* * /\ \ * / \ * Request* / \ *Response* * / \/ * --------------------------------- <----- *SNTP client* * T1 T4 * * The four most recent timestamps, T1 through T4, are used to compute * the clock offset of SNTP client relative to the server where: * * T1 = Client Request Transmit Time * T2 = Server Receive Time (of client request) * T3 = Server Response Transmit Time * T4 = Client Receive Time (of server response) * * Clock Offset = T(NTP/SNTP server) - T(SNTP client) * = [( T2 - T1 ) + ( T3 - T4 )] * --------------------------- * 2 * * @note Both NTPv4 and SNTPv4 specifications suggest calculating the * clock offset value, if possible. As the timestamp format uses 64 bit * integer and there exist 2 orders of arithmetic calculations on the * timestamp values (subtraction followed by addition as shown in the * diagram above), the clock offset for the system can be calculated * ONLY if the value can be represented in 62 significant bits and 2 sign * bits i.e. if the system clock is within 34 years (in the future or past) * of the server time. * * @param[in] pClientTxTime The system time of sending the SNTP request. * This is the same as "T1" in the above diagram. * @param[in] pServerRxTime The server time of receiving the SNTP request * packet from the client. This is the same as "T2" in the above diagram. * @param[in] pServerTxTime The server time of sending the SNTP response * packet. This is the same as "T3" in the above diagram. * @param[in] pClientRxTime The system time of receiving the SNTP response * from the server. This is the same as "T4" in the above diagram. * @param[out] pClockOffset This will be filled with the calculated offset value * of the system clock relative to the server time with the assumption that the * system clock is within 68 years of server time. */ static void calculateClockOffset( const SntpTimestamp_t * pClientTxTime, const SntpTimestamp_t * pServerRxTime, const SntpTimestamp_t * pServerTxTime, const SntpTimestamp_t * pClientRxTime, int64_t * pClockOffset ) { /* Variable for storing the first-order difference between timestamps. */ int64_t firstOrderDiffSend = 0; int64_t firstOrderDiffRecv = 0; assert( pClientTxTime != NULL ); assert( pServerRxTime != NULL ); assert( pServerTxTime != NULL ); assert( pClientRxTime != NULL ); assert( pClockOffset != NULL ); /* Perform first order difference of timestamps on the network send path i.e. T2 - T1. * Note: The calculated difference value will always represent years in the range of *[-68 years, +68 years]. */ firstOrderDiffSend = safeTimeDifference( pServerRxTime, pClientTxTime ); /* Perform first order difference of timestamps on the network receive path i.e. T3 - T4. * Note: The calculated difference value will always represent years in the range of *[-68 years, +68 years]. */ firstOrderDiffRecv = safeTimeDifference( pServerTxTime, pClientRxTime ); /* Now calculate the system clock-offset relative to server time as the average of the * first order difference of timestamps in both directions of network path. * Note: This will ALWAYS represent offset in the range of [-68 years, +68 years]. */ *pClockOffset = ( firstOrderDiffSend + firstOrderDiffRecv ) / 2; } /** * @brief Parse a SNTP response packet by determining whether it is a rejected * or accepted response to an SNTP request, and accordingly, populate the * @p pParsedResponse parameter with the parsed data. * * @note If the server has rejected the request with the a Kiss-o'-Death message, * then this function will set the associated rejection code in the output parameter * while setting the remaining members to zero. * If the server has accepted the time request, then the function will set the * rejectedResponseCode member of the output parameter to #SNTP_KISS_OF_DEATH_CODE_NONE, * and set the other the members with appropriate data extracted from the response * packet. * * @param[in] pResponsePacket The SNTP response packet from server to parse. * @param[in] pRequestTxTime The system time (in SNTP timestamp format) of * sending the SNTP request to server. * @param[in] pResponseRxTime The system time (in SNTP timestamp format) of * receiving the SNTP response from server. * @param[out] pParsedResponse The parameter that will be populated with data * parsed from the response packet, @p pResponsePacket. * * @return This function returns one of the following: * - #SntpSuccess if the server response does not represent a Kiss-o'-Death * message. * - #SntpRejectedResponseChangeServer if the server rejected with a code * indicating that client cannot be retry requests to it. * - #SntpRejectedResponseRetryWithBackoff if the server rejected with a code * indicating that client should back-off before retrying request. * - #SntpRejectedResponseOtherCode if the server rejected with a code * other than "DENY", "RSTR" and "RATE". */ static SntpStatus_t parseValidSntpResponse( const SntpPacket_t * pResponsePacket, const SntpTimestamp_t * pRequestTxTime, const SntpTimestamp_t * pResponseRxTime, SntpResponseData_t * pParsedResponse ) { SntpStatus_t status = SntpSuccess; assert( pResponsePacket != NULL ); assert( pResponseRxTime != NULL ); assert( pParsedResponse != NULL ); /* Clear the output parameter memory to zero. */ ( void ) memset( pParsedResponse, 0, sizeof( *pParsedResponse ) ); /* Determine if the server has accepted or rejected the request for time. */ if( pResponsePacket->stratum == SNTP_KISS_OF_DEATH_STRATUM ) { /* Server has sent a Kiss-o'-Death message i.e. rejected the request. */ /* Extract the kiss-code sent by the server from the "Reference ID" field * of the SNTP packet. */ pParsedResponse->rejectedResponseCode = readWordFromNetworkByteOrderMemory( &pResponsePacket->refId ); /* Determine the return code based on the Kiss-o'-Death code. */ switch( pParsedResponse->rejectedResponseCode ) { case KOD_CODE_DENY_UINT_VALUE: case KOD_CODE_RSTR_UINT_VALUE: status = SntpRejectedResponseChangeServer; break; case KOD_CODE_RATE_UINT_VALUE: status = SntpRejectedResponseRetryWithBackoff; break; default: status = SntpRejectedResponseOtherCode; break; } } else { /* Server has responded successfully to the time request. */ SntpTimestamp_t serverRxTime; /* Map of integer value to SntpLeapSecondInfo_t enumeration type for the * "Leap Indicator" field in the first byte of an SNTP packet. * Note: This map is used to not violate MISRA Rule 10.5 when directly * converting an integer to enumeration type. */ const SntpLeapSecondInfo_t leapIndicatorTypeMap[] = { NoLeapSecond, LastMinuteHas61Seconds, LastMinuteHas59Seconds, AlarmServerNotSynchronized }; /* Set the Kiss-o'-Death code value to NULL as server has responded favorably * to the time request. */ pParsedResponse->rejectedResponseCode = SNTP_KISS_OF_DEATH_CODE_NONE; /* Fill the output parameter with the server time which is the * "transmit" time in the response packet. */ pParsedResponse->serverTime.seconds = readWordFromNetworkByteOrderMemory( &pResponsePacket->transmitTime.seconds ); pParsedResponse->serverTime.fractions = readWordFromNetworkByteOrderMemory( &pResponsePacket->transmitTime.fractions ); /* Extract information of any upcoming leap second from the response. */ pParsedResponse->leapSecondType = leapIndicatorTypeMap[ ( pResponsePacket->leapVersionMode >> SNTP_LEAP_INDICATOR_LSB_POSITION ) ]; /* Store the "receive" time in SNTP response packet in host order. */ serverRxTime.seconds = readWordFromNetworkByteOrderMemory( &pResponsePacket->receiveTime.seconds ); serverRxTime.fractions = readWordFromNetworkByteOrderMemory( &pResponsePacket->receiveTime.fractions ); /* Calculate system clock offset relative to server time, if possible, within * the 64 bit integer width of the SNTP timestamp. */ calculateClockOffset( pRequestTxTime, &serverRxTime, &pParsedResponse->serverTime, pResponseRxTime, &pParsedResponse->clockOffsetMs ); } return status; } SntpStatus_t Sntp_SerializeRequest( SntpTimestamp_t * pRequestTime, uint32_t randomNumber, void * pBuffer, size_t bufferSize ) { SntpStatus_t status = SntpSuccess; if( pRequestTime == NULL ) { status = SntpErrorBadParameter; } else if( pBuffer == NULL ) { status = SntpErrorBadParameter; } else if( bufferSize < SNTP_PACKET_BASE_SIZE ) { status = SntpErrorBufferTooSmall; } /* Zero timestamps for client request time is not allowed to protect against * attack spoofing server response containing zero value for "originate timestamp". * Note: In SNTP/NTP communication, the "originate timestamp" of a valid server response * matches the "transmit timestamp" in corresponding client request packet. */ else if( isZeroTimestamp( pRequestTime ) == true ) { status = SntpErrorBadParameter; } else { /* MISRA Ref 11.5.1 [Void pointer assignment] */ /* More details at: https://github.com/FreeRTOS/coreSNTP/blob/main/MISRA.md#rule-115 */ /* coverity[misra_c_2012_rule_11_5_violation] */ SntpPacket_t * pRequestPacket = ( SntpPacket_t * ) pBuffer; /* Fill the buffer with zero as most fields are zero for a standard SNTP * request packet.*/ ( void ) memset( pBuffer, 0, sizeof( SntpPacket_t ) ); /* Set the first byte of the request packet for "Version" and "Mode" fields */ pRequestPacket->leapVersionMode = 0U /* Leap Indicator */ | ( SNTP_VERSION << SNTP_VERSION_LSB_POSITION ) /* Version Number */ | SNTP_MODE_CLIENT /* Mode */; /* Add passed random number to non-significant bits of the fractions part * of the transmit timestamp. * This is suggested by the SNTPv4 (and NTPv4) specification(s) * to protect against replay attacks. Refer to RFC 4330 Section 3 for * more information. * Adding random bits to the least significant 16 bits of the fractions * part of the timestamp affects only ~15 microseconds of information * (calculated as 0xFFFF * 232 picoseconds). */ pRequestTime->fractions = ( pRequestTime->fractions | ( randomNumber >> 16 ) ); /* Update the request buffer with request timestamp in network byte order. */ fillWordMemoryInNetworkOrder( &pRequestPacket->transmitTime.seconds, pRequestTime->seconds ); fillWordMemoryInNetworkOrder( &pRequestPacket->transmitTime.fractions, pRequestTime->fractions ); } return status; } SntpStatus_t Sntp_DeserializeResponse( const SntpTimestamp_t * pRequestTime, const SntpTimestamp_t * pResponseRxTime, const void * pResponseBuffer, size_t bufferSize, SntpResponseData_t * pParsedResponse ) { SntpStatus_t status = SntpSuccess; /* MISRA Ref 11.5.1 [Void pointer assignment] */ /* More details at: https://github.com/FreeRTOS/coreSNTP/blob/main/MISRA.md#rule-115 */ /* coverity[misra_c_2012_rule_11_5_violation] */ const SntpPacket_t * pResponsePacket = ( const SntpPacket_t * ) pResponseBuffer; if( ( pRequestTime == NULL ) || ( pResponseRxTime == NULL ) || ( pResponseBuffer == NULL ) || ( pParsedResponse == NULL ) ) { status = SntpErrorBadParameter; } else if( bufferSize < SNTP_PACKET_BASE_SIZE ) { status = SntpErrorBufferTooSmall; } /* Zero timestamps for client request time is not allowed to protect against * attack spoofing server response containing zero value for "originate timestamp". * Note: In SNTP/NTP communication, the "originate timestamp" of a valid server response * matches the "transmit timestamp" in corresponding client request packet. */ else if( isZeroTimestamp( pRequestTime ) == true ) { status = SntpErrorBadParameter; } /* Check if the packet represents a server in the "Mode" field. */ else if( ( pResponsePacket->leapVersionMode & SNTP_MODE_BITS_MASK ) != SNTP_MODE_SERVER ) { status = SntpInvalidResponse; } /* Check if any of the timestamps in the response packet are zero, which is invalid. * Note: This is done to protect against a nuanced server spoofing attack where if the * SNTP client resets its internal state of "Client transmit timestamp" (OR "originate * timestamp" from server perspective) to zero as a protection against replay attack, an * an attacker with this knowledge of the client operation can spoof a server response * containing the "originate timestamp" as zero. Thus, to protect against such attack, * a server response packet with any zero timestamp is rejected. */ else if( ( isZeroTimestamp( &pResponsePacket->originTime ) == true ) || ( isZeroTimestamp( &pResponsePacket->receiveTime ) == true ) || ( isZeroTimestamp( &pResponsePacket->transmitTime ) == true ) ) { status = SntpInvalidResponse; } /* Validate that the server has sent the client's request timestamp in the * "originate" timestamp field of the response. */ else if( ( pRequestTime->seconds != readWordFromNetworkByteOrderMemory( &pResponsePacket->originTime.seconds ) ) || ( pRequestTime->fractions != readWordFromNetworkByteOrderMemory( &pResponsePacket->originTime.fractions ) ) ) { status = SntpInvalidResponse; } else { /* As the response packet is valid, parse more information from it and * populate the output parameter. */ status = parseValidSntpResponse( pResponsePacket, pRequestTime, pResponseRxTime, pParsedResponse ); } return status; } SntpStatus_t Sntp_CalculatePollInterval( uint16_t clockFreqTolerance, uint16_t desiredAccuracy, uint32_t * pPollInterval ) { SntpStatus_t status = SntpSuccess; if( ( clockFreqTolerance == 0U ) || ( desiredAccuracy == 0U ) || ( pPollInterval == NULL ) ) { status = SntpErrorBadParameter; } else { uint32_t exactIntervalForAccuracy = 0U; uint8_t log2PollInterval = 0U; /* Calculate the poll interval required for achieving the exact desired clock accuracy * with the following formulae: * * System Clock Drift Rate ( microseconds / second ) = Clock Frequency Tolerance (in PPM ) * Maximum Clock Drift ( milliseconds ) = Desired Accuracy ( milliseconds ) * * Poll Interval ( seconds ) = Maximum Clock Drift * --------------------------- * System Clock Drift Rate * * = Maximum Drift ( milliseconds ) * 1000 ( microseconds / millisecond ) * ------------------------------------------------------------------------ * System Clock Drift Rate ( microseconds / second ) * * = Desired Accuracy * 1000 * ------------------------------ * Clock Frequency Tolerance */ exactIntervalForAccuracy = ( ( uint32_t ) desiredAccuracy * 1000U ) / clockFreqTolerance; /* Check if calculated poll interval value falls in the supported range of seconds. */ if( exactIntervalForAccuracy == 0U ) { /* Poll interval value is less than 1 second, and is not supported by the function. */ status = SntpZeroPollInterval; } else { /* To calculate the log 2 value of the exact poll interval value, first determine the highest * bit set in the value. */ while( exactIntervalForAccuracy != 0U ) { log2PollInterval++; exactIntervalForAccuracy /= 2U; } /* Convert the highest bit in the exact poll interval value to the nearest integer * value lower or equal to the log2 of the exact poll interval value. */ log2PollInterval--; /* Calculate the poll interval as the closest exponent of 2 value that achieves * equal or higher accuracy than the desired accuracy. */ *pPollInterval = ( ( ( uint32_t ) 1U ) << log2PollInterval ); } } return status; } SntpStatus_t Sntp_ConvertToUnixTime( const SntpTimestamp_t * pSntpTime, uint32_t * pUnixTimeSecs, uint32_t * pUnixTimeMicrosecs ) { SntpStatus_t status = SntpSuccess; if( ( pSntpTime == NULL ) || ( pUnixTimeSecs == NULL ) || ( pUnixTimeMicrosecs == NULL ) ) { status = SntpErrorBadParameter; } /* Check if passed time does not lie in the [UNIX epoch in 1970, UNIX time overflow in 2038] time range. */ else if( ( pSntpTime->seconds > SNTP_TIME_AT_LARGEST_UNIX_TIME_SECS ) && ( pSntpTime->seconds < SNTP_TIME_AT_UNIX_EPOCH_SECS ) ) { /* The SNTP timestamp is outside the supported time range for conversion. */ status = SntpErrorTimeNotSupported; } else { /* Handle case when timestamp represents date in SNTP era 1 * (i.e. time from 7 Feb 2036 6:28:16 UTC onwards). */ if( pSntpTime->seconds <= SNTP_TIME_AT_LARGEST_UNIX_TIME_SECS ) { /* Unix Time ( seconds ) = Seconds Duration in * [UNIX epoch, SNTP Era 1 Epoch Time] * + * Sntp Time since Era 1 Epoch */ *pUnixTimeSecs = UNIX_TIME_SECS_AT_SNTP_ERA_1_SMALLEST_TIME + pSntpTime->seconds; } /* Handle case when SNTP timestamp is in SNTP era 1 time range. */ else { *pUnixTimeSecs = pSntpTime->seconds - SNTP_TIME_AT_UNIX_EPOCH_SECS; } /* Convert SNTP fractions to microseconds for UNIX time. */ *pUnixTimeMicrosecs = pSntpTime->fractions / SNTP_FRACTION_VALUE_PER_MICROSECOND; } return status; }