RaspberrPi project source code
guowenxue
2024-03-12 c5e9e88eb8c6039cd2a4d2b3fdae0b1e3d8aea40
commit | author | age
d6b4a7 1 /*
G 2  * coreSNTP v1.2.0
3  * Copyright (C) 2021 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
4  *
5  * SPDX-License-Identifier: MIT
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining a copy of
8  * this software and associated documentation files (the "Software"), to deal in
9  * the Software without restriction, including without limitation the rights to
10  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
11  * the Software, and to permit persons to whom the Software is furnished to do so,
12  * subject to the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included in all
15  * copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
19  * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
20  * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21  * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23  */
24
25 /**
26  * @file core_sntp_client.c
27  * @brief Implementation of the client API of the coreSNTP library.
28  */
29
30 /* Standard includes. */
31 #include <assert.h>
32 #include <string.h>
33
34 /* SNTP client library API include. */
35 #include "core_sntp_client.h"
36
37 #include "core_sntp_config_defaults.h"
38
39 /**
40  * @brief Utility to convert fractions part of SNTP timestamp to milliseconds.
41  *
42  * @param[in] fractions The fractions value in an SNTP timestamp.
43  */
44 #define FRACTIONS_TO_MS( fractions ) \
45     ( fractions / ( SNTP_FRACTION_VALUE_PER_MICROSECOND * 1000U ) )
46
47 SntpStatus_t Sntp_Init( SntpContext_t * pContext,
48                         const SntpServerInfo_t * pTimeServers,
49                         size_t numOfServers,
50                         uint32_t serverResponseTimeoutMs,
51                         uint8_t * pNetworkBuffer,
52                         size_t bufferSize,
53                         SntpResolveDns_t resolveDnsFunc,
54                         SntpGetTime_t getSystemTimeFunc,
55                         SntpSetTime_t setSystemTimeFunc,
56                         const UdpTransportInterface_t * pTransportIntf,
57                         const SntpAuthenticationInterface_t * pAuthIntf )
58 {
59     SntpStatus_t status = SntpSuccess;
60
61     /* Validate pointer parameters are not NULL. */
62     if( ( pContext == NULL ) || ( pTimeServers == NULL ) ||
63         ( pNetworkBuffer == NULL ) || ( resolveDnsFunc == NULL ) ||
64         ( getSystemTimeFunc == NULL ) || ( setSystemTimeFunc == NULL ) ||
65         ( pTransportIntf == NULL ) )
66     {
67         LogError( ( "Invalid parameter: Pointer parameters (except pAuthIntf) cannot be NULL" ) );
68
69         status = SntpErrorBadParameter;
70     }
71     /* Validate the length of the servers list.*/
72     else if( numOfServers == 0U )
73     {
74         LogError( ( "Invalid parameter: Size of server list cannot be zero" ) );
75         status = SntpErrorBadParameter;
76     }
77     /* Validate that the UDP transport interface functions are valid. */
78     else if( ( pTransportIntf->recvFrom == NULL ) || ( pTransportIntf->sendTo == NULL ) )
79     {
80         LogError( ( "Invalid parameter: Function members of UDP transport interface cannot be NULL" ) );
81         status = SntpErrorBadParameter;
82     }
83
84     /* If an authentication interface is provided, validate that its function pointer
85      * members are valid. */
86     else if( ( pAuthIntf != NULL ) &&
87              ( ( pAuthIntf->generateClientAuth == NULL ) ||
88                ( pAuthIntf->validateServerAuth == NULL ) ) )
89     {
90         LogError( ( "Invalid parameter: Function members of authentication interface cannot be NULL" ) );
91         status = SntpErrorBadParameter;
92     }
93     else if( bufferSize < SNTP_PACKET_BASE_SIZE )
94     {
95         LogError( ( "Cannot initialize context: Passed network buffer size is less than %u bytes: "
96                     "bufferSize=%lu", SNTP_PACKET_BASE_SIZE, ( unsigned long ) bufferSize ) );
97         status = SntpErrorBufferTooSmall;
98     }
99     else
100     {
101         /* Reset the context memory to zero. */
102         ( void ) memset( pContext, 0, sizeof( SntpContext_t ) );
103
104         /* Set the members of the context with passed parameters. */
105         pContext->pTimeServers = pTimeServers;
106         pContext->numOfServers = numOfServers;
107
108         pContext->responseTimeoutMs = serverResponseTimeoutMs;
109
110         pContext->pNetworkBuffer = pNetworkBuffer;
111         pContext->bufferSize = bufferSize;
112
113         pContext->resolveDnsFunc = resolveDnsFunc;
114         pContext->getTimeFunc = getSystemTimeFunc;
115         pContext->setTimeFunc = setSystemTimeFunc;
116
117         /* Copy contents of UDP transport interface to context. */
118         ( void ) memcpy( &pContext->networkIntf, pTransportIntf, sizeof( UdpTransportInterface_t ) );
119
120         /* If authentication interface has been passed, copy its contents to the context. */
121         if( pAuthIntf != NULL )
122         {
123             ( void ) memcpy( &pContext->authIntf, pAuthIntf, sizeof( SntpAuthenticationInterface_t ) );
124         }
125
126         /* Initialize the packet size member to the standard minimum SNTP packet size.*/
127         pContext->sntpPacketSize = SNTP_PACKET_BASE_SIZE;
128     }
129
130     return status;
131 }
132
133 /**
134  * @brief Utility to calculate the difference in milliseconds between 2
135  * SNTP timestamps.
136  *
137  * @param[in] pCurrentTime The more recent timestamp.
138  * @param[in] pOlderTime The older timestamp.
139  *
140  * @note This functions supports the edge case of SNTP timestamp overflow
141  * when @p pCurrentTime represents time in NTP era 1 (i.e. time since 7 Feb 2036)
142  * and the @p OlderTime represents time in NTP era 0 (i.e. time since 1st Jan 1900).
143  *
144  * @return Returns the calculated time duration between the two timestamps.
145  *
146  * @note This function returns the calculated time difference as unsigned 64 bit
147  * to avoid integer overflow when converting time difference between the seconds part
148  * of the timestamps, which are 32 bits wide, to milliseconds.
149  */
150 static uint64_t calculateElapsedTimeMs( const SntpTimestamp_t * pCurrentTime,
151                                         const SntpTimestamp_t * pOlderTime )
152 {
153     uint64_t timeDiffMs = 0UL;
154     uint32_t timeDiffSec = 0U;
155
156     assert( pCurrentTime != NULL );
157     assert( pOlderTime != NULL );
158
159     /* Detect if SNTP time has overflown between the 2 timestamps. */
160     if( pCurrentTime->seconds < pOlderTime->seconds )
161     {
162         /* Handle the SNTP time overflow by calculating the actual time
163          * duration from pOlderTime, that exists in NTP era 0, to pCurrentTime,
164          * that exists in NTP era 1. */
165         timeDiffSec = ( UINT32_MAX - pOlderTime->seconds ) + /* Time in NTP era 0. */
166                       1U +                                   /* Epoch time in NTP era 1, i.e. 7 Feb 2036 6h:14m:28s. */
167                       pCurrentTime->seconds;                 /* Time in NTP era 1. */
168
169         timeDiffMs = ( uint64_t ) timeDiffSec * 1000UL;
170     }
171     else
172     {
173         timeDiffSec = ( pCurrentTime->seconds - pOlderTime->seconds );
174         timeDiffMs = ( uint64_t ) timeDiffSec * 1000UL;
175     }
176
177     if( pCurrentTime->fractions > pOlderTime->fractions )
178     {
179         timeDiffMs += ( ( uint64_t ) pCurrentTime->fractions - ( uint64_t ) pOlderTime->fractions ) /
180                       ( SNTP_FRACTION_VALUE_PER_MICROSECOND * 1000UL );
181     }
182     else
183     {
184         timeDiffMs -= ( ( uint64_t ) pOlderTime->fractions - ( uint64_t ) pCurrentTime->fractions ) /
185                       ( SNTP_FRACTION_VALUE_PER_MICROSECOND * 1000UL );
186     }
187
188     return timeDiffMs;
189 }
190
191 /**
192  * @brief Validates the content of the SNTP context passed to the APIs to
193  * check whether it represents an initialized context.
194  *
195  * @param[in] pContext The SNTP context to validate.
196  *
197  * @return Returns one of the following:
198  * - #SntpSuccess if the context is verified to be initialized.
199  * - #SntpErrorBadParameter if the context is NULL.
200  * - #SntpErrorContextNotInitialized if the context is validated to be initialized.
201  */
202 static SntpStatus_t validateContext( const SntpContext_t * pContext )
203 {
204     SntpStatus_t status = SntpSuccess;
205
206     /* Check if the context parameter is invalid. */
207     if( pContext == NULL )
208     {
209         status = SntpErrorBadParameter;
210         LogError( ( "Invalid context parameter: Context is NULL" ) );
211     }
212
213     /* Validate pointer parameters are not NULL. */
214     else if( ( pContext->pTimeServers == NULL ) || ( pContext->pNetworkBuffer == NULL ) ||
215              ( pContext->resolveDnsFunc == NULL ) ||
216              ( pContext->getTimeFunc == NULL ) || ( pContext->setTimeFunc == NULL ) )
217     {
218         status = SntpErrorContextNotInitialized;
219     }
220
221     /* Validate the size of the configured servers list, network buffer size and the state
222      * variable for the SNTP packet size.*/
223     else if( ( pContext->numOfServers == 0U ) || ( pContext->bufferSize < SNTP_PACKET_BASE_SIZE ) ||
224              ( pContext->sntpPacketSize < SNTP_PACKET_BASE_SIZE ) )
225     {
226         status = SntpErrorContextNotInitialized;
227     }
228     /* Validate that the UDP transport interface functions are valid. */
229     else if( ( pContext->networkIntf.recvFrom == NULL ) || ( pContext->networkIntf.sendTo == NULL ) )
230     {
231         status = SntpErrorContextNotInitialized;
232     }
233
234     /* If an authentication interface is provided, validate that both its function pointer
235      * members are valid. */
236     else if( ( ( pContext->authIntf.generateClientAuth != NULL ) && ( pContext->authIntf.validateServerAuth == NULL ) ) ||
237              ( ( pContext->authIntf.generateClientAuth == NULL ) && ( pContext->authIntf.validateServerAuth != NULL ) ) )
238     {
239         status = SntpErrorContextNotInitialized;
240     }
241     else
242     {
243         status = SntpSuccess;
244     }
245
246     if( status == SntpErrorContextNotInitialized )
247     {
248         LogError( ( "Invalid context parameter: Context is not initialized with Sntp_Init" ) );
249     }
250
251     return status;
252 }
253
254 /**
255  * @brief Sends SNTP request packet to the passed server over the network
256  * using transport interface's send function.
257  *
258  * @note For the case of zero byte transmissions over the network, this function
259  * repeatedly retries the send operation by calling the transport interface
260  * until either:
261  * 1. The requested number of bytes @p packetSize have been sent.
262  *                    OR
263  * 2. There is an error in sending data over the network.
264  *
265  * @note This function treats partial data transmissions as error as UDP
266  * transport protocol does not support partial sends.
267  *
268  * @param[in] pNetworkIntf The UDP transport interface to use for
269  * sending data over the network.
270  * @param[in] timeServer The IPv4 address of the server to send the
271  * SNTP request packet to.
272  * @param[in] serverPort The port of the @p timeServer to send the
273  * request to.
274  * @param[in] getTimeFunc The function to query system time for
275  * tracking retry time period of no data transmissions.
276  * @param[in] pPacket The buffer containing the SNTP packet data
277  * to send over the network.
278  * @param[in] packetSize The size of data in the SNTP request packet.
279  * @param[in] timeoutMs The timeout period for retry attempts of sending
280  * SNTP request packet over the network.
281  *
282  * @return Returns #SntpSuccess on successful transmission of the entire
283  * SNTP request packet over the network; #SntpErrorNetworkFailure
284  * to indicate failure from transport interface; #SntpErrorSendTimeout if
285  * time request could not be sent over the network within the @p timeoutMs
286  * duration.
287  */
288 static SntpStatus_t sendSntpPacket( const UdpTransportInterface_t * pNetworkIntf,
289                                     uint32_t timeServer,
290                                     uint16_t serverPort,
291                                     SntpGetTime_t getTimeFunc,
292                                     const uint8_t * pPacket,
293                                     uint16_t packetSize,
294                                     uint32_t timeoutMs )
295 {
296     const uint8_t * pIndex = pPacket;
297     int32_t bytesSent = 0;
298     SntpTimestamp_t lastSendTime;
299     bool shouldRetry = false;
300     SntpStatus_t status = SntpErrorSendTimeout;
301
302     assert( pPacket != NULL );
303     assert( getTimeFunc != NULL );
304     assert( pNetworkIntf != NULL );
305     assert( packetSize >= SNTP_PACKET_BASE_SIZE );
306
307     /* Record the starting time of attempting to send data. This begins the retry timeout
308      * window. */
309     getTimeFunc( &lastSendTime );
310
311     /* Loop until the entire packet is sent. */
312     do
313     {
314         /* Reset flag for retrying send operation for the iteration. If request packet cannot be
315          * sent and timeout has not occurred, the flag will be set later for the next iteration. */
316         shouldRetry = false;
317
318         bytesSent = pNetworkIntf->sendTo( pNetworkIntf->pUserContext,
319                                           timeServer,
320                                           serverPort,
321                                           pIndex,
322                                           packetSize );
323
324         if( bytesSent < 0 )
325         {
326             LogError( ( "Unable to send request packet: Transport send failed. "
327                         "ErrorCode=%ld.", ( long int ) bytesSent ) );
328             status = SntpErrorNetworkFailure;
329         }
330         else if( bytesSent == 0 )
331         {
332             /* No bytes were sent over the network. Retry send if we have not timed out. */
333
334             SntpTimestamp_t currentTime;
335             uint64_t elapsedTimeMs;
336
337             getTimeFunc( &currentTime );
338
339             /* Calculate time elapsed since last data was sent over network. */
340             elapsedTimeMs = calculateElapsedTimeMs( &currentTime, &lastSendTime );
341
342             /* Check for timeout if we have been waiting to send any data over the network. */
343             if( elapsedTimeMs >= timeoutMs )
344             {
345                 LogError( ( "Unable to send request packet: Timed out retrying send: "
346                             "SendRetryTimeout=%ums", timeoutMs ) );
347                 status = SntpErrorSendTimeout;
348             }
349             else
350             {
351                 shouldRetry = true;
352             }
353         }
354
355         /* Partial sends are not supported by UDP, which only supports sending the entire datagram as a whole.
356          * Thus, if the transport send function returns status representing partial send, it will be treated as failure. */
357         else if( bytesSent != ( int32_t ) packetSize )
358         {
359             LogError( ( "Unable to send request packet: Transport send returned unexpected bytes sent. "
360                         "ReturnCode=%ld, ExpectedCode=%u", ( long int ) bytesSent, packetSize ) );
361
362             status = SntpErrorNetworkFailure;
363         }
364         else
365         {
366             /* The time request packet has been sent over the network. */
367             status = SntpSuccess;
368         }
369     } while( shouldRetry == true );
370
371     return status;
372 }
373
374 /**
375  * @brief Adds client authentication data to SNTP request packet by calling the
376  * authentication interface.
377  *
378  * @param[in] pContext The SNTP context.
379  *
380  * @return Returns one of the following:
381  * - #SntpSuccess if the interface function successfully appends client
382  * authentication data.
383  * - #SntpErrorAuthFailure when the interface returns either an error OR an
384  * incorrect size of the client authentication data.
385  */
386 static SntpStatus_t addClientAuthentication( SntpContext_t * pContext )
387 {
388     SntpStatus_t status = SntpSuccess;
389     uint16_t authDataSize = 0U;
390
391     assert( pContext != NULL );
392     assert( pContext->authIntf.generateClientAuth != NULL );
393     assert( pContext->currentServerIndex <= pContext->numOfServers );
394
395     status = pContext->authIntf.generateClientAuth( pContext->authIntf.pAuthContext,
396                                                     &pContext->pTimeServers[ pContext->currentServerIndex ],
397                                                     pContext->pNetworkBuffer,
398                                                     pContext->bufferSize,
399                                                     &authDataSize );
400
401     if( status != SntpSuccess )
402     {
403         LogError( ( "Unable to send time request: Client authentication function failed: "
404                     "RetStatus=%s", Sntp_StatusToStr( status ) ) );
405     }
406
407     /* Sanity check that the returned authentication data size fits in the remaining space
408      * of the request buffer besides the first #SNTP_PACKET_BASE_SIZE bytes. */
409     else if( authDataSize > ( pContext->bufferSize - SNTP_PACKET_BASE_SIZE ) )
410     {
411         LogError( ( "Unable to send time request: Invalid authentication code size: "
412                     "AuthCodeSize=%lu, NetworkBufferSize=%lu",
413                     ( unsigned long ) authDataSize, ( unsigned long ) pContext->bufferSize ) );
414         status = SntpErrorAuthFailure;
415     }
416     else
417     {
418         /* With the authentication data added. calculate total SNTP request packet size. The same
419          * size would be expected in the SNTP response from server. */
420         pContext->sntpPacketSize = SNTP_PACKET_BASE_SIZE + authDataSize;
421
422         LogInfo( ( "Appended client authentication code to SNTP request packet:"
423                    " AuthCodeSize=%lu, TotalPacketSize=%lu",
424                    ( unsigned long ) authDataSize,
425                    ( unsigned long ) pContext->sntpPacketSize ) );
426     }
427
428     return status;
429 }
430
431 SntpStatus_t Sntp_SendTimeRequest( SntpContext_t * pContext,
432                                    uint32_t randomNumber,
433                                    uint32_t blockTimeMs )
434 {
435     SntpStatus_t status = SntpSuccess;
436
437     /* Validate the context parameter. */
438     status = validateContext( pContext );
439
440     if( status == SntpSuccess )
441     {
442         const SntpServerInfo_t * pServer = NULL;
443
444         /* Set local variable for the currently indexed server to use for time
445          * query. */
446         pServer = &pContext->pTimeServers[ pContext->currentServerIndex ];
447
448         LogDebug( ( "Using server %.*s for time query", ( int ) pServer->serverNameLen, pServer->pServerName ) );
449
450         /* Perform DNS resolution of the currently indexed server in the list
451          * of configured servers. */
452         if( pContext->resolveDnsFunc( pServer, &pContext->currentServerAddr ) == false )
453         {
454             LogError( ( "Unable to send time request: DNS resolution failed: Server=%.*s",
455                         ( int ) pServer->serverNameLen, pServer->pServerName ) );
456
457             status = SntpErrorDnsFailure;
458         }
459         else
460         {
461             LogDebug( ( "Server DNS resolved: Address=0x%08X", pContext->currentServerAddr ) );
462         }
463
464         if( status == SntpSuccess )
465         {
466             /* Obtain current system time to generate SNTP request packet. */
467             pContext->getTimeFunc( &pContext->lastRequestTime );
468
469             LogDebug( ( "Obtained current time for SNTP request packet: Time=%us %ums",
470                         pContext->lastRequestTime.seconds, FRACTIONS_TO_MS( pContext->lastRequestTime.fractions ) ) );
471
472             /* Generate SNTP request packet with the current system time and
473              * the passed random number. */
474             status = Sntp_SerializeRequest( &pContext->lastRequestTime,
475                                             randomNumber,
476                                             pContext->pNetworkBuffer,
477                                             pContext->bufferSize );
478
479             /* The serialization should be successful as all parameter validation has
480              * been done before. */
481             assert( status == SntpSuccess );
482         }
483
484         /* If an authentication interface has been configured, call the function to append client
485          * authentication data to SNTP request buffer. */
486         if( ( status == SntpSuccess ) && ( pContext->authIntf.generateClientAuth != NULL ) )
487         {
488             status = addClientAuthentication( pContext );
489         }
490
491         if( status == SntpSuccess )
492         {
493             LogInfo( ( "Sending serialized SNTP request packet to the server: Addr=%u, Port=%u",
494                        pContext->currentServerAddr,
495                        pContext->pTimeServers[ pContext->currentServerIndex ].port ) );
496
497             /* Send the request packet over the network to the time server. */
498             status = sendSntpPacket( &pContext->networkIntf,
499                                      pContext->currentServerAddr,
500                                      pContext->pTimeServers[ pContext->currentServerIndex ].port,
501                                      pContext->getTimeFunc,
502                                      pContext->pNetworkBuffer,
503                                      pContext->sntpPacketSize,
504                                      blockTimeMs );
505         }
506     }
507
508     return status;
509 }
510
511 /**
512  * @brief Utility to update the SNTP context to rotate the server of use for subsequent
513  * time request(s).
514  *
515  * @note If there is no next server remaining, after the current server's index, in the list of
516  * configured servers, the server rotation algorithm wraps around to the first server in the list.
517  * The wrap around is done so that an application using the library for a long-running SNTP client
518  * functionality (like a daemon task) does not become dysfunctional after all configured time
519  * servers have been used up. Time synchronization can be a critical functionality for a system
520  * and the wrap around logic ensures that the SNTP client continues to function in such a case.
521  *
522  * @note Server rotation is performed ONLY when either of:
523  * - The current server responds with a rejection for time request.
524  *                         OR
525  * - The current server response wait has timed out.
526  */
527 static void rotateServerForNextTimeQuery( SntpContext_t * pContext )
528 {
529     size_t nextServerIndex = ( pContext->currentServerIndex + 1U ) % pContext->numOfServers;
530
531     LogInfo( ( "Rotating server for next time query: PreviousServer=%.*s, NextServer=%.*s",
532                ( int ) pContext->pTimeServers[ pContext->currentServerIndex ].serverNameLen,
533                pContext->pTimeServers[ pContext->currentServerIndex ].pServerName,
534                ( int ) pContext->pTimeServers[ nextServerIndex ].serverNameLen,
535                pContext->pTimeServers[ nextServerIndex ].pServerName ) );
536
537     pContext->currentServerIndex = nextServerIndex;
538 }
539
540
541 /**
542  * @brief This function attempts to receive the SNTP response packet from a server.
543  *
544  * @note This function treats reads of data sizes less than the expected server response packet,
545  * as an error as UDP does not support partial reads. Such a scenario can exist either due:
546  * - An error in the server sending its response with smaller packet size than the request packet OR
547  * - A malicious attacker spoofing or modifying server response OR
548  * - An error in the UDP transport interface implementation for read operation.
549  *
550  * @param[in] pTransportIntf The UDP transport interface to use for receiving data from
551  * the network.
552  * @param[in] timeServer The server to read the response from the network.
553  * @param[in] serverPort The port of the server to read the response from.
554  * @param[in, out] pBuffer This will be filled with the server response read from the
555  * network.
556  * @param[in] responseSize The size of server response to read from the network.
557  *
558  * @return It returns one of the following:
559  * - #SntpSuccess if an SNTP response packet is received from the network.
560  * - #SntpNoResponseReceived if a server response is not received from the network.
561  * - #SntpErrorNetworkFailure if there is an internal failure in reading from the network
562  * in the user-defined transport interface.
563  */
564 static SntpStatus_t receiveSntpResponse( const UdpTransportInterface_t * pTransportIntf,
565                                          uint32_t timeServer,
566                                          uint16_t serverPort,
567                                          uint8_t * pBuffer,
568                                          uint16_t responseSize )
569 {
570     SntpStatus_t status = SntpNoResponseReceived;
571     int32_t bytesRead = 0;
572
573     assert( pTransportIntf != NULL );
574     assert( pTransportIntf->recvFrom != NULL );
575     assert( pBuffer != NULL );
576     assert( responseSize >= SNTP_PACKET_BASE_SIZE );
577
578     bytesRead = pTransportIntf->recvFrom( pTransportIntf->pUserContext,
579                                           timeServer,
580                                           serverPort,
581                                           pBuffer,
582                                           responseSize );
583
584     /* Negative return code indicates error. */
585     if( bytesRead < 0 )
586     {
587         status = SntpErrorNetworkFailure;
588         LogError( ( "Unable to receive server response: Transport receive failed: Code=%ld",
589                     ( long int ) bytesRead ) );
590     }
591     /* If the packet was not available on the network, check whether we can retry. */
592     else if( bytesRead == 0 )
593     {
594         status = SntpNoResponseReceived;
595     }
596
597     /* Partial reads are not supported by UDP, which only supports receiving the entire datagram as a whole.
598      * Thus, if the transport receive function returns reception of partial data, it will be treated as failure. */
599     else if( bytesRead != ( int32_t ) responseSize )
600     {
601         LogError( ( "Failed to receive server response: Transport recv returned less than expected bytes."
602                     "ExpectedBytes=%u, ReadBytes=%ld", responseSize, ( long int ) bytesRead ) );
603         status = SntpErrorNetworkFailure;
604     }
605     else
606     {
607         LogDebug( ( "Received server response: PacketSize=%ld", ( long int ) bytesRead ) );
608         status = SntpSuccess;
609     }
610
611     return status;
612 }
613
614 /**
615  * @brief Processes the response from a server by de-serializing the SNTP packet to
616  * validate the server (if an authentication interface has been configured), determine
617  * whether server has accepted or rejected the time request, and update the system clock
618  * if the server responded positively with time.
619  *
620  * @param[in] pContext The SNTP context representing the SNTP client.
621  * @param[in] pResponseRxTime The time of receiving the server response from the network.
622  *
623  * @return It returns one of the following:
624  * - #SntpSuccess if the server response is successfully de-serialized and system clock
625  * updated.
626  * - #SntpErrorAuthFailure if there is internal failure in user-defined authentication
627  * interface when validating server from the response.
628  * - #SntpServerNotAuthenticated if the server failed authenticated check in the user-defined
629  * interface.
630  * - #SntpRejectedResponse if the server has rejected the time request in its response.
631  * - #SntpInvalidResponse if the server response failed sanity checks.
632  */
633 static SntpStatus_t processServerResponse( SntpContext_t * pContext,
634                                            const SntpTimestamp_t * pResponseRxTime )
635 {
636     SntpStatus_t status = SntpSuccess;
637     const SntpServerInfo_t * pServer = &pContext->pTimeServers[ pContext->currentServerIndex ];
638
639     assert( pContext != NULL );
640     assert( pResponseRxTime != NULL );
641
642     if( pContext->authIntf.validateServerAuth != NULL )
643     {
644         /* Verify the server from the authentication data in the SNTP response packet. */
645         status = pContext->authIntf.validateServerAuth( pContext->authIntf.pAuthContext,
646                                                         pServer,
647                                                         pContext->pNetworkBuffer,
648                                                         pContext->sntpPacketSize );
649         assert( ( status == SntpSuccess ) || ( status == SntpErrorAuthFailure ) ||
650                 ( status == SntpServerNotAuthenticated ) );
651
652         if( status != SntpSuccess )
653         {
654             LogError( ( "Unable to use server response: Server authentication function failed: "
655                         "ReturnStatus=%s", Sntp_StatusToStr( status ) ) );
656         }
657         else
658         {
659             LogDebug( ( "Server response has been validated: Server=%.*s", ( int ) pServer->serverNameLen, pServer->pServerName ) );
660         }
661     }
662
663     if( status == SntpSuccess )
664     {
665         SntpResponseData_t parsedResponse;
666
667         /* De-serialize response packet to determine whether the server accepted or rejected
668          * the request for time. Also, calculate the system clock offset if the server responded
669          * with time. */
670         status = Sntp_DeserializeResponse( &pContext->lastRequestTime,
671                                            pResponseRxTime,
672                                            pContext->pNetworkBuffer,
673                                            pContext->sntpPacketSize,
674                                            &parsedResponse );
675
676         /* We do not expect the following errors to be returned as the context
677          * has been validated in the Sntp_ReceiveTimeResponse API. */
678         assert( status != SntpErrorBadParameter );
679         assert( status != SntpErrorBufferTooSmall );
680
681         if( ( status == SntpRejectedResponseChangeServer ) ||
682             ( status == SntpRejectedResponseRetryWithBackoff ) ||
683             ( status == SntpRejectedResponseOtherCode ) )
684         {
685             /* Server has rejected the time request. Thus, we will rotate to the next time server
686              * in the list. */
687             rotateServerForNextTimeQuery( pContext );
688
689             LogError( ( "Unable to use server response: Server has rejected request for time: RejectionCode=%.*s",
690                         ( int ) SNTP_KISS_OF_DEATH_CODE_LENGTH, ( char * ) &parsedResponse.rejectedResponseCode ) );
691             status = SntpRejectedResponse;
692         }
693         else if( status == SntpInvalidResponse )
694         {
695             LogError( ( "Unable to use server response: Server response failed sanity checks." ) );
696         }
697         else
698         {
699             /* Server has responded successfully with time, and we have calculated the clock offset
700              * of system clock relative to the server.*/
701             LogDebug( ( "Updating system time: ServerTime=%u %ums ClockOffset=%lums",
702                         parsedResponse.serverTime.seconds, FRACTIONS_TO_MS( parsedResponse.serverTime.fractions ),
703                         parsedResponse.clockOffsetMs ) );
704
705             /* Update the system clock with the calculated offset. */
706             pContext->setTimeFunc( pServer, &parsedResponse.serverTime,
707                                    parsedResponse.clockOffsetMs, parsedResponse.leapSecondType );
708
709             status = SntpSuccess;
710         }
711     }
712
713     /* Reset the last request time state in context to protect against replay attacks.
714      * Note: The last request time is not cleared when a rejection response packet is received and the client does
715      * has not authenticated server from the response. This is because clearing of the state causes the coreSNTP
716      * library to discard any subsequent server response packets (as the "originate timestamp" of those packets will
717      * not match the last request time value of the context), and thus, an attacker can cause Denial of Service
718      * attacks by spoofing server response before the actual server is able to respond.
719      */
720     if( ( status == SntpSuccess ) ||
721         ( ( pContext->authIntf.validateServerAuth != NULL ) && ( status == SntpRejectedResponse ) ) )
722     {
723         /* In the attack of SNTP request packet being replayed, the replayed request packet is serviced by
724          * SNTP/NTP server with SNTP response (as servers are stateless) and client receives the response
725          * containing new values of server timestamps but the stale value of "originate timestamp".
726          * To prevent the coreSNTP library from servicing such a server response (associated with the replayed
727          * SNTP request packet), the last request timestamp state is cleared in the context after receiving the
728          * first valid server response. Therefore, any subsequent server response(s) from replayed client request
729          * packets can be invalidated due to the "originate timestamp" not matching the last request time stored
730          * in the context.
731          * Note: If an attacker spoofs a server response with a zero "originate timestamp" after the coreSNTP
732          * library (i.e. the SNTP client) has cleared the internal state to zero, the spoofed packet will be
733          * discarded as the coreSNTP serializer does not accept server responses with zero value for timestamps.
734          */
735         pContext->lastRequestTime.seconds = 0U;
736         pContext->lastRequestTime.fractions = 0U;
737     }
738
739     return status;
740 }
741
742 /**
743  * @brief Determines whether a retry attempt should be made to receive server response packet from the network
744  * depending on the timing constraints of server response timeout, @p responseTimeoutMs, and the block time
745  * period, @p blockTimeMs, passed. If neither of the time windows have expired, the function determines that the
746  * read operation can be re-tried.
747  *
748  * @param[in] pCurrentTime The current time in the system used for calculating elapsed time windows.
749  * @param[in] pReadStartTime The time of the first read attempt in the current set of read tries occurring
750  * from the Sntp_ReceiveTimeRequest API call by the application. This time is used for calculating the elapsed
751  * time to determine whether the block time has expired.
752  * @param[in] pRequestTime The time of sending the SNTP request to the server for which the response is
753  * awaited. This time is used for calculating total elapsed elapsed time of waiting for server response to
754  * determine if a server response timeout has occurred.
755  * @param[in] responseTimeoutMs The server response timeout configuration.
756  * @param[in] blockTimeMs The maximum block time of waiting for server response across read tries in the current
757  * call made by application to Sntp_ReceiveTimeResponse API.
758  * @param[out] pHasResponseTimedOut This will be populated with state to indicate whether the wait for server
759  * response has timed out.
760  *
761  * @return Returns true for retrying read operation of server response; false on either server response timeout
762  * OR completion of block time window.
763  */
764 static bool decideAboutReadRetry( const SntpTimestamp_t * pCurrentTime,
765                                   const SntpTimestamp_t * pReadStartTime,
766                                   const SntpTimestamp_t * pRequestTime,
767                                   uint32_t responseTimeoutMs,
768                                   uint32_t blockTimeMs,
769                                   bool * pHasResponseTimedOut )
770 {
771     uint64_t timeSinceRequestMs = 0UL;
772     uint64_t timeElapsedInReadAttempts = 0UL;
773     bool shouldRetry = false;
774
775     assert( pCurrentTime != NULL );
776     assert( pReadStartTime != NULL );
777     assert( pRequestTime != NULL );
778     assert( pHasResponseTimedOut != NULL );
779
780     /* Calculate time elapsed since the time request was sent to the server
781      * to determine whether the server response has timed out. */
782     timeSinceRequestMs = calculateElapsedTimeMs( pCurrentTime, pRequestTime );
783
784     /* Calculate the time elapsed across all the read attempts so far to determine
785      * whether the block time window for reading server response has expired. */
786     timeElapsedInReadAttempts = calculateElapsedTimeMs( pCurrentTime, pReadStartTime );
787
788     /* Check whether a response timeout has occurred to inform whether we should
789      * wait for server response anymore. */
790     if( timeSinceRequestMs >= ( uint64_t ) responseTimeoutMs )
791     {
792         shouldRetry = false;
793         *pHasResponseTimedOut = true;
794
795         LogError( ( "Unable to receive response: Server response has timed out: "
796                     "RequestTime=%us %ums, TimeoutDuration=%ums, ElapsedTime=%lu",
797                     pRequestTime->seconds, FRACTIONS_TO_MS( pRequestTime->fractions ),
798                     responseTimeoutMs, timeSinceRequestMs ) );
799     }
800     /* Check whether the block time window has expired to determine whether read can be retried. */
801     else if( timeElapsedInReadAttempts >= ( uint64_t ) blockTimeMs )
802     {
803         shouldRetry = false;
804         LogDebug( ( "Did not receive server response: Read block time has expired: "
805                     "BlockTime=%ums, ResponseWaitElapsedTime=%lums",
806                     blockTimeMs, timeSinceRequestMs ) );
807     }
808     else
809     {
810         shouldRetry = true;
811         LogDebug( ( "Did not receive server response: Retrying read: "
812                     "BlockTime=%ums, ResponseWaitElapsedTime=%lums, ResponseTimeout=%u",
813                     blockTimeMs, timeSinceRequestMs, responseTimeoutMs ) );
814     }
815
816     return shouldRetry;
817 }
818
819 SntpStatus_t Sntp_ReceiveTimeResponse( SntpContext_t * pContext,
820                                        uint32_t blockTimeMs )
821 {
822     SntpStatus_t status = SntpNoResponseReceived;
823     bool hasResponseTimedOut = false;
824
825     /* Validate the context parameter. */
826     status = validateContext( pContext );
827
828     if( status == SntpSuccess )
829     {
830         SntpTimestamp_t startTime, currentTime;
831         const SntpTimestamp_t * pRequestTime = &pContext->lastRequestTime;
832         bool shouldRetry = false;
833
834         /* Record time before read attempts so that it can be used as base time for
835          * for tracking the block time window across read retries. */
836         pContext->getTimeFunc( &startTime );
837
838         do
839         {
840             /* Reset the retry read operation flag. If the server response is not received in the current iteration's read
841              * attempt and the wait has not timed out, the flag will be set to perform a retry. */
842             shouldRetry = false;
843
844             /* Make an attempt to read the server response from the network. */
845             status = receiveSntpResponse( &pContext->networkIntf,
846                                           pContext->currentServerAddr,
847                                           pContext->pTimeServers[ pContext->currentServerIndex ].port,
848                                           pContext->pNetworkBuffer,
849                                           pContext->sntpPacketSize );
850
851             /* If the server response is received, deserialize it, validate the server
852              * (if authentication interface is provided), and update system time with
853              * the calculated clock offset. */
854             if( status == SntpSuccess )
855             {
856                 /* Get current time to de-serialize the receive server response packet. */
857                 pContext->getTimeFunc( &currentTime );
858
859                 status = processServerResponse( pContext, &currentTime );
860             }
861             else if( status == SntpNoResponseReceived )
862             {
863                 /* Get current time to determine whether another attempt for reading the packet can
864                  * be made. */
865                 pContext->getTimeFunc( &currentTime );
866
867                 /* Set the flag to retry read of server response from the network. */
868                 shouldRetry = decideAboutReadRetry( &currentTime,
869                                                     &startTime,
870                                                     pRequestTime,
871                                                     pContext->responseTimeoutMs,
872                                                     blockTimeMs,
873                                                     &hasResponseTimedOut );
874             }
875             else
876             {
877                 /* Empty else marker. */
878             }
879         } while( shouldRetry == true );
880
881         /* If the wait for server response to the time request has timed out, rotate the server of use in the
882          * context for subsequent time request(s). Also, update the return status to indicate response timeout. */
883         if( hasResponseTimedOut == true )
884         {
885             status = SntpErrorResponseTimeout;
886
887             /* Rotate server to the next in the list of configured servers in the context. */
888             rotateServerForNextTimeQuery( pContext );
889         }
890     }
891
892     return status;
893 }
894
895 const char * Sntp_StatusToStr( SntpStatus_t status )
896 {
897     const char * pString = NULL;
898
899     switch( status )
900     {
901         case SntpSuccess:
902             pString = "SntpSuccess";
903             break;
904
905         case SntpErrorBadParameter:
906             pString = "SntpErrorBadParameter";
907             break;
908
909         case SntpRejectedResponseChangeServer:
910             pString = "SntpRejectedResponseChangeServer";
911             break;
912
913         case SntpRejectedResponseRetryWithBackoff:
914             pString = "SntpRejectedResponseRetryWithBackoff";
915             break;
916
917         case SntpRejectedResponseOtherCode:
918             pString = "SntpRejectedResponseOtherCode";
919             break;
920
921         case SntpErrorBufferTooSmall:
922             pString = "SntpErrorBufferTooSmall";
923             break;
924
925         case SntpInvalidResponse:
926             pString = "SntpInvalidResponse";
927             break;
928
929         case SntpZeroPollInterval:
930             pString = "SntpZeroPollInterval";
931             break;
932
933         case SntpErrorTimeNotSupported:
934             pString = "SntpErrorTimeNotSupported";
935             break;
936
937         case SntpErrorDnsFailure:
938             pString = "SntpErrorDnsFailure";
939             break;
940
941         case SntpErrorNetworkFailure:
942             pString = "SntpErrorNetworkFailure";
943             break;
944
945         case SntpServerNotAuthenticated:
946             pString = "SntpServerNotAuthenticated";
947             break;
948
949         case SntpErrorAuthFailure:
950             pString = "SntpErrorAuthFailure";
951             break;
952
953         default:
954             pString = "Invalid status code!";
955             break;
956     }
957
958     return pString;
959 }