1/*********************************************************************
2   PicoTCP. Copyright (c) 2012-2017 Altran Intelligent Systems. Some rights reserved.
3   See COPYING, LICENSE.GPLv2 and LICENSE.GPLv3 for usage.
4
5   Author: Toon Stegen
6 *********************************************************************/
7#include "pico_sntp_client.h"
8#include "pico_config.h"
9#include "pico_stack.h"
10#include "pico_addressing.h"
11#include "pico_socket.h"
12#include "pico_ipv4.h"
13#include "pico_ipv6.h"
14#include "pico_dns_client.h"
15#include "pico_tree.h"
16#include "pico_stack.h"
17
18#ifdef PICO_SUPPORT_SNTP_CLIENT
19
20#ifdef DEBUG_SNTP
21#define sntp_dbg dbg
22#else
23#define sntp_dbg(...) do {} while(0)
24#endif
25
26#define SNTP_VERSION 4
27#define PICO_SNTP_MAXBUF (1400)
28
29/* Sntp mode */
30#define SNTP_MODE_CLIENT 3
31
32/* SNTP conversion parameters */
33#define SNTP_FRAC_TO_PICOSEC (4294967llu)
34#define SNTP_THOUSAND (1000llu)
35#define SNTP_UNIX_OFFSET (2208988800llu)    /* nr of seconds from 1900 to 1970 */
36#define SNTP_BITMASK (0X00000000FFFFFFFF)   /* mask to convert from 64 to 32 */
37
38PACKED_STRUCT_DEF pico_sntp_ts
39{
40    uint32_t sec;       /* Seconds */
41    uint32_t frac;      /* Fraction */
42};
43
44PACKED_STRUCT_DEF pico_sntp_header
45{
46    uint8_t mode : 3;   /* Mode */
47    uint8_t vn : 3;     /* Version number */
48    uint8_t li : 2;     /* Leap indicator */
49    uint8_t stratum;    /* Stratum */
50    uint8_t poll;       /* Poll, only significant in server messages */
51    uint8_t prec;       /* Precision, only significant in server messages */
52    int32_t rt_del;     /* Root delay, only significant in server messages */
53    int32_t rt_dis;     /* Root dispersion, only significant in server messages */
54    int32_t ref_id;     /* Reference clock ID, only significant in server messages */
55    struct pico_sntp_ts ref_ts;    /* Reference time stamp */
56    struct pico_sntp_ts orig_ts;   /* Originate time stamp */
57    struct pico_sntp_ts recv_ts;   /* Receive time stamp */
58    struct pico_sntp_ts trs_ts;    /* Transmit time stamp */
59
60};
61
62struct sntp_server_ns_cookie
63{
64    int rec;                    /* Indicates wheter an sntp packet has been received */
65    uint16_t proto;             /* IPV4 or IPV6 prototype */
66    pico_time stamp;            /* Timestamp of the moment the sntp packet is sent */
67    char *hostname;             /* Hostname of the (s)ntp server*/
68    struct pico_socket *sock;   /* Socket which contains the cookie */
69    void (*cb_synced)(pico_err_t status);    /* Callback function for telling the user
70                                                wheter/when the time is synchronised */
71    uint32_t timer;   /* Timer that will signal timeout */
72};
73
74/* global variables */
75static uint16_t sntp_port = 123u;
76static struct pico_timeval server_time = {
77    0
78};
79static pico_time tick_stamp = 0ull;
80static union pico_address sntp_inaddr_any = {
81    .ip6.addr = { 0 }
82};
83
84/*************************************************************************/
85
86/* Converts a sntp time stamp to a pico_timeval struct */
87static int timestamp_convert(const struct pico_sntp_ts *ts, struct pico_timeval *tv, pico_time delay)
88{
89    if(long_be(ts->sec) < SNTP_UNIX_OFFSET) {
90        pico_err = PICO_ERR_EINVAL;
91        tv->tv_sec = 0;
92        tv->tv_msec = 0;
93        sntp_dbg("Error: input too low\n");
94        return -1;
95    }
96
97    sntp_dbg("Delay: %lu\n", delay);
98    tv->tv_msec = (pico_time) (((uint32_t)(long_be(ts->frac))) / SNTP_FRAC_TO_PICOSEC + delay);
99    tv->tv_sec = (pico_time) (long_be(ts->sec) - SNTP_UNIX_OFFSET + (uint32_t)tv->tv_msec / SNTP_THOUSAND);
100    tv->tv_msec = (uint32_t) (tv->tv_msec & SNTP_BITMASK) % SNTP_THOUSAND;
101    sntp_dbg("Converted time stamp: %lusec, %lumsec\n", tv->tv_sec, tv->tv_msec);
102    return 0;
103}
104
105/* Cleanup function that is called when the time is synced or an error occured */
106static void pico_sntp_cleanup(struct sntp_server_ns_cookie *ck, pico_err_t status)
107{
108    sntp_dbg("Cleanup called\n");
109    if(!ck)
110        return;
111
112    pico_timer_cancel(ck->timer);
113
114    ck->cb_synced(status);
115    if(ck->sock)
116        ck->sock->priv = NULL;
117
118    sntp_dbg("FREE!\n");
119    PICO_FREE(ck->hostname);
120    PICO_FREE(ck);
121
122}
123
124/* Extracts the current time from a server sntp packet*/
125static int pico_sntp_parse(char *buf, struct sntp_server_ns_cookie *ck)
126{
127    int ret = 0;
128    struct pico_sntp_header *hp = (struct pico_sntp_header*) buf;
129
130    if(!ck) {
131        sntp_dbg("pico_sntp_parse: invalid cookie\n");
132        return -1;
133    }
134
135    sntp_dbg("Received mode: %u, version: %u, stratum: %u\n", hp->mode, hp->vn, hp->stratum);
136
137    tick_stamp = pico_tick;
138    /* tick_stamp - ck->stamp is the delay between sending and receiving the ntp packet */
139    ret = timestamp_convert(&(hp->trs_ts), &server_time, (tick_stamp - ck->stamp) / 2);
140    if(ret != 0) {
141        sntp_dbg("Conversion error!\n");
142        pico_sntp_cleanup(ck, PICO_ERR_EINVAL);
143        return ret;
144    }
145
146    sntp_dbg("Server time: %lu seconds and %lu milisecs since 1970\n", server_time.tv_sec,  server_time.tv_msec);
147
148    /* Call back the user saying the time is synced */
149    pico_sntp_cleanup(ck, PICO_ERR_NOERR);
150    return ret;
151}
152
153/* callback for UDP socket events */
154static void pico_sntp_client_wakeup(uint16_t ev, struct pico_socket *s)
155{
156    struct sntp_server_ns_cookie *ck = (struct sntp_server_ns_cookie *)s->priv;
157    char *recvbuf;
158    int read = 0;
159    uint32_t peer;
160    uint16_t port;
161
162    if(!ck) {
163        sntp_dbg("pico_sntp_client_wakeup: invalid cookie\n");
164        return;
165    }
166
167    /* process read event, data available */
168    if (ev == PICO_SOCK_EV_RD) {
169        ck->rec = 1;
170        /* receive while data available in socket buffer */
171        recvbuf = PICO_ZALLOC(PICO_SNTP_MAXBUF);
172        if (!recvbuf)
173            return;
174
175        do {
176            read = pico_socket_recvfrom(s, recvbuf, PICO_SNTP_MAXBUF, &peer, &port);
177        } while(read > 0);
178        pico_sntp_parse(recvbuf, s->priv);
179        s->priv = NULL; /* make sure UDP callback does not try to read from freed mem again */
180        PICO_FREE(recvbuf);
181    }
182    /* socket is closed */
183    else if(ev == PICO_SOCK_EV_CLOSE) {
184        sntp_dbg("Socket is closed. Bailing out.\n");
185        pico_sntp_cleanup(ck, PICO_ERR_ENOTCONN);
186        return;
187    }
188    /* process error event, socket error occured */
189    else if(ev == PICO_SOCK_EV_ERR) {
190        sntp_dbg("Socket Error received. Bailing out.\n");
191        pico_sntp_cleanup(ck, PICO_ERR_ENOTCONN);
192        return;
193    }
194
195    sntp_dbg("Received data from %08X:%u\n", peer, port);
196}
197
198/* Function that is called after the receive timer expires */
199static void sntp_receive_timeout(pico_time now, void *arg)
200{
201    struct sntp_server_ns_cookie *ck = (struct sntp_server_ns_cookie *)arg;
202    (void) now;
203
204    if(!ck) {
205        sntp_dbg("sntp_timeout: invalid cookie\n");
206        return;
207    }
208
209    if(!ck->rec) {
210        pico_sntp_cleanup(ck, PICO_ERR_ETIMEDOUT);
211    }
212}
213
214/* Sends an sntp packet on sock to dst*/
215static void pico_sntp_send(struct pico_socket *sock, union pico_address *dst)
216{
217    struct pico_sntp_header header = {
218        0
219    };
220    struct sntp_server_ns_cookie *ck = (struct sntp_server_ns_cookie *)sock->priv;
221
222    if(!ck) {
223        sntp_dbg("pico_sntp_sent: invalid cookie\n");
224        return;
225    }
226
227    ck->timer = pico_timer_add(5000, sntp_receive_timeout, ck);
228    if (!ck->timer) {
229        sntp_dbg("SNTP: Failed to start timeout timer\n");
230        pico_sntp_cleanup(ck, pico_err);
231        pico_socket_close(sock);
232        pico_socket_del(sock);
233        return;
234    }
235    header.vn = SNTP_VERSION;
236    header.mode = SNTP_MODE_CLIENT;
237    /* header.trs_ts.frac = long_be(0ul); */
238    ck->stamp = pico_tick;
239    pico_socket_sendto(sock, &header, sizeof(header), dst, short_be(sntp_port));
240}
241
242static int pico_sntp_sync_start(struct sntp_server_ns_cookie *ck, union pico_address *addr)
243{
244    uint16_t any_port = 0;
245    struct pico_socket *sock;
246
247    sock = pico_socket_open(ck->proto, PICO_PROTO_UDP, &pico_sntp_client_wakeup);
248    if (!sock)
249        return -1;
250
251    sock->priv = ck;
252    ck->sock = sock;
253    if ((pico_socket_bind(sock, &sntp_inaddr_any, &any_port) < 0)) {
254        pico_socket_close(sock);
255        return -1;
256    }
257    pico_sntp_send(sock, addr);
258
259    return 0;
260}
261
262#ifdef PICO_SUPPORT_DNS_CLIENT
263/* used for getting a response from DNS servers */
264static void dnsCallback(char *ip, void *arg)
265{
266    struct sntp_server_ns_cookie *ck = (struct sntp_server_ns_cookie *)arg;
267    union pico_address address;
268    int retval = -1;
269
270    if(!ck) {
271        sntp_dbg("dnsCallback: Invalid argument\n");
272        return;
273    }
274
275    if(ck->proto == PICO_PROTO_IPV6) {
276#ifdef PICO_SUPPORT_IPV6
277        if (ip) {
278            /* add the ip address to the client, and start a tcp connection socket */
279            sntp_dbg("using IPv6 address: %s\n", ip);
280            retval = pico_string_to_ipv6(ip, address.ip6.addr);
281        } else {
282            sntp_dbg("Invalid query response for AAAA\n");
283            retval = -1;
284            pico_sntp_cleanup(ck, PICO_ERR_ENETDOWN);
285        }
286#endif
287    } else if(ck->proto == PICO_PROTO_IPV4) {
288#ifdef PICO_SUPPORT_IPV4
289        if(ip) {
290            sntp_dbg("using IPv4 address: %s\n", ip);
291            retval = pico_string_to_ipv4(ip, (uint32_t *)&address.ip4.addr);
292        } else {
293            sntp_dbg("Invalid query response for A\n");
294            retval = -1;
295            pico_sntp_cleanup(ck, PICO_ERR_ENETDOWN);
296        }
297#endif
298    }
299
300    if (retval >= 0) {
301        retval = pico_sntp_sync_start(ck, &address);
302        if (retval < 0)
303            pico_sntp_cleanup(ck, PICO_ERR_ENOTCONN);
304    }
305}
306#endif
307
308#ifdef PICO_SUPPORT_IPV4
309#ifdef PICO_SUPPORT_DNS_CLIENT
310static int pico_sntp_sync_start_dns_ipv4(const char *sntp_server, void (*cb_synced)(pico_err_t status))
311{
312    int retval = -1;
313    struct sntp_server_ns_cookie *ck;
314    /* IPv4 query */
315    ck = PICO_ZALLOC(sizeof(struct sntp_server_ns_cookie));
316    if (!ck) {
317        pico_err = PICO_ERR_ENOMEM;
318        return -1;
319    }
320
321    ck->proto = PICO_PROTO_IPV4;
322    ck->stamp = 0ull;
323    ck->rec = 0;
324    ck->sock = NULL;
325    ck->hostname = PICO_ZALLOC(strlen(sntp_server) + 1);
326    if (!ck->hostname) {
327        PICO_FREE(ck);
328        pico_err = PICO_ERR_ENOMEM;
329        return -1;
330    }
331
332    strcpy(ck->hostname, sntp_server);
333
334    ck->cb_synced = cb_synced;
335
336    sntp_dbg("Resolving A %s\n", ck->hostname);
337    retval = pico_dns_client_getaddr(sntp_server, &dnsCallback, ck);
338    if (retval != 0) {
339        PICO_FREE(ck->hostname);
340        PICO_FREE(ck);
341        return -1;
342    }
343
344    return 0;
345}
346#endif
347static int pico_sntp_sync_start_ipv4(union pico_address *addr, void (*cb_synced)(pico_err_t status))
348{
349    int retval = -1;
350    struct sntp_server_ns_cookie *ck;
351    ck = PICO_ZALLOC(sizeof(struct sntp_server_ns_cookie));
352    if (!ck) {
353        pico_err = PICO_ERR_ENOMEM;
354        return -1;
355    }
356
357    ck->proto = PICO_PROTO_IPV4;
358    ck->stamp = 0ull;
359    ck->rec = 0;
360    ck->sock = NULL;
361    /* Set the given IP address as hostname, allocate the maximum IPv4 string length  + 1 */
362    ck->hostname = PICO_ZALLOC(15 + 1);
363    if (!ck->hostname) {
364        PICO_FREE(ck);
365        pico_err = PICO_ERR_ENOMEM;
366        return -1;
367    }
368
369    retval = pico_ipv4_to_string(ck->hostname, addr->ip4.addr);
370    if (retval < 0) {
371        PICO_FREE(ck->hostname);
372        PICO_FREE(ck);
373        pico_err = PICO_ERR_EINVAL;
374        return -1;
375    }
376
377    ck->cb_synced = cb_synced;
378
379    retval = pico_sntp_sync_start(ck, addr);
380    if (retval < 0) {
381        pico_sntp_cleanup(ck, PICO_ERR_ENOTCONN);
382        return -1;
383    }
384
385    return 0;
386}
387#endif
388
389#ifdef PICO_SUPPORT_IPV6
390#ifdef PICO_SUPPORT_DNS_CLIENT
391static int pico_sntp_sync_start_dns_ipv6(const char *sntp_server, void (*cb_synced)(pico_err_t status))
392{
393    struct sntp_server_ns_cookie *ck6;
394    int  retval6 = -1;
395    /* IPv6 query */
396    ck6 = PICO_ZALLOC(sizeof(struct sntp_server_ns_cookie));
397    if (!ck6) {
398        pico_err = PICO_ERR_ENOMEM;
399        return -1;
400    }
401
402    ck6->proto = PICO_PROTO_IPV6;
403    ck6->hostname = PICO_ZALLOC(strlen(sntp_server) + 1);
404    if (!ck6->hostname) {
405        PICO_FREE(ck6);
406        pico_err = PICO_ERR_ENOMEM;
407        return -1;
408    }
409
410    strcpy(ck6->hostname, sntp_server);
411    ck6->proto = PICO_PROTO_IPV6;
412    ck6->stamp = 0ull;
413    ck6->rec = 0;
414    ck6->sock = NULL;
415    ck6->cb_synced = cb_synced;
416    sntp_dbg("Resolving AAAA %s\n", ck6->hostname);
417    retval6 = pico_dns_client_getaddr6(sntp_server, &dnsCallback, ck6);
418    if (retval6 != 0) {
419        PICO_FREE(ck6->hostname);
420        PICO_FREE(ck6);
421        return -1;
422    }
423
424    return 0;
425}
426#endif
427static int pico_sntp_sync_start_ipv6(union pico_address *addr, void (*cb_synced)(pico_err_t status))
428{
429    struct sntp_server_ns_cookie *ck6;
430    int  retval6 = -1;
431    ck6 = PICO_ZALLOC(sizeof(struct sntp_server_ns_cookie));
432    if (!ck6) {
433        pico_err = PICO_ERR_ENOMEM;
434        return -1;
435    }
436
437    ck6->proto = PICO_PROTO_IPV6;
438    ck6->stamp = 0ull;
439    ck6->rec = 0;
440    ck6->sock = NULL;
441    ck6->cb_synced = cb_synced;
442    /* Set the given IP address as hostname, allocate the maximum IPv6 string length + 1 */
443    ck6->hostname = PICO_ZALLOC(39 + 1);
444    if (!ck6->hostname) {
445        PICO_FREE(ck6);
446        pico_err = PICO_ERR_ENOMEM;
447        return -1;
448    }
449
450    retval6 = pico_ipv6_to_string(ck6->hostname, addr->ip6.addr);
451    if (retval6 < 0) {
452        PICO_FREE(ck6->hostname);
453        PICO_FREE(ck6);
454        pico_err = PICO_ERR_EINVAL;
455        return -1;
456    }
457
458    retval6 = pico_sntp_sync_start(ck6, addr);
459    if (retval6 < 0) {
460        pico_sntp_cleanup(ck6, PICO_ERR_ENOTCONN);
461        return -1;
462    }
463
464    return 0;
465}
466#endif
467
468/* user function to sync the time from a given sntp source in string notation, DNS resolution is needed */
469int pico_sntp_sync(const char *sntp_server, void (*cb_synced)(pico_err_t status))
470{
471#ifdef PICO_SUPPORT_DNS_CLIENT
472    int retval4 = -1, retval6 = -1;
473    if (sntp_server == NULL) {
474        pico_err = PICO_ERR_EINVAL;
475        return -1;
476    }
477
478    if(cb_synced == NULL) {
479        pico_err = PICO_ERR_EINVAL;
480        return -1;
481    }
482
483#ifdef PICO_SUPPORT_IPV4
484    retval4 = pico_sntp_sync_start_dns_ipv4(sntp_server, cb_synced);
485#endif
486#ifdef PICO_SUPPORT_IPV6
487    retval6 = pico_sntp_sync_start_dns_ipv6(sntp_server, cb_synced);
488#endif
489
490    if (retval4 != 0 && retval6 != 0)
491        return -1;
492
493    return 0;
494#else
495    sntp_debug("No DNS support available\n");
496    pico_err = PICO_ERR_EPROTONOSUPPORT;
497    return -1;
498#endif
499}
500
501/* user function to sync the time from a given sntp source in pico_address notation */
502int pico_sntp_sync_ip(union pico_address *sntp_addr, void (*cb_synced)(pico_err_t status))
503{
504    int retval4 = -1, retval6 = -1;
505    if (sntp_addr == NULL) {
506        pico_err = PICO_ERR_EINVAL;
507        return -1;
508    }
509
510    if (cb_synced == NULL) {
511        pico_err = PICO_ERR_EINVAL;
512        return -1;
513    }
514
515#ifdef PICO_SUPPORT_IPV4
516    retval4 = pico_sntp_sync_start_ipv4(sntp_addr, cb_synced);
517#endif
518#ifdef PICO_SUPPORT_IPV6
519    retval6 = pico_sntp_sync_start_ipv6(sntp_addr, cb_synced);
520#endif
521
522    if (retval4 != 0 && retval6 != 0)
523        return -1;
524
525    return 0;
526}
527
528/* user function to get the current time */
529int pico_sntp_gettimeofday(struct pico_timeval *tv)
530{
531    pico_time diff, temp;
532    uint32_t diffH, diffL;
533    int ret = 0;
534    if (tick_stamp == 0) {
535        /* TODO: set pico_err */
536        ret = -1;
537        sntp_dbg("Error: Unsynchronised\n");
538        return ret;
539    }
540
541    diff = pico_tick - tick_stamp;
542    diffL = ((uint32_t) (diff & SNTP_BITMASK)) / 1000;
543    diffH = ((uint32_t) (diff >> 32)) / 1000;
544
545    temp = server_time.tv_msec + (uint32_t)(diff & SNTP_BITMASK) % SNTP_THOUSAND;
546    tv->tv_sec = server_time.tv_sec + ((uint64_t)diffH << 32) + diffL + (uint32_t)temp / SNTP_THOUSAND;
547    tv->tv_msec = (uint32_t)(temp & SNTP_BITMASK) % SNTP_THOUSAND;
548    sntp_dbg("Time of day: %lu seconds and %lu milisecs since 1970\n", tv->tv_sec,  tv->tv_msec);
549    return ret;
550}
551
552#endif /* PICO_SUPPORT_SNTP_CLIENT */
553