coreSNTP v1.1.0
Client library for synchronizing device time with internet time using Simple Network Time Protocol (SNTP)
Porting Guide

Guide for porting coreSNTP library to a new platform.

A port of coreSNTP library for a new platform must provide the following components:

  1. Logging Configuration Macros
  2. DNS Resolve Function
  3. UDP Transport Interface
  4. Get Time Function
  5. Set Time Function
  6. Authentication Interface

Configuration Macros for Logging

Macros for enabling logging that can be defined through the config header core_sntp_config.h, or passed in as compiler options.

Note
If a custom configuration header core_sntp_config.h is not provided, then the SNTP_DO_NOT_USE_CUSTOM_CONFIG macro must be defined.
See also
Configurations

The following logging macros are used throughout the library:

Here is an example implementation of logging macros for POSIX platforms

/************* Define Logging Macros using printf function ***********/
#define PrintfError( ... ) printf( "Error: "__VA_ARGS__ ); printf( "\n" )
#define PrintfWarn( ... ) printf( "Warn: "__VA_ARGS__ ); printf( "\n" )
#define PrintfInfo( ... ) printf( "Info: " __VA_ARGS__ ); printf( "\n" )
#define PrintfDebug( ... ) printf( "Debug: " __VA_ARGS__ ); printf( "\n" )
#ifdef LOGGING_LEVEL_ERROR
#define LogError( message ) PrintfError message
#elif defined( LOGGING_LEVEL_WARNING )
#define LogError( message ) PrintfError message
#define LogWarn( message ) PrintfWarn message
#elif defined( LOGGING_LEVEL_INFO )
#define LogError( message ) PrintfError message
#define LogWarn( message ) PrintfWarn message
#define LogInfo( message ) PrintfInfo message
#elif defined( LOGGING_LEVEL_DEBUG )
#define LogError( message ) PrintfError message
#define LogWarn( message ) PrintfWarn message
#define LogInfo( message ) PrintfInfo message
#define LogDebug( message ) PrintfDebug message
#endif /* ifdef LOGGING_LEVEL_ERROR */
/**************************************************/

DNS Resolve Function

The coreSNTP library requires a DNS Resolve interface that must be implemented to obtain the latest IPv4 address information of a time server before sending it a time request.

See also
DNS Resolve Function
Note
The coreSNTP library will re-resolve the DNS name of a time server on each attempt of requesting time from it. For efficiency of DNS resolution operations, your implementation can utilize DNS caching of resolved domains if your platform supports it.
/* Example POSIX implementation of SntpDnsReolve_t interface. */
static bool resolveDns( const SntpServerInfo_t * pServerAddr,
uint32_t * pIpV4Addr )
{
bool status = false;
int32_t dnsStatus = -1;
struct addrinfo hints;
struct addrinfo * pListHead = NULL;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = ( int32_t ) SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
dnsStatus = getaddrinfo( pServerAddr->pServerName, NULL, &hints, &pListHead );
if( dnsStatus == 0 )
{
struct sockaddr_in * pAddrInfo = ( struct sockaddr_in * ) pListHead->ai_addr;
inet_ntop( pAddrInfo->sin_family,
&pAddrInfo->sin_addr,
( int8_t * ) pIpV4Addr,
INET_ADDRSTRLEN );
status = true;
}
freeaddrinfo( pListHead );
return status;
}
Structure representing information for a time server.
Definition: core_sntp_client.h:78
const char * pServerName
The time server name.
Definition: core_sntp_client.h:79

UDP Transport Interface

The coreSNTP library requires a UDP transport interface that must be implemented in order to send and receive SNTP packets over the network.

See also
UDP Transport Interface
Note
For security against unwanted server response packets, it is RECOMMENDED that the UDP socket that is used for implementing the UDP transport interface functions of performing network I/O is kept open ONLY during duration of an SNTP request-response iteration as opposed to keeping it always open across iterations. One way to achieve this is to open a new UDP socket before calling Sntp_SendTimeRequest API and close it after receiving server response (or timeout) with the Sntp_ReceiveTimeResponse API.

