/*- Project: A simple SNTP client - Author: Richard James Howe - License: The Unlicense - Email: howe.r.j.89@gmail.com - Repository: https://github.com/howerj/sntp - mbed port (c) 2023 John Watts and Casey Reeves */ #include "sntp.h" #include "UDPSocket.h" #include "mbed.h" #include "mbed_trace.h" #define TRACE_GROUP "SNTP" EventFlags dns_arrived; // Holds a flag used to signal DNS arrival nsapi_value_or_error_t dns_result; // Result from the DNS callback SocketAddress dns_addr; // DNS address from the DNS callback void gethostbyname_callback( nsapi_value_or_error_t result, SocketAddress *address) { dns_result = result; dns_addr = *address; dns_arrived.set(1); } nsapi_error_t gethostbyname_timeout(NetworkInterface &net, const char *server, SocketAddress &addr, uint32_t timeout_ms) { // Clear flags before call in case we had an unexpected callback dns_arrived.clear(); // dns_id will be an ID for the async transaction or an error nsapi_value_or_error_t dns_id; dns_id = net.gethostbyname_async(server, gethostbyname_callback); if (dns_id < 0) { // dns_id is an error, creating the async transaction failed return dns_id; } if (dns_arrived.wait_any(1, timeout_ms) != 1) { // We timed out, cancel the transaction net.gethostbyname_async_cancel(dns_id); return NSAPI_ERROR_TIMEOUT; } if (dns_result < 0) { // The result itself errored, return that value return dns_result; } // Everything is fine, set the address and return addr = dns_addr; return NSAPI_ERROR_OK; } time_t ntp_seconds_to_unix(uint32_t ntp_time) { // NTP time starts at 1900, our time starts at 1970 // Subtract 70 years from NTP time to compensate // We use unsigned ints here, so if NTP gives us a // year before 70 we will wrap around and have the // correct post 2038 year up to year 2106 // Hopefully by then we will have migrated to NTPv5 uint32_t offset = 2208988800; return ntp_time - offset; } unsigned long ntp_fraction_to_microseconds(uint32_t ntp_fraction) { // NTP fractions are 1/4294967296 of a second // microseconds are 1/1000000 of a second // We need to convert between the two // // The process to convert between the two is: // 1. Divide the current fraction back in to seconds // 2. Multiply the seconds in to the new fraction (NTP or microseconds) // For example: // 1. NTP fraction 3439758684 becomes 0.800881 seconds // 2. 0.800881 seconds becomes 800881 microseconds // // Here we use a 32-bit float so we can convert between different // magnitudes of numbers without data loss or overflow. // The actual floating value is only 24-bit which is enough to store // calculate the microseconds without losing precision. float seconds = ntp_fraction / 4294967296.0; float microseconds = seconds * 1000000.0; return (unsigned long)microseconds; } unsigned long unpack32(unsigned char *p) { unsigned long l = 0; l |= ((unsigned long)p[0]) << 24; l |= ((unsigned long)p[1]) << 16; l |= ((unsigned long)p[2]) << 8; l |= ((unsigned long)p[3]) << 0; return l; } SNTPError sntp(NetworkInterface &net, const char *server, unsigned int port, struct timeval *tv) { SocketAddress addr; UDPSocket sock; nsapi_error_t err; unsigned char pkt[48] = { 0x23, // Bits: 00 (No leap second) 100 (NTPv4) 011 (Client) }; tr_info("Getting SNTP time from %s", server); err = gethostbyname_timeout(net, server, addr, 1000); if (err != NSAPI_ERROR_OK) { tr_err("gethostbyname error: %i", err); return SNTPDnsError; } addr.set_port(port); tr_debug("SNTP server: %s:%d", addr.get_ip_address(), addr.get_port()); err = sock.open(&net); if (err != NSAPI_ERROR_OK) { tr_err("socket open error: %i", err); return SNTPOpenError; } sock.set_timeout(1000); err = sock.connect(addr); if (err != NSAPI_ERROR_OK) { tr_err("socket connect error: %i", err); return SNTPConnectError; } if (sock.send(pkt, sizeof pkt) != sizeof pkt) { tr_err("socket send error: %i", err); return SNTPSendError; } if (sock.recv(pkt, sizeof pkt) != sizeof pkt) { tr_err("socket receive error: %i", err); return SNTPReceiveError; } if (sock.close() != NSAPI_ERROR_OK) { tr_warn("socket close error: %i", err); } uint32_t ntp_seconds = unpack32(&pkt[40]); uint32_t ntp_fractional = unpack32(&pkt[44]); tv->tv_sec = ntp_seconds_to_unix(ntp_seconds); tv->tv_usec = ntp_fraction_to_microseconds(ntp_fractional); tr_debug("SNTP time: %lu seconds %lu fractional", ntp_seconds, ntp_fractional); tr_debug("Unix time: %llu seconds %lu microseconds", tv->tv_sec, tv->tv_usec); return SNTPSuccess; }