coreSNTP v1.2.0
Client library for synchronizing device time with internet time using Simple Network Time Protocol (SNTP)
core_sntp_serializer.c File Reference

Implementation of the Serializer API of the coreSNTP library. More...

#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include "core_sntp_serializer.h"
#include "core_sntp_config_defaults.h"

Data Structures

struct  SntpPacket_t
 Structure representing an SNTP packet header. For more information on SNTP packet format, refer to RFC 4330 Section 4. More...
 

Macros

#define SNTP_VERSION   ( 4U )
 The version of SNTP supported by the coreSNTP library by complying with the SNTPv4 specification defined in RFC 4330.
 
#define SNTP_MODE_BITS_MASK   ( 0x07U )
 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. More...
 
#define SNTP_MODE_CLIENT   ( 3U )
 The value indicating a "client" in the "Mode" field of an SNTP packet. More...
 
#define SNTP_MODE_SERVER   ( 4U )
 The value indicating a "server" in the "Mode" field of an SNTP packet. More...
 
#define SNTP_LEAP_INDICATOR_LSB_POSITION   ( 6 )
 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. More...
 
#define SNTP_KISS_OF_DEATH_STRATUM   ( 0U )
 Value of Stratum field in SNTP packet representing a Kiss-o'-Death message from server.
 
#define SNTP_VERSION_LSB_POSITION   ( 3 )
 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. More...
 
#define KOD_CODE_DENY_UINT_VALUE   ( 0x44454e59U )
 The integer value of the Kiss-o'-Death ASCII code, "DENY", used for comparison with data in an SNTP response. More...
 
#define KOD_CODE_RSTR_UINT_VALUE   ( 0x52535452U )
 The integer value of the Kiss-o'-Death ASCII code, "RSTR", used for comparison with data in an SNTP response. More...
 
#define KOD_CODE_RATE_UINT_VALUE   ( 0x52415445U )
 The integer value of the Kiss-o'-Death ASCII code, "RATE", used for comparison with data in an SNTP response. More...
 
#define CLOCK_OFFSET_MAX_TIME_DIFFERENCE   ( ( ( ( int64_t ) INT32_MAX + 1 ) * 1000 ) )
 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. More...
 
#define TOTAL_MILLISECONDS_IN_NTP_ERA   ( ( ( int64_t ) UINT32_MAX + 1 ) * 1000 )
 Macro to represent the total number of milliseconds that are represented in an NTP era period. This macro represents a duration of ~136 years. More...
 

Functions

static void fillWordMemoryInNetworkOrder (uint32_t *pWordMemory, uint32_t data)
 Utility macro to fill 32-bit integer in word-sized memory in network byte (or Big Endian) order. More...
 
static uint32_t readWordFromNetworkByteOrderMemory (const uint32_t *ptr)
 Utility macro to generate a 32-bit integer from memory containing integer in network (or Big Endian) byte order. More...
 
static int64_t absoluteOf (int64_t value)
 Utility to return absolute (or positively signed) value of an signed 64 bit integer. More...
 
static bool isZeroTimestamp (const SntpTimestamp_t *pTime)
 Utility to determine whether a timestamp represents a zero timestamp value. More...
 
static uint32_t fractionsToMs (uint32_t fractions)
 Utility to convert the "fractions" part of an SNTP timestamp to milliseconds duration of time. More...
 
static int64_t safeTimeDifference (const SntpTimestamp_t *pServerTime, const SntpTimestamp_t *pClientTime)
 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 ( serverTimeSec - clientTimeSec ). More...
 
static void calculateClockOffset (const SntpTimestamp_t *pClientTxTime, const SntpTimestamp_t *pServerRxTime, const SntpTimestamp_t *pServerTxTime, const SntpTimestamp_t *pClientRxTime, int64_t *pClockOffset)
 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. More...
 
static SntpStatus_t parseValidSntpResponse (const SntpPacket_t *pResponsePacket, const SntpTimestamp_t *pRequestTxTime, const SntpTimestamp_t *pResponseRxTime, SntpResponseData_t *pParsedResponse)
 Parse a SNTP response packet by determining whether it is a rejected or accepted response to an SNTP request, and accordingly, populate the pParsedResponse parameter with the parsed data. More...
 
SntpStatus_t Sntp_SerializeRequest (SntpTimestamp_t *pRequestTime, uint32_t randomNumber, void *pBuffer, size_t bufferSize)
 Serializes an SNTP request packet to use for querying a time server. More...
 
