sntp_client_misc.c
Go to the documentation of this file.
1 /**
2  * @file sntp_client_misc.c
3  * @brief Helper functions for SNTP client
4  *
5  * @section License
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  *
9  * Copyright (C) 2010-2026 Oryx Embedded SARL. All rights reserved.
10  *
11  * This file is part of CycloneTCP Open.
12  *
13  * This program is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU General Public License
15  * as published by the Free Software Foundation; either version 2
16  * of the License, or (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26  *
27  * @author Oryx Embedded SARL (www.oryx-embedded.com)
28  * @version 2.6.0
29  **/
30 
31 //Switch to the appropriate trace level
32 #define TRACE_LEVEL SNTP_TRACE_LEVEL
33 
34 //Dependencies
35 #include "core/net.h"
36 #include "sntp/sntp_client.h"
37 #include "sntp/sntp_client_misc.h"
38 #include "ntp/ntp_debug.h"
39 #include "debug.h"
40 
41 //Check TCP/IP stack configuration
42 #if (SNTP_CLIENT_SUPPORT == ENABLED)
43 
44 
45 /**
46  * @brief Open UDP connection
47  * @param[in] context Pointer to the SNTP client context
48  * @return Error code
49  **/
50 
52 {
53  error_t error;
54 
55  //Open a UDP socket
56  context->socket = socketOpenEx(context->netContext, SOCKET_TYPE_DGRAM,
58 
59  //Valid socket?
60  if(context->socket != NULL)
61  {
62  //Associate the socket with the relevant interface
63  error = socketBindToInterface(context->socket, context->interface);
64  }
65  else
66  {
67  //Report an error
68  error = ERROR_OPEN_FAILED;
69  }
70 
71 #if (SNTP_CLIENT_SRC_PORT != 0)
72  //Check status code
73  if(!error)
74  {
75  //Set client's source port
77  }
78 #endif
79 
80  //Return status code
81  return error;
82 }
83 
84 
85 /**
86  * @brief Close UDP connection
87  * @param[in] context Pointer to the SNTP client context
88  **/
89 
91 {
92  //Valid socket?
93  if(context->socket != NULL)
94  {
95  //Close UDP socket
96  socketClose(context->socket);
97  context->socket = NULL;
98  }
99 }
100 
101 
102 /**
103  * @brief Send request to the NTP server
104  * @param[in] context Pointer to the SNTP client context
105  * @return Error code
106  **/
107 
109 {
110  error_t error;
111  NtpHeader *header;
112 
113  //Point to the buffer where to format the NTP message
114  header = (NtpHeader *) context->message;
115 
116  //The client initializes the NTP message header. For this purpose, all
117  //the NTP header fields are set to 0, except the Mode, VN, and optional
118  //Transmit Timestamp fields
119  osMemset(header, 0, sizeof(NtpHeader));
120 
121  //Format NTP request
122  header->vn = context->version;
123  header->mode = NTP_MODE_CLIENT;
124 
125  //Time at which the NTP request was sent
127 
128  //The Transmit Timestamp allows a simple calculation to determine the
129  //propagation delay between the server and client and to align the system
130  //clock generally within a few tens of milliseconds relative to the server
131  header->transmitTimestamp.seconds = 0;
132  header->transmitTimestamp.fraction = htonl(context->retransmitStartTime);
133 
134  //Length of the NTP request
135  context->messageLen = sizeof(NtpHeader);
136 
137  //Debug message
138  TRACE_INFO("Sending NTP request message (%" PRIuSIZE " bytes)...\r\n",
139  context->messageLen);
140 
141  //Dump the contents of the NTP packet for debugging purpose
142  ntpDumpPacket(header, context->messageLen);
143 
144  //Send the request to the designated NTP server
145  error = socketSendTo(context->socket, &context->serverIpAddr,
146  context->serverPort, context->message, context->messageLen,
147  NULL, 0);
148 
149  //Check status code
150  if(!error)
151  {
152  //Wait for server's response
154  }
155 
156  //Return status code
157  return error;
158 }
159 
160 
161 /**
162  * @brief Wait for NTP server's response
163  * @param[in] context Pointer to the SNTP client context
164  * @return Error code
165  **/
166 
168 {
169  error_t error;
170  systime_t t1;
171  systime_t t2;
172  systime_t time;
173  IpAddr ipAddr;
174  uint16_t port;
175 
176  //Get current time
177  time = osGetSystemTime();
178 
179  //Compute request timeout
180  if(timeCompare(context->startTime + context->timeout, time) > 0)
181  {
182  t1 = context->startTime + context->timeout - time;
183  }
184  else
185  {
186  t1 = 0;
187  }
188 
189  //Compute retransmission timeout
190  if(timeCompare(context->retransmitStartTime + context->retransmitTimeout, time) > 0)
191  {
192  t2 = context->retransmitStartTime + context->retransmitTimeout - time;
193  }
194  else
195  {
196  t2 = 0;
197  }
198 
199  //Adjust receive timeout
200  error = socketSetTimeout(context->socket, MIN(t1, t2));
201 
202  //Check status code
203  if(!error)
204  {
205  //Wait for server's response
206  error = socketReceiveFrom(context->socket, &ipAddr, &port,
207  context->message, NTP_MAX_MSG_SIZE, &context->messageLen, 0);
208  }
209 
210  //Any datagram received?
211  if(error == NO_ERROR)
212  {
213  //Check NTP response
214  error = sntpClientCheckResponse(context, &ipAddr, port,
215  context->message, context->messageLen);
216 
217  //Check status code
218  if(!error)
219  {
220  //A valid NTP response has been received
222  }
223  else
224  {
225  //Silently discard invalid NTP packets
226  error = sntpClientCheckTimeout(context);
227  }
228  }
229  else if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
230  {
231  //Check whether the timeout has elapsed
232  error = sntpClientCheckTimeout(context);
233  }
234  else
235  {
236  //A communication error has occurred
237  }
238 
239  //Return status code
240  return error;
241 }
242 
243 
244 /**
245  * @brief Check whether the NTP response is valid
246  * @param[in] context Pointer to the SNTP client context
247  * @param[in] ipAddr Remote IP address
248  * @param[in] port Remote port number
249  * @param[in] message Pointer to the NTP message
250  * @param[in] length Length of the NTP message, in bytes
251  * @return Error code
252  **/
253 
255  const IpAddr *ipAddr, uint16_t port, const uint8_t *message,
256  size_t length)
257 {
258  NtpHeader *header;
259 
260  //Ensure the NTP packet is valid
261  if(length < sizeof(NtpHeader))
262  return ERROR_INVALID_MESSAGE;
263 
264  //Point to the NTP response
265  header = (NtpHeader *) context->message;
266 
267  //Debug message
268  TRACE_INFO("NTP response message received (%" PRIuSIZE " bytes)...\r\n",
269  length);
270 
271  //Dump the contents of the NTP packet for debugging purpose
272  ntpDumpPacket(header, length);
273 
274  //The server reply should be discarded if the VN field is 0
275  if(header->vn == 0)
276  return ERROR_INVALID_MESSAGE;
277 
278  //The server reply should be discarded if the Transmit Timestamp fields is 0
279  if(header->transmitTimestamp.seconds == 0 &&
280  header->transmitTimestamp.fraction == 0)
281  {
282  return ERROR_INVALID_MESSAGE;
283  }
284 
285  //The server reply should be discarded if the Mode field is not 4 (unicast)
286  //or 5 (broadcast)
287  if(header->mode != NTP_MODE_SERVER && header->mode != NTP_MODE_BROADCAST)
288  return ERROR_INVALID_MESSAGE;
289 
290  //The Originate Timestamp in the server reply should match the Transmit
291  //Timestamp used in the client request
292  if(header->originateTimestamp.seconds != 0)
293  return ERROR_INVALID_MESSAGE;
294 
295  if(header->originateTimestamp.fraction != htonl(context->retransmitStartTime))
296  return ERROR_INVALID_MESSAGE;
297 
298  //The NTP response message is acceptable
299  return NO_ERROR;
300 }
301 
302 
303 /**
304  * @brief Parse NTP server's response
305  * @param[in] context Pointer to the SNTP client context
306  * @param[out] timestamp Pointer to the NTP timestamp
307  * @return Error code
308  **/
309 
311  NtpTimestamp *timestamp)
312 {
313  NtpHeader *header;
314 
315  //Ensure the NTP packet is valid
316  if(context->messageLen < sizeof(NtpHeader))
317  return ERROR_INVALID_LENGTH;
318 
319  //Point to the NTP response
320  header = (NtpHeader *) context->message;
321 
322  //Clear kiss code
323  context->kissCode = 0;
324 
325  //Kiss-of-Death packet received?
326  if(header->stratum == 0)
327  {
328  //The kiss code is encoded in four-character ASCII strings left
329  //justified and zero filled
330  context->kissCode = htonl(header->referenceId);
331 
332  //An SNTP client should stop sending to a particular server if that
333  //server returns a reply with a Stratum field of 0
334  return ERROR_REQUEST_REJECTED;
335  }
336 
337  //Extract NTP timestamp from server's response
338  timestamp->seconds = ntohl(header->transmitTimestamp.seconds);
339  timestamp->fraction = ntohl(header->transmitTimestamp.fraction);
340 
341  //Successful processing
342  return NO_ERROR;
343 }
344 
345 
346 /**
347  * @brief Determine whether a timeout error has occurred
348  * @param[in] context Pointer to the SNTP client context
349  * @return Error code
350  **/
351 
353 {
354  error_t error;
355  systime_t time;
356 
357  //Get current time
358  time = osGetSystemTime();
359 
360  //Check whether the timeout has elapsed
361  if(timeCompare(time, context->startTime + context->timeout) >= 0)
362  {
363  //Report a timeout error
364  error = ERROR_TIMEOUT;
365  }
366  else if(timeCompare(time, context->retransmitStartTime + context->retransmitTimeout) >= 0)
367  {
368  //The timeout value is doubled for each subsequent retransmission
369  context->retransmitTimeout = MIN(context->retransmitTimeout * 2,
371 
372  //Retransmit NTP request
373  context->state = SNTP_CLIENT_STATE_SENDING;
374 
375  //Continue processing
376  error = NO_ERROR;
377  }
378  else
379  {
380 #if (NET_RTOS_SUPPORT == ENABLED)
381  //Report a timeout error
382  error = ERROR_TIMEOUT;
383 #else
384  //The operation would block
385  error = ERROR_WOULD_BLOCK;
386 #endif
387  }
388 
389  //Return status code
390  return error;
391 }
392 
393 #endif
uint8_t message[NTP_MAX_MSG_SIZE]
Buffer that holds the NTP request/response.
Definition: sntp_client.h:114
size_t messageLen
Length of the NTP message, in bytes.
Definition: sntp_client.h:115
@ SOCKET_IP_PROTO_UDP
Definition: socket.h:108
void ntpDumpPacket(const NtpHeader *packet, size_t length)
Dump NTP packet for debugging purpose.
Definition: ntp_debug.c:108
error_t socketBind(Socket *socket, const IpAddr *localIpAddr, uint16_t localPort)
Associate a local address with a socket.
Definition: socket.c:1344
@ SNTP_CLIENT_STATE_COMPLETE
Definition: sntp_client.h:93
@ ERROR_WOULD_BLOCK
Definition: error.h:96
IP network address.
Definition: ip.h:90
#define SNTP_CLIENT_SRC_PORT
Definition: sntp_client.h:68
NtpTimestamp
Definition: ntp_common.h:185
uint8_t message[]
Definition: chap.h:154
void socketClose(Socket *socket)
Close an existing socket.
Definition: socket.c:2094
@ SOCKET_TYPE_DGRAM
Definition: socket.h:93
void sntpClientCloseConnection(SntpClientContext *context)
Close UDP connection.
@ NTP_MODE_SERVER
Definition: ntp_common.h:94
@ ERROR_INVALID_MESSAGE
Definition: error.h:105
systime_t retransmitStartTime
Time at which the last request was sent.
Definition: sntp_client.h:112
#define timeCompare(t1, t2)
Definition: os_port.h:40
error_t sntpClientOpenConnection(SntpClientContext *context)
Open UDP connection.
Socket * socket
Underlying socket.
Definition: sntp_client.h:110
IpAddr serverIpAddr
NTP server address.
Definition: sntp_client.h:107
@ NTP_MODE_BROADCAST
Definition: ntp_common.h:95
@ ERROR_OPEN_FAILED
Definition: error.h:75
systime_t startTime
Request start time.
Definition: sntp_client.h:111
SNTP client context.
Definition: sntp_client.h:102
const IpAddr IP_ADDR_ANY
Definition: ip.c:53
#define htonl(value)
Definition: cpu_endian.h:414
SntpClientState state
SNTP client state.
Definition: sntp_client.h:103
error_t
Error codes.
Definition: error.h:43
Data logging functions for debugging purpose (NTP)
NetContext * netContext
TCP/IP stack context.
Definition: sntp_client.h:105
error_t sntpClientCheckResponse(SntpClientContext *context, const IpAddr *ipAddr, uint16_t port, const uint8_t *message, size_t length)
Check whether the NTP response is valid.
@ SNTP_CLIENT_STATE_RECEIVING
Definition: sntp_client.h:92
@ ERROR_INVALID_LENGTH
Definition: error.h:111
error_t socketReceiveFrom(Socket *socket, IpAddr *srcIpAddr, uint16_t *srcPort, void *data, size_t size, size_t *received, uint_t flags)
Receive a datagram from a connectionless socket.
Definition: socket.c:1746
uint32_t t2
#define TRACE_INFO(...)
Definition: debug.h:105
uint8_t length
Definition: tcp.h:375
#define MIN(a, b)
Definition: os_port.h:63
#define NTP_MAX_MSG_SIZE
Definition: ntp_common.h:40
error_t sntpClientReceiveResponse(SntpClientContext *context)
Wait for NTP server's response.
systime_t timeout
Timeout value.
Definition: sntp_client.h:109
#define socketBindToInterface
Definition: net_legacy.h:193
error_t sntpClientParseResponse(SntpClientContext *context, NtpTimestamp *timestamp)
Parse NTP server's response.
uint32_t systime_t
System time.
uint16_t port
Definition: dns_common.h:270
error_t sntpClientCheckTimeout(SntpClientContext *context)
Determine whether a timeout error has occurred.
@ ERROR_TIMEOUT
Definition: error.h:95
@ NTP_MODE_CLIENT
Definition: ntp_common.h:93
uint32_t time
uint32_t t1
NtpHeader
Definition: ntp_common.h:214
error_t sntpClientSendRequest(SntpClientContext *context)
Send request to the NTP server.
uint32_t kissCode
Kiss code.
Definition: sntp_client.h:116
Socket * socketOpenEx(NetContext *context, uint_t type, uint_t protocol)
Create a socket.
Definition: socket.c:146
@ SNTP_CLIENT_STATE_SENDING
Definition: sntp_client.h:91
error_t socketSendTo(Socket *socket, const IpAddr *destIpAddr, uint16_t destPort, const void *data, size_t length, size_t *written, uint_t flags)
Send a datagram to a specific destination.
Definition: socket.c:1535
Ipv4Addr ipAddr
Definition: ipcp.h:105
NtpVersion version
NTP protocol version.
Definition: sntp_client.h:104
systime_t retransmitTimeout
Retransmission timeout.
Definition: sntp_client.h:113
NetInterface * interface
Underlying network interface.
Definition: sntp_client.h:106
@ ERROR_REQUEST_REJECTED
Definition: error.h:273
#define PRIuSIZE
#define osMemset(p, value, length)
Definition: os_port.h:138
TCP/IP stack core.
SNTP client (Simple Network Time Protocol)
error_t socketSetTimeout(Socket *socket, systime_t timeout)
Set timeout value for blocking operations.
Definition: socket.c:169
#define ntohl(value)
Definition: cpu_endian.h:422
@ NO_ERROR
Success.
Definition: error.h:44
Debugging facilities.
#define SNTP_CLIENT_MAX_RETRANSMIT_TIMEOUT
Definition: sntp_client.h:61
uint16_t serverPort
NTP server port.
Definition: sntp_client.h:108
systime_t osGetSystemTime(void)
Retrieve system time.
Helper functions for SNTP client.