diff --git a/server/blink1-tiny-server.c b/server/blink1-tiny-server.c
index 33f71a0fa34013c2a0394c4bc9b8ed94188dc9ef..bce18feba01fa0aaa7aaa63f36c88e3f9d17cc14 100644
--- a/server/blink1-tiny-server.c
+++ b/server/blink1-tiny-server.c
@@ -230,7 +230,7 @@ static void log_access(struct mg_connection *c, char* uri_str, int resp_code) {
     char date_str[100];
     strftime(date_str, sizeof(date_str), "%d/%b/%Y:%H:%M:%S %z", localtime(&rawtime));
     char ip_str[20];
-    mg_ntoa( &(c->peer), ip_str, sizeof(ip_str));
+    mg_ntoa( &(c->rem), ip_str, sizeof(ip_str));
     printf("%s - [%s] \"%s %s HTTP/1.1\" %d %d\n", ip_str, date_str, "GET", uri_str, resp_code, 0 ); // can't get response length I guess
 }
 
@@ -262,7 +262,7 @@ static void ev_handler(struct mg_connection *c, int ev, void *ev_data, void *fn_
     struct mg_str* uri = &hm->uri;
     struct mg_str* querystr = &hm->query;
 
-    snprintf(uri_str, uri->len+1, "%s", uri->ptr); // uri->ptr gives us char ptr
+    mg_snprintf(uri_str, uri->len+1, "%s", uri->ptr); // uri->ptr gives us char ptr
 
     DictionaryInsert(resultsdict, "uri", uri_str);
     DictionaryInsert(resultsdict, "version", blink1_server_version);
@@ -658,7 +658,7 @@ int main(int argc, char *argv[]) {
     mg_mgr_init(&mgr);
 
     if ((c = mg_http_listen(&mgr, http_listen_url, ev_handler, &mgr)) == NULL) {
-      LOG(LL_ERROR, ("Cannot listen on %s.", http_listen_url));
+      MG_LOG(MG_LL_ERROR, ("Cannot listen on %s.", http_listen_url));
       exit(EXIT_FAILURE);
     }
 
diff --git a/server/mongoose/mongoose.c b/server/mongoose/mongoose.c
index 0be9e235ebe69be90a5986aaa6a7645291a1811c..595576fc99cd4dafc46bd8191ae4c588557bb8fa 100644
--- a/server/mongoose/mongoose.c
+++ b/server/mongoose/mongoose.c
@@ -1,5 +1,5 @@
 // Copyright (c) 2004-2013 Sergey Lyubka
-// Copyright (c) 2013-2021 Cesanta Software Limited
+// Copyright (c) 2013-2022 Cesanta Software Limited
 // All rights reserved
 //
 // This software is dual-licensed: you can redistribute it and/or modify
@@ -14,6 +14,8 @@
 //
 // Alternatively, you can license this software under a commercial
 // license, as set out in https://www.mongoose.ws/licensing/
+//
+// SPDX-License-Identifier: GPL-2.0 or commercial
 
 #include "mongoose.h"
 
@@ -21,7 +23,7 @@
 #line 1 "src/base64.c"
 #endif
 
-#include <string.h>
+
 
 static int mg_b64idx(int c) {
   if (c < 26) {
@@ -105,6 +107,145 @@ int mg_base64_decode(const char *src, int n, char *dst) {
   return len;
 }
 
+#ifdef MG_ENABLE_LINES
+#line 1 "src/dbl.c"
+#endif
+
+
+double mg_atod(const char *p, int len, int *numlen) {
+  double d = 0.0;
+  int i = 0, sign = 1;
+
+  // Sign
+  if (i < len && *p == '-') {
+    sign = -1, i++;
+  } else if (i < len && *p == '+') {
+    i++;
+  }
+
+  // Decimal
+  for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
+    d *= 10.0;
+    d += p[i] - '0';
+  }
+  d *= sign;
+
+  // Fractional
+  if (i < len && p[i] == '.') {
+    double frac = 0.0, base = 0.1;
+    i++;
+    for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) {
+      frac += base * (p[i] - '0');
+      base /= 10.0;
+    }
+    d += frac * sign;
+  }
+
+  // Exponential
+  if (i < len && (p[i] == 'e' || p[i] == 'E')) {
+    int j, exp = 0, minus = 0;
+    i++;
+    if (i < len && p[i] == '-') minus = 1, i++;
+    if (i < len && p[i] == '+') i++;
+    while (i < len && p[i] >= '0' && p[i] <= '9' && exp < 308)
+      exp = exp * 10 + (p[i++] - '0');
+    if (minus) exp = -exp;
+    for (j = 0; j < exp; j++) d *= 10.0;
+    for (j = 0; j < -exp; j++) d /= 10.0;
+  }
+
+  if (numlen != NULL) *numlen = i;
+  return d;
+}
+
+static int addexp(char *buf, int e, int sign) {
+  int n = 0;
+  buf[n++] = 'e';
+  buf[n++] = (char) sign;
+  if (e > 400) return 0;
+  if (e < 10) buf[n++] = '0';
+  if (e >= 100) buf[n++] = (char) (e / 100 + '0'), e -= 100 * (e / 100);
+  if (e >= 10) buf[n++] = (char) (e / 10 + '0'), e -= 10 * (e / 10);
+  buf[n++] = (char) (e + '0');
+  return n;
+}
+
+static int xisinf(double x) {
+  union {
+    double f;
+    uint64_t u;
+  } ieee754 = {x};
+  return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) == 0x7ff00000 &&
+         ((unsigned) ieee754.u == 0);
+}
+
+static int xisnan(double x) {
+  union {
+    double f;
+    uint64_t u;
+  } ieee754 = {x};
+  return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) +
+             ((unsigned) ieee754.u != 0) >
+         0x7ff00000;
+}
+
+size_t mg_dtoa(char *dst, size_t dstlen, double d, int width) {
+  char buf[40];
+  int i, s = 0, n = 0, e = 0;
+  double t, mul, saved;
+  if (d == 0.0) return mg_snprintf(dst, dstlen, "%s", "0");
+  if (xisinf(d)) return mg_snprintf(dst, dstlen, "%s", d > 0 ? "inf" : "-inf");
+  if (xisnan(d)) return mg_snprintf(dst, dstlen, "%s", "nan");
+  if (d < 0.0) d = -d, buf[s++] = '-';
+
+  // Round
+  saved = d;
+  mul = 1.0;
+  while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0;
+  while (d <= 1.0 && d / mul <= 1.0) mul /= 10.0;
+  for (i = 0, t = mul * 5; i < width; i++) t /= 10.0;
+  d += t;
+  // Calculate exponent, and 'mul' for scientific representation
+  mul = 1.0;
+  while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0, e++;
+  while (d < 1.0 && d / mul < 1.0) mul /= 10.0, e--;
+  // printf(" --> %g %d %g %g\n", saved, e, t, mul);
+
+  if (e >= width) {
+    n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width);
+    // printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, n, buf);
+    n += addexp(buf + s + n, e, '+');
+    return mg_snprintf(dst, dstlen, "%.*s", n, buf);
+  } else if (e <= -width) {
+    n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width);
+    // printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, n, buf);
+    n += addexp(buf + s + n, -e, '-');
+    return mg_snprintf(dst, dstlen, "%.*s", n, buf);
+  } else {
+    for (i = 0, t = mul; t >= 1.0 && s + n < (int) sizeof(buf); i++) {
+      int ch = (int) (d / t);
+      if (n > 0 || ch > 0) buf[s + n++] = (char) (ch + '0');
+      d -= ch * t;
+      t /= 10.0;
+    }
+    // printf(" --> [%g] -> %g %g (%d) [%.*s]\n", saved, d, t, n, s + n, buf);
+    if (n == 0) buf[s++] = '0';
+    while (t >= 1.0 && n + s < (int) sizeof(buf)) buf[n++] = '0', t /= 10.0;
+    if (s + n < (int) sizeof(buf)) buf[n + s++] = '.';
+    // printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf);
+    for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < width; i++) {
+      int ch = (int) (d / t);
+      buf[s + n++] = (char) (ch + '0');
+      d -= ch * t;
+      t /= 10.0;
+    }
+  }
+  while (n > 0 && buf[s + n - 1] == '0') n--;  // Trim trailing zeros
+  if (n > 0 && buf[s + n - 1] == '.') n--;     // Trim trailing dot
+  buf[s + n] = '\0';
+  return mg_snprintf(dst, dstlen, "%s", buf);
+}
+
 #ifdef MG_ENABLE_LINES
 #line 1 "src/dns.c"
 #endif
@@ -114,36 +255,38 @@ int mg_base64_decode(const char *src, int n, char *dst) {
 
 
 
+
 struct dns_data {
   struct dns_data *next;
   struct mg_connection *c;
-  unsigned long expire;
+  uint64_t expire;
   uint16_t txnid;
 };
 
-static struct dns_data *s_reqs;  // Active DNS requests
-
 static void mg_sendnsreq(struct mg_connection *, struct mg_str *, int,
                          struct mg_dns *, bool);
 
-static void mg_dns_free(struct dns_data *d) {
-  LIST_DELETE(struct dns_data, &s_reqs, d);
+static void mg_dns_free(struct mg_connection *c, struct dns_data *d) {
+  LIST_DELETE(struct dns_data,
+              (struct dns_data **) &c->mgr->active_dns_requests, d);
   free(d);
 }
 
 void mg_resolve_cancel(struct mg_connection *c) {
-  struct dns_data *tmp, *d;
-  for (d = s_reqs; d != NULL; d = tmp) {
+  struct dns_data *tmp, *d = (struct dns_data *) c->mgr->active_dns_requests;
+  for (; d != NULL; d = tmp) {
     tmp = d->next;
-    if (d->c == c) mg_dns_free(d);
+    if (d->c == c) mg_dns_free(c, d);
   }
 }
 
 static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, size_t ofs,
-                                      char *to, size_t tolen, int depth) {
-  size_t i = 0, j = 0;
-  if (tolen > 0) to[0] = '\0';
+                                      char *to, size_t tolen, size_t j,
+                                      int depth) {
+  size_t i = 0;
+  if (tolen > 0 && depth == 0) to[0] = '\0';
   if (depth > 5) return 0;
+  // MG_INFO(("ofs %lx %x %x", (unsigned long) ofs, s[ofs], s[ofs + 1]));
   while (ofs + i + 1 < len) {
     size_t n = s[ofs + i];
     if (n == 0) {
@@ -152,8 +295,9 @@ static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, size_t ofs,
     }
     if (n & 0xc0) {
       size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]);  // 12 is hdr len
+      // MG_INFO(("PTR %lx", (unsigned long) ptr));
       if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 &&
-          mg_dns_parse_name_depth(s, len, ptr, to, tolen, depth + 1) == 0)
+          mg_dns_parse_name_depth(s, len, ptr, to, tolen, j, depth + 1) == 0)
         return 0;
       i += 2;
       break;
@@ -167,6 +311,7 @@ static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, size_t ofs,
     j += n;
     i += n + 1;
     if (j < tolen) to[j] = '\0';  // Zero-terminate this chunk
+    // MG_INFO(("--> [%s]", to));
   }
   if (tolen > 0) to[tolen - 1] = '\0';  // Make sure make sure it is nul-term
   return i;
@@ -174,7 +319,7 @@ static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, size_t ofs,
 
 static size_t mg_dns_parse_name(const uint8_t *s, size_t n, size_t ofs,
                                 char *dst, size_t dstlen) {
-  return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0);
+  return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0, 0);
 }
 
 size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs,
@@ -214,12 +359,13 @@ bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) {
 
   for (i = 0; i < mg_ntohs(h->num_questions); i++) {
     if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0) return false;
-    // LOG(LL_INFO, ("Q %zu %zu", ofs, n));
+    // MG_INFO(("Q %lu %lu %hu/%hu", ofs, n, rr.atype, rr.aclass));
     ofs += n;
   }
   for (i = 0; i < mg_ntohs(h->num_answers); i++) {
-    // LOG(LL_INFO, ("A -- %zu %zu %s", ofs, n, dm->name));
     if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0) return false;
+    // MG_INFO(("A -- %lu %lu %hu/%hu %s", ofs, n, rr.atype, rr.aclass,
+    // dm->name));
     mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name));
     ofs += n;
 
@@ -242,38 +388,37 @@ static void dns_cb(struct mg_connection *c, int ev, void *ev_data,
                    void *fn_data) {
   struct dns_data *d, *tmp;
   if (ev == MG_EV_POLL) {
-    unsigned long now = *(unsigned long *) ev_data;
-    for (d = s_reqs; d != NULL; d = tmp) {
+    uint64_t now = *(uint64_t *) ev_data;
+    for (d = (struct dns_data *) c->mgr->active_dns_requests; d != NULL;
+         d = tmp) {
       tmp = d->next;
-      // LOG(LL_DEBUG, ("%lu %lu dns poll", d->expire, now));
+      // MG_DEBUG ("%lu %lu dns poll", d->expire, now));
       if (now > d->expire) mg_error(d->c, "DNS timeout");
     }
   } else if (ev == MG_EV_READ) {
     struct mg_dns_message dm;
     int resolved = 0;
     if (mg_dns_parse(c->recv.buf, c->recv.len, &dm) == false) {
-      char *s = mg_hexdump(c->recv.buf, c->recv.len);
-      LOG(LL_ERROR, ("Unexpected DNS response:\n%s\n", s));
-      free(s);
+      MG_ERROR(("Unexpected DNS response:"));
+      mg_hexdump(c->recv.buf, c->recv.len);
     } else {
-      LOG(LL_VERBOSE_DEBUG, ("%s %d", dm.name, dm.resolved));
-      for (d = s_reqs; d != NULL; d = tmp) {
+      MG_VERBOSE(("%s %d", dm.name, dm.resolved));
+      for (d = (struct dns_data *) c->mgr->active_dns_requests; d != NULL;
+           d = tmp) {
         tmp = d->next;
-        // LOG(LL_INFO, ("d %p %hu %hu", d, d->txnid, dm.txnid));
+        // MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid));
         if (dm.txnid != d->txnid) continue;
         if (d->c->is_resolving) {
-          d->c->is_resolving = 0;
           if (dm.resolved) {
-#if MG_ENABLE_LOG
             char buf[100];
-#endif
-            dm.addr.port = d->c->peer.port;  // Save port
-            d->c->peer = dm.addr;            // Copy resolved address
-            LOG(LL_DEBUG, ("%lu %s resolved to %s", d->c->id, dm.name,
-                           mg_ntoa(&d->c->peer, buf, sizeof(buf))));
+            dm.addr.port = d->c->rem.port;  // Save port
+            d->c->rem = dm.addr;            // Copy resolved address
+            MG_DEBUG(("%lu %s is %s", d->c->id, dm.name,
+                      mg_ntoa(&d->c->rem, buf, sizeof(buf))));
             mg_connect_resolved(d->c);
 #if MG_ENABLE_IPV6
-          } else if (dm.addr.is_ip6 == false && dm.name[0] != '\0') {
+          } else if (dm.addr.is_ip6 == false && dm.name[0] != '\0' &&
+                     c->mgr->use_dns6 == false) {
             struct mg_str x = mg_str(dm.name);
             mg_sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true);
 #endif
@@ -281,24 +426,26 @@ static void dns_cb(struct mg_connection *c, int ev, void *ev_data,
             mg_error(d->c, "%s DNS lookup failed", dm.name);
           }
         } else {
-          LOG(LL_ERROR, ("%lu already resolved", d->c->id));
+          MG_ERROR(("%lu already resolved", d->c->id));
         }
-        mg_dns_free(d);
+        mg_dns_free(c, d);
         resolved = 1;
       }
     }
-    if (!resolved) LOG(LL_ERROR, ("stray DNS reply"));
+    if (!resolved) MG_ERROR(("stray DNS reply"));
     c->recv.len = 0;
   } else if (ev == MG_EV_CLOSE) {
-    for (d = s_reqs; d != NULL; d = tmp) {
+    for (d = (struct dns_data *) c->mgr->active_dns_requests; d != NULL;
+         d = tmp) {
       tmp = d->next;
-      mg_dns_free(d);
+      mg_error(d->c, "DNS error");
+      mg_dns_free(c, d);
     }
   }
   (void) fn_data;
 }
 
-static void mg_dns_send(struct mg_connection *c, const struct mg_str *name,
+static bool mg_dns_send(struct mg_connection *c, const struct mg_str *name,
                         uint16_t txnid, bool ipv6) {
   struct {
     struct mg_dns_header header;
@@ -322,14 +469,7 @@ static void mg_dns_send(struct mg_connection *c, const struct mg_str *name,
   if (ipv6) pkt.data[n - 3] = 0x1c;  // AAAA query
   // memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6);  // AAAA query
   // n += 6;
-  mg_send(c, &pkt, sizeof(pkt.header) + n);
-#if 0
-  // Immediately after A query, send AAAA query. Whatever reply comes first,
-  // we'll use it. Note: we cannot send two queries in a single packet.
-  // https://stackoverflow.com/questions/4082081/requesting-a-and-aaaa-records-in-single-dns-query
-  pkt.data[n - 3] = 0x1c;  // AAAA query
-  mg_send(c, &pkt, sizeof(pkt.header) + n);
-#endif
+  return mg_send(c, &pkt, sizeof(pkt.header) + n);
 }
 
 static void mg_sendnsreq(struct mg_connection *c, struct mg_str *name, int ms,
@@ -341,7 +481,6 @@ static void mg_sendnsreq(struct mg_connection *c, struct mg_str *name, int ms,
     dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL);
     if (dnsc->c != NULL) {
       dnsc->c->pfn = dns_cb;
-      snprintf(dnsc->c->label, sizeof(dnsc->c->label), "%s", "DNS");
       // dnsc->c->is_hexdumping = 1;
     }
   }
@@ -350,29 +489,32 @@ static void mg_sendnsreq(struct mg_connection *c, struct mg_str *name, int ms,
   } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) == NULL) {
     mg_error(c, "resolve OOM");
   } else {
-#if MG_ENABLE_LOG
+    struct dns_data *reqs = (struct dns_data *) c->mgr->active_dns_requests;
     char buf[100];
-#endif
-    d->txnid = s_reqs ? (uint16_t) (s_reqs->txnid + 1) : 1;
-    d->next = s_reqs;
-    s_reqs = d;
-    d->expire = mg_millis() + (unsigned long) ms;
+    d->txnid = reqs ? (uint16_t) (reqs->txnid + 1) : 1;
+    d->next = (struct dns_data *) c->mgr->active_dns_requests;
+    c->mgr->active_dns_requests = d;
+    d->expire = mg_millis() + (uint64_t) ms;
     d->c = c;
     c->is_resolving = 1;
-    LOG(LL_VERBOSE_DEBUG,
-        ("%lu resolving %.*s @ %s, txnid %hu", c->id, (int) name->len,
-         name->ptr, mg_ntoa(&dnsc->c->peer, buf, sizeof(buf)), d->txnid));
-    mg_dns_send(dnsc->c, name, d->txnid, ipv6);
+    MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int) name->len,
+                name->ptr, mg_ntoa(&dnsc->c->rem, buf, sizeof(buf)), d->txnid));
+    if (!mg_dns_send(dnsc->c, name, d->txnid, ipv6)) {
+      mg_error(dnsc->c, "DNS send");
+    }
   }
 }
 
-void mg_resolve(struct mg_connection *c, struct mg_str *name, int ms) {
-  if (mg_aton(*name, &c->peer)) {
-    // name is an IP address, do not fire name resolution
+void mg_resolve(struct mg_connection *c, const char *url) {
+  struct mg_str host = mg_url_host(url);
+  c->rem.port = mg_htons(mg_url_port(url));
+  if (mg_aton(host, &c->rem)) {
+    // host is an IP address, do not fire name resolution
     mg_connect_resolved(c);
   } else {
-    // name is not an IP, send DNS resolution request
-    mg_sendnsreq(c, name, ms, &c->mgr->dns4, false);
+    // host is not an IP, send DNS resolution request
+    struct mg_dns *dns = c->mgr->use_dns6 ? &c->mgr->dns6 : &c->mgr->dns4;
+    mg_sendnsreq(c, &host, c->mgr->dnstimeout, dns, c->mgr->use_dns6);
   }
 }
 
@@ -398,17 +540,510 @@ void mg_error(struct mg_connection *c, const char *fmt, ...) {
   va_start(ap, fmt);
   mg_vasprintf(&buf, sizeof(mem), fmt, ap);
   va_end(ap);
-  LOG(LL_ERROR, ("%lu %s", c->id, buf));
+  MG_ERROR(("%lu %p %s", c->id, c->fd, buf));
   c->is_closing = 1;             // Set is_closing before sending MG_EV_CALL
   mg_call(c, MG_EV_ERROR, buf);  // Let user handler to override it
   if (buf != mem) free(buf);
 }
 
+#ifdef MG_ENABLE_LINES
+#line 1 "src/fmt.c"
+#endif
+
+
+
+size_t mg_vasprintf(char **buf, size_t size, const char *fmt, va_list ap) {
+  va_list ap_copy;
+  size_t len;
+
+  va_copy(ap_copy, ap);
+  len = mg_vsnprintf(*buf, size, fmt, &ap_copy);
+  va_end(ap_copy);
+
+  if (len >= size) {
+    //  Allocate a buffer that is large enough
+    if ((*buf = (char *) calloc(1, len + 1)) == NULL) {
+      len = 0;
+    } else {
+      va_copy(ap_copy, ap);
+      len = mg_vsnprintf(*buf, len + 1, fmt, &ap_copy);
+      va_end(ap_copy);
+    }
+  }
+
+  return len;
+}
+
+size_t mg_asprintf(char **buf, size_t size, const char *fmt, ...) {
+  size_t ret;
+  va_list ap;
+  va_start(ap, fmt);
+  ret = mg_vasprintf(buf, size, fmt, ap);
+  va_end(ap);
+  return ret;
+}
+
+char *mg_vmprintf(const char *fmt, va_list ap) {
+  char *s = NULL;
+  mg_vasprintf(&s, 0, fmt, ap);
+  return s;
+}
+
+char *mg_mprintf(const char *fmt, ...) {
+  char *s = NULL;
+  va_list ap;
+  va_start(ap, fmt);
+  mg_vasprintf(&s, 0, fmt, ap);
+  va_end(ap);
+  return s;
+}
+
+size_t mg_rprintf(void (*out)(char, void *), void *ptr, const char *fmt, ...) {
+  size_t len = 0;
+  va_list ap;
+  va_start(ap, fmt);
+  len = mg_vrprintf(out, ptr, fmt, &ap);
+  va_end(ap);
+  return len;
+}
+
+static void mg_putchar_iobuf_static(char ch, void *param) {
+  struct mg_iobuf *io = (struct mg_iobuf *) param;
+  if (io->len + 2 <= io->size) {
+    io->buf[io->len++] = (uint8_t) ch;
+    io->buf[io->len] = 0;
+  }
+}
+
+void mg_putchar_iobuf(char ch, void *param) {
+  struct mg_iobuf *io = (struct mg_iobuf *) param;
+  if (io->len + 2 > io->size) mg_iobuf_resize(io, io->size + 64);
+  if (io->len + 2 <= io->size) {
+    io->buf[io->len++] = (uint8_t) ch;
+    io->buf[io->len] = 0;
+  }
+}
+
+// We don't use realloc() in mongoose, so resort to inefficient calloc
+// Every new character reallocates the whole string
+void mg_putchar_realloc(char ch, void *param) {
+  char *s, *buf = *(char **) param;
+  size_t len = buf == NULL ? 0 : strlen(buf);
+  if ((s = (char *) calloc(1, len + 2)) != NULL) {
+    if (buf != NULL) memcpy(s, buf, len);
+    s[len] = ch;
+    s[len + 1] = '\0';
+    free(buf);
+    *(char **) param = s;
+  }
+}
+
+size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list *ap) {
+  struct mg_iobuf io = {(uint8_t *) buf, len, 0};
+  size_t n = mg_vrprintf(mg_putchar_iobuf_static, &io, fmt, ap);
+  if (n < len) buf[n] = '\0';
+  return n;
+}
+
+static bool is_digit(int c) {
+  return c >= '0' && c <= '9';
+}
+
+size_t mg_lld(char *buf, int64_t val, bool is_signed, bool is_hex) {
+  const char *letters = "0123456789abcdef";
+  uint64_t v = (uint64_t) val;
+  size_t s = 0, n, i;
+  if (is_signed && val < 0) buf[s++] = '-', v = (uint64_t) (-val);
+  // This loop prints a number in reverse order. I guess this is because we
+  // write numbers from right to left: least significant digit comes last.
+  // Maybe because we use Arabic numbers, and Arabs write RTL?
+  if (is_hex) {
+    for (n = 0; v; v >>= 4) buf[s + n++] = letters[v & 15];
+  } else {
+    for (n = 0; v; v /= 10) buf[s + n++] = letters[v % 10];
+  }
+  // Reverse a string
+  for (i = 0; i < n / 2; i++) {
+    char t = buf[s + i];
+    buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t;
+  }
+  if (val == 0) buf[n++] = '0';  // Handle special case
+  return n + s;
+}
+
+static size_t scpy(void (*out)(char, void *), void *ptr, char *buf,
+                   size_t len) {
+  size_t i = 0;
+  while (i < len && buf[i] != '\0') out(buf[i++], ptr);
+  return i;
+}
+
+static char mg_esc(int c, bool esc) {
+  const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\"";
+  for (p = esc ? esc1 : esc2; *p != '\0'; p++) {
+    if (*p == c) return esc ? esc2[p - esc1] : esc1[p - esc2];
+  }
+  return 0;
+}
+
+static char mg_escape(int c) {
+  return mg_esc(c, true);
+}
+
+static size_t qcpy(void (*out)(char, void *), void *ptr, char *buf,
+                   size_t len) {
+  size_t i = 0, extra = 2;
+  out('"', ptr);
+  for (i = 0; i < len && buf[i] != '\0'; i++) {
+    char c = mg_escape(buf[i]);
+    if (c) {
+      out('\\', ptr), out(c, ptr), extra++;
+    } else {
+      out(buf[i], ptr);
+    }
+  }
+  out('"', ptr);
+  return i + extra;
+}
+
+static size_t bcpy(void (*out)(char, void *), void *ptr, uint8_t *buf,
+                   size_t len) {
+  size_t i, n = 0;
+  const char *t =
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+  out('"', ptr), n++;
+  for (i = 0; i < len; i += 3) {
+    uint8_t c1 = buf[i], c2 = i + 1 < len ? buf[i + 1] : 0,
+            c3 = i + 2 < len ? buf[i + 2] : 0;
+    char tmp[4] = {t[c1 >> 2], t[(c1 & 3) << 4 | (c2 >> 4)], '=', '='};
+    if (i + 1 < len) tmp[2] = t[(c2 & 15) << 2 | (c3 >> 6)];
+    if (i + 2 < len) tmp[3] = t[c3 & 63];
+    n += scpy(out, ptr, tmp, sizeof(tmp));
+  }
+  out('"', ptr), n++;
+  return n;
+}
+
+size_t mg_vrprintf(void (*out)(char, void *), void *param, const char *fmt,
+                   va_list *ap) {
+  size_t i = 0, n = 0;
+  while (fmt[i] != '\0') {
+    if (fmt[i] == '%') {
+      size_t j, k, x = 0, is_long = 0, w = 0 /* width */, pr = ~0U /* prec */;
+      char pad = ' ', minus = 0, c = fmt[++i];
+      if (c == '#') x++, c = fmt[++i];
+      if (c == '-') minus++, c = fmt[++i];
+      if (c == '0') pad = '0', c = fmt[++i];
+      while (is_digit(c)) w *= 10, w += (size_t) (c - '0'), c = fmt[++i];
+      if (c == '.') {
+        c = fmt[++i];
+        if (c == '*') {
+          pr = (size_t) va_arg(*ap, int);
+          c = fmt[++i];
+        } else {
+          pr = 0;
+          while (is_digit(c)) pr *= 10, pr += (size_t) (c - '0'), c = fmt[++i];
+        }
+      }
+      while (c == 'h') c = fmt[++i];  // Treat h and hh as int
+      if (c == 'l') {
+        is_long++, c = fmt[++i];
+        if (c == 'l') is_long++, c = fmt[++i];
+      }
+      if (c == 'p') x = 1, is_long = 1;
+      if (c == 'd' || c == 'u' || c == 'x' || c == 'X' || c == 'p' ||
+          c == 'g') {
+        bool s = (c == 'd'), h = (c == 'x' || c == 'X' || c == 'p');
+        char tmp[40];
+        size_t xl = x ? 2 : 0;
+        if (c == 'g' || c == 'f') {
+          double v = va_arg(*ap, double);
+          if (pr == ~0U) pr = 6;
+          k = mg_dtoa(tmp, sizeof(tmp), v, (int) pr);
+        } else if (is_long == 2) {
+          int64_t v = va_arg(*ap, int64_t);
+          k = mg_lld(tmp, v, s, h);
+        } else if (is_long == 1) {
+          long v = va_arg(*ap, long);
+          k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned long) v, s, h);
+        } else {
+          int v = va_arg(*ap, int);
+          k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned) v, s, h);
+        }
+        for (j = 0; j < xl && w > 0; j++) w--;
+        for (j = 0; pad == ' ' && !minus && k < w && j + k < w; j++)
+          n += scpy(out, param, &pad, 1);
+        n += scpy(out, param, (char *) "0x", xl);
+        for (j = 0; pad == '0' && k < w && j + k < w; j++)
+          n += scpy(out, param, &pad, 1);
+        n += scpy(out, param, tmp, k);
+        for (j = 0; pad == ' ' && minus && k < w && j + k < w; j++)
+          n += scpy(out, param, &pad, 1);
+      } else if (c == 'M') {
+        mg_pm_t f = va_arg(*ap, mg_pm_t);
+        n += f(out, param, ap);
+      } else if (c == 'c') {
+        int ch = va_arg(*ap, int);
+        out((char) ch, param);
+        n++;
+      } else if (c == 'H') {
+        // Print hex-encoded double-quoted string
+        size_t bl = (size_t) va_arg(*ap, int);
+        uint8_t *p = va_arg(*ap, uint8_t *), dquote = '"';
+        const char *hex = "0123456789abcdef";
+        n += scpy(out, param, (char *) &dquote, 1);
+        for (j = 0; j < bl; j++) {
+          n += scpy(out, param, (char *) &hex[(p[j] >> 4) & 15], 1);
+          n += scpy(out, param, (char *) &hex[p[j] & 15], 1);
+        }
+        n += scpy(out, param, (char *) &dquote, 1);
+      } else if (c == 'V') {
+        // Print base64-encoded double-quoted string
+        size_t len = (size_t) va_arg(*ap, int);
+        uint8_t *buf = va_arg(*ap, uint8_t *);
+        n += bcpy(out, param, buf, len);
+      } else if (c == 's' || c == 'Q') {
+        char *p = va_arg(*ap, char *);
+        size_t (*f)(void (*)(char, void *), void *, char *, size_t) =
+            c == 's' ? scpy : qcpy;
+        if (pr == ~0U) pr = p == NULL ? 0 : strlen(p);
+        for (j = 0; !minus && pr < w && j + pr < w; j++)
+          n += f(out, param, &pad, 1);
+        n += f(out, param, p, pr);
+        for (j = 0; minus && pr < w && j + pr < w; j++)
+          n += f(out, param, &pad, 1);
+      } else if (c == '%') {
+        out('%', param);
+        n++;
+      } else {
+        out('%', param);
+        out(c, param);
+        n += 2;
+      }
+      i++;
+    } else {
+      out(fmt[i], param), n++, i++;
+    }
+  }
+  return n;
+}
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/fs.c"
+#endif
+
+
+
+struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags) {
+  struct mg_fd *fd = (struct mg_fd *) calloc(1, sizeof(*fd));
+  if (fd != NULL) {
+    fd->fd = fs->op(path, flags);
+    fd->fs = fs;
+    if (fd->fd == NULL) {
+      free(fd);
+      fd = NULL;
+    }
+  }
+  return fd;
+}
+
+void mg_fs_close(struct mg_fd *fd) {
+  if (fd != NULL) {
+    fd->fs->cl(fd->fd);
+    free(fd);
+  }
+}
+
+char *mg_file_read(struct mg_fs *fs, const char *path, size_t *sizep) {
+  struct mg_fd *fd;
+  char *data = NULL;
+  size_t size = 0;
+  fs->st(path, &size, NULL);
+  if ((fd = mg_fs_open(fs, path, MG_FS_READ)) != NULL) {
+    data = (char *) calloc(1, size + 1);
+    if (data != NULL) {
+      if (fs->rd(fd->fd, data, size) != size) {
+        free(data);
+        data = NULL;
+      } else {
+        data[size] = '\0';
+        if (sizep != NULL) *sizep = size;
+      }
+    }
+    mg_fs_close(fd);
+  }
+  return data;
+}
+
+bool mg_file_write(struct mg_fs *fs, const char *path, const void *buf,
+                   size_t len) {
+  bool result = false;
+  struct mg_fd *fd;
+  char tmp[MG_PATH_MAX];
+  mg_snprintf(tmp, sizeof(tmp), "%s..%d", path, rand());
+  if ((fd = mg_fs_open(fs, tmp, MG_FS_WRITE)) != NULL) {
+    result = fs->wr(fd->fd, buf, len) == len;
+    mg_fs_close(fd);
+    if (result) {
+      fs->rm(path);
+      fs->mv(tmp, path);
+    } else {
+      fs->rm(tmp);
+    }
+  }
+  return result;
+}
+
+bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...) {
+  char tmp[256], *buf = tmp;
+  bool result;
+  size_t len;
+  va_list ap;
+  va_start(ap, fmt);
+  len = mg_vasprintf(&buf, sizeof(tmp), fmt, ap);
+  va_end(ap);
+  result = mg_file_write(fs, path, buf, len > 0 ? (size_t) len : 0);
+  if (buf != tmp) free(buf);
+  return result;
+}
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/fs_fat.c"
+#endif
+
+
+
+#if MG_ENABLE_FATFS
+#include <ff.h>
+
+static int mg_days_from_epoch(int y, int m, int d) {
+  y -= m <= 2;
+  int era = y / 400;
+  int yoe = y - era * 400;
+  int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
+  int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
+  return era * 146097 + doe - 719468;
+}
+
+static time_t mg_timegm(const struct tm *t) {
+  int year = t->tm_year + 1900;
+  int month = t->tm_mon;  // 0-11
+  if (month > 11) {
+    year += month / 12;
+    month %= 12;
+  } else if (month < 0) {
+    int years_diff = (11 - month) / 12;
+    year -= years_diff;
+    month += 12 * years_diff;
+  }
+  int x = mg_days_from_epoch(year, month + 1, t->tm_mday);
+  return 60 * (60 * (24L * x + t->tm_hour) + t->tm_min) + t->tm_sec;
+}
+
+static time_t ff_time_to_epoch(uint16_t fdate, uint16_t ftime) {
+  struct tm tm;
+  memset(&tm, 0, sizeof(struct tm));
+  tm.tm_sec = (ftime << 1) & 0x3e;
+  tm.tm_min = ((ftime >> 5) & 0x3f);
+  tm.tm_hour = ((ftime >> 11) & 0x1f);
+  tm.tm_mday = (fdate & 0x1f);
+  tm.tm_mon = ((fdate >> 5) & 0x0f) - 1;
+  tm.tm_year = ((fdate >> 9) & 0x7f) + 80;
+  return mg_timegm(&tm);
+}
+
+static int ff_stat(const char *path, size_t *size, time_t *mtime) {
+  FILINFO fi;
+  if (path[0] == '\0') {
+    if (size) *size = 0;
+    if (mtime) *mtime = 0;
+    return MG_FS_DIR;
+  } else if (f_stat(path, &fi) == 0) {
+    if (size) *size = (size_t) fi.fsize;
+    if (mtime) *mtime = ff_time_to_epoch(fi.fdate, fi.ftime);
+    return MG_FS_READ | MG_FS_WRITE | ((fi.fattrib & AM_DIR) ? MG_FS_DIR : 0);
+  } else {
+    return 0;
+  }
+}
+
+static void ff_list(const char *dir, void (*fn)(const char *, void *),
+                    void *userdata) {
+  DIR d;
+  FILINFO fi;
+  if (f_opendir(&d, dir) == FR_OK) {
+    while (f_readdir(&d, &fi) == FR_OK && fi.fname[0] != '\0') {
+      if (!strcmp(fi.fname, ".") || !strcmp(fi.fname, "..")) continue;
+      fn(fi.fname, userdata);
+    }
+    f_closedir(&d);
+  }
+}
+
+static void *ff_open(const char *path, int flags) {
+  FIL f;
+  unsigned char mode = FA_READ;
+  if (flags & MG_FS_WRITE) mode |= FA_WRITE | FA_OPEN_ALWAYS | FA_OPEN_APPEND;
+  if (f_open(&f, path, mode) == 0) {
+    FIL *fp = calloc(1, sizeof(*fp));
+    memcpy(fp, &f, sizeof(*fp));
+    return fp;
+  } else {
+    return NULL;
+  }
+}
+
+static void ff_close(void *fp) {
+  if (fp != NULL) {
+    f_close((FIL *) fp);
+    free(fp);
+  }
+}
+
+static size_t ff_read(void *fp, void *buf, size_t len) {
+  UINT n = 0, misalign = ((size_t) buf) & 3;
+  if (misalign) {
+    char aligned[4];
+    f_read((FIL *) fp, aligned, len > misalign ? misalign : len, &n);
+    memcpy(buf, aligned, n);
+  } else {
+    f_read((FIL *) fp, buf, len, &n);
+  }
+  return n;
+}
+
+static size_t ff_write(void *fp, const void *buf, size_t len) {
+  UINT n = 0;
+  return f_write((FIL *) fp, (char *) buf, len, &n) == FR_OK ? n : 0;
+}
+
+static size_t ff_seek(void *fp, size_t offset) {
+  f_lseek((FIL *) fp, offset);
+  return offset;
+}
+
+static bool ff_rename(const char *from, const char *to) {
+  return f_rename(from, to) == FR_OK;
+}
+
+static bool ff_remove(const char *path) {
+  return f_unlink(path) == FR_OK;
+}
+
+static bool ff_mkdir(const char *path) {
+  return f_mkdir(path) == FR_OK;
+}
+
+struct mg_fs mg_fs_fat = {ff_stat,  ff_list, ff_open,   ff_close,  ff_read,
+                          ff_write, ff_seek, ff_rename, ff_remove, ff_mkdir};
+#endif
+
 #ifdef MG_ENABLE_LINES
 #line 1 "src/fs_packed.c"
 #endif
 
 
+
 struct packed_file {
   const char *data;
   size_t size;
@@ -417,6 +1052,7 @@ struct packed_file {
 
 const char *mg_unpack(const char *path, size_t *size, time_t *mtime);
 const char *mg_unlist(size_t no);
+
 #if MG_ENABLE_PACKED_FS
 #else
 const char *mg_unpack(const char *path, size_t *size, time_t *mtime) {
@@ -430,8 +1066,9 @@ const char *mg_unlist(size_t no) {
 #endif
 
 static int is_dir_prefix(const char *prefix, size_t n, const char *path) {
-  return n < strlen(path) && memcmp(prefix, path, n) == 0 && path[n] == '/';
-  //(n == 0 || path[n] == MG_DIRSEP);
+  // MG_INFO(("[%.*s] [%s] %c", (int) n, prefix, path, path[n]));
+  return n < strlen(path) && strncmp(prefix, path, n) == 0 &&
+         (n == 0 || path[n] == '/' || path[n - 1] == '/');
 }
 
 static int packed_stat(const char *path, size_t *size, time_t *mtime) {
@@ -456,7 +1093,7 @@ static void packed_list(const char *dir, void (*fn)(const char *, void *),
     begin = &path[n + 1];
     end = strchr(begin, '/');
     if (end == NULL) end = begin + strlen(begin);
-    snprintf(buf, sizeof(buf), "%.*s", (int) (end - begin), begin);
+    mg_snprintf(buf, sizeof(buf), "%.*s", (int) (end - begin), begin);
     buf[sizeof(buf) - 1] = '\0';
     // If this entry has been already listed, skip
     // NOTE: we're assuming that file list is sorted alphabetically
@@ -466,24 +1103,20 @@ static void packed_list(const char *dir, void (*fn)(const char *, void *),
   }
 }
 
-static struct mg_fd *packed_open(const char *path, int flags) {
+static void *packed_open(const char *path, int flags) {
   size_t size = 0;
   const char *data = mg_unpack(path, &size, NULL);
   struct packed_file *fp = NULL;
-  struct mg_fd *fd = NULL;
   if (data == NULL) return NULL;
   if (flags & MG_FS_WRITE) return NULL;
   fp = (struct packed_file *) calloc(1, sizeof(*fp));
-  fd = (struct mg_fd *) calloc(1, sizeof(*fd));
   fp->size = size;
   fp->data = data;
-  fd->fd = fp;
-  fd->fs = &mg_fs_packed;
-  return fd;
+  return (void *) fp;
 }
 
-static void packed_close(struct mg_fd *fd) {
-  if (fd) free(fd->fd), free(fd);
+static void packed_close(void *fp) {
+  if (fp != NULL) free(fp);
 }
 
 static size_t packed_read(void *fd, void *buf, size_t len) {
@@ -506,32 +1139,61 @@ static size_t packed_seek(void *fd, size_t offset) {
   return fp->pos;
 }
 
-struct mg_fs mg_fs_packed = {packed_stat,  packed_list, packed_open,
-                             packed_close, packed_read, packed_write,
-                             packed_seek};
+static bool packed_rename(const char *from, const char *to) {
+  (void) from, (void) to;
+  return false;
+}
+
+static bool packed_remove(const char *path) {
+  (void) path;
+  return false;
+}
+
+static bool packed_mkdir(const char *path) {
+  (void) path;
+  return false;
+}
+
+struct mg_fs mg_fs_packed = {
+    packed_stat,  packed_list, packed_open,   packed_close,  packed_read,
+    packed_write, packed_seek, packed_rename, packed_remove, packed_mkdir};
 
 #ifdef MG_ENABLE_LINES
 #line 1 "src/fs_posix.c"
 #endif
 
 
-#if defined(FOPEN_MAX)
+#if MG_ENABLE_FILE
+
+#ifndef MG_STAT_STRUCT
+#define MG_STAT_STRUCT stat
+#endif
+
+#ifndef MG_STAT_FUNC
+#define MG_STAT_FUNC stat
+#endif
+
 static int p_stat(const char *path, size_t *size, time_t *mtime) {
-#ifdef _WIN32
+#if !defined(S_ISDIR)
+  MG_ERROR(("stat() API is not supported. %p %p %p", path, size, mtime));
+  return 0;
+#else
+#if MG_ARCH == MG_ARCH_WIN32
   struct _stati64 st;
   wchar_t tmp[PATH_MAX];
   MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) / sizeof(tmp[0]));
   if (_wstati64(tmp, &st) != 0) return 0;
 #else
-  struct stat st;
-  if (stat(path, &st) != 0) return 0;
+  struct MG_STAT_STRUCT st;
+  if (MG_STAT_FUNC(path, &st) != 0) return 0;
 #endif
   if (size) *size = (size_t) st.st_size;
   if (mtime) *mtime = st.st_mtime;
   return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ? MG_FS_DIR : 0);
+#endif
 }
 
-#ifdef _WIN32
+#if MG_ARCH == MG_ARCH_WIN32
 struct dirent {
   char d_name[MAX_PATH];
 };
@@ -656,30 +1318,20 @@ static void p_list(const char *dir, void (*fn)(const char *, void *),
 #endif
 }
 
-static struct mg_fd *p_open(const char *path, int flags) {
-  const char *mode = flags == (MG_FS_READ | MG_FS_WRITE) ? "r+b"
-                     : flags & MG_FS_READ                ? "rb"
-                     : flags & MG_FS_WRITE               ? "wb"
-                                                         : "";
-  void *fp = NULL;
-  struct mg_fd *fd = NULL;
-#ifdef _WIN32
+static void *p_open(const char *path, int flags) {
+  const char *mode = flags == MG_FS_READ ? "rb" : "a+b";
+#if MG_ARCH == MG_ARCH_WIN32
   wchar_t b1[PATH_MAX], b2[10];
   MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) / sizeof(b1[0]));
   MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) / sizeof(b2[0]));
-  fp = (void *) _wfopen(b1, b2);
+  return (void *) _wfopen(b1, b2);
 #else
-  fp = (void *) fopen(path, mode);
+  return (void *) fopen(path, mode);
 #endif
-  if (fp == NULL) return NULL;
-  fd = (struct mg_fd *) calloc(1, sizeof(*fd));
-  fd->fd = fp;
-  fd->fs = &mg_fs_posix;
-  return fd;
 }
 
-static void p_close(struct mg_fd *fd) {
-  if (fd != NULL) fclose((FILE *) fd->fd), free(fd);
+static void p_close(void *fp) {
+  fclose((FILE *) fp);
 }
 
 static size_t p_read(void *fp, void *buf, size_t len) {
@@ -691,58 +1343,73 @@ static size_t p_write(void *fp, const void *buf, size_t len) {
 }
 
 static size_t p_seek(void *fp, size_t offset) {
-#if (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) || \
-  (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || \
-  (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600)
-  fseeko((FILE *) fp, (off_t) offset, SEEK_SET);
+#if (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) ||  \
+    (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || \
+    (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600)
+  if (fseeko((FILE *) fp, (off_t) offset, SEEK_SET) != 0) (void) 0;
 #else
-  fseek((FILE *) fp, (long) offset, SEEK_SET);
+  if (fseek((FILE *) fp, (long) offset, SEEK_SET) != 0) (void) 0;
 #endif
   return (size_t) ftell((FILE *) fp);
 }
-#else
-static char *p_realpath(const char *path, char *resolved_path) {
-  (void) path, (void) resolved_path;
-  return NULL;
+
+static bool p_rename(const char *from, const char *to) {
+  return rename(from, to) == 0;
 }
 
-static int p_stat(const char *path, size_t *size, time_t *mtime) {
-  (void) path, (void) size, (void) mtime;
-  return 0;
+static bool p_remove(const char *path) {
+  return remove(path) == 0;
 }
 
-static void p_list(const char *path, void (*fn)(const char *, void *),
-                   void *userdata) {
-  (void) path, (void) fn, (void) userdata;
+static bool p_mkdir(const char *path) {
+  return mkdir(path, 0775) == 0;
 }
 
-static struct mg_fd *p_open(const char *path, int flags) {
+#else
+
+static int p_stat(const char *path, size_t *size, time_t *mtime) {
+  (void) path, (void) size, (void) mtime;
+  return 0;
+}
+static void p_list(const char *path, void (*fn)(const char *, void *),
+                   void *userdata) {
+  (void) path, (void) fn, (void) userdata;
+}
+static void *p_open(const char *path, int flags) {
   (void) path, (void) flags;
   return NULL;
 }
-
-static void p_close(struct mg_fd *fd) {
-  (void) fd;
+static void p_close(void *fp) {
+  (void) fp;
 }
-
 static size_t p_read(void *fd, void *buf, size_t len) {
   (void) fd, (void) buf, (void) len;
   return 0;
 }
-
 static size_t p_write(void *fd, const void *buf, size_t len) {
   (void) fd, (void) buf, (void) len;
   return 0;
 }
-
 static size_t p_seek(void *fd, size_t offset) {
   (void) fd, (void) offset;
   return (size_t) ~0;
 }
+static bool p_rename(const char *from, const char *to) {
+  (void) from, (void) to;
+  return false;
+}
+static bool p_remove(const char *path) {
+  (void) path;
+  return false;
+}
+static bool p_mkdir(const char *path) {
+  (void) path;
+  return false;
+}
 #endif
 
-struct mg_fs mg_fs_posix = {p_stat, p_list,  p_open, p_close,
-                            p_read, p_write, p_seek};
+struct mg_fs mg_fs_posix = {p_stat,  p_list, p_open,   p_close,  p_read,
+                            p_write, p_seek, p_rename, p_remove, p_mkdir};
 
 #ifdef MG_ENABLE_LINES
 #line 1 "src/http.c"
@@ -781,7 +1448,7 @@ size_t mg_http_next_multipart(struct mg_str body, size_t ofs,
   // Skip boundary
   while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++;
   if (b <= ofs || b + 2 >= max) return 0;
-  // LOG(LL_INFO, ("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs), s));
+  // MG_INFO(("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs), s));
 
   // Skip headers
   h1 = h2 = b + 2;
@@ -789,7 +1456,7 @@ size_t mg_http_next_multipart(struct mg_str body, size_t ofs,
     while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++;
     if (h2 == h1) break;
     if (h2 + 2 >= max) return 0;
-    // LOG(LL_INFO, ("Header: [%.*s]", (int) (h2 - h1), &s[h1]));
+    // MG_INFO(("Header: [%.*s]", (int) (h2 - h1), &s[h1]));
     if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] == ':' &&
         mg_ncasecmp(&s[h1], cd.ptr, cd.len) == 0) {
       struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 + cd.len + 2));
@@ -805,7 +1472,7 @@ size_t mg_http_next_multipart(struct mg_str body, size_t ofs,
 
   if (b2 + 2 >= max) return 0;
   if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1);
-  // LOG(LL_INFO, ("Body: [%.*s]", (int) (b2 - b1), &s[b1]));
+  // MG_INFO(("Body: [%.*s]", (int) (b2 - b1), &s[b1]));
   return b2 + 2;
 }
 
@@ -831,50 +1498,54 @@ void mg_http_bauth(struct mg_connection *c, const char *user,
     c->send.len += 21 + (size_t) n + 2;
     memcpy(&c->send.buf[c->send.len - 2], "\r\n", 2);
   } else {
-    LOG(LL_ERROR, ("%lu %s cannot resize iobuf %d->%d ", c->id, c->label,
-                   (int) c->send.size, (int) need));
+    MG_ERROR(("%lu %s cannot resize iobuf %d->%d ", c->id, c->label,
+              (int) c->send.size, (int) need));
+  }
+}
+
+struct mg_str mg_http_var(struct mg_str buf, struct mg_str name) {
+  struct mg_str k, v, result = mg_str_n(NULL, 0);
+  while (mg_split(&buf, &k, &v, '&')) {
+    if (name.len == k.len && mg_ncasecmp(name.ptr, k.ptr, k.len) == 0) {
+      result = v;
+      break;
+    }
   }
+  return result;
 }
 
 int mg_http_get_var(const struct mg_str *buf, const char *name, char *dst,
                     size_t dst_len) {
-  const char *p, *e, *s;
-  size_t name_len;
   int len;
-
   if (dst == NULL || dst_len == 0) {
     len = -2;  // Bad destination
   } else if (buf->ptr == NULL || name == NULL || buf->len == 0) {
     len = -1;  // Bad source
     dst[0] = '\0';
   } else {
-    name_len = strlen(name);
-    e = buf->ptr + buf->len;
-    len = -4;  // Name does not exist
-    dst[0] = '\0';
-    for (p = buf->ptr; p + name_len < e; p++) {
-      if ((p == buf->ptr || p[-1] == '&') && p[name_len] == '=' &&
-          !mg_ncasecmp(name, p, name_len)) {
-        p += name_len + 1;
-        s = (const char *) memchr(p, '&', (size_t) (e - p));
-        if (s == NULL) s = e;
-        len = mg_url_decode(p, (size_t) (s - p), dst, dst_len, 1);
-        if (len < 0) len = -3;  // Failed to decode
-        break;
-      }
+    struct mg_str v = mg_http_var(*buf, mg_str(name));
+    if (v.ptr == NULL) {
+      len = -4;  // Name does not exist
+    } else {
+      len = mg_url_decode(v.ptr, v.len, dst, dst_len, 1);
+      if (len < 0) len = -3;  // Failed to decode
     }
   }
   return len;
 }
 
+static bool isx(int c) {
+  return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
+         (c >= 'A' && c <= 'F');
+}
+
 int mg_url_decode(const char *src, size_t src_len, char *dst, size_t dst_len,
                   int is_form_url_encoded) {
   size_t i, j;
   for (i = j = 0; i < src_len && j + 1 < dst_len; i++, j++) {
     if (src[i] == '%') {
       // Use `i + 2 < src_len`, not `i < src_len - 2`, note small src_len
-      if (i + 2 < src_len && isxdigit(*(const unsigned char *) (src + i + 1)) &&
-          isxdigit(*(const unsigned char *) (src + i + 2))) {
+      if (i + 2 < src_len && isx(src[i + 1]) && isx(src[i + 2])) {
         mg_unhex(src + i + 1, 2, (uint8_t *) &dst[j]);
         i += 2;
       } else {
@@ -890,11 +1561,14 @@ int mg_url_decode(const char *src, size_t src_len, char *dst, size_t dst_len,
   return i >= src_len && j < dst_len ? (int) j : -1;
 }
 
+static bool isok(uint8_t c) {
+  return c == '\n' || c == '\r' || c >= ' ';
+}
+
 int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) {
   size_t i;
   for (i = 0; i < buf_len; i++) {
-    if (!isprint(buf[i]) && buf[i] != '\r' && buf[i] != '\n' && buf[i] < 128)
-      return -1;
+    if (!isok(buf[i])) return -1;
     if ((i > 0 && buf[i] == '\n' && buf[i - 1] == '\n') ||
         (i > 3 && buf[i] == '\n' && buf[i - 1] == '\r' && buf[i - 2] == '\n'))
       return (int) i + 1;
@@ -931,7 +1605,7 @@ static void mg_http_parse_headers(const char *s, const char *end,
     if (k.len == tmp.len) continue;
     while (v.len > 0 && v.ptr[v.len - 1] == ' ') v.len--;  // Trim spaces
     if (k.len == 0) break;
-    // LOG(LL_INFO, ("--HH [%.*s] [%.*s] [%.*s]", (int) tmp.len - 1, tmp.ptr,
+    // MG_INFO(("--HH [%.*s] [%.*s] [%.*s]", (int) tmp.len - 1, tmp.ptr,
     //(int) k.len, k.ptr, (int) v.len, v.ptr));
     h[i].name = k;
     h[i].value = v;
@@ -1007,8 +1681,8 @@ int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) {
 static void mg_http_vprintf_chunk(struct mg_connection *c, const char *fmt,
                                   va_list ap) {
   char mem[256], *buf = mem;
-  int len = mg_vasprintf(&buf, sizeof(mem), fmt, ap);
-  mg_printf(c, "%X\r\n", len);
+  size_t len = mg_vasprintf(&buf, sizeof(mem), fmt, ap);
+  mg_printf(c, "%lx\r\n", (unsigned long) len);
   mg_send(c, buf, len > 0 ? (size_t) len : 0);
   mg_send(c, "\r\n", 2);
   if (buf != mem) free(buf);
@@ -1022,7 +1696,7 @@ void mg_http_printf_chunk(struct mg_connection *c, const char *fmt, ...) {
 }
 
 void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len) {
-  mg_printf(c, "%lX\r\n", (unsigned long) len);
+  mg_printf(c, "%lx\r\n", (unsigned long) len);
   mg_send(c, buf, len);
   mg_send(c, "\r\n", 2);
 }
@@ -1031,68 +1705,20 @@ void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len) {
 static const char *mg_http_status_code_str(int status_code) {
   switch (status_code) {
     case 100: return "Continue";
-    case 101: return "Switching Protocols";
-    case 102: return "Processing";
-    case 200: return "OK";
     case 201: return "Created";
     case 202: return "Accepted";
-    case 203: return "Non-authoritative Information";
     case 204: return "No Content";
-    case 205: return "Reset Content";
     case 206: return "Partial Content";
-    case 207: return "Multi-Status";
-    case 208: return "Already Reported";
-    case 226: return "IM Used";
-    case 300: return "Multiple Choices";
     case 301: return "Moved Permanently";
     case 302: return "Found";
-    case 303: return "See Other";
     case 304: return "Not Modified";
-    case 305: return "Use Proxy";
-    case 307: return "Temporary Redirect";
-    case 308: return "Permanent Redirect";
     case 400: return "Bad Request";
     case 401: return "Unauthorized";
-    case 402: return "Payment Required";
     case 403: return "Forbidden";
     case 404: return "Not Found";
-    case 405: return "Method Not Allowed";
-    case 406: return "Not Acceptable";
-    case 407: return "Proxy Authentication Required";
-    case 408: return "Request Timeout";
-    case 409: return "Conflict";
-    case 410: return "Gone";
-    case 411: return "Length Required";
-    case 412: return "Precondition Failed";
-    case 413: return "Payload Too Large";
-    case 414: return "Request-URI Too Long";
-    case 415: return "Unsupported Media Type";
-    case 416: return "Requested Range Not Satisfiable";
-    case 417: return "Expectation Failed";
     case 418: return "I'm a teapot";
-    case 421: return "Misdirected Request";
-    case 422: return "Unprocessable Entity";
-    case 423: return "Locked";
-    case 424: return "Failed Dependency";
-    case 426: return "Upgrade Required";
-    case 428: return "Precondition Required";
-    case 429: return "Too Many Requests";
-    case 431: return "Request Header Fields Too Large";
-    case 444: return "Connection Closed Without Response";
-    case 451: return "Unavailable For Legal Reasons";
-    case 499: return "Client Closed Request";
     case 500: return "Internal Server Error";
     case 501: return "Not Implemented";
-    case 502: return "Bad Gateway";
-    case 503: return "Service Unavailable";
-    case 504: return "Gateway Timeout";
-    case 505: return "HTTP Version Not Supported";
-    case 506: return "Variant Also Negotiates";
-    case 507: return "Insufficient Storage";
-    case 508: return "Loop Detected";
-    case 510: return "Not Extended";
-    case 511: return "Network Authentication Required";
-    case 599: return "Network Connect Timeout Error";
     default: return "OK";
   }
 }
@@ -1102,115 +1728,89 @@ void mg_http_reply(struct mg_connection *c, int code, const char *headers,
                    const char *fmt, ...) {
   char mem[256], *buf = mem;
   va_list ap;
-  int len;
+  size_t len;
   va_start(ap, fmt);
   len = mg_vasprintf(&buf, sizeof(mem), fmt, ap);
   va_end(ap);
   mg_printf(c, "HTTP/1.1 %d %s\r\n%sContent-Length: %d\r\n\r\n", code,
             mg_http_status_code_str(code), headers == NULL ? "" : headers, len);
-  mg_send(c, buf, len > 0 ? (size_t) len : 0);
+  mg_send(c, buf, len > 0 ? len : 0);
   if (buf != mem) free(buf);
 }
 
 static void http_cb(struct mg_connection *, int, void *, void *);
 static void restore_http_cb(struct mg_connection *c) {
-  struct mg_fd *fd = (struct mg_fd *) c->pfn_data;
-  if (fd != NULL) fd->fs->close(fd);
+  mg_fs_close((struct mg_fd *) c->pfn_data);
   c->pfn_data = NULL;
   c->pfn = http_cb;
 }
 
 char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime);
 char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime) {
-  snprintf(buf, len, "\"%lx." MG_INT64_FMT "\"", (unsigned long) mtime,
-           (int64_t) size);
+  mg_snprintf(buf, len, "\"%lld.%lld\"", (int64_t) mtime, (int64_t) size);
   return buf;
 }
 
-int mg_http_upload(struct mg_connection *c, struct mg_http_message *hm,
-                   const char *dir) {
-  char offset[40] = "", name[200] = "", path[256];
-  mg_http_get_var(&hm->query, "offset", offset, sizeof(offset));
-  mg_http_get_var(&hm->query, "name", name, sizeof(name));
-  if (name[0] == '\0') {
-    mg_http_reply(c, 400, "", "%s", "name required");
-    return -1;
-  } else {
-    FILE *fp;
-    size_t oft = strtoul(offset, NULL, 0);
-    snprintf(path, sizeof(path), "%s%c%s", dir, MG_DIRSEP, name);
-    LOG(LL_DEBUG,
-        ("%p %d bytes @ %d [%s]", c->fd, (int) hm->body.len, (int) oft, name));
-    if ((fp = fopen(path, oft == 0 ? "wb" : "ab")) == NULL) {
-      mg_http_reply(c, 400, "", "fopen(%s): %d", name, errno);
-      return -2;
-    } else {
-      fwrite(hm->body.ptr, 1, hm->body.len, fp);
-      fclose(fp);
-      mg_http_reply(c, 200, "", "");
-      return (int) hm->body.len;
-    }
-  }
-}
-
 static void static_cb(struct mg_connection *c, int ev, void *ev_data,
                       void *fn_data) {
   if (ev == MG_EV_WRITE || ev == MG_EV_POLL) {
     struct mg_fd *fd = (struct mg_fd *) fn_data;
     // Read to send IO buffer directly, avoid extra on-stack buffer
-    size_t n, max = 2 * MG_IO_SIZE;
+    size_t n, max = MG_IO_SIZE, space, *cl = (size_t *) c->label;
     if (c->send.size < max) mg_iobuf_resize(&c->send, max);
     if (c->send.len >= c->send.size) return;  // Rate limit
-    n = fd->fs->read(fd->fd, c->send.buf + c->send.len,
-                     c->send.size - c->send.len);
-    if (n > 0) c->send.len += n;
-    if (c->send.len < c->send.size) restore_http_cb(c);
+    if ((space = c->send.size - c->send.len) > *cl) space = *cl;
+    n = fd->fs->rd(fd->fd, c->send.buf + c->send.len, space);
+    c->send.len += n;
+    *cl -= n;
+    if (n == 0) restore_http_cb(c);
   } else if (ev == MG_EV_CLOSE) {
     restore_http_cb(c);
   }
   (void) ev_data;
 }
 
+// Known mime types. Keep it outside guess_content_type() function, since
+// some environments don't like it defined there.
+// clang-format off
+static struct mg_str s_known_types[] = {
+    MG_C_STR("html"), MG_C_STR("text/html; charset=utf-8"),
+    MG_C_STR("htm"), MG_C_STR("text/html; charset=utf-8"),
+    MG_C_STR("css"), MG_C_STR("text/css; charset=utf-8"),
+    MG_C_STR("js"), MG_C_STR("text/javascript; charset=utf-8"),
+    MG_C_STR("gif"), MG_C_STR("image/gif"),
+    MG_C_STR("png"), MG_C_STR("image/png"),
+    MG_C_STR("jpg"), MG_C_STR("image/jpeg"),
+    MG_C_STR("jpeg"), MG_C_STR("image/jpeg"),
+    MG_C_STR("woff"), MG_C_STR("font/woff"),
+    MG_C_STR("ttf"), MG_C_STR("font/ttf"),
+    MG_C_STR("svg"), MG_C_STR("image/svg+xml"),
+    MG_C_STR("txt"), MG_C_STR("text/plain; charset=utf-8"),
+    MG_C_STR("avi"), MG_C_STR("video/x-msvideo"),
+    MG_C_STR("csv"), MG_C_STR("text/csv"),
+    MG_C_STR("doc"), MG_C_STR("application/msword"),
+    MG_C_STR("exe"), MG_C_STR("application/octet-stream"),
+    MG_C_STR("gz"), MG_C_STR("application/gzip"),
+    MG_C_STR("ico"), MG_C_STR("image/x-icon"),
+    MG_C_STR("json"), MG_C_STR("application/json"),
+    MG_C_STR("mov"), MG_C_STR("video/quicktime"),
+    MG_C_STR("mp3"), MG_C_STR("audio/mpeg"),
+    MG_C_STR("mp4"), MG_C_STR("video/mp4"),
+    MG_C_STR("mpeg"), MG_C_STR("video/mpeg"),
+    MG_C_STR("pdf"), MG_C_STR("application/pdf"),
+    MG_C_STR("shtml"), MG_C_STR("text/html; charset=utf-8"),
+    MG_C_STR("tgz"), MG_C_STR("application/tar-gz"),
+    MG_C_STR("wav"), MG_C_STR("audio/wav"),
+    MG_C_STR("webp"), MG_C_STR("image/webp"),
+    MG_C_STR("zip"), MG_C_STR("application/zip"),
+    MG_C_STR("3gp"), MG_C_STR("video/3gpp"),
+    {0, 0},
+};
+// clang-format on
+
 static struct mg_str guess_content_type(struct mg_str path, const char *extra) {
-  // clang-format off
-  struct mimeentry { struct mg_str extension, value; };
-  #define MIME_ENTRY(a, b) {{a, sizeof(a) - 1 }, { b, sizeof(b) - 1 }}
-  // clang-format on
-  const struct mimeentry tab[] = {
-      MIME_ENTRY("html", "text/html; charset=utf-8"),
-      MIME_ENTRY("htm", "text/html; charset=utf-8"),
-      MIME_ENTRY("css", "text/css; charset=utf-8"),
-      MIME_ENTRY("js", "text/javascript; charset=utf-8"),
-      MIME_ENTRY("gif", "image/gif"),
-      MIME_ENTRY("png", "image/png"),
-      MIME_ENTRY("jpg", "image/jpeg"),
-      MIME_ENTRY("jpeg", "image/jpeg"),
-      MIME_ENTRY("woff", "font/woff"),
-      MIME_ENTRY("ttf", "font/ttf"),
-      MIME_ENTRY("svg", "image/svg+xml"),
-      MIME_ENTRY("txt", "text/plain; charset=utf-8"),
-      MIME_ENTRY("avi", "video/x-msvideo"),
-      MIME_ENTRY("csv", "text/csv"),
-      MIME_ENTRY("doc", "application/msword"),
-      MIME_ENTRY("exe", "application/octet-stream"),
-      MIME_ENTRY("gz", "application/gzip"),
-      MIME_ENTRY("ico", "image/x-icon"),
-      MIME_ENTRY("json", "application/json"),
-      MIME_ENTRY("mov", "video/quicktime"),
-      MIME_ENTRY("mp3", "audio/mpeg"),
-      MIME_ENTRY("mp4", "video/mp4"),
-      MIME_ENTRY("mpeg", "video/mpeg"),
-      MIME_ENTRY("pdf", "application/pdf"),
-      MIME_ENTRY("shtml", "text/html; charset=utf-8"),
-      MIME_ENTRY("tgz", "application/tar-gz"),
-      MIME_ENTRY("wav", "audio/wav"),
-      MIME_ENTRY("webp", "image/webp"),
-      MIME_ENTRY("zip", "application/zip"),
-      MIME_ENTRY("3gp", "video/3gpp"),
-      {{0, 0}, {0, 0}},
-  };
-  size_t i = 0;
   struct mg_str k, v, s = mg_str(extra);
+  size_t i = 0;
 
   // Shrink path to its extension only
   while (i < path.len && path.ptr[path.len - i - 1] != '.') i++;
@@ -1223,8 +1823,8 @@ static struct mg_str guess_content_type(struct mg_str path, const char *extra) {
   }
 
   // Process built-in mime types
-  for (i = 0; tab[i].extension.ptr != NULL; i++) {
-    if (mg_strcmp(path, tab[i].extension) == 0) return tab[i].value;
+  for (i = 0; s_known_types[i].ptr != NULL; i += 2) {
+    if (mg_strcmp(path, s_known_types[i]) == 0) return s_known_types[i + 1];
   }
 
   return mg_str("text/plain; charset=utf-8");
@@ -1232,18 +1832,18 @@ static struct mg_str guess_content_type(struct mg_str path, const char *extra) {
 
 static int getrange(struct mg_str *s, int64_t *a, int64_t *b) {
   size_t i, numparsed = 0;
-  LOG(LL_INFO, ("%.*s", (int) s->len, s->ptr));
+  // MG_INFO(("%.*s", (int) s->len, s->ptr));
   for (i = 0; i + 6 < s->len; i++) {
     if (memcmp(&s->ptr[i], "bytes=", 6) == 0) {
       struct mg_str p = mg_str_n(s->ptr + i + 6, s->len - i - 6);
       if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++;
       *a = mg_to64(p);
-      // LOG(LL_INFO, ("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed));
+      // MG_INFO(("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed));
       while (p.len && p.ptr[0] >= '0' && p.ptr[0] <= '9') p.ptr++, p.len--;
       if (p.len && p.ptr[0] == '-') p.ptr++, p.len--;
       *b = mg_to64(p);
       if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++;
-      // LOG(LL_INFO, ("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed));
+      // MG_INFO(("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed));
       break;
     }
   }
@@ -1251,62 +1851,85 @@ static int getrange(struct mg_str *s, int64_t *a, int64_t *b) {
 }
 
 void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm,
-                        const char *path, struct mg_http_serve_opts *opts) {
-  char etag[64];
+                        const char *path,
+                        const struct mg_http_serve_opts *opts) {
+  char etag[64], tmp[MG_PATH_MAX];
   struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
-  struct mg_fd *fd = fs->open(path, MG_FS_READ);
+  struct mg_fd *fd = path == NULL ? NULL : mg_fs_open(fs, path, MG_FS_READ);
   size_t size = 0;
   time_t mtime = 0;
   struct mg_str *inm = NULL;
+  struct mg_str mime = guess_content_type(mg_str(path), opts->mime_types);
+  bool gzip = false;
+
+  // If file does not exist, we try to open file PATH.gz - and if such
+  // pre-compressed .gz file exists, serve it with the Content-Encoding: gzip
+  // Note - we ignore Accept-Encoding, cause we don't have a choice
+  if (fd == NULL) {
+    MG_DEBUG(("NULL [%s]", path));
+    mg_snprintf(tmp, sizeof(tmp), "%s.gz", path);
+    if ((fd = mg_fs_open(fs, tmp, MG_FS_READ)) != NULL) {
+      gzip = true;
+      path = tmp;
+    } else if (opts->page404 != NULL) {
+      // No precompressed file, serve 404
+      fd = mg_fs_open(fs, opts->page404, MG_FS_READ);
+      mime = guess_content_type(mg_str(path), opts->mime_types);
+      path = opts->page404;
+    }
+  }
 
-  if (fd == NULL || fs->stat(path, &size, &mtime) == 0) {
-    LOG(LL_DEBUG, ("404 [%s] %p", path, (void *) fd));
-    mg_http_reply(c, 404, "", "%s", "Not found\n");
-    fs->close(fd);
+  if (fd == NULL || fs->st(path, &size, &mtime) == 0) {
+    mg_http_reply(c, 404, opts->extra_headers, "Not found\n");
+    mg_fs_close(fd);
     // NOTE: mg_http_etag() call should go first!
   } else if (mg_http_etag(etag, sizeof(etag), size, mtime) != NULL &&
              (inm = mg_http_get_header(hm, "If-None-Match")) != NULL &&
              mg_vcasecmp(inm, etag) == 0) {
-    fs->close(fd);
-    mg_printf(c, "HTTP/1.1 304 Not Modified\r\nContent-Length: 0\r\n\r\n");
+    mg_fs_close(fd);
+    mg_printf(c, "HTTP/1.1 304 Not Modified\r\n%sContent-Length: 0\r\n\r\n",
+              opts->extra_headers ? opts->extra_headers : "");
   } else {
     int n, status = 200;
-    char range[100] = "";
+    char range[100];
     int64_t r1 = 0, r2 = 0, cl = (int64_t) size;
-    struct mg_str mime = guess_content_type(mg_str(path), opts->mime_types);
 
     // Handle Range header
     struct mg_str *rh = mg_http_get_header(hm, "Range");
+    range[0] = '\0';
     if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0 && r1 >= 0 && r2 >= 0) {
       // If range is specified like "400-", set second limit to content len
       if (n == 1) r2 = cl - 1;
       if (r1 > r2 || r2 >= cl) {
         status = 416;
         cl = 0;
-        snprintf(range, sizeof(range),
-                 "Content-Range: bytes */" MG_INT64_FMT "\r\n", (int64_t) size);
+        mg_snprintf(range, sizeof(range), "Content-Range: bytes */%lld\r\n",
+                    (int64_t) size);
       } else {
         status = 206;
         cl = r2 - r1 + 1;
-        snprintf(range, sizeof(range),
-                 "Content-Range: bytes " MG_INT64_FMT "-" MG_INT64_FMT
-                 "/" MG_INT64_FMT "\r\n",
-                 r1, r1 + cl - 1, (int64_t) size);
-        fs->seek(fd->fd, (size_t) r1);
+        mg_snprintf(range, sizeof(range),
+                    "Content-Range: bytes %lld-%lld/%lld\r\n", r1, r1 + cl - 1,
+                    (int64_t) size);
+        fs->sk(fd->fd, (size_t) r1);
       }
     }
-
     mg_printf(c,
-              "HTTP/1.1 %d %s\r\nContent-Type: %.*s\r\n"
-              "Etag: %s\r\nContent-Length: " MG_INT64_FMT "\r\n%s%s\r\n",
+              "HTTP/1.1 %d %s\r\n"
+              "Content-Type: %.*s\r\n"
+              "Etag: %s\r\n"
+              "Content-Length: %llu\r\n"
+              "%s%s%s\r\n",
               status, mg_http_status_code_str(status), (int) mime.len, mime.ptr,
-              etag, cl, range, opts->extra_headers ? opts->extra_headers : "");
+              etag, cl, gzip ? "Content-Encoding: gzip\r\n" : "", range,
+              opts->extra_headers ? opts->extra_headers : "");
     if (mg_vcasecmp(&hm->method, "HEAD") == 0) {
       c->is_draining = 1;
-      fs->close(fd);
+      mg_fs_close(fd);
     } else {
       c->pfn = static_cb;
       c->pfn_data = fd;
+      *(size_t *) c->label = (size_t) cl;  // Track to-be-sent content length
     }
   }
 }
@@ -1314,7 +1937,7 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm,
 struct printdirentrydata {
   struct mg_connection *c;
   struct mg_http_message *hm;
-  struct mg_http_serve_opts *opts;
+  const struct mg_http_serve_opts *opts;
   const char *dir;
 };
 
@@ -1323,41 +1946,35 @@ static void printdirentry(const char *name, void *userdata) {
   struct mg_fs *fs = d->opts->fs == NULL ? &mg_fs_posix : d->opts->fs;
   size_t size = 0;
   time_t t = 0;
-  char path[MG_PATH_MAX], sz[64], mod[64];
+  char path[MG_PATH_MAX], sz[40], mod[40];
   int flags, n = 0;
 
-  // LOG(LL_DEBUG, ("[%s] [%s]", d->dir, name));
-  if (snprintf(path, sizeof(path), "%s%c%s", d->dir, '/', name) < 0) {
-    LOG(LL_ERROR, ("%s truncated", name));
-  } else if ((flags = fs->stat(path, &size, &t)) == 0) {
-    LOG(LL_ERROR, ("%lu stat(%s): %d", d->c->id, path, errno));
+  // MG_DEBUG(("[%s] [%s]", d->dir, name));
+  if (mg_snprintf(path, sizeof(path), "%s%c%s", d->dir, '/', name) >
+      sizeof(path)) {
+    MG_ERROR(("%s truncated", name));
+  } else if ((flags = fs->st(path, &size, &t)) == 0) {
+    MG_ERROR(("%lu stat(%s): %d", d->c->id, path, errno));
   } else {
     const char *slash = flags & MG_FS_DIR ? "/" : "";
-    struct tm tm;
     if (flags & MG_FS_DIR) {
-      snprintf(sz, sizeof(sz), "%s", "[DIR]");
-    } else if (size < 1024) {
-      snprintf(sz, sizeof(sz), "%d", (int) size);
-    } else if (size < 0x100000) {
-      snprintf(sz, sizeof(sz), "%.1fk", (double) size / 1024.0);
-    } else if (size < 0x40000000) {
-      snprintf(sz, sizeof(sz), "%.1fM", (double) size / 1048576);
+      mg_snprintf(sz, sizeof(sz), "%s", "[DIR]");
     } else {
-      snprintf(sz, sizeof(sz), "%.1fG", (double) size / 1073741824);
+      mg_snprintf(sz, sizeof(sz), "%lld", (uint64_t) size);
     }
-    strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime_r(&t, &tm));
+    mg_snprintf(mod, sizeof(mod), "%ld", (unsigned long) t);
     n = (int) mg_url_encode(name, strlen(name), path, sizeof(path));
     mg_printf(d->c,
               "  <tr><td><a href=\"%.*s%s\">%s%s</a></td>"
-              "<td name=%lu>%s</td><td name=" MG_INT64_FMT ">%s</td></tr>\n",
+              "<td name=%lu>%s</td><td name=%lld>%s</td></tr>\n",
               n, path, slash, name, slash, (unsigned long) t, mod,
               flags & MG_FS_DIR ? (int64_t) -1 : (int64_t) size, sz);
   }
 }
 
 static void listdir(struct mg_connection *c, struct mg_http_message *hm,
-                    struct mg_http_serve_opts *opts, char *dir) {
-  static const char *sort_js_code =
+                    const struct mg_http_serve_opts *opts, char *dir) {
+  const char *sort_js_code =
       "<script>function srt(tb, sc, so, d) {"
       "var tr = Array.prototype.slice.call(tb.rows, 0),"
       "tr = tr.sort(function (a, b) { var c1 = a.cells[sc], c2 = b.cells[sc],"
@@ -1367,7 +1984,7 @@ static void listdir(struct mg_connection *c, struct mg_http_message *hm,
       "return so * (t1 < 0 && t2 >= 0 ? -1 : t2 < 0 && t1 >= 0 ? 1 : "
       "n1 ? parseInt(n2) - parseInt(n1) : "
       "c1.textContent.trim().localeCompare(c2.textContent.trim())); });";
-  static const char *sort_js_code2 =
+  const char *sort_js_code2 =
       "for (var i = 0; i < tr.length; i++) tb.appendChild(tr[i]); "
       "if (!d) window.location.hash = ('sc=' + sc + '&so=' + so); "
       "};"
@@ -1407,88 +2024,65 @@ static void listdir(struct mg_connection *c, struct mg_http_message *hm,
             "<tbody id=\"tb\">\n",
             (int) uri.len, uri.ptr, sort_js_code, sort_js_code2, (int) uri.len,
             uri.ptr);
+  mg_printf(c, "%s",
+            "  <tr><td><a href=\"..\">..</a></td>"
+            "<td name=-1></td><td name=-1>[DIR]</td></tr>\n");
 
-  fs->list(dir, printdirentry, &d);
+  fs->ls(dir, printdirentry, &d);
   mg_printf(c,
             "</tbody><tfoot><tr><td colspan=\"3\"><hr></td></tr></tfoot>"
             "</table><address>Mongoose v.%s</address></body></html>\n",
             MG_VERSION);
-  n = (size_t) snprintf(tmp, sizeof(tmp), "%lu",
-                        (unsigned long) (c->send.len - off));
+  n = mg_snprintf(tmp, sizeof(tmp), "%lu", (unsigned long) (c->send.len - off));
   if (n > sizeof(tmp)) n = 0;
-  memcpy(c->send.buf + off - 10, tmp, n);  // Set content length
-}
-
-static void remove_double_dots(char *s) {
-  char *p = s;
-  while (*s != '\0') {
-    *p++ = *s++;
-    if (s[-1] == '/' || s[-1] == '\\') {
-      while (s[0] != '\0') {
-        if (s[0] == '/' || s[0] == '\\') {
-          s++;
-        } else if (s[0] == '.' && s[1] == '.' &&
-                   (s[2] == '/' || s[2] == '\\')) {
-          s += 2;
-        } else {
-          break;
-        }
-      }
-    }
-  }
-  *p = '\0';
+  memcpy(c->send.buf + off - 12, tmp, n);  // Set content length
 }
 
-// Resolve requested file into `path` and return its fs->stat() result
+// Resolve requested file into `path` and return its fs->st() result
 static int uri_to_path2(struct mg_connection *c, struct mg_http_message *hm,
                         struct mg_fs *fs, struct mg_str url, struct mg_str dir,
                         char *path, size_t path_size) {
-  int flags = 0, tmp;
+  int flags, tmp;
   // Append URI to the root_dir, and sanitize it
-  size_t n = (size_t) snprintf(path, path_size, "%.*s", (int) dir.len, dir.ptr);
+  size_t n = mg_snprintf(path, path_size, "%.*s", (int) dir.len, dir.ptr);
   if (n > path_size) n = path_size;
   path[path_size - 1] = '\0';
-  if ((fs->stat(path, NULL, NULL) & MG_FS_DIR) == 0) {
-    mg_http_reply(c, 400, "", "Invalid web root [%.*s]\n", (int) dir.len,
-                  dir.ptr);
-  } else {
-    if (n + 2 < path_size) path[n++] = '/', path[n] = '\0';
-    mg_url_decode(hm->uri.ptr + url.len, hm->uri.len - url.len, path + n,
-                  path_size - n, 0);
-    path[path_size - 1] = '\0';  // Double-check
-    remove_double_dots(path);
-    n = strlen(path);
-    LOG(LL_VERBOSE_DEBUG,
-        ("%lu %.*s -> %s", c->id, (int) hm->uri.len, hm->uri.ptr, path));
-    while (n > 0 && path[n - 1] == '/') path[--n] = 0;  // Trim trailing slashes
-    flags = fs->stat(path, NULL, NULL);                 // Does it exist?
-    if (flags == 0) {
-      mg_http_reply(c, 404, "", "Not found\n");  // Does not exist, doh
-    } else if ((flags & MG_FS_DIR) && hm->uri.len > 0 &&
-               hm->uri.ptr[hm->uri.len - 1] != '/') {
-      mg_printf(c,
-                "HTTP/1.1 301 Moved\r\n"
-                "Location: %.*s/\r\n"
-                "Content-Length: 0\r\n"
-                "\r\n",
-                (int) hm->uri.len, hm->uri.ptr);
-      flags = 0;
-    } else if (flags & MG_FS_DIR) {
-      if (((snprintf(path + n, path_size - n, "/" MG_HTTP_INDEX) > 0 &&
-            (tmp = fs->stat(path, NULL, NULL)) != 0) ||
-           (snprintf(path + n, path_size - n, "/index.shtml") > 0 &&
-            (tmp = fs->stat(path, NULL, NULL)) != 0))) {
-        flags = tmp;
-      } else {
-        path[n] = '\0';  // Remove appended index file name
-      }
+  if (n + 2 < path_size) path[n++] = '/', path[n] = '\0';
+  mg_url_decode(hm->uri.ptr + url.len, hm->uri.len - url.len, path + n,
+                path_size - n, 0);
+  path[path_size - 1] = '\0';  // Double-check
+  mg_remove_double_dots(path);
+  n = strlen(path);
+  while (n > 1 && path[n - 1] == '/') path[--n] = 0;  // Trim trailing slashes
+  flags = mg_vcmp(&hm->uri, "/") == 0 ? MG_FS_DIR : fs->st(path, NULL, NULL);
+  MG_VERBOSE(("%lu %.*s -> %s %d", c->id, (int) hm->uri.len, hm->uri.ptr, path,
+              flags));
+  if (flags == 0) {
+    // Do nothing - let's caller decide
+  } else if ((flags & MG_FS_DIR) && hm->uri.len > 0 &&
+             hm->uri.ptr[hm->uri.len - 1] != '/') {
+    mg_printf(c,
+              "HTTP/1.1 301 Moved\r\n"
+              "Location: %.*s/\r\n"
+              "Content-Length: 0\r\n"
+              "\r\n",
+              (int) hm->uri.len, hm->uri.ptr);
+    flags = -1;
+  } else if (flags & MG_FS_DIR) {
+    if (((mg_snprintf(path + n, path_size - n, "/" MG_HTTP_INDEX) > 0 &&
+          (tmp = fs->st(path, NULL, NULL)) != 0) ||
+         (mg_snprintf(path + n, path_size - n, "/index.shtml") > 0 &&
+          (tmp = fs->st(path, NULL, NULL)) != 0))) {
+      flags = tmp;
+    } else {
+      path[n] = '\0';  // Remove appended index file name
     }
   }
   return flags;
 }
 
 static int uri_to_path(struct mg_connection *c, struct mg_http_message *hm,
-                       struct mg_http_serve_opts *opts, char *path,
+                       const struct mg_http_serve_opts *opts, char *path,
                        size_t path_size) {
   struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
   struct mg_str k, v, s = mg_str(opts->root_dir), u = {0, 0}, p = {0, 0};
@@ -1502,16 +2096,16 @@ static int uri_to_path(struct mg_connection *c, struct mg_http_message *hm,
 }
 
 void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm,
-                       struct mg_http_serve_opts *opts) {
-  char path[MG_PATH_MAX] = "";
+                       const struct mg_http_serve_opts *opts) {
+  char path[MG_PATH_MAX];
   const char *sp = opts->ssi_pattern;
   int flags = uri_to_path(c, hm, opts, path, sizeof(path));
-  if (flags == 0) return;
-  LOG(LL_VERBOSE_DEBUG,
-      ("%.*s %s %d", (int) hm->uri.len, hm->uri.ptr, path, flags));
-  if (flags & MG_FS_DIR) {
+  if (flags < 0) {
+    // Do nothing: the response has already been sent by uri_to_path()
+  } else if (flags & MG_FS_DIR) {
     listdir(c, hm, opts, path);
-  } else if (sp != NULL && mg_globmatch(sp, strlen(sp), path, strlen(path))) {
+  } else if (flags && sp != NULL &&
+             mg_globmatch(sp, strlen(sp), path, strlen(path))) {
     mg_http_serve_ssi(c, opts->root_dir, path);
   } else {
     mg_http_serve_file(c, hm, path, opts);
@@ -1548,14 +2142,14 @@ void mg_http_creds(struct mg_http_message *hm, char *user, size_t userlen,
     int n = mg_base64_decode(v->ptr + 6, (int) v->len - 6, buf);
     const char *p = (const char *) memchr(buf, ':', n > 0 ? (size_t) n : 0);
     if (p != NULL) {
-      snprintf(user, userlen, "%.*s", (int) (p - buf), buf);
-      snprintf(pass, passlen, "%.*s", n - (int) (p - buf) - 1, p + 1);
+      mg_snprintf(user, userlen, "%.*s", (int) (p - buf), buf);
+      mg_snprintf(pass, passlen, "%.*s", n - (int) (p - buf) - 1, p + 1);
     }
   } else if (v != NULL && v->len > 7 && memcmp(v->ptr, "Bearer ", 7) == 0) {
-    snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->ptr + 7);
+    mg_snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->ptr + 7);
   } else if ((v = mg_http_get_header(hm, "Cookie")) != NULL) {
     struct mg_str t = mg_http_get_header_var(*v, mg_str_n("access_token", 12));
-    if (t.len > 0) snprintf(pass, passlen, "%.*s", (int) t.len, t.ptr);
+    if (t.len > 0) mg_snprintf(pass, passlen, "%.*s", (int) t.len, t.ptr);
   } else {
     mg_http_get_var(&hm->query, "access_token", pass, passlen);
   }
@@ -1576,7 +2170,7 @@ struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v) {
       while (p < x &&
              (q ? p == b || *p != '"' : *p != ';' && *p != ' ' && *p != ','))
         p++;
-      // LOG(LL_INFO, ("[%.*s] [%.*s] [%.*s]", (int) s.len, s.ptr, (int) v.len,
+      // MG_INFO(("[%.*s] [%.*s] [%.*s]", (int) s.len, s.ptr, (int) v.len,
       // v.ptr, (int) (p - b), b));
       return stripquotes(mg_str_n(b, (size_t) (p - b + q)));
     }
@@ -1585,7 +2179,7 @@ struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v) {
 }
 
 bool mg_http_match_uri(const struct mg_http_message *hm, const char *glob) {
-  return mg_globmatch(glob, strlen(glob), hm->uri.ptr, hm->uri.len);
+  return mg_match(hm->uri, mg_str(glob), NULL);
 }
 
 static size_t get_chunk_length(const char *buf, size_t len, size_t *ll) {
@@ -1593,7 +2187,7 @@ static size_t get_chunk_length(const char *buf, size_t len, size_t *ll) {
   while (i < len && buf[i] != '\r' && i != '\n') i++;
   n = mg_unhexn((char *) buf, i);
   while (i < len && (buf[i] == '\r' || i == '\n')) i++;
-  // LOG(LL_INFO, ("len %zu i %zu n %zu ", len, i, n));
+  // MG_INFO(("len %zu i %zu n %zu ", len, i, n));
   if (ll != NULL) *ll = i + 1;
   if (i < len && i + n + 2 < len) return i + n + 3;
   return 0;
@@ -1601,14 +2195,14 @@ static size_t get_chunk_length(const char *buf, size_t len, size_t *ll) {
 
 // Walk through all chunks in the chunked body. For each chunk, fire
 // an MG_EV_HTTP_CHUNK event.
-static void walkchunks(struct mg_connection *c, struct mg_http_message *hm,
+static bool walkchunks(struct mg_connection *c, struct mg_http_message *hm,
                        size_t reqlen) {
   size_t off = 0, bl, ll;
   while (off + reqlen < c->recv.len) {
     char *buf = (char *) &c->recv.buf[reqlen];
     size_t memo = c->recv.len;
     size_t cl = get_chunk_length(&buf[off], memo - reqlen - off, &ll);
-    // LOG(LL_INFO, ("len %zu off %zu cl %zu ll %zu", len, off, cl, ll));
+    // MG_INFO(("len %zu off %zu cl %zu ll %zu", len, off, cl, ll));
     if (cl == 0) break;
     hm->chunk = mg_str_n(&buf[off + ll], cl < ll + 2 ? 0 : cl - ll - 2);
     mg_call(c, MG_EV_HTTP_CHUNK, hm);
@@ -1616,6 +2210,7 @@ static void walkchunks(struct mg_connection *c, struct mg_http_message *hm,
     if (memo == c->recv.len) off += cl;
     if (cl <= 5) {
       // Zero chunk - last one. Prepare body - cut off chunk lengths
+      if (memo != c->recv.len) return true;  // Tell caller to cleanup
       off = bl = 0;
       while (off + reqlen < c->recv.len) {
         char *buf2 = (char *) &c->recv.buf[reqlen];
@@ -1627,56 +2222,96 @@ static void walkchunks(struct mg_connection *c, struct mg_http_message *hm,
         off += cl2;
         if (cl2 <= 5) break;
       }
-      // LOG(LL_INFO, ("BL->%d del %d off %d", (int) bl, (int) del, (int) off));
+      // MG_INFO(("BL->%d del %d off %d", (int) bl, (int) del, (int) off));
       c->recv.len -= off - bl;
       // Set message length to indicate we've received
       // everything, to fire MG_EV_HTTP_MSG
       hm->message.len = bl + reqlen;
       hm->body.len = bl;
+      // If user was deleting chunks, send nothing
       break;
     }
   }
+  return false;
 }
 
 static bool mg_is_chunked(struct mg_http_message *hm) {
-  struct mg_str needle = mg_str_n("chunked", 7);
+  const char *needle = "chunked";
   struct mg_str *te = mg_http_get_header(hm, "Transfer-Encoding");
-  return te != NULL && mg_strstr(*te, needle) != NULL;
+  return te != NULL && mg_vcasecmp(te, needle) == 0;
 }
 
 void mg_http_delete_chunk(struct mg_connection *c, struct mg_http_message *hm) {
   struct mg_str ch = hm->chunk;
-  if (mg_is_chunked(hm)) {
-    ch.len += 4;  // \r\n before and after the chunk
-    ch.ptr -= 2;
+  const char *end = (char *) &c->recv.buf[c->recv.len], *ce;
+  bool chunked = mg_is_chunked(hm);
+  if (chunked) {
+    ch.len += 4, ch.ptr -= 2;  // \r\n before and after the chunk
     while (ch.ptr > hm->body.ptr && *ch.ptr != '\n') ch.ptr--, ch.len++;
   }
-  {
-    const char *end = &ch.ptr[ch.len];
-    size_t n = (size_t) (end - (char *) c->recv.buf);
-    if (c->recv.len > n) {
-      memmove((char *) ch.ptr, end, (size_t) (c->recv.len - n));
+  ce = &ch.ptr[ch.len];
+  if (ce < end) memmove((void *) ch.ptr, ce, (size_t) (end - ce));
+  c->recv.len -= ch.len;
+  if (c->pfn_data != NULL) c->pfn_data = (char *) c->pfn_data - ch.len;
+}
+
+long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm,
+                    struct mg_fs *fs, const char *path, size_t max_size) {
+  char buf[20] = "0";
+  long res = 0, offset;
+  mg_http_get_var(&hm->query, "offset", buf, sizeof(buf));
+  offset = strtol(buf, NULL, 0);
+  if (hm->body.len == 0) {
+    mg_http_reply(c, 200, "", "%ld", res);  // Nothing to write
+  } else {
+    struct mg_fd *fd;
+    size_t current_size = 0;
+    MG_DEBUG(("%s -> %d bytes @ %ld", path, (int) hm->body.len, offset));
+    if (offset == 0) fs->rm(path);  // If offset if 0, truncate file
+    fs->st(path, &current_size, NULL);
+    if (offset < 0) {
+      mg_http_reply(c, 400, "", "offset required");
+      res = -1;
+    } else if (offset > 0 && current_size != (size_t) offset) {
+      mg_http_reply(c, 400, "", "%s: offset mismatch", path);
+      res = -2;
+    } else if ((size_t) offset + hm->body.len > max_size) {
+      mg_http_reply(c, 400, "", "%s: over max size of %lu", path,
+                    (unsigned long) max_size);
+      res = -3;
+    } else if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) {
+      mg_http_reply(c, 400, "", "open(%s): %d", path, errno);
+      res = -4;
+    } else {
+      res = offset + (long) fs->wr(fd->fd, hm->body.ptr, hm->body.len);
+      mg_fs_close(fd);
+      mg_http_reply(c, 200, "", "%ld", res);
     }
-    // LOG(LL_INFO, ("DELETING CHUNK: %zu %zu %zu\n%.*s", c->recv.len, n,
-    // ch.len, (int) ch.len, ch.ptr));
   }
-  c->recv.len -= ch.len;
+  return res;
+}
+
+int mg_http_status(const struct mg_http_message *hm) {
+  return atoi(hm->uri.ptr);
 }
 
 static void http_cb(struct mg_connection *c, int ev, void *evd, void *fnd) {
   if (ev == MG_EV_READ || ev == MG_EV_CLOSE) {
     struct mg_http_message hm;
-    for (;;) {
+    while (c->recv.buf != NULL && c->recv.len > 0) {
       int n = mg_http_parse((char *) c->recv.buf, c->recv.len, &hm);
       bool is_chunked = n > 0 && mg_is_chunked(&hm);
       if (ev == MG_EV_CLOSE) {
         hm.message.len = c->recv.len;
         hm.body.len = hm.message.len - (size_t) (hm.body.ptr - hm.message.ptr);
-      } else if (is_chunked && n > 0) {
-        walkchunks(c, &hm, (size_t) n);
+      } else if (is_chunked && n > 0 && walkchunks(c, &hm, (size_t) n)) {
+        // walkchunks told us to cleanup the request
+        if (n > (int) c->recv.len) n = (int) c->recv.len;
+        mg_iobuf_del(&c->recv, 0, (size_t) n);
+        break;
       }
-      // LOG(LL_INFO,
-      //("---->%d %d\n%.*s", n, is_chunked, (int) c->recv.len, c->recv.buf));
+      // MG_INFO(("---->%d %d\n%.*s", n, is_chunked, (int) c->recv.len,
+      // c->recv.buf));
       if (n < 0 && ev == MG_EV_READ) {
         mg_error(c, "HTTP parse:\n%.*s", (int) c->recv.len, c->recv.buf);
         break;
@@ -1687,7 +2322,16 @@ static void http_cb(struct mg_connection *c, int ev, void *evd, void *fnd) {
         if (n > 0 && !is_chunked) {
           hm.chunk =
               mg_str_n((char *) &c->recv.buf[n], c->recv.len - (size_t) n);
+          // Store remaining body length in c->pfn_data
+          if (c->pfn_data == NULL)
+            c->pfn_data = (void *) (hm.message.len - (size_t) n);
           mg_call(c, MG_EV_HTTP_CHUNK, &hm);
+          if (c->pfn_data == NULL) {
+            hm.chunk.len = 0;                   // Last chunk!
+            mg_call(c, MG_EV_HTTP_CHUNK, &hm);  // Lest user know
+            memmove(c->recv.buf, c->recv.buf + n, c->recv.len - (size_t) n);
+            c->recv.len -= (size_t) n;
+          }
         }
         break;
       }
@@ -1717,7 +2361,6 @@ struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const char *url,
 
 
 
-#include <string.h>
 
 // Not using memset for zeroing memory, cause it can be dropped by compiler
 // See https://github.com/cesanta/mongoose/pull/1265
@@ -1740,21 +2383,22 @@ int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) {
     void *p = calloc(1, new_size);
     if (p != NULL) {
       size_t len = new_size < io->len ? new_size : io->len;
-      if (len > 0) memcpy(p, io->buf, len);
+      if (len > 0) memmove(p, io->buf, len);
       zeromem(io->buf, io->size);
       free(io->buf);
       io->buf = (unsigned char *) p;
       io->size = new_size;
     } else {
       ok = 0;
-      LOG(LL_ERROR,
-          ("%lu->%lu", (unsigned long) io->size, (unsigned long) new_size));
+      MG_ERROR(("%lld->%lld", (uint64_t) io->size, (uint64_t) new_size));
     }
   }
   return ok;
 }
 
 int mg_iobuf_init(struct mg_iobuf *io, size_t size) {
+  io->buf = NULL;
+  io->size = io->len = 0;
   return mg_iobuf_resize(io, size);
 }
 
@@ -1777,8 +2421,8 @@ size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void *buf,
 size_t mg_iobuf_del(struct mg_iobuf *io, size_t ofs, size_t len) {
   if (ofs > io->len) ofs = io->len;
   if (ofs + len > io->len) len = io->len - ofs;
-  memmove(io->buf + ofs, io->buf + ofs + len, io->len - ofs - len);
-  zeromem(io->buf + io->len - len, len);
+  if (io->buf) memmove(io->buf + ofs, io->buf + ofs + len, io->len - ofs - len);
+  if (io->buf) zeromem(io->buf + io->len - len, len);
   io->len -= len;
   return len;
 }
@@ -1787,35 +2431,302 @@ void mg_iobuf_free(struct mg_iobuf *io) {
   mg_iobuf_resize(io, 0);
 }
 
+#ifdef MG_ENABLE_LINES
+#line 1 "src/json.c"
+#endif
+
+
+
+static const char *escapeseq(int esc) {
+  return esc ? "\b\f\n\r\t\\\"" : "bfnrt\\\"";
+}
+
+static char json_esc(int c, int esc) {
+  const char *p, *esc1 = escapeseq(esc), *esc2 = escapeseq(!esc);
+  for (p = esc1; *p != '\0'; p++) {
+    if (*p == c) return esc2[p - esc1];
+  }
+  return 0;
+}
+
+static int mg_pass_string(const char *s, int len) {
+  int i;
+  for (i = 0; i < len; i++) {
+    if (s[i] == '\\' && i + 1 < len && json_esc(s[i + 1], 1)) {
+      i++;
+    } else if (s[i] == '\0') {
+      return MG_JSON_INVALID;
+    } else if (s[i] == '"') {
+      return i;
+    }
+  }
+  return MG_JSON_INVALID;
+}
+
+int mg_json_get(const char *s, int len, const char *path, int *toklen) {
+  enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting = S_VALUE;
+  unsigned char nesting[MG_JSON_MAX_DEPTH];
+  int i, j = 0, depth = 0;
+  int pos = 1;           // Current position in path
+  int ed = 0;            // Expected depth
+  int ci = -1, ei = -1;  // Current and expected index in array
+
+  if (path[0] != '$') return MG_JSON_INVALID;
+
+#if 0
+#define MG_DBGP(x)                                                             \
+  do {                                                                         \
+    printf("%c %.*s j=%d i=%d pos=%d depth=%d ed=%d ci=%d ei=%d\n", x, len, s, \
+           j, i, pos, depth, ed, ci, ei);                                      \
+  } while (0)
+#else
+#define MG_DBGP(x)
+#endif
+
+#define MG_CHECKRET(x)                                  \
+  do {                                                  \
+    MG_DBGP(x);                                         \
+    if (depth == ed && path[pos] == '\0' && ci == ei) { \
+      if (toklen) *toklen = i - j + 1;                  \
+      return j;                                         \
+    }                                                   \
+  } while (0)
+
+// In the ascii table, the distance between `[` and `]` is 2.
+// Ditto for `{` and `}`. Hence +2 in the code below.
+#define MG_EOO(x)                                            \
+  do {                                                       \
+    if (depth == ed && ci != ei) return MG_JSON_NOT_FOUND;   \
+    if (c != nesting[depth - 1] + 2) return MG_JSON_INVALID; \
+    depth--;                                                 \
+    MG_CHECKRET(x);                                          \
+  } while (0)
+
+  for (i = 0; i < len; i++) {
+    unsigned char c = ((unsigned char *) s)[i];
+    if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
+    MG_DBGP('-');
+    switch (expecting) {
+      case S_VALUE:
+        if (depth == ed) j = i;
+        if (c == '{') {
+          if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
+          if (depth == ed && path[pos] == '.') ed++, pos++;
+          nesting[depth++] = c;
+          expecting = S_KEY;
+          break;
+        } else if (c == '[') {
+          if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
+          if (depth == ed && path[pos] == '[' && ei == ci) {
+            ed++, pos++, ci = 0;
+            for (ei = 0; path[pos] != ']' && path[pos] != '\0'; pos++) {
+              ei *= 10;
+              ei += path[pos] - '0';
+            }
+            if (path[pos] != 0) pos++;
+          }
+          nesting[depth++] = c;
+          break;
+        } else if (c == ']' && depth > 0) {  // Empty array
+          MG_EOO(']');
+        } else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4) == 0) {
+          i += 3;
+        } else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4) == 0) {
+          i += 3;
+        } else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5) == 0) {
+          i += 4;
+        } else if (c == '-' || ((c >= '0' && c <= '9'))) {
+          int numlen = 0;
+          mg_atod(&s[i], len - i, &numlen);
+          i += numlen - 1;
+        } else if (c == '"') {
+          int n = mg_pass_string(&s[i + 1], len - i - 1);
+          if (n < 0) return n;
+          i += n + 1;
+        } else {
+          return MG_JSON_INVALID;
+        }
+        MG_CHECKRET('V');
+        if (depth == ed && ei >= 0) ci++;
+        expecting = S_COMMA_OR_EOO;
+        break;
+
+      case S_KEY:
+        if (c == '"') {
+          int n = mg_pass_string(&s[i + 1], len - i - 1);
+          if (n < 0) return n;
+          // printf("K[%.*s] %d %d\n", n, &s[i + 1], depth, ed);
+          if (depth == ed && path[pos - 1] == '.' &&
+              memcmp(&s[i + 1], &path[pos], (size_t) n) == 0) {
+            pos += n;
+          }
+          i += n + 1;
+          expecting = S_COLON;
+        } else if (c == '}') {  // Empty object
+          MG_EOO('}');
+          expecting = S_COMMA_OR_EOO;
+        } else {
+          return MG_JSON_INVALID;
+        }
+        break;
+
+      case S_COLON:
+        if (c == ':') {
+          expecting = S_VALUE;
+        } else {
+          return MG_JSON_INVALID;
+        }
+        break;
+
+      case S_COMMA_OR_EOO:
+        if (depth <= 0) {
+          return MG_JSON_INVALID;
+        } else if (c == ',') {
+          expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE;
+          // MG_CHECKRET('C');
+        } else if (c == ']' || c == '}') {
+          MG_EOO('O');
+          if (depth == ed && ei >= 0) ci++;
+        } else {
+          return MG_JSON_INVALID;
+        }
+        break;
+    }
+    MG_DBGP('E');
+  }
+  return MG_JSON_NOT_FOUND;
+}
+
+bool mg_json_get_num(struct mg_str json, const char *path, double *v) {
+  int n, toklen, found = 0;
+  if ((n = mg_json_get(json.ptr, (int) json.len, path, &toklen)) >= 0 &&
+      (json.ptr[n] == '-' || (json.ptr[n] >= '0' && json.ptr[n] <= '9'))) {
+    if (v != NULL) *v = mg_atod(json.ptr + n, toklen, NULL);
+    found = 1;
+  }
+  return found;
+}
+
+bool mg_json_get_bool(struct mg_str json, const char *path, bool *v) {
+  int n, toklen, found = 0;
+  if ((n = mg_json_get(json.ptr, (int) json.len, path, &toklen)) >= 0 &&
+      (json.ptr[n] == 't' || json.ptr[n] == 'f')) {
+    if (v != NULL) *v = json.ptr[n] == 't';
+    found = 1;
+  }
+  return found;
+}
+
+static bool json_unescape(const char *s, size_t len, char *to, size_t n) {
+  size_t i, j;
+  for (i = 0, j = 0; i < len && j < n; i++, j++) {
+    if (s[i] == '\\' && i + 5 < len && s[i + 1] == 'u') {
+      //  \uXXXX escape. We could process a simple one-byte chars
+      // \u00xx from the ASCII range. More complex chars would require
+      // dragging in a UTF8 library, which is too much for us
+      if (s[i + 2] != '0' || s[i + 3] != '0') return false;  // Give up
+      ((unsigned char *) to)[j] = (unsigned char) mg_unhexn(s + i + 4, 2);
+
+      i += 5;
+    } else if (s[i] == '\\' && i + 1 < len) {
+      char c = json_esc(s[i + 1], 0);
+      if (c == 0) return false;
+      to[j] = c;
+      i++;
+    } else {
+      to[j] = s[i];
+    }
+  }
+  if (j >= n) return false;
+  if (n > 0) to[j] = '\0';
+  return true;
+}
+
+char *mg_json_get_str(struct mg_str json, const char *path) {
+  int n, toklen;
+  char *result = NULL;
+  if ((n = mg_json_get(json.ptr, (int) json.len, path, &toklen)) >= 0 &&
+      json.ptr[n] == '"') {
+    if ((result = (char *) calloc(1, (size_t) toklen)) != NULL &&
+        !json_unescape(json.ptr + n + 1, (size_t) (toklen - 2), result,
+                       (size_t) toklen)) {
+      free(result);
+      result = NULL;
+    }
+  }
+  return result;
+}
+
+char *mg_json_get_b64(struct mg_str json, const char *path, int *len) {
+  int n, toklen;
+  char *result = NULL;
+  if ((n = mg_json_get(json.ptr, (int) json.len, path, &toklen)) >= 0 &&
+      json.ptr[n] == '"' && toklen > 1 &&
+      (result = (char *) calloc(1, (size_t) toklen)) != NULL) {
+    int k = mg_base64_decode(json.ptr + n + 1, toklen - 2, result);
+    if (len != NULL) *len = k;
+  }
+  return result;
+}
+
+char *mg_json_get_hex(struct mg_str json, const char *path, int *len) {
+  int n, toklen;
+  char *result = NULL;
+  if ((n = mg_json_get(json.ptr, (int) json.len, path, &toklen)) >= 0 &&
+      json.ptr[n] == '"' && toklen > 1 &&
+      (result = (char *) calloc(1, (size_t) toklen / 2)) != NULL) {
+    mg_unhex(json.ptr + n + 1, (size_t) (toklen - 2), (uint8_t *) result);
+    result[(toklen - 2) / 2] = '\0';
+    if (len != NULL) *len = (toklen - 2) / 2;
+  }
+  return result;
+}
+
+long mg_json_get_long(struct mg_str json, const char *path, long dflt) {
+  double dv;
+  long result = dflt;
+  if (mg_json_get_num(json, path, &dv)) result = (long) dv;
+  return result;
+}
+
 #ifdef MG_ENABLE_LINES
 #line 1 "src/log.c"
 #endif
 
 
 
-#if MG_ENABLE_LOG
-static void mg_log_stdout(const void *buf, size_t len, void *userdata) {
-  (void) userdata;
-  fwrite(buf, 1, len, stdout);
+static void default_logger(unsigned char c) {
+  putchar(c);
+  (void) c;
 }
 
 static const char *s_spec = "2";
-static void (*s_fn)(const void *, size_t, void *) = mg_log_stdout;
-static void *s_fn_param = NULL;
+static void (*s_log_func)(unsigned char) = default_logger;
+
+void mg_log_set_fn(void (*fn)(unsigned char)) {
+  s_log_func = fn;
+}
+
+static void logc(unsigned char c) {
+  s_log_func(c);
+}
+
+static void logs(const char *buf, size_t len) {
+  size_t i;
+  for (i = 0; i < len; i++) logc(((unsigned char *) buf)[i]);
+}
 
 void mg_log_set(const char *spec) {
-  LOG(LL_DEBUG, ("Setting log level to %s", spec));
+  MG_DEBUG(("Setting log level to %s", spec));
   s_spec = spec;
 }
 
 bool mg_log_prefix(int level, const char *file, int line, const char *fname) {
   // static unsigned long seq;
-  int max = LL_INFO;
+  int max = MG_LL_INFO;
   struct mg_str k, v, s = mg_str(s_spec);
   const char *p = strrchr(file, '/');
 
-  if (s_fn == NULL) return false;
-
   if (p == NULL) p = strrchr(file, '\\');
   p = p == NULL ? file : p + 1;
 
@@ -1825,17 +2736,12 @@ bool mg_log_prefix(int level, const char *file, int line, const char *fname) {
   }
 
   if (level <= max) {
-    char timebuf[21], buf[50] = "";
-    time_t t = time(NULL);
-    struct tm tmp, *tm = gmtime_r(&t, &tmp);
-    int n;
-    (void) tmp;
-    strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", tm);
-    n = snprintf(buf, sizeof(buf), "%s %d %s:%d:%s", timebuf, level, p, line,
-                 fname);
-    if (n < 0 || n > (int) sizeof(buf) - 2) n = sizeof(buf) - 2;
-    while (n < (int) sizeof(buf) - 1) buf[n++] = ' ';
-    s_fn(buf, sizeof(buf) - 1, s_fn_param);
+    char buf[41];
+    size_t n = mg_snprintf(buf, sizeof(buf), "%llx %d %s:%d:%s", mg_millis(),
+                           level, p, line, fname);
+    if (n > sizeof(buf) - 2) n = sizeof(buf) - 2;
+    while (n < sizeof(buf)) buf[n++] = ' ';
+    logs(buf, n - 1);
     return true;
   } else {
     return false;
@@ -1845,25 +2751,44 @@ bool mg_log_prefix(int level, const char *file, int line, const char *fname) {
 void mg_log(const char *fmt, ...) {
   char mem[256], *buf = mem;
   va_list ap;
-  int len = 0;
+  size_t len;
   va_start(ap, fmt);
   len = mg_vasprintf(&buf, sizeof(mem), fmt, ap);
   va_end(ap);
-  s_fn(buf, len > 0 ? (size_t) len : 0, s_fn_param);
-  s_fn("\n", 1, s_fn_param);
+  logs(buf, len);
+  logc((unsigned char) '\n');
   if (buf != mem) free(buf);
 }
 
-void mg_log_set_callback(void (*fn)(const void *, size_t, void *), void *fnd) {
-  s_fn = fn;
-  s_fn_param = fnd;
+static unsigned char nibble(unsigned c) {
+  return (unsigned char) (c < 10 ? c + '0' : c + 'W');
+}
+
+#define ISPRINT(x) ((x) >= ' ' && (x) <= '~')
+void mg_hexdump(const void *buf, size_t len) {
+  const unsigned char *p = (const unsigned char *) buf;
+  unsigned char ascii[16], alen = 0;
+  size_t i;
+  for (i = 0; i < len; i++) {
+    if ((i % 16) == 0) {
+      // Print buffered ascii chars
+      if (i > 0) logs("  ", 2), logs((char *) ascii, 16), logc('\n'), alen = 0;
+      // Print hex address, then \t
+      logc(nibble((i >> 12) & 15)), logc(nibble((i >> 8) & 15)),
+          logc(nibble((i >> 4) & 15)), logc('0'), logs("   ", 3);
+    }
+    logc(nibble(p[i] >> 4)), logc(nibble(p[i] & 15));  // Two nibbles, e.g. c5
+    logc(' ');                                         // Space after hex number
+    ascii[alen++] = ISPRINT(p[i]) ? p[i] : '.';        // Add to the ascii buf
+  }
+  while (alen < 16) logs("   ", 3), ascii[alen++] = ' ';
+  logs("  ", 2), logs((char *) ascii, 16), logc('\n');
 }
-#endif
 
 #ifdef MG_ENABLE_LINES
 #line 1 "src/md5.c"
 #endif
-#include <string.h>
+
 
 
 #if defined(MG_ENABLE_MD5) && MG_ENABLE_MD5
@@ -1881,7 +2806,7 @@ static void mg_byte_reverse(unsigned char *buf, unsigned longs) {
 /* Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN */
 #if BYTE_ORDER == BIG_ENDIAN
   do {
-    uint32_t t = (uint32_t)((unsigned) buf[3] << 8 | buf[2]) << 16 |
+    uint32_t t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
                  ((unsigned) buf[1] << 8 | buf[0]);
     *(uint32_t *) buf = t;
     buf += 4;
@@ -2079,8 +3004,6 @@ void mg_md5_final(mg_md5_ctx *ctx, unsigned char digest[16]) {
 #define MQTT_HAS_PASSWORD 0x40
 #define MQTT_HAS_USER_NAME 0x80
 
-enum { MQTT_OK, MQTT_INCOMPLETE, MQTT_MALFORMED };
-
 void mg_mqtt_send_header(struct mg_connection *c, uint8_t cmd, uint8_t flags,
                          uint32_t len) {
   uint8_t buf[1 + sizeof(len)], *vlen = &buf[1];
@@ -2098,44 +3021,51 @@ static void mg_send_u16(struct mg_connection *c, uint16_t value) {
   mg_send(c, &value, sizeof(value));
 }
 
-void mg_mqtt_login(struct mg_connection *c, struct mg_mqtt_opts *opts) {
-  char rnd[9], client_id[16];
+void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts *opts) {
+  char rnd[10], client_id[21], zero = 0;
   struct mg_str cid = opts->client_id;
   uint32_t total_len = 7 + 1 + 2 + 2;
-  uint8_t connflag = (uint8_t) ((opts->qos & 3) << 1);
+  uint8_t hdr[8] = {0, 4, 'M', 'Q', 'T', 'T', opts->version, 0};
 
   if (cid.len == 0) {
     mg_random(rnd, sizeof(rnd));
-    mg_base64_encode((unsigned char *) rnd, sizeof(rnd), client_id);
+    mg_hex(rnd, sizeof(rnd), client_id);
     client_id[sizeof(client_id) - 1] = '\0';
     cid = mg_str(client_id);
   }
 
+  if (hdr[6] == 0) hdr[6] = 4;  // If version is not set, use 4 (3.1.1)
+  c->is_mqtt5 = hdr[6] == 5;    // Set version 5 flag
+  hdr[7] = (uint8_t) ((opts->will_qos & 3) << 3);  // Connection flags
   if (opts->user.len > 0) {
     total_len += 2 + (uint32_t) opts->user.len;
-    connflag |= MQTT_HAS_USER_NAME;
+    hdr[7] |= MQTT_HAS_USER_NAME;
   }
   if (opts->pass.len > 0) {
     total_len += 2 + (uint32_t) opts->pass.len;
-    connflag |= MQTT_HAS_PASSWORD;
+    hdr[7] |= MQTT_HAS_PASSWORD;
   }
   if (opts->will_topic.len > 0 && opts->will_message.len > 0) {
     total_len +=
         4 + (uint32_t) opts->will_topic.len + (uint32_t) opts->will_message.len;
-    connflag |= MQTT_HAS_WILL;
+    hdr[7] |= MQTT_HAS_WILL;
   }
-  if (opts->clean || cid.len == 0) connflag |= MQTT_CLEAN_SESSION;
-  if (opts->will_retain) connflag |= MQTT_WILL_RETAIN;
+  if (opts->clean || cid.len == 0) hdr[7] |= MQTT_CLEAN_SESSION;
+  if (opts->will_retain) hdr[7] |= MQTT_WILL_RETAIN;
   total_len += (uint32_t) cid.len;
+  if (c->is_mqtt5) total_len += 1U + (hdr[7] & MQTT_HAS_WILL ? 1U : 0);
 
   mg_mqtt_send_header(c, MQTT_CMD_CONNECT, 0, total_len);
-  mg_send(c, "\00\04MQTT\04", 7);
-  mg_send(c, &connflag, sizeof(connflag));
+  mg_send(c, hdr, sizeof(hdr));
   // keepalive == 0 means "do not disconnect us!"
   mg_send_u16(c, mg_htons((uint16_t) opts->keepalive));
+
+  if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero));  // V5 properties
   mg_send_u16(c, mg_htons((uint16_t) cid.len));
   mg_send(c, cid.ptr, cid.len);
-  if (connflag & MQTT_HAS_WILL) {
+
+  if (hdr[7] & MQTT_HAS_WILL) {
+    if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero));  // will props
     mg_send_u16(c, mg_htons((uint16_t) opts->will_topic.len));
     mg_send(c, opts->will_topic.ptr, opts->will_topic.len);
     mg_send_u16(c, mg_htons((uint16_t) opts->will_message.len));
@@ -2151,37 +3081,39 @@ void mg_mqtt_login(struct mg_connection *c, struct mg_mqtt_opts *opts) {
   }
 }
 
-void mg_mqtt_pub(struct mg_connection *c, struct mg_str *topic,
-                 struct mg_str *data, int qos, bool retain) {
-  uint8_t flags = (uint8_t) (((qos & 3) << 1) | (retain ? 1 : 0));
-  uint32_t total_len = 2 + (uint32_t) topic->len + (uint32_t) data->len;
-  LOG(LL_DEBUG, ("%lu [%.*s] -> [%.*s]", c->id, (int) topic->len,
-                 (char *) topic->ptr, (int) data->len, (char *) data->ptr));
-  if (qos > 0) total_len += 2;
-  mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, total_len);
-  mg_send_u16(c, mg_htons((uint16_t) topic->len));
-  mg_send(c, topic->ptr, topic->len);
+void mg_mqtt_pub(struct mg_connection *c, struct mg_str topic,
+                 struct mg_str data, int qos, bool retain) {
+  uint8_t flags = (uint8_t) (((qos & 3) << 1) | (retain ? 1 : 0)), zero = 0;
+  uint32_t len = 2 + (uint32_t) topic.len + (uint32_t) data.len;
+  MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) topic.len, (char *) topic.ptr,
+            (int) data.len, (char *) data.ptr));
+  if (qos > 0) len += 2;
+  if (c->is_mqtt5) len++;
+  mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, len);
+  mg_send_u16(c, mg_htons((uint16_t) topic.len));
+  mg_send(c, topic.ptr, topic.len);
   if (qos > 0) {
-    static uint16_t s_id;
-    if (++s_id == 0) s_id++;
-    mg_send_u16(c, mg_htons(s_id));
-  }
-  mg_send(c, data->ptr, data->len);
-}
-
-void mg_mqtt_sub(struct mg_connection *c, struct mg_str *topic, int qos) {
-  static uint16_t s_id;
-  uint8_t qos_ = qos & 3;
-  uint32_t total_len = 2 + (uint32_t) topic->len + 2 + 1;
-  mg_mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, 2, total_len);
-  if (++s_id == 0) ++s_id;
-  mg_send_u16(c, mg_htons(s_id));
-  mg_send_u16(c, mg_htons((uint16_t) topic->len));
-  mg_send(c, topic->ptr, topic->len);
+    if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
+    mg_send_u16(c, mg_htons(c->mgr->mqtt_id));
+  }
+  if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero));
+  mg_send(c, data.ptr, data.len);
+}
+
+void mg_mqtt_sub(struct mg_connection *c, struct mg_str topic, int qos) {
+  uint8_t qos_ = qos & 3, zero = 0;
+  uint32_t len = 2 + (uint32_t) topic.len + 2 + 1 + (c->is_mqtt5 ? 1 : 0);
+  mg_mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, 2, len);
+  if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id;
+  mg_send_u16(c, mg_htons(c->mgr->mqtt_id));
+  if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero));
+  mg_send_u16(c, mg_htons((uint16_t) topic.len));
+  mg_send(c, topic.ptr, topic.len);
   mg_send(c, &qos_, sizeof(qos_));
 }
 
-int mg_mqtt_parse(const uint8_t *buf, size_t len, struct mg_mqtt_message *m) {
+int mg_mqtt_parse(const uint8_t *buf, size_t len, uint8_t version,
+                  struct mg_mqtt_message *m) {
   uint8_t lc = 0, *p, *end;
   uint32_t n = 0, len_len = 0;
 
@@ -2201,7 +3133,7 @@ int mg_mqtt_parse(const uint8_t *buf, size_t len, struct mg_mqtt_message *m) {
     if (len_len >= 4) return MQTT_MALFORMED;
   }
   end = p + n;
-  if (lc & 0x80 || end > buf + len) return MQTT_INCOMPLETE;
+  if ((lc & 0x80) || (end > buf + len)) return MQTT_INCOMPLETE;
   m->dgram.len = (size_t) (end - buf);
 
   switch (m->cmd) {
@@ -2213,16 +3145,14 @@ int mg_mqtt_parse(const uint8_t *buf, size_t len, struct mg_mqtt_message *m) {
     case MQTT_CMD_PUBREC:
     case MQTT_CMD_PUBREL:
     case MQTT_CMD_PUBCOMP:
+    case MQTT_CMD_SUBSCRIBE:
     case MQTT_CMD_SUBACK:
-      if (p + 2 > end) return MQTT_MALFORMED;
-      m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
-      break;
-    case MQTT_CMD_SUBSCRIBE: {
+    case MQTT_CMD_UNSUBSCRIBE:
+    case MQTT_CMD_UNSUBACK:
       if (p + 2 > end) return MQTT_MALFORMED;
       m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
       p += 2;
       break;
-    }
     case MQTT_CMD_PUBLISH: {
       if (p + 2 > end) return MQTT_MALFORMED;
       m->topic.len = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
@@ -2234,6 +3164,8 @@ int mg_mqtt_parse(const uint8_t *buf, size_t len, struct mg_mqtt_message *m) {
         m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
         p += 2;
       }
+      if (p >= end) return MQTT_MALFORMED;
+      if (version == 5) p += 1 + p[0];  // Skip options
       if (p > end) return MQTT_MALFORMED;
       m->data.ptr = (char *) p;
       m->data.len = (size_t) (end - p);
@@ -2275,29 +3207,34 @@ static void mqtt_cb(struct mg_connection *c, int ev, void *ev_data,
                     void *fn_data) {
   if (ev == MG_EV_READ) {
     for (;;) {
+      uint8_t version = c->is_mqtt5 ? 5 : 4;
       struct mg_mqtt_message mm;
-      int rc = mg_mqtt_parse(c->recv.buf, c->recv.len, &mm);
+      int rc = mg_mqtt_parse(c->recv.buf, c->recv.len, version, &mm);
       if (rc == MQTT_MALFORMED) {
-        LOG(LL_ERROR, ("%lu MQTT malformed message", c->id));
+        MG_ERROR(("%lu MQTT malformed message", c->id));
         c->is_closing = 1;
         break;
       } else if (rc == MQTT_OK) {
-        LOG(LL_VERBOSE_DEBUG,
-            ("%p MQTT CMD %d len %d [%.*s]", c->fd, mm.cmd, (int) mm.dgram.len,
-             (int) mm.data.len, mm.data.ptr));
+        MG_VERBOSE(("%p MQTT CMD %d len %d [%.*s]", c->fd, mm.cmd,
+                    (int) mm.dgram.len, (int) mm.data.len, mm.data.ptr));
         switch (mm.cmd) {
           case MQTT_CMD_CONNACK:
             mg_call(c, MG_EV_MQTT_OPEN, &mm.ack);
             if (mm.ack == 0) {
-              LOG(LL_INFO, ("%lu Connected", c->id));
+              MG_DEBUG(("%lu Connected", c->id));
             } else {
-              LOG(LL_ERROR, ("%lu MQTT auth failed, code %d", c->id, mm.ack));
+              MG_ERROR(("%lu MQTT auth failed, code %d", c->id, mm.ack));
               c->is_closing = 1;
             }
             break;
           case MQTT_CMD_PUBLISH: {
-            LOG(LL_DEBUG, ("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len,
-                           mm.topic.ptr, (int) mm.data.len, mm.data.ptr));
+            MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len,
+                      mm.topic.ptr, (int) mm.data.len, mm.data.ptr));
+            if (mm.qos > 0) {
+              uint16_t id = mg_htons(mm.id);
+              mg_mqtt_send_header(c, MQTT_CMD_PUBACK, 0, sizeof(id));
+              mg_send(c, &id, sizeof(id));
+            }
             mg_call(c, MG_EV_MQTT_MSG, &mm);
             break;
           }
@@ -2326,7 +3263,7 @@ void mg_mqtt_disconnect(struct mg_connection *nc) {
 }
 
 struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const char *url,
-                                      struct mg_mqtt_opts *opts,
+                                      const struct mg_mqtt_opts *opts,
                                       mg_event_handler_t fn, void *fn_data) {
   struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
   if (c != NULL) {
@@ -2352,16 +3289,19 @@ struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url,
 
 
 
-int mg_vprintf(struct mg_connection *c, const char *fmt, va_list ap) {
-  char mem[256], *buf = mem;
-  int len = mg_vasprintf(&buf, sizeof(mem), fmt, ap);
-  len = mg_send(c, buf, len > 0 ? (size_t) len : 0);
-  if (buf != mem) free(buf);
-  return len;
+
+
+
+size_t mg_vprintf(struct mg_connection *c, const char *fmt, va_list ap) {
+  size_t old = c->send.len;
+  va_list tmp;
+  va_copy(tmp, ap);
+  mg_vrprintf(mg_putchar_iobuf, &c->send, fmt, &tmp);
+  return c->send.len - old;
 }
 
-int mg_printf(struct mg_connection *c, const char *fmt, ...) {
-  int len = 0;
+size_t mg_printf(struct mg_connection *c, const char *fmt, ...) {
+  size_t len = 0;
   va_list ap;
   va_start(ap, fmt);
   len = mg_vprintf(c, fmt, ap);
@@ -2369,25 +3309,25 @@ int mg_printf(struct mg_connection *c, const char *fmt, ...) {
   return len;
 }
 
-char *mg_straddr(struct mg_connection *c, char *buf, size_t len) {
-  char tmp[100];
-  const char *fmt = c->peer.is_ip6 ? "[%s]:%d" : "%s:%d";
-  mg_ntoa(&c->peer, tmp, sizeof(tmp));
-  snprintf(buf, len, fmt, tmp, (int) mg_ntohs(c->peer.port));
+char *mg_straddr(struct mg_addr *a, char *buf, size_t len) {
+  char tmp[30];
+  const char *fmt = a->is_ip6 ? "[%s]:%d" : "%s:%d";
+  mg_ntoa(a, tmp, sizeof(tmp));
+  mg_snprintf(buf, len, fmt, tmp, (int) mg_ntohs(a->port));
   return buf;
 }
 
 char *mg_ntoa(const struct mg_addr *addr, char *buf, size_t len) {
   if (addr->is_ip6) {
     uint16_t *p = (uint16_t *) addr->ip6;
-    snprintf(buf, len, "%x:%x:%x:%x:%x:%x:%x:%x", mg_htons(p[0]),
-             mg_htons(p[1]), mg_htons(p[2]), mg_htons(p[3]), mg_htons(p[4]),
-             mg_htons(p[5]), mg_htons(p[6]), mg_htons(p[7]));
+    mg_snprintf(buf, len, "%x:%x:%x:%x:%x:%x:%x:%x", mg_htons(p[0]),
+                mg_htons(p[1]), mg_htons(p[2]), mg_htons(p[3]), mg_htons(p[4]),
+                mg_htons(p[5]), mg_htons(p[6]), mg_htons(p[7]));
   } else {
     uint8_t p[4];
     memcpy(p, &addr->ip, sizeof(p));
-    snprintf(buf, len, "%d.%d.%d.%d", (int) p[0], (int) p[1], (int) p[2],
-             (int) p[3]);
+    mg_snprintf(buf, len, "%d.%d.%d.%d", (int) p[0], (int) p[1], (int) p[2],
+                (int) p[3]);
   }
   return buf;
 }
@@ -2452,8 +3392,7 @@ static bool mg_aton6(struct mg_str str, struct mg_addr *addr) {
         (str.ptr[i] >= 'A' && str.ptr[i] <= 'F')) {
       unsigned long val;
       if (i > j + 3) return false;
-      // LOG(LL_DEBUG, ("%zu %zu [%.*s]", i, j, (int) (i - j + 1),
-      // &str.ptr[j]));
+      // MG_DEBUG(("%zu %zu [%.*s]", i, j, (int) (i - j + 1), &str.ptr[j]));
       val = mg_unhexn(&str.ptr[j], i - j + 1);
       addr->ip6[n] = (uint8_t) ((val >> 8) & 255);
       addr->ip6[n + 1] = (uint8_t) (val & 255);
@@ -2481,52 +3420,144 @@ static bool mg_aton6(struct mg_str str, struct mg_addr *addr) {
 }
 
 bool mg_aton(struct mg_str str, struct mg_addr *addr) {
-  // LOG(LL_INFO, ("[%.*s]", (int) str.len, str.ptr));
+  // MG_INFO(("[%.*s]", (int) str.len, str.ptr));
   return mg_atone(str, addr) || mg_atonl(str, addr) || mg_aton4(str, addr) ||
          mg_aton6(str, addr);
 }
 
-void mg_mgr_free(struct mg_mgr *mgr) {
-  struct mg_connection *c;
-  for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1;
-  mg_mgr_poll(mgr, 0);
-#if MG_ARCH == MG_ARCH_FREERTOS_TCP
-  FreeRTOS_DeleteSocketSet(mgr->ss);
-#endif
-  LOG(LL_INFO, ("All connections closed"));
-}
-
-void mg_mgr_init(struct mg_mgr *mgr) {
-  memset(mgr, 0, sizeof(*mgr));
-#if defined(_WIN32) && MG_ENABLE_WINSOCK
-  // clang-format off
-  { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); }
-  // clang-format on
-#elif MG_ARCH == MG_ARCH_FREERTOS_TCP
-  mgr->ss = FreeRTOS_CreateSocketSet();
-#elif defined(__unix) || defined(__unix__) || defined(__APPLE__)
-  // Ignore SIGPIPE signal, so if client cancels the request, it
-  // won't kill the whole process.
-  signal(SIGPIPE, SIG_IGN);
-#endif
-  mgr->dnstimeout = 3000;
-  mgr->dns4.url = "udp://8.8.8.8:53";
-  mgr->dns6.url = "udp://[2001:4860:4860::8888]:53";
+struct mg_connection *mg_alloc_conn(struct mg_mgr *mgr) {
+  struct mg_connection *c =
+      (struct mg_connection *) calloc(1, sizeof(*c) + mgr->extraconnsize);
+  if (c != NULL) {
+    c->mgr = mgr;
+    c->id = ++mgr->nextid;
+  }
+  return c;
 }
 
-#ifdef MG_ENABLE_LINES
-#line 1 "src/sha1.c"
-#endif
-/* Copyright(c) By Steve Reid <steve@edmweb.com> */
-/* 100% Public Domain */
+void mg_close_conn(struct mg_connection *c) {
+  mg_resolve_cancel(c);  // Close any pending DNS query
+  LIST_DELETE(struct mg_connection, &c->mgr->conns, c);
+  if (c == c->mgr->dns4.c) c->mgr->dns4.c = NULL;
+  if (c == c->mgr->dns6.c) c->mgr->dns6.c = NULL;
+  // Order of operations is important. `MG_EV_CLOSE` event must be fired
+  // before we deallocate received data, see #1331
+  mg_call(c, MG_EV_CLOSE, NULL);
+  MG_DEBUG(("%lu closed", c->id));
 
-#include <string.h>
+  mg_tls_free(c);
+  mg_iobuf_free(&c->recv);
+  mg_iobuf_free(&c->send);
+  memset(c, 0, sizeof(*c));
+  free(c);
+}
 
-/*
- * clang with std=-c99 uses __LITTLE_ENDIAN, by default
- * while for ex, RTOS gcc - LITTLE_ENDIAN, by default
- * it depends on __USE_BSD, but let's have everything
- */
+struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *url,
+                                 mg_event_handler_t fn, void *fn_data) {
+  struct mg_connection *c = NULL;
+  if (url == NULL || url[0] == '\0') {
+    MG_ERROR(("null url"));
+  } else if ((c = mg_alloc_conn(mgr)) == NULL) {
+    MG_ERROR(("OOM"));
+  } else {
+    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
+    c->is_udp = (strncmp(url, "udp:", 4) == 0);
+    c->fn = fn;
+    c->is_client = true;
+    c->fd = (void *) (size_t) -1;  // Set to invalid socket
+    c->fn_data = fn_data;
+    MG_DEBUG(("%lu -1 %s", c->id, url));
+    mg_call(c, MG_EV_OPEN, NULL);
+    mg_resolve(c, url);
+  }
+  return c;
+}
+
+struct mg_connection *mg_listen(struct mg_mgr *mgr, const char *url,
+                                mg_event_handler_t fn, void *fn_data) {
+  struct mg_connection *c = NULL;
+  if ((c = mg_alloc_conn(mgr)) == NULL) {
+    MG_ERROR(("OOM %s", url));
+  } else if (!mg_open_listener(c, url)) {
+    MG_ERROR(("Failed: %s, errno %d", url, errno));
+    free(c);
+    c = NULL;
+  } else {
+    c->is_listening = 1;
+    c->is_udp = strncmp(url, "udp:", 4) == 0;
+    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
+    c->fn = fn;
+    c->fn_data = fn_data;
+    mg_call(c, MG_EV_OPEN, NULL);
+    MG_DEBUG(("%lu %p %s", c->id, c->fd, url));
+  }
+  return c;
+}
+
+struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd,
+                                mg_event_handler_t fn, void *fn_data) {
+  struct mg_connection *c = mg_alloc_conn(mgr);
+  if (c != NULL) {
+    c->fd = (void *) (size_t) fd;
+    c->fn = fn;
+    c->fn_data = fn_data;
+    mg_call(c, MG_EV_OPEN, NULL);
+    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
+  }
+  return c;
+}
+
+struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds,
+                              unsigned flags, void (*fn)(void *), void *arg) {
+  struct mg_timer *t = (struct mg_timer *) calloc(1, sizeof(*t));
+  mg_timer_init(&mgr->timers, t, milliseconds, flags, fn, arg);
+  return t;
+}
+
+void mg_mgr_free(struct mg_mgr *mgr) {
+  struct mg_connection *c;
+  struct mg_timer *tmp, *t = mgr->timers;
+  while (t != NULL) tmp = t->next, free(t), t = tmp;
+  mgr->timers = NULL;  // Important. Next call to poll won't touch timers
+  for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1;
+  mg_mgr_poll(mgr, 0);
+#if MG_ARCH == MG_ARCH_FREERTOS_TCP
+  FreeRTOS_DeleteSocketSet(mgr->ss);
+#endif
+  MG_DEBUG(("All connections closed"));
+}
+
+void mg_mgr_init(struct mg_mgr *mgr) {
+  memset(mgr, 0, sizeof(*mgr));
+#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
+  // clang-format off
+  { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); }
+  // clang-format on
+#elif MG_ARCH == MG_ARCH_FREERTOS_TCP
+  mgr->ss = FreeRTOS_CreateSocketSet();
+#elif defined(__unix) || defined(__unix__) || defined(__APPLE__)
+  // Ignore SIGPIPE signal, so if client cancels the request, it
+  // won't kill the whole process.
+  signal(SIGPIPE, SIG_IGN);
+#endif
+  mgr->dnstimeout = 3000;
+  mgr->dns4.url = "udp://8.8.8.8:53";
+  mgr->dns6.url = "udp://[2001:4860:4860::8888]:53";
+}
+
+#ifdef MG_ENABLE_LINES
+#line 1 "src/sha1.c"
+#endif
+/* Copyright(c) By Steve Reid <steve@edmweb.com> */
+/* 100% Public Domain */
+
+
+
+/*
+ * clang with std=-c99 uses __LITTLE_ENDIAN, by default
+ * while for ex, RTOS gcc - LITTLE_ENDIAN, by default
+ * it depends on __USE_BSD, but let's have everything
+ */
 #if !defined(BYTE_ORDER) && defined(__BYTE_ORDER)
 #define BYTE_ORDER __BYTE_ORDER
 #ifndef LITTLE_ENDIAN
@@ -2581,7 +3612,8 @@ static uint32_t blk0(union char64long16 *block, int i) {
   z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
   w = rol(w, 30);
 
-static void mg_sha1_transform(uint32_t state[5], const unsigned char buffer[64]) {
+static void mg_sha1_transform(uint32_t state[5],
+                              const unsigned char buffer[64]) {
   uint32_t a, b, c, d, e;
   union char64long16 block[1];
 
@@ -2702,7 +3734,7 @@ void mg_sha1_update(mg_sha1_ctx *context, const unsigned char *data,
 
   j = context->count[0];
   if ((context->count[0] += (uint32_t) len << 3) < j) context->count[1]++;
-  context->count[1] += (uint32_t)(len >> 29);
+  context->count[1] += (uint32_t) (len >> 29);
   j = (j >> 3) & 63;
   if ((j + len) > 63) {
     memcpy(&context->buffer[j], data, (i = 64 - j));
@@ -2740,41 +3772,6 @@ void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *context) {
   memset(&finalcount, '\0', sizeof(finalcount));
 }
 
-void mg_hmac_sha1(const unsigned char *key, size_t keylen,
-                  const unsigned char *data, size_t datalen,
-                  unsigned char out[20]) {
-  mg_sha1_ctx ctx;
-  unsigned char buf1[64], buf2[64], tmp_key[20], i;
-
-  if (keylen > sizeof(buf1)) {
-    mg_sha1_init(&ctx);
-    mg_sha1_update(&ctx, key, keylen);
-    mg_sha1_final(tmp_key, &ctx);
-    key = tmp_key;
-    keylen = sizeof(tmp_key);
-  }
-
-  memset(buf1, 0, sizeof(buf1));
-  memset(buf2, 0, sizeof(buf2));
-  memcpy(buf1, key, keylen);
-  memcpy(buf2, key, keylen);
-
-  for (i = 0; i < sizeof(buf1); i++) {
-    buf1[i] ^= 0x36;
-    buf2[i] ^= 0x5c;
-  }
-
-  mg_sha1_init(&ctx);
-  mg_sha1_update(&ctx, buf1, sizeof(buf1));
-  mg_sha1_update(&ctx, data, datalen);
-  mg_sha1_final(out, &ctx);
-
-  mg_sha1_init(&ctx);
-  mg_sha1_update(&ctx, buf2, sizeof(buf2));
-  mg_sha1_update(&ctx, out, 20);
-  mg_sha1_final(out, &ctx);
-}
-
 #ifdef MG_ENABLE_LINES
 #line 1 "src/sntp.c"
 #endif
@@ -2784,57 +3781,54 @@ void mg_hmac_sha1(const unsigned char *key, size_t keylen,
 
 
 
-#define SNTP_INTERVAL_SEC (3600)
+#define SNTP_INTERVAL_SEC 3600
 #define SNTP_TIME_OFFSET 2208988800UL
 
-static unsigned long s_sntmp_next;
-
-int mg_sntp_parse(const unsigned char *buf, size_t len, struct timeval *tv) {
-  int mode = len > 0 ? buf[0] & 7 : 0, res = -1;
+int64_t mg_sntp_parse(const unsigned char *buf, size_t len) {
+  int64_t res = -1;
+  int mode = len > 0 ? buf[0] & 7 : 0;
+  int version = len > 0 ? (buf[0] >> 3) & 7 : 0;
   if (len < 48) {
-    LOG(LL_ERROR, ("%s", "corrupt packet"));
-  } else if ((buf[0] & 0x38) >> 3 != 4) {
-    LOG(LL_ERROR, ("%s", "wrong version"));
+    MG_ERROR(("%s", "corrupt packet"));
   } else if (mode != 4 && mode != 5) {
-    LOG(LL_ERROR, ("%s", "not a server reply"));
+    MG_ERROR(("%s", "not a server reply"));
   } else if (buf[1] == 0) {
-    LOG(LL_ERROR, ("%s", "server sent a kiss of death"));
-  } else {
+    MG_ERROR(("%s", "server sent a kiss of death"));
+  } else if (version == 4 || version == 3) {
     uint32_t *data = (uint32_t *) &buf[40];
-    tv->tv_sec = (time_t) (mg_ntohl(data[0]) - SNTP_TIME_OFFSET);
-    tv->tv_usec = (suseconds_t) mg_ntohl(data[1]);
-    s_sntmp_next = (unsigned long) (tv->tv_sec + SNTP_INTERVAL_SEC);
-    res = 0;
+    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);
+  } else {
+    MG_ERROR(("unexpected version: %d", version));
   }
   return res;
 }
 
 static void sntp_cb(struct mg_connection *c, int ev, void *evd, void *fnd) {
   if (ev == MG_EV_READ) {
-    struct timeval tv = {0, 0};
-    if (mg_sntp_parse(c->recv.buf, c->recv.len, &tv) == 0) {
-      mg_call(c, MG_EV_SNTP_TIME, &tv);
-      LOG(LL_DEBUG, ("%u.%u, next at %lu", (unsigned) tv.tv_sec,
-                     (unsigned) tv.tv_usec, s_sntmp_next));
+    int64_t milliseconds = mg_sntp_parse(c->recv.buf, c->recv.len);
+    if (milliseconds > 0) {
+      mg_call(c, MG_EV_SNTP_TIME, (uint64_t *) &milliseconds);
+      MG_VERBOSE(("%u.%u", (unsigned) (milliseconds / 1000),
+                  (unsigned) (milliseconds % 1000)));
     }
-    c->recv.len = 0;  // Clear receive buffer
+    mg_iobuf_del(&c->recv, 0, c->recv.len);  // Free receive buffer
   } else if (ev == MG_EV_CONNECT) {
-    mg_sntp_send(c, (unsigned long) time(NULL));
+    mg_sntp_request(c);
   } else if (ev == MG_EV_CLOSE) {
   }
   (void) fnd;
   (void) evd;
 }
 
-void mg_sntp_send(struct mg_connection *c, unsigned long utc) {
+void mg_sntp_request(struct mg_connection *c) {
   if (c->is_resolving) {
-    LOG(LL_ERROR, ("%lu wait until resolved", c->id));
-  } else if (utc > s_sntmp_next) {
+    MG_ERROR(("%lu wait until resolved", c->id));
+  } else {
     uint8_t buf[48] = {0};
-    s_sntmp_next = utc + SNTP_INTERVAL_SEC;
     buf[0] = (0 << 6) | (4 << 3) | 3;
     mg_send(c, buf, sizeof(buf));
-    LOG(LL_DEBUG, ("%lu ct %lu, next at %lu", c->id, utc, s_sntmp_next));
   }
 }
 
@@ -2860,8 +3854,14 @@ struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const char *url,
 
 
 #if MG_ENABLE_SOCKET
-#if defined(_WIN32) && MG_ENABLE_WINSOCK
+#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
+typedef unsigned long nfds_t;
 #define MG_SOCK_ERRNO WSAGetLastError()
+#if defined(_MSC_VER)
+#pragma comment(lib, "ws2_32.lib")
+#define alloca(a) _alloca(a)
+#endif
+#define poll(a, b, c) WSAPoll((a), (b), (c))
 #ifndef SO_EXCLUSIVEADDRUSE
 #define SO_EXCLUSIVEADDRUSE ((int) (~SO_REUSEADDR))
 #endif
@@ -2869,6 +3869,9 @@ struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const char *url,
 #define MG_SOCK_ERRNO errno
 typedef Socket_t SOCKET;
 #define INVALID_SOCKET FREERTOS_INVALID_SOCKET
+#elif MG_ARCH == MG_ARCH_TIRTOS
+#define MG_SOCK_ERRNO errno
+#define closesocket(x) close(x)
 #else
 #define MG_SOCK_ERRNO errno
 #ifndef closesocket
@@ -2926,140 +3929,195 @@ static void tomgaddr(union usa *usa, struct mg_addr *a, bool is_ip6) {
 #endif
 }
 
-static bool mg_sock_would_block(void) {
+bool mg_sock_would_block(void);
+bool mg_sock_would_block(void) {
   int err = MG_SOCK_ERRNO;
   return err == EINPROGRESS || err == EWOULDBLOCK
 #ifndef WINCE
          || err == EAGAIN || err == EINTR
 #endif
-#if defined(_WIN32) && MG_ENABLE_WINSOCK
+#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
          || err == WSAEINTR || err == WSAEWOULDBLOCK
 #endif
       ;
 }
 
-static struct mg_connection *alloc_conn(struct mg_mgr *mgr, bool is_client,
-                                        SOCKET fd) {
-  struct mg_connection *c = (struct mg_connection *) calloc(1, sizeof(*c));
-  if (c != NULL) {
-    c->is_client = is_client;
-    c->fd = S2PTR(fd);
-    c->mgr = mgr;
-    c->id = ++mgr->nextid;
+bool mg_sock_conn_reset(void);
+bool mg_sock_conn_reset(void) {
+  int err = MG_SOCK_ERRNO;
+#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
+  return err == WSAECONNRESET;
+#else
+  return err == EPIPE || err == ECONNRESET;
+#endif
+}
+
+static void setlocaddr(SOCKET fd, struct mg_addr *addr) {
+  union usa usa;
+  socklen_t n = sizeof(usa);
+  if (getsockname(fd, &usa.sa, &n) == 0) {
+    tomgaddr(&usa, addr, n != sizeof(usa.sin));
+  }
+}
+
+static void iolog(struct mg_connection *c, char *buf, long n, bool r) {
+  if (n == 0) {
+    // Do nothing
+  } else if (n < 0) {
+    c->is_closing = 1;  // Termination. Don't call mg_error(): #1529
+  } else if (n > 0) {
+    if (c->is_hexdumping) {
+      union usa usa;
+      char t1[50], t2[50];
+      socklen_t slen = sizeof(usa.sin);
+      struct mg_addr a;
+      memset(&usa, 0, sizeof(usa));
+      memset(&a, 0, sizeof(a));
+      if (getsockname(FD(c), &usa.sa, &slen) < 0) (void) 0;  // Ignore result
+      tomgaddr(&usa, &a, c->rem.is_ip6);
+      MG_INFO(("\n-- %lu %s %s %s %s %ld", c->id,
+               mg_straddr(&a, t1, sizeof(t1)), r ? "<-" : "->",
+               mg_straddr(&c->rem, t2, sizeof(t2)), c->label, n));
+
+      mg_hexdump(buf, (size_t) n);
+    }
+    if (r) {
+      struct mg_str evd = mg_str_n(buf, (size_t) n);
+      c->recv.len += (size_t) n;
+      mg_call(c, MG_EV_READ, &evd);
+    } else {
+      mg_iobuf_del(&c->send, 0, (size_t) n);
+      // if (c->send.len == 0) mg_iobuf_resize(&c->send, 0);
+      mg_call(c, MG_EV_WRITE, &n);
+    }
   }
-  return c;
 }
 
 static long mg_sock_send(struct mg_connection *c, const void *buf, size_t len) {
   long n;
-#if !defined(__APPLE__)
-  // See #1338, #1382. On Windows, UDP send() can fail despite connected.
-  // Use sendto() instead. But not on Mac - we'll get EISCONN
   if (c->is_udp) {
     union usa usa;
-    socklen_t slen = tousa(&c->peer, &usa);
+    socklen_t slen = tousa(&c->rem, &usa);
     n = sendto(FD(c), (char *) buf, len, 0, &usa.sa, slen);
-  } else
-#endif
-  {
+    if (n > 0) setlocaddr(FD(c), &c->loc);
+  } else {
     n = send(FD(c), (char *) buf, len, MSG_NONBLOCKING);
+#if MG_ARCH == MG_ARCH_RTX
+    if (n == BSD_EWOULDBLOCK) return 0;
+#endif
   }
   return n == 0 ? -1 : n < 0 && mg_sock_would_block() ? 0 : n;
 }
 
 bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
-  return c->is_udp ? mg_sock_send(c, buf, len) > 0
-                   : mg_iobuf_add(&c->send, c->send.len, buf, len, MG_IO_SIZE);
+  if (c->is_udp) {
+    long n = mg_sock_send(c, buf, len);
+    MG_DEBUG(("%lu %p %d:%d %ld err %d", c->id, c->fd, (int) c->send.len,
+              (int) c->recv.len, n, MG_SOCK_ERRNO));
+    iolog(c, (char *) buf, n, false);
+    return n > 0;
+  } else {
+    return mg_iobuf_add(&c->send, c->send.len, buf, len, MG_IO_SIZE);
+  }
 }
 
 static void mg_set_non_blocking_mode(SOCKET fd) {
-#if defined(_WIN32) && MG_ENABLE_WINSOCK
+#if defined(MG_CUSTOM_NONBLOCK)
+  MG_CUSTOM_NONBLOCK(fd);
+#elif MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
+  unsigned long on = 1;
+  ioctlsocket(fd, FIONBIO, &on);
+#elif MG_ARCH == MG_ARCH_RTX
   unsigned long on = 1;
   ioctlsocket(fd, FIONBIO, &on);
 #elif MG_ARCH == MG_ARCH_FREERTOS_TCP
   const BaseType_t off = 0;
-  setsockopt(fd, 0, FREERTOS_SO_RCVTIMEO, &off, sizeof(off));
-  setsockopt(fd, 0, FREERTOS_SO_SNDTIMEO, &off, sizeof(off));
-#elif MG_ARCH == MG_ARCH_FREERTOS_LWIP
+  if (setsockopt(fd, 0, FREERTOS_SO_RCVTIMEO, &off, sizeof(off)) != 0) (void) 0;
+  if (setsockopt(fd, 0, FREERTOS_SO_SNDTIMEO, &off, sizeof(off)) != 0) (void) 0;
+#elif MG_ARCH == MG_ARCH_FREERTOS_LWIP || MG_ARCH == MG_ARCH_RTX_LWIP
   lwip_fcntl(fd, F_SETFL, O_NONBLOCK);
 #elif MG_ARCH == MG_ARCH_AZURERTOS
   fcntl(fd, F_SETFL, O_NONBLOCK);
+#elif MG_ARCH == MG_ARCH_TIRTOS
+  int val = 0;
+  setsockopt(fd, 0, SO_BLOCKING, &val, sizeof(val));
+  int status = 0;
+  int res = SockStatus(fd, FDSTATUS_SEND, &status);
+  if (res == 0 && status > 0) {
+    val = status / 2;
+    int val_size = sizeof(val);
+    res = SockSet(fd, SOL_SOCKET, SO_SNDLOWAT, &val, val_size);
+  }
 #else
-  fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);  // Set non-blocking mode
-  fcntl(fd, F_SETFD, FD_CLOEXEC);                       // Set close-on-exec
+  fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);  // Non-blocking mode
+  fcntl(fd, F_SETFD, FD_CLOEXEC);                          // Set close-on-exec
 #endif
 }
 
-static SOCKET mg_open_listener(const char *url, struct mg_addr *addr) {
+bool mg_open_listener(struct mg_connection *c, const char *url) {
   SOCKET fd = INVALID_SOCKET;
-  int s_err = 0;  // Memoized socket error, in case closesocket() overrides it
-  memset(addr, 0, sizeof(*addr));
-  addr->port = mg_htons(mg_url_port(url));
-  if (!mg_aton(mg_url_host(url), addr)) {
-    LOG(LL_ERROR, ("invalid listening URL: %s", url));
+  bool success = false;
+  c->loc.port = mg_htons(mg_url_port(url));
+  if (!mg_aton(mg_url_host(url), &c->loc)) {
+    MG_ERROR(("invalid listening URL: %s", url));
   } else {
     union usa usa;
-    socklen_t slen = tousa(addr, &usa);
-    int on = 1, af = addr->is_ip6 ? AF_INET6 : AF_INET;
+    socklen_t slen = tousa(&c->loc, &usa);
+    int on = 1, af = c->loc.is_ip6 ? AF_INET6 : AF_INET;
     int type = strncmp(url, "udp:", 4) == 0 ? SOCK_DGRAM : SOCK_STREAM;
     int proto = type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP;
     (void) on;
 
-    if ((fd = socket(af, type, proto)) != INVALID_SOCKET &&
-#if (!defined(_WIN32) || !defined(SO_EXCLUSIVEADDRUSE)) && \
-    (!defined(LWIP_SOCKET) || (defined(LWIP_SOCKET) && SO_REUSE == 1))
-        // 1. SO_RESUSEADDR is not enabled on Windows because the semantics of
-        //    SO_REUSEADDR on UNIX and Windows is different. On Windows,
-        //    SO_REUSEADDR allows to bind a socket to a port without error even
-        //    if the port is already open by another program. This is not the
-        //    behavior SO_REUSEADDR was designed for, and leads to hard-to-track
-        //    failure scenarios. Therefore, SO_REUSEADDR was disabled on Windows
-        //    unless SO_EXCLUSIVEADDRUSE is supported and set on a socket.
-        // 2. In case of LWIP, SO_REUSEADDR should be explicitly enabled, by
-        // defining
-        //    SO_REUSE (in lwipopts.h), otherwise the code below will compile
-        //    but won't work! (setsockopt will return EINVAL)
-        !setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) &&
+    if ((fd = socket(af, type, proto)) == INVALID_SOCKET) {
+      MG_ERROR(("socket: %d", MG_SOCK_ERRNO));
+#if ((MG_ARCH == MG_ARCH_WIN32) || (MG_ARCH == MG_ARCH_UNIX) || \
+     (defined(LWIP_SOCKET) && SO_REUSE == 1))
+    } else if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on,
+                          sizeof(on)) != 0) {
+      // 1. SO_RESUSEADDR is not enabled on Windows because the semantics of
+      //    SO_REUSEADDR on UNIX and Windows is different. On Windows,
+      //    SO_REUSEADDR allows to bind a socket to a port without error even
+      //    if the port is already open by another program. This is not the
+      //    behavior SO_REUSEADDR was designed for, and leads to hard-to-track
+      //    failure scenarios. Therefore, SO_REUSEADDR was disabled on Windows
+      //    unless SO_EXCLUSIVEADDRUSE is supported and set on a socket.
+      // 2. In case of LWIP, SO_REUSEADDR should be explicitly enabled, by
+      // defining
+      //    SO_REUSE (in lwipopts.h), otherwise the code below will compile
+      //    but won't work! (setsockopt will return EINVAL)
+      MG_ERROR(("reuseaddr: %d", MG_SOCK_ERRNO));
 #endif
-#if defined(_WIN32) && defined(SO_EXCLUSIVEADDRUSE) && !defined(WINCE)
-        // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE"
-        //! setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (char *) &on, sizeof(on))
-        //! &&
-        !setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char *) &on,
-                    sizeof(on)) &&
+#if MG_ARCH == MG_ARCH_WIN32 && !defined(SO_EXCLUSIVEADDRUSE) && !defined(WINCE)
+    } else if (setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char *) &on,
+                          sizeof(on)) != 0) {
+      // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE"
+      MG_ERROR(("exclusiveaddruse: %d", MG_SOCK_ERRNO));
 #endif
-        bind(fd, &usa.sa, slen) == 0 &&
-        // NOTE(lsm): FreeRTOS uses backlog value as a connection limit
-        (type == SOCK_DGRAM || listen(fd, MG_SOCK_LISTEN_BACKLOG_SIZE) == 0)) {
+    } else if (bind(fd, &usa.sa, slen) != 0) {
+      MG_ERROR(("bind: %d", MG_SOCK_ERRNO));
+    } else if ((type == SOCK_STREAM &&
+                listen(fd, MG_SOCK_LISTEN_BACKLOG_SIZE) != 0)) {
+      // NOTE(lsm): FreeRTOS uses backlog value as a connection limit
       // In case port was set to 0, get the real port number
-      if (getsockname(fd, &usa.sa, &slen) == 0) {
-        addr->port = usa.sin.sin_port;
-#if MG_ENABLE_IPV6
-        if (addr->is_ip6) addr->port = usa.sin6.sin6_port;
-#endif
-      }
+      MG_ERROR(("listen: %d", MG_SOCK_ERRNO));
+    } else {
+      setlocaddr(fd, &c->loc);
       mg_set_non_blocking_mode(fd);
-    } else if (fd != INVALID_SOCKET) {
-      s_err = MG_SOCK_ERRNO;
-      closesocket(fd);
-      fd = INVALID_SOCKET;
+      c->fd = S2PTR(fd);
+      success = true;
     }
   }
-  if (fd == INVALID_SOCKET) {
-    if (s_err == 0) s_err = MG_SOCK_ERRNO;
-    LOG(LL_ERROR, ("Failed to listen on %s, errno %d", url, s_err));
-  }
-
-  return fd;
+  if (success == false && fd != INVALID_SOCKET) closesocket(fd);
+  return success;
 }
 
 static long mg_sock_recv(struct mg_connection *c, void *buf, size_t len) {
   long n = 0;
   if (c->is_udp) {
     union usa usa;
-    socklen_t slen = tousa(&c->peer, &usa);
+    socklen_t slen = tousa(&c->rem, &usa);
     n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa, &slen);
-    if (n > 0) tomgaddr(&usa, &c->peer, slen != sizeof(usa.sin));
+    if (n > 0) tomgaddr(&usa, &c->rem, slen != sizeof(usa.sin));
   } else {
     n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING);
   }
@@ -3068,139 +4126,96 @@ static long mg_sock_recv(struct mg_connection *c, void *buf, size_t len) {
 
 // NOTE(lsm): do only one iteration of reads, cause some systems
 // (e.g. FreeRTOS stack) return 0 instead of -1/EWOULDBLOCK when no data
-static long read_conn(struct mg_connection *c) {
+static void read_conn(struct mg_connection *c) {
   long n = -1;
-  if (c->recv.len >= MG_MAX_RECV_BUF_SIZE) {
+  if (c->recv.len >= MG_MAX_RECV_SIZE) {
     mg_error(c, "max_recv_buf_size reached");
-  } else if (c->recv.size - c->recv.len < MG_IO_SIZE &&
+  } else if (c->recv.size <= c->recv.len &&
              !mg_iobuf_resize(&c->recv, c->recv.size + MG_IO_SIZE)) {
     mg_error(c, "oom");
   } else {
     char *buf = (char *) &c->recv.buf[c->recv.len];
     size_t len = c->recv.size - c->recv.len;
     n = c->is_tls ? mg_tls_recv(c, buf, len) : mg_sock_recv(c, buf, len);
-    LOG(n > 0 ? LL_VERBOSE_DEBUG : LL_DEBUG,
-        ("%-3lu %d%d%d%d%d%d%d%d%d%d%d%d%d%d %7ld %ld/%ld err %d", c->id,
-         c->is_listening, c->is_client, c->is_accepted, c->is_resolving,
-         c->is_connecting, c->is_tls, c->is_tls_hs, c->is_udp, c->is_websocket,
-         c->is_hexdumping, c->is_draining, c->is_closing, c->is_readable,
-         c->is_writable, (long) c->recv.len, n, (long) len, MG_SOCK_ERRNO));
-    if (n == 0) {
-      // Do nothing
-    } else if (n < 0) {
-      c->is_closing = 1;  // Error, or normal termination
-    } else if (n > 0) {
-      struct mg_str evd = mg_str_n(buf, (size_t) n);
-      if (c->is_hexdumping) {
-        char *s = mg_hexdump(buf, (size_t) n);
-        LOG(LL_INFO, ("\n-- %lu %s %s %ld\n%s", c->id, c->label, "<-", n, s));
-        free(s);
-      }
-      c->recv.len += (size_t) n;
-      mg_call(c, MG_EV_READ, &evd);
-    }
+    MG_DEBUG(("%lu %p %d:%d %ld err %d", c->id, c->fd, (int) c->send.len,
+              (int) c->recv.len, n, MG_SOCK_ERRNO));
+    iolog(c, buf, n, true);
   }
-  return n;
 }
 
 static void write_conn(struct mg_connection *c) {
   char *buf = (char *) c->send.buf;
   size_t len = c->send.len;
   long n = c->is_tls ? mg_tls_send(c, buf, len) : mg_sock_send(c, buf, len);
-
-  LOG(n > 0 ? LL_VERBOSE_DEBUG : LL_DEBUG,
-      ("%-3lu %d%d%d%d%d%d%d%d%d%d%d%d%d%d %7ld %ld err %d", c->id,
-       c->is_listening, c->is_client, c->is_accepted, c->is_resolving,
-       c->is_connecting, c->is_tls, c->is_tls_hs, c->is_udp, c->is_websocket,
-       c->is_hexdumping, c->is_draining, c->is_closing, c->is_readable,
-       c->is_writable, (long) c->send.len, n, MG_SOCK_ERRNO));
-
-  if (n == 0) {
-    // Do nothing
-  } else if (n < 0) {
-    c->is_closing = 1;  // Error, or normal termination
-  } else if (n > 0) {
-    // Hexdump before deleting
-    if (c->is_hexdumping) {
-      char *s = mg_hexdump(buf, (size_t) n);
-      LOG(LL_INFO, ("\n-- %lu %s %s %ld\n%s", c->id, c->label, "<-", n, s));
-      free(s);
-    }
-    mg_iobuf_del(&c->send, 0, (size_t) n);
-    if (c->send.len == 0) mg_iobuf_resize(&c->send, 0);
-    mg_call(c, MG_EV_WRITE, &n);
-    // if (c->send.len == 0) mg_iobuf_resize(&c->send, 0);
-  }
+  MG_DEBUG(("%lu %p %d:%d %ld err %d", c->id, c->fd, (int) c->send.len,
+            (int) c->recv.len, n, MG_SOCK_ERRNO));
+  iolog(c, buf, n, false);
 }
 
 static void close_conn(struct mg_connection *c) {
-  mg_resolve_cancel(c);  // Close any pending DNS query
-  LIST_DELETE(struct mg_connection, &c->mgr->conns, c);
-  if (c == c->mgr->dns4.c) c->mgr->dns4.c = NULL;
-  if (c == c->mgr->dns6.c) c->mgr->dns6.c = NULL;
-  // Order of operations is important. `MG_EV_CLOSE` event must be fired
-  // before we deallocate received data, see #1331
-  mg_call(c, MG_EV_CLOSE, NULL);
-  LOG(LL_DEBUG, ("%lu closed", c->id));
   if (FD(c) != INVALID_SOCKET) {
     closesocket(FD(c));
 #if MG_ARCH == MG_ARCH_FREERTOS_TCP
     FreeRTOS_FD_CLR(c->fd, c->mgr->ss, eSELECT_ALL);
 #endif
-    c->fd = S2PTR(INVALID_SOCKET);
+    c->fd = NULL;
+  }
+  mg_close_conn(c);
+}
+
+static void connect_conn(struct mg_connection *c) {
+  union usa usa;
+  socklen_t n = sizeof(usa);
+  // Use getpeername() to test whether we have connected
+  if (getpeername(FD(c), &usa.sa, &n) == 0) {
+    c->is_connecting = 0;
+    mg_call(c, MG_EV_CONNECT, NULL);
+    if (c->is_tls_hs) mg_tls_handshake(c);
+  } else {
+    mg_error(c, "socket error");
   }
-  mg_tls_free(c);
-  mg_iobuf_free(&c->recv);
-  mg_iobuf_free(&c->send);
-  memset(c, 0, sizeof(*c));
-  free(c);
 }
 
 static void setsockopts(struct mg_connection *c) {
-#if MG_ARCH == MG_ARCH_FREERTOS_TCP || MG_ARCH == MG_ARCH_AZURERTOS
+#if MG_ARCH == MG_ARCH_FREERTOS_TCP || MG_ARCH == MG_ARCH_AZURERTOS || \
+    MG_ARCH == MG_ARCH_TIRTOS
   (void) c;
 #else
   int on = 1;
 #if !defined(SOL_TCP)
 #define SOL_TCP IPPROTO_TCP
 #endif
-  setsockopt(FD(c), SOL_TCP, TCP_NODELAY, (char *) &on, sizeof(on));
-#if defined(TCP_QUICKACK)
-  setsockopt(FD(c), SOL_TCP, TCP_QUICKACK, (char *) &on, sizeof(on));
-#endif
-  setsockopt(FD(c), SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof(on));
-#if (defined(ESP32) && ESP32) || (defined(ESP8266) && ESP8266) || \
-    defined(__linux__)
-  int idle = 60;
-  setsockopt(FD(c), IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle));
-#endif
-#if !defined(_WIN32) && !defined(__QNX__)
-  {
-    int cnt = 3, intvl = 20;
-    setsockopt(FD(c), IPPROTO_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt));
-    setsockopt(FD(c), IPPROTO_TCP, TCP_KEEPINTVL, &intvl, sizeof(intvl));
-  }
-#endif
+  if (setsockopt(FD(c), SOL_TCP, TCP_NODELAY, (char *) &on, sizeof(on)) != 0)
+    (void) 0;
+  if (setsockopt(FD(c), SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof(on)) !=
+      0)
+    (void) 0;
 #endif
 }
 
 void mg_connect_resolved(struct mg_connection *c) {
-  char buf[40];
+  // char buf[40];
   int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM;
-  int rc, af = c->peer.is_ip6 ? AF_INET6 : AF_INET;
-  mg_straddr(c, buf, sizeof(buf));
+  int rc, af = c->rem.is_ip6 ? AF_INET6 : AF_INET;
+  // mg_straddr(&c->rem, buf, sizeof(buf));
   c->fd = S2PTR(socket(af, type, 0));
+  c->is_resolving = 0;
   if (FD(c) == INVALID_SOCKET) {
     mg_error(c, "socket(): %d", MG_SOCK_ERRNO);
+  } else if (c->is_udp) {
+    mg_call(c, MG_EV_RESOLVE, NULL);
+    mg_call(c, MG_EV_CONNECT, NULL);
   } else {
     union usa usa;
-    socklen_t slen = tousa(&c->peer, &usa);
-    if (c->is_udp == 0) mg_set_non_blocking_mode(FD(c));
-    if (c->is_udp == 0) setsockopts(c);
+    socklen_t slen = tousa(&c->rem, &usa);
+    mg_set_non_blocking_mode(FD(c));
+    setsockopts(c);
     mg_call(c, MG_EV_RESOLVE, NULL);
     if ((rc = connect(FD(c), &usa.sa, slen)) == 0) {
       mg_call(c, MG_EV_CONNECT, NULL);
     } else if (mg_sock_would_block()) {
+      MG_DEBUG(("%lu %p -> %x:%hu pend", c->id, c->fd, mg_ntohl(c->rem.ip),
+                mg_ntohs(c->rem.port)));
       c->is_connecting = 1;
     } else {
       mg_error(c, "connect: %d", MG_SOCK_ERRNO);
@@ -3208,56 +4223,49 @@ void mg_connect_resolved(struct mg_connection *c) {
   }
 }
 
-struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *url,
-                                 mg_event_handler_t fn, void *fn_data) {
-  struct mg_connection *c = NULL;
-  if ((c = alloc_conn(mgr, 1, INVALID_SOCKET)) == NULL) {
-    LOG(LL_ERROR, ("OOM"));
-  } else {
-    struct mg_str host = mg_url_host(url);
-    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
-    c->is_udp = (strncmp(url, "udp:", 4) == 0);
-    c->peer.port = mg_htons(mg_url_port(url));
-    c->fn = fn;
-    c->fn_data = fn_data;
-    LOG(LL_DEBUG, ("%lu -> %s", c->id, url));
-    mg_call(c, MG_EV_OPEN, NULL);
-    mg_resolve(c, &host, mgr->dnstimeout);
-  }
-  return c;
+static SOCKET raccept(SOCKET sock, union usa *usa, socklen_t len) {
+  SOCKET s = INVALID_SOCKET;
+  do {
+    memset(usa, 0, sizeof(*usa));
+    s = accept(sock, &usa->sa, &len);
+  } while (s == INVALID_SOCKET && errno == EINTR);
+  return s;
 }
 
 static void accept_conn(struct mg_mgr *mgr, struct mg_connection *lsn) {
   struct mg_connection *c = NULL;
   union usa usa;
   socklen_t sa_len = sizeof(usa);
-  SOCKET fd = accept(FD(lsn), &usa.sa, &sa_len);
+  SOCKET fd = raccept(FD(lsn), &usa, sa_len);
   if (fd == INVALID_SOCKET) {
 #if MG_ARCH == MG_ARCH_AZURERTOS
     // AzureRTOS, in non-block socket mode can mark listening socket readable
-    // even it is not. See comment for 'select' func implementation in nx_bsd.c
-    // That's not an error, just should try later
+    // even it is not. See comment for 'select' func implementation in
+    // nx_bsd.c That's not an error, just should try later
     if (MG_SOCK_ERRNO != EAGAIN)
 #endif
-      LOG(LL_ERROR, ("%lu accept failed, errno %d", lsn->id, MG_SOCK_ERRNO));
-#if (!defined(_WIN32) && (MG_ARCH != MG_ARCH_FREERTOS_TCP))
+      MG_ERROR(("%lu accept failed, errno %d", lsn->id, MG_SOCK_ERRNO));
+#if (MG_ARCH != MG_ARCH_WIN32) && (MG_ARCH != MG_ARCH_FREERTOS_TCP) && \
+    (MG_ARCH != MG_ARCH_TIRTOS) && !(MG_ENABLE_POLL)
   } else if ((long) fd >= FD_SETSIZE) {
-    LOG(LL_ERROR, ("%ld > %ld", (long) fd, (long) FD_SETSIZE));
+    MG_ERROR(("%ld > %ld", (long) fd, (long) FD_SETSIZE));
     closesocket(fd);
 #endif
-  } else if ((c = alloc_conn(mgr, 0, fd)) == NULL) {
-    LOG(LL_ERROR, ("%lu OOM", lsn->id));
+  } else if ((c = mg_alloc_conn(mgr)) == NULL) {
+    MG_ERROR(("%lu OOM", lsn->id));
     closesocket(fd);
   } else {
     char buf[40];
-    tomgaddr(&usa, &c->peer, sa_len != sizeof(usa.sin));
-    mg_straddr(c, buf, sizeof(buf));
-    LOG(LL_DEBUG, ("%lu accepted %s", c->id, buf));
+    tomgaddr(&usa, &c->rem, sa_len != sizeof(usa.sin));
+    mg_straddr(&c->rem, buf, sizeof(buf));
+    MG_DEBUG(("%lu accepted %s", c->id, buf));
+    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
+    c->fd = S2PTR(fd);
     mg_set_non_blocking_mode(FD(c));
     setsockopts(c);
-    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
     c->is_accepted = 1;
     c->is_hexdumping = lsn->is_hexdumping;
+    c->loc = lsn->loc;
     c->pfn = lsn->pfn;
     c->pfn_data = lsn->pfn_data;
     c->fn = lsn->fn;
@@ -3267,102 +4275,86 @@ static void accept_conn(struct mg_mgr *mgr, struct mg_connection *lsn) {
   }
 }
 
-static bool mg_socketpair(SOCKET sp[2], union usa usa[2]) {
+static bool mg_socketpair(SOCKET sp[2], union usa usa[2], bool udp) {
+  SOCKET sock;
   socklen_t n = sizeof(usa[0].sin);
-  bool result = false;
+  bool success = false;
 
+  sock = sp[0] = sp[1] = INVALID_SOCKET;
   (void) memset(&usa[0], 0, sizeof(usa[0]));
   usa[0].sin.sin_family = AF_INET;
-  *(uint32_t *) &usa->sin.sin_addr = mg_htonl(0x7f000001);  // 127.0.0.1
+  *(uint32_t *) &usa->sin.sin_addr = mg_htonl(0x7f000001U);  // 127.0.0.1
   usa[1] = usa[0];
 
-  if ((sp[0] = socket(AF_INET, SOCK_DGRAM, 0)) != INVALID_SOCKET &&
+  if (udp && (sp[0] = socket(AF_INET, SOCK_DGRAM, 0)) != INVALID_SOCKET &&
       (sp[1] = socket(AF_INET, SOCK_DGRAM, 0)) != INVALID_SOCKET &&
       bind(sp[0], &usa[0].sa, n) == 0 && bind(sp[1], &usa[1].sa, n) == 0 &&
       getsockname(sp[0], &usa[0].sa, &n) == 0 &&
       getsockname(sp[1], &usa[1].sa, &n) == 0 &&
       connect(sp[0], &usa[1].sa, n) == 0 &&
       connect(sp[1], &usa[0].sa, n) == 0) {
-    mg_set_non_blocking_mode(sp[1]);  // Set close-on-exec
-    result = true;
+    success = true;
+  } else if (!udp &&
+             (sock = socket(AF_INET, SOCK_STREAM, 0)) != INVALID_SOCKET &&
+             bind(sock, &usa[0].sa, n) == 0 &&
+             listen(sock, MG_SOCK_LISTEN_BACKLOG_SIZE) == 0 &&
+             getsockname(sock, &usa[0].sa, &n) == 0 &&
+             (sp[0] = socket(AF_INET, SOCK_STREAM, 0)) != INVALID_SOCKET &&
+             connect(sp[0], &usa[0].sa, n) == 0 &&
+             (sp[1] = raccept(sock, &usa[1], n)) != INVALID_SOCKET) {
+    success = true;
+  }
+  if (success) {
+    mg_set_non_blocking_mode(sp[1]);
   } else {
     if (sp[0] != INVALID_SOCKET) closesocket(sp[0]);
     if (sp[1] != INVALID_SOCKET) closesocket(sp[1]);
     sp[0] = sp[1] = INVALID_SOCKET;
   }
-
-  return result;
-}
-
-void mg_mgr_wakeup(struct mg_connection *c) {
-  LOG(LL_INFO, ("skt: %p", c->pfn_data));
-  send((SOCKET) (size_t) c->pfn_data, "\x01", 1, MSG_NONBLOCKING);
-}
-
-static void pf1(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
-  if (ev == MG_EV_READ) mg_iobuf_free(&c->recv);
-  (void) ev_data, (void) fn_data;
+  if (sock != INVALID_SOCKET) closesocket(sock);
+  return success;
 }
 
-struct mg_connection *mg_mkpipe(struct mg_mgr *mgr, mg_event_handler_t fn,
-                                void *fn_data) {
+int mg_mkpipe(struct mg_mgr *mgr, mg_event_handler_t fn, void *fn_data,
+              bool udp) {
   union usa usa[2];
   SOCKET sp[2] = {INVALID_SOCKET, INVALID_SOCKET};
   struct mg_connection *c = NULL;
-  if (!mg_socketpair(sp, usa)) {
-    LOG(LL_ERROR, ("Cannot create socket pair"));
-  } else if ((c = alloc_conn(mgr, false, sp[1])) == NULL) {
+  if (!mg_socketpair(sp, usa, udp)) {
+    MG_ERROR(("Cannot create socket pair"));
+  } else if ((c = mg_wrapfd(mgr, (int) sp[1], fn, fn_data)) == NULL) {
     closesocket(sp[0]);
     closesocket(sp[1]);
-    LOG(LL_ERROR, ("OOM"));
+    sp[0] = sp[1] = INVALID_SOCKET;
   } else {
-    LOG(LL_INFO, ("pipe %lu", (unsigned long) sp[0]));
-    tomgaddr(&usa[0], &c->peer, false);
-    c->is_udp = 1;
-    c->pfn = pf1;
-    c->pfn_data = (void *) (size_t) sp[0];
-    c->fn = fn;
-    c->fn_data = fn_data;
-    mg_call(c, MG_EV_OPEN, NULL);
-    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
+    tomgaddr(&usa[0], &c->rem, false);
+    MG_DEBUG(("%lu %p pipe %lu", c->id, c->fd, (unsigned long) sp[0]));
   }
-  return c;
+  return (int) sp[0];
 }
 
-struct mg_connection *mg_listen(struct mg_mgr *mgr, const char *url,
-                                mg_event_handler_t fn, void *fn_data) {
-  struct mg_connection *c = NULL;
-  bool is_udp = strncmp(url, "udp:", 4) == 0;
-  struct mg_addr addr;
-  SOCKET fd = mg_open_listener(url, &addr);
-  if (fd == INVALID_SOCKET) {
-    LOG(LL_ERROR, ("Failed: %s, errno %d", url, MG_SOCK_ERRNO));
-  } else if ((c = alloc_conn(mgr, 0, fd)) == NULL) {
-    LOG(LL_ERROR, ("OOM %s", url));
-    closesocket(fd);
-  } else {
-    memcpy(&c->peer, &addr, sizeof(struct mg_addr));
-    c->fd = S2PTR(fd);
-    c->is_listening = 1;
-    c->is_udp = is_udp;
-    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
-    c->fn = fn;
-    c->fn_data = fn_data;
-    mg_call(c, MG_EV_OPEN, NULL);
-    LOG(LL_DEBUG,
-        ("%lu accepting on %s (port %u)", c->id, url, mg_ntohs(c->peer.port)));
-  }
-  return c;
+static bool can_read(const struct mg_connection *c) {
+  return c->is_full == false;
+}
+
+static bool can_write(const struct mg_connection *c) {
+  return c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0);
+}
+
+static bool skip_iotest(const struct mg_connection *c) {
+  return (c->is_closing || c->is_resolving || FD(c) == INVALID_SOCKET) ||
+         (can_read(c) == false && can_write(c) == false);
 }
 
 static void mg_iotest(struct mg_mgr *mgr, int ms) {
 #if MG_ARCH == MG_ARCH_FREERTOS_TCP
   struct mg_connection *c;
   for (c = mgr->conns; c != NULL; c = c->next) {
-    if (c->is_closing || c->is_resolving || FD(c) == INVALID_SOCKET) continue;
-    FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_READ | eSELECT_EXCEPT);
-    if (c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0))
-      FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_WRITE);
+    c->is_readable = c->is_writable = 0;
+    if (skip_iotest(c)) continue;
+    if (can_read(c))
+      FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_READ | eSELECT_EXCEPT);
+    if (can_write(c)) FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_WRITE);
   }
   FreeRTOS_select(mgr->ss, pdMS_TO_TICKS(ms));
   for (c = mgr->conns; c != NULL; c = c->next) {
@@ -3372,71 +4364,108 @@ static void mg_iotest(struct mg_mgr *mgr, int ms) {
     FreeRTOS_FD_CLR(c->fd, mgr->ss,
                     eSELECT_READ | eSELECT_EXCEPT | eSELECT_WRITE);
   }
+#elif MG_ENABLE_POLL
+  nfds_t n = 0;
+  for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) n++;
+  struct pollfd *fds = (struct pollfd *) alloca(n * sizeof(fds[0]));
+  memset(fds, 0, n * sizeof(fds[0]));
+  n = 0;
+  for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
+    c->is_readable = c->is_writable = 0;
+    if (skip_iotest(c)) {
+      // Socket not valid, ignore
+    } else if (mg_tls_pending(c) > 0) {
+      ms = 1;  // Don't wait if TLS is ready
+    } else {
+      fds[n].fd = FD(c);
+      if (can_read(c)) fds[n].events |= POLLIN;
+      if (can_write(c)) fds[n].events |= POLLOUT;
+      n++;
+    }
+  }
+
+  // MG_INFO(("poll n=%d ms=%d", (int) n, ms));
+  if (poll(fds, n, ms) < 0) {
+#if MG_ARCH == MG_ARCH_WIN32
+    if (n == 0) Sleep(ms);  // On Windows, poll fails if no sockets
+#endif
+    memset(fds, 0, n * sizeof(fds[0]));
+  }
+  n = 0;
+  for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
+    if (skip_iotest(c)) {
+      // Socket not valid, ignore
+    } else if (mg_tls_pending(c) > 0) {
+      c->is_readable = 1;
+    } else {
+      if (fds[n].revents & POLLERR) {
+        mg_error(c, "socket error");
+      } else {
+        c->is_readable =
+            (unsigned) (fds[n].revents & (POLLIN | POLLHUP) ? 1 : 0);
+        c->is_writable = (unsigned) (fds[n].revents & POLLOUT ? 1 : 0);
+      }
+      n++;
+    }
+  }
 #else
-  struct timeval tv = {ms / 1000, (ms % 1000) * 1000};
+  struct timeval tv = {ms / 1000, (ms % 1000) * 1000}, tv_zero = {0, 0};
   struct mg_connection *c;
-  fd_set rset, wset;
+  fd_set rset, wset, eset;
   SOCKET maxfd = 0;
   int rc;
 
   FD_ZERO(&rset);
   FD_ZERO(&wset);
-
+  FD_ZERO(&eset);
   for (c = mgr->conns; c != NULL; c = c->next) {
-    if (c->is_closing || c->is_resolving || FD(c) == INVALID_SOCKET) continue;
-    FD_SET(FD(c), &rset);
+    c->is_readable = c->is_writable = 0;
+    if (skip_iotest(c)) continue;
+    FD_SET(FD(c), &eset);
+    if (can_read(c)) FD_SET(FD(c), &rset);
+    if (can_write(c)) FD_SET(FD(c), &wset);
+    if (mg_tls_pending(c) > 0) tv = tv_zero;
     if (FD(c) > maxfd) maxfd = FD(c);
-    if (c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0))
-      FD_SET(FD(c), &wset);
   }
 
-  if ((rc = select((int) maxfd + 1, &rset, &wset, NULL, &tv)) < 0) {
-    LOG(LL_DEBUG, ("select: %d %d", rc, MG_SOCK_ERRNO));
+  if ((rc = select((int) maxfd + 1, &rset, &wset, &eset, &tv)) < 0) {
+#if MG_ARCH == MG_ARCH_WIN32
+    if (maxfd == 0) Sleep(ms);  // On Windows, select fails if no sockets
+#else
+    MG_ERROR(("select: %d %d", rc, MG_SOCK_ERRNO));
+#endif
     FD_ZERO(&rset);
     FD_ZERO(&wset);
+    FD_ZERO(&eset);
   }
 
   for (c = mgr->conns; c != NULL; c = c->next) {
-    // TLS might have stuff buffered, so dig everything
-    c->is_readable = FD(c) != INVALID_SOCKET && FD_ISSET(FD(c), &rset);
-    c->is_writable = FD(c) != INVALID_SOCKET && FD_ISSET(FD(c), &wset);
+    if (FD(c) != INVALID_SOCKET && FD_ISSET(FD(c), &eset)) {
+      mg_error(c, "socket error");
+    } else {
+      c->is_readable = FD(c) != INVALID_SOCKET && FD_ISSET(FD(c), &rset);
+      c->is_writable = FD(c) != INVALID_SOCKET && FD_ISSET(FD(c), &wset);
+      if (mg_tls_pending(c) > 0) c->is_readable = 1;
+    }
   }
 #endif
 }
 
-static void connect_conn(struct mg_connection *c) {
-  int rc = 0;
-#if MG_ARCH != MG_ARCH_FREERTOS_TCP
-  socklen_t len = sizeof(rc);
-  if (getsockopt(FD(c), SOL_SOCKET, SO_ERROR, (char *) &rc, &len)) rc = 1;
-#endif
-  if (rc == EAGAIN || rc == EWOULDBLOCK) rc = 0;
-  c->is_connecting = 0;
-  if (rc) {
-    char buf[40];
-    mg_error(c, "error connecting to %s", mg_straddr(c, buf, sizeof(buf)));
-  } else {
-    if (c->is_tls_hs) mg_tls_handshake(c);
-    mg_call(c, MG_EV_CONNECT, NULL);
-  }
-}
-
 void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
   struct mg_connection *c, *tmp;
-  unsigned long now;
+  uint64_t now;
 
   mg_iotest(mgr, ms);
   now = mg_millis();
-  mg_timer_poll(now);
+  mg_timer_poll(&mgr->timers, now);
 
   for (c = mgr->conns; c != NULL; c = tmp) {
     tmp = c->next;
     mg_call(c, MG_EV_POLL, &now);
-    LOG(LL_VERBOSE_DEBUG,
-        ("%lu %c%c %c%c%c%c%c", c->id, c->is_readable ? 'r' : '-',
-         c->is_writable ? 'w' : '-', c->is_tls ? 'T' : 't',
-         c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',
-         c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c'));
+    MG_VERBOSE(("%lu %c%c %c%c%c%c%c", c->id, c->is_readable ? 'r' : '-',
+                c->is_writable ? 'w' : '-', c->is_tls ? 'T' : 't',
+                c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',
+                c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c'));
     if (c->is_resolving || c->is_closing) {
       // Do nothing
     } else if (c->is_listening && c->is_udp == 0) {
@@ -3448,7 +4477,6 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
     } else {
       if (c->is_readable) read_conn(c);
       if (c->is_writable) write_conn(c);
-      while (c->is_tls && read_conn(c) > 0) (void) 0;  // Read buffered TLS data
     }
 
     if (c->is_draining && c->send.len == 0) c->is_closing = 1;
@@ -3468,42 +4496,48 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
 #define MG_MAX_SSI_DEPTH 5
 #endif
 
+#ifndef MG_SSI_BUFSIZ
+#define MG_SSI_BUFSIZ 1024
+#endif
+
 #if MG_ENABLE_SSI
 static char *mg_ssi(const char *path, const char *root, int depth) {
   struct mg_iobuf b = {NULL, 0, 0};
   FILE *fp = fopen(path, "rb");
   if (fp != NULL) {
-    char buf[BUFSIZ], arg[sizeof(buf)];
+    char buf[MG_SSI_BUFSIZ], arg[sizeof(buf)];
     int ch, intag = 0;
     size_t len = 0, align = MG_IO_SIZE;
+    buf[0] = arg[0] = '\0';
     while ((ch = fgetc(fp)) != EOF) {
       if (intag && ch == '>' && buf[len - 1] == '-' && buf[len - 2] == '-') {
         buf[len++] = (char) (ch & 0xff);
+        buf[len] = '\0';
         if (sscanf(buf, "<!--#include file=\"%[^\"]", arg)) {
-          char tmp[MG_PATH_MAX + BUFSIZ + 10],
+          char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10],
               *p = (char *) path + strlen(path), *data;
           while (p > path && p[-1] != MG_DIRSEP && p[-1] != '/') p--;
-          snprintf(tmp, sizeof(tmp), "%.*s%s", (int) (p - path), path, arg);
+          mg_snprintf(tmp, sizeof(tmp), "%.*s%s", (int) (p - path), path, arg);
           if (depth < MG_MAX_SSI_DEPTH &&
               (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
             mg_iobuf_add(&b, b.len, data, strlen(data), align);
             free(data);
           } else {
-            LOG(LL_ERROR, ("%s: file=%s error or too deep", path, arg));
+            MG_ERROR(("%s: file=%s error or too deep", path, arg));
           }
         } else if (sscanf(buf, "<!--#include virtual=\"%[^\"]", arg)) {
-          char tmp[MG_PATH_MAX + BUFSIZ + 10], *data;
-          snprintf(tmp, sizeof(tmp), "%s%s", root, arg);
+          char tmp[MG_PATH_MAX + MG_SSI_BUFSIZ + 10], *data;
+          mg_snprintf(tmp, sizeof(tmp), "%s%s", root, arg);
           if (depth < MG_MAX_SSI_DEPTH &&
               (data = mg_ssi(tmp, root, depth + 1)) != NULL) {
             mg_iobuf_add(&b, b.len, data, strlen(data), align);
             free(data);
           } else {
-            LOG(LL_ERROR, ("%s: virtual=%s error or too deep", path, arg));
+            MG_ERROR(("%s: virtual=%s error or too deep", path, arg));
           }
         } else {
           // Unknown SSI tag
-          LOG(LL_INFO, ("Unknown SSI tag: %.*s", (int) len, buf));
+          MG_ERROR(("Unknown SSI tag: %.*s", (int) len, buf));
           mg_iobuf_add(&b, b.len, buf, len, align);
         }
         intag = 0;
@@ -3517,7 +4551,7 @@ static char *mg_ssi(const char *path, const char *root, int depth) {
         if (len == 5 && strncmp(buf, "<!--#", 5) != 0) {
           intag = 0;
         } else if (len >= sizeof(buf) - 2) {
-          LOG(LL_ERROR, ("%s: SSI tag is too large", path));
+          MG_ERROR(("%s: SSI tag is too large", path));
           len = 0;
         }
         buf[len++] = (char) (ch & 0xff);
@@ -3557,7 +4591,6 @@ void mg_http_serve_ssi(struct mg_connection *c, const char *root,
 #line 1 "src/str.c"
 #endif
 
-#include <stdlib.h>
 
 struct mg_str mg_str_s(const char *s) {
   struct mg_str str = {s, s == NULL ? 0 : strlen(s)};
@@ -3570,7 +4603,9 @@ struct mg_str mg_str_n(const char *s, size_t n) {
 }
 
 int mg_lower(const char *s) {
-  return tolower(*(const unsigned char *) s);
+  int c = *s;
+  if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
+  return c;
 }
 
 int mg_ncasecmp(const char *s1, const char *s2, size_t len) {
@@ -3639,12 +4674,166 @@ const char *mg_strstr(const struct mg_str haystack,
   return NULL;
 }
 
+static bool is_space(int c) {
+  return c == ' ' || c == '\r' || c == '\n' || c == '\t';
+}
+
 struct mg_str mg_strstrip(struct mg_str s) {
-  while (s.len > 0 && isspace((int) *s.ptr)) s.ptr++, s.len--;
-  while (s.len > 0 && isspace((int) *(s.ptr + s.len - 1))) s.len--;
+  while (s.len > 0 && is_space((int) *s.ptr)) s.ptr++, s.len--;
+  while (s.len > 0 && is_space((int) *(s.ptr + s.len - 1))) s.len--;
   return s;
 }
 
+bool mg_match(struct mg_str s, struct mg_str p, struct mg_str *caps) {
+  size_t i = 0, j = 0, ni = 0, nj = 0;
+  if (caps) caps->ptr = NULL, caps->len = 0;
+  while (i < p.len || j < s.len) {
+    if (i < p.len && j < s.len && (p.ptr[i] == '?' || s.ptr[j] == p.ptr[i])) {
+      if (caps == NULL) {
+      } else if (p.ptr[i] == '?') {
+        caps->ptr = &s.ptr[j], caps->len = 1;     // Finalize `?` cap
+        caps++, caps->ptr = NULL, caps->len = 0;  // Init next cap
+      } else if (caps->ptr != NULL && caps->len == 0) {
+        caps->len = (size_t) (&s.ptr[j] - caps->ptr);  // Finalize current cap
+        caps++, caps->len = 0, caps->ptr = NULL;       // Init next cap
+      }
+      i++, j++;
+    } else if (i < p.len && (p.ptr[i] == '*' || p.ptr[i] == '#')) {
+      if (caps && !caps->ptr) caps->len = 0, caps->ptr = &s.ptr[j];  // Init cap
+      ni = i++, nj = j + 1;
+    } else if (nj > 0 && nj <= s.len && (p.ptr[ni] == '#' || s.ptr[j] != '/')) {
+      i = ni, j = nj;
+      if (caps && caps->ptr == NULL && caps->len == 0) {
+        caps--, caps->len = 0;  // Restart previous cap
+      }
+    } else {
+      return false;
+    }
+  }
+  if (caps && caps->ptr && caps->len == 0) {
+    caps->len = (size_t) (&s.ptr[j] - caps->ptr);
+  }
+  return true;
+}
+
+bool mg_globmatch(const char *s1, size_t n1, const char *s2, size_t n2) {
+  return mg_match(mg_str_n(s2, n2), mg_str_n(s1, n1), NULL);
+}
+
+static size_t mg_nce(const char *s, size_t n, size_t ofs, size_t *koff,
+                     size_t *klen, size_t *voff, size_t *vlen, char delim) {
+  size_t kvlen, kl;
+  for (kvlen = 0; ofs + kvlen < n && s[ofs + kvlen] != delim;) kvlen++;
+  for (kl = 0; kl < kvlen && s[ofs + kl] != '=';) kl++;
+  if (koff != NULL) *koff = ofs;
+  if (klen != NULL) *klen = kl;
+  if (voff != NULL) *voff = kl < kvlen ? ofs + kl + 1 : 0;
+  if (vlen != NULL) *vlen = kl < kvlen ? kvlen - kl - 1 : 0;
+  ofs += kvlen + 1;
+  return ofs > n ? n : ofs;
+}
+
+bool mg_split(struct mg_str *s, struct mg_str *k, struct mg_str *v, char sep) {
+  size_t koff = 0, klen = 0, voff = 0, vlen = 0, off = 0;
+  if (s->ptr == NULL || s->len == 0) return 0;
+  off = mg_nce(s->ptr, s->len, 0, &koff, &klen, &voff, &vlen, sep);
+  if (k != NULL) *k = mg_str_n(s->ptr + koff, klen);
+  if (v != NULL) *v = mg_str_n(s->ptr + voff, vlen);
+  *s = mg_str_n(s->ptr + off, s->len - off);
+  return off > 0;
+}
+
+bool mg_commalist(struct mg_str *s, struct mg_str *k, struct mg_str *v) {
+  return mg_split(s, k, v, ',');
+}
+
+size_t mg_snprintf(char *buf, size_t len, const char *fmt, ...) {
+  va_list ap;
+  size_t n;
+  va_start(ap, fmt);
+  n = mg_vsnprintf(buf, len, fmt, &ap);
+  va_end(ap);
+  return n;
+}
+
+char *mg_hex(const void *buf, size_t len, char *to) {
+  const unsigned char *p = (const unsigned char *) buf;
+  const char *hex = "0123456789abcdef";
+  size_t i = 0;
+  for (; len--; p++) {
+    to[i++] = hex[p[0] >> 4];
+    to[i++] = hex[p[0] & 0x0f];
+  }
+  to[i] = '\0';
+  return to;
+}
+
+static unsigned char mg_unhex_nimble(unsigned char c) {
+  return (c >= '0' && c <= '9')   ? (unsigned char) (c - '0')
+         : (c >= 'A' && c <= 'F') ? (unsigned char) (c - '7')
+                                  : (unsigned char) (c - 'W');
+}
+
+unsigned long mg_unhexn(const char *s, size_t len) {
+  unsigned long i = 0, v = 0;
+  for (i = 0; i < len; i++) v <<= 4, v |= mg_unhex_nimble(((uint8_t *) s)[i]);
+  return v;
+}
+
+void mg_unhex(const char *buf, size_t len, unsigned char *to) {
+  size_t i;
+  for (i = 0; i < len; i += 2) {
+    to[i >> 1] = (unsigned char) mg_unhexn(&buf[i], 2);
+  }
+}
+
+uint64_t mg_tou64(struct mg_str str) {
+  uint64_t result = 0;
+  size_t i = 0;
+  while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] == '\t')) i++;
+  while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') {
+    result *= 10;
+    result += (unsigned) (str.ptr[i] - '0');
+    i++;
+  }
+  return result;
+}
+
+int64_t mg_to64(struct mg_str str) {
+  int64_t result = 0, neg = 1, max = 922337203685477570 /* INT64_MAX/10-10 */;
+  size_t i = 0;
+  while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] == '\t')) i++;
+  if (i < str.len && str.ptr[i] == '-') neg = -1, i++;
+  while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') {
+    if (result > max) return 0;
+    result *= 10;
+    result += (str.ptr[i] - '0');
+    i++;
+  }
+  return result * neg;
+}
+
+char *mg_remove_double_dots(char *s) {
+  char *saved = s, *p = s;
+  while (*s != '\0') {
+    *p++ = *s++;
+    if (s[-1] == '/' || s[-1] == '\\') {
+      while (s[0] != '\0') {
+        if (s[0] == '/' || s[0] == '\\') {
+          s++;
+        } else if (s[0] == '.' && s[1] == '.' &&
+                   (s[2] == '/' || s[2] == '\\')) {
+          s += 2;
+        } else {
+          break;
+        }
+      }
+    }
+  }
+  *p = '\0';
+  return saved;
+}
+
 #ifdef MG_ENABLE_LINES
 #line 1 "src/timer.c"
 #endif
@@ -3654,42 +4843,42 @@ struct mg_str mg_strstrip(struct mg_str s) {
 
 
 
-struct mg_timer *g_timers;
+#define MG_TIMER_CALLED 4
 
-void mg_timer_init(struct mg_timer *t, unsigned long ms, unsigned flags,
-                   void (*fn)(void *), void *arg) {
-  struct mg_timer tmp = {ms, 0UL, flags, fn, arg, g_timers};
+void mg_timer_init(struct mg_timer **head, struct mg_timer *t, uint64_t ms,
+                   unsigned flags, void (*fn)(void *), void *arg) {
+  struct mg_timer tmp = {ms, 0U, 0U, flags, fn, arg, *head};
   *t = tmp;
-  g_timers = t;
-  if (flags & MG_TIMER_RUN_NOW) fn(arg);
+  *head = t;
 }
 
-void mg_timer_free(struct mg_timer *t) {
-  struct mg_timer **head = &g_timers;
+void mg_timer_free(struct mg_timer **head, struct mg_timer *t) {
   while (*head && *head != t) head = &(*head)->next;
   if (*head) *head = t->next;
 }
 
-void mg_timer_poll(unsigned long now_ms) {
+void mg_timer_poll(struct mg_timer **head, uint64_t now_ms) {
   // If time goes back (wrapped around), reset timers
   struct mg_timer *t, *tmp;
-  static unsigned long oldnow;  // Timestamp in a previous invocation
-  if (oldnow > now_ms) {        // If it is wrapped, reset timers
-    for (t = g_timers; t != NULL; t = t->next) t->expire = 0;
-  }
-  oldnow = now_ms;
-
-  for (t = g_timers; t != NULL; t = tmp) {
+  for (t = *head; t != NULL; t = tmp) {
     tmp = t->next;
-    if (t->expire == 0) t->expire = now_ms + t->period_ms;
+    if (t->prev_ms > now_ms) t->expire = 0;  // Handle time wrap
+    t->prev_ms = now_ms;
+    if (t->expire == 0 && (t->flags & MG_TIMER_RUN_NOW) &&
+        !(t->flags & MG_TIMER_CALLED)) {
+      // Handle MG_TIMER_NOW only once
+    } else if (t->expire == 0) {
+      t->expire = now_ms + t->period_ms;
+    }
     if (t->expire > now_ms) continue;
-    t->fn(t->arg);
+    if ((t->flags & MG_TIMER_REPEAT) || !(t->flags & MG_TIMER_CALLED)) {
+      t->fn(t->arg);
+    }
+    t->flags |= MG_TIMER_CALLED;
     // Try to tick timers with the given period as accurate as possible,
     // even if this polling function is called with some random period.
-    t->expire = now_ms - t->expire > (unsigned long) t->period_ms
-                    ? now_ms + t->period_ms
-                    : t->expire + t->period_ms;
-    if (!(t->flags & MG_TIMER_REPEAT)) mg_timer_free(t);
+    t->expire = now_ms - t->expire > t->period_ms ? now_ms + t->period_ms
+                                                  : t->expire + t->period_ms;
   }
 }
 
@@ -3699,7 +4888,7 @@ void mg_timer_poll(unsigned long now_ms) {
 
 
 #if !MG_ENABLE_MBEDTLS && !MG_ENABLE_OPENSSL && !MG_ENABLE_CUSTOM_TLS
-void mg_tls_init(struct mg_connection *c, struct mg_tls_opts *opts) {
+void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
   (void) opts;
   mg_error(c, "TLS is not enabled");
 }
@@ -3715,6 +4904,10 @@ long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
 long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
   return c == NULL || buf == NULL || len == 0 ? 0 : -1;
 }
+size_t mg_tls_pending(struct mg_connection *c) {
+  (void) c;
+  return 0;
+}
 #endif
 
 #ifdef MG_ENABLE_LINES
@@ -3722,19 +4915,70 @@ long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
 #endif
 
 
+
 #if MG_ENABLE_MBEDTLS
+
+#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000
+#define MGRNG , rng_get, NULL
+#else
+#define MGRNG
+#endif
+
+void mg_tls_free(struct mg_connection *c) {
+  struct mg_tls *tls = (struct mg_tls *) c->tls;
+  if (tls != NULL) {
+    free(tls->cafile);
+    mbedtls_ssl_free(&tls->ssl);
+    mbedtls_pk_free(&tls->pk);
+    mbedtls_x509_crt_free(&tls->ca);
+    mbedtls_x509_crt_free(&tls->cert);
+    mbedtls_ssl_config_free(&tls->conf);
+    free(tls);
+    c->tls = NULL;
+  }
+}
+
+bool mg_sock_would_block(void);
+bool mg_sock_conn_reset(void);
+
+static int mg_net_send(void *ctx, const unsigned char *buf, size_t len) {
+  struct mg_connection *c = (struct mg_connection *) ctx;
+  int fd = (int) (size_t) c->fd;
+  int n = (int) send(fd, buf, len, 0);
+  MG_VERBOSE(("%lu n=%d, errno=%d", c->id, n, errno));
+  if (n < 0) {
+    if (mg_sock_would_block()) return MBEDTLS_ERR_SSL_WANT_WRITE;
+    if (mg_sock_conn_reset()) return MBEDTLS_ERR_NET_CONN_RESET;
+    return MBEDTLS_ERR_NET_SEND_FAILED;
+  }
+  return n;
+}
+
+static int mg_net_recv(void *ctx, unsigned char *buf, size_t len) {
+  struct mg_connection *c = (struct mg_connection *) ctx;
+  int n, fd = (int) (size_t) c->fd;
+  n = (int) recv(fd, buf, len, 0);
+  MG_VERBOSE(("%lu n=%d, errno=%d", c->id, n, errno));
+  if (n < 0) {
+    if (mg_sock_would_block()) return MBEDTLS_ERR_SSL_WANT_READ;
+    if (mg_sock_conn_reset()) return MBEDTLS_ERR_NET_CONN_RESET;
+    return MBEDTLS_ERR_NET_RECV_FAILED;
+  }
+  return n;
+}
+
 void mg_tls_handshake(struct mg_connection *c) {
   struct mg_tls *tls = (struct mg_tls *) c->tls;
   int rc;
-  mbedtls_ssl_set_bio(&tls->ssl, &c->fd, mbedtls_net_send, mbedtls_net_recv, 0);
+  mbedtls_ssl_set_bio(&tls->ssl, c, mg_net_send, mg_net_recv, 0);
   rc = mbedtls_ssl_handshake(&tls->ssl);
   if (rc == 0) {  // Success
-    LOG(LL_DEBUG, ("%lu success", c->id));
+    MG_DEBUG(("%lu success", c->id));
     c->is_tls_hs = 0;
   } else if (rc == MBEDTLS_ERR_SSL_WANT_READ ||
              rc == MBEDTLS_ERR_SSL_WANT_WRITE) {  // Still pending
-    LOG(LL_VERBOSE_DEBUG, ("%lu pending, %d%d %d (-%#x)", c->id,
-                           c->is_connecting, c->is_tls_hs, rc, -rc));
+    MG_VERBOSE(("%lu pending, %d%d %d (-%#x)", c->id, c->is_connecting,
+                c->is_tls_hs, rc, -rc));
   } else {
     mg_error(c, "TLS handshake: -%#x", -rc);  // Error
   }
@@ -3748,10 +4992,8 @@ static int mbed_rng(void *ctx, unsigned char *buf, size_t len) {
 
 static void debug_cb(void *c, int lev, const char *s, int n, const char *s2) {
   n = (int) strlen(s2) - 1;
-  LOG(LL_VERBOSE_DEBUG, ("%p %.*s", ((struct mg_connection *) c)->fd, n, s2));
+  MG_VERBOSE(("%lu %d %.*s", ((struct mg_connection *) c)->id, lev, n, s2));
   (void) s;
-  (void) c;
-  (void) lev;
 }
 
 #if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000
@@ -3762,37 +5004,32 @@ static int rng_get(void *p_rng, unsigned char *buf, size_t len) {
 }
 #endif
 
-void mg_tls_init(struct mg_connection *c, struct mg_tls_opts *opts) {
+static struct mg_str mg_loadfile(struct mg_fs *fs, const char *path) {
+  size_t n = 0;
+  if (path[0] == '-') return mg_str(path);
+  char *p = mg_file_read(fs, path, &n);
+  return mg_str_n(p, n);
+}
+
+void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
+  struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
   struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
   int rc = 0;
-  const char *ca = opts->ca == NULL     ? "-"
-                   : opts->ca[0] == '-' ? "(emb)"
-                                        : opts->ca;
-  const char *crl = opts->crl == NULL     ? "-"
-                    : opts->crl[0] == '-' ? "(emb)"
-                                          : opts->crl;
-  const char *cert = opts->cert == NULL     ? "-"
-                     : opts->cert[0] == '-' ? "(emb)"
-                                            : opts->cert;
-  const char *certkey = opts->certkey == NULL     ? "-"
-                        : opts->certkey[0] == '-' ? "(emb)"
-                                                  : opts->certkey;
-  if (tls == NULL) {
+  c->tls = tls;
+  if (c->tls == NULL) {
     mg_error(c, "TLS OOM");
     goto fail;
   }
-  LOG(LL_DEBUG, ("%lu Setting TLS, CA: %s, CRL: %s, cert: %s, key: %s", c->id,
-                 ca, crl, cert, certkey));
+  MG_DEBUG(("%lu Setting TLS", c->id));
   mbedtls_ssl_init(&tls->ssl);
   mbedtls_ssl_config_init(&tls->conf);
   mbedtls_x509_crt_init(&tls->ca);
-  mbedtls_x509_crl_init(&tls->crl);
   mbedtls_x509_crt_init(&tls->cert);
   mbedtls_pk_init(&tls->pk);
   mbedtls_ssl_conf_dbg(&tls->conf, debug_cb, c);
-  //#if !defined(ESP_PLATFORM)
-  // mbedtls_debug_set_threshold(5);
-  //#endif
+#if defined(MG_MBEDTLS_DEBUG_LEVEL)
+  mbedtls_debug_set_threshold(MG_MBEDTLS_DEBUG_LEVEL);
+#endif
   if ((rc = mbedtls_ssl_config_defaults(
            &tls->conf,
            c->is_client ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER,
@@ -3803,35 +5040,23 @@ void mg_tls_init(struct mg_connection *c, struct mg_tls_opts *opts) {
   mbedtls_ssl_conf_rng(&tls->conf, mbed_rng, c);
   if (opts->ca == NULL || strcmp(opts->ca, "*") == 0) {
     mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_NONE);
-  }
-  if (opts->ca != NULL && opts->ca[0] != '\0') {
-    if (opts->crl != NULL && opts->crl[0] != '\0') {
-      rc = opts->crl[0] == '-'
-               ? mbedtls_x509_crl_parse(&tls->crl, (uint8_t *) opts->crl,
-                                        strlen(opts->crl) + 1)
-               : mbedtls_x509_crl_parse_file(&tls->crl, opts->crl);
-      if (rc != 0) {
-        mg_error(c, "parse(%s) err %#x", crl, -rc);
-        goto fail;
-      }
-    }
+  } else if (opts->ca != NULL && opts->ca[0] != '\0') {
 #if defined(MBEDTLS_X509_CA_CHAIN_ON_DISK)
     tls->cafile = strdup(opts->ca);
-    rc = mbedtls_ssl_conf_ca_chain_file(&tls->conf, tls->cafile, &tls->crl);
+    rc = mbedtls_ssl_conf_ca_chain_file(&tls->conf, tls->cafile, NULL);
     if (rc != 0) {
-      mg_error(c, "parse on-disk chain(%s) err %#x", ca, -rc);
+      mg_error(c, "parse on-disk chain(%s) err %#x", tls->cafile, -rc);
       goto fail;
     }
 #else
-    rc = opts->ca[0] == '-'
-             ? mbedtls_x509_crt_parse(&tls->ca, (uint8_t *) opts->ca,
-                                      strlen(opts->ca) + 1)
-             : mbedtls_x509_crt_parse_file(&tls->ca, opts->ca);
+    struct mg_str s = mg_loadfile(fs, opts->ca);
+    rc = mbedtls_x509_crt_parse(&tls->ca, (uint8_t *) s.ptr, s.len + 1);
+    if (opts->ca[0] != '-') free((char *) s.ptr);
     if (rc != 0) {
-      mg_error(c, "parse(%s) err %#x", ca, -rc);
+      mg_error(c, "parse(%s) err %#x", opts->ca, -rc);
       goto fail;
     }
-    mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->ca, &tls->crl);
+    mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->ca, NULL);
 #endif
     if (opts->srvname.len > 0) {
       char mem[128], *buf = mem;
@@ -3843,24 +5068,20 @@ void mg_tls_init(struct mg_connection *c, struct mg_tls_opts *opts) {
     mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
   }
   if (opts->cert != NULL && opts->cert[0] != '\0') {
-    const char *key = opts->certkey;
-    if (key == NULL) {
-      key = opts->cert;
-      certkey = cert;
-    }
-    rc = opts->cert[0] == '-'
-             ? mbedtls_x509_crt_parse(&tls->cert, (uint8_t *) opts->cert,
-                                      strlen(opts->cert) + 1)
-             : mbedtls_x509_crt_parse_file(&tls->cert, opts->cert);
+    struct mg_str s = mg_loadfile(fs, opts->cert);
+    const char *key = opts->certkey == NULL ? opts->cert : opts->certkey;
+    rc = mbedtls_x509_crt_parse(&tls->cert, (uint8_t *) s.ptr, s.len + 1);
+    if (opts->cert[0] != '-') free((char *) s.ptr);
     if (rc != 0) {
-      mg_error(c, "parse(%s) err %#x", cert, -rc);
+      mg_error(c, "parse(%s) err %#x", opts->cert, -rc);
       goto fail;
     }
-    rc = key[0] == '-' ? mbedtls_pk_parse_key(&tls->pk, (uint8_t *) key,
-                                              strlen(key) + 1, NULL, 0 RNG)
-                       : mbedtls_pk_parse_keyfile(&tls->pk, key, NULL RNG);
+    s = mg_loadfile(fs, key);
+    rc = mbedtls_pk_parse_key(&tls->pk, (uint8_t *) s.ptr, s.len + 1, NULL,
+                              0 MGRNG);
+    if (key[0] != '-') free((char *) s.ptr);
     if (rc != 0) {
-      mg_error(c, "tls key(%s) %#x", certkey, -rc);
+      mg_error(c, "tls key(%s) %#x", key, -rc);
       goto fail;
     }
     rc = mbedtls_ssl_conf_own_cert(&tls->conf, &tls->cert, &tls->pk);
@@ -3881,8 +5102,12 @@ void mg_tls_init(struct mg_connection *c, struct mg_tls_opts *opts) {
   }
   return;
 fail:
-  c->is_closing = 1;
-  free(tls);
+  mg_tls_free(c);
+}
+
+size_t mg_tls_pending(struct mg_connection *c) {
+  struct mg_tls *tls = (struct mg_tls *) c->tls;
+  return tls == NULL ? 0 : mbedtls_ssl_get_bytes_avail(&tls->ssl);
 }
 
 long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
@@ -3896,20 +5121,6 @@ long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
   long n = mbedtls_ssl_write(&tls->ssl, (unsigned char *) buf, len);
   return n == 0 ? -1 : n == MBEDTLS_ERR_SSL_WANT_WRITE ? 0 : n;
 }
-
-void mg_tls_free(struct mg_connection *c) {
-  struct mg_tls *tls = (struct mg_tls *) c->tls;
-  if (tls == NULL) return;
-  free(tls->cafile);
-  mbedtls_ssl_free(&tls->ssl);
-  mbedtls_pk_free(&tls->pk);
-  mbedtls_x509_crt_free(&tls->ca);
-  mbedtls_x509_crl_free(&tls->crl);
-  mbedtls_x509_crt_free(&tls->cert);
-  mbedtls_ssl_config_free(&tls->conf);
-  free(tls);
-  c->tls = NULL;
-}
 #endif
 
 #ifdef MG_ENABLE_LINES
@@ -3936,7 +5147,7 @@ static int mg_tls_err(struct mg_tls *tls, int res) {
   return err;
 }
 
-void mg_tls_init(struct mg_connection *c, struct mg_tls_opts *opts) {
+void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
   struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
   const char *id = "mongoose";
   static unsigned char s_initialised = 0;
@@ -3951,10 +5162,10 @@ void mg_tls_init(struct mg_connection *c, struct mg_tls_opts *opts) {
     SSL_library_init();
     s_initialised++;
   }
-  LOG(LL_DEBUG, ("%lu Setting TLS, CA: %s, cert: %s, key: %s", c->id,
-                 opts->ca == NULL ? "null" : opts->ca,
-                 opts->cert == NULL ? "null" : opts->cert,
-                 opts->certkey == NULL ? "null" : opts->certkey));
+  MG_DEBUG(("%lu Setting TLS, CA: %s, cert: %s, key: %s", c->id,
+            opts->ca == NULL ? "null" : opts->ca,
+            opts->cert == NULL ? "null" : opts->cert,
+            opts->certkey == NULL ? "null" : opts->certkey));
   tls->ctx = c->is_client ? SSL_CTX_new(SSLv23_client_method())
                           : SSL_CTX_new(SSLv23_server_method());
   if ((tls->ssl = SSL_new(tls->ctx)) == NULL) {
@@ -3979,7 +5190,7 @@ void mg_tls_init(struct mg_connection *c, struct mg_tls_opts *opts) {
     SSL_set_verify(tls->ssl, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
                    NULL);
     if ((rc = SSL_CTX_load_verify_locations(tls->ctx, opts->ca, NULL)) != 1) {
-      mg_error(c, "parse(%s): err %d", opts->ca, mg_tls_err(tls, rc));
+      mg_error(c, "load('%s') %d err %d", opts->ca, rc, mg_tls_err(tls, rc));
       goto fail;
     }
   }
@@ -3995,7 +5206,7 @@ void mg_tls_init(struct mg_connection *c, struct mg_tls_opts *opts) {
 #if OPENSSL_VERSION_NUMBER > 0x10100000L
     } else if ((rc = SSL_use_certificate_chain_file(tls->ssl, opts->cert)) !=
                1) {
-      mg_error(c, "Invalid CA, err %d", mg_tls_err(tls, rc));
+      mg_error(c, "Invalid chain, err %d", mg_tls_err(tls, rc));
       goto fail;
 #endif
     } else {
@@ -4005,6 +5216,15 @@ void mg_tls_init(struct mg_connection *c, struct mg_tls_opts *opts) {
 #endif
     }
   }
+#if OPENSSL_VERSION_NUMBER > 0x10002000L
+  if (opts->srvname.len > 0) {
+    char mem[128], *buf = mem;
+    mg_asprintf(&buf, sizeof(mem), "%.*s", (int) opts->srvname.len,
+                opts->srvname.ptr);
+    SSL_set1_host(tls->ssl, buf);
+    if (buf != mem) free(buf);
+  }
+#endif
   if (opts->ciphers != NULL) SSL_set_cipher_list(tls->ssl, opts->ciphers);
   if (opts->srvname.len > 0) {
     char mem[128], *buf = mem;
@@ -4019,8 +5239,7 @@ void mg_tls_init(struct mg_connection *c, struct mg_tls_opts *opts) {
   if (c->is_client && c->is_resolving == 0 && c->is_connecting == 0) {
     mg_tls_handshake(c);
   }
-  c->is_hexdumping = 1;
-  LOG(LL_DEBUG, ("%lu SSL %s OK", c->id, c->is_accepted ? "accept" : "client"));
+  MG_DEBUG(("%lu SSL %s OK", c->id, c->is_accepted ? "accept" : "client"));
   return;
 fail:
   c->is_closing = 1;
@@ -4033,7 +5252,7 @@ void mg_tls_handshake(struct mg_connection *c) {
   SSL_set_fd(tls->ssl, (int) (size_t) c->fd);
   rc = c->is_client ? SSL_connect(tls->ssl) : SSL_accept(tls->ssl);
   if (rc == 1) {
-    LOG(LL_DEBUG, ("%lu success", c->id));
+    MG_DEBUG(("%lu success", c->id));
     c->is_tls_hs = 0;
   } else {
     int code = mg_tls_err(tls, rc);
@@ -4050,6 +5269,11 @@ void mg_tls_free(struct mg_connection *c) {
   c->tls = NULL;
 }
 
+size_t mg_tls_pending(struct mg_connection *c) {
+  struct mg_tls *tls = (struct mg_tls *) c->tls;
+  return tls == NULL ? 0 : (size_t) SSL_pending(tls->ssl);
+}
+
 long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
   struct mg_tls *tls = (struct mg_tls *) c->tls;
   int n = SSL_read(tls->ssl, buf, (int) len);
@@ -4067,7 +5291,6 @@ long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
 #line 1 "src/url.c"
 #endif
 
-#include <stdlib.h>
 
 struct url {
   size_t key, user, pass, host, port, uri, end;
@@ -4084,19 +5307,19 @@ static struct url urlparse(const char *url) {
   struct url u;
   memset(&u, 0, sizeof(u));
   for (i = 0; url[i] != '\0'; i++) {
-    if (i > 0 && u.host == 0 && url[i - 1] == '/' && url[i] == '/') {
+    if (url[i] == '/' && i > 0 && u.host == 0 && url[i - 1] == '/') {
       u.host = i + 1;
       u.port = 0;
     } else if (url[i] == ']') {
       u.port = 0;  // IPv6 URLs, like http://[::1]/bar
     } else if (url[i] == ':' && u.port == 0 && u.uri == 0) {
       u.port = i + 1;
-    } else if (url[i] == '@' && u.user == 0 && u.pass == 0) {
+    } else if (url[i] == '@' && u.user == 0 && u.pass == 0 && u.uri == 0) {
       u.user = u.host;
       u.pass = u.port;
       u.host = i + 1;
       u.port = 0;
-    } else if (u.host && u.uri == 0 && url[i] == '/') {
+    } else if (url[i] == '/' && u.host && u.uri == 0) {
       u.uri = i;
     }
   }
@@ -4124,10 +5347,11 @@ const char *mg_url_uri(const char *url) {
 unsigned short mg_url_port(const char *url) {
   struct url u = urlparse(url);
   unsigned short port = 0;
-  if (memcmp(url, "http:", 5) == 0 || memcmp(url, "ws:", 3) == 0) port = 80;
-  if (memcmp(url, "wss:", 4) == 0 || memcmp(url, "https:", 6) == 0) port = 443;
-  if (memcmp(url, "mqtt:", 5) == 0) port = 1883;
-  if (memcmp(url, "mqtts:", 6) == 0) port = 8883;
+  if (strncmp(url, "http:", 5) == 0 || strncmp(url, "ws:", 3) == 0) port = 80;
+  if (strncmp(url, "wss:", 4) == 0 || strncmp(url, "https:", 6) == 0)
+    port = 443;
+  if (strncmp(url, "mqtt:", 5) == 0) port = 1883;
+  if (strncmp(url, "mqtts:", 6) == 0) port = 8883;
   if (u.port) port = (unsigned short) atoi(url + u.port);
   return port;
 }
@@ -4157,67 +5381,6 @@ struct mg_str mg_url_pass(const char *url) {
 #endif
 
 
-
-
-#if MG_ARCH == MG_ARCH_UNIX && defined(__APPLE__)
-#include <mach/mach_time.h>
-#endif
-
-char *mg_file_read(const char *path, size_t *sizep) {
-  FILE *fp;
-  char *data = NULL;
-  size_t size = 0;
-  if ((fp = fopen(path, "rb")) != NULL) {
-    fseek(fp, 0, SEEK_END);
-    size = (size_t) ftell(fp);
-    rewind(fp);
-    data = (char *) calloc(1, size + 1);
-    if (data != NULL) {
-      if (fread(data, 1, size, fp) != size) {
-        free(data);
-        data = NULL;
-      } else {
-        data[size] = '\0';
-        if (sizep != NULL) *sizep = size;
-      }
-    }
-    fclose(fp);
-  }
-  return data;
-}
-
-bool mg_file_write(const char *path, const void *buf, size_t len) {
-  bool result = false;
-  FILE *fp;
-  char tmp[MG_PATH_MAX];
-  snprintf(tmp, sizeof(tmp), "%s.%d", path, rand());
-  fp = fopen(tmp, "wb");
-  if (fp != NULL) {
-    result = fwrite(buf, 1, len, fp) == len;
-    fclose(fp);
-    if (result) {
-      remove(path);
-      rename(tmp, path);
-    } else {
-      remove(tmp);
-    }
-  }
-  return result;
-}
-
-bool mg_file_printf(const char *path, const char *fmt, ...) {
-  char tmp[256], *buf = tmp;
-  bool result;
-  int len;
-  va_list ap;
-  va_start(ap, fmt);
-  len = mg_vasprintf(&buf, sizeof(tmp), fmt, ap);
-  va_end(ap);
-  result = mg_file_write(path, buf, len > 0 ? (size_t) len : 0);
-  if (buf != tmp) free(buf);
-  return result;
-}
-
 #if MG_ENABLE_CUSTOM_RANDOM
 #else
 void mg_random(void *buf, size_t len) {
@@ -4227,7 +5390,7 @@ void mg_random(void *buf, size_t len) {
   while (len--) *p++ = (unsigned char) (esp_random() & 255);
   done = true;
 #elif MG_ARCH == MG_ARCH_WIN32
-#elif MG_ARCH_UNIX
+#elif MG_ARCH == MG_ARCH_UNIX
   FILE *fp = fopen("/dev/urandom", "rb");
   if (fp != NULL) {
     if (fread(buf, 1, len, fp) == len) done = true;
@@ -4239,43 +5402,17 @@ void mg_random(void *buf, size_t len) {
 }
 #endif
 
-bool mg_globmatch(const char *s1, size_t n1, const char *s2, size_t n2) {
-  size_t i = 0, j = 0, ni = 0, nj = 0;
-  while (i < n1 || j < n2) {
-    if (i < n1 && j < n2 && (s1[i] == '?' || s2[j] == s1[i])) {
-      i++, j++;
-    } else if (i < n1 && (s1[i] == '*' || s1[i] == '#')) {
-      ni = i, nj = j + 1, i++;
-    } else if (nj > 0 && nj <= n2 && (s1[ni] == '#' || s2[j] != '/')) {
-      i = ni, j = nj;
-    } else {
-      // printf(">>: [%s] [%s] %d %d %d %d\n", s1, s2, i, j, ni, nj);
-      return false;
-    }
+char *mg_random_str(char *buf, size_t len) {
+  size_t i;
+  mg_random(buf, len);
+  for (i = 0; i < len; i++) {
+    uint8_t c = ((uint8_t *) buf)[i] % 62U;
+    buf[i] = i == len - 1 ? (char) '\0'            // 0-terminate last byte
+             : c < 26     ? (char) ('a' + c)       // lowercase
+             : c < 52     ? (char) ('A' + c - 26)  // uppercase
+                          : (char) ('0' + c - 52);     // numeric
   }
-  return true;
-}
-
-static size_t mg_nce(const char *s, size_t n, size_t ofs, size_t *koff,
-                     size_t *klen, size_t *voff, size_t *vlen) {
-  size_t kvlen, kl;
-  for (kvlen = 0; ofs + kvlen < n && s[ofs + kvlen] != ',';) kvlen++;
-  for (kl = 0; kl < kvlen && s[ofs + kl] != '=';) kl++;
-  if (koff != NULL) *koff = ofs;
-  if (klen != NULL) *klen = kl;
-  if (voff != NULL) *voff = kl < kvlen ? ofs + kl + 1 : 0;
-  if (vlen != NULL) *vlen = kl < kvlen ? kvlen - kl - 1 : 0;
-  ofs += kvlen + 1;
-  return ofs > n ? n : ofs;
-}
-
-bool mg_commalist(struct mg_str *s, struct mg_str *k, struct mg_str *v) {
-  size_t koff = 0, klen = 0, voff = 0, vlen = 0;
-  size_t off = mg_nce(s->ptr, s->len, 0, &koff, &klen, &voff, &vlen);
-  if (k != NULL) *k = mg_str_n(s->ptr + koff, klen);
-  if (v != NULL) *v = mg_str_n(s->ptr + voff, vlen);
-  *s = mg_str_n(s->ptr + off, s->len - off);
-  return off > 0;
+  return buf;
 }
 
 uint32_t mg_ntohl(uint32_t net) {
@@ -4291,131 +5428,6 @@ uint16_t mg_ntohs(uint16_t net) {
   return (uint16_t) ((uint16_t) data[1] | (((uint16_t) data[0]) << 8));
 }
 
-char *mg_hexdump(const void *buf, size_t len) {
-  const unsigned char *p = (const unsigned char *) buf;
-  size_t i, idx, n = 0, ofs = 0, dlen = len * 5 + 100;
-  char ascii[17] = "", *dst = (char *) calloc(1, dlen);
-  if (dst == NULL) return dst;
-  for (i = 0; i < len; i++) {
-    idx = i % 16;
-    if (idx == 0) {
-      if (i > 0 && dlen > n)
-        n += (size_t) snprintf(dst + n, dlen - n, "  %s\n", ascii);
-      if (dlen > n)
-        n += (size_t) snprintf(dst + n, dlen - n, "%04x ", (int) (i + ofs));
-    }
-    if (dlen < n) break;
-    n += (size_t) snprintf(dst + n, dlen - n, " %02x", p[i]);
-    ascii[idx] = (char) (p[i] < 0x20 || p[i] > 0x7e ? '.' : p[i]);
-    ascii[idx + 1] = '\0';
-  }
-  while (i++ % 16) {
-    if (n < dlen) n += (size_t) snprintf(dst + n, dlen - n, "%s", "   ");
-  }
-  if (n < dlen) n += (size_t) snprintf(dst + n, dlen - n, "  %s\n", ascii);
-  if (n > dlen - 1) n = dlen - 1;
-  dst[n] = '\0';
-  return dst;
-}
-
-char *mg_hex(const void *buf, size_t len, char *to) {
-  const unsigned char *p = (const unsigned char *) buf;
-  static const char *hex = "0123456789abcdef";
-  size_t i = 0;
-  for (; len--; p++) {
-    to[i++] = hex[p[0] >> 4];
-    to[i++] = hex[p[0] & 0x0f];
-  }
-  to[i] = '\0';
-  return to;
-}
-
-static unsigned char mg_unhex_nimble(unsigned char c) {
-  return (c >= '0' && c <= '9')   ? (unsigned char) (c - '0')
-         : (c >= 'A' && c <= 'F') ? (unsigned char) (c - '7')
-                                  : (unsigned char) (c - 'W');
-}
-
-unsigned long mg_unhexn(const char *s, size_t len) {
-  unsigned long i = 0, v = 0;
-  for (i = 0; i < len; i++) v <<= 4, v |= mg_unhex_nimble(((uint8_t *) s)[i]);
-  return v;
-}
-
-void mg_unhex(const char *buf, size_t len, unsigned char *to) {
-  size_t i;
-  for (i = 0; i < len; i += 2) {
-    to[i >> 1] = (unsigned char) mg_unhexn(&buf[i], 2);
-  }
-}
-
-int mg_vasprintf(char **buf, size_t size, const char *fmt, va_list ap) {
-  va_list ap_copy;
-  int len;
-
-  va_copy(ap_copy, ap);
-  len = vsnprintf(*buf, size, fmt, ap_copy);
-  va_end(ap_copy);
-
-  if (len < 0) {
-    // eCos and Windows are not standard-compliant and return -1 when
-    // the buffer is too small. Keep allocating larger buffers until we
-    // succeed or out of memory.
-    // LCOV_EXCL_START
-    *buf = NULL;
-    while (len < 0) {
-      free(*buf);
-      if (size == 0) size = 5;
-      size *= 2;
-      if ((*buf = (char *) calloc(1, size)) == NULL) {
-        len = -1;
-        break;
-      }
-      va_copy(ap_copy, ap);
-      len = vsnprintf(*buf, size - 1, fmt, ap_copy);
-      va_end(ap_copy);
-    }
-    // Microsoft version of vsnprintf() is not always null-terminated, so put
-    // the terminator manually
-    if (*buf != NULL) (*buf)[len] = 0;
-    // LCOV_EXCL_STOP
-  } else if (len >= (int) size) {
-    /// Standard-compliant code path. Allocate a buffer that is large enough
-    if ((*buf = (char *) calloc(1, (size_t) len + 1)) == NULL) {
-      len = -1;  // LCOV_EXCL_LINE
-    } else {     // LCOV_EXCL_LINE
-      va_copy(ap_copy, ap);
-      len = vsnprintf(*buf, (size_t) len + 1, fmt, ap_copy);
-      va_end(ap_copy);
-    }
-  }
-
-  return len;
-}
-
-int mg_asprintf(char **buf, size_t size, const char *fmt, ...) {
-  int ret;
-  va_list ap;
-  va_start(ap, fmt);
-  ret = mg_vasprintf(buf, size, fmt, ap);
-  va_end(ap);
-  return ret;
-}
-
-int64_t mg_to64(struct mg_str str) {
-  int64_t result = 0, neg = 1, max = 922337203685477570 /* INT64_MAX/10-10 */;
-  size_t i = 0;
-  while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] == '\t')) i++;
-  if (i < str.len && str.ptr[i] == '-') neg = -1, i++;
-  while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') {
-    if (result > max) return 0;
-    result *= 10;
-    result += (str.ptr[i] - '0');
-    i++;
-  }
-  return result * neg;
-}
-
 uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len) {
   int i;
   crc = ~crc;
@@ -4451,55 +5463,18 @@ int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip) {
     uint32_t net, mask;
     if (k.ptr[0] != '+' && k.ptr[0] != '-') return -1;
     if (parse_net(&k.ptr[1], &net, &mask) == 0) return -2;
-    if ((remote_ip & mask) == net) allowed = k.ptr[0];
+    if ((mg_ntohl(remote_ip) & mask) == net) allowed = k.ptr[0];
   }
   return allowed == '+';
 }
 
-double mg_time(void) {
-#if MG_ARCH == MG_ARCH_WIN32
-  SYSTEMTIME sysnow;
-  FILETIME ftime;
-  GetLocalTime(&sysnow);
-  SystemTimeToFileTime(&sysnow, &ftime);
-  /*
-   * 1. VC 6.0 doesn't support conversion uint64 -> double, so, using int64
-   * This should not cause a problems in this (21th) century
-   * 2. Windows FILETIME is a number of 100-nanosecond intervals since January
-   * 1, 1601 while time_t is a number of _seconds_ since January 1, 1970 UTC,
-   * thus, we need to convert to seconds and adjust amount (subtract 11644473600
-   * seconds)
-   */
-  return (double) (((int64_t) ftime.dwLowDateTime +
-                    ((int64_t) ftime.dwHighDateTime << 32)) /
-                   10000000.0) -
-         11644473600;
-#elif MG_ARCH == MG_ARCH_FREERTOS_TCP || MG_ARCH == MG_ARCH_AZURERTOS
-  return mg_millis() / 1000.0;
-#else
-  struct timeval tv;
-  if (gettimeofday(&tv, NULL /* tz */) != 0) return 0;
-  return (double) tv.tv_sec + (((double) tv.tv_usec) / 1000000.0);
-#endif /* _WIN32 */
-}
-
-void mg_usleep(unsigned long usecs) {
-#if MG_ARCH == MG_ARCH_WIN32
-  Sleep(usecs / 1000);
-#elif MG_ARCH == MG_ARCH_ESP8266
-  ets_delay_us(usecs);
-#elif MG_ARCH == MG_ARCH_FREERTOS_TCP || MG_ARCH == MG_ARCH_FREERTOS_LWIP
-  vTaskDelay(pdMS_TO_TICKS(usecs / 1000));
-#elif MG_ARCH == MG_ARCH_AZURERTOS
-  tx_thread_sleep((usecs / 1000) * TX_TIMER_TICKS_PER_SECOND);
+#if MG_ENABLE_CUSTOM_MILLIS
 #else
-  usleep((useconds_t) usecs);
-#endif
-}
-
-unsigned long mg_millis(void) {
+uint64_t mg_millis(void) {
 #if MG_ARCH == MG_ARCH_WIN32
   return GetTickCount();
+#elif MG_ARCH == MG_ARCH_RP2040
+  return time_us_64() / 1000;
 #elif MG_ARCH == MG_ARCH_ESP32
   return esp_timer_get_time() / 1000;
 #elif MG_ARCH == MG_ARCH_ESP8266
@@ -4509,27 +5484,29 @@ unsigned long mg_millis(void) {
 #elif MG_ARCH == MG_ARCH_AZURERTOS
   return tx_time_get() * (1000 /* MS per SEC */ / TX_TIMER_TICKS_PER_SECOND);
 #elif MG_ARCH == MG_ARCH_UNIX && defined(__APPLE__)
-  uint64_t ticks = mach_absolute_time();
-  static mach_timebase_info_data_t timebase;
-  mach_timebase_info(&timebase);
-  double ticks_to_nanos = (double)timebase.numer / timebase.denom;
-  uint64_t uptime_nanos = (uint64_t)(ticks_to_nanos * ticks);
-  return (unsigned long) (uptime_nanos / 1000000);
-#else
-  struct timespec ts;
-#ifdef _POSIX_MONOTONIC_CLOCK
-#ifdef CLOCK_MONOTONIC_RAW
+  // Apple CLOCK_MONOTONIC_RAW is equivalent to CLOCK_BOOTTIME on linux
+  // Apple CLOCK_UPTIME_RAW is equivalent to CLOCK_MONOTONIC_RAW on linux
+  return clock_gettime_nsec_np(CLOCK_UPTIME_RAW) / 1000000;
+#elif MG_ARCH == MG_ARCH_UNIX
+  struct timespec ts = {0, 0};
+  // See #1615 - prefer monotonic clock
+#if defined(CLOCK_MONOTONIC_RAW)
+  // Raw hardware-based time that is not subject to NTP adjustment
   clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
-#else
+#elif defined(CLOCK_MONOTONIC)
+  // Affected by the incremental adjustments performed by adjtime and NTP
   clock_gettime(CLOCK_MONOTONIC, &ts);
-#endif
 #else
+  // Affected by discontinuous jumps in the system time and by the incremental
+  // adjustments performed by adjtime and NTP
   clock_gettime(CLOCK_REALTIME, &ts);
 #endif
-  return (unsigned long) ((uint64_t) ts.tv_sec * 1000 +
-                          (uint64_t) ts.tv_nsec / 1000000);
+  return ((uint64_t) ts.tv_sec * 1000 + (uint64_t) ts.tv_nsec / 1000000);
+#else
+  return (uint64_t) (time(NULL) * 1000);
 #endif
 }
+#endif
 
 #ifdef MG_ENABLE_LINES
 #line 1 "src/ws.c"
@@ -4549,6 +5526,24 @@ struct ws_msg {
   size_t data_len;
 };
 
+size_t mg_ws_vprintf(struct mg_connection *c, int op, const char *fmt,
+                     va_list ap) {
+  char mem[256], *buf = mem;
+  size_t len = mg_vasprintf(&buf, sizeof(mem), fmt, ap);
+  len = mg_ws_send(c, buf, len, op);
+  if (buf != mem) free(buf);
+  return len;
+}
+
+size_t mg_ws_printf(struct mg_connection *c, int op, const char *fmt, ...) {
+  size_t len = 0;
+  va_list ap;
+  va_start(ap, fmt);
+  len = mg_ws_vprintf(c, op, fmt, ap);
+  va_end(ap);
+  return len;
+}
+
 static void ws_handshake(struct mg_connection *c, const struct mg_str *wskey,
                          const struct mg_str *wsproto, const char *fmt,
                          va_list ap) {
@@ -4651,7 +5646,7 @@ size_t mg_ws_send(struct mg_connection *c, const char *buf, size_t len,
   uint8_t header[14];
   size_t header_len = mkhdr(len, op, c->is_client, header);
   mg_send(c, header, header_len);
-  LOG(LL_VERBOSE_DEBUG, ("WS out: %d [%.*s]", (int) len, (int) len, buf));
+  MG_VERBOSE(("WS out: %d [%.*s]", (int) len, (int) len, buf));
   mg_send(c, buf, len);
   mg_ws_mask(c, len);
   return header_len + len;
@@ -4670,8 +5665,7 @@ static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data,
         c->is_closing = 1;  // Some just, not an HTTP request
       } else if (n > 0) {
         if (n < 15 || memcmp(c->recv.buf + 9, "101", 3) != 0) {
-          LOG(LL_ERROR,
-              ("%lu WS handshake error: %.*s", c->id, 15, c->recv.buf));
+          MG_ERROR(("%lu WS handshake error: %.*s", c->id, 15, c->recv.buf));
           c->is_closing = 1;
         } else {
           struct mg_http_message hm;
@@ -4690,14 +5684,14 @@ static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data,
       struct mg_ws_message m = {{s, msg.data_len}, msg.flags};
       size_t len = msg.header_len + msg.data_len;
       uint8_t final = msg.flags & 128, op = msg.flags & 15;
-      // LOG(LL_VERBOSE_DEBUG, ("fin %d op %d len %d [%.*s]", final, op,
+      // MG_VERBOSE ("fin %d op %d len %d [%.*s]", final, op,
       //                       (int) m.data.len, (int) m.data.len, m.data.ptr));
       switch (op) {
         case WEBSOCKET_OP_CONTINUE:
           mg_call(c, MG_EV_WS_CTL, &m);
           break;
         case WEBSOCKET_OP_PING:
-          LOG(LL_DEBUG, ("%s", "WS PONG"));
+          MG_DEBUG(("%s", "WS PONG"));
           mg_ws_send(c, s, msg.data_len, WEBSOCKET_OP_PONG);
           mg_call(c, MG_EV_WS_CTL, &m);
           break;
@@ -4709,9 +5703,10 @@ static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data,
           if (final) mg_call(c, MG_EV_WS_MSG, &m);
           break;
         case WEBSOCKET_OP_CLOSE:
-          LOG(LL_ERROR, ("%lu Got WS CLOSE", c->id));
+          MG_DEBUG(("%lu Got WS CLOSE", c->id));
           mg_call(c, MG_EV_WS_CTL, &m);
-          c->is_closing = 1;
+          mg_ws_send(c, "", 0, WEBSOCKET_OP_CLOSE);
+          c->is_draining = 1;
           break;
         default:
           // Per RFC6455, close conn when an unknown op is recvd
@@ -4726,7 +5721,7 @@ static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data,
         len -= msg.header_len;
         ofs += len;
         c->pfn_data = (void *) ofs;
-        // LOG(LL_INFO, ("FRAG %d [%.*s]", (int) ofs, (int) ofs, c->recv.buf));
+        // MG_INFO(("FRAG %d [%.*s]", (int) ofs, (int) ofs, c->recv.buf));
       }
       // Remove non-fragmented frame
       if (final && op) mg_iobuf_del(&c->recv, ofs, len);
@@ -4752,7 +5747,8 @@ struct mg_connection *mg_ws_connect(struct mg_mgr *mgr, const char *url,
   if (c != NULL) {
     char nonce[16], key[30], mem1[128], mem2[256], *buf1 = mem1, *buf2 = mem2;
     struct mg_str host = mg_url_host(url);
-    int n1 = 0, n2 = 0;
+    size_t n1 = 0, n2 = 0;
+    nonce[0] = key[0] = mem1[0] = mem2[0] = '\0';
     if (fmt != NULL) {
       va_list ap;
       va_start(ap, fmt);
@@ -4760,10 +5756,8 @@ struct mg_connection *mg_ws_connect(struct mg_mgr *mgr, const char *url,
       va_end(ap);
     }
     // Send handshake request
-    // mg_url_host(url, host, sizeof(host));
     mg_random(nonce, sizeof(nonce));
     mg_base64_encode((unsigned char *) nonce, sizeof(nonce), key);
-    // LOG(LL_DEBUG, "%p [%s]", uri, uri == NULL ? "???" : uri);
     n2 = mg_asprintf(&buf2, sizeof(mem2),
                      "GET %s HTTP/1.1\r\n"
                      "Upgrade: websocket\r\n"
@@ -4773,8 +5767,9 @@ struct mg_connection *mg_ws_connect(struct mg_mgr *mgr, const char *url,
                      "Sec-WebSocket-Version: 13\r\n"
                      "Sec-WebSocket-Key: %s\r\n"
                      "\r\n",
-                     mg_url_uri(url), (int) host.len, host.ptr, n1, buf1, key);
-    mg_send(c, buf2, n2 > 0 ? (size_t) n2 : 0);
+                     mg_url_uri(url), (int) host.len, host.ptr, (int) n1, buf1,
+                     key);
+    mg_send(c, buf2, n2);
     if (buf1 != mem1) free(buf1);
     if (buf2 != mem2) free(buf2);
     c->pfn = mg_ws_cb;
@@ -4815,3 +5810,966 @@ size_t mg_ws_wrap(struct mg_connection *c, size_t len, int op) {
 
   return c->send.len;
 }
+
+#ifdef MG_ENABLE_LINES
+#line 1 "mip/driver_stm32.c"
+#endif
+// Copyright (c) 2022 Cesanta Software Limited
+// All rights reserved
+
+
+
+#if MG_ENABLE_MIP && defined(__arm__)
+
+struct stm32_eth {
+  volatile uint32_t MACCR, MACFFR, MACHTHR, MACHTLR, MACMIIAR, MACMIIDR, MACFCR,
+      MACVLANTR, RESERVED0[2], MACRWUFFR, MACPMTCSR, RESERVED1, MACDBGR, MACSR,
+      MACIMR, MACA0HR, MACA0LR, MACA1HR, MACA1LR, MACA2HR, MACA2LR, MACA3HR,
+      MACA3LR, RESERVED2[40], MMCCR, MMCRIR, MMCTIR, MMCRIMR, MMCTIMR,
+      RESERVED3[14], MMCTGFSCCR, MMCTGFMSCCR, RESERVED4[5], MMCTGFCR,
+      RESERVED5[10], MMCRFCECR, MMCRFAECR, RESERVED6[10], MMCRGUFCR,
+      RESERVED7[334], PTPTSCR, PTPSSIR, PTPTSHR, PTPTSLR, PTPTSHUR, PTPTSLUR,
+      PTPTSAR, PTPTTHR, PTPTTLR, RESERVED8, PTPTSSR, PTPPPSCR, RESERVED9[564],
+      DMABMR, DMATPDR, DMARPDR, DMARDLAR, DMATDLAR, DMASR, DMAOMR, DMAIER,
+      DMAMFBOCR, DMARSWTR, RESERVED10[8], DMACHTDR, DMACHRDR, DMACHTBAR,
+      DMACHRBAR;
+};
+#define ETH ((struct stm32_eth *) (uintptr_t) 0x40028000)
+
+#define BIT(x) ((uint32_t) 1 << (x))
+#define ETH_PKT_SIZE 1540  // Max frame size
+#define ETH_DESC_CNT 4     // Descriptors count
+#define ETH_DS 4           // Descriptor size (words)
+
+static uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS];      // RX descriptors
+static uint32_t s_txdesc[ETH_DESC_CNT][ETH_DS];      // TX descriptors
+static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];  // RX ethernet buffers
+static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];  // TX ethernet buffers
+static void (*s_rx)(void *, size_t, void *);         // Recv callback
+static void *s_rxdata;                               // Recv callback data
+static void *s_userdata;                             // Driver data
+enum { PHY_ADDR = 0, PHY_BCR = 0, PHY_BSR = 1 };     // PHY constants
+
+static inline void spin(volatile uint32_t count) {
+  while (count--) asm("nop");
+}
+
+static uint32_t eth_read_phy(uint8_t addr, uint8_t reg) {
+  ETH->MACMIIAR &= (7 << 2);
+  ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6);
+  ETH->MACMIIAR |= BIT(0);
+  while (ETH->MACMIIAR & BIT(0)) spin(1);
+  return ETH->MACMIIDR;
+}
+
+static void eth_write_phy(uint8_t addr, uint8_t reg, uint32_t val) {
+  ETH->MACMIIDR = val;
+  ETH->MACMIIAR &= (7 << 2);
+  ETH->MACMIIAR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6) | BIT(1);
+  ETH->MACMIIAR |= BIT(0);
+  while (ETH->MACMIIAR & BIT(0)) spin(1);
+}
+
+static void mip_driver_stm32_init(void *userdata) {
+  s_userdata = userdata;
+
+  // Init RX descriptors
+  for (int i = 0; i < ETH_DESC_CNT; i++) {
+    s_rxdesc[i][0] = BIT(31);                            // Own
+    s_rxdesc[i][1] = sizeof(s_rxbuf[i]) | BIT(14);       // 2nd address chained
+    s_rxdesc[i][2] = (uint32_t) (uintptr_t) s_rxbuf[i];  // Point to data buffer
+    s_rxdesc[i][3] =
+        (uint32_t) (uintptr_t) s_rxdesc[(i + 1) % ETH_DESC_CNT];  // Chain
+  }
+
+  // Init TX descriptors
+  for (int i = 0; i < ETH_DESC_CNT; i++) {
+    s_txdesc[i][2] = (uint32_t) (uintptr_t) s_txbuf[i];  // Buf pointer
+    s_txdesc[i][3] =
+        (uint32_t) (uintptr_t) s_txdesc[(i + 1) % ETH_DESC_CNT];  // Chain
+  }
+
+  ETH->DMABMR |= BIT(0);                        // Software reset
+  while ((ETH->DMABMR & BIT(0)) != 0) spin(1);  // Wait until done
+  // NOTE(cpq): we do not use extended descriptor bit 7, and do not use
+  // hardware checksum. Therefore, descriptor size is 4, not 8
+  // ETH->DMABMR = BIT(13) | BIT(16) | BIT(22) | BIT(23) | BIT(25);
+  ETH->MACIMR = BIT(3) | BIT(9);              // Mask timestamp & PMT IT
+  ETH->MACMIIAR = 4 << 2;                     // MDC clock 150-216 MHz, 38.8.1
+  ETH->MACFCR = BIT(7);                       // Disable zero quarta pause
+  ETH->MACFFR = BIT(31);                      // Receive all
+  eth_write_phy(PHY_ADDR, PHY_BCR, BIT(15));  // Reset PHY
+  eth_write_phy(PHY_ADDR, PHY_BCR, BIT(12));  // Set autonegotiation
+  ETH->DMARDLAR = (uint32_t) (uintptr_t) s_rxdesc;     // RX descriptors
+  ETH->DMATDLAR = (uint32_t) (uintptr_t) s_txdesc;     // RX descriptors
+  ETH->DMAIER = BIT(6) | BIT(16);                      // RIE, NISE
+  ETH->MACCR = BIT(2) | BIT(3) | BIT(11) | BIT(14);    // RE, TE, Duplex, Fast
+  ETH->DMAOMR = BIT(1) | BIT(13) | BIT(21) | BIT(25);  // SR, ST, TSF, RSF
+}
+
+static void mip_driver_stm32_setrx(void (*rx)(void *, size_t, void *),
+                                   void *rxdata) {
+  s_rx = rx;
+  s_rxdata = rxdata;
+}
+
+static uint32_t s_txno;
+static size_t mip_driver_stm32_tx(const void *buf, size_t len, void *userdata) {
+  if (len > sizeof(s_txbuf[s_txno])) {
+    printf("%s: frame too big, %ld\n", __func__, (long) len);
+    len = 0;  // Frame is too big
+  } else if ((s_txdesc[s_txno][0] & BIT(31))) {
+    printf("%s: no free descr\n", __func__);
+    len = 0;  // All descriptors are busy, fail
+  } else {
+    memcpy(s_txbuf[s_txno], buf, len);     // Copy data
+    s_txdesc[s_txno][1] = (uint32_t) len;  // Set data len
+    s_txdesc[s_txno][0] = BIT(20) | BIT(28) | BIT(29) | BIT(30);  // Chain,FS,LS
+    s_txdesc[s_txno][0] |= BIT(31);  // Set OWN bit - let DMA take over
+    if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
+  }
+  uint32_t sr = ETH->DMASR;
+  if (sr & BIT(2)) ETH->DMASR = BIT(2), ETH->DMATPDR = 0;  // Resume
+  if (sr & BIT(5)) ETH->DMASR = BIT(5), ETH->DMATPDR = 0;  // if busy
+  if (len == 0) printf("E: D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long) sr);
+  return len;
+  (void) userdata;
+}
+
+static bool mip_driver_stm32_status(void *userdata) {
+  uint32_t bsr = eth_read_phy(PHY_ADDR, PHY_BSR);
+  return bsr & BIT(2) ? 1 : 0;
+  (void) userdata;
+}
+
+void ETH_IRQHandler(void);
+void ETH_IRQHandler(void) {
+  volatile uint32_t sr = ETH->DMASR;
+  if (sr & BIT(6)) {  // Frame received, loop
+    for (uint32_t i = 0; i < ETH_DESC_CNT; i++) {
+      if (s_rxdesc[i][0] & BIT(31)) continue;
+      uint32_t len = ((s_rxdesc[i][0] >> 16) & (BIT(14) - 1));
+      //    printf("%lx %lu %lx %lx\n", i, len, s_rxdesc[i][0], sr);
+      if (s_rx != NULL) s_rx(s_rxbuf[i], len > 4 ? len - 4 : len, s_rxdata);
+      s_rxdesc[i][0] = BIT(31);
+    }
+  }
+  if (sr & BIT(7)) ETH->DMARPDR = 0;     // Resume RX
+  ETH->DMASR = sr & ~(BIT(2) | BIT(7));  // Clear status
+}
+
+struct mip_driver mip_driver_stm32 = {.init = mip_driver_stm32_init,
+                                      .tx = mip_driver_stm32_tx,
+                                      .rxcb = mip_driver_stm32_setrx,
+                                      .status = mip_driver_stm32_status};
+#endif  // MG_ENABLE_MIP
+
+#ifdef MG_ENABLE_LINES
+#line 1 "mip/mip.c"
+#endif
+
+
+#if MG_ENABLE_MIP
+
+#if defined(_MSC_VER)
+#define _Atomic
+#define NET16(x) _byteswap_ushort(x)
+#define NET32(x) _byteswap_ulong(x)
+#else
+#include <stdatomic.h>
+#define NET16(x) __builtin_bswap16(x)
+#define NET32(x) __builtin_bswap32(x)
+#endif
+
+#define MIP_ETHEMERAL_PORT 49152
+#define U16(ptr) ((((uint16_t) (ptr)[0]) << 8) | (ptr)[1])
+#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a))))
+
+#ifndef MIP_ARP_ENTRIES
+#define MIP_ARP_ENTRIES 5  // Number of ARP cache entries. Maximum 21
+#endif
+#define MIP_ARP_CS (2 + 12 * MIP_ARP_ENTRIES)  // ARP cache size
+
+struct str {
+  uint8_t *buf;
+  size_t len;
+};
+
+// Receive queue - single producer, single consumer queue.  Interrupt-based
+// drivers copy received frames to the queue in interrupt context. mip_poll()
+// function runs in event loop context, reads from the queue
+struct queue {
+  uint8_t *buf;
+  size_t len;
+  volatile _Atomic size_t tail, head;
+};
+
+// Network interface
+struct mip_if {
+  uint8_t mac[6];             // MAC address. Must be set to a valid MAC
+  uint32_t ip, mask, gw;      // IP address, mask, default gateway. Can be 0
+  struct str rx;              // Output (TX) buffer
+  struct str tx;              // Input (RX) buffer
+  bool use_dhcp;              // Enable DCHP
+  struct mip_driver *driver;  // Low level driver
+  struct mg_mgr *mgr;         // Mongoose event manager
+
+  // Internal state, user can use it but should not change it
+  uint64_t curtime;               // Last poll timestamp in millis
+  uint64_t timer;                 // Timer
+  uint8_t arp_cache[MIP_ARP_CS];  // Each entry is 12 bytes
+  uint16_t eport;                 // Next ephemeral port
+  int state;                      // Current state
+#define MIP_STATE_DOWN 0          // Interface is down
+#define MIP_STATE_UP 1            // Interface is up
+#define MIP_STATE_READY 2         // Interface is up and has IP
+  struct queue queue;             // Receive queue
+};
+
+#pragma pack(push, 1)
+
+struct lcp {
+  uint8_t addr, ctrl, proto[2], code, id, len[2];
+};
+
+struct eth {
+  uint8_t dst[6];  // Destination MAC address
+  uint8_t src[6];  // Source MAC address
+  uint16_t type;   // Ethernet type
+};
+
+struct ip {
+  uint8_t ver;    // Version
+  uint8_t tos;    // Unused
+  uint16_t len;   // Length
+  uint16_t id;    // Unused
+  uint16_t frag;  // Fragmentation
+  uint8_t ttl;    // Time to live
+  uint8_t proto;  // Upper level protocol
+  uint16_t csum;  // Checksum
+  uint32_t src;   // Source IP
+  uint32_t dst;   // Destination IP
+};
+
+struct ip6 {
+  uint8_t ver;      // Version
+  uint8_t opts[3];  // Options
+  uint16_t len;     // Length
+  uint8_t proto;    // Upper level protocol
+  uint8_t ttl;      // Time to live
+  uint8_t src[16];  // Source IP
+  uint8_t dst[16];  // Destination IP
+};
+
+struct icmp {
+  uint8_t type;
+  uint8_t code;
+  uint16_t csum;
+};
+
+struct arp {
+  uint16_t fmt;    // Format of hardware address
+  uint16_t pro;    // Format of protocol address
+  uint8_t hlen;    // Length of hardware address
+  uint8_t plen;    // Length of protocol address
+  uint16_t op;     // Operation
+  uint8_t sha[6];  // Sender hardware address
+  uint32_t spa;    // Sender protocol address
+  uint8_t tha[6];  // Target hardware address
+  uint32_t tpa;    // Target protocol address
+};
+
+struct tcp {
+  uint16_t sport;  // Source port
+  uint16_t dport;  // Destination port
+  uint32_t seq;    // Sequence number
+  uint32_t ack;    // Acknowledgement number
+  uint8_t off;     // Data offset
+  uint8_t flags;   // TCP flags
+#define TH_FIN 0x01
+#define TH_SYN 0x02
+#define TH_RST 0x04
+#define TH_PUSH 0x08
+#define TH_ACK 0x10
+#define TH_URG 0x20
+#define TH_ECE 0x40
+#define TH_CWR 0x80
+  uint16_t win;   // Window
+  uint16_t csum;  // Checksum
+  uint16_t urp;   // Urgent pointer
+};
+
+struct udp {
+  uint16_t sport;  // Source port
+  uint16_t dport;  // Destination port
+  uint16_t len;    // UDP length
+  uint16_t csum;   // UDP checksum
+};
+
+struct dhcp {
+  uint8_t op, htype, hlen, hops;
+  uint32_t xid;
+  uint16_t secs, flags;
+  uint32_t ciaddr, yiaddr, siaddr, giaddr;
+  uint8_t hwaddr[208];
+  uint32_t magic;
+  uint8_t options[32];
+};
+
+#pragma pack(pop)
+
+struct pkt {
+  struct str raw;  // Raw packet data
+  struct str pay;  // Payload data
+  struct eth *eth;
+  struct llc *llc;
+  struct arp *arp;
+  struct ip *ip;
+  struct ip6 *ip6;
+  struct icmp *icmp;
+  struct tcp *tcp;
+  struct udp *udp;
+  struct dhcp *dhcp;
+};
+
+static void q_copyin(struct queue *q, const uint8_t *buf, size_t len,
+                     size_t head) {
+  size_t i = 0, left = q->len - head;
+  for (; i < len && i < left; i++) q->buf[head + i] = buf[i];
+  for (; i < len; i++) q->buf[i - left] = buf[i];
+}
+
+static void q_copyout(struct queue *q, uint8_t *buf, size_t len, size_t tail) {
+  size_t i = 0, left = q->len - tail;
+  for (; i < len && i < left; i++) buf[i] = q->buf[tail + i];
+  for (; i < len; i++) buf[i] = q->buf[i - left];
+}
+
+static bool q_write(struct queue *q, const void *buf, size_t len) {
+  bool success = false;
+  size_t left = q->len - q->head + q->tail;
+  if (len + sizeof(size_t) <= left) {
+    q_copyin(q, (uint8_t *) &len, sizeof(len), q->head);
+    q_copyin(q, (uint8_t *) buf, len, (q->head + sizeof(size_t)) % q->len);
+    q->head = (q->head + sizeof(len) + len) % q->len;
+    success = true;
+  }
+  return success;
+}
+
+static size_t q_avail(struct queue *q) {
+  size_t n = 0;
+  if (q->tail != q->head) q_copyout(q, (uint8_t *) &n, sizeof(n), q->tail);
+  return n;
+}
+
+static size_t q_read(struct queue *q, void *buf) {
+  size_t n = q_avail(q);
+  if (n > 0) {
+    q_copyout(q, (uint8_t *) buf, n, (q->tail + sizeof(n)) % q->len);
+    q->tail = (q->tail + sizeof(n) + n) % q->len;
+  }
+  return n;
+}
+
+static struct str mkstr(void *buf, size_t len) {
+  struct str str = {(uint8_t *) buf, len};
+  return str;
+}
+
+static void mkpay(struct pkt *pkt, void *p) {
+  pkt->pay = mkstr(p, (size_t) (&pkt->raw.buf[pkt->raw.len] - (uint8_t *) p));
+}
+
+static uint32_t csumup(uint32_t sum, const void *buf, size_t len) {
+  const uint8_t *p = (const uint8_t *) buf;
+  for (size_t i = 0; i < len; i++) sum += i & 1 ? p[i] : (uint32_t) (p[i] << 8);
+  return sum;
+}
+
+static uint16_t csumfin(uint32_t sum) {
+  while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16);
+  return NET16(~sum & 0xffff);
+}
+
+static uint16_t ipcsum(const void *buf, size_t len) {
+  uint32_t sum = csumup(0, buf, len);
+  return csumfin(sum);
+}
+
+// ARP cache is organised as a doubly linked list. A successful cache lookup
+// moves an entry to the head of the list. New entries are added by replacing
+// the last entry in the list with a new IP/MAC.
+// ARP cache format: | prev | next | Entry0 | Entry1 | .... | EntryN |
+// ARP entry format: | prev | next | IP (4bytes) | MAC (6bytes) |
+// prev and next are 1-byte offsets in the cache, so cache size is max 256 bytes
+// ARP entry size is 12 bytes
+static void arp_cache_init(uint8_t *p, int n, int size) {
+  for (int i = 0; i < n; i++) p[2 + i * size] = (uint8_t) (2 + (i - 1) * size);
+  for (int i = 0; i < n; i++) p[3 + i * size] = (uint8_t) (2 + (i + 1) * size);
+  p[0] = p[2] = (uint8_t) (2 + (n - 1) * size);
+  p[1] = p[3 + (n - 1) * size] = 2;
+}
+
+static uint8_t *arp_cache_find(struct mip_if *ifp, uint32_t ip) {
+  uint8_t *p = ifp->arp_cache;
+  if (ip == 0) return NULL;
+  if (p[0] == 0 || p[1] == 0) arp_cache_init(p, MIP_ARP_ENTRIES, 12);
+  for (uint8_t i = 0, j = p[1]; i < MIP_ARP_ENTRIES; i++, j = p[j + 1]) {
+    if (memcmp(p + j + 2, &ip, sizeof(ip)) == 0) {
+      p[1] = j, p[0] = p[j];  // Found entry! Point list head to us
+      // MG_DEBUG(("ARP find: %#lx @ %x:%x:%x:%x:%x:%x\n", (long) ip, p[j + 6],
+      //          p[j + 7], p[j + 8], p[j + 9], p[j + 10], p[j + 11]));
+      return p + j + 6;  // And return MAC address
+    }
+  }
+  return NULL;
+}
+
+static void arp_cache_add(struct mip_if *ifp, uint32_t ip, uint8_t mac[6]) {
+  uint8_t *p = ifp->arp_cache;
+  if (ip == 0 || ip == ~0U) return;             // Bad IP
+  if (arp_cache_find(ifp, ip) != NULL) return;  // Already exists, do nothing
+  memcpy(p + p[0] + 2, &ip, sizeof(ip));  // Replace last entry: IP address
+  memcpy(p + p[0] + 6, mac, 6);           // And MAC address
+  p[1] = p[0], p[0] = p[p[1]];            // Point list head to us
+  // MG_DEBUG(("ARP cache: added %#lx @ %x:%x:%x:%x:%x:%x\n", (long) ip, mac[0],
+  //      mac[1], mac[2], mac[3], mac[4], mac[5]));
+}
+
+static void arp_ask(struct mip_if *ifp, uint32_t ip) {
+  struct eth *eth = (struct eth *) ifp->tx.buf;
+  struct arp *arp = (struct arp *) (eth + 1);
+  memset(eth->dst, 255, sizeof(eth->dst));
+  memcpy(eth->src, ifp->mac, sizeof(eth->src));
+  eth->type = NET16(0x806);
+  memset(arp, 0, sizeof(*arp));
+  arp->fmt = NET16(1), arp->pro = NET16(0x800), arp->hlen = 6, arp->plen = 4;
+  arp->op = NET16(1), arp->tpa = ip, arp->spa = ifp->ip;
+  memcpy(arp->sha, ifp->mac, sizeof(arp->sha));
+  ifp->driver->tx(eth, PDIFF(eth, arp + 1), ifp->driver->data);
+}
+
+static void onstatechange(struct mip_if *ifp) {
+  if (ifp->state == MIP_STATE_READY) {
+    char buf[40];
+    struct mg_addr addr = {.ip = ifp->ip};
+    MG_INFO(("READY, IP: %s", mg_ntoa(&addr, buf, sizeof(buf))));
+    arp_ask(ifp, ifp->gw);
+  } else if (ifp->state == MIP_STATE_UP) {
+    MG_ERROR(("Network up"));
+  } else if (ifp->state == MIP_STATE_DOWN) {
+    MG_ERROR(("Network down"));
+  }
+}
+
+static struct ip *tx_ip(struct mip_if *ifp, uint8_t proto, uint32_t ip_src,
+                        uint32_t ip_dst, size_t plen) {
+  struct eth *eth = (struct eth *) ifp->tx.buf;
+  struct ip *ip = (struct ip *) (eth + 1);
+  uint8_t *mac = arp_cache_find(ifp, ip_dst);         // Dst IP in ARP cache ?
+  if (!mac) mac = arp_cache_find(ifp, ifp->gw);       // No, use gateway
+  if (mac) memcpy(eth->dst, mac, sizeof(eth->dst));   // Found? Use it
+  if (!mac) memset(eth->dst, 255, sizeof(eth->dst));  // No? Use broadcast
+  memcpy(eth->src, ifp->mac, sizeof(eth->src));       // TODO(cpq): ARP lookup
+  eth->type = NET16(0x800);
+  memset(ip, 0, sizeof(*ip));
+  ip->ver = 0x45;   // Version 4, header length 5 words
+  ip->frag = 0x40;  // Don't fragment
+  ip->len = NET16((uint16_t) (sizeof(*ip) + plen));
+  ip->ttl = 64;
+  ip->proto = proto;
+  ip->src = ip_src;
+  ip->dst = ip_dst;
+  ip->csum = ipcsum(ip, sizeof(*ip));
+  return ip;
+}
+
+static void tx_udp(struct mip_if *ifp, uint32_t ip_src, uint16_t sport,
+                   uint32_t ip_dst, uint16_t dport, const void *buf,
+                   size_t len) {
+  struct ip *ip = tx_ip(ifp, 17, ip_src, ip_dst, len + sizeof(struct udp));
+  struct udp *udp = (struct udp *) (ip + 1);
+  udp->sport = sport;
+  udp->dport = dport;
+  udp->len = NET16((uint16_t) (sizeof(*udp) + len));
+  udp->csum = 0;
+  uint32_t cs = csumup(0, udp, sizeof(*udp));
+  cs = csumup(cs, buf, len);
+  cs = csumup(cs, &ip->src, sizeof(ip->src));
+  cs = csumup(cs, &ip->dst, sizeof(ip->dst));
+  cs += (uint32_t) (ip->proto + sizeof(*udp) + len);
+  udp->csum = csumfin(cs);
+  memmove(udp + 1, buf, len);
+  // MG_DEBUG(("UDP LEN %d %d\n", (int) len, (int) ifp->frame_len));
+  ifp->driver->tx(ifp->tx.buf,
+                  sizeof(struct eth) + sizeof(*ip) + sizeof(*udp) + len,
+                  ifp->driver->data);
+}
+
+static void tx_dhcp(struct mip_if *ifp, uint32_t src, uint32_t dst,
+                    uint8_t *opts, size_t optslen) {
+  struct dhcp dhcp = {.op = 1,
+                      .htype = 1,
+                      .hlen = 6,
+                      .ciaddr = src,
+                      .magic = NET32(0x63825363)};
+  memcpy(&dhcp.hwaddr, ifp->mac, sizeof(ifp->mac));
+  memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid));
+  memcpy(&dhcp.options, opts, optslen);
+  tx_udp(ifp, src, NET16(68), dst, NET16(67), &dhcp, sizeof(dhcp));
+}
+
+static void tx_dhcp_request(struct mip_if *ifp, uint32_t src, uint32_t dst) {
+  uint8_t opts[] = {
+      53, 1, 3,                 // Type: DHCP request
+      55, 2, 1,   3,            // GW and mask
+      12, 3, 'm', 'i', 'p',     // Host name: "mip"
+      54, 4, 0,   0,   0,   0,  // DHCP server ID
+      50, 4, 0,   0,   0,   0,  // Requested IP
+      255                       // End of options
+  };
+  memcpy(opts + 14, &dst, sizeof(dst));
+  memcpy(opts + 20, &src, sizeof(src));
+  tx_dhcp(ifp, src, dst, opts, sizeof(opts));
+}
+
+static void tx_dhcp_discover(struct mip_if *ifp) {
+  uint8_t opts[] = {
+      53, 1, 1,     // Type: DHCP discover
+      55, 2, 1, 3,  // Parameters: ip, mask
+      255           // End of options
+  };
+  tx_dhcp(ifp, 0, 0xffffffff, opts, sizeof(opts));
+}
+
+static void rx_arp(struct mip_if *ifp, struct pkt *pkt) {
+  // MG_DEBUG(("ARP op %d %#x %#x\n", NET16(arp->op), arp->spa, arp->tpa));
+  if (pkt->arp->op == NET16(1) && pkt->arp->tpa == ifp->ip) {
+    // ARP request. Make a response, then send
+    struct eth *eth = (struct eth *) ifp->tx.buf;
+    struct arp *arp = (struct arp *) (eth + 1);
+    memcpy(eth->dst, pkt->eth->src, sizeof(eth->dst));
+    memcpy(eth->src, ifp->mac, sizeof(eth->src));
+    eth->type = NET16(0x806);
+    *arp = *pkt->arp;
+    arp->op = NET16(2);
+    memcpy(arp->tha, pkt->arp->sha, sizeof(pkt->arp->tha));
+    memcpy(arp->sha, ifp->mac, sizeof(pkt->arp->sha));
+    arp->tpa = pkt->arp->spa;
+    arp->spa = ifp->ip;
+    MG_DEBUG(("ARP response: we're %#lx", (long) ifp->ip));
+    ifp->driver->tx(ifp->tx.buf, PDIFF(eth, arp + 1), ifp->driver->data);
+  } else if (pkt->arp->op == NET16(2)) {
+    if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) != 0) return;
+    // MG_INFO(("ARP RESPONSE"));
+    arp_cache_add(ifp, pkt->arp->spa, pkt->arp->sha);
+  }
+}
+
+static void rx_icmp(struct mip_if *ifp, struct pkt *pkt) {
+  // MG_DEBUG(("ICMP %d\n", (int) len));
+  if (pkt->icmp->type == 8 && pkt->ip->dst == ifp->ip) {
+    struct ip *ip = tx_ip(ifp, 1, ifp->ip, pkt->ip->src,
+                          sizeof(struct icmp) + pkt->pay.len);
+    struct icmp *icmp = (struct icmp *) (ip + 1);
+    memset(icmp, 0, sizeof(*icmp));  // Important - set csum to 0
+    memcpy(icmp + 1, pkt->pay.buf, pkt->pay.len);
+    icmp->csum = ipcsum(icmp, sizeof(*icmp) + pkt->pay.len);
+    ifp->driver->tx(ifp->tx.buf, PDIFF(ifp->tx.buf, icmp + 1) + pkt->pay.len,
+                    ifp->driver->data);
+  }
+}
+
+static void rx_dhcp(struct mip_if *ifp, struct pkt *pkt) {
+  uint32_t ip = 0, gw = 0, mask = 0;
+  uint8_t *p = pkt->dhcp->options, *end = &pkt->raw.buf[pkt->raw.len];
+  if (end < (uint8_t *) (pkt->dhcp + 1)) return;
+  // MG_DEBUG(("DHCP %u\n", (unsigned) pkt->raw.len));
+  while (p < end && p[0] != 255) {
+    if (p[0] == 1 && p[1] == sizeof(ifp->mask)) {
+      memcpy(&mask, p + 2, sizeof(mask));
+      // MG_DEBUG(("MASK %x\n", mask));
+    } else if (p[0] == 3 && p[1] == sizeof(ifp->gw)) {
+      memcpy(&gw, p + 2, sizeof(gw));
+      ip = pkt->dhcp->yiaddr;
+      // MG_DEBUG(("IP %x GW %x\n", ip, gw));
+    }
+    p += p[1] + 2;
+  }
+  if (ip && mask && gw && ifp->ip == 0) {
+    // MG_DEBUG(("DHCP offer ip %#08lx mask %#08lx gw %#08lx\n",
+    //      (long) ip, (long) mask, (long) gw));
+    arp_cache_add(ifp, pkt->dhcp->siaddr, ((struct eth *) pkt->raw.buf)->src);
+    ifp->ip = ip, ifp->gw = gw, ifp->mask = mask;
+    ifp->state = MIP_STATE_READY;
+    onstatechange(ifp);
+    tx_dhcp_request(ifp, ip, pkt->dhcp->siaddr);
+  }
+}
+
+static struct mg_connection *getpeer(struct mg_mgr *mgr, struct pkt *pkt,
+                                     bool lsn) {
+  struct mg_connection *c = NULL;
+  for (c = mgr->conns; c != NULL; c = c->next) {
+    if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport) break;
+    if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport &&
+        lsn == c->is_listening && (lsn || c->rem.port == pkt->tcp->sport))
+      break;
+  }
+  return c;
+}
+
+static void rx_udp(struct mip_if *ifp, struct pkt *pkt) {
+  struct mg_connection *c = getpeer(ifp->mgr, pkt, true);
+  if (c == NULL) {
+    // No UDP listener on this port. Should send ICMP, but keep silent.
+  } else if (c != NULL) {
+    c->rem.port = pkt->udp->sport;
+    c->rem.ip = pkt->ip->src;
+    if (c->recv.len >= MG_MAX_RECV_SIZE) {
+      mg_error(c, "max_recv_buf_size reached");
+    } else if (c->recv.size - c->recv.len < pkt->pay.len &&
+               !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) {
+      mg_error(c, "oom");
+    } else {
+      memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len);
+      c->recv.len += pkt->pay.len;
+      struct mg_str evd = mg_str_n((char *) pkt->pay.buf, pkt->pay.len);
+      mg_call(c, MG_EV_READ, &evd);
+    }
+  }
+}
+
+struct tcpstate {
+  uint32_t seq, ack;
+  time_t expire;
+};
+
+static size_t tx_tcp(struct mip_if *ifp, uint32_t dst_ip, uint8_t flags,
+                     uint16_t sport, uint16_t dport, uint32_t seq, uint32_t ack,
+                     const void *buf, size_t len) {
+  struct ip *ip = tx_ip(ifp, 6, ifp->ip, dst_ip, sizeof(struct tcp) + len);
+  struct tcp *tcp = (struct tcp *) (ip + 1);
+  memset(tcp, 0, sizeof(*tcp));
+  memmove(tcp + 1, buf, len);
+  tcp->sport = sport;
+  tcp->dport = dport;
+  tcp->seq = seq;
+  tcp->ack = ack;
+  tcp->flags = flags;
+  tcp->win = mg_htons(8192);
+  tcp->off = (uint8_t) (sizeof(*tcp) / 4 << 4);
+  uint32_t cs = 0;
+  uint16_t n = (uint16_t) (sizeof(*tcp) + len);
+  uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8), (uint8_t) (n & 255)};
+  cs = csumup(cs, tcp, n);
+  cs = csumup(cs, &ip->src, sizeof(ip->src));
+  cs = csumup(cs, &ip->dst, sizeof(ip->dst));
+  cs = csumup(cs, pseudo, sizeof(pseudo));
+  tcp->csum = csumfin(cs);
+  return ifp->driver->tx(ifp->tx.buf, PDIFF(ifp->tx.buf, tcp + 1) + len,
+                         ifp->driver->data);
+}
+
+static size_t tx_tcp_pkt(struct mip_if *ifp, struct pkt *pkt, uint8_t flags,
+                         uint32_t seq, const void *buf, size_t len) {
+  uint32_t delta = (pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0;
+  return tx_tcp(ifp, pkt->ip->src, flags, pkt->tcp->dport, pkt->tcp->sport, seq,
+                mg_htonl(mg_ntohl(pkt->tcp->seq) + delta), buf, len);
+}
+
+static struct mg_connection *accept_conn(struct mg_connection *lsn,
+                                         struct pkt *pkt) {
+  struct mg_connection *c = mg_alloc_conn(lsn->mgr);
+  struct tcpstate *s = (struct tcpstate *) (c + 1);
+  s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq);
+  c->rem.ip = pkt->ip->src;
+  c->rem.port = pkt->tcp->sport;
+  MG_DEBUG(("%lu accepted %lx:%hx", c->id, c->rem.ip, c->rem.port));
+  LIST_ADD_HEAD(struct mg_connection, &lsn->mgr->conns, c);
+  c->fd = (void *) (size_t) mg_ntohl(pkt->tcp->ack);
+  c->is_accepted = 1;
+  c->is_hexdumping = lsn->is_hexdumping;
+  c->pfn = lsn->pfn;
+  c->loc = lsn->loc;
+  c->pfn_data = lsn->pfn_data;
+  c->fn = lsn->fn;
+  c->fn_data = lsn->fn_data;
+  mg_call(c, MG_EV_OPEN, NULL);
+  mg_call(c, MG_EV_ACCEPT, NULL);
+  return c;
+}
+
+static void read_conn(struct mg_connection *c, struct pkt *pkt) {
+  struct tcpstate *s = (struct tcpstate *) (c + 1);
+  if (pkt->tcp->flags & TH_FIN) {
+    s->ack = mg_htonl(pkt->tcp->seq) + 1, s->seq = mg_htonl(pkt->tcp->ack);
+    c->is_closing = 1;
+  } else if (pkt->pay.len == 0) {
+  } else if (c->recv.size - c->recv.len < pkt->pay.len &&
+             !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) {
+    mg_error(c, "oom");
+  } else if (mg_ntohl(pkt->tcp->seq) != s->ack) {
+    mg_error(c, "oob: %x %x", mg_ntohl(pkt->tcp->seq), s->ack);
+  } else {
+    s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len);
+    memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len);
+    c->recv.len += pkt->pay.len;
+    struct mg_str evd = mg_str_n((char *) pkt->pay.buf, pkt->pay.len);
+    mg_call(c, MG_EV_READ, &evd);
+#if 0
+    // Send ACK immediately
+    tx_tcp(ifp, c->rem.ip, TH_ACK, c->loc.port, c->rem.port, mg_htonl(s->seq),
+           mg_htonl(s->ack), NULL, 0);
+#endif
+  }
+}
+
+static void rx_tcp(struct mip_if *ifp, struct pkt *pkt) {
+  struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
+#if 0
+  MG_INFO(("%lu %hhu %d", c ? c->id : 0, pkt->tcp->flags, (int) pkt->pay.len));
+#endif
+  if (c != NULL) {
+#if 0
+     MG_DEBUG(("%lu %d %lx:%hx -> %lx:%hx", c->id, (int) pkt->raw.len,
+             pkt->ip->src, pkt->tcp->sport, pkt->ip->dst, pkt->tcp->dport));
+     hexdump(pkt->pay.buf, pkt->pay.len);
+#endif
+    read_conn(c, pkt);
+  } else if ((c = getpeer(ifp->mgr, pkt, true)) == NULL) {
+    tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
+  } else if (pkt->tcp->flags & TH_SYN) {
+    // Use peer's source port as ISN, in order to recognise the handshake
+    uint32_t isn = mg_htonl((uint32_t) mg_ntohs(pkt->tcp->sport));
+    tx_tcp_pkt(ifp, pkt, TH_SYN | TH_ACK, isn, NULL, 0);
+  } else if (pkt->tcp->flags & TH_FIN) {
+    tx_tcp_pkt(ifp, pkt, TH_FIN | TH_ACK, pkt->tcp->ack, NULL, 0);
+  } else if (mg_htonl(pkt->tcp->ack) == mg_htons(pkt->tcp->sport) + 1U) {
+    accept_conn(c, pkt);
+  } else {
+    // MG_DEBUG(("dropped silently.."));
+  }
+}
+
+static void rx_ip(struct mip_if *ifp, struct pkt *pkt) {
+  // MG_DEBUG(("IP %d", (int) pkt->pay.len));
+  if (pkt->ip->proto == 1) {
+    pkt->icmp = (struct icmp *) (pkt->ip + 1);
+    if (pkt->pay.len < sizeof(*pkt->icmp)) return;
+    mkpay(pkt, pkt->icmp + 1);
+    rx_icmp(ifp, pkt);
+  } else if (pkt->ip->proto == 17) {
+    pkt->udp = (struct udp *) (pkt->ip + 1);
+    if (pkt->pay.len < sizeof(*pkt->udp)) return;
+    // MG_DEBUG(("  UDP %u %u -> %u\n", len, NET16(udp->sport),
+    // NET16(udp->dport)));
+    mkpay(pkt, pkt->udp + 1);
+    if (pkt->udp->dport == NET16(68)) {
+      pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
+      mkpay(pkt, pkt->dhcp + 1);
+      rx_dhcp(ifp, pkt);
+    } else {
+      rx_udp(ifp, pkt);
+    }
+  } else if (pkt->ip->proto == 6) {
+    pkt->tcp = (struct tcp *) (pkt->ip + 1);
+    if (pkt->pay.len < sizeof(*pkt->tcp)) return;
+    mkpay(pkt, pkt->tcp + 1);
+    uint16_t iplen = mg_ntohs(pkt->ip->len);
+    uint16_t off = (uint16_t) (sizeof(*pkt->ip) + ((pkt->tcp->off >> 4) * 4U));
+    if (iplen >= off) pkt->pay.len = (size_t) (iplen - off);
+    rx_tcp(ifp, pkt);
+  }
+}
+
+static void rx_ip6(struct mip_if *ifp, struct pkt *pkt) {
+  // MG_DEBUG(("IP %d\n", (int) len));
+  if (pkt->ip6->proto == 1 || pkt->ip6->proto == 58) {
+    pkt->icmp = (struct icmp *) (pkt->ip6 + 1);
+    if (pkt->pay.len < sizeof(*pkt->icmp)) return;
+    mkpay(pkt, pkt->icmp + 1);
+    rx_icmp(ifp, pkt);
+  } else if (pkt->ip->proto == 17) {
+    pkt->udp = (struct udp *) (pkt->ip6 + 1);
+    if (pkt->pay.len < sizeof(*pkt->udp)) return;
+    // MG_DEBUG(("  UDP %u %u -> %u\n", len, NET16(udp->sport),
+    // NET16(udp->dport)));
+    mkpay(pkt, pkt->udp + 1);
+  }
+}
+
+static void mip_rx(struct mip_if *ifp, void *buf, size_t len) {
+  const uint8_t broadcast[] = {255, 255, 255, 255, 255, 255};
+  struct pkt pkt = {.raw = {.buf = (uint8_t *) buf, .len = len}};
+  pkt.eth = (struct eth *) buf;
+  if (pkt.raw.len < sizeof(*pkt.eth)) return;  // Truncated - runt?
+  if (memcmp(pkt.eth->dst, ifp->mac, sizeof(pkt.eth->dst)) != 0 &&
+      memcmp(pkt.eth->dst, broadcast, sizeof(pkt.eth->dst)) != 0) {
+    // Not for us. Drop silently
+  } else if (pkt.eth->type == NET16(0x806)) {
+    pkt.arp = (struct arp *) (pkt.eth + 1);
+    if (sizeof(*pkt.eth) + sizeof(*pkt.arp) > pkt.raw.len) return;  // Truncated
+    rx_arp(ifp, &pkt);
+  } else if (pkt.eth->type == NET16(0x86dd)) {
+    pkt.ip6 = (struct ip6 *) (pkt.eth + 1);
+    if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip6)) return;  // Truncated
+    if ((pkt.ip6->ver >> 4) != 0x6) return;                         // Not IP
+    mkpay(&pkt, pkt.ip6 + 1);
+    rx_ip6(ifp, &pkt);
+  } else if (pkt.eth->type == NET16(0x800)) {
+    pkt.ip = (struct ip *) (pkt.eth + 1);
+    if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return;  // Truncated
+    if ((pkt.ip->ver >> 4) != 4) return;                           // Not IP
+    mkpay(&pkt, pkt.ip + 1);
+    rx_ip(ifp, &pkt);
+  } else {
+    MG_DEBUG(("  Unknown eth type %x\n", NET16(pkt.eth->type)));
+  }
+}
+
+static void mip_poll(struct mip_if *ifp, uint64_t uptime_ms) {
+  ifp->curtime = uptime_ms;
+
+  if (ifp->ip == 0 && uptime_ms > ifp->timer) {
+    tx_dhcp_discover(ifp);          // If IP not configured, send DHCP
+    ifp->timer = uptime_ms + 1000;  // with some interval
+  } else if (ifp->use_dhcp == false && uptime_ms > ifp->timer &&
+             arp_cache_find(ifp, ifp->gw) == NULL) {
+    arp_ask(ifp, ifp->gw);          // If GW's MAC address in not in ARP cache
+    ifp->timer = uptime_ms + 1000;  // send ARP who-has request
+  }
+
+  // Handle physical interface up/down status
+  if (ifp->driver->status) {
+    bool up = ifp->driver->status(ifp->driver->data);
+    bool current = ifp->state != MIP_STATE_DOWN;
+    if (up != current) {
+      ifp->state = up == false     ? MIP_STATE_DOWN
+                   : ifp->use_dhcp ? MIP_STATE_UP
+                                   : MIP_STATE_READY;
+      if (!up && ifp->use_dhcp) ifp->ip = 0;
+      onstatechange(ifp);
+    }
+  }
+
+  // Read data from the network
+  for (;;) {
+    size_t len = ifp->queue.len > 0 ? q_read(&ifp->queue, ifp->rx.buf)
+                                    : ifp->driver->rx(ifp->rx.buf, ifp->rx.len,
+                                                      ifp->driver->data);
+    if (len == 0) break;
+    mip_rx(ifp, ifp->rx.buf, len);
+  }
+}
+
+// This function executes in interrupt context, thus it should copy data
+// somewhere fast. Note that newlib's malloc is not thread safe, thus use
+// our lock-free queue with preallocated buffer to copy data and return asap
+static void on_rx(void *buf, size_t len, void *userdata) {
+  struct mip_if *ifp = (struct mip_if *) userdata;
+  if (!q_write(&ifp->queue, buf, len)) MG_ERROR(("dropped %d", (int) len));
+}
+
+void mip_init(struct mg_mgr *mgr, struct mip_ipcfg *ipcfg,
+              struct mip_driver *driver) {
+  size_t maxpktsize = 1500, qlen = driver->rxcb ? 1024 * 16 : 0;
+  struct mip_if *ifp =
+      (struct mip_if *) calloc(1, sizeof(*ifp) + 2 * maxpktsize + qlen);
+  memcpy(ifp->mac, ipcfg->mac, sizeof(ifp->mac));
+  ifp->use_dhcp = ipcfg->ip == 0;
+  ifp->ip = ipcfg->ip, ifp->mask = ipcfg->mask, ifp->gw = ipcfg->gw;
+  ifp->rx.buf = (uint8_t *) (ifp + 1), ifp->rx.len = maxpktsize;
+  ifp->tx.buf = ifp->rx.buf + maxpktsize, ifp->tx.len = maxpktsize;
+  ifp->driver = driver;
+  ifp->mgr = mgr;
+  ifp->queue.buf = ifp->tx.buf + maxpktsize;
+  ifp->queue.len = qlen;
+  if (driver->init) driver->init(driver->data);
+  if (driver->rxcb) driver->rxcb(on_rx, ifp);
+  mgr->priv = ifp;
+  mgr->extraconnsize = sizeof(struct tcpstate);
+}
+
+int mg_mkpipe(struct mg_mgr *m, mg_event_handler_t fn, void *d, bool udp) {
+  (void) m, (void) fn, (void) d, (void) udp;
+  MG_ERROR(("Not implemented"));
+  return -1;
+}
+
+void mg_connect_resolved(struct mg_connection *c) {
+  struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
+  if (ifp->eport < MIP_ETHEMERAL_PORT) ifp->eport = MIP_ETHEMERAL_PORT;
+  if (c->is_udp) {
+    c->loc.ip = ifp->ip;
+    c->loc.port = mg_htons(ifp->eport++);
+    MG_DEBUG(("%lu %08lx.%hu->%08lx.%hu", c->id, mg_ntohl(c->loc.ip),
+              mg_ntohs(c->loc.port), mg_ntohl(c->rem.ip),
+              mg_ntohs(c->rem.port)));
+    mg_call(c, MG_EV_RESOLVE, NULL);
+    mg_call(c, MG_EV_CONNECT, NULL);
+  } else {
+    mg_error(c, "Not implemented");
+  }
+  c->is_resolving = 0;
+}
+
+bool mg_open_listener(struct mg_connection *c, const char *url) {
+  c->loc.port = mg_htons(mg_url_port(url));
+  return true;
+}
+
+static void write_conn(struct mg_connection *c) {
+  struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
+  struct tcpstate *s = (struct tcpstate *) (c + 1);
+  size_t sent, n = c->send.len, hdrlen = 14 + 24 /*max IP*/ + 60 /*max TCP*/;
+  if (n + hdrlen > ifp->tx.len) n = ifp->tx.len - hdrlen;
+  sent = tx_tcp(ifp, c->rem.ip, TH_PUSH | TH_ACK, c->loc.port, c->rem.port,
+                mg_htonl(s->seq), mg_htonl(s->ack), c->send.buf, n);
+  if (sent > 0) {
+    mg_iobuf_del(&c->send, 0, n);
+    s->seq += (uint32_t) n;
+    mg_call(c, MG_EV_WRITE, &n);
+  }
+}
+
+static void fin_conn(struct mg_connection *c) {
+  struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
+  struct tcpstate *s = (struct tcpstate *) (c + 1);
+  tx_tcp(ifp, c->rem.ip, TH_FIN | TH_ACK, c->loc.port, c->rem.port,
+         mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
+}
+
+void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
+  struct mg_connection *c, *tmp;
+  uint64_t now = mg_millis();
+  mip_poll((struct mip_if *) mgr->priv, now);
+  mg_timer_poll(&mgr->timers, now);
+  for (c = mgr->conns; c != NULL; c = tmp) {
+    tmp = c->next;
+    if (c->send.len > 0) write_conn(c);
+    if (c->is_draining && c->send.len == 0) c->is_closing = 1;
+    if (c->is_closing) {
+      if (c->is_udp == false && c->is_listening == false) fin_conn(c);
+      mg_close_conn(c);
+    }
+  }
+  (void) ms;
+}
+
+bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
+  struct mip_if *ifp = (struct mip_if *) c->mgr->priv;
+  bool res = false;
+  if (ifp->ip == 0) {
+    mg_error(c, "net down");
+  } else if (c->is_udp) {
+    tx_udp(ifp, ifp->ip, c->loc.port, c->rem.ip, c->rem.port, buf, len);
+    res = true;
+  } else {
+    // tx_tdp(ifp, ifp->ip, c->loc.port, c->rem.ip, c->rem.port, buf, len);
+    return mg_iobuf_add(&c->send, c->send.len, buf, len, MG_IO_SIZE);
+  }
+  return res;
+}
+#endif  // MG_ENABLE_MIP
diff --git a/server/mongoose/mongoose.h b/server/mongoose/mongoose.h
index 90a8a1f4062939e4b270fa75c0edb617bc4fc3f2..b8d6b5bc0089b4f2503bf10d9f1e4d060587cc19 100644
--- a/server/mongoose/mongoose.h
+++ b/server/mongoose/mongoose.h
@@ -1,5 +1,5 @@
 // Copyright (c) 2004-2013 Sergey Lyubka
-// Copyright (c) 2013-2021 Cesanta Software Limited
+// Copyright (c) 2013-2022 Cesanta Software Limited
 // All rights reserved
 //
 // This software is dual-licensed: you can redistribute it and/or modify
@@ -14,93 +14,19 @@
 //
 // Alternatively, you can license this software under a commercial
 // license, as set out in https://www.mongoose.ws/licensing/
+//
+// SPDX-License-Identifier: GPL-2.0 or commercial
 
 #ifndef MONGOOSE_H
 #define MONGOOSE_H
 
-#define MG_VERSION "7.5"
+#define MG_VERSION "7.7"
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 
-#ifndef MG_ENABLE_SOCKET
-#define MG_ENABLE_SOCKET 1
-#endif
-
-#ifndef MG_ENABLE_MBEDTLS
-#define MG_ENABLE_MBEDTLS 0
-#endif
-
-#ifndef MG_ENABLE_OPENSSL
-#define MG_ENABLE_OPENSSL 0
-#endif
-
-#ifndef MG_ENABLE_CUSTOM_TLS
-#define MG_ENABLE_CUSTOM_TLS 0
-#endif
-
-#ifndef MG_ENABLE_SSI
-#define MG_ENABLE_SSI 1
-#endif
-
-#ifndef MG_ENABLE_IPV6
-#define MG_ENABLE_IPV6 0
-#endif
-
-#ifndef MG_ENABLE_LOG
-#define MG_ENABLE_LOG 1
-#endif
-
-#ifndef MG_ENABLE_MD5
-#define MG_ENABLE_MD5 0
-#endif
-
-// Set MG_ENABLE_WINSOCK=0 for Win32 builds with external IP stack (like LWIP)
-#ifndef MG_ENABLE_WINSOCK
-#define MG_ENABLE_WINSOCK 1
-#endif
-
-#ifndef MG_ENABLE_DIRLIST
-#define MG_ENABLE_DIRLIST 0
-#endif
-
-#ifndef MG_ENABLE_CUSTOM_RANDOM
-#define MG_ENABLE_CUSTOM_RANDOM 0
-#endif
-
-#ifndef MG_ENABLE_PACKED_FS
-#define MG_ENABLE_PACKED_FS 0
-#endif
-
-// Granularity of the send/recv IO buffer growth
-#ifndef MG_IO_SIZE
-#define MG_IO_SIZE 2048
-#endif
-
-// Maximum size of the recv IO buffer
-#ifndef MG_MAX_RECV_BUF_SIZE
-#define MG_MAX_RECV_BUF_SIZE (3 * 1024 * 1024)
-#endif
-
-#ifndef MG_MAX_HTTP_HEADERS
-#define MG_MAX_HTTP_HEADERS 40
-#endif
-
-#ifndef MG_HTTP_INDEX
-#define MG_HTTP_INDEX "index.html"
-#endif
-
-#ifndef MG_PATH_MAX
-#define MG_PATH_MAX PATH_MAX
-#endif
-
-#ifndef MG_SOCK_LISTEN_BACKLOG_SIZE
-#define MG_SOCK_LISTEN_BACKLOG_SIZE 128
-#endif
-
-
 #define MG_ARCH_CUSTOM 0
 #define MG_ARCH_UNIX 1
 #define MG_ARCH_WIN32 2
@@ -109,6 +35,12 @@ extern "C" {
 #define MG_ARCH_FREERTOS_TCP 5
 #define MG_ARCH_FREERTOS_LWIP 6
 #define MG_ARCH_AZURERTOS 7
+#define MG_ARCH_RTX_LWIP 8
+#define MG_ARCH_ZEPHYR 9
+#define MG_ARCH_NEWLIB 10
+#define MG_ARCH_RTX 11
+#define MG_ARCH_TIRTOS 12
+#define MG_ARCH_RP2040 13
 
 #if !defined(MG_ARCH)
 #if defined(__unix__) || defined(__APPLE__)
@@ -123,21 +55,17 @@ extern "C" {
 #define MG_ARCH MG_ARCH_FREERTOS_TCP
 #elif defined(AZURE_RTOS_THREADX)
 #define MG_ARCH MG_ARCH_AZURERTOS
+#elif defined(__ZEPHYR__)
+#define MG_ARCH MG_ARCH_ZEPHYR
+#elif defined(PICO_TARGET_NAME)
+#define MG_ARCH MG_ARCH_RP2040
 #endif
 
 #if !defined(MG_ARCH)
-#error "MG_ARCH is not specified and we couldn't guess it."
+#error "MG_ARCH is not specified and we couldn't guess it. Set -D MG_ARCH=..."
 #endif
 #endif  // !defined(MG_ARCH)
 
-#if !defined(PRINTF_LIKE)
-#if defined(__GNUC__) || defined(__clang__) || defined(__TI_COMPILER_VERSION__)
-#define PRINTF_LIKE(f, a) __attribute__((format(printf, f, a)))
-#else
-#define PRINTF_LIKE(f, a)
-#endif
-#endif
-
 #if MG_ARCH == MG_ARCH_CUSTOM
 #include <mongoose_custom.h>
 #endif
@@ -150,24 +78,28 @@ extern "C" {
 
 
 
+
+
+
+
 #if MG_ARCH == MG_ARCH_AZURERTOS
 
 #include <stdarg.h>
-#include <stdint.h>
 #include <stdbool.h>
-#include <time.h>
+#include <stdint.h>
 #include <stdio.h>
- 
-#include <tx_api.h>
+#include <time.h>
+
 #include <fx_api.h>
+#include <tx_api.h>
 
-#include <tx_port.h>
-#include <nx_port.h>
 #include <nx_api.h>
 #include <nx_bsd.h>
+#include <nx_port.h>
+#include <tx_port.h>
 
 #ifdef __REDLIB__
-#define va_copy(d,s)__builtin_va_copy(d,s)
+#define va_copy(d, s) __builtin_va_copy(d, s)
 #endif
 
 #define PATH_MAX FX_MAXIMUM_PATH
@@ -175,13 +107,6 @@ extern "C" {
 
 #define socklen_t int
 #define closesocket(x) soc_close(x)
-#define gmtime_r(a, b) gmtime(a)
-#define MG_INT64_FMT "%lld"
-
-static __inline struct tm *localtime_r(time_t *t, struct tm *tm) {
-  (void) tm;
-  return localtime(t);
-}
 
 #undef FOPEN_MAX
 
@@ -205,13 +130,9 @@ static __inline struct tm *localtime_r(time_t *t, struct tm *tm) {
 #include <sys/types.h>
 #include <time.h>
 
-#undef MG_PATH_MAX
-#undef MG_ENABLE_DIRLIST
+#include <esp_timer.h>
 
-#define MG_DIRSEP '/'
-#define MG_INT64_FMT "%lld"
 #define MG_PATH_MAX 128
-#define MG_ENABLE_DIRLIST 1
 
 #endif
 
@@ -237,33 +158,27 @@ static __inline struct tm *localtime_r(time_t *t, struct tm *tm) {
 
 #include <esp_system.h>
 
-#undef MG_PATH_MAX
-#undef MG_ENABLE_DIRLIST
-
-#define MG_DIRSEP '/'
-#define MG_INT64_FMT "%lld"
 #define MG_PATH_MAX 128
-#define MG_ENABLE_DIRLIST 1
 
 #endif
 
 
 #if MG_ARCH == MG_ARCH_FREERTOS_LWIP
 
+#include <ctype.h>
 #include <stdarg.h>
 #include <stdbool.h>
 #include <stdint.h>
-#include <string.h>
 #include <stdio.h>
+#include <string.h>
 
 #if defined(__GNUC__)
 #include <sys/stat.h>
 #include <sys/time.h>
 #else
-typedef long suseconds_t;
 struct timeval {
   time_t tv_sec;
-  suseconds_t tv_usec;
+  long tv_usec;
 };
 #endif
 
@@ -277,27 +192,25 @@ struct timeval {
 #error Set LWIP_SOCKET variable to 1 (in lwipopts.h)
 #endif
 
-#if LWIP_POSIX_SOCKETS_IO_NAMES != 0
-// LWIP_POSIX_SOCKETS_IO_NAMES must be disabled in posix-compatible OS
-// enviroment (freertos mimics to one) otherwise names like `read` and `write`
-// conflict
-#error LWIP_POSIX_SOCKETS_IO_NAMES must be set to 0 (in lwipopts.h) for FreeRTOS
-#endif
-
-#define MG_INT64_FMT "%lld"
-#define MG_DIRSEP '/'
-
 // Re-route calloc/free to the FreeRTOS's functions, don't use stdlib
 static inline void *mg_calloc(int cnt, size_t size) {
   void *p = pvPortMalloc(cnt * size);
-  if (p != NULL) memset(p, 0, size);
+  if (p != NULL) memset(p, 0, size * cnt);
   return p;
 }
 #define calloc(a, b) mg_calloc((a), (b))
 #define free(a) vPortFree(a)
 #define malloc(a) pvPortMalloc(a)
 
-#define gmtime_r(a, b) gmtime(a)
+#define mkdir(a, b) (-1)
+
+#ifndef MG_IO_SIZE
+#define MG_IO_SIZE 512
+#endif
+
+#ifndef MG_PATH_MAX
+#define MG_PATH_MAX 128
+#endif
 
 #endif  // MG_ARCH == MG_ARCH_FREERTOS_LWIP
 
@@ -321,9 +234,6 @@ static inline void *mg_calloc(int cnt, size_t size) {
 #include <FreeRTOS_Sockets.h>
 #include <task.h>
 
-#define MG_INT64_FMT "%lld"
-#define MG_DIRSEP '/'
-
 // Why FreeRTOS-TCP did not implement a clean BSD API, but its own thing
 // with FreeRTOS_ prefix, is beyond me
 #define IPPROTO_TCP FREERTOS_IPPROTO_TCP
@@ -351,25 +261,24 @@ static inline void *mg_calloc(int cnt, size_t size) {
 #define closesocket(x) FreeRTOS_closesocket(x)
 #define gethostbyname(x) FreeRTOS_gethostbyname(x)
 #define getsockname(a, b, c) (-1)
+#define getpeername(a, b, c) 0
 
 // Re-route calloc/free to the FreeRTOS's functions, don't use stdlib
 static inline void *mg_calloc(int cnt, size_t size) {
   void *p = pvPortMalloc(cnt * size);
-  if (p != NULL) memset(p, 0, size);
+  if (p != NULL) memset(p, 0, size * cnt);
   return p;
 }
 #define calloc(a, b) mg_calloc((a), (b))
 #define free(a) vPortFree(a)
 #define malloc(a) pvPortMalloc(a)
-
-#define gmtime_r(a, b) gmtime(a)
+#define mkdir(a, b) (-1)
 
 #if !defined(__GNUC__)
 // copied from GCC on ARM; for some reason useconds are signed
-typedef long suseconds_t;
 struct timeval {
   time_t tv_sec;
-  suseconds_t tv_usec;
+  long tv_usec;
 };
 #endif
 
@@ -389,10 +298,147 @@ struct timeval {
 #endif  // MG_ARCH == MG_ARCH_FREERTOS_TCP
 
 
+#if MG_ARCH == MG_ARCH_NEWLIB
+#define _POSIX_TIMERS
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#define MG_PATH_MAX 100
+#define MG_ENABLE_SOCKET 0
+#define MG_ENABLE_DIRLIST 0
+
+#endif
+
+
+#if MG_ARCH == MG_ARCH_RP2040
+#include <errno.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <pico/stdlib.h>
+int mkdir(const char *, mode_t);
+#endif
+
+
+#if MG_ARCH == MG_ARCH_RTX
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <rl_net.h>
+
+#define MG_ENABLE_CUSTOM_MILLIS 1
+typedef int socklen_t;
+#define closesocket(x) closesocket(x)
+#define mkdir(a, b) (-1)
+#define EWOULDBLOCK BSD_EWOULDBLOCK
+#define EAGAIN BSD_EWOULDBLOCK
+#define EINPROGRESS BSD_EWOULDBLOCK
+#define EINTR BSD_EWOULDBLOCK
+#define ECONNRESET BSD_ECONNRESET
+#define EPIPE BSD_ECONNRESET
+#define TCP_NODELAY SO_KEEPALIVE
+
+#endif
+
+
+#if MG_ARCH == MG_ARCH_RTX_LWIP
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#if defined(__GNUC__)
+#include <sys/stat.h>
+#include <sys/time.h>
+#else
+struct timeval {
+  time_t tv_sec;
+  long tv_usec;
+};
+#endif
+
+#include <lwip/sockets.h>
+
+#if LWIP_SOCKET != 1
+// Sockets support disabled in LWIP by default
+#error Set LWIP_SOCKET variable to 1 (in lwipopts.h)
+#endif
+
+#define mkdir(a, b) (-1)
+
+#ifndef MG_IO_SIZE
+#define MG_IO_SIZE 512
+#endif
+
+#ifndef MG_PATH_MAX
+#define MG_PATH_MAX 128
+#endif
+
+
+#endif
+
+
+#if MG_ARCH == MG_ARCH_TIRTOS
+
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <time.h>
+#include <errno.h>
+
+#include <sys/socket.h>
+
+extern int SockStatus(SOCKET hSock, int request, int *results );
+extern int SockSet(SOCKET hSock, int Type, int Prop, void *pbuf, int size);
+
+#endif
+
+
 #if MG_ARCH == MG_ARCH_UNIX
 
 #define _DARWIN_UNLIMITED_SELECT 1  // No limit on file descriptors
 
+#if defined(__APPLE__)
+#include <mach/mach_time.h>
+#endif
+
+#if !defined(MG_ENABLE_POLL) && (defined(__linux__) || defined(__APPLE__))
+#define MG_ENABLE_POLL 1
+#endif
+
 #include <arpa/inet.h>
 #include <ctype.h>
 #include <dirent.h>
@@ -401,6 +447,7 @@ struct timeval {
 #include <inttypes.h>
 #include <limits.h>
 #include <netdb.h>
+#include <netinet/in.h>
 #include <netinet/tcp.h>
 #include <signal.h>
 #include <stdarg.h>
@@ -410,7 +457,11 @@ struct timeval {
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#if defined(MG_ENABLE_POLL) && MG_ENABLE_POLL
+#include <poll.h>
+#else
 #include <sys/select.h>
+#endif
 #include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/time.h>
@@ -418,10 +469,9 @@ struct timeval {
 #include <time.h>
 #include <unistd.h>
 
-#define MG_DIRSEP '/'
-#define MG_INT64_FMT "%" PRId64
-#undef MG_ENABLE_DIRLIST
+#ifndef MG_ENABLE_DIRLIST
 #define MG_ENABLE_DIRLIST 1
+#endif
 
 #endif
 
@@ -441,9 +491,11 @@ struct timeval {
 #endif
 
 #include <ctype.h>
+#include <direct.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <limits.h>
+#include <signal.h>
 #include <stdarg.h>
 #include <stddef.h>
 #include <stdio.h>
@@ -470,6 +522,8 @@ typedef enum { false = 0, true = 1 } bool;
 #include <ws2tcpip.h>
 #endif
 
+#include <process.h>
+#include <winerror.h>
 #include <winsock2.h>
 
 // Protect from calls like std::snprintf in app code
@@ -482,7 +536,6 @@ typedef enum { false = 0, true = 1 } bool;
 #endif
 #endif
 
-typedef unsigned suseconds_t;
 typedef int socklen_t;
 #define MG_DIRSEP '\\'
 #ifndef PATH_MAX
@@ -495,6 +548,10 @@ typedef int socklen_t;
 #define EWOULDBLOCK WSAEWOULDBLOCK
 #endif
 
+#define realpath(a, b) _fullpath((b), (a), MG_PATH_MAX)
+#define sleep(x) Sleep(x)
+#define mkdir(a, b) _mkdir(a)
+
 #ifndef va_copy
 #ifdef __va_copy
 #define va_copy __va_copy
@@ -506,27 +563,151 @@ typedef int socklen_t;
 #define S_ISDIR(x) (((x) &_S_IFMT) == _S_IFDIR)
 #endif
 
-#define MG_INT64_FMT "%I64d"
-
-#undef MG_ENABLE_DIRLIST
+#ifndef MG_ENABLE_DIRLIST
 #define MG_ENABLE_DIRLIST 1
+#endif
 
-// https://lgtm.com/rules/2154840805/ -gmtime, localtime, ctime and asctime
-static __inline struct tm *gmtime_r(time_t *t, struct tm *tm) {
-  (void) tm;
-  return gmtime(t);
-}
+#endif
 
-static __inline struct tm *localtime_r(time_t *t, struct tm *tm) {
-  (void) tm;
-  return localtime(t);
-}
 
-#endif
+#if MG_ARCH == MG_ARCH_ZEPHYR
 
+#include <zephyr.h>
 
 #include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <net/socket.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
+#include <sys/types.h>
+#include <time.h>
+
+#define MG_PUTCHAR(x) printk("%c", x)
+#define strerror(x) zsock_gai_strerror(x)
+#define FD_CLOEXEC 0
+#define F_SETFD 0
+#define MG_ENABLE_SSI 0
+
+int rand(void);
+int sscanf(const char *, const char *, ...);
+
+#endif
+
+
+#ifndef MG_ENABLE_LOG
+#define MG_ENABLE_LOG 1
+#endif
+
+#ifndef MG_ENABLE_MIP
+#define MG_ENABLE_MIP 0
+#endif
+
+#ifndef MG_ENABLE_POLL
+#define MG_ENABLE_POLL 0
+#endif
+
+#ifndef MG_ENABLE_FATFS
+#define MG_ENABLE_FATFS 0
+#endif
+
+#ifndef MG_ENABLE_SOCKET
+#define MG_ENABLE_SOCKET 1
+#endif
+
+#ifndef MG_ENABLE_MBEDTLS
+#define MG_ENABLE_MBEDTLS 0
+#endif
+
+#ifndef MG_ENABLE_OPENSSL
+#define MG_ENABLE_OPENSSL 0
+#endif
+
+#ifndef MG_ENABLE_CUSTOM_TLS
+#define MG_ENABLE_CUSTOM_TLS 0
+#endif
+
+#ifndef MG_ENABLE_SSI
+#define MG_ENABLE_SSI 0
+#endif
+
+#ifndef MG_ENABLE_IPV6
+#define MG_ENABLE_IPV6 0
+#endif
+
+#ifndef MG_ENABLE_MD5
+#define MG_ENABLE_MD5 0
+#endif
+
+// Set MG_ENABLE_WINSOCK=0 for Win32 builds with external IP stack (like LWIP)
+#ifndef MG_ENABLE_WINSOCK
+#define MG_ENABLE_WINSOCK 1
+#endif
+
+#ifndef MG_ENABLE_DIRLIST
+#define MG_ENABLE_DIRLIST 0
+#endif
+
+#ifndef MG_ENABLE_CUSTOM_RANDOM
+#define MG_ENABLE_CUSTOM_RANDOM 0
+#endif
+
+#ifndef MG_ENABLE_CUSTOM_MILLIS
+#define MG_ENABLE_CUSTOM_MILLIS 0
+#endif
+
+#ifndef MG_ENABLE_PACKED_FS
+#define MG_ENABLE_PACKED_FS 0
+#endif
+
+// Granularity of the send/recv IO buffer growth
+#ifndef MG_IO_SIZE
+#define MG_IO_SIZE 2048
+#endif
+
+// Maximum size of the recv IO buffer
+#ifndef MG_MAX_RECV_SIZE
+#define MG_MAX_RECV_SIZE (3 * 1024 * 1024)
+#endif
+
+#ifndef MG_MAX_HTTP_HEADERS
+#define MG_MAX_HTTP_HEADERS 40
+#endif
+
+#ifndef MG_HTTP_INDEX
+#define MG_HTTP_INDEX "index.html"
+#endif
+
+#ifndef MG_PATH_MAX
+#ifdef PATH_MAX
+#define MG_PATH_MAX PATH_MAX
+#else
+#define MG_PATH_MAX 128
+#endif
+#endif
+
+#ifndef MG_SOCK_LISTEN_BACKLOG_SIZE
+#define MG_SOCK_LISTEN_BACKLOG_SIZE 3
+#endif
+
+#ifndef MG_DIRSEP
+#define MG_DIRSEP '/'
+#endif
+
+#ifndef MG_ENABLE_FILE
+#if defined(FOPEN_MAX)
+#define MG_ENABLE_FILE 1
+#else
+#define MG_ENABLE_FILE 0
+#endif
+#endif
+
+
+
 
 struct mg_str {
   const char *ptr;  // Pointer to string data
@@ -536,6 +717,9 @@ struct mg_str {
 #define MG_NULL_STR \
   { NULL, 0 }
 
+#define MG_C_STR(a) \
+  { (a), sizeof(a) - 1 }
+
 // Using macro to avoid shadowing C++ struct constructor, see #1298
 #define mg_str(s) mg_str_s(s)
 
@@ -550,93 +734,142 @@ int mg_strcmp(const struct mg_str str1, const struct mg_str str2);
 struct mg_str mg_strstrip(struct mg_str s);
 struct mg_str mg_strdup(const struct mg_str s);
 const char *mg_strstr(const struct mg_str haystack, const struct mg_str needle);
+bool mg_match(struct mg_str str, struct mg_str pattern, struct mg_str *caps);
+bool mg_globmatch(const char *pattern, size_t plen, const char *s, size_t n);
+bool mg_commalist(struct mg_str *s, struct mg_str *k, struct mg_str *v);
+bool mg_split(struct mg_str *s, struct mg_str *k, struct mg_str *v, char delim);
+char *mg_hex(const void *buf, size_t len, char *dst);
+void mg_unhex(const char *buf, size_t len, unsigned char *to);
+unsigned long mg_unhexn(const char *s, size_t len);
+int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip);
+int64_t mg_to64(struct mg_str str);
+uint64_t mg_tou64(struct mg_str str);
+size_t mg_lld(char *buf, int64_t val, bool is_signed, bool is_hex);
+double mg_atod(const char *buf, int len, int *numlen);
+size_t mg_dtoa(char *buf, size_t len, double d, int width);
+char *mg_remove_double_dots(char *s);
+
+typedef void (*mg_pc_t)(char, void *);                  // Custom putchar
+typedef size_t (*mg_pm_t)(mg_pc_t, void *, va_list *);  // %M printer
+void mg_putchar_realloc(char ch, void *param);          // Print to malloced str
+void mg_putchar_iobuf(char ch, void *param);            // Print to iobuf
 
+size_t mg_vrprintf(void (*)(char, void *), void *, const char *fmt, va_list *);
+size_t mg_rprintf(void (*fn)(char, void *), void *, const char *fmt, ...);
+size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list *ap);
+size_t mg_snprintf(char *, size_t, const char *fmt, ...);
+size_t mg_asprintf(char **, size_t, const char *fmt, ...);
+size_t mg_vasprintf(char **buf, size_t size, const char *fmt, va_list ap);
+char *mg_mprintf(const char *fmt, ...);
+char *mg_vmprintf(const char *fmt, va_list ap);
 
 
 
 
+
+enum { MG_LL_NONE, MG_LL_ERROR, MG_LL_INFO, MG_LL_DEBUG, MG_LL_VERBOSE };
+void mg_log(const char *fmt, ...);
+bool mg_log_prefix(int ll, const char *file, int line, const char *fname);
+void mg_log_set(const char *spec);
+void mg_hexdump(const void *buf, size_t len);
+void mg_log_set_fn(void (*logfunc)(unsigned char ch));
+
 #if MG_ENABLE_LOG
-#define LOG(level, args)                                                   \
+#define MG_LOG(level, args)                                                \
   do {                                                                     \
     if (mg_log_prefix((level), __FILE__, __LINE__, __func__)) mg_log args; \
   } while (0)
-enum { LL_NONE, LL_ERROR, LL_INFO, LL_DEBUG, LL_VERBOSE_DEBUG };
-bool mg_log_prefix(int ll, const char *file, int line, const char *fname);
-void mg_log(const char *fmt, ...) PRINTF_LIKE(1, 2);
-void mg_log_set(const char *spec);
-void mg_log_set_callback(void (*fn)(const void *, size_t, void *), void *param);
 #else
-#define LOG(level, args) (void) 0
-#define mg_log_set(x) (void) (x)
+#define MG_LOG(level, args) \
+  do {                      \
+    if (0) mg_log args;     \
+  } while (0)
 #endif
 
+#define MG_ERROR(args) MG_LOG(MG_LL_ERROR, args)
+#define MG_INFO(args) MG_LOG(MG_LL_INFO, args)
+#define MG_DEBUG(args) MG_LOG(MG_LL_DEBUG, args)
+#define MG_VERBOSE(args) MG_LOG(MG_LL_VERBOSE, args)
+
+
+
 
 struct mg_timer {
-  unsigned long period_ms;  // Timer period in milliseconds
-  unsigned long expire;     // Expiration timestamp in milliseconds
+  uint64_t period_ms;       // Timer period in milliseconds
+  uint64_t prev_ms;         // Timestamp of a previous poll
+  uint64_t expire;          // Expiration timestamp in milliseconds
   unsigned flags;           // Possible flags values below
-#define MG_TIMER_REPEAT 1   // Call function periodically, otherwise run once
+#define MG_TIMER_ONCE 0     // Call function once
+#define MG_TIMER_REPEAT 1   // Call function periodically
 #define MG_TIMER_RUN_NOW 2  // Call immediately when timer is set
   void (*fn)(void *);       // Function to call
   void *arg;                // Function argument
-  struct mg_timer *next;    // Linkage in g_timers list
+  struct mg_timer *next;    // Linkage
+};
+
+void mg_timer_init(struct mg_timer **head, struct mg_timer *timer,
+                   uint64_t milliseconds, unsigned flags, void (*fn)(void *),
+                   void *arg);
+void mg_timer_free(struct mg_timer **head, struct mg_timer *);
+void mg_timer_poll(struct mg_timer **head, uint64_t new_ms);
+
+
+
+
+
+enum { MG_FS_READ = 1, MG_FS_WRITE = 2, MG_FS_DIR = 4 };
+
+// Filesystem API functions
+// st() returns MG_FS_* flags and populates file size and modification time
+// ls() calls fn() for every directory entry, allowing to list a directory
+//
+// NOTE: UNIX-style shorthand names for the API functions are deliberately
+// chosen to avoid conflicts with some libraries that make macros for e.g.
+// stat(), write(), read() calls.
+struct mg_fs {
+  int (*st)(const char *path, size_t *size, time_t *mtime);  // stat file
+  void (*ls)(const char *path, void (*fn)(const char *, void *), void *);
+  void *(*op)(const char *path, int flags);             // Open file
+  void (*cl)(void *fd);                                 // Close file
+  size_t (*rd)(void *fd, void *buf, size_t len);        // Read file
+  size_t (*wr)(void *fd, const void *buf, size_t len);  // Write file
+  size_t (*sk)(void *fd, size_t offset);                // Set file position
+  bool (*mv)(const char *from, const char *to);         // Rename file
+  bool (*rm)(const char *path);                         // Delete file
+  bool (*mkd)(const char *path);                        // Create directory
+};
+
+extern struct mg_fs mg_fs_posix;   // POSIX open/close/read/write/seek
+extern struct mg_fs mg_fs_packed;  // Packed FS, see examples/device-dashboard
+extern struct mg_fs mg_fs_fat;     // FAT FS
+
+// File descriptor
+struct mg_fd {
+  void *fd;
+  struct mg_fs *fs;
 };
 
-extern struct mg_timer *g_timers;  // Global list of timers
+struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags);
+void mg_fs_close(struct mg_fd *fd);
+char *mg_file_read(struct mg_fs *fs, const char *path, size_t *size);
+bool mg_file_write(struct mg_fs *fs, const char *path, const void *, size_t);
+bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...);
 
-void mg_timer_init(struct mg_timer *, unsigned long ms, unsigned,
-                   void (*fn)(void *), void *);
-void mg_timer_free(struct mg_timer *);
-void mg_timer_poll(unsigned long current_time_ms);
 
 
 
 
 
-char *mg_file_read(const char *path, size_t *size);
-bool mg_file_write(const char *path, const void *buf, size_t len);
-bool mg_file_printf(const char *path, const char *fmt, ...);
 void mg_random(void *buf, size_t len);
-bool mg_globmatch(const char *pattern, size_t plen, const char *s, size_t n);
-bool mg_commalist(struct mg_str *s, struct mg_str *k, struct mg_str *v);
+char *mg_random_str(char *buf, size_t len);
 uint16_t mg_ntohs(uint16_t net);
 uint32_t mg_ntohl(uint32_t net);
 uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len);
-char *mg_hexdump(const void *buf, size_t len);
-char *mg_hex(const void *buf, size_t len, char *dst);
-void mg_unhex(const char *buf, size_t len, unsigned char *to);
-unsigned long mg_unhexn(const char *s, size_t len);
-int mg_asprintf(char **buf, size_t size, const char *fmt, ...);
-int mg_vasprintf(char **buf, size_t size, const char *fmt, va_list ap);
-int64_t mg_to64(struct mg_str str);
-int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip);
-double mg_time(void);
-unsigned long mg_millis(void);
-void mg_usleep(unsigned long usecs);
+uint64_t mg_millis(void);
 
 #define mg_htons(x) mg_ntohs(x)
 #define mg_htonl(x) mg_ntohl(x)
 
-#ifndef EXTERN_C
-#ifdef __cplusplus
-#define EXTERN_C extern "C"
-#else
-#define EXTERN_C
-#endif
-#endif
-
-// Expands to a string representation of its argument: e.g.
-// MG_STRINGIFY_LITERAL(5) expands to "5"
-#if !defined(_MSC_VER) || _MSC_VER >= 1900
-#define MG_STRINGIFY_LITERAL(...) #__VA_ARGS__
-#else
-#define MG_STRINGIFY_LITERAL(x) #x
-#endif
-// Expands to a string representation of its argument, which can be a macro:
-// #define FOO 123
-// MG_STRINGIFY_MACRO(FOO)  // Expands to 123
-#define MG_STRINGIFY_MACRO(x) MG_STRINGIFY_LITERAL(x)
-
 // Linked list management macros
 #define LIST_ADD_HEAD(type_, head_, elem_) \
   do {                                     \
@@ -660,33 +893,6 @@ void mg_usleep(unsigned long usecs);
 
 
 
-
-enum { MG_FS_READ = 1, MG_FS_WRITE = 2, MG_FS_DIR = 4 };
-
-// Filesystem API functions
-// stat() returns MG_FS_* flags and populates file size and modification time
-// list() calls fn() for every directory entry, allowing to list a directory
-struct mg_fs {
-  int (*stat)(const char *path, size_t *size, time_t *mtime);
-  void (*list)(const char *path, void (*fn)(const char *, void *), void *);
-  struct mg_fd *(*open)(const char *path, int flags);      // Open file
-  void (*close)(struct mg_fd *fd);                         // Close file
-  size_t (*read)(void *fd, void *buf, size_t len);         // Read file
-  size_t (*write)(void *fd, const void *buf, size_t len);  // Write file
-  size_t (*seek)(void *fd, size_t offset);                 // Set file position
-};
-
-// File descriptor
-struct mg_fd {
-  void *fd;
-  struct mg_fs *fs;
-};
-
-extern struct mg_fs mg_fs_posix;   // POSIX open/close/read/write/seek
-extern struct mg_fs mg_fs_packed;  // Packed FS, see examples/complete
-
-
-
 unsigned short mg_url_port(const char *url);
 int mg_url_is_ssl(const char *url);
 struct mg_str mg_url_host(const char *url);
@@ -695,7 +901,7 @@ struct mg_str mg_url_pass(const char *url);
 const char *mg_url_uri(const char *url);
 
 
-#include <stddef.h>
+
 
 struct mg_iobuf {
   unsigned char *buf;  // Pointer to stored data
@@ -739,9 +945,6 @@ typedef struct {
 void mg_sha1_init(mg_sha1_ctx *);
 void mg_sha1_update(mg_sha1_ctx *, const unsigned char *data, size_t len);
 void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *);
-void mg_hmac_sha1(const unsigned char *key, size_t key_len,
-                  const unsigned char *text, size_t text_len,
-                  unsigned char out[20]);
 
 
 struct mg_connection;
@@ -753,7 +956,7 @@ void mg_error(struct mg_connection *c, const char *fmt, ...);
 enum {
   MG_EV_ERROR,       // Error                        char *error_message
   MG_EV_OPEN,        // Connection created           NULL
-  MG_EV_POLL,        // mg_mgr_poll iteration        unsigned long *millis
+  MG_EV_POLL,        // mg_mgr_poll iteration        uint64_t *milliseconds
   MG_EV_RESOLVE,     // Host name is resolved        NULL
   MG_EV_CONNECT,     // Connection established       NULL
   MG_EV_ACCEPT,      // Connection accepted          NULL
@@ -768,7 +971,7 @@ enum {
   MG_EV_MQTT_CMD,    // MQTT low-level command       struct mg_mqtt_message *
   MG_EV_MQTT_MSG,    // MQTT PUBLISH received        struct mg_mqtt_message *
   MG_EV_MQTT_OPEN,   // MQTT CONNACK received        int *connack_status_code
-  MG_EV_SNTP_TIME,   // SNTP time received           struct timeval *
+  MG_EV_SNTP_TIME,   // SNTP time received           uint64_t *milliseconds
   MG_EV_USER,        // Starting ID for user events
 };
 
@@ -778,6 +981,7 @@ enum {
 
 
 
+
 struct mg_dns {
   const char *url;          // DNS server URL
   struct mg_connection *c;  // DNS server connection
@@ -795,8 +999,14 @@ struct mg_mgr {
   struct mg_dns dns4;           // DNS for IPv4
   struct mg_dns dns6;           // DNS for IPv6
   int dnstimeout;               // DNS resolve timeout in milliseconds
+  bool use_dns6;                // Use DNS6 server by default, see #1532
   unsigned long nextid;         // Next connection ID
   void *userdata;               // Arbitrary user data pointer
+  uint16_t mqtt_id;             // MQTT IDs for pub/sub
+  void *active_dns_requests;    // DNS requests in progress
+  struct mg_timer *timers;      // Active timers
+  void *priv;                   // Used by the experimental stack
+  size_t extraconnsize;         // Used by the experimental stack
 #if MG_ARCH == MG_ARCH_FREERTOS_TCP
   SocketSet_t ss;  // NOTE(lsm): referenced from socket struct
 #endif
@@ -805,7 +1015,8 @@ struct mg_mgr {
 struct mg_connection {
   struct mg_connection *next;  // Linkage in struct mg_mgr :: connections
   struct mg_mgr *mgr;          // Our container
-  struct mg_addr peer;         // Remote address. For listeners, local address
+  struct mg_addr loc;          // Local address
+  struct mg_addr rem;          // Remote address
   void *fd;                    // Connected socket, or LWIP data
   unsigned long id;            // Auto-incrementing unique connection ID
   struct mg_iobuf recv;        // Incoming data
@@ -825,9 +1036,11 @@ struct mg_connection {
   unsigned is_tls_hs : 1;      // TLS handshake is in progress
   unsigned is_udp : 1;         // UDP connection
   unsigned is_websocket : 1;   // WebSocket connection
+  unsigned is_mqtt5 : 1;       // For MQTT connection, v5 indicator
   unsigned is_hexdumping : 1;  // Hexdump in/out traffic
   unsigned is_draining : 1;    // Send remaining data, then close and free
   unsigned is_closing : 1;     // Close and free the connection immediately
+  unsigned is_full : 1;        // Stop reads, until cleared
   unsigned is_readable : 1;    // Connection is ready to read
   unsigned is_writable : 1;    // Connection is ready to write
 };
@@ -840,16 +1053,24 @@ struct mg_connection *mg_listen(struct mg_mgr *, const char *url,
                                 mg_event_handler_t fn, void *fn_data);
 struct mg_connection *mg_connect(struct mg_mgr *, const char *url,
                                  mg_event_handler_t fn, void *fn_data);
+struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd,
+                                mg_event_handler_t fn, void *fn_data);
 void mg_connect_resolved(struct mg_connection *);
 bool mg_send(struct mg_connection *, const void *, size_t);
-int mg_printf(struct mg_connection *, const char *fmt, ...);
-int mg_vprintf(struct mg_connection *, const char *fmt, va_list ap);
-char *mg_straddr(struct mg_connection *, char *, size_t);
+size_t mg_printf(struct mg_connection *, const char *fmt, ...);
+size_t mg_vprintf(struct mg_connection *, const char *fmt, va_list ap);
+char *mg_straddr(struct mg_addr *, char *, size_t);
 bool mg_aton(struct mg_str str, struct mg_addr *addr);
 char *mg_ntoa(const struct mg_addr *addr, char *buf, size_t len);
+int mg_mkpipe(struct mg_mgr *, mg_event_handler_t, void *, bool udp);
+
+// These functions are used to integrate with custom network stacks
+struct mg_connection *mg_alloc_conn(struct mg_mgr *);
+void mg_close_conn(struct mg_connection *c);
+bool mg_open_listener(struct mg_connection *c, const char *url);
+struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds,
+                              unsigned flags, void (*fn)(void *), void *arg);
 
-struct mg_connection *mg_mkpipe(struct mg_mgr *, mg_event_handler_t, void *);
-void mg_mgr_wakeup(struct mg_connection *pipe);
 
 
 
@@ -877,6 +1098,7 @@ struct mg_http_serve_opts {
   const char *ssi_pattern;    // SSI file name pattern, e.g. #.shtml
   const char *extra_headers;  // Extra HTTP headers to add in responses
   const char *mime_types;     // Extra mime types, ext1=type1,ext2=type2,..
+  const char *page404;        // Path to the 404 page, or NULL by default
   struct mg_fs *fs;           // Filesystem implementation. Use NULL for POSIX
 };
 
@@ -897,22 +1119,24 @@ struct mg_connection *mg_http_listen(struct mg_mgr *, const char *url,
 struct mg_connection *mg_http_connect(struct mg_mgr *, const char *url,
                                       mg_event_handler_t fn, void *fn_data);
 void mg_http_serve_dir(struct mg_connection *, struct mg_http_message *hm,
-                       struct mg_http_serve_opts *opts);
+                       const struct mg_http_serve_opts *);
 void mg_http_serve_file(struct mg_connection *, struct mg_http_message *hm,
-                        const char *path, struct mg_http_serve_opts *opts);
+                        const char *path, const struct mg_http_serve_opts *);
 void mg_http_reply(struct mg_connection *, int status_code, const char *headers,
                    const char *body_fmt, ...);
 struct mg_str *mg_http_get_header(struct mg_http_message *, const char *name);
+struct mg_str mg_http_var(struct mg_str buf, struct mg_str name);
 int mg_http_get_var(const struct mg_str *, const char *name, char *, size_t);
 int mg_url_decode(const char *s, size_t n, char *to, size_t to_len, int form);
 size_t mg_url_encode(const char *s, size_t n, char *buf, size_t len);
 void mg_http_creds(struct mg_http_message *, char *, size_t, char *, size_t);
 bool mg_http_match_uri(const struct mg_http_message *, const char *glob);
-int mg_http_upload(struct mg_connection *, struct mg_http_message *hm,
-                   const char *dir);
+long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm,
+                    struct mg_fs *fs, const char *path, size_t max_size);
 void mg_http_bauth(struct mg_connection *, const char *user, const char *pass);
 struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v);
 size_t mg_http_next_multipart(struct mg_str, size_t, struct mg_http_part *);
+int mg_http_status(const struct mg_http_message *hm);
 
 
 void mg_http_serve_ssi(struct mg_connection *c, const char *root,
@@ -930,37 +1154,30 @@ struct mg_tls_opts {
   const char *certkey;    // Certificate key
   const char *ciphers;    // Cipher list
   struct mg_str srvname;  // If not empty, enables server name verification
+  struct mg_fs *fs;       // FS API for reading certificate files
 };
 
-void mg_tls_init(struct mg_connection *, struct mg_tls_opts *);
+void mg_tls_init(struct mg_connection *, const struct mg_tls_opts *);
 void mg_tls_free(struct mg_connection *);
 long mg_tls_send(struct mg_connection *, const void *buf, size_t len);
 long mg_tls_recv(struct mg_connection *, void *buf, size_t len);
+size_t mg_tls_pending(struct mg_connection *);
 void mg_tls_handshake(struct mg_connection *);
 
 
-#if MG_ENABLE_MBEDTLS
 
 
 
 
+
+#if MG_ENABLE_MBEDTLS
 #include <mbedtls/debug.h>
+#include <mbedtls/net_sockets.h>
 #include <mbedtls/ssl.h>
 
-#if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000
-#define RNG , rng_get, NULL
-#else
-#define RNG
-#endif
-
-// Different versions have those in different files, so declare here
-EXTERN_C int mbedtls_net_recv(void *, unsigned char *, size_t);
-EXTERN_C int mbedtls_net_send(void *, const unsigned char *, size_t);
-
 struct mg_tls {
   char *cafile;             // CA certificate path
   mbedtls_x509_crt ca;      // Parsed CA certificate
-  mbedtls_x509_crl crl;     // Parsed Certificate Revocation List
   mbedtls_x509_crt cert;    // Parsed certificate
   mbedtls_ssl_context ssl;  // SSL/TLS context
   mbedtls_ssl_config conf;  // SSL-TLS config
@@ -1002,14 +1219,16 @@ void mg_ws_upgrade(struct mg_connection *, struct mg_http_message *,
                    const char *fmt, ...);
 size_t mg_ws_send(struct mg_connection *, const char *buf, size_t len, int op);
 size_t mg_ws_wrap(struct mg_connection *, size_t len, int op);
+size_t mg_ws_printf(struct mg_connection *c, int op, const char *fmt, ...);
+size_t mg_ws_vprintf(struct mg_connection *c, int op, const char *fmt, va_list);
 
 
 
 
 struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const char *url,
                                       mg_event_handler_t fn, void *fn_data);
-void mg_sntp_send(struct mg_connection *c, unsigned long utc);
-int mg_sntp_parse(const unsigned char *buf, size_t len, struct timeval *tv);
+void mg_sntp_request(struct mg_connection *c);
+int64_t mg_sntp_parse(const unsigned char *buf, size_t len);
 
 
 
@@ -1029,6 +1248,9 @@ int mg_sntp_parse(const unsigned char *buf, size_t len, struct timeval *tv);
 #define MQTT_CMD_PINGREQ 12
 #define MQTT_CMD_PINGRESP 13
 #define MQTT_CMD_DISCONNECT 14
+#define MQTT_CMD_AUTH 15
+
+enum { MQTT_OK, MQTT_INCOMPLETE, MQTT_MALFORMED };
 
 struct mg_mqtt_opts {
   struct mg_str user;          // Username, can be empty
@@ -1036,10 +1258,11 @@ struct mg_mqtt_opts {
   struct mg_str client_id;     // Client ID
   struct mg_str will_topic;    // Will topic
   struct mg_str will_message;  // Will message
-  uint8_t qos;                 // Quality of service
+  uint8_t will_qos;            // Will message quality of service
+  uint8_t version;             // Can be 4 (3.1.1), or 5. If 0, assume 4.
+  uint16_t keepalive;          // Keep-alive timer in seconds
   bool will_retain;            // Retain last will
   bool clean;                  // Use clean session, 0 or 1
-  uint16_t keepalive;          // Keep-alive timer in seconds
 };
 
 struct mg_mqtt_message {
@@ -1053,15 +1276,15 @@ struct mg_mqtt_message {
 };
 
 struct mg_connection *mg_mqtt_connect(struct mg_mgr *, const char *url,
-                                      struct mg_mqtt_opts *opts,
+                                      const struct mg_mqtt_opts *opts,
                                       mg_event_handler_t fn, void *fn_data);
 struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url,
                                      mg_event_handler_t fn, void *fn_data);
-void mg_mqtt_login(struct mg_connection *c, struct mg_mqtt_opts *opts);
-void mg_mqtt_pub(struct mg_connection *c, struct mg_str *topic,
-                 struct mg_str *data, int qos, bool retain);
-void mg_mqtt_sub(struct mg_connection *, struct mg_str *topic, int qos);
-int mg_mqtt_parse(const uint8_t *buf, size_t len, struct mg_mqtt_message *m);
+void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts *opts);
+void mg_mqtt_pub(struct mg_connection *c, struct mg_str topic,
+                 struct mg_str data, int qos, bool retain);
+void mg_mqtt_sub(struct mg_connection *, struct mg_str topic, int qos);
+int mg_mqtt_parse(const uint8_t *, size_t, uint8_t, struct mg_mqtt_message *);
 void mg_mqtt_send_header(struct mg_connection *, uint8_t cmd, uint8_t flags,
                          uint32_t len);
 size_t mg_mqtt_next_sub(struct mg_mqtt_message *msg, struct mg_str *topic,
@@ -1104,12 +1327,53 @@ struct mg_dns_rr {
   uint16_t alen;    // Address length
 };
 
-void mg_resolve(struct mg_connection *, struct mg_str *, int);
+void mg_resolve(struct mg_connection *, const char *url);
 void mg_resolve_cancel(struct mg_connection *);
 bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *);
 size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs,
                        bool is_question, struct mg_dns_rr *);
-size_t mg_dns_decode_name(const uint8_t *, size_t, size_t, char *, size_t);
+
+
+
+
+
+#ifndef MG_JSON_MAX_DEPTH
+#define MG_JSON_MAX_DEPTH 30
+#endif
+
+// Error return values - negative. Successful returns are >= 0
+enum { MG_JSON_TOO_DEEP = -1, MG_JSON_INVALID = -2, MG_JSON_NOT_FOUND = -3 };
+int mg_json_get(const char *buf, int len, const char *path, int *toklen);
+
+bool mg_json_get_num(struct mg_str json, const char *path, double *v);
+bool mg_json_get_bool(struct mg_str json, const char *path, bool *v);
+long mg_json_get_long(struct mg_str json, const char *path, long dflt);
+char *mg_json_get_str(struct mg_str json, const char *path);
+char *mg_json_get_hex(struct mg_str json, const char *path, int *len);
+char *mg_json_get_b64(struct mg_str json, const char *path, int *len);
+
+
+
+
+
+struct mip_driver {
+  void *data;                                       // Driver-specific data
+  void (*init)(void *data);                         // Initialise driver
+  size_t (*tx)(const void *, size_t, void *data);   // Transmit frame
+  size_t (*rx)(void *buf, size_t len, void *data);  // Receive frame (polling)
+  bool (*status)(void *data);                       // Up/down status
+  // Set receive callback for interrupt-driven drivers
+  void (*rxcb)(void (*fn)(void *buf, size_t len, void *rxdata), void *rxdata);
+};
+
+struct mip_ipcfg {
+  uint8_t mac[6];         // MAC address. Must not be 0
+  uint32_t ip, mask, gw;  // IP, netmask, GW. If IP is 0, DHCP is used
+};
+
+void mip_init(struct mg_mgr *, struct mip_ipcfg *, struct mip_driver *);
+
+extern struct mip_driver mip_driver_stm32;
 
 #ifdef __cplusplus
 }