SntpStatus_t Sntp_DeserializeResponse (const SntpTimestamp_t *pRequestTime, const SntpTimestamp_t *pResponseRxTime, const void *pResponseBuffer, size_t bufferSize, SntpResponseData_t *pParsedResponse)
 De-serializes an SNTP packet received from a server as a response to a SNTP request. More...
 
SntpStatus_t Sntp_CalculatePollInterval (uint16_t clockFreqTolerance, uint16_t desiredAccuracy, uint32_t *pPollInterval)
 Utility to calculate the poll interval of sending periodic time queries to servers to achieve a desired system clock accuracy for a given frequency tolerance of the system clock. More...
 
SntpStatus_t Sntp_ConvertToUnixTime (const SntpTimestamp_t *pSntpTime, uint32_t *pUnixTimeSecs, uint32_t *pUnixTimeMicrosecs)
 Utility to convert SNTP timestamp (that uses 1st Jan 1900 as the epoch) to UNIX timestamp (that uses 1st Jan 1970 as the epoch). More...
 

Detailed Description

Implementation of the Serializer API of the coreSNTP library.

Macro Definition Documentation

◆ SNTP_MODE_BITS_MASK

#define SNTP_MODE_BITS_MASK   ( 0x07U )

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 for more information.

◆ SNTP_MODE_CLIENT

#define SNTP_MODE_CLIENT   ( 3U )

The value indicating a "client" in the "Mode" field of an SNTP packet.

Note
Refer to the RFC 4330 Section 4 for more information.

◆ SNTP_MODE_SERVER

#define SNTP_MODE_SERVER   ( 4U )

The value indicating a "server" in the "Mode" field of an SNTP packet.

Note
Refer to the RFC 4330 Section 4 for more information.

◆ SNTP_LEAP_INDICATOR_LSB_POSITION

#define SNTP_LEAP_INDICATOR_LSB_POSITION   ( 6 )

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 for more information.

◆ SNTP_VERSION_LSB_POSITION

#define SNTP_VERSION_LSB_POSITION   ( 3 )

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 for more information.

◆ KOD_CODE_DENY_UINT_VALUE

#define KOD_CODE_DENY_UINT_VALUE   ( 0x44454e59U )

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 for more information.

◆ KOD_CODE_RSTR_UINT_VALUE

#define KOD_CODE_RSTR_UINT_VALUE   ( 0x52535452U )

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 for more information.

◆ KOD_CODE_RATE_UINT_VALUE

#define KOD_CODE_RATE_UINT_VALUE   ( 0x52415445U )

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 for more information.

◆ CLOCK_OFFSET_MAX_TIME_DIFFERENCE

#define CLOCK_OFFSET_MAX_TIME_DIFFERENCE   ( ( ( ( int64_t ) INT32_MAX + 1 ) * 1000 ) )

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.

◆ TOTAL_MILLISECONDS_IN_NTP_ERA

#define TOTAL_MILLISECONDS_IN_NTP_ERA   ( ( ( int64_t ) UINT32_MAX + 1 ) * 1000 )

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).

Function Documentation

◆ fillWordMemoryInNetworkOrder()

static void fillWordMemoryInNetworkOrder ( uint32_t *  pWordMemory,
uint32_t  data 
)
static

Utility macro to fill 32-bit integer in word-sized memory in network byte (or Big Endian) order.

Parameters
[out]pWordMemoryPointer to the word-sized memory in which the 32-bit integer will be filled.
[in]dataThe 32-bit integer to fill in the 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.

◆ readWordFromNetworkByteOrderMemory()

static uint32_t readWordFromNetworkByteOrderMemory ( const uint32_t *  ptr)
static

Utility macro to generate a 32-bit integer from memory containing integer in network (or Big Endian) byte order.

Parameters
[in]ptrPointer to the memory containing 32-bit integer in network byte order.
Returns
The host representation of the 32-bit integer in the passed word memory.

◆ absoluteOf()

static int64_t absoluteOf ( int64_t  value)
static

Utility to return absolute (or positively signed) value of an signed 64 bit integer.

Parameters
[in]valueThe integer to return the absolute value of.
Returns
The absolute value of value.

◆ isZeroTimestamp()

static bool isZeroTimestamp ( const SntpTimestamp_t pTime)
static

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.
Parameters
[in]pTimeThe timestamp whose value is to be inspected for zero value.
Returns
true if the timestamp is zero; otherwise false.

◆ fractionsToMs()

static uint32_t fractionsToMs ( uint32_t  fractions)
static

Utility to convert the "fractions" part of an SNTP timestamp to milliseconds duration of time.

Parameters
[in]fractionsThe fractions value.
Returns
The milliseconds equivalent of the fractions value.

◆ safeTimeDifference()

