From b7406357c545dbe694e2121fb3b3dbad758ab6a1 Mon Sep 17 00:00:00 2001 From: cpq Date: Tue, 9 Aug 2022 12:13:23 +0100 Subject: [PATCH] Fix #1600 - more precise SNTP time calculation --- mongoose.c | 26 ++++++++++++++++++++------ src/sntp.c | 26 ++++++++++++++++++++------ test/unit_test.c | 39 +++++++++------------------------------ 3 files changed, 49 insertions(+), 42 deletions(-) diff --git a/mongoose.c b/mongoose.c index c1ce1b59..f062cc99 100644 --- a/mongoose.c +++ b/mongoose.c @@ -3912,8 +3912,14 @@ void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *context) { -#define SNTP_INTERVAL_SEC 3600 -#define SNTP_TIME_OFFSET 2208988800UL +#define SNTP_TIME_OFFSET 2208988800UL // (1970 - 1900) in seconds +#define SNTP_MAX_FRAC 4294967295.0 // 2 ** 32 - 1 + +static int64_t gettimestamp(const uint32_t *data) { + uint32_t sec = mg_ntohl(data[0]), frac = mg_ntohl(data[1]); + if (sec) sec -= SNTP_TIME_OFFSET; + return ((int64_t) sec) * 1000 + (int64_t) (frac / SNTP_MAX_FRAC * 1000.0); +} int64_t mg_sntp_parse(const unsigned char *buf, size_t len) { int64_t res = -1; @@ -3926,10 +3932,14 @@ int64_t mg_sntp_parse(const unsigned char *buf, size_t len) { } else if (buf[1] == 0) { MG_ERROR(("%s", "server sent a kiss of death")); } else if (version == 4 || version == 3) { - uint32_t *data = (uint32_t *) &buf[40]; - unsigned long seconds = mg_ntohl(data[0]) - SNTP_TIME_OFFSET; - unsigned long fracseconds = mg_ntohl(data[1]); - res = ((int64_t) seconds) * 1000 + (((int64_t) (fracseconds) * 1000) >> 32); + // int64_t ref = gettimestamp((uint32_t *) &buf[16]); + int64_t t0 = gettimestamp((uint32_t *) &buf[24]); + int64_t t1 = gettimestamp((uint32_t *) &buf[32]); + int64_t t2 = gettimestamp((uint32_t *) &buf[40]); + int64_t t3 = (int64_t) mg_millis(); + int64_t delta = (t3 - t0) - (t2 - t1); + MG_DEBUG(("%lld %lld %lld %lld delta:%lld", t0, t1, t2, t3, delta)); + res = t2 + delta / 2; } else { MG_ERROR(("unexpected version: %d", version)); } @@ -3957,8 +3967,12 @@ void mg_sntp_request(struct mg_connection *c) { if (c->is_resolving) { MG_ERROR(("%lu wait until resolved", c->id)); } else { + uint64_t now = mg_millis(); uint8_t buf[48] = {0}; + uint32_t *t = (uint32_t *) &buf[40]; buf[0] = (0 << 6) | (4 << 3) | 3; + t[0] = mg_htonl((uint32_t) (now / 1000) + SNTP_TIME_OFFSET); + t[1] = mg_htonl((uint32_t) ((now % 1000) / 1000.0 * SNTP_MAX_FRAC)); mg_send(c, buf, sizeof(buf)); } } diff --git a/src/sntp.c b/src/sntp.c index dfb645e3..9491ec1e 100644 --- a/src/sntp.c +++ b/src/sntp.c @@ -4,8 +4,14 @@ #include "log.h" #include "util.h" -#define SNTP_INTERVAL_SEC 3600 -#define SNTP_TIME_OFFSET 2208988800UL +#define SNTP_TIME_OFFSET 2208988800UL // (1970 - 1900) in seconds +#define SNTP_MAX_FRAC 4294967295.0 // 2 ** 32 - 1 + +static int64_t gettimestamp(const uint32_t *data) { + uint32_t sec = mg_ntohl(data[0]), frac = mg_ntohl(data[1]); + if (sec) sec -= SNTP_TIME_OFFSET; + return ((int64_t) sec) * 1000 + (int64_t) (frac / SNTP_MAX_FRAC * 1000.0); +} int64_t mg_sntp_parse(const unsigned char *buf, size_t len) { int64_t res = -1; @@ -18,10 +24,14 @@ int64_t mg_sntp_parse(const unsigned char *buf, size_t len) { } else if (buf[1] == 0) { MG_ERROR(("%s", "server sent a kiss of death")); } else if (version == 4 || version == 3) { - uint32_t *data = (uint32_t *) &buf[40]; - unsigned long seconds = mg_ntohl(data[0]) - SNTP_TIME_OFFSET; - unsigned long fracseconds = mg_ntohl(data[1]); - res = ((int64_t) seconds) * 1000 + (((int64_t) (fracseconds) * 1000) >> 32); + // int64_t ref = gettimestamp((uint32_t *) &buf[16]); + int64_t t0 = gettimestamp((uint32_t *) &buf[24]); + int64_t t1 = gettimestamp((uint32_t *) &buf[32]); + int64_t t2 = gettimestamp((uint32_t *) &buf[40]); + int64_t t3 = (int64_t) mg_millis(); + int64_t delta = (t3 - t0) - (t2 - t1); + MG_DEBUG(("%lld %lld %lld %lld delta:%lld", t0, t1, t2, t3, delta)); + res = t2 + delta / 2; } else { MG_ERROR(("unexpected version: %d", version)); } @@ -49,8 +59,12 @@ void mg_sntp_request(struct mg_connection *c) { if (c->is_resolving) { MG_ERROR(("%lu wait until resolved", c->id)); } else { + uint64_t now = mg_millis(); uint8_t buf[48] = {0}; + uint32_t *t = (uint32_t *) &buf[40]; buf[0] = (0 << 6) | (4 << 3) | 3; + t[0] = mg_htonl((uint32_t) (now / 1000) + SNTP_TIME_OFFSET); + t[1] = mg_htonl((uint32_t) ((now % 1000) / 1000.0 * SNTP_MAX_FRAC)); mg_send(c, buf, sizeof(buf)); } } diff --git a/test/unit_test.c b/test/unit_test.c index 5405edb6..0d95c090 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -299,7 +299,7 @@ static void sntp_cb(struct mg_connection *c, int ev, void *evd, void *fnd) { int64_t ms = (int64_t) tv.tv_sec * 1000 + tv.tv_usec / 1000; int64_t diff = ms > received ? ms - received : received - ms; MG_DEBUG(("diff: %lld", diff)); - // ASSERT(diff < 100); // The diff should be less than 300 ms + ASSERT(diff < 100); #endif } else if (ev == MG_EV_OPEN) { c->is_hexdumping = 1; @@ -324,39 +324,18 @@ static void test_sntp_server(const char *url) { } static void test_sntp(void) { + const unsigned char bad[] = + "\x55\x02\x00\xeb\x00\x00\x00\x1e\x00\x00\x07\xb6\x3e\xc9\xd6\xa2" + "\xdb\xde\xea\x30\x91\x86\xb7\x10\xdb\xde\xed\x98\x00\x00\x00\xde" + "\xdb\xde\xed\x99\x0a\xe2\xc7\x96\xdb\xde\xed\x99\x0a\xe4\x6b\xda"; + + ASSERT(mg_sntp_parse(bad, sizeof(bad)) < 0); + ASSERT(mg_sntp_parse(NULL, 0) == -1); test_sntp_server("udp://time.windows.com:123"); // NOTE(cpq): temporarily disabled until Github Actions fix their NTP // port blockage issue, https://github.com/actions/runner-images/issues/5615 + // test_sntp_server("udp://time.apple.com:123"); // test_sntp_server(NULL); - - { - int64_t ms; - const unsigned char sntp_good[] = - "\x24\x02\x00\xeb\x00\x00\x00\x1e\x00\x00\x07\xb6\x3e" - "\xc9\xd6\xa2\xdb\xde\xea\x30\x91\x86\xb7\x10\xdb\xde" - "\xed\x98\x00\x00\x00\xde\xdb\xde\xed\x99\x0a\xe2\xc7" - "\x96\xdb\xde\xed\x99\x0a\xe4\x6b\xda"; - const unsigned char bad_good[] = - "\x55\x02\x00\xeb\x00\x00\x00\x1e\x00\x00\x07\xb6\x3e" - "\xc9\xd6\xa2\xdb\xde\xea\x30\x91\x86\xb7\x10\xdb\xde" - "\xed\x98\x00\x00\x00\xde\xdb\xde\xed\x99\x0a\xe2\xc7" - "\x96\xdb\xde\xed\x99\x0a\xe4\x6b\xda"; - ASSERT((ms = mg_sntp_parse(sntp_good, sizeof(sntp_good))) > 0); -#if MG_ARCH == MG_ARCH_UNIX - time_t t = (time_t) (ms / 1000); - struct tm tm; - gmtime_r(&t, &tm); - ASSERT(tm.tm_year == 116); - ASSERT(tm.tm_mon == 10); - ASSERT(tm.tm_mday == 22); - ASSERT(tm.tm_hour == 16); - ASSERT(tm.tm_min == 15); - ASSERT(tm.tm_sec == 21); -#endif - ASSERT(mg_sntp_parse(bad_good, sizeof(bad_good)) < 0); - } - - ASSERT(mg_sntp_parse(NULL, 0) == -1); } struct mqtt_data {