A port must implement functions corresponding to the following functions pointers:

  • UDP Transport Send: A function to send bytes on the network over UDP. It is RECOMMENDED to implement this function as non-blocking so the total block time can be managed by the Sntp_SendTimeRequest API.
/* Example POSIX implementation of the UdpTransportSendTo_t function of UDP transport interface. */
static int32_t UdpTransport_Send( NetworkContext_t * pNetworkContext,
uint32_t serverAddr,
uint16_t serverPort,
const void * pBuffer,
uint16_t bytesToSend )
{
int32_t bytesSent = -1, pollStatus = 1;
struct pollfd pollFds;
pollFds.events = POLLOUT | POLLPRI;
pollFds.revents = 0;
pollFds.fd = pNetworkContext->udpSocket;
/* Check if there is data to read from the socket. */
pollStatus = poll( &pollFds, 1, 0 );
if( pollStatus > 0 )
{
struct sockaddr_in addrInfo;
addrInfo.sin_family = AF_INET;
addrInfo.sin_port = htons( serverPort );
addrInfo.sin_addr.s_addr = htonl( serverAddr );
bytesSent = sendto( pNetworkContext->udpSocket,
pBuffer,
bytesToSend, 0,
( const struct sockaddr * ) &addrInfo,
sizeof( addrInfo ) );
}
else if( pollStatus == 0 )
{
bytesSent = 0;
}
return bytesSent;
}
struct NetworkContext NetworkContext_t
A user-defined type for context that is passed to the transport interface functions....
Definition: core_sntp_client.h:161
/* Example POSIX implementation of the UdpTransportRecvFrom_t function of UDP transport interface. */
static int32_t UdpTransport_Recv( NetworkContext_t * pNetworkContext,
uint32_t serverAddr,
uint16_t serverPort,
void * pBuffer,
uint16_t bytesToRecv )
{
int32_t bytesReceived = -1, pollStatus = 1;
struct pollfd pollFds;
pollFds.events = POLLIN | POLLPRI;
pollFds.revents = 0;
pollFds.fd = pNetworkContext->udpSocket;
/* Check if there is data to read from the socket. */
pollStatus = poll( &pollFds, 1, 0 );
if( pollStatus > 0 )
{
struct sockaddr_in addrInfo;
addrInfo.sin_family = AF_INET;
addrInfo.sin_port = htons( serverPort );
addrInfo.sin_addr.s_addr = htonl( serverAddr );
socklen_t addrLen = sizeof( addrInfo );
bytesReceived = recvfrom( pNetworkContext->udpSocket, pBuffer,
bytesToRecv, 0,
( struct sockaddr * ) &addrInfo,
&addrLen );
}
else if( pollStatus == 0 )
{
bytesReceived = 0;
}
return bytesReceived;
}

The above two functions take in a pointer to a NetworkContext_t, the typename of a struct NetworkContext. The NetworkContext struct must also be defined by the port, and ought to contain any information necessary to send and receive data with the UdpTransportSendTo_t and UdpTransportRecvFrom_t implementations, respectively:

/* Example definition of NetworkContext_t for UDP socket operations. */
struct NetworkContext
{
int udpSocket;
};

Get Time Function

The coreSNTP library uses this function to obtain time from system for tracking timeout durations as well as generating SNTP request packet.

See also
SntpGetTime_t

If the device does not have real-world time information (on device boot-up for example), it is acceptable for this function to provide the system-time that does not match the real-world time, because once a time information is received from a time server, the system time can be corrected to match the real-world time. Refer to the next section on how to correct the system time.