static int64_t safeTimeDifference ( const SntpTimestamp_t pServerTime,
const SntpTimestamp_t pClientTime 
)
static

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 ( serverTimeSec - 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,
  1. 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.
Parameters
[in]pServerTimeThe server timestamp.
[in]pClientTimeThe client timestamp.
Returns
The calculated difference between server and client times as a signed 64 bit integer.

◆ calculateClockOffset()

static void calculateClockOffset ( const SntpTimestamp_t pClientTxTime,
const SntpTimestamp_t pServerRxTime,
const SntpTimestamp_t pServerTxTime,
const SntpTimestamp_t pClientRxTime,
int64_t *  pClockOffset 
)
static

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.

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.
Parameters
[in]pClientTxTimeThe system time of sending the SNTP request. This is the same as "T1" in the above diagram.
[in]pServerRxTimeThe server time of receiving the SNTP request packet from the client. This is the same as "T2" in the above diagram.
[in]pServerTxTimeThe server time of sending the SNTP response packet. This is the same as "T3" in the above diagram.
[in]pClientRxTimeThe system time of receiving the SNTP response from the server. This is the same as "T4" in the above diagram.
[out]pClockOffsetThis 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.

◆ parseValidSntpResponse()

static SntpStatus_t parseValidSntpResponse ( const SntpPacket_t pResponsePacket,
const SntpTimestamp_t pRequestTxTime,
const SntpTimestamp_t pResponseRxTime,
SntpResponseData_t pParsedResponse 
)
static

Parse a SNTP response packet by determining whether it is a rejected or accepted response to an SNTP request, and accordingly, populate the 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.
Parameters
[in]pResponsePacketThe SNTP response packet from server to parse.
[in]pRequestTxTimeThe system time (in SNTP timestamp format) of sending the SNTP request to server.
[in]pResponseRxTimeThe system time (in SNTP timestamp format) of receiving the SNTP response from server.
[out]pParsedResponseThe parameter that will be populated with data parsed from the response packet, pResponsePacket.
Returns
This function returns one of the following:

◆ Sntp_SerializeRequest()

SntpStatus_t Sntp_SerializeRequest ( SntpTimestamp_t pRequestTime,
uint32_t  randomNumber,
void *  pBuffer,
size_t  bufferSize 
)

Serializes an SNTP request packet to use for querying a time server.

This function will fill only SNTP_PACKET_BASE_SIZE bytes of data in the passed buffer.

Parameters
[in,out]pRequestTimeThe current time of the system, expressed as time since the SNTP epoch (i.e. 0h of 1st Jan 1900 ). This time will be serialized in the SNTP request packet. The function will use this parameter to return the timestamp serialized in the SNTP request. To protect against attacks spoofing server responses, the timestamp MUST NOT be zero in value.
[in]randomNumberA random number (generated by a True Random Generator) for use in the SNTP request packet to protect against replay attacks as suggested by SNTPv4 specification. For more information, refer to RFC 4330 Section 3.
[out]pBufferThe buffer that will be populated with the serialized SNTP request packet.
[in]bufferSizeThe size of the pBuffer buffer. It should be at least SNTP_PACKET_BASE_SIZE bytes in size.
Note
It is recommended to use a True Random Generator (TRNG) to generate the random number.
The application MUST save the pRequestTime value for de-serializing the server response with Sntp_DeserializeResponse API.
Returns
This function returns one of the following:

◆ Sntp_DeserializeResponse()

SntpStatus_t Sntp_DeserializeResponse ( const SntpTimestamp_t pRequestTime,
const SntpTimestamp_t pResponseRxTime,
const void *  pResponseBuffer,
size_t  bufferSize,
SntpResponseData_t pParsedResponse 
)

De-serializes an SNTP packet received from a server as a response to a SNTP request.

This function will parse only the SNTP_PACKET_BASE_SIZE bytes of data in the passed buffer.

Note
If the server has sent a Kiss-o'-Death message to reject the associated time request, the API function will return the appropriate return code and, also, provide the ASCII code (of fixed length, SNTP_KISS_OF_DEATH_CODE_LENGTH bytes) in the SntpResponseData_t.rejectedResponseCode member of pParsedResponse parameter, parsed from the response packet. The application SHOULD respect the server rejection and take appropriate action based on the rejection code. If the server response represents an accepted SNTP client request, then the API function will set the SntpResponseData_t.rejectedResponseCode member of pParsedResponse parameter to SNTP_KISS_OF_DEATH_CODE_NONE.
If the server has positively responded with its clock time, then this API function will calculate the clock-offset. For the clock-offset to be correctly calculated, the system clock MUST be within ~68 years (or 2^31 seconds) of the server time mentioned. This function supports clock-offset calculation when server and client timestamps are in adjacent NTP eras, with one system is in NTP era 0 (i.e. before 7 Feb 2036 6h:28m:14s UTC) and another system in NTP era 1 (on or after 7 Feb 2036 6h:28m:14s UTC).
In the special case when the server and client times are exactly 2^31 seconds apart, the library ASSUMES that the server time is ahead of the client time, and returns the positive clock-offset value of INT32_MAX seconds.
Parameters
[in]pRequestTimeThe system time used in the SNTP request packet that is associated with the server response. This MUST be the same as the time returned by the Sntp_SerializeRequest API. To protect against attacks spoofing server responses, this timestamp MUST NOT be zero in value.
[in]pResponseRxTimeThe time of the system, expressed as time since the SNTP epoch (i.e. 0h of 1st Jan 1900 ), at receiving SNTP response from server. This time will be used to calculate system clock offset relative to server.
[in]pResponseBufferThe buffer containing the SNTP response from the server.
[in]bufferSizeThe size of the pResponseBuffer containing the SNTP response. It MUST be at least SNTP_PACKET_BASE_SIZE bytes long for a valid SNTP response.
[out]pParsedResponseThe information parsed from the SNTP response packet. If possible to calculate without overflow, it also contains the system clock offset relative to the server time.
Returns
This function returns one of the following:

◆ Sntp_CalculatePollInterval()

SntpStatus_t Sntp_CalculatePollInterval ( uint16_t  clockFreqTolerance,
uint16_t  desiredAccuracy,
uint32_t *  pPollInterval 
)

Utility to calculate the poll interval of sending periodic time queries to servers to achieve a desired system clock accuracy for a given frequency tolerance of the system clock.

For example, from the SNTPv4 specification, "if the frequency tolerance is 200 parts per million (PPM) and the required accuracy is one minute, the maximum timeout is about 3.5 days". In this example, the system clock frequency tolerance is 200 PPM and the desired accuracy is 60000 milliseconds (or 1 minute) for which this API function will return the maximum poll interval value as 2^18 seconds (or ~3 days).

Note
The poll interval returned is a power of 2, which is the standard way to represent the value. According to the SNTPv4 specification Best Practices, an SNTP client SHOULD NOT have a poll interval less than 15 seconds. https://tools.ietf.org/html/rfc4330#section-10. This API function DOES NOT support poll interval calculation less than 1 second.
Parameters
[in]clockFreqToleranceThe frequency tolerance of system clock in parts per million (PPM) units. This parameter MUST be non-zero.
[in]desiredAccuracyThe acceptable maximum drift, in milliseconds, for the system clock. The maximum value (0xFFFF) represents ~1 minute of desired clock accuracy. This parameter MUST be non-zero.
[out]pPollIntervalThis is filled with the poll interval, in seconds calculated as the closest power of 2 value that will achieve either the exact desired or higher clock accuracy desiredAccuracy, for the given clock frequency tolerance, clockFreqTolerance.
Returns
Returns one of the following:

◆ Sntp_ConvertToUnixTime()

SntpStatus_t Sntp_ConvertToUnixTime ( const SntpTimestamp_t pSntpTime,
uint32_t *  pUnixTimeSecs,
uint32_t *  pUnixTimeMicrosecs 
)

Utility to convert SNTP timestamp (that uses 1st Jan 1900 as the epoch) to UNIX timestamp (that uses 1st Jan 1970 as the epoch).

Note
This function can ONLY handle conversions of SNTP timestamps that lie in the range from 1st Jan 1970 0h 0m 0s, the UNIX epoch time, to 19th Jan 2038 3h 14m 7s, the maximum UNIX time that can be represented in a signed 32 bit integer. (The limitation is to support systems that use signed 32-bit integer to represent the seconds part of the UNIX time.)
This function supports overflow of the SNTP timestamp (from the 7 Feb 2036 6h 28m 16s time, i.e. SNTP era 1) by treating the timestamps with seconds part in the range [0, 61,505,152] seconds where the upper limit represents the UNIX overflow time (i.e. 19 Jan 2038 3h 14m 7s) for systems that use signed 32-bit integer to represent time.
Parameters
[in]pSntpTimeThe SNTP timestamp to convert to UNIX time.
[out]pUnixTimeSecsThis will be filled with the seconds part of the UNIX time equivalent of the SNTP time, pSntpTime.
[out]pUnixTimeMicrosecsThis will be filled with the microseconds part of the UNIX time equivalent of the SNTP time, pSntpTime.
Returns
Returns one of the following: