From 9e517fde00cd4d891cb96949b3d2f1cce17899ea Mon Sep 17 00:00:00 2001
From: Alexander Alashkin <alexander.alashkin@cesanta.com>
Date: Thu, 24 Nov 2016 09:46:40 +0000
Subject: [PATCH] Implement SNTP client

PUBLISHED_FROM=ac54bcbc81a9ee688e8b90e261172be76a9fbacd
---
 examples/sntp_client/Makefile      |   3 +
 examples/sntp_client/sntp_client.c |  51 +++++
 mongoose.c                         | 292 +++++++++++++++++++++++++++++
 mongoose.h                         |  60 ++++++
 4 files changed, 406 insertions(+)
 create mode 100644 examples/sntp_client/Makefile
 create mode 100644 examples/sntp_client/sntp_client.c

diff --git a/examples/sntp_client/Makefile b/examples/sntp_client/Makefile
new file mode 100644
index 000000000..9dedb8696
--- /dev/null
+++ b/examples/sntp_client/Makefile
@@ -0,0 +1,3 @@
+PROG = sntp_client
+MODULE_CFLAGS = -DMG_ENABLE_SNTP
+include ../examples.mk
diff --git a/examples/sntp_client/sntp_client.c b/examples/sntp_client/sntp_client.c
new file mode 100644
index 000000000..6e1468aa3
--- /dev/null
+++ b/examples/sntp_client/sntp_client.c
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#include "mongoose.h"
+
+static int s_exit_flag = 0;
+static const char *s_default_server = "pool.ntp.org";
+
+static void ev_handler(struct mg_connection *c, int ev, void *ev_data) {
+  struct mg_sntp_message *sm = (struct mg_sntp_message *) ev_data;
+  time_t t;
+  (void) c;
+
+  switch (ev) {
+    case MG_SNTP_REPLY:
+      t = time(NULL);
+      fprintf(stdout, "Local time: %s", ctime(&t));
+      t = (time_t) sm->time;
+      fprintf(stdout, "Time from %s: %s", s_default_server, ctime(&t));
+      s_exit_flag = 1;
+      break;
+    case MG_SNTP_FAILED:
+      fprintf(stderr, "Failed to get time\n");
+      s_exit_flag = 1;
+      break;
+  }
+}
+
+int main() {
+  struct mg_mgr mgr;
+  struct mg_connection *c;
+
+  mg_mgr_init(&mgr, NULL);
+
+  c = mg_sntp_get_time(&mgr, ev_handler, s_default_server);
+
+  if (c == NULL) {
+    fprintf(stderr, "Failed to connect to %s\n", s_default_server);
+    return -1;
+  }
+
+  while (s_exit_flag == 0) {
+    mg_mgr_poll(&mgr, 1000);
+  }
+
+  mg_mgr_free(&mgr);
+
+  return 0;
+}
diff --git a/mongoose.c b/mongoose.c
index e5ba8322c..b4d13ca95 100644
--- a/mongoose.c
+++ b/mongoose.c
@@ -165,6 +165,16 @@ MG_INTERNAL int mg_get_errno(void);
 
 MG_INTERNAL void mg_close_conn(struct mg_connection *conn);
 
+MG_INTERNAL int mg_http_common_url_parse(const char *url, const char *schema,
+                                         const char *schema_tls, int *use_ssl,
+                                         char **user, char **pass, char **addr,
+                                         int *port_i, const char **path);
+
+#if MG_ENABLE_SNTP
+MG_INTERNAL int mg_sntp_parse_reply(const char *buf, int len,
+                                    struct mg_sntp_message *msg);
+#endif
+
 #endif /* CS_MONGOOSE_SRC_INTERNAL_H_ */
 #ifdef MG_MODULE_LINES
 #line 1 "common/cs_dbg.h"
@@ -11463,6 +11473,288 @@ void mg_tun_send_frame(struct mg_connection *ws, uint32_t stream_id,
 
 #endif /* MG_ENABLE_TUN */
 #ifdef MG_MODULE_LINES
+#line 1 "mongoose/src/sntp.c"
+#endif
+/*
+ * Copyright (c) 2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+/* Amalgamated: #include "mongoose/src/internal.h" */
+/* Amalgamated: #include "mongoose/src/sntp.h" */
+/* Amalgamated: #include "mongoose/src/util.h" */
+
+#if MG_ENABLE_SNTP
+
+#define SNTP_TIME_OFFSET 2208988800
+
+#ifndef SNTP_TIMEOUT
+#define SNTP_TIMEOUT 10
+#endif
+
+#ifndef SNTP_ATTEMPTS
+#define SNTP_ATTEMPTS 3
+#endif
+
+static uint64_t mg_get_sec(uint64_t val) {
+  return (val & 0xFFFFFFFF00000000) >> 32;
+}
+
+static uint64_t mg_get_usec(uint64_t val) {
+  uint64_t tmp = (val & 0x00000000FFFFFFFF);
+  tmp *= 1000000;
+  tmp >>= 32;
+  return tmp;
+}
+
+static void mg_ntp_to_tv(uint64_t val, struct timeval *tv) {
+  uint64_t tmp;
+  tmp = mg_get_sec(val);
+  tmp -= SNTP_TIME_OFFSET;
+  tv->tv_sec = tmp;
+  tv->tv_usec = mg_get_usec(val);
+}
+
+static void mg_get_ntp_ts(const char *ntp, uint64_t *val) {
+  uint32_t tmp;
+  memcpy(&tmp, ntp, sizeof(tmp));
+  tmp = ntohl(tmp);
+  *val = (uint64_t) tmp << 32;
+  memcpy(&tmp, ntp + 4, sizeof(tmp));
+  tmp = ntohl(tmp);
+  *val |= tmp;
+}
+
+void mg_sntp_send_request(struct mg_connection *c) {
+  char buf[48] = {0};
+  /*
+   * header - 8 bit:
+   * LI (2 bit) - 3 (not in sync), VN (3 bit) - 4 (version),
+   * mode (3 bit) - 3 (client)
+   */
+  buf[0] = (3 << 6) | (4 << 3) | 3;
+
+/*
+ * Next fields should be empty in client request
+ * stratum, 8 bit
+ * poll interval, 8 bit
+ * rrecision, 8 bit
+ * root delay, 32 bit
+ * root dispersion, 32 bit
+ * ref id, 32 bit
+ * ref timestamp, 64 bit
+ * originate Timestamp, 64 bit
+ * receive Timestamp, 64 bit
+*/
+
+/*
+ * convert time to sntp format (sntp starts from 00:00:00 01.01.1900)
+ * according to rfc868 it is 2208988800L sec
+ * this information is used to correct roundtrip delay
+ * but if local clock is absolutely broken (and doesn't work even
+ * as simple timer), it is better to disable it
+*/
+#ifndef MG_SNMP_NO_DELAY_CORRECTION
+  uint32_t sec;
+  sec = htonl(mg_time() + SNTP_TIME_OFFSET);
+  memcpy(&buf[40], &sec, sizeof(sec));
+#endif
+
+  mg_send(c, buf, sizeof(buf));
+}
+
+#ifndef MG_SNMP_NO_DELAY_CORRECTION
+static uint64_t mg_calculate_delay(uint64_t t1, uint64_t t2, uint64_t t3) {
+  /* roundloop delay = (T4 - T1) - (T3 - T2) */
+  uint64_t d1 = ((mg_time() + SNTP_TIME_OFFSET) * 1000000) -
+                (mg_get_sec(t1) * 1000000 + mg_get_usec(t1));
+  uint64_t d2 = (mg_get_sec(t3) * 1000000 + mg_get_usec(t3)) -
+                (mg_get_sec(t2) * 1000000 + mg_get_usec(t2));
+
+  return (d1 > d2) ? d1 - d2 : 0;
+}
+#endif
+
+MG_INTERNAL int mg_sntp_parse_reply(const char *buf, int len,
+                                    struct mg_sntp_message *msg) {
+  uint8_t hdr;
+  uint64_t orig_ts_T1, recv_ts_T2, trsm_ts_T3, delay = 0;
+  int mode;
+  struct timeval tv;
+
+  (void) orig_ts_T1;
+  (void) recv_ts_T2;
+  if (len < 48) {
+    return -1;
+  }
+
+  hdr = buf[0];
+
+  if ((hdr & 0x38) >> 3 != 4) {
+    /* Wrong version */
+    return -1;
+  }
+
+  mode = hdr & 0x7;
+  if (mode != 4 && mode != 5) {
+    /* Not a server reply */
+    return -1;
+  }
+
+  memset(msg, 0, sizeof(*msg));
+
+  msg->kiss_of_death = (buf[1] == 0); /* Server asks to not send requests */
+
+  mg_get_ntp_ts(&buf[40], &trsm_ts_T3);
+
+#ifndef MG_SNMP_NO_DELAY_CORRECTION
+  mg_get_ntp_ts(&buf[24], &orig_ts_T1);
+  mg_get_ntp_ts(&buf[32], &recv_ts_T2);
+  delay = mg_calculate_delay(orig_ts_T1, recv_ts_T2, trsm_ts_T3);
+#endif
+
+  mg_ntp_to_tv(trsm_ts_T3, &tv);
+
+  msg->time = (double) tv.tv_sec + (((double) tv.tv_usec + delay) / 1000000.0);
+
+  return 0;
+}
+
+static void mg_sntp_handler(struct mg_connection *c, int ev, void *ev_data) {
+  struct mbuf *io = &c->recv_mbuf;
+  struct mg_sntp_message msg;
+
+  c->handler(c, ev, ev_data);
+
+  switch (ev) {
+    case MG_EV_RECV: {
+      if (mg_sntp_parse_reply(io->buf, io->len, &msg) < 0) {
+        DBG(("Invalid SNTP packet received (%d)", (int) io->len));
+        c->handler(c, MG_SNTP_MALFORMED_REPLY, NULL);
+      } else {
+        c->handler(c, MG_SNTP_REPLY, (void *) &msg);
+      }
+
+      mbuf_remove(io, io->len);
+      break;
+    }
+  }
+}
+
+int mg_set_protocol_sntp(struct mg_connection *c) {
+  if ((c->flags & MG_F_UDP) == 0) {
+    return -1;
+  }
+
+  c->proto_handler = mg_sntp_handler;
+
+  return 0;
+}
+
+struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr,
+                                      mg_event_handler_t event_handler,
+                                      const char *sntp_server_name) {
+  struct mg_connection *c = NULL;
+  char url[100], *p_url = url;
+  const char *proto = "", *port = "", *tmp;
+
+  /* If port is not specified, use default (123) */
+  tmp = strchr(sntp_server_name, ':');
+  if (tmp != NULL && *(tmp + 1) == '/') {
+    tmp = strchr(tmp + 1, ':');
+  }
+
+  if (tmp == NULL) {
+    port = ":123";
+  }
+
+  /* Add udp:// if needed */
+  if (strncmp(sntp_server_name, "udp://", 6) != 0) {
+    proto = "udp://";
+  }
+
+  mg_asprintf(&p_url, sizeof(url), "%s%s%s", proto, sntp_server_name, port);
+
+  c = mg_connect(mgr, p_url, event_handler);
+
+  if (c == NULL) {
+    goto cleanup;
+  }
+
+  mg_set_protocol_sntp(c);
+
+cleanup:
+  if (p_url != url) {
+    MG_FREE(p_url);
+  }
+
+  return c;
+}
+
+struct sntp_data {
+  mg_event_handler_t hander;
+  int count;
+};
+
+static void mg_sntp_util_ev_handler(struct mg_connection *c, int ev,
+                                    void *ev_data) {
+  struct sntp_data *sd = (struct sntp_data *) c->user_data;
+
+  switch (ev) {
+    case MG_EV_CONNECT:
+      if (*(int *) ev_data != 0) {
+        mg_call(c, sd->hander, MG_SNTP_FAILED, NULL);
+        break;
+      }
+    /* fallthrough */
+    case MG_EV_TIMER:
+      if (sd->count <= SNTP_ATTEMPTS) {
+        mg_sntp_send_request(c);
+        mg_set_timer(c, mg_time() + 10);
+        sd->count++;
+      } else {
+        mg_call(c, sd->hander, MG_SNTP_FAILED, NULL);
+        c->flags |= MG_F_CLOSE_IMMEDIATELY;
+      }
+      break;
+    case MG_SNTP_MALFORMED_REPLY:
+      mg_call(c, sd->hander, MG_SNTP_FAILED, NULL);
+      c->flags |= MG_F_CLOSE_IMMEDIATELY;
+      break;
+    case MG_SNTP_REPLY:
+      mg_call(c, sd->hander, MG_SNTP_REPLY, ev_data);
+      c->flags |= MG_F_CLOSE_IMMEDIATELY;
+      break;
+    case MG_EV_CLOSE:
+      MG_FREE(c->user_data);
+      c->user_data = NULL;
+      break;
+  }
+}
+
+struct mg_connection *mg_sntp_get_time(struct mg_mgr *mgr,
+                                       mg_event_handler_t event_handler,
+                                       const char *sntp_server_name) {
+  struct mg_connection *c;
+  struct sntp_data *sd = (struct sntp_data *) MG_CALLOC(1, sizeof(*sd));
+  if (sd == NULL) {
+    return NULL;
+  }
+
+  c = mg_sntp_connect(mgr, mg_sntp_util_ev_handler, sntp_server_name);
+  if (c == NULL) {
+    MG_FREE(sd);
+    return NULL;
+  }
+
+  sd->hander = event_handler;
+  c->user_data = sd;
+
+  return c;
+}
+
+#endif /* MG_ENABLE_SNTP */
+#ifdef MG_MODULE_LINES
 #line 1 "common/platforms/cc3200/cc3200_libc.c"
 #endif
 /*
diff --git a/mongoose.h b/mongoose.h
index f7499fbb7..405905ae1 100644
--- a/mongoose.h
+++ b/mongoose.h
@@ -2812,6 +2812,10 @@ struct {								\
 #define MG_ENABLE_TUN MG_ENABLE_HTTP_WEBSOCKET
 #endif
 
+#ifndef MG_ENABLE_SNTP
+#define MG_ENABLE_SNTP 0
+#endif
+
 #endif /* CS_MONGOOSE_SRC_FEATURES_H_ */
 #ifdef MG_MODULE_LINES
 #line 1 "mongoose/src/net_if.h"
@@ -5591,3 +5595,59 @@ uint32_t mg_coap_compose(struct mg_coap_message *cm, struct mbuf *io);
 #endif /* MG_ENABLE_COAP */
 
 #endif /* CS_MONGOOSE_SRC_COAP_H_ */
+#ifdef MG_MODULE_LINES
+#line 1 "mongoose/src/sntp.h"
+#endif
+/*
+ * Copyright (c) 2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef CS_MONGOOSE_SRC_SNTP_H_
+#define CS_MONGOOSE_SRC_SNTP_H_
+
+#if MG_ENABLE_SNTP
+
+#define MG_SNTP_EVENT_BASE 500
+
+/*
+ * Received reply from time server. Event handler parameter contains
+ * pointer to mg_sntp_message structure
+ */
+#define MG_SNTP_REPLY (MG_SNTP_EVENT_BASE + 1)
+
+/* Received malformed SNTP packet */
+#define MG_SNTP_MALFORMED_REPLY (MG_SNTP_EVENT_BASE + 2)
+
+/* Failed to get time from server (timeout etc) */
+#define MG_SNTP_FAILED (MG_SNTP_EVENT_BASE + 3)
+
+struct mg_sntp_message {
+  /* if server sends this flags, user should not send requests to it */
+  int kiss_of_death;
+  /* usual mg_time */
+  double time;
+};
+
+/* Establishes connection to given sntp server */
+struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr,
+                                      mg_event_handler_t event_handler,
+                                      const char *sntp_server_name);
+
+/* Sends time request to given connection */
+void mg_sntp_send_request(struct mg_connection *c);
+
+/*
+ * Helper function
+ * Establishes connection to time server, tries to send request
+ * repeats sending SNTP_ATTEMPTS times every SNTP_TIMEOUT sec
+ * (if needed)
+ * See sntp_client example
+ */
+struct mg_connection *mg_sntp_get_time(struct mg_mgr *mgr,
+                                       mg_event_handler_t event_handler,
+                                       const char *sntp_server_name);
+
+#endif
+
+#endif /* CS_MONGOOSE_SRC_SNTP_H_ */
-- 
GitLab