/* Example implementation of the SntpGetTime_t interface for POSIX platforms. */
static void sntpClient_GetTime( SntpTimestamp_t * pCurrentTime )
{
struct timespec currTime;
( void ) clock_gettime( CLOCK_REALTIME, &currTime );
pCurrentTime->seconds = currTime.tv_sec;
pCurrentTime->fractions = ( currTime.tv_sec / 1000 ) * SNTP_FRACTION_VALUE_PER_MICROSECOND;
}
#define SNTP_FRACTION_VALUE_PER_MICROSECOND
Number of SNTP timestamp fractions in 1 microsecond.
Definition: core_sntp_serializer.h:66
Structure representing an SNTP timestamp.
Definition: core_sntp_serializer.h:282
uint32_t fractions
The fractions part of the SNTP timestamp with resolution of 2^(-32) ~ 232 picoseconds.
Definition: core_sntp_serializer.h:284
uint32_t seconds
Number of seconds since epoch time.
Definition: core_sntp_serializer.h:283

Set Time Function

The coreSNTP library calls this function to notify the device about the latest time received from a time server as well as the clock drift of the system time from the server time.

/* Example implementation of the SntpSetTime_t interface for POSIX platforms. */
static void sntpClient_SetTime( const SntpServerInfo_t * pTimeServer,
const SntpTimestamp_t * pServerTime,
int64_t clockOffsetMs,
SntpLeapSecondInfo_t leapSecondInfo )
{
/* @[code_example_sntp_converttounixtime] */
uint32_t unixSecs;
uint32_t unixMs;
SntpStatus_t status = Sntp_ConvertToUnixTime( pServerTime, &unixSecs, &unixMs );
/* @[code_example_sntp_converttounixtime] */
assert( status == SntpSuccess );
struct timespec serverTime =
{
.tv_sec = unixSecs,
.tv_nsec = unixMs * 1000
};
clock_settime( CLOCK_REALTIME, &serverTime );
}
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 ...
Definition: core_sntp_serializer.c:802
SntpLeapSecondInfo_t
Enumeration for leap second information that an SNTP server can send its response to a time request....
Definition: core_sntp_serializer.h:263
SntpStatus_t
Enumeration of status codes that can be returned by the coreSNTP Library API.
Definition: core_sntp_serializer.h:138
@ SntpSuccess
Successful operation of an SNTP API.
Definition: core_sntp_serializer.h:142
See also
SntpSetTime_t

Platforms should implement this function to perform clock disciple operation on the system clock, that is appropriate for the clock accuracy needs of the application.

Authentication Interface

The coreSNTP library exposes an authentication interface to allow customer-chosen authentication mechanism to be used in SNTP communication with time server(s) for security.

Note
It is RECOMMENDED to enable authentication in communication with your time server(s) of choice to protect against attacks that modify or spoof server responses. The SNTPv4 protocol is flexible to be used with any symmetric-key or asymmetric key cryptographic algorithm depending on the support provided by time servers of your choice. For an example of using AES-128-CMAC as the authentication algorithm, please refer to coreSNTP demo in FreeRTOS/FreeRTOS repository.
See also
SntpAuthenticationInterface_t A port that uses authentication to communicate with time server must implement the following function pointers:
  • Add client authentication code: A function to generate and append authentication data for client to be validated by the time server. The first SNTP_PACKET_BASE_SIZE bytes in the buffer supplied to this function contains the SNTP request data which can be used to generate the authentication code. The generated authentication code SHOULD be written to the same buffer after the first SNTP_PACKET_BASE_SIZE bytes. This function should also return the number of authentication bytes appended to the library through an output parameter, so that the library knows about the total size of the SNTP packet.
  • Validate server authentication: A function to validate the authentication code in a received SNTP time response from the network to confirm that the expected server is the sender of the response and the timestamps in the packet are trustworthy to update system time. This server authentication data is usually validated by checking that the data can be regenerated by the client from the first SNTP_PACKET_BASE_SIZE bytes of the received SNTP packet from the network.

The above two functions take in a pointer to a SntpAuthContext_t, the typename of a struct SntpAuthContext. The SntpAuthContext struct must also be defined by the port, to store necessary information (like a PKCS#11 label representing credential secret) for performing cryptographic generation and validation operations in the SntpGenerateAuthCode_t and SntpValidateServerAuth_t functions respectively.