Guide for porting coreSNTP library to a new platform.
A port of coreSNTP library for a new platform must provide the following components:
- Logging Configuration Macros
- DNS Resolve Function
- UDP Transport Interface
- Get Time Function
- Set Time Function
- 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 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
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.
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.
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;
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
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;
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:
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.
{
struct timespec currTime;
( void ) clock_gettime( CLOCK_REALTIME, &currTime );
pCurrentTime->
seconds = currTime.tv_sec;
}
#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.
int64_t clockOffsetMs,
{
uint32_t unixSecs;
uint32_t unixMs;
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.