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