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