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-2025 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.5.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
57 
58  //Valid socket?
59  if(context->socket != NULL)
60  {
61  //Associate the socket with the relevant interface
62  error = socketBindToInterface(context->socket, context->interface);
63  }
64  else
65  {
66  //Report an error
67  error = ERROR_OPEN_FAILED;
68  }
69 
70  //Return status code
71  return error;
72 }
73 
74 
75 /**
76  * @brief Close UDP connection
77  * @param[in] context Pointer to the SNTP client context
78  **/
79 
81 {
82  //Valid socket?
83  if(context->socket != NULL)
84  {
85  //Close UDP socket
86  socketClose(context->socket);
87  context->socket = NULL;
88  }
89 }
90 
91 
92 /**
93  * @brief Send request to the NTP server
94  * @param[in] context Pointer to the SNTP client context
95  * @return Error code
96  **/
97 
99 {
100  error_t error;
101  NtpHeader *header;
102 
103  //Point to the buffer where to format the NTP message
104  header = (NtpHeader *) context->message;
105 
106  //The client initializes the NTP message header. For this purpose, all
107  //the NTP header fields are set to 0, except the Mode, VN, and optional
108  //Transmit Timestamp fields
109  osMemset(header, 0, sizeof(NtpHeader));
110 
111  //Format NTP request
112  header->vn = context->version;
113  header->mode = NTP_MODE_CLIENT;
114 
115  //Time at which the NTP request was sent
117 
118  //The Transmit Timestamp allows a simple calculation to determine the
119  //propagation delay between the server and client and to align the system
120  //clock generally within a few tens of milliseconds relative to the server
121  header->transmitTimestamp.seconds = 0;
122  header->transmitTimestamp.fraction = htonl(context->retransmitStartTime);
123 
124  //Length of the NTP request
125  context->messageLen = sizeof(NtpHeader);
126 
127  //Debug message
128  TRACE_INFO("Sending NTP request message (%" PRIuSIZE " bytes)...\r\n",
129  context->messageLen);
130 
131  //Dump the contents of the NTP packet for debugging purpose
132  ntpDumpPacket(header, context->messageLen);
133 
134  //Send the request to the designated NTP server
135  error = socketSendTo(context->socket, &context->serverIpAddr,
136  context->serverPort, context->message, context->messageLen,
137  NULL, 0);
138 
139  //Check status code
140  if(!error)
141  {
142  //Wait for server's response
144  }
145 
146  //Return status code
147  return error;
148 }
149 
150 
151 /**
152  * @brief Wait for NTP server's response
153  * @param[in] context Pointer to the SNTP client context
154  * @return Error code
155  **/
156 
158 {
159  error_t error;
160  systime_t t1;
161  systime_t t2;
162  systime_t time;
163  IpAddr ipAddr;
164  uint16_t port;
165 
166  //Get current time
167  time = osGetSystemTime();
168 
169  //Compute request timeout
170  if(timeCompare(context->startTime + context->timeout, time) > 0)
171  {
172  t1 = context->startTime + context->timeout - time;
173  }
174  else
175  {
176  t1 = 0;
177  }
178 
179  //Compute retransmission timeout
180  if(timeCompare(context->retransmitStartTime + context->retransmitTimeout, time) > 0)
181  {
182  t2 = context->retransmitStartTime + context->retransmitTimeout - time;
183  }
184  else
185  {
186  t2 = 0;
187  }
188 
189  //Adjust receive timeout
190  error = socketSetTimeout(context->socket, MIN(t1, t2));
191 
192  //Check status code
193  if(!error)
194  {
195  //Wait for server's response
196  error = socketReceiveFrom(context->socket, &ipAddr, &port,
197  context->message, NTP_MAX_MSG_SIZE, &context->messageLen, 0);
198  }
199 
200  //Any datagram received?
201  if(error == NO_ERROR)
202  {
203  //Check NTP response
204  error = sntpClientCheckResponse(context, &ipAddr, port,
205  context->message, context->messageLen);
206 
207  //Check status code
208  if(!error)
209  {
210  //A valid NTP response has been received
212  }
213  else
214  {
215  //Silently discard invalid NTP packets
216  error = sntpClientCheckTimeout(context);
217  }
218  }
219  else if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
220  {
221  //Check whether the timeout has elapsed
222  error = sntpClientCheckTimeout(context);
223  }
224  else
225  {
226  //A communication error has occurred
227  }
228 
229  //Return status code
230  return error;
231 }
232 
233 
234 /**
235  * @brief Check whether the NTP response is valid
236  * @param[in] context Pointer to the SNTP client context
237  * @param[in] ipAddr Remote IP address
238  * @param[in] port Remote port number
239  * @param[in] message Pointer to the NTP message
240  * @param[in] length Length of the NTP message, in bytes
241  * @return Error code
242  **/
243 
245  const IpAddr *ipAddr, uint16_t port, const uint8_t *message,
246  size_t length)
247 {
248  NtpHeader *header;
249 
250  //Ensure the NTP packet is valid
251  if(length < sizeof(NtpHeader))
252  return ERROR_INVALID_MESSAGE;
253 
254  //Point to the NTP response
255  header = (NtpHeader *) context->message;
256 
257  //Debug message
258  TRACE_INFO("NTP response message received (%" PRIuSIZE " bytes)...\r\n",
259  length);
260 
261  //Dump the contents of the NTP packet for debugging purpose
262  ntpDumpPacket(header, length);
263 
264  //The server reply should be discarded if the VN field is 0
265  if(header->vn == 0)
266  return ERROR_INVALID_MESSAGE;
267 
268  //The server reply should be discarded if the Transmit Timestamp fields is 0
269  if(header->transmitTimestamp.seconds == 0 &&
270  header->transmitTimestamp.fraction == 0)
271  {
272  return ERROR_INVALID_MESSAGE;
273  }
274 
275  //The server reply should be discarded if the Mode field is not 4 (unicast)
276  //or 5 (broadcast)
277  if(header->mode != NTP_MODE_SERVER && header->mode != NTP_MODE_BROADCAST)
278  return ERROR_INVALID_MESSAGE;
279 
280  //The Originate Timestamp in the server reply should match the Transmit
281  //Timestamp used in the client request
282  if(header->originateTimestamp.seconds != 0)
283  return ERROR_INVALID_MESSAGE;
284 
285  if(header->originateTimestamp.fraction != htonl(context->retransmitStartTime))
286  return ERROR_INVALID_MESSAGE;
287 
288  //The NTP response message is acceptable
289  return NO_ERROR;
290 }
291 
292 
293 /**
294  * @brief Parse NTP server's response
295  * @param[in] context Pointer to the SNTP client context
296  * @param[out] timestamp Pointer to the NTP timestamp
297  * @return Error code
298  **/
299 
301  NtpTimestamp *timestamp)
302 {
303  NtpHeader *header;
304 
305  //Ensure the NTP packet is valid
306  if(context->messageLen < sizeof(NtpHeader))
307  return ERROR_INVALID_LENGTH;
308 
309  //Point to the NTP response
310  header = (NtpHeader *) context->message;
311 
312  //Clear kiss code
313  context->kissCode = 0;
314 
315  //Kiss-of-Death packet received?
316  if(header->stratum == 0)
317  {
318  //The kiss code is encoded in four-character ASCII strings left
319  //justified and zero filled
320  context->kissCode = htonl(header->referenceId);
321 
322  //An SNTP client should stop sending to a particular server if that
323  //server returns a reply with a Stratum field of 0
324  return ERROR_REQUEST_REJECTED;
325  }
326 
327  //Extract NTP timestamp from server's response
328  timestamp->seconds = ntohl(header->transmitTimestamp.seconds);
329  timestamp->fraction = ntohl(header->transmitTimestamp.fraction);
330 
331  //Successful processing
332  return NO_ERROR;
333 }
334 
335 
336 /**
337  * @brief Determine whether a timeout error has occurred
338  * @param[in] context Pointer to the SNTP client context
339  * @return Error code
340  **/
341 
343 {
344  error_t error;
345  systime_t time;
346 
347  //Get current time
348  time = osGetSystemTime();
349 
350  //Check whether the timeout has elapsed
351  if(timeCompare(time, context->startTime + context->timeout) >= 0)
352  {
353  //Report a timeout error
354  error = ERROR_TIMEOUT;
355  }
356  else if(timeCompare(time, context->retransmitStartTime + context->retransmitTimeout) >= 0)
357  {
358  //The timeout value is doubled for each subsequent retransmission
359  context->retransmitTimeout = MIN(context->retransmitTimeout * 2,
361 
362  //Retransmit NTP request
363  context->state = SNTP_CLIENT_STATE_SENDING;
364 
365  //Continue processing
366  error = NO_ERROR;
367  }
368  else
369  {
370 #if (NET_RTOS_SUPPORT == ENABLED)
371  //Report a timeout error
372  error = ERROR_TIMEOUT;
373 #else
374  //The operation would block
375  error = ERROR_WOULD_BLOCK;
376 #endif
377  }
378 
379  //Return status code
380  return error;
381 }
382 
383 #endif
uint8_t message[NTP_MAX_MSG_SIZE]
Buffer that holds the NTP request/response.
Definition: sntp_client.h:106
size_t messageLen
Length of the NTP message, in bytes.
Definition: sntp_client.h:107
@ 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
@ SNTP_CLIENT_STATE_COMPLETE
Definition: sntp_client.h:86
@ ERROR_WOULD_BLOCK
Definition: error.h:96
IP network address.
Definition: ip.h:90
NtpTimestamp
Definition: ntp_common.h:179
uint8_t message[]
Definition: chap.h:154
void socketClose(Socket *socket)
Close an existing socket.
Definition: socket.c:2067
@ SOCKET_TYPE_DGRAM
Definition: socket.h:93
void sntpClientCloseConnection(SntpClientContext *context)
Close UDP connection.
@ NTP_MODE_SERVER
Definition: ntp_common.h:88
@ ERROR_INVALID_MESSAGE
Definition: error.h:105
systime_t retransmitStartTime
Time at which the last request was sent.
Definition: sntp_client.h:104
#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:102
IpAddr serverIpAddr
NTP server address.
Definition: sntp_client.h:99
@ NTP_MODE_BROADCAST
Definition: ntp_common.h:89
@ ERROR_OPEN_FAILED
Definition: error.h:75
systime_t startTime
Request start time.
Definition: sntp_client.h:103
SNTP client context.
Definition: sntp_client.h:95
#define htonl(value)
Definition: cpu_endian.h:414
SntpClientState state
SNTP client state.
Definition: sntp_client.h:96
error_t
Error codes.
Definition: error.h:43
Data logging functions for debugging purpose (NTP)
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:85
@ 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:1719
uint32_t t2
#define TRACE_INFO(...)
Definition: debug.h:105
uint8_t length
Definition: tcp.h:375
Socket * socketOpen(uint_t type, uint_t protocol)
Create a socket (UDP or TCP)
Definition: socket.c:125
#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:101
#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:267
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:87
uint32_t time
uint32_t t1
NtpHeader
Definition: ntp_common.h:208
error_t sntpClientSendRequest(SntpClientContext *context)
Send request to the NTP server.
uint32_t kissCode
Kiss code.
Definition: sntp_client.h:108
@ SNTP_CLIENT_STATE_SENDING
Definition: sntp_client.h:84
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:1512
Ipv4Addr ipAddr
Definition: ipcp.h:105
NtpVersion version
NTP protocol version.
Definition: sntp_client.h:97
systime_t retransmitTimeout
Retransmission timeout.
Definition: sntp_client.h:105
NetInterface * interface
Underlying network interface.
Definition: sntp_client.h:98
@ 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:148
#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:100
systime_t osGetSystemTime(void)
Retrieve system time.
Helper functions for SNTP client.