coreSNTP v1.1.0
使用简单网络时间协议 (SNTP) 同步设备时间与互联网时间的客户端库
移植指南

将 coreSNTP 库移植到新平台的指南。

用于新平台的 coreSNTP 库的移植必须提供以下组件:

  1. 日志记录配置宏
  2. DNS 解析函数
  3. UDP 传输接口
  4. 获取时间函数
  5. 设置时间函数
  6. 身份验证接口

用于日志记录的配置宏

用于启用日志记录的宏可以通过配置标头 core_sntp_config.h 定义,也可以作为编译器选项传入。

注意
如果未提供自定义配置标头 core_sntp_config.h ,则必须定义 SNTP_DO_NOT_USE_CUSTOM_CONFIG 宏。
另请参阅
配置

在整个库中使用以下日志记录宏:

以下是 POSIX 平台日志记录宏的实现示例

/************* 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 解析函数

CoreSNTP 库需要一个 DNS 解析接口,必须先实现该接口才能获得时间服务器的最新 IPv4 地址信息,然后再向其发送时间请求。

另请参阅
DNS 解析函数
注意
CoreSNTP 库将在每次尝试请求时间时重新解析时间服务器的 DNS 名称。为提高 DNS 解析操作效率,如果您的平台支持已解析域名的 DNS 缓存,则可以在实现中使用该功能。
/* 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;
}
表示时间服务器信息的结构体。
定义: core_sntp_client.h:78
const char * pServerName
时间服务器的名称。
定义: core_sntp_client.h:79

UDP 传输接口

coreSNTP 库需要实现一个 UDP 传输接口,以通过网络发送和接收 SNTP 数据包。

另请参阅
UDP 传输接口
注意
在安全性方面,为了防止不必要的服务器响应数据包,建议仅在 SNTP 请求-响应迭代期间始终开启 UDP 套接字(用于实现执行网络 I/O 的 UDP 传输接口函数),而无需在所有迭代中保持开启状态。为此,可以在调用 Sntp_SendTimeRequest API 之前开启一个新的 UDP 套接字,并在使用 Sntp_ReceiveTimeResponse 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
传递给传输接口函数的用户定义上下文类型……
定义: 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;
}

以上两个函数将指向 NetworkContext_t ,即结构体NetworkContext 的类型名称。NetworkContext 结构体也必须由移植定义,并且应该分别包含使用 UdpTransportSendTo_tUdpTransportRecvFrom_t 实现发送和接收数据所需的任何信息:

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

获取时间函数

CoreSNTP 库使用此函数从系统获取时间,用于跟踪超时持续时间以及生成 SNTP 请求数据包。

另请参阅
SntpGetTime_t

如果设备没有真实时间信息(例如在设备启动时),则此函数可以提供与真实时间不匹配的系统时间,因为从时间服务器接收到时间信息后,系统时间就会被更正以匹配真实时间。请参阅下节内容,了解如何更正系统时间。

/* 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
一微秒内的 SNTP 时间戳分数数量。
定义: core_sntp_serializer.h:66
表示 SNTP 时间戳的结构体。
定义: core_sntp_serializer.h:282
uint32_t fractions
SNTP 时间戳的小数部分,分辨率为 2^(-32) ~ 232 皮秒。
定义: core_sntp_serializer.h:284
uint32_t seconds
自纪元时间以来的秒数。
定义: core_sntp_serializer.h:283

设置时间函数

coreSNTP 库调用此函数向设备通知从时间服务器接收的最新时间以及系统时间相对于服务器时间的时钟偏移。

/* 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)
此实用程序可将 SNTP 时间戳(纪元始于 1900 年 1 月 1 日)转换为 UNIX 时间戳(使用……
定义: core_sntp_serializer.c:802
SntpLeapSecondInfo_t
闰秒信息枚举,以便 SNTP 服务器可以对时间请求发送其响应……
定义: core_sntp_serializer.h:263
SntpStatus_t
coreSNTP 库 API 可以返回的状态码的枚举。
定义: core_sntp_serializer.h:138
@ SntpSuccess
SNTP API 操作成功。
定义: core_sntp_serializer.h:142
另请参阅
SntpSetTime_t

平台应实现此函数,对系统时钟执行时钟调节操作,以符合应用程序对时钟精度的要求。

身份验证接口

coreSNTP 库公开了一个身份验证接口,可将客户选择的身份验证机制用于与时间服务器进行 SNTP 通信,以确保安全。

注意
建议您在与选择的时间服务器通信时启用身份验证,以防止出现对服务器响应的修改或欺骗攻击。SNTPv4 协议可灵活与任何对称密钥或非对称密钥加密算法搭配使用,具体取决于所选时间服务器提供的支持。有关使用 AES-128-CMAC 作为身份验证算法的示例,请参阅 FreeRTOS/FreeRTOS 存储库中的 coreSNTP 演示
另请参阅
SntpAuthenticationInterface_t:使用身份验证功能与时间服务器进行通信的移植必须实现以下函数指针:
  • 添加客户端身份验证码:用于对需要由时间服务器验证的客户端生成和附加身份验证数据的函数。提供给此函数的缓冲区中的前几个 SNTP_PACKET_BASE_SIZE 字节包含可用于生成身份验证码的 SNTP 请求数据。生成的身份验证码应在前几个 SNTP_PACKET_BASE_SIZE 字节之后写入相同缓冲区。此函数还应通过输出参数返回附加到库的身份验证字节数,让库了解 SNTP 数据包的总大小。
  • 验证服务器身份验证:用于验证从网络接收的 SNTP 时间响应中包含的身份验证码的函数,以确认所需服务器是否为响应发送方以及数据包中的时间戳是否可用于更新系统时间。这些服务器身份验证数据通常通过检查从网络接收的 SNTP 数据包的前几个 SNTP_PACKET_BASE_SIZE 字节进行验证。

以上两个函数接收指向 SntpAuthContext_t 的指针,即 struct SntpAuthContext 的类型名称。SntpAuthContext 结构体也必须由移植定义,以存储在 SntpGenerateAuthCode_tSntpValidateServerAuth_t 函数中分别执行加密生成和验证操作所需的必要信息(如表示凭据机密的 PKCS#11 标签)。