]> gitweb.ps.run Git - chirp/commitdiff
delete files
authorpatrick-scho <patrick.schoenberger@posteo.de>
Sat, 2 Nov 2024 11:02:50 +0000 (12:02 +0100)
committerpatrick-scho <patrick.schoenberger@posteo.de>
Sat, 2 Nov 2024 11:02:50 +0000 (12:02 +0100)
build.sh [deleted file]
ext/mongoose.c [deleted file]
ext/mongoose.h [deleted file]
ext/stb_ds.h [deleted file]
src/main.c [deleted file]

diff --git a/build.sh b/build.sh
deleted file mode 100644 (file)
index a7a014d..0000000
--- a/build.sh
+++ /dev/null
@@ -1 +0,0 @@
-cc src/main.c -o out/main.exe -I ext ext/mongoose.c -lws2_32
\ No newline at end of file
diff --git a/ext/mongoose.c b/ext/mongoose.c
deleted file mode 100644 (file)
index a76a127..0000000
+++ /dev/null
@@ -1,9141 +0,0 @@
-// Copyright (c) 2004-2013 Sergey Lyubka
-// Copyright (c) 2013-2022 Cesanta Software Limited
-// All rights reserved
-//
-// This software is dual-licensed: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 2 as
-// published by the Free Software Foundation. For the terms of this
-// license, see http://www.gnu.org/licenses/
-//
-// You are free to use this software under the terms of the GNU General
-// Public License, but WITHOUT ANY WARRANTY; without even the implied
-// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-// See the GNU General Public License for more details.
-//
-// 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-only or commercial
-
-#include "mongoose.h"
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/base64.c"
-#endif
-
-
-
-static int mg_b64idx(int c) {
-  if (c < 26) {
-    return c + 'A';
-  } else if (c < 52) {
-    return c - 26 + 'a';
-  } else if (c < 62) {
-    return c - 52 + '0';
-  } else {
-    return c == 62 ? '+' : '/';
-  }
-}
-
-static int mg_b64rev(int c) {
-  if (c >= 'A' && c <= 'Z') {
-    return c - 'A';
-  } else if (c >= 'a' && c <= 'z') {
-    return c + 26 - 'a';
-  } else if (c >= '0' && c <= '9') {
-    return c + 52 - '0';
-  } else if (c == '+') {
-    return 62;
-  } else if (c == '/') {
-    return 63;
-  } else if (c == '=') {
-    return 64;
-  } else {
-    return -1;
-  }
-}
-
-int mg_base64_update(unsigned char ch, char *to, int n) {
-  int rem = (n & 3) % 3;
-  if (rem == 0) {
-    to[n] = (char) mg_b64idx(ch >> 2);
-    to[++n] = (char) ((ch & 3) << 4);
-  } else if (rem == 1) {
-    to[n] = (char) mg_b64idx(to[n] | (ch >> 4));
-    to[++n] = (char) ((ch & 15) << 2);
-  } else {
-    to[n] = (char) mg_b64idx(to[n] | (ch >> 6));
-    to[++n] = (char) mg_b64idx(ch & 63);
-    n++;
-  }
-  return n;
-}
-
-int mg_base64_final(char *to, int n) {
-  int saved = n;
-  // printf("---[%.*s]\n", n, to);
-  if (n & 3) n = mg_base64_update(0, to, n);
-  if ((saved & 3) == 2) n--;
-  // printf("    %d[%.*s]\n", n, n, to);
-  while (n & 3) to[n++] = '=';
-  to[n] = '\0';
-  return n;
-}
-
-int mg_base64_encode(const unsigned char *p, int n, char *to) {
-  int i, len = 0;
-  for (i = 0; i < n; i++) len = mg_base64_update(p[i], to, len);
-  len = mg_base64_final(to, len);
-  return len;
-}
-
-int mg_base64_decode(const char *src, int n, char *dst) {
-  const char *end = src == NULL ? NULL : src + n;  // Cannot add to NULL
-  int len = 0;
-  while (src != NULL && src + 3 < end) {
-    int a = mg_b64rev(src[0]), b = mg_b64rev(src[1]), c = mg_b64rev(src[2]),
-        d = mg_b64rev(src[3]);
-    if (a == 64 || a < 0 || b == 64 || b < 0 || c < 0 || d < 0) return 0;
-    dst[len++] = (char) ((a << 2) | (b >> 4));
-    if (src[2] != '=') {
-      dst[len++] = (char) ((b << 4) | (c >> 2));
-      if (src[3] != '=') dst[len++] = (char) ((c << 6) | d);
-    }
-    src += 4;
-  }
-  dst[len] = '\0';
-  return len;
-}
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/dns.c"
-#endif
-
-
-
-
-
-
-
-
-struct dns_data {
-  struct dns_data *next;
-  struct mg_connection *c;
-  uint64_t expire;
-  uint16_t txnid;
-};
-
-static void mg_sendnsreq(struct mg_connection *, struct mg_str *, int,
-                         struct mg_dns *, bool);
-
-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 = (struct dns_data *) c->mgr->active_dns_requests;
-  for (; d != NULL; d = tmp) {
-    tmp = d->next;
-    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, 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) {
-      i++;
-      break;
-    }
-    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, j, depth + 1) == 0)
-        return 0;
-      i += 2;
-      break;
-    }
-    if (ofs + i + n + 1 >= len) return 0;
-    if (j > 0) {
-      if (j < tolen) to[j] = '.';
-      j++;
-    }
-    if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n);
-    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;
-}
-
-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, 0);
-}
-
-size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs,
-                       bool is_question, struct mg_dns_rr *rr) {
-  const uint8_t *s = buf + ofs, *e = &buf[len];
-
-  memset(rr, 0, sizeof(*rr));
-  if (len < sizeof(struct mg_dns_header)) return 0;  // Too small
-  if (len > 512) return 0;  //  Too large, we don't expect that
-  if (s >= e) return 0;     //  Overflow
-
-  if ((rr->nlen = (uint16_t) mg_dns_parse_name(buf, len, ofs, NULL, 0)) == 0)
-    return 0;
-  s += rr->nlen + 4;
-  if (s > e) return 0;
-  rr->atype = (uint16_t) (((uint16_t) s[-4] << 8) | s[-3]);
-  rr->aclass = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
-  if (is_question) return (size_t) (rr->nlen + 4);
-
-  s += 6;
-  if (s > e) return 0;
-  rr->alen = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]);
-  if (s + rr->alen > e) return 0;
-  return (size_t) (rr->nlen + rr->alen + 10);
-}
-
-bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) {
-  const struct mg_dns_header *h = (struct mg_dns_header *) buf;
-  struct mg_dns_rr rr;
-  size_t i, n, ofs = sizeof(*h);
-  memset(dm, 0, sizeof(*dm));
-
-  if (len < sizeof(*h)) return 0;                // Too small, headers dont fit
-  if (mg_ntohs(h->num_questions) > 1) return 0;  // Sanity
-  if (mg_ntohs(h->num_answers) > 10) return 0;   // Sanity
-  dm->txnid = mg_ntohs(h->txnid);
-
-  for (i = 0; i < mg_ntohs(h->num_questions); i++) {
-    if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0) return false;
-    // 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++) {
-    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;
-
-    if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) {
-      dm->addr.is_ip6 = false;
-      memcpy(&dm->addr.ip, &buf[ofs - 4], 4);
-      dm->resolved = true;
-      break;  // Return success
-    } else if (rr.alen == 16 && rr.atype == 28 && rr.aclass == 1) {
-      dm->addr.is_ip6 = true;
-      memcpy(&dm->addr.ip, &buf[ofs - 16], 16);
-      dm->resolved = true;
-      break;  // Return success
-    }
-  }
-  return true;
-}
-
-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) {
-    uint64_t now = *(uint64_t *) ev_data;
-    for (d = (struct dns_data *) c->mgr->active_dns_requests; d != NULL;
-         d = tmp) {
-      tmp = d->next;
-      // 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) {
-      MG_ERROR(("Unexpected DNS response:"));
-      mg_hexdump(c->recv.buf, c->recv.len);
-    } else {
-      // 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;
-        // MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid));
-        if (dm.txnid != d->txnid) continue;
-        if (d->c->is_resolving) {
-          if (dm.resolved) {
-            dm.addr.port = d->c->rem.port;  // Save port
-            d->c->rem = dm.addr;            // Copy resolved address
-            MG_DEBUG(
-                ("%lu %s is %M", d->c->id, dm.name, mg_print_ip, &d->c->rem));
-            mg_connect_resolved(d->c);
-#if MG_ENABLE_IPV6
-          } 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
-          } else {
-            mg_error(d->c, "%s DNS lookup failed", dm.name);
-          }
-        } else {
-          MG_ERROR(("%lu already resolved", d->c->id));
-        }
-        mg_dns_free(c, d);
-        resolved = 1;
-      }
-    }
-    if (!resolved) MG_ERROR(("stray DNS reply"));
-    c->recv.len = 0;
-  } else if (ev == MG_EV_CLOSE) {
-    for (d = (struct dns_data *) c->mgr->active_dns_requests; d != NULL;
-         d = tmp) {
-      tmp = d->next;
-      mg_error(d->c, "DNS error");
-      mg_dns_free(c, d);
-    }
-  }
-  (void) fn_data;
-}
-
-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;
-    uint8_t data[256];
-  } pkt;
-  size_t i, n;
-  memset(&pkt, 0, sizeof(pkt));
-  pkt.header.txnid = mg_htons(txnid);
-  pkt.header.flags = mg_htons(0x100);
-  pkt.header.num_questions = mg_htons(1);
-  for (i = n = 0; i < sizeof(pkt.data) - 5; i++) {
-    if (name->ptr[i] == '.' || i >= name->len) {
-      pkt.data[n] = (uint8_t) (i - n);
-      memcpy(&pkt.data[n + 1], name->ptr + n, i - n);
-      n = i + 1;
-    }
-    if (i >= name->len) break;
-  }
-  memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5);  // A query
-  n += 5;
-  if (ipv6) pkt.data[n - 3] = 0x1c;  // AAAA query
-  // memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6);  // AAAA query
-  // n += 6;
-  return mg_send(c, &pkt, sizeof(pkt.header) + n);
-}
-
-static void mg_sendnsreq(struct mg_connection *c, struct mg_str *name, int ms,
-                         struct mg_dns *dnsc, bool ipv6) {
-  struct dns_data *d = NULL;
-  if (dnsc->url == NULL) {
-    mg_error(c, "DNS server URL is NULL. Call mg_mgr_init()");
-  } else if (dnsc->c == NULL) {
-    dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL);
-    if (dnsc->c != NULL) {
-      dnsc->c->pfn = dns_cb;
-      // dnsc->c->is_hexdumping = 1;
-    }
-  }
-  if (dnsc->c == NULL) {
-    mg_error(c, "resolver");
-  } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) == NULL) {
-    mg_error(c, "resolve OOM");
-  } else {
-    struct dns_data *reqs = (struct dns_data *) c->mgr->active_dns_requests;
-    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;
-    MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int) name->len,
-                name->ptr, dnsc->url, 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, 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 {
-    // 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);
-  }
-}
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/event.c"
-#endif
-
-
-
-
-
-void mg_call(struct mg_connection *c, int ev, void *ev_data) {
-  // Run user-defined handler first, in order to give it an ability
-  // to intercept processing (e.g. clean input buffer) before the
-  // protocol handler kicks in
-  if (c->fn != NULL) c->fn(c, ev, ev_data, c->fn_data);
-  if (c->pfn != NULL) c->pfn(c, ev, ev_data, c->pfn_data);
-}
-
-void mg_error(struct mg_connection *c, const char *fmt, ...) {
-  char buf[64];
-  va_list ap;
-  va_start(ap, fmt);
-  mg_vsnprintf(buf, sizeof(buf), fmt, &ap);
-  va_end(ap);
-  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
-}
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/fmt.c"
-#endif
-
-
-
-
-static bool is_digit(int c) {
-  return c >= '0' && c <= '9';
-}
-
-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;
-}
-
-static size_t mg_dtoa(char *dst, size_t dstlen, double d, int width, bool tz) {
-  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 && width > 1) {
-    n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz);
-    // 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 && width > 1) {
-    n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width, tz);
-    // 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 (tz && n > 0 && buf[s + n - 1] == '0') n--;  // Trim trailing zeroes
-  if (n > 0 && buf[s + n - 1] == '.') n--;           // Trim trailing dot
-  n += s;
-  if (n >= (int) sizeof(buf)) n = (int) sizeof(buf) - 1;
-  buf[n] = '\0';
-  return mg_snprintf(dst, dstlen, "%s", buf);
-}
-
-static 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;
-}
-
-size_t mg_xprintf(void (*out)(char, void *), void *ptr, const char *fmt, ...) {
-  size_t len = 0;
-  va_list ap;
-  va_start(ap, fmt);
-  len = mg_vxprintf(out, ptr, fmt, &ap);
-  va_end(ap);
-  return len;
-}
-
-size_t mg_vxprintf(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' || c == 'f') {
-        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, c == 'g');
-        } 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' || c == 'M') {
-        mg_pm_t f = va_arg(*ap, mg_pm_t);
-        if (c == 'm') out('"', param);
-        n += f(out, param, ap);
-        if (c == 'm') n += 2, out('"', param);
-      } else if (c == 'c') {
-        int ch = va_arg(*ap, int);
-        out((char) ch, param);
-        n++;
-      } else if (c == 's') {
-        char *p = va_arg(*ap, char *);
-        if (pr == ~0U) pr = p == NULL ? 0 : strlen(p);
-        for (j = 0; !minus && pr < w && j + pr < w; j++)
-          n += scpy(out, param, &pad, 1);
-        n += scpy(out, param, p, pr);
-        for (j = 0; minus && pr < w && j + pr < w; j++)
-          n += scpy(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, ...) {
-  va_list ap;
-  char *data;
-  bool result = false;
-  va_start(ap, fmt);
-  data = mg_vmprintf(fmt, &ap);
-  va_end(ap);
-  result = mg_file_write(fs, path, data, strlen(data));
-  free(data);
-  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;
-    if ((fp = calloc(1, sizeof(*fp))) != NULL) {
-      memcpy(fp, &f, sizeof(*fp));
-      return fp;
-    }
-  }
-  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;
-  size_t pos;
-};
-
-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) {
-  (void) path, (void) size, (void) mtime;
-  return NULL;
-}
-const char *mg_unlist(size_t no) {
-  (void) no;
-  return NULL;
-}
-#endif
-
-static int is_dir_prefix(const char *prefix, size_t n, const char *path) {
-  // 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) {
-  const char *p;
-  size_t i, n = strlen(path);
-  if (mg_unpack(path, size, mtime)) return MG_FS_READ;  // Regular file
-  // Scan all files. If `path` is a dir prefix for any of them, it's a dir
-  for (i = 0; (p = mg_unlist(i)) != NULL; i++) {
-    if (is_dir_prefix(path, n, p)) return MG_FS_DIR;
-  }
-  return 0;
-}
-
-static void packed_list(const char *dir, void (*fn)(const char *, void *),
-                        void *userdata) {
-  char buf[MG_PATH_MAX], tmp[sizeof(buf)];
-  const char *path, *begin, *end;
-  size_t i, n = strlen(dir);
-  tmp[0] = '\0';  // Previously listed entry
-  for (i = 0; (path = mg_unlist(i)) != NULL; i++) {
-    if (!is_dir_prefix(dir, n, path)) continue;
-    begin = &path[n + 1];
-    end = strchr(begin, '/');
-    if (end == NULL) end = begin + strlen(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
-    if (strcmp(buf, tmp) == 0) continue;
-    fn(buf, userdata);  // Not yet listed, call user function
-    strcpy(tmp, buf);   // And save this entry as listed
-  }
-}
-
-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;
-  if (data == NULL) return NULL;
-  if (flags & MG_FS_WRITE) return NULL;
-  if ((fp = (struct packed_file *) calloc(1, sizeof(*fp))) != NULL) {
-    fp->size = size;
-    fp->data = data;
-  }
-  return (void *) fp;
-}
-
-static void packed_close(void *fp) {
-  if (fp != NULL) free(fp);
-}
-
-static size_t packed_read(void *fd, void *buf, size_t len) {
-  struct packed_file *fp = (struct packed_file *) fd;
-  if (fp->pos + len > fp->size) len = fp->size - fp->pos;
-  memcpy(buf, &fp->data[fp->pos], len);
-  fp->pos += len;
-  return len;
-}
-
-static size_t packed_write(void *fd, const void *buf, size_t len) {
-  (void) fd, (void) buf, (void) len;
-  return 0;
-}
-
-static size_t packed_seek(void *fd, size_t offset) {
-  struct packed_file *fp = (struct packed_file *) fd;
-  fp->pos = offset;
-  if (fp->pos > fp->size) fp->pos = fp->size;
-  return fp->pos;
-}
-
-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 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) {
-#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[MG_PATH_MAX];
-  MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) / sizeof(tmp[0]));
-  if (_wstati64(tmp, &st) != 0) return 0;
-  // If path is a symlink, windows reports 0 in st.st_size.
-  // Get a real file size by opening it and jumping to the end
-  if (st.st_size == 0 && (st.st_mode & _S_IFREG)) {
-    FILE *fp = _wfopen(tmp, L"rb");
-    if (fp != NULL) {
-      fseek(fp, 0, SEEK_END);
-      if (ftell(fp) > 0) st.st_size = ftell(fp); // Use _ftelli64 on win10+
-      fclose(fp);
-    }
-  }
-#else
-  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
-}
-
-#if MG_ARCH == MG_ARCH_WIN32
-struct dirent {
-  char d_name[MAX_PATH];
-};
-
-typedef struct win32_dir {
-  HANDLE handle;
-  WIN32_FIND_DATAW info;
-  struct dirent result;
-} DIR;
-
-int gettimeofday(struct timeval *tv, void *tz) {
-  FILETIME ft;
-  unsigned __int64 tmpres = 0;
-
-  if (tv != NULL) {
-    GetSystemTimeAsFileTime(&ft);
-    tmpres |= ft.dwHighDateTime;
-    tmpres <<= 32;
-    tmpres |= ft.dwLowDateTime;
-    tmpres /= 10;  // convert into microseconds
-    tmpres -= (int64_t) 11644473600000000;
-    tv->tv_sec = (long) (tmpres / 1000000UL);
-    tv->tv_usec = (long) (tmpres % 1000000UL);
-  }
-  (void) tz;
-  return 0;
-}
-
-static int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) {
-  int ret;
-  char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p;
-  strncpy(buf, path, sizeof(buf));
-  buf[sizeof(buf) - 1] = '\0';
-  // Trim trailing slashes. Leave backslash for paths like "X:\"
-  p = buf + strlen(buf) - 1;
-  while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0';
-  memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
-  ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);
-  // Convert back to Unicode. If doubly-converted string does not match the
-  // original, something is fishy, reject.
-  WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2),
-                      NULL, NULL);
-  if (strcmp(buf, buf2) != 0) {
-    wbuf[0] = L'\0';
-    ret = 0;
-  }
-  return ret;
-}
-
-DIR *opendir(const char *name) {
-  DIR *d = NULL;
-  wchar_t wpath[MAX_PATH];
-  DWORD attrs;
-
-  if (name == NULL) {
-    SetLastError(ERROR_BAD_ARGUMENTS);
-  } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) {
-    SetLastError(ERROR_NOT_ENOUGH_MEMORY);
-  } else {
-    to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0]));
-    attrs = GetFileAttributesW(wpath);
-    if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
-      (void) wcscat(wpath, L"\\*");
-      d->handle = FindFirstFileW(wpath, &d->info);
-      d->result.d_name[0] = '\0';
-    } else {
-      free(d);
-      d = NULL;
-    }
-  }
-  return d;
-}
-
-int closedir(DIR *d) {
-  int result = 0;
-  if (d != NULL) {
-    if (d->handle != INVALID_HANDLE_VALUE)
-      result = FindClose(d->handle) ? 0 : -1;
-    free(d);
-  } else {
-    result = -1;
-    SetLastError(ERROR_BAD_ARGUMENTS);
-  }
-  return result;
-}
-
-struct dirent *readdir(DIR *d) {
-  struct dirent *result = NULL;
-  if (d != NULL) {
-    memset(&d->result, 0, sizeof(d->result));
-    if (d->handle != INVALID_HANDLE_VALUE) {
-      result = &d->result;
-      WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1, result->d_name,
-                          sizeof(result->d_name), NULL, NULL);
-      if (!FindNextFileW(d->handle, &d->info)) {
-        FindClose(d->handle);
-        d->handle = INVALID_HANDLE_VALUE;
-      }
-    } else {
-      SetLastError(ERROR_FILE_NOT_FOUND);
-    }
-  } else {
-    SetLastError(ERROR_BAD_ARGUMENTS);
-  }
-  return result;
-}
-#endif
-
-static void p_list(const char *dir, void (*fn)(const char *, void *),
-                   void *userdata) {
-#if MG_ENABLE_DIRLIST
-  struct dirent *dp;
-  DIR *dirp;
-  if ((dirp = (opendir(dir))) == NULL) return;
-  while ((dp = readdir(dirp)) != NULL) {
-    if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue;
-    fn(dp->d_name, userdata);
-  }
-  closedir(dirp);
-#else
-  (void) dir, (void) fn, (void) userdata;
-#endif
-}
-
-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[MG_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]));
-  return (void *) _wfopen(b1, b2);
-#else
-  return (void *) fopen(path, mode);
-#endif
-}
-
-static void p_close(void *fp) {
-  fclose((FILE *) fp);
-}
-
-static size_t p_read(void *fp, void *buf, size_t len) {
-  return fread(buf, 1, len, (FILE *) fp);
-}
-
-static size_t p_write(void *fp, const void *buf, size_t len) {
-  return fwrite(buf, 1, len, (FILE *) fp);
-}
-
-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)
-  if (fseeko((FILE *) fp, (off_t) offset, SEEK_SET) != 0) (void) 0;
-#else
-  if (fseek((FILE *) fp, (long) offset, SEEK_SET) != 0) (void) 0;
-#endif
-  return (size_t) ftell((FILE *) fp);
-}
-
-static bool p_rename(const char *from, const char *to) {
-  return rename(from, to) == 0;
-}
-
-static bool p_remove(const char *path) {
-  return remove(path) == 0;
-}
-
-static bool p_mkdir(const char *path) {
-  return mkdir(path, 0775) == 0;
-}
-
-#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(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, p_rename, p_remove, p_mkdir};
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/http.c"
-#endif
-
-
-
-
-
-
-
-
-
-
-
-
-
-bool mg_to_size_t(struct mg_str str, size_t *val);
-bool mg_to_size_t(struct mg_str str, size_t *val) {
-  size_t i = 0, max = (size_t) -1, max2 = max / 10, result = 0, ndigits = 0;
-  while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] == '\t')) i++;
-  if (i < str.len && str.ptr[i] == '-') return false;
-  while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') {
-    size_t digit = (size_t) (str.ptr[i] - '0');
-    if (result > max2) return false;  // Overflow
-    result *= 10;
-    if (result > max - digit) return false;  // Overflow
-    result += digit;
-    i++, ndigits++;
-  }
-  while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] == '\t')) i++;
-  if (ndigits == 0) return false;  // #2322: Content-Length = 1 * DIGIT
-  if (i != str.len) return false;  // Ditto
-  *val = (size_t) result;
-  return true;
-}
-
-// Chunk deletion marker is the MSB in the "processed" counter
-#define MG_DMARK ((size_t) 1 << (sizeof(size_t) * 8 - 1))
-
-// Multipart POST example:
-// --xyz
-// Content-Disposition: form-data; name="val"
-//
-// abcdef
-// --xyz
-// Content-Disposition: form-data; name="foo"; filename="a.txt"
-// Content-Type: text/plain
-//
-// hello world
-//
-// --xyz--
-size_t mg_http_next_multipart(struct mg_str body, size_t ofs,
-                              struct mg_http_part *part) {
-  struct mg_str cd = mg_str_n("Content-Disposition", 19);
-  const char *s = body.ptr;
-  size_t b = ofs, h1, h2, b1, b2, max = body.len;
-
-  // Init part params
-  if (part != NULL) part->name = part->filename = part->body = mg_str_n(0, 0);
-
-  // Skip boundary
-  while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++;
-  if (b <= ofs || b + 2 >= max) return 0;
-  // MG_INFO(("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs), s));
-
-  // Skip headers
-  h1 = h2 = b + 2;
-  for (;;) {
-    while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++;
-    if (h2 == h1) break;
-    if (h2 + 2 >= max) return 0;
-    // 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));
-      part->name = mg_http_get_header_var(v, mg_str_n("name", 4));
-      part->filename = mg_http_get_header_var(v, mg_str_n("filename", 8));
-    }
-    h1 = h2 = h2 + 2;
-  }
-  b1 = b2 = h2 + 2;
-  while (b2 + 2 + (b - ofs) + 2 < max && !(s[b2] == '\r' && s[b2 + 1] == '\n' &&
-                                           memcmp(&s[b2 + 2], s, b - ofs) == 0))
-    b2++;
-
-  if (b2 + 2 >= max) return 0;
-  if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1);
-  // MG_INFO(("Body: [%.*s]", (int) (b2 - b1), &s[b1]));
-  return b2 + 2;
-}
-
-void mg_http_bauth(struct mg_connection *c, const char *user,
-                   const char *pass) {
-  struct mg_str u = mg_str(user), p = mg_str(pass);
-  size_t need = c->send.len + 36 + (u.len + p.len) * 2;
-  if (c->send.size < need) mg_iobuf_resize(&c->send, need);
-  if (c->send.size >= need) {
-    int i, n = 0;
-    char *buf = (char *) &c->send.buf[c->send.len];
-    memcpy(buf, "Authorization: Basic ", 21);  // DON'T use mg_send!
-    for (i = 0; i < (int) u.len; i++) {
-      n = mg_base64_update(((unsigned char *) u.ptr)[i], buf + 21, n);
-    }
-    if (p.len > 0) {
-      n = mg_base64_update(':', buf + 21, n);
-      for (i = 0; i < (int) p.len; i++) {
-        n = mg_base64_update(((unsigned char *) p.ptr)[i], buf + 21, n);
-      }
-    }
-    n = mg_base64_final(buf + 21, n);
-    c->send.len += 21 + (size_t) n + 2;
-    memcpy(&c->send.buf[c->send.len - 2], "\r\n", 2);
-  } else {
-    MG_ERROR(("%lu oom %d->%d ", c->id, (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) {
-  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 {
-    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 && isx(src[i + 1]) && isx(src[i + 2])) {
-        mg_unhex(src + i + 1, 2, (uint8_t *) &dst[j]);
-        i += 2;
-      } else {
-        return -1;
-      }
-    } else if (is_form_url_encoded && src[i] == '+') {
-      dst[j] = ' ';
-    } else {
-      dst[j] = src[i];
-    }
-  }
-  if (j < dst_len) dst[j] = '\0';  // Null-terminate the destination
-  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 (!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;
-  }
-  return 0;
-}
-struct mg_str *mg_http_get_header(struct mg_http_message *h, const char *name) {
-  size_t i, n = strlen(name), max = sizeof(h->headers) / sizeof(h->headers[0]);
-  for (i = 0; i < max && h->headers[i].name.len > 0; i++) {
-    struct mg_str *k = &h->headers[i].name, *v = &h->headers[i].value;
-    if (n == k->len && mg_ncasecmp(k->ptr, name, n) == 0) return v;
-  }
-  return NULL;
-}
-
-// Get character length. Used to parse method, URI, headers
-static size_t clen(const char *s) {
-  uint8_t c = *(uint8_t *) s;
-  if (c > ' ' && c < '~') return 1;  // Usual ascii printed char
-  if ((c & 0xe0) == 0xc0) return 2;  // 2-byte UTF8
-  if ((c & 0xf0) == 0xe0) return 3;  // 3-byte UTF8
-  if ((c & 0xf8) == 0xf0) return 4;  // 4-byte UTF8
-  return 0;
-}
-
-// Skip until the newline. Return advanced `s`, or NULL on error
-static const char *skiptorn(const char *s, const char *end, struct mg_str *v) {
-  v->ptr = s;
-  while (s < end && s[0] != '\n' && s[0] != '\r') s++, v->len++;  // To newline
-  if (s >= end || (s[0] == '\r' && s[1] != '\n')) return NULL;    // Stray \r
-  if (s < end && s[0] == '\r') s++;                               // Skip \r
-  if (s >= end || *s++ != '\n') return NULL;                      // Skip \n
-  return s;
-}
-
-static bool mg_http_parse_headers(const char *s, const char *end,
-                                  struct mg_http_header *h, size_t max_hdrs) {
-  size_t i, n;
-  for (i = 0; i < max_hdrs; i++) {
-    struct mg_str k = {NULL, 0}, v = {NULL, 0};
-    if (s >= end) return false;
-    if (s[0] == '\n' || (s[0] == '\r' && s[1] == '\n')) break;
-    k.ptr = s;
-    while (s < end && s[0] != ':' && (n = clen(s)) > 0) s += n, k.len += n;
-    if (k.len == 0) return false;               // Empty name
-    if (s >= end || *s++ != ':') return false;  // Invalid, not followed by :
-    while (s < end && s[0] == ' ') s++;         // Skip spaces
-    if ((s = skiptorn(s, end, &v)) == NULL) return false;
-    while (v.len > 0 && v.ptr[v.len - 1] == ' ') v.len--;  // Trim spaces
-    // MG_INFO(("--HH [%.*s] [%.*s]", (int) k.len, k.ptr, (int) v.len, v.ptr));
-    h[i].name = k, h[i].value = v;  // Success. Assign values
-  }
-  return true;
-}
-
-int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) {
-  int is_response, req_len = mg_http_get_request_len((unsigned char *) s, len);
-  const char *end = s == NULL ? NULL : s + req_len, *qs;  // Cannot add to NULL
-  struct mg_str *cl;
-  size_t n;
-
-  memset(hm, 0, sizeof(*hm));
-  if (req_len <= 0) return req_len;
-
-  hm->message.ptr = hm->head.ptr = s;
-  hm->body.ptr = end;
-  hm->head.len = (size_t) req_len;
-  hm->chunk.ptr = end;
-  hm->message.len = hm->body.len = (size_t) ~0;  // Set body length to infinite
-
-  // Parse request line
-  hm->method.ptr = s;
-  while (s < end && (n = clen(s)) > 0) s += n, hm->method.len += n;
-  while (s < end && s[0] == ' ') s++;  // Skip spaces
-  hm->uri.ptr = s;
-  while (s < end && (n = clen(s)) > 0) s += n, hm->uri.len += n;
-  while (s < end && s[0] == ' ') s++;  // Skip spaces
-  if ((s = skiptorn(s, end, &hm->proto)) == NULL) return false;
-
-  // Sanity check. Allow protocol/reason to be empty
-  if (hm->method.len == 0 || hm->uri.len == 0) return -1;
-
-  // If URI contains '?' character, setup query string
-  if ((qs = (const char *) memchr(hm->uri.ptr, '?', hm->uri.len)) != NULL) {
-    hm->query.ptr = qs + 1;
-    hm->query.len = (size_t) (&hm->uri.ptr[hm->uri.len] - (qs + 1));
-    hm->uri.len = (size_t) (qs - hm->uri.ptr);
-  }
-
-  if (!mg_http_parse_headers(s, end, hm->headers,
-                             sizeof(hm->headers) / sizeof(hm->headers[0])))
-    return -1;  // error when parsing
-  if ((cl = mg_http_get_header(hm, "Content-Length")) != NULL) {
-    if (mg_to_size_t(*cl, &hm->body.len) == false) return -1;
-    hm->message.len = (size_t) req_len + hm->body.len;
-  }
-
-  // mg_http_parse() is used to parse both HTTP requests and HTTP
-  // responses. If HTTP response does not have Content-Length set, then
-  // body is read until socket is closed, i.e. body.len is infinite (~0).
-  //
-  // For HTTP requests though, according to
-  // http://tools.ietf.org/html/rfc7231#section-8.1.3,
-  // only POST and PUT methods have defined body semantics.
-  // Therefore, if Content-Length is not specified and methods are
-  // not one of PUT or POST, set body length to 0.
-  //
-  // So, if it is HTTP request, and Content-Length is not set,
-  // and method is not (PUT or POST) then reset body length to zero.
-  is_response = mg_ncasecmp(hm->method.ptr, "HTTP/", 5) == 0;
-  if (hm->body.len == (size_t) ~0 && !is_response &&
-      mg_vcasecmp(&hm->method, "PUT") != 0 &&
-      mg_vcasecmp(&hm->method, "POST") != 0) {
-    hm->body.len = 0;
-    hm->message.len = (size_t) req_len;
-  }
-
-  // The 204 (No content) responses also have 0 body length
-  if (hm->body.len == (size_t) ~0 && is_response &&
-      mg_vcasecmp(&hm->uri, "204") == 0) {
-    hm->body.len = 0;
-    hm->message.len = (size_t) req_len;
-  }
-  if (hm->message.len < (size_t) req_len) return -1; // Overflow protection
-
-  return req_len;
-}
-
-static void mg_http_vprintf_chunk(struct mg_connection *c, const char *fmt,
-                                  va_list *ap) {
-  size_t len = c->send.len;
-  mg_send(c, "        \r\n", 10);
-  mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
-  if (c->send.len >= len + 10) {
-    mg_snprintf((char *) c->send.buf + len, 9, "%08lx", c->send.len - len - 10);
-    c->send.buf[len + 8] = '\r';
-    if (c->send.len == len + 10) c->is_resp = 0;  // Last chunk, reset marker
-  }
-  mg_send(c, "\r\n", 2);
-}
-
-void mg_http_printf_chunk(struct mg_connection *c, const char *fmt, ...) {
-  va_list ap;
-  va_start(ap, fmt);
-  mg_http_vprintf_chunk(c, fmt, &ap);
-  va_end(ap);
-}
-
-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_send(c, buf, len);
-  mg_send(c, "\r\n", 2);
-  if (len == 0) c->is_resp = 0;
-}
-
-// clang-format off
-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 "";
-  }
-}
-// clang-format on
-
-void mg_http_reply(struct mg_connection *c, int code, const char *headers,
-                   const char *fmt, ...) {
-  va_list ap;
-  size_t len;
-  mg_printf(c, "HTTP/1.1 %d %s\r\n%sContent-Length:            \r\n\r\n", code,
-            mg_http_status_code_str(code), headers == NULL ? "" : headers);
-  len = c->send.len;
-  va_start(ap, fmt);
-  mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, &ap);
-  va_end(ap);
-  if (c->send.len > 16) {
-    size_t n = mg_snprintf((char *) &c->send.buf[len - 15], 11, "%-10lu",
-                           (unsigned long) (c->send.len - len));
-    c->send.buf[len - 15 + n] = ' ';  // Change ending 0 to space
-  }
-  c->is_resp = 0;
-}
-
-static void http_cb(struct mg_connection *, int, void *, void *);
-static void restore_http_cb(struct mg_connection *c) {
-  mg_fs_close((struct mg_fd *) c->pfn_data);
-  c->pfn_data = NULL;
-  c->pfn = http_cb;
-  c->is_resp = 0;
-}
-
-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) {
-  mg_snprintf(buf, len, "\"%lld.%lld\"", (int64_t) mtime, (int64_t) size);
-  return buf;
-}
-
-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 = MG_IO_SIZE, space;
-    size_t *cl = (size_t *) &c->data[(sizeof(c->data) - sizeof(size_t)) /
-                                     sizeof(size_t) * sizeof(size_t)];
-    if (c->send.size < max) mg_iobuf_resize(&c->send, max);
-    if (c->send.len >= c->send.size) return;  // Rate limit
-    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) {
-  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++;
-  path.ptr += path.len - i;
-  path.len = i;
-
-  // Process user-provided mime type overrides, if any
-  while (mg_commalist(&s, &k, &v)) {
-    if (mg_strcmp(path, k) == 0) return v;
-  }
-
-  // Process built-in mime types
-  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");
-}
-
-static int getrange(struct mg_str *s, size_t *a, size_t *b) {
-  size_t i, numparsed = 0;
-  for (i = 0; i + 6 < s->len; i++) {
-    struct mg_str k, v = mg_str_n(s->ptr + i + 6, s->len - i - 6);
-    if (memcmp(&s->ptr[i], "bytes=", 6) != 0) continue;
-    if (mg_split(&v, &k, NULL, '-')) {
-      if (mg_to_size_t(k, a)) numparsed++;
-      if (v.len > 0 && mg_to_size_t(v, b)) numparsed++;
-    } else {
-      if (mg_to_size_t(v, a)) numparsed++;
-    }
-    break;
-  }
-  return (int) numparsed;
-}
-
-void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm,
-                        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 = NULL;
-  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 (path != NULL) {
-    // If a browser sends us "Accept-Encoding: gzip", try to open .gz first
-    struct mg_str *ae = mg_http_get_header(hm, "Accept-Encoding");
-    if (ae != NULL && mg_strstr(*ae, mg_str("gzip")) != NULL) {
-      mg_snprintf(tmp, sizeof(tmp), "%s.gz", path);
-      fd = mg_fs_open(fs, tmp, MG_FS_READ);
-      if (fd != NULL) gzip = true, path = tmp;
-    }
-    // No luck opening .gz? Open what we've told to open
-    if (fd == NULL) fd = mg_fs_open(fs, path, MG_FS_READ);
-  }
-
-  // Failed to open, and page404 is configured? Open it, then
-  if (fd == NULL && opts->page404 != NULL) {
-    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->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) {
-    mg_fs_close(fd);
-    mg_http_reply(c, 304, opts->extra_headers, "");
-  } else {
-    int n, status = 200;
-    char range[100];
-    size_t r1 = 0, r2 = 0, cl = size;
-
-    // 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) {
-      // 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;
-        mg_snprintf(range, sizeof(range), "Content-Range: bytes */%lld\r\n",
-                    (int64_t) size);
-      } else {
-        status = 206;
-        cl = r2 - r1 + 1;
-        mg_snprintf(range, sizeof(range),
-                    "Content-Range: bytes %llu-%llu/%llu\r\n", (uint64_t) r1,
-                    (uint64_t) (r1 + cl - 1), (uint64_t) size);
-        fs->sk(fd->fd, r1);
-      }
-    }
-    mg_printf(c,
-              "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, (uint64_t) 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;
-      c->is_resp = 0;
-      mg_fs_close(fd);
-    } else {
-      // Track to-be-sent content length at the end of c->data, aligned
-      size_t *clp = (size_t *) &c->data[(sizeof(c->data) - sizeof(size_t)) /
-                                        sizeof(size_t) * sizeof(size_t)];
-      c->pfn = static_cb;
-      c->pfn_data = fd;
-      *clp = cl;
-    }
-  }
-}
-
-struct printdirentrydata {
-  struct mg_connection *c;
-  struct mg_http_message *hm;
-  const struct mg_http_serve_opts *opts;
-  const char *dir;
-};
-
-#if MG_ENABLE_DIRLIST
-static void printdirentry(const char *name, void *userdata) {
-  struct printdirentrydata *d = (struct printdirentrydata *) 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[40], mod[40];
-  int flags, n = 0;
-
-  // 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 ? "/" : "";
-    if (flags & MG_FS_DIR) {
-      mg_snprintf(sz, sizeof(sz), "%s", "[DIR]");
-    } else {
-      mg_snprintf(sz, sizeof(sz), "%lld", (uint64_t) size);
-    }
-#if defined(MG_HTTP_DIRLIST_TIME_FMT)
-    {
-      char time_str[40];
-      struct tm *time_info = localtime(&t);
-      strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S", time_info);
-      mg_snprintf(mod, sizeof(mod), "%s", time_str);
-    }
-#else
-    mg_snprintf(mod, sizeof(mod), "%lu", (unsigned long) t);
-#endif
-    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=%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,
-                    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],"
-      "n1 = c1.getAttribute('name'), n2 = c2.getAttribute('name'), "
-      "t1 = a.cells[2].getAttribute('name'), "
-      "t2 = b.cells[2].getAttribute('name'); "
-      "return so * (t1 < 0 && t2 >= 0 ? -1 : t2 < 0 && t1 >= 0 ? 1 : "
-      "n1 ? parseInt(n2) - parseInt(n1) : "
-      "c1.textContent.trim().localeCompare(c2.textContent.trim())); });";
-  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); "
-      "};"
-      "window.onload = function() {"
-      "var tb = document.getElementById('tb');"
-      "var m = /sc=([012]).so=(1|-1)/.exec(window.location.hash) || [0, 2, 1];"
-      "var sc = m[1], so = m[2]; document.onclick = function(ev) { "
-      "var c = ev.target.rel; if (c) {if (c == sc) so *= -1; srt(tb, c, so); "
-      "sc = c; ev.preventDefault();}};"
-      "srt(tb, sc, so, true);"
-      "}"
-      "</script>";
-  struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
-  struct printdirentrydata d = {c, hm, opts, dir};
-  char tmp[10], buf[MG_PATH_MAX];
-  size_t off, n;
-  int len = mg_url_decode(hm->uri.ptr, hm->uri.len, buf, sizeof(buf), 0);
-  struct mg_str uri = len > 0 ? mg_str_n(buf, (size_t) len) : hm->uri;
-
-  mg_printf(c,
-            "HTTP/1.1 200 OK\r\n"
-            "Content-Type: text/html; charset=utf-8\r\n"
-            "%s"
-            "Content-Length:         \r\n\r\n",
-            opts->extra_headers == NULL ? "" : opts->extra_headers);
-  off = c->send.len;  // Start of body
-  mg_printf(c,
-            "<!DOCTYPE html><html><head><title>Index of %.*s</title>%s%s"
-            "<style>th,td {text-align: left; padding-right: 1em; "
-            "font-family: monospace; }</style></head>"
-            "<body><h1>Index of %.*s</h1><table cellpadding=\"0\"><thead>"
-            "<tr><th><a href=\"#\" rel=\"0\">Name</a></th><th>"
-            "<a href=\"#\" rel=\"1\">Modified</a></th>"
-            "<th><a href=\"#\" rel=\"2\">Size</a></th></tr>"
-            "<tr><td colspan=\"3\"><hr></td></tr>"
-            "</thead>"
-            "<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->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 = mg_snprintf(tmp, sizeof(tmp), "%lu", (unsigned long) (c->send.len - off));
-  if (n > sizeof(tmp)) n = 0;
-  memcpy(c->send.buf + off - 12, tmp, n);  // Set content length
-  c->is_resp = 0;                          // Mark response end
-}
-#endif
-
-// 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, tmp;
-  // Append URI to the root_dir, and sanitize it
-  size_t n = mg_snprintf(path, path_size, "%.*s", (int) dir.len, dir.ptr);
-  if (n + 2 >= path_size) {
-    mg_http_reply(c, 400, "", "Exceeded path size");
-    return -1;
-  }
-  path[path_size - 1] = '\0';
-  // Terminate root dir with slash
-  if (n > 0 && path[n - 1] != '/') path[n++] = '/', path[n] = '\0';
-  if (url.len < hm->uri.len) {
-    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
-  if (!mg_path_is_sane(path)) {
-    mg_http_reply(c, 400, "", "Invalid path");
-    return -1;
-  }
-  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);
-    c->is_resp = 0;
-    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 if ((mg_snprintf(path + n, path_size - n, "/" MG_HTTP_INDEX ".gz") >
-                    0 &&
-                (tmp = fs->st(path, NULL, NULL)) !=
-                    0)) {  // check for gzipped index
-      flags = tmp;
-      path[n + 1 + strlen(MG_HTTP_INDEX)] =
-          '\0';  // Remove appended .gz in index file name
-    } 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,
-                       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};
-  while (mg_commalist(&s, &k, &v)) {
-    if (v.len == 0) v = k, k = mg_str("/"), u = k, p = v;
-    if (hm->uri.len < k.len) continue;
-    if (mg_strcmp(k, mg_str_n(hm->uri.ptr, k.len)) != 0) continue;
-    u = k, p = v;
-  }
-  return uri_to_path2(c, hm, fs, u, p, path, path_size);
-}
-
-void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm,
-                       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) {
-    // Do nothing: the response has already been sent by uri_to_path()
-  } else if (flags & MG_FS_DIR) {
-#if MG_ENABLE_DIRLIST
-    listdir(c, hm, opts, path);
-#else
-    mg_http_reply(c, 403, "", "Forbidden\n");
-#endif
-  } 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);
-  }
-}
-
-static bool mg_is_url_safe(int c) {
-  return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
-         (c >= 'A' && c <= 'Z') || c == '.' || c == '_' || c == '-' || c == '~';
-}
-
-size_t mg_url_encode(const char *s, size_t sl, char *buf, size_t len) {
-  size_t i, n = 0;
-  for (i = 0; i < sl; i++) {
-    int c = *(unsigned char *) &s[i];
-    if (n + 4 >= len) return 0;
-    if (mg_is_url_safe(c)) {
-      buf[n++] = s[i];
-    } else {
-      buf[n++] = '%';
-      mg_hex(&s[i], 1, &buf[n]);
-      n += 2;
-    }
-  }
-  if (len > 0 && n < len - 1) buf[n] = '\0';  // Null-terminate the destination
-  if (len > 0) buf[len - 1] = '\0';           // Always.
-  return n;
-}
-
-void mg_http_creds(struct mg_http_message *hm, char *user, size_t userlen,
-                   char *pass, size_t passlen) {
-  struct mg_str *v = mg_http_get_header(hm, "Authorization");
-  user[0] = pass[0] = '\0';
-  if (v != NULL && v->len > 6 && memcmp(v->ptr, "Basic ", 6) == 0) {
-    char buf[256];
-    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) {
-      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) {
-    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) mg_snprintf(pass, passlen, "%.*s", (int) t.len, t.ptr);
-  } else {
-    mg_http_get_var(&hm->query, "access_token", pass, passlen);
-  }
-}
-
-static struct mg_str stripquotes(struct mg_str s) {
-  return s.len > 1 && s.ptr[0] == '"' && s.ptr[s.len - 1] == '"'
-             ? mg_str_n(s.ptr + 1, s.len - 2)
-             : s;
-}
-
-struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v) {
-  size_t i;
-  for (i = 0; v.len > 0 && i + v.len + 2 < s.len; i++) {
-    if (s.ptr[i + v.len] == '=' && memcmp(&s.ptr[i], v.ptr, v.len) == 0) {
-      const char *p = &s.ptr[i + v.len + 1], *b = p, *x = &s.ptr[s.len];
-      int q = p < x && *p == '"' ? 1 : 0;
-      while (p < x &&
-             (q ? p == b || *p != '"' : *p != ';' && *p != ' ' && *p != ','))
-        p++;
-      // 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)));
-    }
-  }
-  return mg_str_n(NULL, 0);
-}
-
-bool mg_http_match_uri(const struct mg_http_message *hm, const char *glob) {
-  return mg_match(hm->uri, mg_str(glob), NULL);
-}
-
-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);
-    }
-  }
-  return res;
-}
-
-int mg_http_status(const struct mg_http_message *hm) {
-  return atoi(hm->uri.ptr);
-}
-
-// If a server sends data to the client using chunked encoding, Mongoose strips
-// off the chunking prefix (hex length and \r\n) and suffix (\r\n), appends the
-// stripped data to the body, and fires the MG_EV_HTTP_CHUNK event.  When zero
-// chunk is received, we fire MG_EV_HTTP_MSG, and the body already has all
-// chunking prefixes/suffixes stripped.
-//
-// If a server sends data without chunked encoding, we also fire a series of
-// MG_EV_HTTP_CHUNK events for every received piece of data, and then we fire
-// MG_EV_HTTP_MSG event in the end.
-//
-// We track total processed length in the c->pfn_data, which is a void *
-// pointer: we store a size_t value there.
-static bool getchunk(struct mg_str s, size_t *prefixlen, size_t *datalen) {
-  size_t i = 0, n;
-  while (i < s.len && s.ptr[i] != '\r' && s.ptr[i] != '\n') i++;
-  n = mg_unhexn(s.ptr, i);
-  // MG_INFO(("%d %d", (int) (i + n + 4), (int) s.len));
-  if (s.len < i + n + 4) return false;  // Chunk not yet fully buffered
-  if (s.ptr[i] != '\r' || s.ptr[i + 1] != '\n') return false;
-  if (s.ptr[i + n + 2] != '\r' || s.ptr[i + n + 3] != '\n') return false;
-  *prefixlen = i + 2;
-  *datalen = n;
-  return true;
-}
-
-static bool mg_is_chunked(struct mg_http_message *hm) {
-  const char *needle = "chunked";
-  struct mg_str *te = mg_http_get_header(hm, "Transfer-Encoding");
-  return te != NULL && mg_vcasecmp(te, needle) == 0;
-}
-
-void mg_http_delete_chunk(struct mg_connection *c, struct mg_http_message *hm) {
-  size_t ofs = (size_t) (hm->chunk.ptr - (char *) c->recv.buf);
-  mg_iobuf_del(&c->recv, ofs, hm->chunk.len);
-  c->pfn_data = (void *) ((size_t) c->pfn_data | MG_DMARK);
-}
-
-static void deliver_chunked_chunks(struct mg_connection *c, size_t hlen,
-                                   struct mg_http_message *hm, bool *next) {
-  //  |  ... headers ... | HEXNUM\r\n ..data.. \r\n | ......
-  //  +------------------+--------------------------+----
-  //  |      hlen        |           chunk1         | ......
-  char *buf = (char *) &c->recv.buf[hlen], *p = buf;
-  size_t len = c->recv.len - hlen;
-  size_t processed = ((size_t) c->pfn_data) & ~MG_DMARK;
-  size_t mark, pl, dl, del = 0, ofs = 0;
-  bool last = false;
-  if (processed <= len) len -= processed, buf += processed;
-  while (!last && getchunk(mg_str_n(buf + ofs, len - ofs), &pl, &dl)) {
-    size_t saved = c->recv.len;
-    memmove(p + processed, buf + ofs + pl, dl);
-    // MG_INFO(("P2 [%.*s]", (int) (processed + dl), p));
-    hm->chunk = mg_str_n(p + processed, dl);
-    mg_call(c, MG_EV_HTTP_CHUNK, hm);
-    ofs += pl + dl + 2, del += pl + 2;  // 2 is for \r\n suffix
-    processed += dl;
-    if (c->recv.len != saved) processed -= dl, buf -= dl;
-    // mg_hexdump(c->recv.buf, hlen + processed);
-    last = (dl == 0);
-  }
-  mg_iobuf_del(&c->recv, hlen + processed, del);
-  mark = ((size_t) c->pfn_data) & MG_DMARK;
-  c->pfn_data = (void *) (processed | mark);
-  if (last) {
-    hm->body.len = processed;
-    hm->message.len = hlen + processed;
-    c->pfn_data = NULL;
-    if (mark) mg_iobuf_del(&c->recv, 0, hlen), *next = true;
-    // MG_INFO(("LAST, mark: %lx", mark));
-    // mg_hexdump(c->recv.buf, c->recv.len);
-  }
-}
-
-static void deliver_normal_chunks(struct mg_connection *c, size_t hlen,
-                                  struct mg_http_message *hm, bool *next) {
-  size_t left, processed = ((size_t) c->pfn_data) & ~MG_DMARK;
-  size_t deleted = ((size_t) c->pfn_data) & MG_DMARK;
-  hm->chunk = mg_str_n((char *) &c->recv.buf[hlen], c->recv.len - hlen);
-  if (processed <= hm->chunk.len && !deleted) {
-    hm->chunk.len -= processed;
-    hm->chunk.ptr += processed;
-  }
-  left = hm->body.len < processed ? 0 : hm->body.len - processed;
-  if (hm->chunk.len > left) hm->chunk.len = left;
-  if (hm->chunk.len > 0) mg_call(c, MG_EV_HTTP_CHUNK, hm);
-  processed += hm->chunk.len;
-  deleted = ((size_t) c->pfn_data) & MG_DMARK;  // Re-evaluate after user call
-  if (processed >= hm->body.len) {              // Last, 0-len chunk
-    hm->chunk.len = 0;                          // Reset length
-    mg_call(c, MG_EV_HTTP_CHUNK, hm);           // Call user handler
-    c->pfn_data = NULL;                         // Reset processed counter
-    if (processed && deleted) mg_iobuf_del(&c->recv, 0, hlen), *next = true;
-  } else {
-    c->pfn_data = (void *) (processed | deleted);  // if it is set
-  }
-}
-
-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;
-    while (c->recv.buf != NULL && c->recv.len > 0) {
-      bool next = false;
-      int hlen = mg_http_parse((char *) c->recv.buf, c->recv.len, &hm);
-      if (hlen < 0) {
-        mg_error(c, "HTTP parse:\n%.*s", (int) c->recv.len, c->recv.buf);
-        break;
-      }
-      if (c->is_resp) break;           // Response is still generated
-      if (hlen == 0) break;            // Request is not buffered yet
-      if (ev == MG_EV_CLOSE) {         // If client did not set Content-Length
-        hm.message.len = c->recv.len;  // and closes now, deliver a MSG
-        hm.body.len = hm.message.len - (size_t) (hm.body.ptr - hm.message.ptr);
-      }
-      if (mg_is_chunked(&hm)) {
-        deliver_chunked_chunks(c, (size_t) hlen, &hm, &next);
-      } else {
-        deliver_normal_chunks(c, (size_t) hlen, &hm, &next);
-      }
-      if (next) continue;  // Chunks & request were deleted
-      //  Chunk events are delivered. If we have full body, deliver MSG
-      if (c->recv.len < hm.message.len) break;
-      if (c->is_accepted) c->is_resp = 1;  // Start generating response
-      mg_call(c, MG_EV_HTTP_MSG, &hm);     // User handler can clear is_resp
-      mg_iobuf_del(&c->recv, 0, hm.message.len);
-    }
-  }
-  (void) evd, (void) fnd;
-}
-
-static void mg_hfn(struct mg_connection *c, int ev, void *ev_data, void *fnd) {
-  if (ev == MG_EV_HTTP_MSG) {
-    struct mg_http_message *hm = (struct mg_http_message *) ev_data;
-    if (mg_http_match_uri(hm, "/quit")) {
-      mg_http_reply(c, 200, "", "ok\n");
-      c->is_draining = 1;
-      c->data[0] = 'X';
-    } else if (mg_http_match_uri(hm, "/debug")) {
-      int level = (int) mg_json_get_long(hm->body, "$.level", MG_LL_DEBUG);
-      mg_log_set(level);
-      mg_http_reply(c, 200, "", "Debug level set to %d\n", level);
-    } else {
-      mg_http_reply(c, 200, "", "hi\n");
-    }
-  } else if (ev == MG_EV_CLOSE) {
-    if (c->data[0] == 'X') *(bool *) fnd = true;
-  }
-}
-
-void mg_hello(const char *url) {
-  struct mg_mgr mgr;
-  bool done = false;
-  mg_mgr_init(&mgr);
-  if (mg_http_listen(&mgr, url, mg_hfn, &done) == NULL) done = true;
-  while (done == false) mg_mgr_poll(&mgr, 100);
-  mg_mgr_free(&mgr);
-}
-
-struct mg_connection *mg_http_connect(struct mg_mgr *mgr, const char *url,
-                                      mg_event_handler_t fn, void *fn_data) {
-  struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
-  if (c != NULL) c->pfn = http_cb;
-  return c;
-}
-
-struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const char *url,
-                                     mg_event_handler_t fn, void *fn_data) {
-  struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
-  if (c != NULL) c->pfn = http_cb;
-  return c;
-}
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/iobuf.c"
-#endif
-
-
-
-
-// Not using memset for zeroing memory, cause it can be dropped by compiler
-// See https://github.com/cesanta/mongoose/pull/1265
-static void zeromem(volatile unsigned char *buf, size_t len) {
-  if (buf != NULL) {
-    while (len--) *buf++ = 0;
-  }
-}
-
-static size_t roundup(size_t size, size_t align) {
-  return align == 0 ? size : (size + align - 1) / align * align;
-}
-
-int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) {
-  int ok = 1;
-  new_size = roundup(new_size, io->align);
-  if (new_size == 0) {
-    zeromem(io->buf, io->size);
-    free(io->buf);
-    io->buf = NULL;
-    io->len = io->size = 0;
-  } else if (new_size != io->size) {
-    // NOTE(lsm): do not use realloc here. Use calloc/free only, to ease the
-    // porting to some obscure platforms like FreeRTOS
-    void *p = calloc(1, new_size);
-    if (p != NULL) {
-      size_t len = new_size < io->len ? new_size : io->len;
-      if (len > 0 && io->buf != NULL) 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;
-      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, size_t align) {
-  io->buf = NULL;
-  io->align = align;
-  io->size = io->len = 0;
-  return mg_iobuf_resize(io, size);
-}
-
-size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void *buf,
-                    size_t len) {
-  size_t new_size = roundup(io->len + len, io->align);
-  mg_iobuf_resize(io, new_size);      // Attempt to resize
-  if (new_size != io->size) len = 0;  // Resize failure, append nothing
-  if (ofs < io->len) memmove(io->buf + ofs + len, io->buf + ofs, io->len - ofs);
-  if (buf != NULL) memmove(io->buf + ofs, buf, len);
-  if (ofs > io->len) io->len += ofs - io->len;
-  io->len += len;
-  return len;
-}
-
-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;
-  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;
-}
-
-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;
-}
-
-static 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;
-}
-
-int mg_json_get(struct mg_str json, const char *path, int *toklen) {
-  const char *s = json.ptr;
-  int len = (int) json.len;
-  enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting = S_VALUE;
-  unsigned char nesting[MG_JSON_MAX_DEPTH];
-  int i = 0;             // Current offset in `s`
-  int j = 0;             // Offset in `s` we're looking for (return value)
-  int depth = 0;         // Current depth (nesting level)
-  int ed = 0;            // Expected depth
-  int pos = 1;           // Current position in `path`
-  int ci = -1, ei = -1;  // Current and expected index in array
-
-  if (toklen) *toklen = 0;
-  if (path[0] != '$') return MG_JSON_INVALID;
-
-#define MG_CHECKRET(x)                                  \
-  do {                                                  \
-    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;
-    switch (expecting) {
-      case S_VALUE:
-        // p("V %s [%.*s] %d %d %d %d\n", path, pos, path, depth, ed, ci, ei);
-        if (depth == ed) j = i;
-        if (c == '{') {
-          if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP;
-          if (depth == ed && path[pos] == '.' && ci == ei) {
-            // If we start the object, reset array indices
-            ed++, pos++, ci = ei = -1;
-          }
-          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;
-          if (i + 1 + n >= len) return MG_JSON_NOT_FOUND;
-          if (depth < ed) return MG_JSON_NOT_FOUND;
-          if (depth == ed && path[pos - 1] != '.') return MG_JSON_NOT_FOUND;
-          // printf("K %s [%.*s] [%.*s] %d %d %d %d %d\n", path, pos, path, n,
-          //        &s[i + 1], n, depth, ed, ci, ei);
-          //  NOTE(cpq): in the check sequence below is important.
-          //  strncmp() must go first: it fails fast if the remaining length of
-          //  the path is smaller than `n`.
-          if (depth == ed && path[pos - 1] == '.' &&
-              strncmp(&s[i + 1], &path[pos], (size_t) n) == 0 &&
-              (path[pos + n] == '\0' || path[pos + n] == '.' ||
-               path[pos + n] == '[')) {
-            pos += n;
-          }
-          i += n + 1;
-          expecting = S_COLON;
-        } else if (c == '}') {  // Empty object
-          MG_EOO('}');
-          expecting = S_COMMA_OR_EOO;
-          if (depth == ed && ei >= 0) ci++;
-        } 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;
-        } else if (c == ']' || c == '}') {
-          if (depth == ed && c == '}' && path[pos - 1] == '.')
-            return MG_JSON_NOT_FOUND;
-          if (depth == ed && c == ']' && path[pos - 1] == ',')
-            return MG_JSON_NOT_FOUND;
-          MG_EOO('O');
-          if (depth == ed && ei >= 0) ci++;
-        } else {
-          return MG_JSON_INVALID;
-        }
-        break;
-    }
-  }
-  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, 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 found = 0, off = mg_json_get(json, path, NULL);
-  if (off >= 0 && (json.ptr[off] == 't' || json.ptr[off] == 'f')) {
-    if (v != NULL) *v = json.ptr[off] == 't';
-    found = 1;
-  }
-  return found;
-}
-
-bool mg_json_unescape(struct mg_str s, char *to, size_t n) {
-  size_t i, j;
-  for (i = 0, j = 0; i < s.len && j < n; i++, j++) {
-    if (s.ptr[i] == '\\' && i + 5 < s.len && s.ptr[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.ptr[i + 2] != '0' || s.ptr[i + 3] != '0') return false;  // Give up
-      ((unsigned char *) to)[j] = (unsigned char) mg_unhexn(s.ptr + i + 4, 2);
-
-      i += 5;
-    } else if (s.ptr[i] == '\\' && i + 1 < s.len) {
-      char c = json_esc(s.ptr[i + 1], 0);
-      if (c == 0) return false;
-      to[j] = c;
-      i++;
-    } else {
-      to[j] = s.ptr[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) {
-  char *result = NULL;
-  int len = 0, off = mg_json_get(json, path, &len);
-  if (off >= 0 && len > 1 && json.ptr[off] == '"') {
-    if ((result = (char *) calloc(1, (size_t) len)) != NULL &&
-        !mg_json_unescape(mg_str_n(json.ptr + off + 1, (size_t) (len - 2)),
-                          result, (size_t) len)) {
-      free(result);
-      result = NULL;
-    }
-  }
-  return result;
-}
-
-char *mg_json_get_b64(struct mg_str json, const char *path, int *slen) {
-  char *result = NULL;
-  int len = 0, off = mg_json_get(json, path, &len);
-  if (off >= 0 && json.ptr[off] == '"' && len > 1 &&
-      (result = (char *) calloc(1, (size_t) len)) != NULL) {
-    int k = mg_base64_decode(json.ptr + off + 1, len - 2, result);
-    if (slen != NULL) *slen = k;
-  }
-  return result;
-}
-
-char *mg_json_get_hex(struct mg_str json, const char *path, int *slen) {
-  char *result = NULL;
-  int len = 0, off = mg_json_get(json, path, &len);
-  if (off >= 0 && json.ptr[off] == '"' && len > 1 &&
-      (result = (char *) calloc(1, (size_t) len / 2)) != NULL) {
-    mg_unhex(json.ptr + off + 1, (size_t) (len - 2), (uint8_t *) result);
-    result[len / 2 - 1] = '\0';
-    if (slen != NULL) *slen = len / 2 - 1;
-  }
-  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
-
-
-
-
-
-static int s_level = MG_LL_INFO;
-static mg_pfn_t s_log_func = mg_pfn_stdout;
-static void *s_log_func_param = NULL;
-
-void mg_log_set_fn(mg_pfn_t fn, void *param) {
-  s_log_func = fn;
-  s_log_func_param = param;
-}
-
-static void logc(unsigned char c) {
-  s_log_func((char) c, s_log_func_param);
-}
-
-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(int log_level) {
-  MG_DEBUG(("Setting log level to %d", log_level));
-  s_level = log_level;
-}
-
-bool mg_log_prefix(int level, const char *file, int line, const char *fname) {
-  if (level <= s_level) {
-    const char *p = strrchr(file, '/');
-    char buf[41];
-    size_t n;
-    if (p == NULL) p = strrchr(file, '\\');
-    n = mg_snprintf(buf, sizeof(buf), "%-6llx %d %s:%d:%s", mg_millis(), level,
-                    p == NULL ? file : p + 1, 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;
-  }
-}
-
-void mg_log(const char *fmt, ...) {
-  va_list ap;
-  va_start(ap, fmt);
-  mg_vxprintf(s_log_func, s_log_func_param, fmt, &ap);
-  va_end(ap);
-  logs("\r\n", 2);
-}
-
-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');
-}
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/md5.c"
-#endif
-
-
-
-#if defined(MG_ENABLE_MD5) && MG_ENABLE_MD5
-
-static void mg_byte_reverse(unsigned char *buf, unsigned longs) {
-  if (MG_BIG_ENDIAN) {
-    do {
-      uint32_t t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
-                   ((unsigned) buf[1] << 8 | buf[0]);
-      *(uint32_t *) buf = t;
-      buf += 4;
-    } while (--longs);
-  } else {
-    (void) buf, (void) longs;  // Little endian. Do nothing
-  }
-}
-
-#define F1(x, y, z) (z ^ (x & (y ^ z)))
-#define F2(x, y, z) F1(z, x, y)
-#define F3(x, y, z) (x ^ y ^ z)
-#define F4(x, y, z) (y ^ (x | ~z))
-
-#define MD5STEP(f, w, x, y, z, data, s) \
-  (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x)
-
-/*
- * Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
- * initialization constants.
- */
-void mg_md5_init(mg_md5_ctx *ctx) {
-  ctx->buf[0] = 0x67452301;
-  ctx->buf[1] = 0xefcdab89;
-  ctx->buf[2] = 0x98badcfe;
-  ctx->buf[3] = 0x10325476;
-
-  ctx->bits[0] = 0;
-  ctx->bits[1] = 0;
-}
-
-static void mg_md5_transform(uint32_t buf[4], uint32_t const in[16]) {
-  uint32_t a, b, c, d;
-
-  a = buf[0];
-  b = buf[1];
-  c = buf[2];
-  d = buf[3];
-
-  MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
-  MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
-  MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
-  MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
-  MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
-  MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
-  MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
-  MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
-  MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
-  MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
-  MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
-  MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
-  MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
-  MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
-  MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
-  MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
-
-  MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
-  MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
-  MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
-  MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
-  MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
-  MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
-  MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
-  MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
-  MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
-  MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
-  MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
-  MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
-  MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
-  MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
-  MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
-  MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
-
-  MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
-  MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
-  MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
-  MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
-  MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
-  MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
-  MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
-  MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
-  MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
-  MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
-  MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
-  MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
-  MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
-  MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
-  MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
-  MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
-
-  MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
-  MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
-  MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
-  MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
-  MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
-  MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
-  MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
-  MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
-  MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
-  MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
-  MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
-  MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
-  MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
-  MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
-  MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
-  MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
-
-  buf[0] += a;
-  buf[1] += b;
-  buf[2] += c;
-  buf[3] += d;
-}
-
-void mg_md5_update(mg_md5_ctx *ctx, const unsigned char *buf, size_t len) {
-  uint32_t t;
-
-  t = ctx->bits[0];
-  if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) ctx->bits[1]++;
-  ctx->bits[1] += (uint32_t) len >> 29;
-
-  t = (t >> 3) & 0x3f;
-
-  if (t) {
-    unsigned char *p = (unsigned char *) ctx->in + t;
-
-    t = 64 - t;
-    if (len < t) {
-      memcpy(p, buf, len);
-      return;
-    }
-    memcpy(p, buf, t);
-    mg_byte_reverse(ctx->in, 16);
-    mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
-    buf += t;
-    len -= t;
-  }
-
-  while (len >= 64) {
-    memcpy(ctx->in, buf, 64);
-    mg_byte_reverse(ctx->in, 16);
-    mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
-    buf += 64;
-    len -= 64;
-  }
-
-  memcpy(ctx->in, buf, len);
-}
-
-void mg_md5_final(mg_md5_ctx *ctx, unsigned char digest[16]) {
-  unsigned count;
-  unsigned char *p;
-  uint32_t *a;
-
-  count = (ctx->bits[0] >> 3) & 0x3F;
-
-  p = ctx->in + count;
-  *p++ = 0x80;
-  count = 64 - 1 - count;
-  if (count < 8) {
-    memset(p, 0, count);
-    mg_byte_reverse(ctx->in, 16);
-    mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
-    memset(ctx->in, 0, 56);
-  } else {
-    memset(p, 0, count - 8);
-  }
-  mg_byte_reverse(ctx->in, 14);
-
-  a = (uint32_t *) ctx->in;
-  a[14] = ctx->bits[0];
-  a[15] = ctx->bits[1];
-
-  mg_md5_transform(ctx->buf, (uint32_t *) ctx->in);
-  mg_byte_reverse((unsigned char *) ctx->buf, 4);
-  memcpy(digest, ctx->buf, 16);
-  memset((char *) ctx, 0, sizeof(*ctx));
-}
-#endif
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/mqtt.c"
-#endif
-
-
-
-
-
-
-
-
-#define MQTT_CLEAN_SESSION 0x02
-#define MQTT_HAS_WILL 0x04
-#define MQTT_WILL_RETAIN 0x20
-#define MQTT_HAS_PASSWORD 0x40
-#define MQTT_HAS_USER_NAME 0x80
-
-struct mg_mqtt_pmap {
-  uint8_t id;
-  uint8_t type;
-};
-
-static const struct mg_mqtt_pmap s_prop_map[] = {
-    {MQTT_PROP_PAYLOAD_FORMAT_INDICATOR, MQTT_PROP_TYPE_BYTE},
-    {MQTT_PROP_MESSAGE_EXPIRY_INTERVAL, MQTT_PROP_TYPE_INT},
-    {MQTT_PROP_CONTENT_TYPE, MQTT_PROP_TYPE_STRING},
-    {MQTT_PROP_RESPONSE_TOPIC, MQTT_PROP_TYPE_STRING},
-    {MQTT_PROP_CORRELATION_DATA, MQTT_PROP_TYPE_BINARY_DATA},
-    {MQTT_PROP_SUBSCRIPTION_IDENTIFIER, MQTT_PROP_TYPE_VARIABLE_INT},
-    {MQTT_PROP_SESSION_EXPIRY_INTERVAL, MQTT_PROP_TYPE_INT},
-    {MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER, MQTT_PROP_TYPE_STRING},
-    {MQTT_PROP_SERVER_KEEP_ALIVE, MQTT_PROP_TYPE_SHORT},
-    {MQTT_PROP_AUTHENTICATION_METHOD, MQTT_PROP_TYPE_STRING},
-    {MQTT_PROP_AUTHENTICATION_DATA, MQTT_PROP_TYPE_BINARY_DATA},
-    {MQTT_PROP_REQUEST_PROBLEM_INFORMATION, MQTT_PROP_TYPE_BYTE},
-    {MQTT_PROP_WILL_DELAY_INTERVAL, MQTT_PROP_TYPE_INT},
-    {MQTT_PROP_REQUEST_RESPONSE_INFORMATION, MQTT_PROP_TYPE_BYTE},
-    {MQTT_PROP_RESPONSE_INFORMATION, MQTT_PROP_TYPE_STRING},
-    {MQTT_PROP_SERVER_REFERENCE, MQTT_PROP_TYPE_STRING},
-    {MQTT_PROP_REASON_STRING, MQTT_PROP_TYPE_STRING},
-    {MQTT_PROP_RECEIVE_MAXIMUM, MQTT_PROP_TYPE_SHORT},
-    {MQTT_PROP_TOPIC_ALIAS_MAXIMUM, MQTT_PROP_TYPE_SHORT},
-    {MQTT_PROP_TOPIC_ALIAS, MQTT_PROP_TYPE_SHORT},
-    {MQTT_PROP_MAXIMUM_QOS, MQTT_PROP_TYPE_BYTE},
-    {MQTT_PROP_RETAIN_AVAILABLE, MQTT_PROP_TYPE_BYTE},
-    {MQTT_PROP_USER_PROPERTY, MQTT_PROP_TYPE_STRING_PAIR},
-    {MQTT_PROP_MAXIMUM_PACKET_SIZE, MQTT_PROP_TYPE_INT},
-    {MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE, MQTT_PROP_TYPE_BYTE},
-    {MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE, MQTT_PROP_TYPE_BYTE},
-    {MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE, MQTT_PROP_TYPE_BYTE}};
-
-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];
-  buf[0] = (uint8_t) ((cmd << 4) | flags);
-  do {
-    *vlen = len % 0x80;
-    len /= 0x80;
-    if (len > 0) *vlen |= 0x80;
-    vlen++;
-  } while (len > 0 && vlen < &buf[sizeof(buf)]);
-  mg_send(c, buf, (size_t) (vlen - buf));
-}
-
-static void mg_send_u16(struct mg_connection *c, uint16_t value) {
-  mg_send(c, &value, sizeof(value));
-}
-
-static void mg_send_u32(struct mg_connection *c, uint32_t value) {
-  mg_send(c, &value, sizeof(value));
-}
-
-static uint8_t varint_size(size_t length) {
-  uint8_t bytes_needed = 0;
-  do {
-    bytes_needed++;
-    length /= 0x80;
-  } while (length > 0);
-  return bytes_needed;
-}
-
-static size_t encode_varint(uint8_t *buf, size_t value) {
-  size_t len = 0;
-
-  do {
-    uint8_t byte = (uint8_t) (value % 128);
-    value /= 128;
-    if (value > 0) byte |= 0x80;
-    buf[len++] = byte;
-  } while (value > 0);
-
-  return len;
-}
-
-static size_t decode_varint(const uint8_t *buf, size_t len, size_t *value) {
-  size_t multiplier = 1, offset;
-  *value = 0;
-
-  for (offset = 0; offset < 4 && offset < len; offset++) {
-    uint8_t encoded_byte = buf[offset];
-    *value += (encoded_byte & 0x7f) * multiplier;
-    multiplier *= 128;
-
-    if ((encoded_byte & 0x80) == 0) return offset + 1;
-  }
-
-  return 0;
-}
-
-static int mqtt_prop_type_by_id(uint8_t prop_id) {
-  size_t i, num_properties = sizeof(s_prop_map) / sizeof(s_prop_map[0]);
-  for (i = 0; i < num_properties; ++i) {
-    if (s_prop_map[i].id == prop_id) return s_prop_map[i].type;
-  }
-  return -1;  // Property ID not found
-}
-
-// Returns the size of the properties section, without the
-// size of the content's length
-static size_t get_properties_length(struct mg_mqtt_prop *props, size_t count) {
-  size_t i, size = 0;
-  for (i = 0; i < count; i++) {
-    size++;  // identifier
-    switch (mqtt_prop_type_by_id(props[i].id)) {
-      case MQTT_PROP_TYPE_STRING_PAIR:
-        size += (uint32_t) (props[i].val.len + props[i].key.len +
-                            2 * sizeof(uint16_t));
-        break;
-      case MQTT_PROP_TYPE_STRING:
-        size += (uint32_t) (props[i].val.len + sizeof(uint16_t));
-        break;
-      case MQTT_PROP_TYPE_BINARY_DATA:
-        size += (uint32_t) (props[i].val.len + sizeof(uint16_t));
-        break;
-      case MQTT_PROP_TYPE_VARIABLE_INT:
-        size += varint_size((uint32_t) props[i].iv);
-        break;
-      case MQTT_PROP_TYPE_INT:
-        size += (uint32_t) sizeof(uint32_t);
-        break;
-      case MQTT_PROP_TYPE_SHORT:
-        size += (uint32_t) sizeof(uint16_t);
-        break;
-      case MQTT_PROP_TYPE_BYTE:
-        size += (uint32_t) sizeof(uint8_t);
-        break;
-      default:
-        return size;  // cannot parse further down
-    }
-  }
-
-  return size;
-}
-
-// returns the entire size of the properties section, including the
-// size of the variable length of the content
-static size_t get_props_size(struct mg_mqtt_prop *props, size_t count) {
-  size_t size = get_properties_length(props, count);
-  size += varint_size(size);
-  return size;
-}
-
-static void mg_send_mqtt_properties(struct mg_connection *c,
-                                    struct mg_mqtt_prop *props, size_t nprops) {
-  size_t total_size = get_properties_length(props, nprops);
-  uint8_t buf_v[4] = {0, 0, 0, 0};
-  uint8_t buf[4] = {0, 0, 0, 0};
-  size_t i, len = encode_varint(buf, total_size);
-
-  mg_send(c, buf, (size_t) len);
-  for (i = 0; i < nprops; i++) {
-    mg_send(c, &props[i].id, sizeof(props[i].id));
-    switch (mqtt_prop_type_by_id(props[i].id)) {
-      case MQTT_PROP_TYPE_STRING_PAIR:
-        mg_send_u16(c, mg_htons((uint16_t) props[i].key.len));
-        mg_send(c, props[i].key.ptr, props[i].key.len);
-        mg_send_u16(c, mg_htons((uint16_t) props[i].val.len));
-        mg_send(c, props[i].val.ptr, props[i].val.len);
-        break;
-      case MQTT_PROP_TYPE_BYTE:
-        mg_send(c, &props[i].iv, sizeof(uint8_t));
-        break;
-      case MQTT_PROP_TYPE_SHORT:
-        mg_send_u16(c, mg_htons((uint16_t) props[i].iv));
-        break;
-      case MQTT_PROP_TYPE_INT:
-        mg_send_u32(c, mg_htonl((uint32_t) props[i].iv));
-        break;
-      case MQTT_PROP_TYPE_STRING:
-        mg_send_u16(c, mg_htons((uint16_t) props[i].val.len));
-        mg_send(c, props[i].val.ptr, props[i].val.len);
-        break;
-      case MQTT_PROP_TYPE_BINARY_DATA:
-        mg_send_u16(c, mg_htons((uint16_t) props[i].val.len));
-        mg_send(c, props[i].val.ptr, props[i].val.len);
-        break;
-      case MQTT_PROP_TYPE_VARIABLE_INT:
-        len = encode_varint(buf_v, props[i].iv);
-        mg_send(c, buf_v, (size_t) len);
-        break;
-    }
-  }
-}
-
-size_t mg_mqtt_next_prop(struct mg_mqtt_message *msg, struct mg_mqtt_prop *prop,
-                         size_t ofs) {
-  uint8_t *i = (uint8_t *) msg->dgram.ptr + msg->props_start + ofs;
-  uint8_t *end = (uint8_t *) msg->dgram.ptr + msg->dgram.len;
-  size_t new_pos = ofs, len;
-  prop->id = i[0];
-
-  if (ofs >= msg->dgram.len || ofs >= msg->props_start + msg->props_size)
-    return 0;
-  i++, new_pos++;
-
-  switch (mqtt_prop_type_by_id(prop->id)) {
-    case MQTT_PROP_TYPE_STRING_PAIR:
-      prop->key.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
-      prop->key.ptr = (char *) i + 2;
-      i += 2 + prop->key.len;
-      prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
-      prop->val.ptr = (char *) i + 2;
-      new_pos += 2 * sizeof(uint16_t) + prop->val.len + prop->key.len;
-      break;
-    case MQTT_PROP_TYPE_BYTE:
-      prop->iv = (uint8_t) i[0];
-      new_pos++;
-      break;
-    case MQTT_PROP_TYPE_SHORT:
-      prop->iv = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
-      new_pos += sizeof(uint16_t);
-      break;
-    case MQTT_PROP_TYPE_INT:
-      prop->iv = ((uint32_t) i[0] << 24) | ((uint32_t) i[1] << 16) |
-                 ((uint32_t) i[2] << 8) | i[3];
-      new_pos += sizeof(uint32_t);
-      break;
-    case MQTT_PROP_TYPE_STRING:
-      prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
-      prop->val.ptr = (char *) i + 2;
-      new_pos += 2 + prop->val.len;
-      break;
-    case MQTT_PROP_TYPE_BINARY_DATA:
-      prop->val.len = (uint16_t) ((((uint16_t) i[0]) << 8) | i[1]);
-      prop->val.ptr = (char *) i + 2;
-      new_pos += 2 + prop->val.len;
-      break;
-    case MQTT_PROP_TYPE_VARIABLE_INT:
-      len = decode_varint(i, (size_t) (end - i), (size_t *) &prop->iv);
-      new_pos = (!len) ? 0 : new_pos + len;
-      break;
-    default:
-      new_pos = 0;
-  }
-
-  return new_pos;
-}
-
-void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts *opts) {
-  char rnd[10], client_id[21];
-  struct mg_str cid = opts->client_id;
-  size_t total_len = 7 + 1 + 2 + 2;
-  uint8_t hdr[8] = {0, 4, 'M', 'Q', 'T', 'T', opts->version, 0};
-
-  if (cid.len == 0) {
-    mg_random(rnd, sizeof(rnd));
-    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->qos & 3) << 3);  // Connection flags
-  if (opts->user.len > 0) {
-    total_len += 2 + (uint32_t) opts->user.len;
-    hdr[7] |= MQTT_HAS_USER_NAME;
-  }
-  if (opts->pass.len > 0) {
-    total_len += 2 + (uint32_t) opts->pass.len;
-    hdr[7] |= MQTT_HAS_PASSWORD;
-  }
-  if (opts->topic.len > 0 && opts->message.len > 0) {
-    total_len += 4 + (uint32_t) opts->topic.len + (uint32_t) opts->message.len;
-    hdr[7] |= MQTT_HAS_WILL;
-  }
-  if (opts->clean || cid.len == 0) hdr[7] |= MQTT_CLEAN_SESSION;
-  if (opts->retain) hdr[7] |= MQTT_WILL_RETAIN;
-  total_len += (uint32_t) cid.len;
-  if (c->is_mqtt5) {
-    total_len += get_props_size(opts->props, opts->num_props);
-    if (hdr[7] & MQTT_HAS_WILL)
-      total_len += get_props_size(opts->will_props, opts->num_will_props);
-  }
-
-  mg_mqtt_send_header(c, MQTT_CMD_CONNECT, 0, (uint32_t) total_len);
-  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_mqtt_properties(c, opts->props, opts->num_props);
-
-  mg_send_u16(c, mg_htons((uint16_t) cid.len));
-  mg_send(c, cid.ptr, cid.len);
-
-  if (hdr[7] & MQTT_HAS_WILL) {
-    if (c->is_mqtt5)
-      mg_send_mqtt_properties(c, opts->will_props, opts->num_will_props);
-
-    mg_send_u16(c, mg_htons((uint16_t) opts->topic.len));
-    mg_send(c, opts->topic.ptr, opts->topic.len);
-    mg_send_u16(c, mg_htons((uint16_t) opts->message.len));
-    mg_send(c, opts->message.ptr, opts->message.len);
-  }
-  if (opts->user.len > 0) {
-    mg_send_u16(c, mg_htons((uint16_t) opts->user.len));
-    mg_send(c, opts->user.ptr, opts->user.len);
-  }
-  if (opts->pass.len > 0) {
-    mg_send_u16(c, mg_htons((uint16_t) opts->pass.len));
-    mg_send(c, opts->pass.ptr, opts->pass.len);
-  }
-}
-
-void mg_mqtt_pub(struct mg_connection *c, const struct mg_mqtt_opts *opts) {
-  uint8_t flags = (uint8_t) (((opts->qos & 3) << 1) | (opts->retain ? 1 : 0));
-  size_t len = 2 + opts->topic.len + opts->message.len;
-  MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) opts->topic.len,
-            (char *) opts->topic.ptr, (int) opts->message.len,
-            (char *) opts->message.ptr));
-  if (opts->qos > 0) len += 2;
-  if (c->is_mqtt5) len += get_props_size(opts->props, opts->num_props);
-
-  mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, (uint32_t) len);
-  mg_send_u16(c, mg_htons((uint16_t) opts->topic.len));
-  mg_send(c, opts->topic.ptr, opts->topic.len);
-  if (opts->qos > 0) {
-    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_mqtt_properties(c, opts->props, opts->num_props);
-
-  mg_send(c, opts->message.ptr, opts->message.len);
-}
-
-void mg_mqtt_sub(struct mg_connection *c, const struct mg_mqtt_opts *opts) {
-  uint8_t qos_ = opts->qos & 3;
-  size_t plen = c->is_mqtt5 ? get_props_size(opts->props, opts->num_props) : 0;
-  size_t len = 2 + opts->topic.len + 2 + 1 + plen;
-
-  mg_mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, 2, (uint32_t) 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_mqtt_properties(c, opts->props, opts->num_props);
-
-  mg_send_u16(c, mg_htons((uint16_t) opts->topic.len));
-  mg_send(c, opts->topic.ptr, opts->topic.len);
-  mg_send(c, &qos_, sizeof(qos_));
-}
-
-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;
-
-  memset(m, 0, sizeof(*m));
-  m->dgram.ptr = (char *) buf;
-  if (len < 2) return MQTT_INCOMPLETE;
-  m->cmd = (uint8_t) (buf[0] >> 4);
-  m->qos = (buf[0] >> 1) & 3;
-
-  n = len_len = 0;
-  p = (uint8_t *) buf + 1;
-  while ((size_t) (p - buf) < len) {
-    lc = *((uint8_t *) p++);
-    n += (uint32_t) ((lc & 0x7f) << 7 * len_len);
-    len_len++;
-    if (!(lc & 0x80)) break;
-    if (len_len >= 4) return MQTT_MALFORMED;
-  }
-  end = p + n;
-  if ((lc & 0x80) || (end > buf + len)) return MQTT_INCOMPLETE;
-  m->dgram.len = (size_t) (end - buf);
-
-  switch (m->cmd) {
-    case MQTT_CMD_CONNACK:
-      if (end - p < 2) return MQTT_MALFORMED;
-      m->ack = p[1];
-      break;
-    case MQTT_CMD_PUBACK:
-    case MQTT_CMD_PUBREC:
-    case MQTT_CMD_PUBREL:
-    case MQTT_CMD_PUBCOMP:
-    case MQTT_CMD_SUBSCRIBE:
-    case MQTT_CMD_SUBACK:
-    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]);
-      m->topic.ptr = (char *) p + 2;
-      p += 2 + m->topic.len;
-      if (p > end) return MQTT_MALFORMED;
-      if (m->qos > 0) {
-        if (p + 2 > end) return MQTT_MALFORMED;
-        m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]);
-        p += 2;
-      }
-      if (p > end) return MQTT_MALFORMED;
-      if (version == 5 && p + 2 < end) {
-        len_len = (uint32_t) decode_varint(p, (size_t) (end - p), &m->props_size);
-        if (!len_len) return MQTT_MALFORMED;
-        m->props_start = (size_t) (p + len_len - buf);
-        p += len_len + m->props_size;
-      }
-      if (p > end) return MQTT_MALFORMED;
-      m->data.ptr = (char *) p;
-      m->data.len = (size_t) (end - p);
-      break;
-    }
-    default:
-      break;
-  }
-  return MQTT_OK;
-}
-
-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, version, &mm);
-      if (rc == MQTT_MALFORMED) {
-        MG_ERROR(("%lu MQTT malformed message", c->id));
-        c->is_closing = 1;
-        break;
-      } else if (rc == MQTT_OK) {
-        MG_VERBOSE(("%lu MQTT CMD %d len %d [%.*s]", c->id, 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) {
-              MG_DEBUG(("%lu Connected", c->id));
-            } else {
-              MG_ERROR(("%lu MQTT auth failed, code %d", c->id, mm.ack));
-              c->is_closing = 1;
-            }
-            break;
-          case MQTT_CMD_PUBLISH: {
-            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_ntohs(mm.id);
-              uint32_t remaining_len = sizeof(id);
-              if (c->is_mqtt5) remaining_len += 2;  // 3.4.2
-
-              mg_mqtt_send_header(
-                  c,
-                  (uint8_t) (mm.qos == 2 ? MQTT_CMD_PUBREC : MQTT_CMD_PUBACK),
-                  0, remaining_len);
-              mg_send(c, &id, sizeof(id));
-
-              if (c->is_mqtt5) {
-                uint16_t zero = 0;
-                mg_send(c, &zero, sizeof(zero));
-              }
-            }
-            mg_call(c, MG_EV_MQTT_MSG, &mm);  // let the app handle qos stuff
-            break;
-          }
-          case MQTT_CMD_PUBREC: {  // MQTT5: 3.5.2-1 TODO(): variable header rc
-            uint16_t id = mg_ntohs(mm.id);
-            uint32_t remaining_len = sizeof(id);  // MQTT5 3.6.2-1
-            mg_mqtt_send_header(c, MQTT_CMD_PUBREL, 2, remaining_len);
-            mg_send(c, &id, sizeof(id));  // MQTT5 3.6.1-1, flags = 2
-            break;
-          }
-          case MQTT_CMD_PUBREL: {  // MQTT5: 3.6.2-1 TODO(): variable header rc
-            uint16_t id = mg_ntohs(mm.id);
-            uint32_t remaining_len = sizeof(id);  // MQTT5 3.7.2-1
-            mg_mqtt_send_header(c, MQTT_CMD_PUBCOMP, 0, remaining_len);
-            mg_send(c, &id, sizeof(id));
-            break;
-          }
-        }
-        mg_call(c, MG_EV_MQTT_CMD, &mm);
-        mg_iobuf_del(&c->recv, 0, mm.dgram.len);
-      } else {
-        break;
-      }
-    }
-  }
-  (void) ev_data;
-  (void) fn_data;
-}
-
-void mg_mqtt_ping(struct mg_connection *nc) {
-  mg_mqtt_send_header(nc, MQTT_CMD_PINGREQ, 0, 0);
-}
-
-void mg_mqtt_pong(struct mg_connection *nc) {
-  mg_mqtt_send_header(nc, MQTT_CMD_PINGRESP, 0, 0);
-}
-
-void mg_mqtt_disconnect(struct mg_connection *c,
-                        const struct mg_mqtt_opts *opts) {
-  size_t len = 0;
-  if (c->is_mqtt5) len = 1 + get_props_size(opts->props, opts->num_props);
-  mg_mqtt_send_header(c, MQTT_CMD_DISCONNECT, 0, (uint32_t) len);
-
-  if (c->is_mqtt5) {
-    uint8_t zero = 0;
-    mg_send(c, &zero, sizeof(zero));  // reason code
-    mg_send_mqtt_properties(c, opts->props, opts->num_props);
-  }
-}
-
-struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const char *url,
-                                      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) {
-    struct mg_mqtt_opts empty;
-    memset(&empty, 0, sizeof(empty));
-    mg_mqtt_login(c, opts == NULL ? &empty : opts);
-    c->pfn = mqtt_cb;
-  }
-  return c;
-}
-
-struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url,
-                                     mg_event_handler_t fn, void *fn_data) {
-  struct mg_connection *c = mg_listen(mgr, url, fn, fn_data);
-  if (c != NULL) c->pfn = mqtt_cb, c->pfn_data = mgr;
-  return c;
-}
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/net.c"
-#endif
-
-
-
-
-
-
-
-
-size_t mg_vprintf(struct mg_connection *c, const char *fmt, va_list *ap) {
-  size_t old = c->send.len;
-  mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
-  return c->send.len - old;
-}
-
-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);
-  va_end(ap);
-  return len;
-}
-
-static bool mg_atonl(struct mg_str str, struct mg_addr *addr) {
-  uint32_t localhost = mg_htonl(0x7f000001);
-  if (mg_vcasecmp(&str, "localhost") != 0) return false;
-  memcpy(addr->ip, &localhost, sizeof(uint32_t));
-  addr->is_ip6 = false;
-  return true;
-}
-
-static bool mg_atone(struct mg_str str, struct mg_addr *addr) {
-  if (str.len > 0) return false;
-  memset(addr->ip, 0, sizeof(addr->ip));
-  addr->is_ip6 = false;
-  return true;
-}
-
-static bool mg_aton4(struct mg_str str, struct mg_addr *addr) {
-  uint8_t data[4] = {0, 0, 0, 0};
-  size_t i, num_dots = 0;
-  for (i = 0; i < str.len; i++) {
-    if (str.ptr[i] >= '0' && str.ptr[i] <= '9') {
-      int octet = data[num_dots] * 10 + (str.ptr[i] - '0');
-      if (octet > 255) return false;
-      data[num_dots] = (uint8_t) octet;
-    } else if (str.ptr[i] == '.') {
-      if (num_dots >= 3 || i == 0 || str.ptr[i - 1] == '.') return false;
-      num_dots++;
-    } else {
-      return false;
-    }
-  }
-  if (num_dots != 3 || str.ptr[i - 1] == '.') return false;
-  memcpy(&addr->ip, data, sizeof(data));
-  addr->is_ip6 = false;
-  return true;
-}
-
-static bool mg_v4mapped(struct mg_str str, struct mg_addr *addr) {
-  int i;
-  uint32_t ipv4;
-  if (str.len < 14) return false;
-  if (str.ptr[0] != ':' || str.ptr[1] != ':' || str.ptr[6] != ':') return false;
-  for (i = 2; i < 6; i++) {
-    if (str.ptr[i] != 'f' && str.ptr[i] != 'F') return false;
-  }
-  // struct mg_str s = mg_str_n(&str.ptr[7], str.len - 7);
-  if (!mg_aton4(mg_str_n(&str.ptr[7], str.len - 7), addr)) return false;
-  memcpy(&ipv4, addr->ip, sizeof(ipv4));
-  memset(addr->ip, 0, sizeof(addr->ip));
-  addr->ip[10] = addr->ip[11] = 255;
-  memcpy(&addr->ip[12], &ipv4, 4);
-  addr->is_ip6 = true;
-  return true;
-}
-
-static bool mg_aton6(struct mg_str str, struct mg_addr *addr) {
-  size_t i, j = 0, n = 0, dc = 42;
-  if (str.len > 2 && str.ptr[0] == '[') str.ptr++, str.len -= 2;
-  if (mg_v4mapped(str, addr)) return true;
-  for (i = 0; i < str.len; i++) {
-    if ((str.ptr[i] >= '0' && str.ptr[i] <= '9') ||
-        (str.ptr[i] >= 'a' && str.ptr[i] <= 'f') ||
-        (str.ptr[i] >= 'A' && str.ptr[i] <= 'F')) {
-      unsigned long val;
-      if (i > j + 3) return false;
-      // MG_DEBUG(("%zu %zu [%.*s]", i, j, (int) (i - j + 1), &str.ptr[j]));
-      val = mg_unhexn(&str.ptr[j], i - j + 1);
-      addr->ip[n] = (uint8_t) ((val >> 8) & 255);
-      addr->ip[n + 1] = (uint8_t) (val & 255);
-    } else if (str.ptr[i] == ':') {
-      j = i + 1;
-      if (i > 0 && str.ptr[i - 1] == ':') {
-        dc = n;  // Double colon
-        if (i > 1 && str.ptr[i - 2] == ':') return false;
-      } else if (i > 0) {
-        n += 2;
-      }
-      if (n > 14) return false;
-      addr->ip[n] = addr->ip[n + 1] = 0;  // For trailing ::
-    } else {
-      return false;
-    }
-  }
-  if (n < 14 && dc == 42) return false;
-  if (n < 14) {
-    memmove(&addr->ip[dc + (14 - n)], &addr->ip[dc], n - dc + 2);
-    memset(&addr->ip[dc], 0, 14 - n);
-  }
-
-  addr->is_ip6 = true;
-  return true;
-}
-
-bool mg_aton(struct mg_str str, struct mg_addr *addr) {
-  // 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);
-}
-
-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->send.align = c->recv.align = MG_IO_SIZE;
-    c->id = ++mgr->nextid;
-  }
-  return c;
-}
-
-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 %p closed", c->id, c->fd));
-
-  mg_tls_free(c);
-  mg_iobuf_free(&c->recv);
-  mg_iobuf_free(&c->send);
-  memset(c, 0, sizeof(*c));
-  free(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 (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->fd = (void *) (size_t) MG_INVALID_SOCKET;
-    c->fn = fn;
-    c->is_client = true;
-    c->fn_data = fn_data;
-    MG_DEBUG(("%lu %p %s", c->id, c->fd, url));
-    mg_call(c, MG_EV_OPEN, (void *) url);
-    mg_resolve(c, url);
-    if (mg_url_is_ssl(url)) {
-      struct mg_str host = mg_url_host(url);
-      mg_tls_init(c, host);
-    }
-  }
-  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);
-    if (mg_url_is_ssl(url)) c->is_tls = 1; // Accepted connection must
-    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_EPOLL_ADD(c);
-    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));
-  if (t != NULL) {
-    mg_timer_init(&mgr->timers, t, milliseconds, flags, fn, arg);
-    t->id = mgr->timerid++;
-  }
-  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_ENABLE_FREERTOS_TCP
-  FreeRTOS_DeleteSocketSet(mgr->ss);
-#endif
-  MG_DEBUG(("All connections closed"));
-#if MG_ENABLE_EPOLL
-  if (mgr->epoll_fd >= 0) close(mgr->epoll_fd), mgr->epoll_fd = -1;
-#endif
-  mg_tls_ctx_free(mgr);
-}
-
-void mg_mgr_init(struct mg_mgr *mgr) {
-  memset(mgr, 0, sizeof(*mgr));
-#if MG_ENABLE_EPOLL
-  if ((mgr->epoll_fd = epoll_create1(0)) < 0) MG_ERROR(("epoll: %d", errno));
-#else
-  mgr->epoll_fd = -1;
-#endif
-#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK
-  // clang-format off
-  { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); }
-  // clang-format on
-#elif MG_ENABLE_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/net_builtin.c"
-#endif
-
-
-#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP
-#define MG_EPHEMERAL_PORT_BASE 32768
-#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a))))
-
-#ifndef MIP_TCP_KEEPALIVE_MS
-#define MIP_TCP_KEEPALIVE_MS 45000  // TCP keep-alive period, ms
-#endif
-
-#define MIP_TCP_ACK_MS 150    // Timeout for ACKing
-#define MIP_TCP_ARP_MS 100    // Timeout for ARP response
-#define MIP_TCP_SYN_MS 15000  // Timeout for connection establishment
-#define MIP_TCP_FIN_MS 1000   // Timeout for closing connection
-
-struct connstate {
-  uint32_t seq, ack;           // TCP seq/ack counters
-  uint64_t timer;              // TCP keep-alive / ACK timer
-  uint8_t mac[6];              // Peer MAC address
-  uint8_t ttype;               // Timer type. 0: ack, 1: keep-alive
-#define MIP_TTYPE_KEEPALIVE 0  // Connection is idle for long, send keepalive
-#define MIP_TTYPE_ACK 1        // Peer sent us data, we have to ack it soon
-#define MIP_TTYPE_ARP 2        // ARP resolve sent, waiting for response
-#define MIP_TTYPE_SYN 3        // SYN sent, waiting for response
-#define MIP_TTYPE_FIN 4  // FIN sent, waiting until terminating the connection
-  uint8_t tmiss;         // Number of keep-alive misses
-  struct mg_iobuf raw;   // For TLS only. Incoming raw data
-};
-
-#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 mg_str raw;  // Raw packet data
-  struct mg_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 send_syn(struct mg_connection *c);
-
-static void mkpay(struct pkt *pkt, void *p) {
-  pkt->pay =
-      mg_str_n((char *) p, (size_t) (&pkt->raw.ptr[pkt->raw.len] - (char *) 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 mg_htons(~sum & 0xffff);
-}
-
-static uint16_t ipcsum(const void *buf, size_t len) {
-  uint32_t sum = csumup(0, buf, len);
-  return csumfin(sum);
-}
-
-static void settmout(struct mg_connection *c, uint8_t type) {
-  struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
-  struct connstate *s = (struct connstate *) (c + 1);
-  unsigned n = type == MIP_TTYPE_ACK   ? MIP_TCP_ACK_MS
-               : type == MIP_TTYPE_ARP ? MIP_TCP_ARP_MS
-               : type == MIP_TTYPE_SYN ? MIP_TCP_SYN_MS
-               : type == MIP_TTYPE_FIN ? MIP_TCP_FIN_MS
-                                       : MIP_TCP_KEEPALIVE_MS;
-  s->timer = ifp->now + n;
-  s->ttype = type;
-  MG_VERBOSE(("%lu %d -> %llx", c->id, type, s->timer));
-}
-
-static size_t ether_output(struct mg_tcpip_if *ifp, size_t len) {
-  // size_t min = 64;  // Pad short frames to 64 bytes (minimum Ethernet size)
-  // if (len < min) memset(ifp->tx.ptr + len, 0, min - len), len = min;
-  // mg_hexdump(ifp->tx.ptr, len);
-  size_t n = ifp->driver->tx(ifp->tx.ptr, len, ifp);
-  if (n == len) ifp->nsent++;
-  return n;
-}
-
-static void arp_ask(struct mg_tcpip_if *ifp, uint32_t ip) {
-  struct eth *eth = (struct eth *) ifp->tx.ptr;
-  struct arp *arp = (struct arp *) (eth + 1);
-  memset(eth->dst, 255, sizeof(eth->dst));
-  memcpy(eth->src, ifp->mac, sizeof(eth->src));
-  eth->type = mg_htons(0x806);
-  memset(arp, 0, sizeof(*arp));
-  arp->fmt = mg_htons(1), arp->pro = mg_htons(0x800), arp->hlen = 6,
-  arp->plen = 4;
-  arp->op = mg_htons(1), arp->tpa = ip, arp->spa = ifp->ip;
-  memcpy(arp->sha, ifp->mac, sizeof(arp->sha));
-  ether_output(ifp, PDIFF(eth, arp + 1));
-}
-
-static void onstatechange(struct mg_tcpip_if *ifp) {
-  if (ifp->state == MG_TCPIP_STATE_READY) {
-    MG_INFO(("READY, IP: %M", mg_print_ip4, &ifp->ip));
-    MG_INFO(("       GW: %M", mg_print_ip4, &ifp->gw));
-    MG_INFO(("      MAC: %M", mg_print_mac, &ifp->mac));
-    arp_ask(ifp, ifp->gw);
-  } else if (ifp->state == MG_TCPIP_STATE_UP) {
-    MG_ERROR(("Link up"));
-    srand((unsigned int) mg_millis());
-  } else if (ifp->state == MG_TCPIP_STATE_DOWN) {
-    MG_ERROR(("Link down"));
-  }
-}
-
-static struct ip *tx_ip(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
-                        uint8_t proto, uint32_t ip_src, uint32_t ip_dst,
-                        size_t plen) {
-  struct eth *eth = (struct eth *) ifp->tx.ptr;
-  struct ip *ip = (struct ip *) (eth + 1);
-  memcpy(eth->dst, mac_dst, sizeof(eth->dst));
-  memcpy(eth->src, ifp->mac, sizeof(eth->src));  // Use our MAC
-  eth->type = mg_htons(0x800);
-  memset(ip, 0, sizeof(*ip));
-  ip->ver = 0x45;   // Version 4, header length 5 words
-  ip->frag = 0x40;  // Don't fragment
-  ip->len = mg_htons((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 mg_tcpip_if *ifp, uint8_t *mac_dst, 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, mac_dst, 17, ip_src, ip_dst, len + sizeof(struct udp));
-  struct udp *udp = (struct udp *) (ip + 1);
-  // MG_DEBUG(("UDP XX LEN %d %d", (int) len, (int) ifp->tx.len));
-  udp->sport = sport;
-  udp->dport = dport;
-  udp->len = mg_htons((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", (int) len, (int) ifp->frame_len));
-  ether_output(ifp, sizeof(struct eth) + sizeof(*ip) + sizeof(*udp) + len);
-}
-
-static void tx_dhcp(struct mg_tcpip_if *ifp, uint8_t *mac_dst, uint32_t ip_src,
-                    uint32_t ip_dst, uint8_t *opts, size_t optslen,
-                    bool ciaddr) {
-  // https://datatracker.ietf.org/doc/html/rfc2132#section-9.6
-  struct dhcp dhcp = {1, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
-  dhcp.magic = mg_htonl(0x63825363);
-  memcpy(&dhcp.hwaddr, ifp->mac, sizeof(ifp->mac));
-  memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid));
-  memcpy(&dhcp.options, opts, optslen);
-  if (ciaddr) dhcp.ciaddr = ip_src;
-  tx_udp(ifp, mac_dst, ip_src, mg_htons(68), ip_dst, mg_htons(67), &dhcp,
-         sizeof(dhcp));
-}
-
-static const uint8_t broadcast[] = {255, 255, 255, 255, 255, 255};
-
-// RFC-2131 #4.3.6, #4.4.1
-static void tx_dhcp_request_sel(struct mg_tcpip_if *ifp, uint32_t ip_req,
-                                uint32_t ip_srv) {
-  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, &ip_srv, sizeof(ip_srv));
-  memcpy(opts + 20, &ip_req, sizeof(ip_req));
-  tx_dhcp(ifp, (uint8_t *) broadcast, 0, 0xffffffff, opts, sizeof(opts), false);
-  MG_DEBUG(("DHCP req sent"));
-}
-
-// RFC-2131 #4.3.6, #4.4.5 (renewing: unicast, rebinding: bcast)
-static void tx_dhcp_request_re(struct mg_tcpip_if *ifp, uint8_t *mac_dst,
-                               uint32_t ip_src, uint32_t ip_dst) {
-  uint8_t opts[] = {
-      53, 1, 3,  // Type: DHCP request
-      255        // End of options
-  };
-  tx_dhcp(ifp, mac_dst, ip_src, ip_dst, opts, sizeof(opts), true);
-  MG_DEBUG(("DHCP req sent"));
-}
-
-static void tx_dhcp_discover(struct mg_tcpip_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, (uint8_t *) broadcast, 0, 0xffffffff, opts, sizeof(opts), false);
-  MG_DEBUG(("DHCP discover sent. Our MAC: %M", mg_print_mac, ifp->mac));
-}
-
-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_arplooking && pkt->arp &&
-        memcmp(&pkt->arp->spa, c->rem.ip, sizeof(pkt->arp->spa)) == 0)
-      break;
-    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_arp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
-  if (pkt->arp->op == mg_htons(1) && pkt->arp->tpa == ifp->ip) {
-    // ARP request. Make a response, then send
-    // MG_DEBUG(("ARP op %d %M: %M", mg_ntohs(pkt->arp->op), mg_print_ip4,
-    //          &pkt->arp->spa, mg_print_ip4, &pkt->arp->tpa));
-    struct eth *eth = (struct eth *) ifp->tx.ptr;
-    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 = mg_htons(0x806);
-    *arp = *pkt->arp;
-    arp->op = mg_htons(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: tell %M we're %M", mg_print_ip4, &arp->tpa, mg_print_mac,
-              &ifp->mac));
-    ether_output(ifp, PDIFF(eth, arp + 1));
-  } else if (pkt->arp->op == mg_htons(2)) {
-    if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) != 0) return;
-    if (pkt->arp->spa == ifp->gw) {
-      // Got response for the GW ARP request. Set ifp->gwmac
-      memcpy(ifp->gwmac, pkt->arp->sha, sizeof(ifp->gwmac));
-    } else {
-      struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
-      if (c != NULL && c->is_arplooking) {
-        struct connstate *s = (struct connstate *) (c + 1);
-        memcpy(s->mac, pkt->arp->sha, sizeof(s->mac));
-        MG_DEBUG(("%lu ARP resolved %M -> %M", c->id, mg_print_ip4, c->rem.ip,
-                  mg_print_mac, s->mac));
-        c->is_arplooking = 0;
-        send_syn(c);
-        settmout(c, MIP_TTYPE_SYN);
-      }
-    }
-  }
-}
-
-static void rx_icmp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
-  // MG_DEBUG(("ICMP %d", (int) len));
-  if (pkt->icmp->type == 8 && pkt->ip != NULL && pkt->ip->dst == ifp->ip) {
-    size_t hlen = sizeof(struct eth) + sizeof(struct ip) + sizeof(struct icmp);
-    size_t space = ifp->tx.len - hlen, plen = pkt->pay.len;
-    if (plen > space) plen = space;
-    struct ip *ip = tx_ip(ifp, pkt->eth->src, 1, ifp->ip, pkt->ip->src,
-                          sizeof(struct icmp) + plen);
-    struct icmp *icmp = (struct icmp *) (ip + 1);
-    memset(icmp, 0, sizeof(*icmp));        // Set csum to 0
-    memcpy(icmp + 1, pkt->pay.ptr, plen);  // Copy RX payload to TX
-    icmp->csum = ipcsum(icmp, sizeof(*icmp) + plen);
-    ether_output(ifp, hlen + plen);
-  }
-}
-
-static void rx_dhcp_client(struct mg_tcpip_if *ifp, struct pkt *pkt) {
-  uint32_t ip = 0, gw = 0, mask = 0, lease = 0;
-  uint8_t msgtype = 0, state = ifp->state;
-  // perform size check first, then access fields
-  uint8_t *p = pkt->dhcp->options,
-          *end = (uint8_t *) &pkt->raw.ptr[pkt->raw.len];
-  if (end < (uint8_t *) (pkt->dhcp + 1)) return;
-  if (memcmp(&pkt->dhcp->xid, ifp->mac + 2, sizeof(pkt->dhcp->xid))) return;
-  while (p + 1 < end && p[0] != 255) {  // Parse options RFC-1533 #9
-    if (p[0] == 1 && p[1] == sizeof(ifp->mask) && p + 6 < end) {  // Mask
-      memcpy(&mask, p + 2, sizeof(mask));
-    } else if (p[0] == 3 && p[1] == sizeof(ifp->gw) && p + 6 < end) {  // GW
-      memcpy(&gw, p + 2, sizeof(gw));
-      ip = pkt->dhcp->yiaddr;
-    } else if (p[0] == 51 && p[1] == 4 && p + 6 < end) {  // Lease
-      memcpy(&lease, p + 2, sizeof(lease));
-      lease = mg_ntohl(lease);
-    } else if (p[0] == 53 && p[1] == 1 && p + 6 < end) {  // Msg Type
-      msgtype = p[2];
-    }
-    p += p[1] + 2;
-  }
-  // Process message type, RFC-1533 (9.4); RFC-2131 (3.1, 4)
-  if (msgtype == 6 && ifp->ip == ip) {  // DHCPNACK, release IP
-    ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0;
-  } else if (msgtype == 2 && ifp->state == MG_TCPIP_STATE_UP && ip && gw &&
-             lease) {                                 // DHCPOFFER
-    tx_dhcp_request_sel(ifp, ip, pkt->dhcp->siaddr);  // select IP, (4.4.1)
-    ifp->state = MG_TCPIP_STATE_REQ;                  // REQUESTING state
-  } else if (msgtype == 5) {                          // DHCPACK
-    if (ifp->state == MG_TCPIP_STATE_REQ && ip && gw && lease) {  // got an IP
-      ifp->lease_expire = ifp->now + lease * 1000;
-      MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire / 1000));
-      // assume DHCP server = router until ARP resolves
-      memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac));
-      ifp->ip = ip, ifp->gw = gw, ifp->mask = mask;
-      ifp->state = MG_TCPIP_STATE_READY;  // BOUND state
-      uint64_t rand;
-      mg_random(&rand, sizeof(rand));
-      srand((unsigned int) (rand + mg_millis()));
-    } else if (ifp->state == MG_TCPIP_STATE_READY && ifp->ip == ip) {  // renew
-      ifp->lease_expire = ifp->now + lease * 1000;
-      MG_INFO(("Lease: %u sec (%lld)", lease, ifp->lease_expire / 1000));
-    }  // TODO(): accept provided T1/T2 and store server IP for renewal (4.4)
-  }
-  if (ifp->state != state) onstatechange(ifp);
-}
-
-// Simple DHCP server that assigns a next IP address: ifp->ip + 1
-static void rx_dhcp_server(struct mg_tcpip_if *ifp, struct pkt *pkt) {
-  uint8_t op = 0, *p = pkt->dhcp->options,
-          *end = (uint8_t *) &pkt->raw.ptr[pkt->raw.len];
-  if (end < (uint8_t *) (pkt->dhcp + 1)) return;
-  // struct dhcp *req = pkt->dhcp;
-  struct dhcp res = {2, 1, 6, 0, 0, 0, 0, 0, 0, 0, 0, {0}, 0, {0}};
-  res.yiaddr = ifp->ip;
-  ((uint8_t *) (&res.yiaddr))[3]++;                // Offer our IP + 1
-  while (p + 1 < end && p[0] != 255) {             // Parse options
-    if (p[0] == 53 && p[1] == 1 && p + 2 < end) {  // Message type
-      op = p[2];
-    }
-    p += p[1] + 2;
-  }
-  if (op == 1 || op == 3) {         // DHCP Discover or DHCP Request
-    uint8_t msg = op == 1 ? 2 : 5;  // Message type: DHCP OFFER or DHCP ACK
-    uint8_t opts[] = {
-        53, 1, msg,                 // Message type
-        1,  4, 0,   0,   0,   0,    // Subnet mask
-        54, 4, 0,   0,   0,   0,    // Server ID
-        12, 3, 'm', 'i', 'p',       // Host name: "mip"
-        51, 4, 255, 255, 255, 255,  // Lease time
-        255                         // End of options
-    };
-    memcpy(&res.hwaddr, pkt->dhcp->hwaddr, 6);
-    memcpy(opts + 5, &ifp->mask, sizeof(ifp->mask));
-    memcpy(opts + 11, &ifp->ip, sizeof(ifp->ip));
-    memcpy(&res.options, opts, sizeof(opts));
-    res.magic = pkt->dhcp->magic;
-    res.xid = pkt->dhcp->xid;
-    // memcpy(ifp->gwmac, pkt->eth->src, sizeof(ifp->gwmac));
-    tx_udp(ifp, pkt->eth->src, ifp->ip, mg_htons(67),
-           op == 1 ? ~0U : res.yiaddr, mg_htons(68), &res, sizeof(res));
-  }
-}
-
-static void rx_udp(struct mg_tcpip_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 {
-    c->rem.port = pkt->udp->sport;
-    memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t));
-    struct connstate *s = (struct connstate *) (c + 1);
-    memcpy(s->mac, pkt->eth->src, sizeof(s->mac));
-    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.ptr, pkt->pay.len);
-      c->recv.len += pkt->pay.len;
-      mg_call(c, MG_EV_READ, &pkt->pay.len);
-    }
-  }
-}
-
-static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *dst_mac, 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, dst_mac, 6, ifp->ip, dst_ip, sizeof(struct tcp) + len);
-  struct tcp *tcp = (struct tcp *) (ip + 1);
-  memset(tcp, 0, sizeof(*tcp));
-  if (buf != NULL && len) 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);
-  MG_VERBOSE(("TCP %M:%hu -> %M:%hu fl %x len %u", mg_print_ip4, &ip->src,
-              mg_ntohs(tcp->sport), mg_print_ip4, &ip->dst,
-              mg_ntohs(tcp->dport), tcp->flags, (int) len));
-  // mg_hexdump(ifp->tx.ptr, PDIFF(ifp->tx.ptr, tcp + 1) + len);
-  return ether_output(ifp, PDIFF(ifp->tx.ptr, tcp + 1) + len);
-}
-
-static size_t tx_tcp_pkt(struct mg_tcpip_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->eth->src, 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);
-  if (c == NULL) {
-    MG_ERROR(("OOM"));
-    return NULL;
-  }
-  struct connstate *s = (struct connstate *) (c + 1);
-  s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq);
-  memcpy(s->mac, pkt->eth->src, sizeof(s->mac));
-  settmout(c, MIP_TTYPE_KEEPALIVE);
-  memcpy(c->rem.ip, &pkt->ip->src, sizeof(uint32_t));
-  c->rem.port = pkt->tcp->sport;
-  MG_DEBUG(("%lu accepted %M", c->id, mg_print_ip_port, &c->rem));
-  LIST_ADD_HEAD(struct mg_connection, &lsn->mgr->conns, c);
-  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);
-  if (lsn->is_tls) mg_tls_init(c, mg_str(""));
-  return c;
-}
-
-long mg_io_send(struct mg_connection *c, const void *buf, size_t len) {
-  struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
-  struct connstate *s = (struct connstate *) (c + 1);
-  uint32_t rem_ip;
-  memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
-  if (c->is_udp) {
-    size_t max_headers_len = 14 + 24 /* max IP */ + 8 /* UDP */;
-    if (len + max_headers_len > ifp->tx.len) {
-      len = ifp->tx.len - max_headers_len;
-    }
-    tx_udp(ifp, s->mac, ifp->ip, c->loc.port, rem_ip, c->rem.port, buf, len);
-  } else {
-    size_t max_headers_len = 14 + 24 /* max IP */ + 60 /* max TCP */;
-    if (len + max_headers_len > ifp->tx.len)
-      len = ifp->tx.len - max_headers_len;
-    if (tx_tcp(ifp, s->mac, rem_ip, TH_PUSH | TH_ACK, c->loc.port, c->rem.port,
-               mg_htonl(s->seq), mg_htonl(s->ack), buf, len) > 0) {
-      s->seq += (uint32_t) len;
-      if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE);
-    } else {
-      return MG_IO_ERR;
-    }
-  }
-  return (long) len;
-}
-
-long mg_io_recv(struct mg_connection *c, void *buf, size_t len) {
-  struct connstate *s = (struct connstate *) (c + 1);
-  if (s->raw.len == 0) return MG_IO_WAIT;
-  if (len > s->raw.len) len = s->raw.len;
-  memcpy(buf, s->raw.buf, len);
-  mg_iobuf_del(&s->raw, 0, len);
-  return (long) len;
-}
-
-static void read_conn(struct mg_connection *c, struct pkt *pkt) {
-  struct connstate *s = (struct connstate *) (c + 1);
-  struct mg_iobuf *io = c->is_tls ? &s->raw : &c->recv;
-  uint32_t seq = mg_ntohl(pkt->tcp->seq);
-  s->raw.align = c->recv.align;
-  uint32_t rem_ip;
-  memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
-  if (pkt->tcp->flags & TH_FIN) {
-    // If we initiated the closure, we reply with ACK upon receiving FIN
-    // If we didn't initiate it, we reply with FIN as part of the normal TCP
-    // closure process
-    uint8_t flags = TH_ACK;
-    s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len + 1);
-    if (c->is_draining && s->ttype == MIP_TTYPE_FIN) {
-      if (s->seq == mg_htonl(pkt->tcp->ack))
-        // Checking for simultaneous closure
-        s->seq++;
-      else
-        s->seq = mg_htonl(pkt->tcp->ack);
-    } else {
-      flags |= TH_FIN;
-      c->is_draining = 1;
-      settmout(c, MIP_TTYPE_FIN);
-    }
-    tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, flags,
-           c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
-  } else if (pkt->pay.len == 0) {
-    // TODO(cpq): handle this peer's ACK
-  } else if (seq != s->ack) {
-    uint32_t ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len);
-    if (s->ack == ack) {
-      MG_VERBOSE(("ignoring duplicate pkt"));
-    } else {
-      MG_VERBOSE(("SEQ != ACK: %x %x %x", seq, s->ack, ack));
-      tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, TH_ACK,
-             c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "",
-             0);
-    }
-  } else if (io->size - io->len < pkt->pay.len &&
-             !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
-    mg_error(c, "oom");
-  } else {
-    // Copy TCP payload into the IO buffer. If the connection is plain text,
-    // we copy to c->recv. If the connection is TLS, this data is encrypted,
-    // therefore we copy that encrypted data to the s->raw iobuffer instead,
-    // and then call mg_tls_recv() to decrypt it. NOTE: mg_tls_recv() will
-    // call back mg_io_recv() which grabs raw data from s->raw
-    memcpy(&io->buf[io->len], pkt->pay.ptr, pkt->pay.len);
-    io->len += pkt->pay.len;
-
-    MG_VERBOSE(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq), s->ack));
-    // Advance ACK counter
-    s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len);
-#if 0
-    // Send ACK immediately
-    uint32_t rem_ip;
-    memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
-    MG_DEBUG(("  imm ACK", c->id, mg_htonl(pkt->tcp->seq), s->ack));
-    tx_tcp((struct mg_tcpip_if *) c->mgr->priv, s->mac, rem_ip, TH_ACK, c->loc.port,
-           c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
-#else
-    // if not already running, setup a timer to send an ACK later
-    if (s->ttype != MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_ACK);
-#endif
-
-    if (c->is_tls) {
-      // TLS connection. Make room for decrypted data in c->recv
-      io = &c->recv;
-      if (io->size - io->len < pkt->pay.len &&
-          !mg_iobuf_resize(io, io->len + pkt->pay.len)) {
-        mg_error(c, "oom");
-      } else {
-        // Decrypt data directly into c->recv
-        long n = mg_tls_recv(c, &io->buf[io->len], io->size - io->len);
-        if (n == MG_IO_ERR) {
-          mg_error(c, "TLS recv error");
-        } else if (n > 0) {
-          // Decrypted successfully - trigger MG_EV_READ
-          io->len += (size_t) n;
-          mg_call(c, MG_EV_READ, &n);
-        }
-      }
-    } else {
-      // Plain text connection, data is already in c->recv, trigger
-      // MG_EV_READ
-      mg_call(c, MG_EV_READ, &pkt->pay.len);
-    }
-  }
-}
-
-static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
-  struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
-  struct connstate *s = c == NULL ? NULL : (struct connstate *) (c + 1);
-#if 0
-  MG_INFO(("%lu %hhu %d", c ? c->id : 0, pkt->tcp->flags, (int) pkt->pay.len));
-#endif
-  if (c != NULL && c->is_connecting && pkt->tcp->flags & (TH_SYN | TH_ACK)) {
-    s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq) + 1;
-    tx_tcp_pkt(ifp, pkt, TH_ACK, pkt->tcp->ack, NULL, 0);
-    c->is_connecting = 0;  // Client connected
-    settmout(c, MIP_TTYPE_KEEPALIVE);
-    mg_call(c, MG_EV_CONNECT, NULL);  // Let user know
-  } else if (c != NULL && c->is_connecting) {
-    // mg_hexdump(pkt->raw.ptr, pkt->raw.len);
-    tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
-  } else if (c != NULL && pkt->tcp->flags & TH_RST) {
-    mg_error(c, "peer RST");  // RFC-1122 4.2.2.13
-  } else if (c != NULL) {
-#if 0
-    MG_DEBUG(("%lu %d %M:%hu -> %M:%hu", c->id, (int) pkt->raw.len,
-              mg_print_ip4, &pkt->ip->src, mg_ntohs(pkt->tcp->sport),
-              mg_print_ip4, &pkt->ip->dst, mg_ntohs(pkt->tcp->dport)));
-    mg_hexdump(pkt->pay.ptr, pkt->pay.len);
-#endif
-    s->tmiss = 0;                         // Reset missed keep-alive counter
-    if (s->ttype == MIP_TTYPE_KEEPALIVE)  // Advance keep-alive timer
-      settmout(c,
-               MIP_TTYPE_KEEPALIVE);  // unless a former ACK timeout is pending
-    read_conn(c, pkt);  // Override timer with ACK timeout if needed
-  } 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_RST) {
-    if (c->is_accepted) mg_error(c, "peer RST");  // RFC-1122 4.2.2.13
-    // ignore RST if not connected
-  } 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 if (!c->is_accepted) {  // no peer
-    tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0);
-  } else {
-    // MG_VERBOSE(("dropped silently.."));
-  }
-}
-
-static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) {
-  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;
-    mkpay(pkt, pkt->udp + 1);
-    MG_VERBOSE(("UDP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src,
-                mg_ntohs(pkt->udp->sport), mg_print_ip4, &pkt->ip->dst,
-                mg_ntohs(pkt->udp->dport), (int) pkt->pay.len));
-    if (ifp->enable_dhcp_client && pkt->udp->dport == mg_htons(68)) {
-      pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
-      mkpay(pkt, pkt->dhcp + 1);
-      rx_dhcp_client(ifp, pkt);
-    } else if (ifp->enable_dhcp_server && pkt->udp->dport == mg_htons(67)) {
-      pkt->dhcp = (struct dhcp *) (pkt->udp + 1);
-      mkpay(pkt, pkt->dhcp + 1);
-      rx_dhcp_server(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);
-    MG_VERBOSE(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src,
-                mg_ntohs(pkt->tcp->sport), mg_print_ip4, &pkt->ip->dst,
-                mg_ntohs(pkt->tcp->dport), (int) pkt->pay.len));
-    rx_tcp(ifp, pkt);
-  }
-}
-
-static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
-  // MG_DEBUG(("IP %d", (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->ip6->proto == 17) {
-    pkt->udp = (struct udp *) (pkt->ip6 + 1);
-    if (pkt->pay.len < sizeof(*pkt->udp)) return;
-    // MG_DEBUG(("  UDP %u %u -> %u", len, mg_htons(udp->sport),
-    // mg_htons(udp->dport)));
-    mkpay(pkt, pkt->udp + 1);
-  }
-}
-
-static void mg_tcpip_rx(struct mg_tcpip_if *ifp, void *buf, size_t len) {
-  struct pkt pkt;
-  memset(&pkt, 0, sizeof(pkt));
-  pkt.raw.ptr = (char *) buf;
-  pkt.raw.len = len;
-  pkt.eth = (struct eth *) buf;
-  // mg_hexdump(buf, len > 16 ? 16: len);
-  if (pkt.raw.len < sizeof(*pkt.eth)) return;  // Truncated - runt?
-  if (ifp->enable_mac_check &&
-      memcmp(pkt.eth->dst, ifp->mac, sizeof(pkt.eth->dst)) != 0 &&
-      memcmp(pkt.eth->dst, broadcast, sizeof(pkt.eth->dst)) != 0)
-    return;
-  if (ifp->enable_crc32_check && len > 4) {
-    len -= 4;  // TODO(scaprile): check on bigendian
-    uint32_t crc = mg_crc32(0, (const char *) buf, len);
-    if (memcmp((void *) ((size_t) buf + len), &crc, sizeof(crc))) return;
-  }
-  if (pkt.eth->type == mg_htons(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 == mg_htons(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 == mg_htons(0x800)) {
-    pkt.ip = (struct ip *) (pkt.eth + 1);
-    if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return;  // Truncated
-    // Truncate frame to what IP header tells us
-    if ((size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth) < pkt.raw.len) {
-      pkt.raw.len = (size_t) mg_ntohs(pkt.ip->len) + sizeof(struct eth);
-    }
-    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", mg_htons(pkt.eth->type)));
-    //mg_hexdump(buf, len >= 32 ? 32 : len);
-  }
-}
-
-static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t uptime_ms) {
-  if (ifp == NULL || ifp->driver == NULL) return;
-  bool expired_1000ms = mg_timer_expired(&ifp->timer_1000ms, 1000, uptime_ms);
-  ifp->now = uptime_ms;
-
-  // Handle physical interface up/down status
-  if (expired_1000ms && ifp->driver->up) {
-    bool up = ifp->driver->up(ifp);
-    bool current = ifp->state != MG_TCPIP_STATE_DOWN;
-    if (up != current) {
-      ifp->state = up == false               ? MG_TCPIP_STATE_DOWN
-                   : ifp->enable_dhcp_client ? MG_TCPIP_STATE_UP
-                                             : MG_TCPIP_STATE_READY;
-      if (!up && ifp->enable_dhcp_client) ifp->ip = 0;
-      onstatechange(ifp);
-    }
-  }
-  if (ifp->state == MG_TCPIP_STATE_DOWN) return;
-
-  // DHCP RFC-2131 (4.4)
-  if (ifp->state == MG_TCPIP_STATE_UP && expired_1000ms) {
-    tx_dhcp_discover(ifp);  // INIT (4.4.1)
-  } else if (expired_1000ms && ifp->state == MG_TCPIP_STATE_READY &&
-             ifp->lease_expire > 0) {  // BOUND / RENEWING / REBINDING
-    if (ifp->now >= ifp->lease_expire) {
-      ifp->state = MG_TCPIP_STATE_UP, ifp->ip = 0;  // expired, release IP
-      onstatechange(ifp);
-    } else if (ifp->now + 30UL * 60UL * 1000UL > ifp->lease_expire &&
-               ((ifp->now / 1000) % 60) == 0) {
-      // hack: 30 min before deadline, try to rebind (4.3.6) every min
-      tx_dhcp_request_re(ifp, (uint8_t *) broadcast, ifp->ip, 0xffffffff);
-    }  // TODO(): Handle T1 (RENEWING) and T2 (REBINDING) (4.4.5)
-  }
-
-  // Read data from the network
-  if (ifp->driver->rx != NULL) {  // Polling driver. We must call it
-    size_t len =
-        ifp->driver->rx(ifp->recv_queue.buf, ifp->recv_queue.size, ifp);
-    if (len > 0) mg_tcpip_rx(ifp, ifp->recv_queue.buf, len);
-  } else {  // Interrupt-based driver. Fills recv queue itself
-    char *buf;
-    size_t len = mg_queue_next(&ifp->recv_queue, &buf);
-    if (len > 0) {
-      mg_tcpip_rx(ifp, buf, len);
-      mg_queue_del(&ifp->recv_queue, len);
-    }
-  }
-
-  // Process timeouts
-  for (struct mg_connection *c = ifp->mgr->conns; c != NULL; c = c->next) {
-    if (c->is_udp || c->is_listening || c->is_resolving) continue;
-    struct connstate *s = (struct connstate *) (c + 1);
-    uint32_t rem_ip;
-    memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
-    if (uptime_ms > s->timer) {
-      if (s->ttype == MIP_TTYPE_ACK) {
-        MG_VERBOSE(("%lu ack %x %x", c->id, s->seq, s->ack));
-        tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
-               mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
-      } else if (s->ttype == MIP_TTYPE_ARP) {
-        mg_error(c, "ARP timeout");
-      } else if (s->ttype == MIP_TTYPE_SYN) {
-        mg_error(c, "Connection timeout");
-      } else if (s->ttype == MIP_TTYPE_FIN) {
-        c->is_closing = 1;
-        continue;
-      } else {
-        if (s->tmiss++ > 2) {
-          mg_error(c, "keepalive");
-        } else {
-          MG_VERBOSE(("%lu keepalive", c->id));
-          tx_tcp(ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port,
-                 mg_htonl(s->seq - 1), mg_htonl(s->ack), "", 0);
-        }
-      }
-
-      settmout(c, MIP_TTYPE_KEEPALIVE);
-    }
-  }
-}
-
-// 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
-void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp) {
-  char *p;
-  if (mg_queue_book(&ifp->recv_queue, &p, len) >= len) {
-    memcpy(p, buf, len);
-    mg_queue_add(&ifp->recv_queue, len);
-    ifp->nrecv++;
-  } else {
-    ifp->ndrop++;
-  }
-}
-
-void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) {
-  // If MAC address is not set, make a random one
-  if (ifp->mac[0] == 0 && ifp->mac[1] == 0 && ifp->mac[2] == 0 &&
-      ifp->mac[3] == 0 && ifp->mac[4] == 0 && ifp->mac[5] == 0) {
-    ifp->mac[0] = 0x02;  // Locally administered, unicast
-    mg_random(&ifp->mac[1], sizeof(ifp->mac) - 1);
-    MG_INFO(("MAC not set. Generated random: %M", mg_print_mac, ifp->mac));
-  }
-
-  if (ifp->driver->init && !ifp->driver->init(ifp)) {
-    MG_ERROR(("driver init failed"));
-  } else {
-    size_t framesize = 1540;
-    ifp->tx.ptr = (char *) calloc(1, framesize), ifp->tx.len = framesize;
-    if (ifp->recv_queue.size == 0)
-      ifp->recv_queue.size = ifp->driver->rx ? framesize : 8192;
-    ifp->recv_queue.buf = (char *) calloc(1, ifp->recv_queue.size);
-    ifp->timer_1000ms = mg_millis();
-    mgr->priv = ifp;
-    ifp->mgr = mgr;
-    mgr->extraconnsize = sizeof(struct connstate);
-    if (ifp->ip == 0) ifp->enable_dhcp_client = true;
-    memset(ifp->gwmac, 255, sizeof(ifp->gwmac));  // Set to broadcast
-    mg_random(&ifp->eport, sizeof(ifp->eport));   // Random from 0 to 65535
-    ifp->eport |= MG_EPHEMERAL_PORT_BASE;         // Random from
-                                           // MG_EPHEMERAL_PORT_BASE to 65535
-    if (ifp->tx.ptr == NULL || ifp->recv_queue.buf == NULL) MG_ERROR(("OOM"));
-  }
-}
-
-void mg_tcpip_free(struct mg_tcpip_if *ifp) {
-  free(ifp->recv_queue.buf);
-  free((char *) ifp->tx.ptr);
-}
-
-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;
-}
-
-static void send_syn(struct mg_connection *c) {
-  struct connstate *s = (struct connstate *) (c + 1);
-  uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port));
-  struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
-  uint32_t rem_ip;
-  memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
-  tx_tcp(ifp, s->mac, rem_ip, TH_SYN, c->loc.port, c->rem.port, isn, 0, NULL,
-         0);
-}
-
-void mg_connect_resolved(struct mg_connection *c) {
-  struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
-  uint32_t rem_ip;
-  memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
-  c->is_resolving = 0;
-  if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE;
-  memcpy(c->loc.ip, &ifp->ip, sizeof(uint32_t));
-  c->loc.port = mg_htons(ifp->eport++);
-  MG_DEBUG(("%lu %M -> %M", c->id, mg_print_ip_port, &c->loc, mg_print_ip_port,
-            &c->rem));
-  mg_call(c, MG_EV_RESOLVE, NULL);
-  if (((rem_ip & ifp->mask) == (ifp->ip & ifp->mask))) {
-    // If we're in the same LAN, fire an ARP lookup.
-    MG_DEBUG(("%lu ARP lookup...", c->id));
-    arp_ask(ifp, rem_ip);
-    settmout(c, MIP_TTYPE_ARP);
-    c->is_arplooking = 1;
-    c->is_connecting = 1;
-  } else if (rem_ip == (ifp->ip | ~ifp->mask)) {
-    struct connstate *s = (struct connstate *) (c + 1);
-    memset(s->mac, 0xFF, sizeof(s->mac));  // local broadcast
-  } else if ((*((uint8_t *) &rem_ip) & 0xE0) == 0xE0) {
-    struct connstate *s = (struct connstate *) (c + 1);  // 224 to 239, E0 to EF
-    uint8_t mcastp[3] = {0x01, 0x00, 0x5E};              // multicast group
-    memcpy(s->mac, mcastp, 3);
-    memcpy(s->mac + 3, ((uint8_t *) &rem_ip) + 1, 3);  // 23 LSb
-    s->mac[3] &= 0x7F;
-  } else {
-    struct connstate *s = (struct connstate *) (c + 1);
-    memcpy(s->mac, ifp->gwmac, sizeof(ifp->gwmac));
-    if (c->is_udp) {
-      mg_call(c, MG_EV_CONNECT, NULL);
-    } else {
-      send_syn(c);
-      settmout(c, MIP_TTYPE_SYN);
-      c->is_connecting = 1;
-    }
-  }
-}
-
-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) {
-  long len = c->is_tls ? mg_tls_send(c, c->send.buf, c->send.len)
-                       : mg_io_send(c, c->send.buf, c->send.len);
-  if (len > 0) {
-    mg_iobuf_del(&c->send, 0, (size_t) len);
-    mg_call(c, MG_EV_WRITE, &len);
-  }
-}
-
-static void init_closure(struct mg_connection *c) {
-  struct connstate *s = (struct connstate *) (c + 1);
-  if (c->is_udp == false && c->is_listening == false &&
-      c->is_connecting == false) {  // For TCP conns,
-    struct mg_tcpip_if *ifp =
-        (struct mg_tcpip_if *) c->mgr->priv;  // send TCP FIN
-    uint32_t rem_ip;
-    memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
-    tx_tcp(ifp, s->mac, rem_ip, TH_FIN | TH_ACK, c->loc.port, c->rem.port,
-           mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0);
-    settmout(c, MIP_TTYPE_FIN);
-  }
-}
-
-static void close_conn(struct mg_connection *c) {
-  struct connstate *s = (struct connstate *) (c + 1);
-  mg_iobuf_free(&s->raw);  // For TLS connections, release raw data
-  mg_close_conn(c);
-}
-
-static bool can_write(struct mg_connection *c) {
-  return c->is_connecting == 0 && c->is_resolving == 0 && c->send.len > 0 &&
-         c->is_tls_hs == 0 && c->is_arplooking == 0;
-}
-
-void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
-  struct mg_connection *c, *tmp;
-  uint64_t now = mg_millis();
-  mg_tcpip_poll((struct mg_tcpip_if *) mgr->priv, now);
-  mg_timer_poll(&mgr->timers, now);
-  for (c = mgr->conns; c != NULL; c = tmp) {
-    tmp = c->next;
-    struct connstate *s = (struct connstate *) (c + 1);
-    mg_call(c, MG_EV_POLL, &now);
-    MG_VERBOSE(("%lu .. %c%c%c%c%c", c->id, 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_tls_hs) mg_tls_handshake(c);
-    if (can_write(c)) write_conn(c);
-    if (c->is_draining && c->send.len == 0 && s->ttype != MIP_TTYPE_FIN)
-      init_closure(c);
-    if (c->is_closing) close_conn(c);
-  }
-  (void) ms;
-}
-
-bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
-  struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
-  bool res = false;
-  uint32_t rem_ip;
-  memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
-  if (ifp->ip == 0 || ifp->state != MG_TCPIP_STATE_READY) {
-    mg_error(c, "net down");
-  } else if (c->is_udp) {
-    struct connstate *s = (struct connstate *) (c + 1);
-    tx_udp(ifp, s->mac, ifp->ip, c->loc.port, rem_ip, c->rem.port, buf, len);
-    res = true;
-  } else {
-    res = mg_iobuf_add(&c->send, c->send.len, buf, len);
-  }
-  return res;
-}
-#endif  // MG_ENABLE_TCPIP
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/printf.c"
-#endif
-
-
-
-
-size_t mg_queue_vprintf(struct mg_queue *q, const char *fmt, va_list *ap) {
-  size_t len = mg_snprintf(NULL, 0, fmt, ap);
-  char *buf;
-  if (len == 0 || mg_queue_book(q, &buf, len + 1) < len + 1) {
-    len = 0;  // Nah. Not enough space
-  } else {
-    len = mg_vsnprintf((char *) buf, len + 1, fmt, ap);
-    mg_queue_add(q, len);
-  }
-  return len;
-}
-
-size_t mg_queue_printf(struct mg_queue *q, const char *fmt, ...) {
-  va_list ap;
-  size_t len;
-  va_start(ap, fmt);
-  len = mg_queue_vprintf(q, fmt, &ap);
-  va_end(ap);
-  return len;
-}
-
-static void mg_pfn_iobuf_private(char ch, void *param, bool expand) {
-  struct mg_iobuf *io = (struct mg_iobuf *) param;
-  if (expand && io->len + 2 > io->size) mg_iobuf_resize(io, io->len + 2);
-  if (io->len + 2 <= io->size) {
-    io->buf[io->len++] = (uint8_t) ch;
-    io->buf[io->len] = 0;
-  } else if (io->len < io->size) {
-    io->buf[io->len++] = 0;  // Guarantee to 0-terminate
-  }
-}
-
-static void mg_putchar_iobuf_static(char ch, void *param) {
-  mg_pfn_iobuf_private(ch, param, false);
-}
-
-void mg_pfn_iobuf(char ch, void *param) {
-  mg_pfn_iobuf_private(ch, param, true);
-}
-
-size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list *ap) {
-  struct mg_iobuf io = {(uint8_t *) buf, len, 0, 0};
-  size_t n = mg_vxprintf(mg_putchar_iobuf_static, &io, fmt, ap);
-  if (n < len) buf[n] = '\0';
-  return n;
-}
-
-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_vmprintf(const char *fmt, va_list *ap) {
-  struct mg_iobuf io = {0, 0, 0, 256};
-  mg_vxprintf(mg_pfn_iobuf, &io, fmt, ap);
-  return (char *) io.buf;
-}
-
-char *mg_mprintf(const char *fmt, ...) {
-  char *s;
-  va_list ap;
-  va_start(ap, fmt);
-  s = mg_vmprintf(fmt, &ap);
-  va_end(ap);
-  return s;
-}
-
-void mg_pfn_stdout(char c, void *param) {
-  putchar(c);
-  (void) param;
-}
-
-static size_t print_ip4(void (*out)(char, void *), void *arg, uint8_t *p) {
-  return mg_xprintf(out, arg, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
-}
-
-static size_t print_ip6(void (*out)(char, void *), void *arg, uint16_t *p) {
-  return mg_xprintf(out, arg, "[%x:%x:%x:%x:%x:%x:%x:%x]", mg_ntohs(p[0]),
-                    mg_ntohs(p[1]), mg_ntohs(p[2]), mg_ntohs(p[3]),
-                    mg_ntohs(p[4]), mg_ntohs(p[5]), mg_ntohs(p[6]),
-                    mg_ntohs(p[7]));
-}
-
-size_t mg_print_ip4(void (*out)(char, void *), void *arg, va_list *ap) {
-  uint8_t *p = va_arg(*ap, uint8_t *);
-  return print_ip4(out, arg, p);
-}
-
-size_t mg_print_ip6(void (*out)(char, void *), void *arg, va_list *ap) {
-  uint16_t *p = va_arg(*ap, uint16_t *);
-  return print_ip6(out, arg, p);
-}
-
-size_t mg_print_ip(void (*out)(char, void *), void *arg, va_list *ap) {
-  struct mg_addr *addr = va_arg(*ap, struct mg_addr *);
-  if (addr->is_ip6) return print_ip6(out, arg, (uint16_t *) addr->ip);
-  return print_ip4(out, arg, (uint8_t *) &addr->ip);
-}
-
-size_t mg_print_ip_port(void (*out)(char, void *), void *arg, va_list *ap) {
-  struct mg_addr *a = va_arg(*ap, struct mg_addr *);
-  return mg_xprintf(out, arg, "%M:%hu", mg_print_ip, a, mg_ntohs(a->port));
-}
-
-size_t mg_print_mac(void (*out)(char, void *), void *arg, va_list *ap) {
-  uint8_t *p = va_arg(*ap, uint8_t *);
-  return mg_xprintf(out, arg, "%02x:%02x:%02x:%02x:%02x:%02x", p[0], p[1], p[2],
-                    p[3], p[4], p[5]);
-}
-
-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 = 0;
-  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);
-    }
-  }
-  return i + extra;
-}
-
-static size_t bcpy(void (*out)(char, void *), void *arg, uint8_t *buf,
-                   size_t len) {
-  size_t i, j, n = 0;
-  const char *t =
-      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-  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];
-    for (j = 0; j < sizeof(tmp) && tmp[j] != '\0'; j++) out(tmp[j], arg);
-    n += j;
-  }
-  return n;
-}
-
-size_t mg_print_hex(void (*out)(char, void *), void *arg, va_list *ap) {
-  size_t bl = (size_t) va_arg(*ap, int);
-  uint8_t *p = va_arg(*ap, uint8_t *);
-  const char *hex = "0123456789abcdef";
-  size_t j;
-  for (j = 0; j < bl; j++) {
-    out(hex[(p[j] >> 4) & 0x0F], arg);
-    out(hex[p[j] & 0x0F], arg);
-  }
-  return 2 * bl;
-}
-size_t mg_print_base64(void (*out)(char, void *), void *arg, va_list *ap) {
-  size_t len = (size_t) va_arg(*ap, int);
-  uint8_t *buf = va_arg(*ap, uint8_t *);
-  return bcpy(out, arg, buf, len);
-}
-
-size_t mg_print_esc(void (*out)(char, void *), void *arg, va_list *ap) {
-  size_t len = (size_t) va_arg(*ap, int);
-  char *p = va_arg(*ap, char *);
-  if (len == 0) len = p == NULL ? 0 : strlen(p);
-  return qcpy(out, arg, p, len);
-}
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/queue.c"
-#endif
-
-
-
-#if defined(__GNUC__) || defined(__clang__)
-#define MG_MEMORY_BARRIER() __sync_synchronize()
-#elif defined(_MSC_VER) && _MSC_VER >= 1700
-#define MG_MEMORY_BARRIER() MemoryBarrier()
-#elif !defined(MG_MEMORY_BARRIER)
-#define MG_MEMORY_BARRIER()
-#endif
-
-// Every message in a queue is prepended by a 32-bit message length (ML).
-// If ML is 0, then it is the end, and reader must wrap to the beginning.
-//
-//  Queue when q->tail <= q->head:
-//  |----- free -----| ML | message1 | ML | message2 |  ----- free ------|
-//  ^                ^                               ^                   ^
-// buf              tail                            head                len
-//
-//  Queue when q->tail > q->head:
-//  | ML | message2 |----- free ------| ML | message1 | 0 |---- free ----|
-//  ^               ^                 ^                                  ^
-// buf             head              tail                               len
-
-void mg_queue_init(struct mg_queue *q, char *buf, size_t size) {
-  q->size = size;
-  q->buf = buf;
-  q->head = q->tail = 0;
-}
-
-static size_t mg_queue_read_len(struct mg_queue *q) {
-  uint32_t n = 0;
-  MG_MEMORY_BARRIER();
-  memcpy(&n, q->buf + q->tail, sizeof(n));
-  assert(q->tail + n + sizeof(n) <= q->size);
-  return n;
-}
-
-static void mg_queue_write_len(struct mg_queue *q, size_t len) {
-  uint32_t n = (uint32_t) len;
-  memcpy(q->buf + q->head, &n, sizeof(n));
-  MG_MEMORY_BARRIER();
-}
-
-size_t mg_queue_book(struct mg_queue *q, char **buf, size_t len) {
-  size_t space = 0, hs = sizeof(uint32_t) * 2;  // *2 is for the 0 marker
-  if (q->head >= q->tail && q->head + len + hs <= q->size) {
-    space = q->size - q->head - hs;  // There is enough space
-  } else if (q->head >= q->tail && q->tail > hs) {
-    mg_queue_write_len(q, 0);  // Not enough space ahead
-    q->head = 0;               // Wrap head to the beginning
-  }
-  if (q->head + hs + len < q->tail) space = q->tail - q->head - hs;
-  if (buf != NULL) *buf = q->buf + q->head + sizeof(uint32_t);
-  return space;
-}
-
-size_t mg_queue_next(struct mg_queue *q, char **buf) {
-  size_t len = 0;
-  if (q->tail != q->head) {
-    len = mg_queue_read_len(q);
-    if (len == 0) {  // Zero (head wrapped) ?
-      q->tail = 0;   // Reset tail to the start
-      if (q->head > q->tail) len = mg_queue_read_len(q);  // Read again
-    }
-  }
-  if (buf != NULL) *buf = q->buf + q->tail + sizeof(uint32_t);
-  assert(q->tail + len <= q->size);
-  return len;
-}
-
-void mg_queue_add(struct mg_queue *q, size_t len) {
-  assert(len > 0);
-  mg_queue_write_len(q, len);
-  assert(q->head + sizeof(uint32_t) * 2 + len <= q->size);
-  q->head += len + sizeof(uint32_t);
-}
-
-void mg_queue_del(struct mg_queue *q, size_t len) {
-  q->tail += len + sizeof(uint32_t);
-  assert(q->tail + sizeof(uint32_t) <= q->size);
-}
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/rpc.c"
-#endif
-
-
-
-void mg_rpc_add(struct mg_rpc **head, struct mg_str method,
-                void (*fn)(struct mg_rpc_req *), void *fn_data) {
-  struct mg_rpc *rpc = (struct mg_rpc *) calloc(1, sizeof(*rpc));
-  if (rpc != NULL) {
-    rpc->method = mg_strdup(method), rpc->fn = fn, rpc->fn_data = fn_data;
-    rpc->next = *head, *head = rpc;
-  }
-}
-
-void mg_rpc_del(struct mg_rpc **head, void (*fn)(struct mg_rpc_req *)) {
-  struct mg_rpc *r;
-  while ((r = *head) != NULL) {
-    if (r->fn == fn || fn == NULL) {
-      *head = r->next;
-      free((void *) r->method.ptr);
-      free(r);
-    } else {
-      head = &(*head)->next;
-    }
-  }
-}
-
-static void mg_rpc_call(struct mg_rpc_req *r, struct mg_str method) {
-  struct mg_rpc *h = r->head == NULL ? NULL : *r->head;
-  while (h != NULL && !mg_match(method, h->method, NULL)) h = h->next;
-  if (h != NULL) {
-    r->rpc = h;
-    h->fn(r);
-  } else {
-    mg_rpc_err(r, -32601, "\"%.*s not found\"", (int) method.len, method.ptr);
-  }
-}
-
-void mg_rpc_process(struct mg_rpc_req *r) {
-  int len, off = mg_json_get(r->frame, "$.method", &len);
-  if (off > 0 && r->frame.ptr[off] == '"') {
-    struct mg_str method = mg_str_n(&r->frame.ptr[off + 1], (size_t) len - 2);
-    mg_rpc_call(r, method);
-  } else if ((off = mg_json_get(r->frame, "$.result", &len)) > 0 ||
-             (off = mg_json_get(r->frame, "$.error", &len)) > 0) {
-    mg_rpc_call(r, mg_str(""));  // JSON response! call "" method handler
-  } else {
-    mg_rpc_err(r, -32700, "%m", mg_print_esc, (int) r->frame.len,
-               r->frame.ptr);  // Invalid
-  }
-}
-
-void mg_rpc_vok(struct mg_rpc_req *r, const char *fmt, va_list *ap) {
-  int len, off = mg_json_get(r->frame, "$.id", &len);
-  if (off > 0) {
-    mg_xprintf(r->pfn, r->pfn_data, "{%m:%.*s,%m:", mg_print_esc, 0, "id", len,
-               &r->frame.ptr[off], mg_print_esc, 0, "result");
-    mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
-    mg_xprintf(r->pfn, r->pfn_data, "}");
-  }
-}
-
-void mg_rpc_ok(struct mg_rpc_req *r, const char *fmt, ...) {
-  va_list ap;
-  va_start(ap, fmt);
-  mg_rpc_vok(r, fmt, &ap);
-  va_end(ap);
-}
-
-void mg_rpc_verr(struct mg_rpc_req *r, int code, const char *fmt, va_list *ap) {
-  int len, off = mg_json_get(r->frame, "$.id", &len);
-  mg_xprintf(r->pfn, r->pfn_data, "{");
-  if (off > 0) {
-    mg_xprintf(r->pfn, r->pfn_data, "%m:%.*s,", mg_print_esc, 0, "id", len,
-               &r->frame.ptr[off]);
-  }
-  mg_xprintf(r->pfn, r->pfn_data, "%m:{%m:%d,%m:", mg_print_esc, 0, "error",
-             mg_print_esc, 0, "code", code, mg_print_esc, 0, "message");
-  mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap);
-  mg_xprintf(r->pfn, r->pfn_data, "}}");
-}
-
-void mg_rpc_err(struct mg_rpc_req *r, int code, const char *fmt, ...) {
-  va_list ap;
-  va_start(ap, fmt);
-  mg_rpc_verr(r, code, fmt, &ap);
-  va_end(ap);
-}
-
-static size_t print_methods(mg_pfn_t pfn, void *pfn_data, va_list *ap) {
-  struct mg_rpc *h, **head = (struct mg_rpc **) va_arg(*ap, void **);
-  size_t len = 0;
-  for (h = *head; h != NULL; h = h->next) {
-    if (h->method.len == 0) continue;  // Ignore response handler
-    len += mg_xprintf(pfn, pfn_data, "%s%m", h == *head ? "" : ",",
-                      mg_print_esc, (int) h->method.len, h->method.ptr);
-  }
-  return len;
-}
-
-void mg_rpc_list(struct mg_rpc_req *r) {
-  mg_rpc_ok(r, "[%M]", print_methods, r->head);
-}
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/sha1.c"
-#endif
-/* Copyright(c) By Steve Reid <steve@edmweb.com> */
-/* 100% Public Domain */
-
-
-
-union char64long16 {
-  unsigned char c[64];
-  uint32_t l[16];
-};
-
-#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
-
-static uint32_t blk0(union char64long16 *block, int i) {
-  if (MG_BIG_ENDIAN) {
-  } else {
-    block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) |
-                  (rol(block->l[i], 8) & 0x00FF00FF);
-  }
-  return block->l[i];
-}
-
-/* Avoid redefine warning (ARM /usr/include/sys/ucontext.h define R0~R4) */
-#undef blk
-#undef R0
-#undef R1
-#undef R2
-#undef R3
-#undef R4
-
-#define blk(i)                                                               \
-  (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ \
-                              block->l[(i + 2) & 15] ^ block->l[i & 15],     \
-                          1))
-#define R0(v, w, x, y, z, i)                                          \
-  z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v, 5); \
-  w = rol(w, 30);
-#define R1(v, w, x, y, z, i)                                  \
-  z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
-  w = rol(w, 30);
-#define R2(v, w, x, y, z, i)                          \
-  z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \
-  w = rol(w, 30);
-#define R3(v, w, x, y, z, i)                                        \
-  z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \
-  w = rol(w, 30);
-#define R4(v, w, x, y, z, 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) {
-  uint32_t a, b, c, d, e;
-  union char64long16 block[1];
-
-  memcpy(block, buffer, 64);
-  a = state[0];
-  b = state[1];
-  c = state[2];
-  d = state[3];
-  e = state[4];
-  R0(a, b, c, d, e, 0);
-  R0(e, a, b, c, d, 1);
-  R0(d, e, a, b, c, 2);
-  R0(c, d, e, a, b, 3);
-  R0(b, c, d, e, a, 4);
-  R0(a, b, c, d, e, 5);
-  R0(e, a, b, c, d, 6);
-  R0(d, e, a, b, c, 7);
-  R0(c, d, e, a, b, 8);
-  R0(b, c, d, e, a, 9);
-  R0(a, b, c, d, e, 10);
-  R0(e, a, b, c, d, 11);
-  R0(d, e, a, b, c, 12);
-  R0(c, d, e, a, b, 13);
-  R0(b, c, d, e, a, 14);
-  R0(a, b, c, d, e, 15);
-  R1(e, a, b, c, d, 16);
-  R1(d, e, a, b, c, 17);
-  R1(c, d, e, a, b, 18);
-  R1(b, c, d, e, a, 19);
-  R2(a, b, c, d, e, 20);
-  R2(e, a, b, c, d, 21);
-  R2(d, e, a, b, c, 22);
-  R2(c, d, e, a, b, 23);
-  R2(b, c, d, e, a, 24);
-  R2(a, b, c, d, e, 25);
-  R2(e, a, b, c, d, 26);
-  R2(d, e, a, b, c, 27);
-  R2(c, d, e, a, b, 28);
-  R2(b, c, d, e, a, 29);
-  R2(a, b, c, d, e, 30);
-  R2(e, a, b, c, d, 31);
-  R2(d, e, a, b, c, 32);
-  R2(c, d, e, a, b, 33);
-  R2(b, c, d, e, a, 34);
-  R2(a, b, c, d, e, 35);
-  R2(e, a, b, c, d, 36);
-  R2(d, e, a, b, c, 37);
-  R2(c, d, e, a, b, 38);
-  R2(b, c, d, e, a, 39);
-  R3(a, b, c, d, e, 40);
-  R3(e, a, b, c, d, 41);
-  R3(d, e, a, b, c, 42);
-  R3(c, d, e, a, b, 43);
-  R3(b, c, d, e, a, 44);
-  R3(a, b, c, d, e, 45);
-  R3(e, a, b, c, d, 46);
-  R3(d, e, a, b, c, 47);
-  R3(c, d, e, a, b, 48);
-  R3(b, c, d, e, a, 49);
-  R3(a, b, c, d, e, 50);
-  R3(e, a, b, c, d, 51);
-  R3(d, e, a, b, c, 52);
-  R3(c, d, e, a, b, 53);
-  R3(b, c, d, e, a, 54);
-  R3(a, b, c, d, e, 55);
-  R3(e, a, b, c, d, 56);
-  R3(d, e, a, b, c, 57);
-  R3(c, d, e, a, b, 58);
-  R3(b, c, d, e, a, 59);
-  R4(a, b, c, d, e, 60);
-  R4(e, a, b, c, d, 61);
-  R4(d, e, a, b, c, 62);
-  R4(c, d, e, a, b, 63);
-  R4(b, c, d, e, a, 64);
-  R4(a, b, c, d, e, 65);
-  R4(e, a, b, c, d, 66);
-  R4(d, e, a, b, c, 67);
-  R4(c, d, e, a, b, 68);
-  R4(b, c, d, e, a, 69);
-  R4(a, b, c, d, e, 70);
-  R4(e, a, b, c, d, 71);
-  R4(d, e, a, b, c, 72);
-  R4(c, d, e, a, b, 73);
-  R4(b, c, d, e, a, 74);
-  R4(a, b, c, d, e, 75);
-  R4(e, a, b, c, d, 76);
-  R4(d, e, a, b, c, 77);
-  R4(c, d, e, a, b, 78);
-  R4(b, c, d, e, a, 79);
-  state[0] += a;
-  state[1] += b;
-  state[2] += c;
-  state[3] += d;
-  state[4] += e;
-  /* Erase working structures. The order of operations is important,
-   * used to ensure that compiler doesn't optimize those out. */
-  memset(block, 0, sizeof(block));
-  a = b = c = d = e = 0;
-  (void) a;
-  (void) b;
-  (void) c;
-  (void) d;
-  (void) e;
-}
-
-void mg_sha1_init(mg_sha1_ctx *context) {
-  context->state[0] = 0x67452301;
-  context->state[1] = 0xEFCDAB89;
-  context->state[2] = 0x98BADCFE;
-  context->state[3] = 0x10325476;
-  context->state[4] = 0xC3D2E1F0;
-  context->count[0] = context->count[1] = 0;
-}
-
-void mg_sha1_update(mg_sha1_ctx *context, const unsigned char *data,
-                    size_t len) {
-  size_t i, j;
-
-  j = context->count[0];
-  if ((context->count[0] += (uint32_t) len << 3) < j) context->count[1]++;
-  context->count[1] += (uint32_t) (len >> 29);
-  j = (j >> 3) & 63;
-  if ((j + len) > 63) {
-    memcpy(&context->buffer[j], data, (i = 64 - j));
-    mg_sha1_transform(context->state, context->buffer);
-    for (; i + 63 < len; i += 64) {
-      mg_sha1_transform(context->state, &data[i]);
-    }
-    j = 0;
-  } else
-    i = 0;
-  memcpy(&context->buffer[j], &data[i], len - i);
-}
-
-void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *context) {
-  unsigned i;
-  unsigned char finalcount[8], c;
-
-  for (i = 0; i < 8; i++) {
-    finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >>
-                                      ((3 - (i & 3)) * 8)) &
-                                     255);
-  }
-  c = 0200;
-  mg_sha1_update(context, &c, 1);
-  while ((context->count[0] & 504) != 448) {
-    c = 0000;
-    mg_sha1_update(context, &c, 1);
-  }
-  mg_sha1_update(context, finalcount, 8);
-  for (i = 0; i < 20; i++) {
-    digest[i] =
-        (unsigned char) ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
-  }
-  memset(context, '\0', sizeof(*context));
-  memset(&finalcount, '\0', sizeof(finalcount));
-}
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/sntp.c"
-#endif
-
-
-
-
-
-
-#define SNTP_TIME_OFFSET 2208988800U  // (1970 - 1900) in seconds
-#define SNTP_MAX_FRAC 4294967295.0    // 2 ** 32 - 1
-
-static int64_t gettimestamp(const uint32_t *data) {
-  uint32_t sec = mg_ntohl(data[0]), frac = mg_ntohl(data[1]);
-  if (sec) sec -= SNTP_TIME_OFFSET;
-  return ((int64_t) sec) * 1000 + (int64_t) (frac / SNTP_MAX_FRAC * 1000.0);
-}
-
-int64_t mg_sntp_parse(const unsigned char *buf, size_t len) {
-  int64_t res = -1;
-  int mode = len > 0 ? buf[0] & 7 : 0;
-  int version = len > 0 ? (buf[0] >> 3) & 7 : 0;
-  if (len < 48) {
-    MG_ERROR(("%s", "corrupt packet"));
-  } else if (mode != 4 && mode != 5) {
-    MG_ERROR(("%s", "not a server reply"));
-  } else if (buf[1] == 0) {
-    MG_ERROR(("%s", "server sent a kiss of death"));
-  } else if (version == 4 || version == 3) {
-    // int64_t ref = gettimestamp((uint32_t *) &buf[16]);
-    int64_t t0 = gettimestamp((uint32_t *) &buf[24]);
-    int64_t t1 = gettimestamp((uint32_t *) &buf[32]);
-    int64_t t2 = gettimestamp((uint32_t *) &buf[40]);
-    int64_t t3 = (int64_t) mg_millis();
-    int64_t delta = (t3 - t0) - (t2 - t1);
-    MG_VERBOSE(("%lld %lld %lld %lld delta:%lld", t0, t1, t2, t3, delta));
-    res = t2 + delta / 2;
-  } 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) {
-    int64_t milliseconds = mg_sntp_parse(c->recv.buf, c->recv.len);
-    if (milliseconds > 0) {
-      MG_INFO(("%lu got time: %lld ms from epoch", c->id, milliseconds));
-      mg_call(c, MG_EV_SNTP_TIME, (uint64_t *) &milliseconds);
-      MG_VERBOSE(("%u.%u", (unsigned) (milliseconds / 1000),
-                  (unsigned) (milliseconds % 1000)));
-    }
-    mg_iobuf_del(&c->recv, 0, c->recv.len);  // Free receive buffer
-  } else if (ev == MG_EV_CONNECT) {
-    mg_sntp_request(c);
-  } else if (ev == MG_EV_CLOSE) {
-  }
-  (void) fnd;
-  (void) evd;
-}
-
-void mg_sntp_request(struct mg_connection *c) {
-  if (c->is_resolving) {
-    MG_ERROR(("%lu wait until resolved", c->id));
-  } else {
-    int64_t now = (int64_t) mg_millis();  // Use int64_t, for vc98
-    uint8_t buf[48] = {0};
-    uint32_t *t = (uint32_t *) &buf[40];
-    double frac = ((double) (now % 1000)) / 1000.0 * SNTP_MAX_FRAC;
-    buf[0] = (0 << 6) | (4 << 3) | 3;
-    t[0] = mg_htonl((uint32_t) (now / 1000) + SNTP_TIME_OFFSET);
-    t[1] = mg_htonl((uint32_t) frac);
-    mg_send(c, buf, sizeof(buf));
-  }
-}
-
-struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const char *url,
-                                      mg_event_handler_t fn, void *fnd) {
-  struct mg_connection *c = NULL;
-  if (url == NULL) url = "udp://time.google.com:123";
-  if ((c = mg_connect(mgr, url, fn, fnd)) != NULL) c->pfn = sntp_cb;
-  return c;
-}
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/sock.c"
-#endif
-
-
-
-
-
-
-
-
-
-
-
-#if MG_ENABLE_SOCKET
-
-#ifndef closesocket
-#define closesocket(x) close(x)
-#endif
-
-#define FD(c_) ((MG_SOCKET_TYPE) (size_t) (c_)->fd)
-#define S2PTR(s_) ((void *) (size_t) (s_))
-
-#ifndef MSG_NONBLOCKING
-#define MSG_NONBLOCKING 0
-#endif
-
-#ifndef AF_INET6
-#define AF_INET6 10
-#endif
-
-#ifndef MG_SOCK_ERR
-#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? errno : 0)
-#endif
-
-#ifndef MG_SOCK_INTR
-#define MG_SOCK_INTR(fd) (fd == MG_INVALID_SOCKET && MG_SOCK_ERR(-1) == EINTR)
-#endif
-
-#ifndef MG_SOCK_PENDING
-#define MG_SOCK_PENDING(errcode) \
-  (((errcode) < 0) && (errno == EINPROGRESS || errno == EWOULDBLOCK))
-#endif
-
-#ifndef MG_SOCK_RESET
-#define MG_SOCK_RESET(errcode) \
-  (((errcode) < 0) && (errno == EPIPE || errno == ECONNRESET))
-#endif
-
-union usa {
-  struct sockaddr sa;
-  struct sockaddr_in sin;
-#if MG_ENABLE_IPV6
-  struct sockaddr_in6 sin6;
-#endif
-};
-
-static socklen_t tousa(struct mg_addr *a, union usa *usa) {
-  socklen_t len = sizeof(usa->sin);
-  memset(usa, 0, sizeof(*usa));
-  usa->sin.sin_family = AF_INET;
-  usa->sin.sin_port = a->port;
-  memcpy(&usa->sin.sin_addr, a->ip, sizeof(uint32_t));
-#if MG_ENABLE_IPV6
-  if (a->is_ip6) {
-    usa->sin.sin_family = AF_INET6;
-    usa->sin6.sin6_port = a->port;
-    memcpy(&usa->sin6.sin6_addr, a->ip, sizeof(a->ip));
-    len = sizeof(usa->sin6);
-  }
-#endif
-  return len;
-}
-
-static void tomgaddr(union usa *usa, struct mg_addr *a, bool is_ip6) {
-  a->is_ip6 = is_ip6;
-  a->port = usa->sin.sin_port;
-  memcpy(&a->ip, &usa->sin.sin_addr, sizeof(uint32_t));
-#if MG_ENABLE_IPV6
-  if (is_ip6) {
-    memcpy(a->ip, &usa->sin6.sin6_addr, sizeof(a->ip));
-    a->port = usa->sin6.sin6_port;
-  }
-#endif
-}
-
-static void setlocaddr(MG_SOCKET_TYPE 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 == MG_IO_WAIT) {
-    // 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;
-      socklen_t slen = sizeof(usa.sin);
-      if (getsockname(FD(c), &usa.sa, &slen) < 0) (void) 0;  // Ignore result
-      MG_INFO(("\n-- %lu %M %s %M %ld", c->id, mg_print_ip_port, &c->loc,
-               r ? "<-" : "->", mg_print_ip_port, &c->rem, n));
-
-      mg_hexdump(buf, (size_t) n);
-    }
-    if (r) {
-      c->recv.len += (size_t) n;
-      mg_call(c, MG_EV_READ, &n);
-    } else {
-      mg_iobuf_del(&c->send, 0, (size_t) n);
-      // if (c->send.len == 0) mg_iobuf_resize(&c->send, 0);
-      if (c->send.len == 0) {
-        MG_EPOLL_MOD(c, 0);
-      }
-      mg_call(c, MG_EV_WRITE, &n);
-    }
-  }
-}
-
-long mg_io_send(struct mg_connection *c, const void *buf, size_t len) {
-  long n;
-  if (c->is_udp) {
-    union usa usa;
-    socklen_t slen = tousa(&c->rem, &usa);
-    n = sendto(FD(c), (char *) buf, len, 0, &usa.sa, slen);
-    if (n > 0) setlocaddr(FD(c), &c->loc);
-  } else {
-    n = send(FD(c), (char *) buf, len, MSG_NONBLOCKING);
-  }
-  if (MG_SOCK_PENDING(n)) return MG_IO_WAIT;
-  if (MG_SOCK_RESET(n)) return MG_IO_RESET;
-  if (n <= 0) return MG_IO_ERR;
-  return n;
-}
-
-bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
-  if (c->is_udp) {
-    long n = mg_io_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_ERR(n)));
-    iolog(c, (char *) buf, n, false);
-    return n > 0;
-  } else {
-    return mg_iobuf_add(&c->send, c->send.len, buf, len);
-  }
-}
-
-static void mg_set_non_blocking_mode(MG_SOCKET_TYPE fd) {
-#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_ENABLE_RL
-  unsigned long on = 1;
-  ioctlsocket(fd, FIONBIO, &on);
-#elif MG_ENABLE_FREERTOS_TCP
-  const BaseType_t off = 0;
-  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_ENABLE_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, SOL_SOCKET, SO_BLOCKING, &val, sizeof(val));
-  // SPRU524J section 3.3.3 page 63, SO_SNDLOWAT
-  int sz = sizeof(val);
-  getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &val, &sz);
-  val /= 2;  // set send low-water mark at half send buffer size
-  setsockopt(fd, SOL_SOCKET, SO_SNDLOWAT, &val, sizeof(val));
-#else
-  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
-}
-
-bool mg_open_listener(struct mg_connection *c, const char *url) {
-  MG_SOCKET_TYPE fd = MG_INVALID_SOCKET;
-  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(&c->loc, &usa);
-    int rc, 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)) == MG_INVALID_SOCKET) {
-      MG_ERROR(("socket: %d", MG_SOCK_ERR(-1)));
-#if defined(SO_EXCLUSIVEADDRUSE)
-    } else if ((rc = setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
-                                (char *) &on, sizeof(on))) != 0) {
-      // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE"
-      MG_ERROR(("setsockopt(SO_EXCLUSIVEADDRUSE): %d %d", on, MG_SOCK_ERR(rc)));
-#elif defined(SO_REUSEADDR) && (!defined(LWIP_SOCKET) || SO_REUSE)
-    } else if ((rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on,
-                                sizeof(on))) != 0) {
-      // 1. SO_REUSEADDR semantics 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.
-      //
-      // 2. For LWIP, SO_REUSEADDR should be explicitly enabled by defining
-      // SO_REUSE = 1 in lwipopts.h, otherwise the code below will compile but
-      // won't work! (setsockopt will return EINVAL)
-      MG_ERROR(("setsockopt(SO_REUSEADDR): %d", MG_SOCK_ERR(rc)));
-#endif
-#if defined(IPV6_V6ONLY)
-    } else if (c->loc.is_ip6 &&
-               (rc = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &on,
-                                sizeof(on))) != 0) {
-      // See #2089. Allow to bind v4 and v6 sockets on the same port
-      MG_ERROR(("setsockopt(IPV6_V6ONLY): %d", MG_SOCK_ERR(rc)));
-#endif
-    } else if ((rc = bind(fd, &usa.sa, slen)) != 0) {
-      MG_ERROR(("bind: %d", MG_SOCK_ERR(rc)));
-    } else if ((type == SOCK_STREAM &&
-                (rc = 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
-      MG_ERROR(("listen: %d", MG_SOCK_ERR(rc)));
-    } else {
-      setlocaddr(fd, &c->loc);
-      mg_set_non_blocking_mode(fd);
-      c->fd = S2PTR(fd);
-      MG_EPOLL_ADD(c);
-      success = true;
-    }
-  }
-  if (success == false && fd != MG_INVALID_SOCKET) closesocket(fd);
-  return success;
-}
-
-long mg_io_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->rem, &usa);
-    n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa, &slen);
-    if (n > 0) tomgaddr(&usa, &c->rem, slen != sizeof(usa.sin));
-  } else {
-    n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING);
-  }
-  if (MG_SOCK_PENDING(n)) return MG_IO_WAIT;
-  if (MG_SOCK_RESET(n)) return MG_IO_RESET;
-  if (n <= 0) return MG_IO_ERR;
-  return n;
-}
-
-// 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 void read_conn(struct mg_connection *c) {
-  long n = -1;
-  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_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_io_recv(c, buf, len);
-    MG_DEBUG(("%lu %p snd %ld/%ld rcv %ld/%ld n=%ld err=%d", c->id, c->fd,
-              (long) c->send.len, (long) c->send.size, (long) c->recv.len,
-              (long) c->recv.size, n, MG_SOCK_ERR(n)));
-    iolog(c, buf, n, true);
-  }
-}
-
-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_io_send(c, buf, len);
-  MG_DEBUG(("%lu %p snd %ld/%ld rcv %ld/%ld n=%ld err=%d", c->id, c->fd,
-            (long) c->send.len, (long) c->send.size, (long) c->recv.len,
-            (long) c->recv.size, n, MG_SOCK_ERR(n)));
-  iolog(c, buf, n, false);
-}
-
-static void close_conn(struct mg_connection *c) {
-  if (FD(c) != MG_INVALID_SOCKET) {
-#if MG_ENABLE_EPOLL
-    epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_DEL, FD(c), NULL);
-#endif
-    closesocket(FD(c));
-#if MG_ENABLE_FREERTOS_TCP
-    FreeRTOS_FD_CLR(c->fd, c->mgr->ss, eSELECT_ALL);
-#endif
-  }
-  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);
-    MG_EPOLL_MOD(c, 0);
-    if (c->is_tls_hs) mg_tls_handshake(c);
-  } else {
-    mg_error(c, "socket error");
-  }
-}
-
-static void setsockopts(struct mg_connection *c) {
-#if MG_ENABLE_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
-  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) {
-  int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM;
-  int rc, af = c->rem.is_ip6 ? AF_INET6 : AF_INET;  // c->rem has resolved IP
-  c->fd = S2PTR(socket(af, type, 0));               // Create outbound socket
-  c->is_resolving = 0;                              // Clear resolving flag
-  if (FD(c) == MG_INVALID_SOCKET) {
-    mg_error(c, "socket(): %d", MG_SOCK_ERR(-1));
-  } else if (c->is_udp) {
-    MG_EPOLL_ADD(c);
-#if MG_ARCH == MG_ARCH_TIRTOS
-    union usa usa;  // TI-RTOS NDK requires binding to receive on UDP sockets
-    socklen_t slen = tousa(&c->loc, &usa);
-    if ((rc = bind(c->fd, &usa.sa, slen)) != 0)
-      MG_ERROR(("bind: %d", MG_SOCK_ERR(rc)));
-#endif
-    mg_call(c, MG_EV_RESOLVE, NULL);
-    mg_call(c, MG_EV_CONNECT, NULL);
-  } else {
-    union usa usa;
-    socklen_t slen = tousa(&c->rem, &usa);
-    mg_set_non_blocking_mode(FD(c));
-    setsockopts(c);
-    MG_EPOLL_ADD(c);
-    mg_call(c, MG_EV_RESOLVE, NULL);
-    rc = connect(FD(c), &usa.sa, slen);  // Attempt to connect
-    if (rc == 0) {                       // Success
-      mg_call(c, MG_EV_CONNECT, NULL);   // Send MG_EV_CONNECT to the user
-    } else if (MG_SOCK_PENDING(rc)) {    // Need to wait for TCP handshake
-      MG_DEBUG(("%lu %p -> %M pend", c->id, c->fd, mg_print_ip_port, &c->rem));
-      c->is_connecting = 1;
-    } else {
-      mg_error(c, "connect: %d", MG_SOCK_ERR(rc));
-    }
-  }
-}
-
-static MG_SOCKET_TYPE raccept(MG_SOCKET_TYPE sock, union usa *usa,
-                              socklen_t *len) {
-  MG_SOCKET_TYPE fd = MG_INVALID_SOCKET;
-  do {
-    memset(usa, 0, sizeof(*usa));
-    fd = accept(sock, &usa->sa, len);
-  } while (MG_SOCK_INTR(fd));
-  return fd;
-}
-
-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);
-  MG_SOCKET_TYPE fd = raccept(FD(lsn), &usa, &sa_len);
-  if (fd == MG_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
-    if (errno != EAGAIN)
-#endif
-      MG_ERROR(("%lu accept failed, errno %d", lsn->id, MG_SOCK_ERR(-1)));
-#if (MG_ARCH != MG_ARCH_WIN32) && !MG_ENABLE_FREERTOS_TCP && \
-    (MG_ARCH != MG_ARCH_TIRTOS) && !MG_ENABLE_POLL && !MG_ENABLE_EPOLL
-  } else if ((long) fd >= FD_SETSIZE) {
-    MG_ERROR(("%ld > %ld", (long) fd, (long) FD_SETSIZE));
-    closesocket(fd);
-#endif
-  } else if ((c = mg_alloc_conn(mgr)) == NULL) {
-    MG_ERROR(("%lu OOM", lsn->id));
-    closesocket(fd);
-  } else {
-    tomgaddr(&usa, &c->rem, sa_len != sizeof(usa.sin));
-    LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
-    c->fd = S2PTR(fd);
-    MG_EPOLL_ADD(c);
-    mg_set_non_blocking_mode(FD(c));
-    setsockopts(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;
-    c->fn_data = lsn->fn_data;
-    MG_DEBUG(("%lu %p accepted %M -> %M", c->id, c->fd, mg_print_ip_port,
-              &c->rem, mg_print_ip_port, &c->loc));
-    mg_call(c, MG_EV_OPEN, NULL);
-    mg_call(c, MG_EV_ACCEPT, NULL);
-    if (lsn->is_tls) mg_tls_init(c, mg_str(""));
-  }
-}
-
-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) == MG_INVALID_SOCKET) ||
-         (can_read(c) == false && can_write(c) == false);
-}
-
-static void mg_iotest(struct mg_mgr *mgr, int ms) {
-#if MG_ENABLE_FREERTOS_TCP
-  struct mg_connection *c;
-  for (c = mgr->conns; c != NULL; c = c->next) {
-    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) {
-    EventBits_t bits = FreeRTOS_FD_ISSET(c->fd, mgr->ss);
-    c->is_readable = bits & (eSELECT_READ | eSELECT_EXCEPT) ? 1U : 0;
-    c->is_writable = bits & eSELECT_WRITE ? 1U : 0;
-    if (c->fd != MG_INVALID_SOCKET)
-      FreeRTOS_FD_CLR(c->fd, mgr->ss,
-                      eSELECT_READ | eSELECT_EXCEPT | eSELECT_WRITE);
-  }
-#elif MG_ENABLE_EPOLL
-  size_t max = 1;
-  for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
-    c->is_readable = c->is_writable = 0;
-    if (mg_tls_pending(c) > 0) ms = 1, c->is_readable = 1;
-    if (can_write(c)) MG_EPOLL_MOD(c, 1);
-    max++;
-  }
-  struct epoll_event *evs = (struct epoll_event *) alloca(max * sizeof(evs[0]));
-  int n = epoll_wait(mgr->epoll_fd, evs, (int) max, ms);
-  for (int i = 0; i < n; i++) {
-    struct mg_connection *c = (struct mg_connection *) evs[i].data.ptr;
-    if (evs[i].events & EPOLLERR) {
-      mg_error(c, "socket error");
-    } else if (c->is_readable == 0) {
-      bool rd = evs[i].events & (EPOLLIN | EPOLLHUP);
-      bool wr = evs[i].events & EPOLLOUT;
-      c->is_readable = can_read(c) && rd ? 1U : 0;
-      c->is_writable = can_write(c) && wr ? 1U : 0;
-    }
-  }
-  (void) skip_iotest;
-#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}, tv_zero = {0, 0}, *tvp;
-  struct mg_connection *c;
-  fd_set rset, wset, eset;
-  MG_SOCKET_TYPE maxfd = 0;
-  int rc;
-
-  FD_ZERO(&rset);
-  FD_ZERO(&wset);
-  FD_ZERO(&eset);
-  tvp = ms < 0 ? NULL : &tv;
-  for (c = mgr->conns; c != NULL; c = c->next) {
-    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) tvp = &tv_zero;
-    if (FD(c) > maxfd) maxfd = FD(c);
-  }
-
-  if ((rc = select((int) maxfd + 1, &rset, &wset, &eset, tvp)) < 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_ERR(rc)));
-#endif
-    FD_ZERO(&rset);
-    FD_ZERO(&wset);
-    FD_ZERO(&eset);
-  }
-
-  for (c = mgr->conns; c != NULL; c = c->next) {
-    if (FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &eset)) {
-      mg_error(c, "socket error");
-    } else {
-      c->is_readable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &rset);
-      c->is_writable = FD(c) != MG_INVALID_SOCKET && FD_ISSET(FD(c), &wset);
-      if (mg_tls_pending(c) > 0) c->is_readable = 1;
-    }
-  }
-#endif
-}
-
-void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
-  struct mg_connection *c, *tmp;
-  uint64_t now;
-
-  mg_iotest(mgr, ms);
-  now = mg_millis();
-  mg_timer_poll(&mgr->timers, now);
-
-  for (c = mgr->conns; c != NULL; c = tmp) {
-    bool is_resp = c->is_resp;
-    tmp = c->next;
-    mg_call(c, MG_EV_POLL, &now);
-    if (is_resp && !c->is_resp) {
-      long n = 0;
-      mg_call(c, MG_EV_READ, &n);
-    }
-    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) {
-      if (c->is_readable) accept_conn(mgr, c);
-    } else if (c->is_connecting) {
-      if (c->is_readable || c->is_writable) connect_conn(c);
-    } else if (c->is_tls_hs) {
-      if ((c->is_readable || c->is_writable)) mg_tls_handshake(c);
-    } else {
-      if (c->is_readable) read_conn(c);
-      if (c->is_writable) write_conn(c);
-    }
-
-    if (c->is_draining && c->send.len == 0) c->is_closing = 1;
-    if (c->is_closing) close_conn(c);
-  }
-}
-#endif
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/ssi.c"
-#endif
-
-
-
-
-#ifndef MG_MAX_SSI_DEPTH
-#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, MG_IO_SIZE};
-  FILE *fp = fopen(path, "rb");
-  if (fp != NULL) {
-    char buf[MG_SSI_BUFSIZ], arg[sizeof(buf)];
-    int ch, intag = 0;
-    size_t len = 0;
-    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 + MG_SSI_BUFSIZ + 10],
-              *p = (char *) path + strlen(path), *data;
-          while (p > path && p[-1] != MG_DIRSEP && p[-1] != '/') p--;
-          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));
-            free(data);
-          } else {
-            MG_ERROR(("%s: file=%s error or too deep", path, arg));
-          }
-        } else if (sscanf(buf, "<!--#include virtual=\"%[^\"]", 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));
-            free(data);
-          } else {
-            MG_ERROR(("%s: virtual=%s error or too deep", path, arg));
-          }
-        } else {
-          // Unknown SSI tag
-          MG_ERROR(("Unknown SSI tag: %.*s", (int) len, buf));
-          mg_iobuf_add(&b, b.len, buf, len);
-        }
-        intag = 0;
-        len = 0;
-      } else if (ch == '<') {
-        intag = 1;
-        if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
-        len = 0;
-        buf[len++] = (char) (ch & 0xff);
-      } else if (intag) {
-        if (len == 5 && strncmp(buf, "<!--#", 5) != 0) {
-          intag = 0;
-        } else if (len >= sizeof(buf) - 2) {
-          MG_ERROR(("%s: SSI tag is too large", path));
-          len = 0;
-        }
-        buf[len++] = (char) (ch & 0xff);
-      } else {
-        buf[len++] = (char) (ch & 0xff);
-        if (len >= sizeof(buf)) {
-          mg_iobuf_add(&b, b.len, buf, len);
-          len = 0;
-        }
-      }
-    }
-    if (len > 0) mg_iobuf_add(&b, b.len, buf, len);
-    if (b.len > 0) mg_iobuf_add(&b, b.len, "", 1);  // nul-terminate
-    fclose(fp);
-  }
-  (void) depth;
-  (void) root;
-  return (char *) b.buf;
-}
-
-void mg_http_serve_ssi(struct mg_connection *c, const char *root,
-                       const char *fullpath) {
-  const char *headers = "Content-Type: text/html; charset=utf-8\r\n";
-  char *data = mg_ssi(fullpath, root, 0);
-  mg_http_reply(c, 200, headers, "%s", data == NULL ? "" : data);
-  free(data);
-}
-#else
-void mg_http_serve_ssi(struct mg_connection *c, const char *root,
-                       const char *fullpath) {
-  mg_http_reply(c, 501, NULL, "SSI not enabled");
-  (void) root, (void) fullpath;
-}
-#endif
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/str.c"
-#endif
-
-
-struct mg_str mg_str_s(const char *s) {
-  struct mg_str str = {s, s == NULL ? 0 : strlen(s)};
-  return str;
-}
-
-struct mg_str mg_str_n(const char *s, size_t n) {
-  struct mg_str str = {s, n};
-  return str;
-}
-
-int mg_lower(const 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) {
-  int diff = 0;
-  if (len > 0) do {
-      diff = mg_lower(s1++) - mg_lower(s2++);
-    } while (diff == 0 && s1[-1] != '\0' && --len > 0);
-  return diff;
-}
-
-int mg_casecmp(const char *s1, const char *s2) {
-  return mg_ncasecmp(s1, s2, (size_t) ~0);
-}
-
-int mg_vcmp(const struct mg_str *s1, const char *s2) {
-  size_t n2 = strlen(s2), n1 = s1->len;
-  int r = strncmp(s1->ptr, s2, (n1 < n2) ? n1 : n2);
-  if (r == 0) return (int) (n1 - n2);
-  return r;
-}
-
-int mg_vcasecmp(const struct mg_str *str1, const char *str2) {
-  size_t n2 = strlen(str2), n1 = str1->len;
-  int r = mg_ncasecmp(str1->ptr, str2, (n1 < n2) ? n1 : n2);
-  if (r == 0) return (int) (n1 - n2);
-  return r;
-}
-
-struct mg_str mg_strdup(const struct mg_str s) {
-  struct mg_str r = {NULL, 0};
-  if (s.len > 0 && s.ptr != NULL) {
-    char *sc = (char *) calloc(1, s.len + 1);
-    if (sc != NULL) {
-      memcpy(sc, s.ptr, s.len);
-      sc[s.len] = '\0';
-      r.ptr = sc;
-      r.len = s.len;
-    }
-  }
-  return r;
-}
-
-int mg_strcmp(const struct mg_str str1, const struct mg_str str2) {
-  size_t i = 0;
-  while (i < str1.len && i < str2.len) {
-    int c1 = str1.ptr[i];
-    int c2 = str2.ptr[i];
-    if (c1 < c2) return -1;
-    if (c1 > c2) return 1;
-    i++;
-  }
-  if (i < str1.len) return 1;
-  if (i < str2.len) return -1;
-  return 0;
-}
-
-const char *mg_strstr(const struct mg_str haystack,
-                      const struct mg_str needle) {
-  size_t i;
-  if (needle.len > haystack.len) return NULL;
-  if (needle.len == 0) return haystack.ptr;
-  for (i = 0; i <= haystack.len - needle.len; i++) {
-    if (memcmp(haystack.ptr + i, needle.ptr, needle.len) == 0) {
-      return haystack.ptr + i;
-    }
-  }
-  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 && 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, ',');
-}
-
-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);
-  }
-}
-
-bool mg_path_is_sane(const char *path) {
-  const char *s = path;
-  for (; s[0] != '\0'; s++) {
-    if (s == path || s[0] == '/' || s[0] == '\\') {  // Subdir?
-      if (s[1] == '.' && s[2] == '.') return false;  // Starts with ..
-    }
-  }
-  return true;
-}
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/timer.c"
-#endif
-
-
-
-#define MG_TIMER_CALLED 4
-
-void mg_timer_init(struct mg_timer **head, struct mg_timer *t, uint64_t ms,
-                   unsigned flags, void (*fn)(void *), void *arg) {
-  t->id = 0, t->period_ms = ms, t->expire = 0;
-  t->flags = flags, t->fn = fn, t->arg = arg, t->next = *head;
-  *head = t;
-}
-
-void mg_timer_free(struct mg_timer **head, struct mg_timer *t) {
-  while (*head && *head != t) head = &(*head)->next;
-  if (*head) *head = t->next;
-}
-
-// t: expiration time, prd: period, now: current time. Return true if expired
-bool mg_timer_expired(uint64_t *t, uint64_t prd, uint64_t now) {
-  if (now + prd < *t) *t = 0;                    // Time wrapped? Reset timer
-  if (*t == 0) *t = now + prd;                   // Firt poll? Set expiration
-  if (*t > now) return false;                    // Not expired yet, return
-  *t = (now - *t) > prd ? now + prd : *t + prd;  // Next expiration time
-  return true;                                   // Expired, return true
-}
-
-void mg_timer_poll(struct mg_timer **head, uint64_t now_ms) {
-  struct mg_timer *t, *tmp;
-  for (t = *head; t != NULL; t = tmp) {
-    bool once = t->expire == 0 && (t->flags & MG_TIMER_RUN_NOW) &&
-                !(t->flags & MG_TIMER_CALLED);  // Handle MG_TIMER_NOW only once
-    bool expired = mg_timer_expired(&t->expire, t->period_ms, now_ms);
-    tmp = t->next;
-    if (!once && !expired) continue;
-    if ((t->flags & MG_TIMER_REPEAT) || !(t->flags & MG_TIMER_CALLED)) {
-      t->fn(t->arg);
-    }
-    t->flags |= MG_TIMER_CALLED;
-  }
-}
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/tls_builtin.c"
-#endif
-
-
-#if MG_TLS == MG_TLS_BUILTIN
-struct tls_data {
-  struct mg_iobuf send;
-  struct mg_iobuf recv;
-};
-
-#define MG_LOAD_BE16(p) ((uint16_t) ((MG_U8P(p)[0] << 8U) | MG_U8P(p)[1]))
-#define TLS_HDR_SIZE 5  // 1 byte type, 2 bytes version, 2 bytes len
-
-static inline bool mg_is_big_endian(void) {
-  int v = 1;
-  return *(unsigned char *) &v == 1;
-}
-static inline uint16_t mg_swap16(uint16_t v) {
-  return (uint16_t) ((v << 8U) | (v >> 8U));
-}
-static inline uint32_t mg_swap32(uint32_t v) {
-  return (v >> 24) | (v >> 8 & 0xff00) | (v << 8 & 0xff0000) | (v << 24);
-}
-static inline uint64_t mg_swap64(uint64_t v) {
-  return (((uint64_t) mg_swap32((uint32_t) v)) << 32) |
-         mg_swap32((uint32_t) (v >> 32));
-}
-static inline uint16_t mg_be16(uint16_t v) {
-  return mg_is_big_endian() ? mg_swap16(v) : v;
-}
-static inline uint32_t mg_be32(uint32_t v) {
-  return mg_is_big_endian() ? mg_swap32(v) : v;
-}
-
-static inline void add8(struct mg_iobuf *io, uint8_t data) {
-  mg_iobuf_add(io, io->len, &data, sizeof(data));
-}
-static inline void add16(struct mg_iobuf *io, uint16_t data) {
-  data = mg_htons(data);
-  mg_iobuf_add(io, io->len, &data, sizeof(data));
-}
-static inline void add32(struct mg_iobuf *io, uint32_t data) {
-  data = mg_htonl(data);
-  mg_iobuf_add(io, io->len, &data, sizeof(data));
-}
-
-void mg_tls_init(struct mg_connection *c, struct mg_str hostname) {
-  struct tls_data *tls = (struct tls_data *) calloc(1, sizeof(struct tls_data));
-  if (tls != NULL) {
-    tls->send.align = tls->recv.align = MG_IO_SIZE;
-    c->tls = tls;
-    c->is_tls = c->is_tls_hs = 1;
-  } else {
-    mg_error(c, "tls oom");
-  }
-  (void) hostname;
-}
-void mg_tls_free(struct mg_connection *c) {
-  struct tls_data *tls = c->tls;
-  if (tls != NULL) {
-    mg_iobuf_free(&tls->send);
-    mg_iobuf_free(&tls->recv);
-  }
-  free(c->tls);
-  c->tls = NULL;
-}
-long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
-  (void) c, (void) buf, (void) len;
-  // MG_INFO(("BBBBBBBB"));
-  return -1;
-}
-long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
-  (void) c, (void) buf, (void) len;
-  char tmp[8192];
-  long n = mg_io_recv(c, tmp, sizeof(tmp));
-  if (n > 0) mg_hexdump(tmp, (size_t) n);
-  MG_INFO(("AAAAAAAA"));
-  return -1;
-  // struct mg_tls *tls = (struct mg_tls *) c->tls;
-  // long n = mbedtls_ssl_read(&tls->ssl, (unsigned char *) buf, len);
-  // if (n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE)
-  //   return MG_IO_WAIT;
-  // if (n <= 0) return MG_IO_ERR;
-  // return n;
-}
-size_t mg_tls_pending(struct mg_connection *c) {
-  (void) c;
-  return 0;
-}
-void mg_tls_handshake(struct mg_connection *c) {
-  struct tls_data *tls = c->tls;
-  struct mg_iobuf *rio = &tls->recv;
-  struct mg_iobuf *wio = &tls->send;
-  // Pull data from TCP
-  for (;;) {
-    mg_iobuf_resize(rio, rio->len + 1);
-    long n = mg_io_recv(c, &rio->buf[rio->len], rio->size - rio->len);
-    if (n > 0) {
-      rio->len += (size_t) n;
-    } else if (n == MG_IO_WAIT) {
-      break;
-    } else {
-      mg_error(c, "IO err");
-      return;
-    }
-  }
-  // Look if we've pulled everything
-  if (rio->len < TLS_HDR_SIZE) return;
-  uint8_t record_type = rio->buf[0];
-  uint16_t record_len = MG_LOAD_BE16(rio->buf + 3);
-  uint16_t record_version = MG_LOAD_BE16(rio->buf + 1);
-  if (record_type != 22) {
-    mg_error(c, "no 22");
-    return;
-  }
-  if (rio->len < (size_t) TLS_HDR_SIZE + record_len) return;
-  // Got full hello
-  // struct tls_hello *hello = (struct tls_hello *) (hdr + 1);
-  MG_INFO(("CT=%d V=%hx L=%hu", record_type, record_version, record_len));
-  mg_hexdump(rio->buf, rio->len);
-
-  // Send response. Server Hello
-  size_t ofs = wio->len;
-  add8(wio, 22), add16(wio, 0x303), add16(wio, 0);  // Layer: type, ver, len
-  add8(wio, 2), add8(wio, 0), add16(wio, 0), add16(wio, 0x304);  // Hello
-  mg_iobuf_add(wio, wio->len, NULL, 32);                         // 32 random
-  mg_random(wio->buf + wio->len - 32, 32);                       // bytes
-  add8(wio, 0);                                                  // Session ID
-  add16(wio, 0x1301);  // Cipher: TLS_AES_128_GCM_SHA256
-  add8(wio, 0);        // Compression method: 0
-  add16(wio, 46);      // Extensions length
-  add16(wio, 43), add16(wio, 2), add16(wio, 0x304);  // extension: TLS 1.3
-  add16(wio, 51), add16(wio, 36), add16(wio, 29), add16(wio, 32);  // keyshare
-  mg_iobuf_add(wio, wio->len, NULL, 32);                           // 32 random
-  mg_random(wio->buf + wio->len - 32, 32);                         // bytes
-  *(uint16_t *) &wio->buf[ofs + 3] = mg_be16((uint16_t) (wio->len - ofs - 5));
-  *(uint16_t *) &wio->buf[ofs + 7] = mg_be16((uint16_t) (wio->len - ofs - 9));
-
-  // Change cipher. Cipher's payload is an encypted app data
-  // ofs = wio->len;
-  add8(wio, 20), add16(wio, 0x303);  // Layer: type, version
-  add16(wio, 1), add8(wio, 1);
-
-  ofs = wio->len;                                   // Application data
-  add8(wio, 23), add16(wio, 0x303), add16(wio, 5);  // Layer: type, version
-  // mg_iobuf_add(wio, wio->len, "\x01\x02\x03\x04\x05", 5);
-  add8(wio, 22);                                       // handshake message
-  add8(wio, 8);                                        // encrypted extensions
-  add8(wio, 0), add16(wio, 2), add16(wio, 0);          // empty 2 bytes
-  add8(wio, 11);                                       // certificate message
-  add8(wio, 0), add16(wio, 4), add32(wio, 0x1020304);  // len
-  *(uint16_t *) &wio->buf[ofs + 3] = mg_be16((uint16_t)(wio->len - ofs - 5));
-
-  mg_io_send(c, wio->buf, wio->len);
-  wio->len = 0;
-
-  rio->len = 0;
-  c->is_tls_hs = 0;
-  mg_error(c, "doh");
-}
-void mg_tls_ctx_free(struct mg_mgr *mgr) {
-  mgr->tls_ctx = NULL;
-}
-void mg_tls_ctx_init(struct mg_mgr *mgr, const struct mg_tls_opts *opts) {
-  (void) opts, (void) mgr;
-}
-#endif
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/tls_dummy.c"
-#endif
-
-
-#if MG_TLS == MG_TLS_NONE
-void mg_tls_init(struct mg_connection *c, struct mg_str hostname) {
-  (void) hostname;
-  mg_error(c, "TLS is not enabled");
-}
-void mg_tls_handshake(struct mg_connection *c) {
-  (void) c;
-}
-void mg_tls_free(struct mg_connection *c) {
-  (void) c;
-}
-long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
-  return c == NULL || buf == NULL || len == 0 ? 0 : -1;
-}
-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;
-}
-void mg_tls_ctx_free(struct mg_mgr *mgr) {
-  mgr->tls_ctx = NULL;
-}
-void mg_tls_ctx_init(struct mg_mgr *mgr, const struct mg_tls_opts *opts) {
-  (void) opts, (void) mgr;
-}
-#endif
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/tls_mbed.c"
-#endif
-
-
-
-
-#if MG_TLS == MG_TLS_MBED
-
-#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) {
-    mbedtls_ssl_free(&tls->ssl);
-    mbedtls_ssl_config_free(&tls->conf);
-    free(tls);
-    c->tls = NULL;
-  }
-}
-
-static int mg_net_send(void *ctx, const unsigned char *buf, size_t len) {
-  long n = mg_io_send((struct mg_connection *) ctx, buf, len);
-  MG_VERBOSE(("%lu n=%ld e=%d", ((struct mg_connection *) ctx)->id, n, errno));
-  if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
-  if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
-  if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_SEND_FAILED;
-  return (int) n;
-}
-
-static int mg_net_recv(void *ctx, unsigned char *buf, size_t len) {
-  long n = mg_io_recv((struct mg_connection *) ctx, buf, len);
-  MG_VERBOSE(("%lu n=%ld", ((struct mg_connection *) ctx)->id, n));
-  if (n == MG_IO_WAIT) return MBEDTLS_ERR_SSL_WANT_WRITE;
-  if (n == MG_IO_RESET) return MBEDTLS_ERR_NET_CONN_RESET;
-  if (n == MG_IO_ERR) return MBEDTLS_ERR_NET_RECV_FAILED;
-  return (int) n;
-}
-
-void mg_tls_handshake(struct mg_connection *c) {
-  struct mg_tls *tls = (struct mg_tls *) c->tls;
-  int rc = mbedtls_ssl_handshake(&tls->ssl);
-  if (rc == 0) {  // Success
-    MG_DEBUG(("%lu success", c->id));
-    c->is_tls_hs = 0;
-    mg_call(c, MG_EV_TLS_HS, NULL);
-  } else if (rc == MBEDTLS_ERR_SSL_WANT_READ ||
-             rc == MBEDTLS_ERR_SSL_WANT_WRITE) {  // Still pending
-    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
-  }
-}
-
-static int mbed_rng(void *ctx, unsigned char *buf, size_t len) {
-  mg_random(buf, len);
-  (void) ctx;
-  return 0;
-}
-
-static void debug_cb(void *c, int lev, const char *s, int n, const char *s2) {
-  n = (int) strlen(s2) - 1;
-  MG_INFO(("%lu %d %.*s", ((struct mg_connection *) c)->id, lev, n, s2));
-  (void) s;
-}
-
-#ifdef MBEDTLS_SSL_SESSION_TICKETS
-static int rng_get(void *p_rng, unsigned char *buf, size_t len) {
-  (void) p_rng;
-  mg_random(buf, len);
-  return 0;
-}
-#endif
-
-void mg_tls_init(struct mg_connection *c, struct mg_str hostname) {
-  struct mg_tls_ctx *ctx = (struct mg_tls_ctx *) c->mgr->tls_ctx;
-  struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
-  int rc = 0;
-
-  c->tls = tls;
-  if (c->tls == NULL) {
-    mg_error(c, "TLS OOM");
-    goto fail;
-  }
-
-  MG_DEBUG(("%lu Setting TLS", c->id));
-  mbedtls_ssl_init(&tls->ssl);
-  mbedtls_ssl_config_init(&tls->conf);
-  mbedtls_ssl_conf_dbg(&tls->conf, debug_cb, c);
-#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,
-           MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
-    mg_error(c, "tls defaults %#x", -rc);
-    goto fail;
-  }
-  mbedtls_ssl_conf_rng(&tls->conf, mbed_rng, c);
-
-  if (c->is_client && ctx->client_ca.version) {
-    mbedtls_ssl_conf_ca_chain(&tls->conf, &ctx->client_ca, NULL);
-    mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
-    if (hostname.ptr != NULL && hostname.ptr[0] != '\0') {
-      struct mg_addr addr;
-      if (!mg_aton(hostname, &addr)) {  // if srvname is not an IP address
-        char *host = mg_mprintf("%.*s", (int) hostname.len, hostname.ptr);
-        mbedtls_ssl_set_hostname(&tls->ssl, host);
-        free(host);
-      }
-    }
-  } else if (!c->is_client && ctx->server_ca.version) {
-    mbedtls_ssl_conf_ca_chain(&tls->conf, &ctx->server_ca, NULL);
-    mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
-  } else {
-    mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_NONE);
-  }
-  if (c->is_client && ctx->client_cert.version &&
-      (rc = mbedtls_ssl_conf_own_cert(&tls->conf, &ctx->client_cert,
-                                      &ctx->client_key)) != 0) {
-    mg_error(c, "own cert %#x", -rc);
-    goto fail;
-  }
-  if (!c->is_client && ctx->server_cert.version &&
-      (rc = mbedtls_ssl_conf_own_cert(&tls->conf, &ctx->server_cert,
-                                      &ctx->server_key)) != 0) {
-    mg_error(c, "own cert %#x", -rc);
-    goto fail;
-  }
-#ifdef MBEDTLS_SSL_SESSION_TICKETS
-  mbedtls_ssl_conf_session_tickets_cb(&tls->conf, mbedtls_ssl_ticket_write,
-                                      mbedtls_ssl_ticket_parse,
-                                      &ctx->ticket_ctx);
-#endif
-
-  if ((rc = mbedtls_ssl_setup(&tls->ssl, &tls->conf)) != 0) {
-    mg_error(c, "setup err %#x", -rc);
-    goto fail;
-  }
-
-  c->tls = tls;
-  c->is_tls = 1;
-  c->is_tls_hs = 1;
-  mbedtls_ssl_set_bio(&tls->ssl, c, mg_net_send, mg_net_recv, 0);
-  if (c->is_client && c->is_resolving == 0 && c->is_connecting == 0) {
-    mg_tls_handshake(c);
-  }
-  return;
-fail:
-  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) {
-  struct mg_tls *tls = (struct mg_tls *) c->tls;
-  long n = mbedtls_ssl_read(&tls->ssl, (unsigned char *) buf, len);
-  if (n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE)
-    return MG_IO_WAIT;
-  if (n <= 0) return MG_IO_ERR;
-  return n;
-}
-
-long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
-  struct mg_tls *tls = (struct mg_tls *) c->tls;
-  long n = mbedtls_ssl_write(&tls->ssl, (unsigned char *) buf, len);
-  if (n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE)
-    return MG_IO_WAIT;
-  if (n <= 0) return MG_IO_ERR;
-  return n;
-}
-
-static bool load_cert(struct mg_str str, mbedtls_x509_crt *p) {
-  int rc;
-  if (str.ptr == NULL || str.ptr[0] == '\0' || str.ptr[0] == '*') return true;
-  if (str.ptr[0] == '-') str.len++;  // PEM, include trailing NUL
-  if ((rc = mbedtls_x509_crt_parse(p, (uint8_t *) str.ptr, str.len)) != 0) {
-    MG_ERROR(("cert err %#x", -rc));
-    return false;
-  }
-  return true;
-}
-
-static bool load_key(struct mg_str str, mbedtls_pk_context *p) {
-  int rc;
-  if (str.ptr == NULL || str.ptr[0] == '\0' || str.ptr[0] == '*') return true;
-  if (str.ptr[0] == '-') str.len++;  // PEM, include trailing NUL
-  if ((rc = mbedtls_pk_parse_key(p, (uint8_t *) str.ptr, str.len, NULL,
-                                 0 MGRNG)) != 0) {
-    MG_ERROR(("key err %#x", -rc));
-    return false;
-  }
-  return true;
-}
-
-void mg_tls_ctx_init(struct mg_mgr *mgr, const struct mg_tls_opts *opts) {
-  struct mg_tls_ctx *ctx = (struct mg_tls_ctx *) calloc(1, sizeof(*ctx));
-  if (ctx == NULL) goto fail;
-  MG_DEBUG(("Setting up TLS context"));
-
-#if defined(MG_MBEDTLS_DEBUG_LEVEL)
-  mbedtls_debug_set_threshold(MG_MBEDTLS_DEBUG_LEVEL);
-#endif
-
-  if (!load_cert(opts->client_ca, &ctx->client_ca)) goto fail;
-  if (!load_cert(opts->server_ca, &ctx->server_ca)) goto fail;
-  if (!load_cert(opts->client_cert, &ctx->client_cert)) goto fail;
-  if (!load_cert(opts->server_cert, &ctx->server_cert)) goto fail;
-  if (!load_key(opts->server_key, &ctx->server_key)) goto fail;
-  if (!load_key(opts->client_key, &ctx->client_key)) goto fail;
-
-#ifdef MBEDTLS_SSL_SESSION_TICKETS
-  {
-    int rc;
-    mbedtls_ssl_ticket_init(&ctx->ticket_ctx);
-    if ((rc = mbedtls_ssl_ticket_setup(&ctx->ticket_ctx, rng_get, NULL,
-                                       MBEDTLS_CIPHER_AES_128_GCM, 86400)) !=
-        0) {
-      MG_ERROR(("setup session tickets err %#x", -rc));
-      goto fail;
-    }
-  }
-#endif
-  mgr->tls_ctx = ctx;
-  return;
-fail:
-  mg_tls_ctx_free(mgr);
-}
-
-void mg_tls_ctx_free(struct mg_mgr *mgr) {
-  struct mg_tls_ctx *ctx = (struct mg_tls_ctx *) mgr->tls_ctx;
-  if (ctx != NULL) {
-    mbedtls_x509_crt_free(&ctx->server_cert);
-    mbedtls_pk_free(&ctx->server_key);
-    mbedtls_x509_crt_free(&ctx->client_cert);
-    mbedtls_pk_free(&ctx->client_key);
-    mbedtls_x509_crt_free(&ctx->client_ca);
-    mbedtls_x509_crt_free(&ctx->server_ca);
-#ifdef MBEDTLS_SSL_SESSION_TICKETS
-    mbedtls_ssl_ticket_free(&ctx->ticket_ctx);
-#endif
-    free(ctx);
-    mgr->tls_ctx = NULL;
-  }
-}
-#endif
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/tls_openssl.c"
-#endif
-
-
-
-#if MG_TLS == MG_TLS_OPENSSL
-static int mg_tls_err(struct mg_tls *tls, int res) {
-  int err = SSL_get_error(tls->ssl, res);
-  // We've just fetched the last error from the queue.
-  // Now we need to clear the error queue. If we do not, then the following
-  // can happen (actually reported):
-  //  - A new connection is accept()-ed with cert error (e.g. self-signed cert)
-  //  - Since all accept()-ed connections share listener's context,
-  //  - *ALL* SSL accepted connection report read error on the next poll cycle.
-  //    Thus a single errored connection can close all the rest, unrelated ones.
-  // Clearing the error keeps the shared SSL_CTX in an OK state.
-
-  if (err != 0) ERR_print_errors_fp(stderr);
-  ERR_clear_error();
-  if (err == SSL_ERROR_WANT_READ) return 0;
-  if (err == SSL_ERROR_WANT_WRITE) return 0;
-  return err;
-}
-
-static STACK_OF(X509_INFO) * load_ca_certs(const char *ca, int ca_len) {
-  BIO *ca_bio = BIO_new_mem_buf(ca, ca_len);
-  if (!ca_bio) return NULL;
-  STACK_OF(X509_INFO) *certs = PEM_X509_INFO_read_bio(ca_bio, NULL, NULL, NULL);
-  BIO_free(ca_bio);
-  return certs;
-}
-
-static bool add_ca_certs(SSL_CTX *ctx, STACK_OF(X509_INFO) * certs) {
-  X509_STORE *cert_store = SSL_CTX_get_cert_store(ctx);
-  for (int i = 0; i < sk_X509_INFO_num(certs); i++) {
-    X509_INFO *cert_info = sk_X509_INFO_value(certs, i);
-    if (cert_info->x509 && !X509_STORE_add_cert(cert_store, cert_info->x509))
-      return false;
-  }
-  return true;
-}
-
-static EVP_PKEY *load_key(const char *key, int key_len) {
-  BIO *key_bio = BIO_new_mem_buf(key, key_len);
-  if (!key_bio) return NULL;
-  EVP_PKEY *priv_key = PEM_read_bio_PrivateKey(key_bio, NULL, 0, NULL);
-  BIO_free(key_bio);
-  return priv_key;
-}
-
-static X509 *load_cert(const char *cert, int cert_len) {
-  BIO *cert_bio = BIO_new_mem_buf(cert, cert_len);
-  if (!cert_bio) return NULL;
-  X509 *x509 = PEM_read_bio_X509(cert_bio, NULL, 0, NULL);
-  BIO_free(cert_bio);
-  return x509;
-}
-
-void mg_tls_init(struct mg_connection *c, struct mg_str hostname) {
-  struct mg_tls_ctx *ctx = (struct mg_tls_ctx *) c->mgr->tls_ctx;
-  struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
-
-  if (ctx == NULL) {
-    mg_error(c, "TLS context not initialized");
-    goto fail;
-  }
-
-  if (tls == NULL) {
-    mg_error(c, "TLS OOM");
-    goto fail;
-  }
-
-  tls->ctx = c->is_client ? SSL_CTX_new(TLS_client_method())
-                          : SSL_CTX_new(TLS_server_method());
-  if ((tls->ssl = SSL_new(tls->ctx)) == NULL) {
-    mg_error(c, "SSL_new");
-    goto fail;
-  }
-
-  SSL_set_min_proto_version(tls->ssl, TLS1_2_VERSION);
-
-#ifdef MG_ENABLE_OPENSSL_NO_COMPRESSION
-  SSL_set_options(tls->ssl, SSL_OP_NO_COMPRESSION);
-#endif
-#ifdef MG_ENABLE_OPENSSL_CIPHER_SERVER_PREFERENCE
-  SSL_set_options(tls->ssl, SSL_OP_CIPHER_SERVER_PREFERENCE);
-#endif
-
-  if (c->is_client) {
-    if (ctx->client_ca) {
-      SSL_set_verify(tls->ssl,
-                     SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
-      if (!add_ca_certs(tls->ctx, ctx->client_ca)) goto fail;
-    }
-    if (ctx->client_cert && ctx->client_key) {
-      if (SSL_use_certificate(tls->ssl, ctx->client_cert) != 1) {
-        mg_error(c, "SSL_CTX_use_certificate");
-        goto fail;
-      }
-      if (SSL_use_PrivateKey(tls->ssl, ctx->client_key) != 1) {
-        mg_error(c, "SSL_CTX_use_PrivateKey");
-        goto fail;
-      }
-    }
-  } else {
-    if (ctx->server_ca) {
-      SSL_set_verify(tls->ssl,
-                     SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
-      if (!add_ca_certs(tls->ctx, ctx->server_ca)) goto fail;
-    }
-    if (ctx->server_cert && ctx->server_key) {
-      if (SSL_use_certificate(tls->ssl, ctx->server_cert) != 1) {
-        mg_error(c, "SSL_CTX_use_certificate");
-        goto fail;
-      }
-      if (SSL_use_PrivateKey(tls->ssl, ctx->server_key) != 1) {
-        mg_error(c, "SSL_CTX_use_PrivateKey");
-        goto fail;
-      }
-    }
-  }
-
-  SSL_set_mode(tls->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
-#if OPENSSL_VERSION_NUMBER > 0x10002000L
-  SSL_set_ecdh_auto(tls->ssl, 1);
-#endif
-
-#if OPENSSL_VERSION_NUMBER >= 0x10100000L
-  if (c->is_client && hostname.ptr && hostname.ptr[0] != '\0') {
-    char *s = mg_mprintf("%.*s", (int) hostname.len, hostname.ptr);
-    SSL_set1_host(tls->ssl, s);
-    SSL_set_tlsext_host_name(tls->ssl, s);
-    free(s);
-  }
-#endif
-
-  c->tls = tls;
-  c->is_tls = 1;
-  c->is_tls_hs = 1;
-  if (c->is_client && c->is_resolving == 0 && c->is_connecting == 0) {
-    mg_tls_handshake(c);
-  }
-  MG_DEBUG(("%lu SSL %s OK", c->id, c->is_accepted ? "accept" : "client"));
-  return;
-
-fail:
-  c->is_closing = 1;
-  free(tls);
-}
-
-void mg_tls_handshake(struct mg_connection *c) {
-  struct mg_tls *tls = (struct mg_tls *) c->tls;
-  int rc;
-  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) {
-    MG_DEBUG(("%lu success", c->id));
-    c->is_tls_hs = 0;
-    mg_call(c, MG_EV_TLS_HS, NULL);
-  } else {
-    int code = mg_tls_err(tls, rc);
-    if (code != 0) mg_error(c, "tls hs: rc %d, err %d", rc, code);
-  }
-}
-
-void mg_tls_free(struct mg_connection *c) {
-  struct mg_tls *tls = (struct mg_tls *) c->tls;
-  if (tls == NULL) return;
-  SSL_free(tls->ssl);
-  SSL_CTX_free(tls->ctx);
-  free(tls);
-  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);
-  if (n < 0 && mg_tls_err(tls, n) == 0) return MG_IO_WAIT;
-  if (n <= 0) return MG_IO_ERR;
-  return n;
-}
-
-long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
-  struct mg_tls *tls = (struct mg_tls *) c->tls;
-  int n = SSL_write(tls->ssl, buf, (int) len);
-  if (n < 0 && mg_tls_err(tls, n) == 0) return MG_IO_WAIT;
-  if (n <= 0) return MG_IO_ERR;
-  return n;
-}
-
-void mg_tls_ctx_free(struct mg_mgr *mgr) {
-  struct mg_tls_ctx *ctx = (struct mg_tls_ctx *) mgr->tls_ctx;
-  if (ctx) {
-    if (ctx->server_cert) X509_free(ctx->server_cert);
-    if (ctx->server_key) EVP_PKEY_free(ctx->server_key);
-    if (ctx->server_ca)
-      sk_X509_INFO_pop_free(ctx->server_ca, X509_INFO_free);
-    if (ctx->client_cert) X509_free(ctx->client_cert);
-    if (ctx->client_key) EVP_PKEY_free(ctx->client_key);
-    if (ctx->client_ca)
-      sk_X509_INFO_pop_free(ctx->client_ca, X509_INFO_free);
-    free(ctx);
-    mgr->tls_ctx = NULL;
-  }
-}
-
-void mg_tls_ctx_init(struct mg_mgr *mgr, const struct mg_tls_opts *opts) {
-  static unsigned char s_initialised = 0;
-  if (!s_initialised) {
-    SSL_library_init();
-    s_initialised++;
-  }
-
-  struct mg_tls_ctx *ctx = (struct mg_tls_ctx *) calloc(1, sizeof(*ctx));
-  if (ctx == NULL) return;
-
-  if (opts->server_cert.ptr && opts->server_cert.ptr[0] != '\0') {
-    struct mg_str key = opts->server_key;
-    if (!key.ptr) key = opts->server_cert;
-    if (!(ctx->server_cert =
-              load_cert(opts->server_cert.ptr, (int) opts->server_cert.len)))
-      goto fail;
-    if (!(ctx->server_key = load_key(key.ptr, (int) key.len))) goto fail;
-  }
-
-  if (opts->server_ca.ptr && opts->server_ca.ptr[0] != '\0') {
-    if (!(ctx->server_ca =
-              load_ca_certs(opts->server_ca.ptr, (int) opts->server_ca.len)))
-      goto fail;
-  }
-
-  if (opts->client_cert.ptr && opts->client_cert.ptr[0] != '\0') {
-    struct mg_str key = opts->client_key;
-    if (!key.ptr) key = opts->client_cert;
-    if (!(ctx->client_cert =
-              load_cert(opts->client_cert.ptr, (int) opts->client_cert.len)))
-      goto fail;
-    if (!(ctx->client_key = load_key(key.ptr, (int) key.len))) goto fail;
-  }
-
-  if (opts->client_ca.ptr && opts->client_ca.ptr[0] != '\0') {
-    if (!(ctx->client_ca =
-              load_ca_certs(opts->client_ca.ptr, (int) opts->client_ca.len)))
-      goto fail;
-  }
-
-  mgr->tls_ctx = ctx;
-  return;
-fail:
-  MG_ERROR(("TLS ctx init error"));
-  mg_tls_ctx_free(mgr);
-}
-
-#endif
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/url.c"
-#endif
-
-
-struct url {
-  size_t key, user, pass, host, port, uri, end;
-};
-
-int mg_url_is_ssl(const char *url) {
-  return strncmp(url, "wss:", 4) == 0 || strncmp(url, "https:", 6) == 0 ||
-         strncmp(url, "mqtts:", 6) == 0 || strncmp(url, "ssl:", 4) == 0 ||
-         strncmp(url, "tls:", 4) == 0;
-}
-
-static struct url urlparse(const char *url) {
-  size_t i;
-  struct url u;
-  memset(&u, 0, sizeof(u));
-  for (i = 0; url[i] != '\0'; 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 && u.uri == 0) {
-      u.user = u.host;
-      u.pass = u.port;
-      u.host = i + 1;
-      u.port = 0;
-    } else if (url[i] == '/' && u.host && u.uri == 0) {
-      u.uri = i;
-    }
-  }
-  u.end = i;
-#if 0
-  printf("[%s] %d %d %d %d %d\n", url, u.user, u.pass, u.host, u.port, u.uri);
-#endif
-  return u;
-}
-
-struct mg_str mg_url_host(const char *url) {
-  struct url u = urlparse(url);
-  size_t n = u.port  ? u.port - u.host - 1
-             : u.uri ? u.uri - u.host
-                     : u.end - u.host;
-  struct mg_str s = mg_str_n(url + u.host, n);
-  return s;
-}
-
-const char *mg_url_uri(const char *url) {
-  struct url u = urlparse(url);
-  return u.uri ? url + u.uri : "/";
-}
-
-unsigned short mg_url_port(const char *url) {
-  struct url u = urlparse(url);
-  unsigned short port = 0;
-  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;
-}
-
-struct mg_str mg_url_user(const char *url) {
-  struct url u = urlparse(url);
-  struct mg_str s = mg_str("");
-  if (u.user && (u.pass || u.host)) {
-    size_t n = u.pass ? u.pass - u.user - 1 : u.host - u.user - 1;
-    s = mg_str_n(url + u.user, n);
-  }
-  return s;
-}
-
-struct mg_str mg_url_pass(const char *url) {
-  struct url u = urlparse(url);
-  struct mg_str s = mg_str_n("", 0UL);
-  if (u.pass && u.host) {
-    size_t n = u.host - u.pass - 1;
-    s = mg_str_n(url + u.pass, n);
-  }
-  return s;
-}
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/util.c"
-#endif
-
-
-#if MG_ENABLE_CUSTOM_RANDOM
-#else
-void mg_random(void *buf, size_t len) {
-  bool done = false;
-  unsigned char *p = (unsigned char *) buf;
-#if MG_ARCH == MG_ARCH_ESP32
-  while (len--) *p++ = (unsigned char) (esp_random() & 255);
-  done = true;
-#elif MG_ARCH == MG_ARCH_WIN32
-#elif MG_ARCH == MG_ARCH_UNIX
-  FILE *fp = fopen("/dev/urandom", "rb");
-  if (fp != NULL) {
-    if (fread(buf, 1, len, fp) == len) done = true;
-    fclose(fp);
-  }
-#endif
-  // If everything above did not work, fallback to a pseudo random generator
-  while (!done && len--) *p++ = (unsigned char) (rand() & 255);
-}
-#endif
-
-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 buf;
-}
-
-uint32_t mg_ntohl(uint32_t net) {
-  uint8_t data[4] = {0, 0, 0, 0};
-  memcpy(&data, &net, sizeof(data));
-  return (((uint32_t) data[3]) << 0) | (((uint32_t) data[2]) << 8) |
-         (((uint32_t) data[1]) << 16) | (((uint32_t) data[0]) << 24);
-}
-
-uint16_t mg_ntohs(uint16_t net) {
-  uint8_t data[2] = {0, 0};
-  memcpy(&data, &net, sizeof(data));
-  return (uint16_t) ((uint16_t) data[1] | (((uint16_t) data[0]) << 8));
-}
-
-uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len) {
-  static const uint32_t crclut[16] = {
-      // table for polynomial 0xEDB88320 (reflected)
-      0x00000000, 0x1DB71064, 0x3B6E20C8, 0x26D930AC, 0x76DC4190, 0x6B6B51F4,
-      0x4DB26158, 0x5005713C, 0xEDB88320, 0xF00F9344, 0xD6D6A3E8, 0xCB61B38C,
-      0x9B64C2B0, 0x86D3D2D4, 0xA00AE278, 0xBDBDF21C};
-  crc = ~crc;
-  while (len--) {
-    uint8_t byte = *(uint8_t *) buf++;
-    crc = crclut[(crc ^ byte) & 0x0F] ^ (crc >> 4);
-    crc = crclut[(crc ^ (byte >> 4)) & 0x0F] ^ (crc >> 4);
-  }
-  return ~crc;
-}
-
-static int isbyte(int n) {
-  return n >= 0 && n <= 255;
-}
-
-static int parse_net(const char *spec, uint32_t *net, uint32_t *mask) {
-  int n, a, b, c, d, slash = 32, len = 0;
-  if ((sscanf(spec, "%d.%d.%d.%d/%d%n", &a, &b, &c, &d, &slash, &n) == 5 ||
-       sscanf(spec, "%d.%d.%d.%d%n", &a, &b, &c, &d, &n) == 4) &&
-      isbyte(a) && isbyte(b) && isbyte(c) && isbyte(d) && slash >= 0 &&
-      slash < 33) {
-    len = n;
-    *net = ((uint32_t) a << 24) | ((uint32_t) b << 16) | ((uint32_t) c << 8) |
-           (uint32_t) d;
-    *mask = slash ? (uint32_t) (0xffffffffU << (32 - slash)) : (uint32_t) 0;
-  }
-  return len;
-}
-
-int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip) {
-  struct mg_str k, v;
-  int allowed = acl.len == 0 ? '+' : '-';  // If any ACL is set, deny by default
-  uint32_t remote_ip4;
-  if (remote_ip->is_ip6) {
-    return -1;  // TODO(): handle IPv6 ACL and addresses
-  } else {  // IPv4
-    memcpy((void *) &remote_ip4, remote_ip->ip, sizeof(remote_ip4));
-    while (mg_commalist(&acl, &k, &v)) {
-      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 ((mg_ntohl(remote_ip4) & mask) == net) allowed = k.ptr[0];
-    }
-  }
-  return allowed == '+';
-}
-
-#if MG_ENABLE_CUSTOM_MILLIS
-#else
-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 || MG_ARCH == MG_ARCH_FREERTOS
-  return xTaskGetTickCount() * portTICK_PERIOD_MS;
-#elif MG_ARCH == MG_ARCH_AZURERTOS
-  return tx_time_get() * (1000 /* MS per SEC */ / TX_TIMER_TICKS_PER_SECOND);
-#elif MG_ARCH == MG_ARCH_TIRTOS
-  return (uint64_t) Clock_getTicks();
-#elif MG_ARCH == MG_ARCH_ZEPHYR
-  return (uint64_t) k_uptime_get();
-#elif MG_ARCH == MG_ARCH_CMSIS_RTOS1
-  return (uint64_t) rt_time_get();
-#elif MG_ARCH == MG_ARCH_CMSIS_RTOS2
-  return (uint64_t) ((osKernelGetTickCount() * 1000) / osKernelGetTickFreq());
-#elif MG_ARCH == MG_ARCH_RTTHREAD
-  return (uint64_t) ((rt_tick_get() * 1000) / RT_TICK_PER_SECOND);
-#elif MG_ARCH == MG_ARCH_UNIX && defined(__APPLE__)
-  // 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);
-#elif defined(CLOCK_MONOTONIC)
-  // Affected by the incremental adjustments performed by adjtime and NTP
-  clock_gettime(CLOCK_MONOTONIC, &ts);
-#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 ((uint64_t) ts.tv_sec * 1000 + (uint64_t) ts.tv_nsec / 1000000);
-#elif defined(ARDUINO)
-  return (uint64_t) millis();
-#else
-  return (uint64_t) (time(NULL) * 1000);
-#endif
-}
-#endif
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/ws.c"
-#endif
-
-
-
-
-
-
-
-
-
-
-
-struct ws_msg {
-  uint8_t flags;
-  size_t header_len;
-  size_t data_len;
-};
-
-size_t mg_ws_vprintf(struct mg_connection *c, int op, const char *fmt,
-                     va_list *ap) {
-  size_t len = c->send.len;
-  size_t n = mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
-  mg_ws_wrap(c, c->send.len - len, op);
-  return n;
-}
-
-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) {
-  const char *magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
-  unsigned char sha[20], b64_sha[30];
-
-  mg_sha1_ctx sha_ctx;
-  mg_sha1_init(&sha_ctx);
-  mg_sha1_update(&sha_ctx, (unsigned char *) wskey->ptr, wskey->len);
-  mg_sha1_update(&sha_ctx, (unsigned char *) magic, 36);
-  mg_sha1_final(sha, &sha_ctx);
-  mg_base64_encode(sha, sizeof(sha), (char *) b64_sha);
-  mg_xprintf(mg_pfn_iobuf, &c->send,
-             "HTTP/1.1 101 Switching Protocols\r\n"
-             "Upgrade: websocket\r\n"
-             "Connection: Upgrade\r\n"
-             "Sec-WebSocket-Accept: %s\r\n",
-             b64_sha);
-  if (fmt != NULL) mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, ap);
-  if (wsproto != NULL) {
-    mg_printf(c, "Sec-WebSocket-Protocol: %.*s\r\n", (int) wsproto->len,
-              wsproto->ptr);
-  }
-  mg_send(c, "\r\n", 2);
-}
-
-static uint32_t be32(const uint8_t *p) {
-  return (((uint32_t) p[3]) << 0) | (((uint32_t) p[2]) << 8) |
-         (((uint32_t) p[1]) << 16) | (((uint32_t) p[0]) << 24);
-}
-
-static size_t ws_process(uint8_t *buf, size_t len, struct ws_msg *msg) {
-  size_t i, n = 0, mask_len = 0;
-  memset(msg, 0, sizeof(*msg));
-  if (len >= 2) {
-    n = buf[1] & 0x7f;                // Frame length
-    mask_len = buf[1] & 128 ? 4 : 0;  // last bit is a mask bit
-    msg->flags = buf[0];
-    if (n < 126 && len >= mask_len) {
-      msg->data_len = n;
-      msg->header_len = 2 + mask_len;
-    } else if (n == 126 && len >= 4 + mask_len) {
-      msg->header_len = 4 + mask_len;
-      msg->data_len = (((size_t) buf[2]) << 8) | buf[3];
-    } else if (len >= 10 + mask_len) {
-      msg->header_len = 10 + mask_len;
-      msg->data_len =
-          (size_t) (((uint64_t) be32(buf + 2) << 32) + be32(buf + 6));
-    }
-  }
-  // Sanity check, and integer overflow protection for the boundary check below
-  // data_len should not be larger than 1 Gb
-  if (msg->data_len > 1024 * 1024 * 1024) return 0;
-  if (msg->header_len + msg->data_len > len) return 0;
-  if (mask_len > 0) {
-    uint8_t *p = buf + msg->header_len, *m = p - mask_len;
-    for (i = 0; i < msg->data_len; i++) p[i] ^= m[i & 3];
-  }
-  return msg->header_len + msg->data_len;
-}
-
-static size_t mkhdr(size_t len, int op, bool is_client, uint8_t *buf) {
-  size_t n = 0;
-  buf[0] = (uint8_t) (op | 128);
-  if (len < 126) {
-    buf[1] = (unsigned char) len;
-    n = 2;
-  } else if (len < 65536) {
-    uint16_t tmp = mg_htons((uint16_t) len);
-    buf[1] = 126;
-    memcpy(&buf[2], &tmp, sizeof(tmp));
-    n = 4;
-  } else {
-    uint32_t tmp;
-    buf[1] = 127;
-    tmp = mg_htonl((uint32_t) (((uint64_t) len) >> 32));
-    memcpy(&buf[2], &tmp, sizeof(tmp));
-    tmp = mg_htonl((uint32_t) (len & 0xffffffffU));
-    memcpy(&buf[6], &tmp, sizeof(tmp));
-    n = 10;
-  }
-  if (is_client) {
-    buf[1] |= 1 << 7;  // Set masking flag
-    mg_random(&buf[n], 4);
-    n += 4;
-  }
-  return n;
-}
-
-static void mg_ws_mask(struct mg_connection *c, size_t len) {
-  if (c->is_client && c->send.buf != NULL) {
-    size_t i;
-    uint8_t *p = c->send.buf + c->send.len - len, *mask = p - 4;
-    for (i = 0; i < len; i++) p[i] ^= mask[i & 3];
-  }
-}
-
-size_t mg_ws_send(struct mg_connection *c, const void *buf, size_t len,
-                  int op) {
-  uint8_t header[14];
-  size_t header_len = mkhdr(len, op, c->is_client, header);
-  mg_send(c, header, header_len);
-  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;
-}
-
-static bool mg_ws_client_handshake(struct mg_connection *c) {
-  int n = mg_http_get_request_len(c->recv.buf, c->recv.len);
-  if (n < 0) {
-    mg_error(c, "not http");  // Some just, not an HTTP request
-  } else if (n > 0) {
-    if (n < 15 || memcmp(c->recv.buf + 9, "101", 3) != 0) {
-      mg_error(c, "ws handshake error");
-    } else {
-      struct mg_http_message hm;
-      if (mg_http_parse((char *) c->recv.buf, c->recv.len, &hm)) {
-        c->is_websocket = 1;
-        mg_call(c, MG_EV_WS_OPEN, &hm);
-      } else {
-        mg_error(c, "ws handshake error");
-      }
-    }
-    mg_iobuf_del(&c->recv, 0, (size_t) n);
-  } else {
-    return true;  // Request is not yet received, quit event handler
-  }
-  return false;  // Continue event handler
-}
-
-static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data,
-                     void *fn_data) {
-  struct ws_msg msg;
-  size_t ofs = (size_t) c->pfn_data;
-
-  // assert(ofs < c->recv.len);
-  if (ev == MG_EV_READ) {
-    if (c->is_client && !c->is_websocket && mg_ws_client_handshake(c)) return;
-
-    while (ws_process(c->recv.buf + ofs, c->recv.len - ofs, &msg) > 0) {
-      char *s = (char *) c->recv.buf + ofs + msg.header_len;
-      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;
-      // 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:
-          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;
-        case WEBSOCKET_OP_PONG:
-          mg_call(c, MG_EV_WS_CTL, &m);
-          break;
-        case WEBSOCKET_OP_TEXT:
-        case WEBSOCKET_OP_BINARY:
-          if (final) mg_call(c, MG_EV_WS_MSG, &m);
-          break;
-        case WEBSOCKET_OP_CLOSE:
-          MG_DEBUG(("%lu WS CLOSE", c->id));
-          mg_call(c, MG_EV_WS_CTL, &m);
-          // Echo the payload of the received CLOSE message back to the sender
-          mg_ws_send(c, m.data.ptr, m.data.len, WEBSOCKET_OP_CLOSE);
-          c->is_draining = 1;
-          break;
-        default:
-          // Per RFC6455, close conn when an unknown op is recvd
-          mg_error(c, "unknown WS op %d", op);
-          break;
-      }
-
-      // Handle fragmented frames: strip header, keep in c->recv
-      if (final == 0 || op == 0) {
-        if (op) ofs++, len--, msg.header_len--;       // First frame
-        mg_iobuf_del(&c->recv, ofs, msg.header_len);  // Strip header
-        len -= msg.header_len;
-        ofs += len;
-        c->pfn_data = (void *) ofs;
-        // 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);
-      // Last chunk of the fragmented frame
-      if (final && !op) {
-        m.flags = c->recv.buf[0];
-        m.data = mg_str_n((char *) &c->recv.buf[1], (size_t) (ofs - 1));
-        mg_call(c, MG_EV_WS_MSG, &m);
-        mg_iobuf_del(&c->recv, 0, ofs);
-        ofs = 0;
-        c->pfn_data = NULL;
-      }
-    }
-  }
-  (void) fn_data;
-  (void) ev_data;
-}
-
-struct mg_connection *mg_ws_connect(struct mg_mgr *mgr, const char *url,
-                                    mg_event_handler_t fn, void *fn_data,
-                                    const char *fmt, ...) {
-  struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
-  if (c != NULL) {
-    char nonce[16], key[30];
-    struct mg_str host = mg_url_host(url);
-    mg_random(nonce, sizeof(nonce));
-    mg_base64_encode((unsigned char *) nonce, sizeof(nonce), key);
-    mg_xprintf(mg_pfn_iobuf, &c->send,
-               "GET %s HTTP/1.1\r\n"
-               "Upgrade: websocket\r\n"
-               "Host: %.*s\r\n"
-               "Connection: Upgrade\r\n"
-               "Sec-WebSocket-Version: 13\r\n"
-               "Sec-WebSocket-Key: %s\r\n",
-               mg_url_uri(url), (int) host.len, host.ptr, key);
-    if (fmt != NULL) {
-      va_list ap;
-      va_start(ap, fmt);
-      mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, &ap);
-      va_end(ap);
-    }
-    mg_xprintf(mg_pfn_iobuf, &c->send, "\r\n");
-    c->pfn = mg_ws_cb;
-    c->pfn_data = NULL;
-  }
-  return c;
-}
-
-void mg_ws_upgrade(struct mg_connection *c, struct mg_http_message *hm,
-                   const char *fmt, ...) {
-  struct mg_str *wskey = mg_http_get_header(hm, "Sec-WebSocket-Key");
-  c->pfn = mg_ws_cb;
-  c->pfn_data = NULL;
-  if (wskey == NULL) {
-    mg_http_reply(c, 426, "", "WS upgrade expected\n");
-    c->is_draining = 1;
-  } else {
-    struct mg_str *wsproto = mg_http_get_header(hm, "Sec-WebSocket-Protocol");
-    va_list ap;
-    va_start(ap, fmt);
-    ws_handshake(c, wskey, wsproto, fmt, &ap);
-    va_end(ap);
-    c->is_websocket = 1;
-    c->is_resp = 0;
-    mg_call(c, MG_EV_WS_OPEN, hm);
-  }
-}
-
-size_t mg_ws_wrap(struct mg_connection *c, size_t len, int op) {
-  uint8_t header[14], *p;
-  size_t header_len = mkhdr(len, op, c->is_client, header);
-
-  // NOTE: order of operations is important!
-  mg_iobuf_add(&c->send, c->send.len, NULL, header_len);
-  p = &c->send.buf[c->send.len - len];         // p points to data
-  memmove(p, p - header_len, len);             // Shift data
-  memcpy(p - header_len, header, header_len);  // Prepend header
-  mg_ws_mask(c, len);                          // Mask data
-
-  return c->send.len;
-}
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/drivers/rt1020.c"
-#endif
-
-
-/*
- * Todo
- * This driver doesn't support 10M line autoconfiguration yet.
- * Packets aren't sent if the link negociated 10M line.
- * todo: MAC back auto reconfiguration.
- */
-
-#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_IMXRT1020)
-struct imx_rt1020_enet {
-volatile uint32_t RESERVED0, EIR, EIMR, RESERVED1, RDAR, TDAR, RESERVED2[3], ECR, RESERVED3[6], MMFR, MSCR, RESERVED4[7], MIBC, RESERVED5[7], RCR, RESERVED6[15], TCR, RESERVED7[7], PALR, PAUR, OPD, TXIC0, TXIC1, TXIC2, RESERVED8, RXIC0, RXIC1, RXIC2, RESERVED9[3], IAUR, IALR, GAUR, GALR, RESERVED10[7], TFWR, RESERVED11[14], RDSR, TDSR, MRBR[2], RSFL, RSEM, RAEM, RAFL, TSEM, TAEM, TAFL, TIPG, FTRL, RESERVED12[3], TACC, RACC, RESERVED13[15], RMON_T_PACKETS, RMON_T_BC_PKT, RMON_T_MC_PKT, RMON_T_CRC_ALIGN, RMON_T_UNDERSIZE, RMON_T_OVERSIZE, RMON_T_FRAG, RMON_T_JAB, RMON_T_COL, RMON_T_P64, RMON_T_P65TO127, RMON_T_P128TO255, RMON_T_P256TO511, RMON_T_P512TO1023, RMON_T_P1024TO2048, RMON_T_GTE2048, RMON_T_OCTETS, IEEE_T_DROP, IEEE_T_FRAME_OK, IEEE_T_1COL, IEEE_T_MCOL, IEEE_T_DEF, IEEE_T_LCOL, IEEE_T_EXCOL, IEEE_T_MACERR, IEEE_T_CSERR, IEEE_T_SQE, IEEE_T_FDXFC, IEEE_T_OCTETS_OK, RESERVED14[3], RMON_R_PACKETS, RMON_R_BC_PKT, RMON_R_MC_PKT, RMON_R_CRC_ALIGN, RMON_R_UNDERSIZE, RMON_R_OVERSIZE, RMON_R_FRAG, RMON_R_JAB, RESERVED15, RMON_R_P64, RMON_R_P65TO127, RMON_R_P128TO255, RMON_R_P256TO511, RMON_R_P512TO1023, RMON_R_P1024TO2047, RMON_R_GTE2048, RMON_R_OCTETS, IEEE_R_DROP, IEEE_R_FRAME_OK, IEEE_R_CRC, IEEE_R_ALIGN, IEEE_R_MACERR, IEEE_R_FDXFC, IEEE_R_OCTETS_OK, RESERVED16[71], ATCR, ATVR, ATOFF, ATPER, ATCOR, ATINC, ATSTMP, RESERVED17[122], TGSR, TCSR0, TCCR0, TCSR1, TCCR1, TCSR2, TCCR2, TCSR3;
-};
-
-#undef ENET
-#define ENET ((struct imx_rt1020_enet *) (uintptr_t) 0x402D8000u)
-
-#undef BIT
-#define BIT(x) ((uint32_t) 1 << (x))
-
-#define ENET_RXBUFF_SIZE 1536 // 1522 Buffer must be 64bits aligned
-#define ENET_TXBUFF_SIZE 1536 // 1522 hence set to 0x600 (1536)
-#define ENET_RXBD_NUM          (4)
-#define ENET_TXBD_NUM          (4)
-
-const uint32_t EIMR_RX_ERR = 0x2400000;              // Intr mask RXF+EBERR
-
-void ETH_IRQHandler(void);
-static bool mg_tcpip_driver_imxrt1020_init(struct mg_tcpip_if *ifp);
-static void wait_phy_complete(void);
-static struct mg_tcpip_if *s_ifp;                         // MIP interface
-
-static size_t mg_tcpip_driver_imxrt1020_tx(const void *, size_t , struct mg_tcpip_if *);
-static bool mg_tcpip_driver_imxrt1020_up(struct mg_tcpip_if *ifp);
-
-enum { IMXRT1020_PHY_ADDR = 0x02, IMXRT1020_PHY_BCR = 0, IMXRT1020_PHY_BSR = 1 };     // PHY constants
-
-void delay(uint32_t);
-void delay (uint32_t di) {
-  volatile int dno = 0; // Prevent optimization
-  for (uint32_t i = 0; i < di; i++)
-    for (int j=0; j<20; j++) // PLLx20 (500 MHz/24MHz)
-      dno++;
-}
-
-static void wait_phy_complete(void) {
-  delay(0x00010000);
-  const uint32_t delay_max = 0x00100000;
-  uint32_t delay_cnt = 0;
-  while (!(ENET->EIR & BIT(23)) && (delay_cnt < delay_max))
-  {delay_cnt++;}
-  ENET->EIR |= BIT(23); // MII interrupt clear
-}
-
-static uint32_t imxrt1020_eth_read_phy(uint8_t addr, uint8_t reg) {
-  ENET->EIR |= BIT(23); // MII interrupt clear
-  uint32_t mask_phy_adr_reg = 0x1f; // 0b00011111: Ensure we write 5 bits (Phy address & register)
-  uint32_t phy_transaction = 0x00;
-  phy_transaction = (0x1 << 30) \
-                  | (0x2 << 28) \
-                  | ((uint32_t)(addr & mask_phy_adr_reg) << 23) \
-                  | ((uint32_t)(reg & mask_phy_adr_reg)  << 18) \
-                  | (0x2 << 16);
-
-  ENET->MMFR = phy_transaction;
-  wait_phy_complete();
-
-  return (ENET->MMFR & 0x0000ffff);
-}
-
-static void imxrt1020_eth_write_phy(uint8_t addr, uint8_t reg, uint32_t val) {
-  ENET->EIR |= BIT(23); // MII interrupt clear
-  uint8_t mask_phy_adr_reg = 0x1f; // 0b00011111: Ensure we write 5 bits (Phy address & register)
-  uint32_t mask_phy_data = 0x0000ffff; // Ensure we write 16 bits (data)
-  addr &= mask_phy_adr_reg;
-  reg &= mask_phy_adr_reg;
-  val &= mask_phy_data;
-  uint32_t phy_transaction = 0x00;
-  phy_transaction = (uint32_t)(0x1 << 30) \
-                  | (uint32_t)(0x1 << 28) \
-                  | (uint32_t)(addr << 23) \
-                  | (uint32_t)(reg  << 18) \
-                  | (uint32_t)(0x2 << 16) \
-                  | (uint32_t)(val);
-  ENET->MMFR = phy_transaction;
-  wait_phy_complete();
-}
-
-// FEC RX/TX descriptors (Enhanced descriptor not enabled)
-// Descriptor buffer structure, little endian
-
-typedef struct enet_bd_struct_def
-{
-    uint16_t length;  // Data length
-    uint16_t control; // Control and status
-    uint32_t *buffer; // Data ptr
-} enet_bd_struct_t;
-
-// Descriptor and buffer globals, in non-cached area, 64 bits aligned.
-
-__attribute__((section("NonCacheable,\"aw\",%nobits @"))) enet_bd_struct_t rx_buffer_descriptor[(ENET_RXBD_NUM)] __attribute__((aligned((64U))));
-__attribute__((section("NonCacheable,\"aw\",%nobits @"))) enet_bd_struct_t tx_buffer_descriptor[(ENET_TXBD_NUM)] __attribute__((aligned((64U))));
-
-uint8_t rx_data_buffer[(ENET_RXBD_NUM)][((unsigned int)(((ENET_RXBUFF_SIZE)) + (((64U))-1U)) & (unsigned int)(~(unsigned int)(((64U))-1U)))] __attribute__((aligned((64U))));
-uint8_t tx_data_buffer[(ENET_TXBD_NUM)][((unsigned int)(((ENET_TXBUFF_SIZE)) + (((64U))-1U)) & (unsigned int)(~(unsigned int)(((64U))-1U)))] __attribute__((aligned((64U))));
-
-// Initialise driver imx_rt1020
-
-// static bool mg_tcpip_driver_imxrt1020_init(uint8_t *mac, void *data) { // VO
-static bool mg_tcpip_driver_imxrt1020_init(struct mg_tcpip_if *ifp) {
-
-  struct mg_tcpip_driver_imxrt1020_data *d = (struct mg_tcpip_driver_imxrt1020_data *) ifp->driver_data;
-  s_ifp = ifp;
-
-  // ENET Reset, wait complete
-  ENET->ECR |= BIT(0);
-  while((ENET->ECR & BIT(0)) != 0) {}
-
-  // Re-latches the pin strapping pin values
-  ENET->ECR |= BIT(0);
-  while((ENET->ECR & BIT(0)) != 0) {}
-
-  // Setup MII/RMII MDC clock divider (<= 2.5MHz).
-  ENET->MSCR = 0x130; // HOLDTIME 2 clk, Preamble enable, MDC MII_Speed Div 0x30
-  imxrt1020_eth_write_phy(IMXRT1020_PHY_ADDR, IMXRT1020_PHY_BCR, 0x8000); // PHY W @0x00 D=0x8000 Soft reset
-  while (imxrt1020_eth_read_phy(IMXRT1020_PHY_ADDR, IMXRT1020_PHY_BSR) & BIT(15)) {delay(0x5000);} // Wait finished poll 10ms
-
-  // PHY: Start Link
-  {
-    imxrt1020_eth_write_phy(IMXRT1020_PHY_ADDR, IMXRT1020_PHY_BCR, 0x1200); // PHY W @0x00 D=0x1200 Autonego enable + start
-    imxrt1020_eth_write_phy(IMXRT1020_PHY_ADDR, 0x1f, 0x8180);    // PHY W @0x1f D=0x8180 Ref clock 50 MHz at XI input
-
-    uint32_t bcr = imxrt1020_eth_read_phy(IMXRT1020_PHY_ADDR, IMXRT1020_PHY_BCR);
-    bcr &= ~BIT(10); // Isolation -> Normal
-    imxrt1020_eth_write_phy(IMXRT1020_PHY_ADDR, IMXRT1020_PHY_BCR, bcr);
-  }
-
-  // Disable ENET
-  ENET->ECR = 0x0; //  Disable before configuration
-
-  // Configure ENET
-  ENET->RCR = 0x05ee0104; // #CRCFWD=0 (CRC kept in frame) + RMII + MII Enable
-  
-  ENET->TCR = BIT(8) | BIT(2); // Addins (MAC address from PAUR+PALR) + Full duplex enable
-  //ENET->TFWR = BIT(8); // Store And Forward Enable, 64 bytes (minimize tx latency)
-
-  // Configure descriptors and buffers
-  // RX
-  for (int i = 0; i < ENET_RXBD_NUM; i++) {
-    // Wrap last descriptor buffer ptr
-    rx_buffer_descriptor[i].control = (BIT(15) | ((i<(ENET_RXBD_NUM-1))?0:BIT(13))); // E+(W*)
-    rx_buffer_descriptor[i].buffer = (uint32_t *)rx_data_buffer[i];
-  }
-
-  // TX
-  for (int i = 0; i < ENET_TXBD_NUM; i++) {
-    // Wrap last descriptor buffer ptr
-    tx_buffer_descriptor[i].control = ((i<(ENET_RXBD_NUM-1))?0:BIT(13)) | BIT(10); // (W*)+TC
-    tx_buffer_descriptor[i].buffer = (uint32_t *)tx_data_buffer[i];
-  }
-
-  // Continue ENET configuration
-  ENET->RDSR = (uint32_t)(uintptr_t)rx_buffer_descriptor;
-  ENET->TDSR = (uint32_t)(uintptr_t)tx_buffer_descriptor;
-  ENET->MRBR[0] = ENET_RXBUFF_SIZE; // Same size for RX/TX buffers
-
-  // MAC address filtering (bytes in reversed order)
-  ENET->PAUR = ((uint32_t) ifp->mac[4] << 24U) | (uint32_t) ifp->mac[5] << 16U;
-  ENET->PALR = (uint32_t) (ifp->mac[0] << 24U) | ((uint32_t) ifp->mac[1] << 16U) |
-                 ((uint32_t) ifp->mac[2] << 8U) | ifp->mac[3];
-
-  // Init Hash tables (mac filtering)
-  ENET->IAUR = 0; // Unicast
-  ENET->IALR = 0;
-  ENET->GAUR = 0; // Multicast
-  ENET->GALR = 0;
-
-  // Set ENET Online
-  ENET->ECR |= BIT(8); // ENET Set Little-endian + (FEC buffer desc.)
-  ENET->ECR |= BIT(1); // Enable
-
-  // Set interrupt mask
-  ENET->EIMR = EIMR_RX_ERR;
-
-  // RX Descriptor activation
-  ENET->RDAR = BIT(24); // Activate Receive Descriptor
-  return true;
-}
-
-// Transmit frame
-static uint32_t s_rt1020_txno;
-
-static size_t mg_tcpip_driver_imxrt1020_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
-
-  if (len > sizeof(tx_data_buffer[ENET_TXBD_NUM])) {
-  //  MG_ERROR(("Frame too big, %ld", (long) len));
-    len = 0;  // Frame is too big
-  } else if ((tx_buffer_descriptor[s_rt1020_txno].control & BIT(15))) {
-  MG_ERROR(("No free descriptors"));
-    // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long) ETH->DMASR);
-    len = 0;  // All descriptors are busy, fail
-  } else {
-    memcpy(tx_data_buffer[s_rt1020_txno], buf, len);     // Copy data
-    tx_buffer_descriptor[s_rt1020_txno].length = (uint16_t) len;  // Set data len
-    tx_buffer_descriptor[s_rt1020_txno].control |= (uint16_t)(BIT(10)); // TC (transmit CRC)
-    //  tx_buffer_descriptor[s_rt1020_txno].control &= (uint16_t)(BIT(14) | BIT(12)); // Own doesn't affect HW
-    tx_buffer_descriptor[s_rt1020_txno].control |= (uint16_t)(BIT(15) | BIT(11)); // R+L (ready+last)
-    ENET->TDAR = BIT(24); // Descriptor updated. Hand over to DMA.
-    // INFO
-    // Relevant Descriptor bits: 15(R)  Ready
-    //                           11(L)  last in frame
-    //                           10(TC) transmis CRC
-    // __DSB(); // ARM errata 838869 Cortex-M4, M4F, M7, M7F: "store immediate overlapping
-                // exception" return might vector to incorrect interrupt.
-    if (++s_rt1020_txno >= ENET_TXBD_NUM) s_rt1020_txno = 0;
-  }
-  (void) ifp;
-  return len;
-}
-
-// IRQ (RX)
-static uint32_t s_rt1020_rxno;
-
-void ENET_IRQHandler(void) {
-  ENET->EIMR = 0;           // Mask interrupts.
-  uint32_t eir = ENET->EIR; // Read EIR
-  ENET->EIR = 0xffffffff;   // Clear interrupts
-
-  if (eir & EIMR_RX_ERR) // Global mask used
-  {
-    if (rx_buffer_descriptor[s_rt1020_rxno].control & BIT(15)) {
-      ENET->EIMR = EIMR_RX_ERR; // Enable interrupts
-      return;  // Empty? -> exit.
-    }
-    // Read inframes
-    else { // Frame received, loop
-      for (uint32_t i = 0; i < 10; i++) {  // read as they arrive but not forever
-        if (rx_buffer_descriptor[s_rt1020_rxno].control & BIT(15)) break;  // exit when done
-        // Process if CRC OK and frame not truncated
-        if (!(rx_buffer_descriptor[s_rt1020_rxno].control & (BIT(2) | BIT(0)))) {
-          uint32_t len = (rx_buffer_descriptor[s_rt1020_rxno].length);
-          mg_tcpip_qwrite(rx_buffer_descriptor[s_rt1020_rxno].buffer, len > 4 ? len - 4 : len, s_ifp);
-        }
-        rx_buffer_descriptor[s_rt1020_rxno].control |= BIT(15); // Inform DMA RX is empty
-        if (++s_rt1020_rxno >= ENET_RXBD_NUM) s_rt1020_rxno = 0;
-      }
-    }
-  }
-  ENET->EIMR = EIMR_RX_ERR; // Enable interrupts
-}
-
-// Up/down status
-static bool mg_tcpip_driver_imxrt1020_up(struct mg_tcpip_if *ifp) {
-  uint32_t bsr = imxrt1020_eth_read_phy(IMXRT1020_PHY_ADDR, IMXRT1020_PHY_BSR);
-  (void) ifp;
-  return bsr & BIT(2) ? 1 : 0;
-}
-
-// API
-struct mg_tcpip_driver mg_tcpip_driver_imxrt1020 = {
-  mg_tcpip_driver_imxrt1020_init, mg_tcpip_driver_imxrt1020_tx, NULL,
-  mg_tcpip_driver_imxrt1020_up};
-
-#endif
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/drivers/same54.c"
-#endif
-
-
-#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_SAME54) && \
-    MG_ENABLE_DRIVER_SAME54
-
-#include <sam.h>
-
-#undef BIT
-#define BIT(x) ((uint32_t) 1 << (x))
-#define ETH_PKT_SIZE 1536  // Max frame size
-#define ETH_DESC_CNT 4     // Descriptors count
-#define ETH_DS 2           // Descriptor size (words)
-
-static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
-static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE];
-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_txno;                           // Current TX descriptor
-static uint8_t s_rxno;                           // Current RX descriptor
-
-static struct mg_tcpip_if *s_ifp;  // MIP interface
-enum { PHY_ADDR = 0, PHY_BCR = 0, PHY_BSR = 1 };
-
-#define PHY_BCR_DUPLEX_MODE_Msk BIT(8)
-#define PHY_BCR_SPEED_Msk BIT(13)
-#define PHY_BSR_LINK_STATUS_Msk BIT(2)
-
-static uint16_t eth_read_phy(uint8_t addr, uint8_t reg) {
-  GMAC_REGS->GMAC_MAN = GMAC_MAN_CLTTO_Msk |
-                        GMAC_MAN_OP(2) |  // Setting the read operation
-                        GMAC_MAN_WTN(2) | GMAC_MAN_PHYA(addr) |  // PHY address
-                        GMAC_MAN_REGA(reg);  // Setting the register
-  while (!(GMAC_REGS->GMAC_NSR & GMAC_NSR_IDLE_Msk)) (void) 0;
-  return GMAC_REGS->GMAC_MAN & GMAC_MAN_DATA_Msk;  // Getting the read value
-}
-
-#if 0
-static void eth_write_phy(uint8_t addr, uint8_t reg, uint16_t val) {
-  GMAC_REGS->GMAC_MAN = GMAC_MAN_CLTTO_Msk | GMAC_MAN_OP(1) |   // Setting the write operation
-                        GMAC_MAN_WTN(2) | GMAC_MAN_PHYA(addr) | // PHY address
-                        GMAC_MAN_REGA(reg) | GMAC_MAN_DATA(val);  // Setting the register
-  while (!(GMAC_REGS->GMAC_NSR & GMAC_NSR_IDLE_Msk)); // Waiting until the write op is complete
-}
-#endif
-
-int get_clock_rate(struct mg_tcpip_driver_same54_data *d) {
-  if (d && d->mdc_cr >= 0 && d->mdc_cr <= 5) {
-    return d->mdc_cr;
-  } else {
-    // get MCLK from GCLK_GENERATOR 0
-    uint32_t div = 512;
-    uint32_t mclk;
-    if (!(GCLK_REGS->GCLK_GENCTRL[0] & GCLK_GENCTRL_DIVSEL_Msk)) {
-      div = ((GCLK_REGS->GCLK_GENCTRL[0] & 0x00FF0000) >> 16);
-      if (div == 0) div = 1;
-    }
-    switch (GCLK_REGS->GCLK_GENCTRL[0] & GCLK_GENCTRL_SRC_Msk) {
-      case GCLK_GENCTRL_SRC_XOSC0_Val:
-        mclk = 32000000UL; /* 32MHz */
-        break;
-      case GCLK_GENCTRL_SRC_XOSC1_Val:
-        mclk = 32000000UL; /* 32MHz */
-        break;
-      case GCLK_GENCTRL_SRC_OSCULP32K_Val:
-        mclk = 32000UL;
-        break;
-      case GCLK_GENCTRL_SRC_XOSC32K_Val:
-        mclk = 32000UL;
-        break;
-      case GCLK_GENCTRL_SRC_DFLL_Val:
-        mclk = 48000000UL; /* 48MHz */
-        break;
-      case GCLK_GENCTRL_SRC_DPLL0_Val:
-        mclk = 200000000UL; /* 200MHz */
-        break;
-      case GCLK_GENCTRL_SRC_DPLL1_Val:
-        mclk = 200000000UL; /* 200MHz */
-        break;
-      default:
-        mclk = 200000000UL; /* 200MHz */
-    }
-
-    mclk /= div;
-    uint8_t crs[] = {0, 1, 2, 3, 4, 5};          // GMAC->NCFGR::CLK values
-    uint8_t dividers[] = {8, 16, 32, 48, 64, 128};  // Respective CLK dividers
-    for (int i = 0; i < 6; i++) {
-      if (mclk / dividers[i] <= 2375000UL /* 2.5MHz - 5% */) {
-        return crs[i];
-      }
-    }
-
-    return 5;
-  }
-}
-
-static bool mg_tcpip_driver_same54_init(struct mg_tcpip_if *ifp) {
-  struct mg_tcpip_driver_same54_data *d =
-      (struct mg_tcpip_driver_same54_data *) ifp->driver_data;
-  s_ifp = ifp;
-
-  MCLK_REGS->MCLK_APBCMASK |= MCLK_APBCMASK_GMAC_Msk;
-  MCLK_REGS->MCLK_AHBMASK |= MCLK_AHBMASK_GMAC_Msk;
-  GMAC_REGS->GMAC_NCFGR = GMAC_NCFGR_CLK(get_clock_rate(d));  // Set MDC divider
-  GMAC_REGS->GMAC_NCR = 0;                            // Disable RX & TX
-  GMAC_REGS->GMAC_NCR |= GMAC_NCR_MPE_Msk;            // Enable MDC & MDIO
-
-  for (int i = 0; i < ETH_DESC_CNT; i++) {   // Init TX descriptors
-    s_txdesc[i][0] = (uint32_t) s_txbuf[i];  // Point to data buffer
-    s_txdesc[i][1] = BIT(31);                // OWN bit
-  }
-  s_txdesc[ETH_DESC_CNT - 1][1] |= BIT(30);  // Last tx descriptor - wrap
-
-  GMAC_REGS->GMAC_DCFGR = GMAC_DCFGR_DRBS(0x18);  // DMA recv buf 1536
-  for (int i = 0; i < ETH_DESC_CNT; i++) {        // Init RX descriptors
-    s_rxdesc[i][0] = (uint32_t) s_rxbuf[i];       // Address of the data buffer
-    s_rxdesc[i][1] = 0;                           // Clear status
-  }
-  s_rxdesc[ETH_DESC_CNT - 1][0] |= BIT(1);  // Last rx descriptor - wrap
-
-  GMAC_REGS->GMAC_TBQB = (uint32_t) s_txdesc;  // about the descriptor addresses
-  GMAC_REGS->GMAC_RBQB = (uint32_t) s_rxdesc;  // Let the controller know
-
-  GMAC_REGS->SA[0].GMAC_SAB =
-      MG_U32(ifp->mac[3], ifp->mac[2], ifp->mac[1], ifp->mac[0]);
-  GMAC_REGS->SA[0].GMAC_SAT = MG_U32(0, 0, ifp->mac[5], ifp->mac[4]);
-
-  GMAC_REGS->GMAC_UR &= ~GMAC_UR_MII_Msk;  // Disable MII, use RMII
-  GMAC_REGS->GMAC_NCFGR |= GMAC_NCFGR_MAXFS_Msk | GMAC_NCFGR_MTIHEN_Msk |
-                           GMAC_NCFGR_EFRHD_Msk | GMAC_NCFGR_CAF_Msk;
-  GMAC_REGS->GMAC_TSR = GMAC_TSR_HRESP_Msk | GMAC_TSR_UND_Msk |
-                        GMAC_TSR_TXCOMP_Msk | GMAC_TSR_TFC_Msk |
-                        GMAC_TSR_TXGO_Msk | GMAC_TSR_RLE_Msk |
-                        GMAC_TSR_COL_Msk | GMAC_TSR_UBR_Msk;
-  GMAC_REGS->GMAC_RSR = GMAC_RSR_HNO_Msk | GMAC_RSR_RXOVR_Msk |
-                        GMAC_RSR_REC_Msk | GMAC_RSR_BNA_Msk;
-  GMAC_REGS->GMAC_IDR = ~0U;  // Disable interrupts, then enable required
-  GMAC_REGS->GMAC_IER = GMAC_IER_HRESP_Msk | GMAC_IER_ROVR_Msk |
-                        GMAC_IER_TCOMP_Msk | GMAC_IER_TFC_Msk |
-                        GMAC_IER_RLEX_Msk | GMAC_IER_TUR_Msk |
-                        GMAC_IER_RXUBR_Msk | GMAC_IER_RCOMP_Msk;
-  GMAC_REGS->GMAC_NCR |= GMAC_NCR_TXEN_Msk | GMAC_NCR_RXEN_Msk;
-  NVIC_EnableIRQ(GMAC_IRQn);
-
-  return true;
-}
-
-static size_t mg_tcpip_driver_same54_tx(const void *buf, size_t len,
-                                        struct mg_tcpip_if *ifp) {
-  if (len > sizeof(s_txbuf[s_txno])) {
-    MG_ERROR(("Frame too big, %ld", (long) len));
-    len = 0;  // Frame is too big
-  } else if ((s_txdesc[s_txno][1] & BIT(31)) == 0) {
-    ifp->nerr++;
-    MG_ERROR(("No free descriptors"));
-    len = 0;  // All descriptors are busy, fail
-  } else {
-    uint32_t status = len | BIT(15);  // Frame length, last chunk
-    if (s_txno == ETH_DESC_CNT - 1) status |= BIT(30);  // wrap
-    memcpy(s_txbuf[s_txno], buf, len);                  // Copy data
-    s_txdesc[s_txno][1] = status;
-    if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
-  }
-  __DSB();  // Ensure descriptors have been written
-  GMAC_REGS->GMAC_NCR |= GMAC_NCR_TSTART_Msk;  // Enable transmission
-  return len;
-}
-
-static bool mg_tcpip_driver_same54_up(struct mg_tcpip_if *ifp) {
-  uint16_t bsr = eth_read_phy(PHY_ADDR, PHY_BSR);
-  bool up = bsr & PHY_BSR_LINK_STATUS_Msk ? 1 : 0;
-
-  // If PHY is ready, update NCFGR accordingly
-  if (ifp->state == MG_TCPIP_STATE_DOWN && up) {
-    uint16_t bcr = eth_read_phy(PHY_ADDR, PHY_BCR);
-    bool fd = bcr & PHY_BCR_DUPLEX_MODE_Msk ? 1 : 0;
-    bool spd = bcr & PHY_BCR_SPEED_Msk ? 1 : 0;
-    GMAC_REGS->GMAC_NCFGR |= GMAC_NCFGR_SPD(spd) | GMAC_NCFGR_FD(fd);
-  }
-
-  return up;
-}
-
-void GMAC_Handler(void);
-void GMAC_Handler(void) {
-  uint32_t isr = GMAC_REGS->GMAC_ISR;
-  uint32_t rsr = GMAC_REGS->GMAC_RSR;
-  uint32_t tsr = GMAC_REGS->GMAC_TSR;
-  if (isr & GMAC_ISR_RCOMP_Msk) {
-    if (rsr & GMAC_ISR_RCOMP_Msk) {
-      for (uint8_t i = 0; i < ETH_DESC_CNT; i++) {
-        if ((s_rxdesc[s_rxno][0] & BIT(0)) == 0) break;
-        size_t len = s_rxdesc[s_rxno][1] & (BIT(13) - 1);
-        mg_tcpip_qwrite(s_rxbuf[s_rxno], len, s_ifp);
-        s_rxdesc[s_rxno][0] &= ~BIT(0);  // Disown
-        if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
-      }
-    }
-  }
-
-  if ((tsr & (GMAC_TSR_HRESP_Msk | GMAC_TSR_UND_Msk | GMAC_TSR_TXCOMP_Msk |
-              GMAC_TSR_TFC_Msk | GMAC_TSR_TXGO_Msk | GMAC_TSR_RLE_Msk |
-              GMAC_TSR_COL_Msk | GMAC_TSR_UBR_Msk)) != 0) {
-    // MG_INFO((" --> %#x %#x", s_txdesc[s_txno][1], tsr));
-    if (!(s_txdesc[s_txno][1] & BIT(31))) s_txdesc[s_txno][1] |= BIT(31);
-  }
-
-  GMAC_REGS->GMAC_RSR = rsr;
-  GMAC_REGS->GMAC_TSR = tsr;
-}
-
-struct mg_tcpip_driver mg_tcpip_driver_same54 = {
-    mg_tcpip_driver_same54_init, mg_tcpip_driver_same54_tx, NULL,
-    mg_tcpip_driver_same54_up};
-#endif
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/drivers/stm32.c"
-#endif
-
-
-#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32) && MG_ENABLE_DRIVER_STM32
-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;
-};
-#undef ETH
-#define ETH ((struct stm32_eth *) (uintptr_t) 0x40028000)
-
-#undef DSB
-#if defined(__CC_ARM)
-#define DSB() __dsb(0xF)
-#elif defined(__ARMCC_VERSION)
-#define DSB() __builtin_arm_dsb(0xF)
-#elif defined(__GNUC__) && defined(__arm__) && defined(__thumb__)
-#define DSB() asm("DSB 0xF")
-#elif defined(__ICCARM__)
-#define DSB() __iar_builtin_DSB()
-#else
-#define DSB()
-#endif
-
-#undef BIT
-#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 uint8_t s_txno;                               // Current TX descriptor
-static uint8_t s_rxno;                               // Current RX descriptor
-
-static struct mg_tcpip_if *s_ifp;  // MIP interface
-enum { PHY_ADDR = 0, PHY_BCR = 0, PHY_BSR = 1, PHY_CSCR = 31 };
-
-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)) (void) 0;
-  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)) (void) 0;
-}
-
-static uint32_t get_hclk(void) {
-  struct rcc {
-    volatile uint32_t CR, PLLCFGR, CFGR;
-  } *rcc = (struct rcc *) 0x40023800;
-  uint32_t clk = 0, hsi = 16000000 /* 16 MHz */, hse = 8000000 /* 8MHz */;
-
-  if (rcc->CFGR & (1 << 2)) {
-    clk = hse;
-  } else if (rcc->CFGR & (1 << 3)) {
-    uint32_t vco, m, n, p;
-    m = (rcc->PLLCFGR & (0x3f << 0)) >> 0;
-    n = (rcc->PLLCFGR & (0x1ff << 6)) >> 6;
-    p = (((rcc->PLLCFGR & (3 << 16)) >> 16) + 1) * 2;
-    clk = (rcc->PLLCFGR & (1 << 22)) ? hse : hsi;
-    vco = (uint32_t) ((uint64_t) clk * n / m);
-    clk = vco / p;
-  } else {
-    clk = hsi;
-  }
-  uint32_t hpre = (rcc->CFGR & (15 << 4)) >> 4;
-  if (hpre < 8) return clk;
-
-  uint8_t ahbptab[8] = {1, 2, 3, 4, 6, 7, 8, 9};  // log2(div)
-  return ((uint32_t) clk) >> ahbptab[hpre - 8];
-}
-
-//  Guess CR from HCLK. MDC clock is generated from HCLK (AHB); as per 802.3,
-//  it must not exceed 2.5MHz As the AHB clock can be (and usually is) derived
-//  from the HSI (internal RC), and it can go above specs, the datasheets
-//  specify a range of frequencies and activate one of a series of dividers to
-//  keep the MDC clock safely below 2.5MHz. We guess a divider setting based on
-//  HCLK with a +5% drift. If the user uses a different clock from our
-//  defaults, needs to set the macros on top Valid for STM32F74xxx/75xxx
-//  (38.8.1) and STM32F42xxx/43xxx (33.8.1) (both 4.5% worst case drift)
-static int guess_mdc_cr(void) {
-  uint8_t crs[] = {2, 3, 0, 1, 4, 5};          // ETH->MACMIIAR::CR values
-  uint8_t div[] = {16, 26, 42, 62, 102, 124};  // Respective HCLK dividers
-  uint32_t hclk = get_hclk();                  // Guess system HCLK
-  int result = -1;                             // Invalid CR value
-  if (hclk < 25000000) {
-    MG_ERROR(("HCLK too low"));
-  } else {
-    for (int i = 0; i < 6; i++) {
-      if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
-        result = crs[i];
-        break;
-      }
-    }
-    if (result < 0) MG_ERROR(("HCLK too high"));
-  }
-  MG_DEBUG(("HCLK: %u, CR: %d", hclk, result));
-  return result;
-}
-
-static bool mg_tcpip_driver_stm32_init(struct mg_tcpip_if *ifp) {
-  struct mg_tcpip_driver_stm32_data *d =
-      (struct mg_tcpip_driver_stm32_data *) ifp->driver_data;
-  s_ifp = ifp;
-
-  // 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) (void) 0;  // Wait until done
-
-  // Set MDC clock divider. If user told us the value, use it. Otherwise, guess
-  int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr;
-  ETH->MACMIIAR = ((uint32_t) cr & 7) << 2;
-
-  // 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->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
-
-  // MAC address filtering
-  ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
-  ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) |
-                 ((uint32_t) ifp->mac[2] << 16) |
-                 ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
-  return true;
-}
-
-static size_t mg_tcpip_driver_stm32_tx(const void *buf, size_t len,
-                                       struct mg_tcpip_if *ifp) {
-  if (len > sizeof(s_txbuf[s_txno])) {
-    MG_ERROR(("Frame too big, %ld", (long) len));
-    len = 0;  // Frame is too big
-  } else if ((s_txdesc[s_txno][0] & BIT(31))) {
-    ifp->nerr++;
-    MG_ERROR(("No free descriptors"));
-    // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long) ETH->DMASR);
-    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);  // 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;
-  }
-  DSB();                         // ensure descriptors have been written
-  ETH->DMASR = BIT(2) | BIT(5);  // Clear any prior TBUS/TUS
-  ETH->DMATPDR = 0;              // and resume
-  return len;
-}
-
-static bool mg_tcpip_driver_stm32_up(struct mg_tcpip_if *ifp) {
-  uint32_t bsr = eth_read_phy(PHY_ADDR, PHY_BSR);
-  bool up = bsr & BIT(2) ? 1 : 0;
-  if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) {  // link state just went up
-    uint32_t scsr = eth_read_phy(PHY_ADDR, PHY_CSCR);
-    uint32_t maccr = ETH->MACCR | BIT(14) | BIT(11);  // 100M, Full-duplex
-    if ((scsr & BIT(3)) == 0) maccr &= ~BIT(14);      // 10M
-    if ((scsr & BIT(4)) == 0) maccr &= ~BIT(11);      // Half-duplex
-    ETH->MACCR = maccr;  // IRQ handler does not fiddle with this register
-    MG_DEBUG(("Link is %uM %s-duplex", maccr & BIT(14) ? 100 : 10,
-              maccr & BIT(11) ? "full" : "half"));
-  }
-  return up;
-}
-
-void ETH_IRQHandler(void);
-void ETH_IRQHandler(void) {
-  if (ETH->DMASR & BIT(6)) {             // Frame received, loop
-    ETH->DMASR = BIT(16) | BIT(6);       // Clear flag
-    for (uint32_t i = 0; i < 10; i++) {  // read as they arrive but not forever
-      if (s_rxdesc[s_rxno][0] & BIT(31)) break;  // exit when done
-      if (((s_rxdesc[s_rxno][0] & (BIT(8) | BIT(9))) == (BIT(8) | BIT(9))) &&
-          !(s_rxdesc[s_rxno][0] & BIT(15))) {  // skip partial/errored frames
-        uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (BIT(14) - 1));
-        //  printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
-        //  ETH->DMASR);
-        mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
-      }
-      s_rxdesc[s_rxno][0] = BIT(31);
-      if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
-    }
-  }
-  ETH->DMASR = BIT(7);  // Clear possible RBUS while processing
-  ETH->DMARPDR = 0;     // and resume RX
-}
-
-struct mg_tcpip_driver mg_tcpip_driver_stm32 = {mg_tcpip_driver_stm32_init,
-                                                mg_tcpip_driver_stm32_tx, NULL,
-                                                mg_tcpip_driver_stm32_up};
-#endif
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/drivers/stm32h.c"
-#endif
-
-
-#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_STM32H) && \
-    MG_ENABLE_DRIVER_STM32H
-struct stm32h_eth {
-  volatile uint32_t MACCR, MACECR, MACPFR, MACWTR, MACHT0R, MACHT1R,
-      RESERVED1[14], MACVTR, RESERVED2, MACVHTR, RESERVED3, MACVIR, MACIVIR,
-      RESERVED4[2], MACTFCR, RESERVED5[7], MACRFCR, RESERVED6[7], MACISR,
-      MACIER, MACRXTXSR, RESERVED7, MACPCSR, MACRWKPFR, RESERVED8[2], MACLCSR,
-      MACLTCR, MACLETR, MAC1USTCR, RESERVED9[12], MACVR, MACDR, RESERVED10,
-      MACHWF0R, MACHWF1R, MACHWF2R, RESERVED11[54], MACMDIOAR, MACMDIODR,
-      RESERVED12[2], MACARPAR, RESERVED13[59], MACA0HR, MACA0LR, MACA1HR,
-      MACA1LR, MACA2HR, MACA2LR, MACA3HR, MACA3LR, RESERVED14[248], MMCCR,
-      MMCRIR, MMCTIR, MMCRIMR, MMCTIMR, RESERVED15[14], MMCTSCGPR, MMCTMCGPR,
-      RESERVED16[5], MMCTPCGR, RESERVED17[10], MMCRCRCEPR, MMCRAEPR,
-      RESERVED18[10], MMCRUPGR, RESERVED19[9], MMCTLPIMSTR, MMCTLPITCR,
-      MMCRLPIMSTR, MMCRLPITCR, RESERVED20[65], MACL3L4C0R, MACL4A0R,
-      RESERVED21[2], MACL3A0R0R, MACL3A1R0R, MACL3A2R0R, MACL3A3R0R,
-      RESERVED22[4], MACL3L4C1R, MACL4A1R, RESERVED23[2], MACL3A0R1R,
-      MACL3A1R1R, MACL3A2R1R, MACL3A3R1R, RESERVED24[108], MACTSCR, MACSSIR,
-      MACSTSR, MACSTNR, MACSTSUR, MACSTNUR, MACTSAR, RESERVED25, MACTSSR,
-      RESERVED26[3], MACTTSSNR, MACTTSSSR, RESERVED27[2], MACACR, RESERVED28,
-      MACATSNR, MACATSSR, MACTSIACR, MACTSEACR, MACTSICNR, MACTSECNR,
-      RESERVED29[4], MACPPSCR, RESERVED30[3], MACPPSTTSR, MACPPSTTNR, MACPPSIR,
-      MACPPSWR, RESERVED31[12], MACPOCR, MACSPI0R, MACSPI1R, MACSPI2R, MACLMIR,
-      RESERVED32[11], MTLOMR, RESERVED33[7], MTLISR, RESERVED34[55], MTLTQOMR,
-      MTLTQUR, MTLTQDR, RESERVED35[8], MTLQICSR, MTLRQOMR, MTLRQMPOCR, MTLRQDR,
-      RESERVED36[177], DMAMR, DMASBMR, DMAISR, DMADSR, RESERVED37[60], DMACCR,
-      DMACTCR, DMACRCR, RESERVED38[2], DMACTDLAR, RESERVED39, DMACRDLAR,
-      DMACTDTPR, RESERVED40, DMACRDTPR, DMACTDRLR, DMACRDRLR, DMACIER,
-      DMACRIWTR, DMACSFCSR, RESERVED41, DMACCATDR, RESERVED42, DMACCARDR,
-      RESERVED43, DMACCATBR, RESERVED44, DMACCARBR, DMACSR, RESERVED45[2],
-      DMACMFCR;
-};
-#undef ETH
-#define ETH \
-  ((struct stm32h_eth *) (uintptr_t) (0x40000000UL + 0x00020000UL + 0x8000UL))
-
-#undef BIT
-#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 volatile uint32_t s_rxdesc[ETH_DESC_CNT][ETH_DS];  // RX descriptors
-static volatile 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 struct mg_tcpip_if *s_ifp;                         // MIP interface
-enum {
-  PHY_ADDR = 0,
-  PHY_BCR = 0,
-  PHY_BSR = 1,
-  PHY_CSCR = 31
-};  // PHY constants
-
-static uint32_t eth_read_phy(uint8_t addr, uint8_t reg) {
-  ETH->MACMDIOAR &= (0xF << 8);
-  ETH->MACMDIOAR |= ((uint32_t) addr << 21) | ((uint32_t) reg << 16) | 3 << 2;
-  ETH->MACMDIOAR |= BIT(0);
-  while (ETH->MACMDIOAR & BIT(0)) (void) 0;
-  return ETH->MACMDIODR;
-}
-
-static void eth_write_phy(uint8_t addr, uint8_t reg, uint32_t val) {
-  ETH->MACMDIODR = val;
-  ETH->MACMDIOAR &= (0xF << 8);
-  ETH->MACMDIOAR |= ((uint32_t) addr << 21) | ((uint32_t) reg << 16) | 1 << 2;
-  ETH->MACMDIOAR |= BIT(0);
-  while (ETH->MACMDIOAR & BIT(0)) (void) 0;
-}
-
-static uint32_t get_hclk(void) {
-  struct rcc {
-    volatile uint32_t CR, HSICFGR, CRRCR, CSICFGR, CFGR, RESERVED1, D1CFGR,
-        D2CFGR, D3CFGR, RESERVED2, PLLCKSELR, PLLCFGR, PLL1DIVR, PLL1FRACR,
-        PLL2DIVR, PLL2FRACR, PLL3DIVR, PLL3FRACR, RESERVED3, D1CCIPR, D2CCIP1R,
-        D2CCIP2R, D3CCIPR, RESERVED4, CIER, CIFR, CICR, RESERVED5, BDCR, CSR,
-        RESERVED6, AHB3RSTR, AHB1RSTR, AHB2RSTR, AHB4RSTR, APB3RSTR, APB1LRSTR,
-        APB1HRSTR, APB2RSTR, APB4RSTR, GCR, RESERVED8, D3AMR, RESERVED11[9],
-        RSR, AHB3ENR, AHB1ENR, AHB2ENR, AHB4ENR, APB3ENR, APB1LENR, APB1HENR,
-        APB2ENR, APB4ENR, RESERVED12, AHB3LPENR, AHB1LPENR, AHB2LPENR,
-        AHB4LPENR, APB3LPENR, APB1LLPENR, APB1HLPENR, APB2LPENR, APB4LPENR,
-        RESERVED13[4];
-  } *rcc = ((struct rcc *) (0x40000000 + 0x18020000 + 0x4400));
-  uint32_t clk = 0, hsi = 64000000 /* 64 MHz */, hse = 8000000 /* 8MHz */,
-           csi = 4000000 /* 4MHz */;
-  unsigned int sel = (rcc->CFGR & (7 << 3)) >> 3;
-
-  if (sel == 1) {
-    clk = csi;
-  } else if (sel == 2) {
-    clk = hse;
-  } else if (sel == 3) {
-    uint32_t vco, m, n, p;
-    unsigned int src = (rcc->PLLCKSELR & (3 << 0)) >> 0;
-    m = ((rcc->PLLCKSELR & (0x3F << 4)) >> 4);
-    n = ((rcc->PLL1DIVR & (0x1FF << 0)) >> 0) + 1 +
-        ((rcc->PLLCFGR & BIT(0)) ? 1 : 0);  // round-up in fractional mode
-    p = ((rcc->PLL1DIVR & (0x7F << 9)) >> 9) + 1;
-    if (src == 1) {
-      clk = csi;
-    } else if (src == 2) {
-      clk = hse;
-    } else {
-      clk = hsi;
-      clk >>= ((rcc->CR & 3) >> 3);
-    }
-    vco = (uint32_t) ((uint64_t) clk * n / m);
-    clk = vco / p;
-  } else {
-    clk = hsi;
-    clk >>= ((rcc->CR & 3) >> 3);
-  }
-  const uint8_t cptab[12] = {1, 2, 3, 4, 6, 7, 8, 9};  // log2(div)
-  uint32_t d1cpre = (rcc->D1CFGR & (0x0F << 8)) >> 8;
-  if (d1cpre >= 8) clk >>= cptab[d1cpre - 8];
-  MG_DEBUG(("D1 CLK: %u", clk));
-  uint32_t hpre = (rcc->D1CFGR & (0x0F << 0)) >> 0;
-  if (hpre < 8) return clk;
-  return ((uint32_t) clk) >> cptab[hpre - 8];
-}
-
-//  Guess CR from AHB1 clock. MDC clock is generated from the ETH peripheral
-//  clock (AHB1); as per 802.3, it must not exceed 2. As the AHB clock can
-//  be derived from HSI or CSI (internal RC) clocks, and those can go above
-//  specs, the datasheets specify a range of frequencies and activate one of a
-//  series of dividers to keep the MDC clock safely below 2.5MHz. We guess a
-//  divider setting based on HCLK with some drift. If the user uses a different
-//  clock from our defaults, needs to set the macros on top. Valid for
-//  STM32H74xxx/75xxx (58.11.4)(4.5% worst case drift)(CSI clock has a 7.5 %
-//  worst case drift @ max temp)
-static int guess_mdc_cr(void) {
-  const uint8_t crs[] = {2, 3, 0, 1, 4, 5};  // ETH->MACMDIOAR::CR values
-  const uint8_t div[] = {16, 26, 42, 62, 102, 124};  // Respective HCLK dividers
-  uint32_t hclk = get_hclk();                        // Guess system HCLK
-  int result = -1;                                   // Invalid CR value
-  for (int i = 0; i < 6; i++) {
-    if (hclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
-      result = crs[i];
-      break;
-    }
-  }
-  if (result < 0) MG_ERROR(("HCLK too high"));
-  MG_DEBUG(("HCLK: %u, CR: %d", hclk, result));
-  return result;
-}
-
-static bool mg_tcpip_driver_stm32h_init(struct mg_tcpip_if *ifp) {
-  struct mg_tcpip_driver_stm32h_data *d =
-      (struct mg_tcpip_driver_stm32h_data *) ifp->driver_data;
-  s_ifp = ifp;
-
-  // Init RX descriptors
-  for (int i = 0; i < ETH_DESC_CNT; i++) {
-    s_rxdesc[i][0] = (uint32_t) (uintptr_t) s_rxbuf[i];  // Point to data buffer
-    s_rxdesc[i][3] = BIT(31) | BIT(30) | BIT(24);        // OWN, IOC, BUF1V
-  }
-
-  // Init TX descriptors
-  for (int i = 0; i < ETH_DESC_CNT; i++) {
-    s_txdesc[i][0] = (uint32_t) (uintptr_t) s_txbuf[i];  // Buf pointer
-  }
-
-  ETH->DMAMR |= BIT(0);                         // Software reset
-  while ((ETH->DMAMR & BIT(0)) != 0) (void) 0;  // Wait until done
-
-  // Set MDC clock divider. If user told us the value, use it. Otherwise, guess
-  int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr;
-  ETH->MACMDIOAR = ((uint32_t) cr & 0xF) << 8;
-
-  // NOTE(scaprile): We do not use timing facilities so the DMA engine does not
-  // re-write buffer address
-  ETH->DMAMR = 0 << 16;     // use interrupt mode 0 (58.8.1) (reset value)
-  ETH->DMASBMR |= BIT(12);  // AAL NOTE(scaprile): is this actually needed
-  ETH->MACIER = 0;        // Do not enable additional irq sources (reset value)
-  ETH->MACTFCR = BIT(7);  // Disable zero-quanta pause
-  // ETH->MACPFR = 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->DMACRDLAR =
-      (uint32_t) (uintptr_t) s_rxdesc;  // RX descriptors start address
-  ETH->DMACRDRLR = ETH_DESC_CNT - 1;    // ring length
-  ETH->DMACRDTPR =
-      (uint32_t) (uintptr_t) &s_rxdesc[ETH_DESC_CNT -
-                                       1];  // last valid descriptor address
-  ETH->DMACTDLAR =
-      (uint32_t) (uintptr_t) s_txdesc;  // TX descriptors start address
-  ETH->DMACTDRLR = ETH_DESC_CNT - 1;    // ring length
-  ETH->DMACTDTPR =
-      (uint32_t) (uintptr_t) s_txdesc;  // first available descriptor address
-  ETH->DMACCR = 0;  // DSL = 0 (contiguous descriptor table) (reset value)
-  ETH->DMACIER = BIT(6) | BIT(15);  // RIE, NIE
-  ETH->MACCR = BIT(0) | BIT(1) | BIT(13) | BIT(14) |
-               BIT(15);     // RE, TE, Duplex, Fast, Reserved
-  ETH->MTLTQOMR |= BIT(1);  // TSF
-  ETH->MTLRQOMR |= BIT(5);  // RSF
-  ETH->DMACTCR |= BIT(0);   // ST
-  ETH->DMACRCR |= BIT(0);   // SR
-
-  // MAC address filtering
-  ETH->MACA0HR = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
-  ETH->MACA0LR = (uint32_t) (ifp->mac[3] << 24) |
-                 ((uint32_t) ifp->mac[2] << 16) |
-                 ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
-  return true;
-}
-
-static uint32_t s_txno;
-static size_t mg_tcpip_driver_stm32h_tx(const void *buf, size_t len,
-                                        struct mg_tcpip_if *ifp) {
-  if (len > sizeof(s_txbuf[s_txno])) {
-    MG_ERROR(("Frame too big, %ld", (long) len));
-    len = 0;  // Frame is too big
-  } else if ((s_txdesc[s_txno][3] & BIT(31))) {
-    MG_ERROR(("No free descriptors: %u %08X %08X %08X", s_txno,
-              s_txdesc[s_txno][3], ETH->DMACSR, ETH->DMACTCR));
-    for (int i = 0; i < ETH_DESC_CNT; i++) MG_ERROR(("%08X", s_txdesc[i][3]));
-    len = 0;  // All descriptors are busy, fail
-  } else {
-    memcpy(s_txbuf[s_txno], buf, len);        // Copy data
-    s_txdesc[s_txno][2] = (uint32_t) len;     // Set data len
-    s_txdesc[s_txno][3] = BIT(28) | BIT(29);  // FD, LD
-    s_txdesc[s_txno][3] |= BIT(31);           // Set OWN bit - let DMA take over
-    if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
-  }
-  ETH->DMACSR |= BIT(2) | BIT(1);  // Clear any prior TBU, TPS
-  ETH->DMACTDTPR = (uint32_t) (uintptr_t) &s_txdesc[s_txno];  // and resume
-  return len;
-  (void) ifp;
-}
-
-static bool mg_tcpip_driver_stm32h_up(struct mg_tcpip_if *ifp) {
-  uint32_t bsr = eth_read_phy(PHY_ADDR, PHY_BSR);
-  bool up = bsr & BIT(2) ? 1 : 0;
-  if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) {  // link state just went up
-    uint32_t scsr = eth_read_phy(PHY_ADDR, PHY_CSCR);
-    uint32_t maccr = ETH->MACCR | BIT(14) | BIT(13);  // 100M, Full-duplex
-    if ((scsr & BIT(3)) == 0) maccr &= ~BIT(14);      // 10M
-    if ((scsr & BIT(4)) == 0) maccr &= ~BIT(13);      // Half-duplex
-    ETH->MACCR = maccr;  // IRQ handler does not fiddle with this register
-    MG_DEBUG(("Link is %uM %s-duplex", maccr & BIT(14) ? 100 : 10,
-              maccr & BIT(13) ? "full" : "half"));
-  }
-  return up;
-}
-
-void ETH_IRQHandler(void);
-static uint32_t s_rxno;
-void ETH_IRQHandler(void) {
-  if (ETH->DMACSR & BIT(6)) {            // Frame received, loop
-    ETH->DMACSR = BIT(15) | BIT(6);      // Clear flag
-    for (uint32_t i = 0; i < 10; i++) {  // read as they arrive but not forever
-      if (s_rxdesc[s_rxno][3] & BIT(31)) break;  // exit when done
-      if (((s_rxdesc[s_rxno][3] & (BIT(28) | BIT(29))) ==
-           (BIT(28) | BIT(29))) &&
-          !(s_rxdesc[s_rxno][3] & BIT(15))) {  // skip partial/errored frames
-        uint32_t len = s_rxdesc[s_rxno][3] & (BIT(15) - 1);
-        // MG_DEBUG(("%lx %lu %lx %08lx", s_rxno, len, s_rxdesc[s_rxno][3],
-        // ETH->DMACSR));
-        mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
-      }
-      s_rxdesc[s_rxno][3] = BIT(31) | BIT(30) | BIT(24);  // OWN, IOC, BUF1V
-      if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
-    }
-  }
-  ETH->DMACSR = BIT(7) | BIT(8);  // Clear possible RBU RPS while processing
-  ETH->DMACRDTPR =
-      (uint32_t) (uintptr_t) &s_rxdesc[ETH_DESC_CNT - 1];  // and resume RX
-}
-
-struct mg_tcpip_driver mg_tcpip_driver_stm32h = {
-    mg_tcpip_driver_stm32h_init, mg_tcpip_driver_stm32h_tx, NULL,
-    mg_tcpip_driver_stm32h_up};
-#endif
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/drivers/tm4c.c"
-#endif
-
-
-#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_TM4C) && MG_ENABLE_DRIVER_TM4C
-struct tm4c_emac {
-  volatile uint32_t EMACCFG, EMACFRAMEFLTR, EMACHASHTBLH, EMACHASHTBLL,
-      EMACMIIADDR, EMACMIIDATA, EMACFLOWCTL, EMACVLANTG, RESERVED0, EMACSTATUS,
-      EMACRWUFF, EMACPMTCTLSTAT, RESERVED1[2], EMACRIS, EMACIM, EMACADDR0H,
-      EMACADDR0L, EMACADDR1H, EMACADDR1L, EMACADDR2H, EMACADDR2L, EMACADDR3H,
-      EMACADDR3L, RESERVED2[31], EMACWDOGTO, RESERVED3[8], EMACMMCCTRL,
-      EMACMMCRXRIS, EMACMMCTXRIS, EMACMMCRXIM, EMACMMCTXIM, RESERVED4,
-      EMACTXCNTGB, RESERVED5[12], EMACTXCNTSCOL, EMACTXCNTMCOL, RESERVED6[4],
-      EMACTXOCTCNTG, RESERVED7[6], EMACRXCNTGB, RESERVED8[4], EMACRXCNTCRCERR,
-      EMACRXCNTALGNERR, RESERVED9[10], EMACRXCNTGUNI, RESERVED10[239],
-      EMACVLNINCREP, EMACVLANHASH, RESERVED11[93], EMACTIMSTCTRL, EMACSUBSECINC,
-      EMACTIMSEC, EMACTIMNANO, EMACTIMSECU, EMACTIMNANOU, EMACTIMADD,
-      EMACTARGSEC, EMACTARGNANO, EMACHWORDSEC, EMACTIMSTAT, EMACPPSCTRL,
-      RESERVED12[12], EMACPPS0INTVL, EMACPPS0WIDTH, RESERVED13[294],
-      EMACDMABUSMOD, EMACTXPOLLD, EMACRXPOLLD, EMACRXDLADDR, EMACTXDLADDR,
-      EMACDMARIS, EMACDMAOPMODE, EMACDMAIM, EMACMFBOC, EMACRXINTWDT,
-      RESERVED14[8], EMACHOSTXDESC, EMACHOSRXDESC, EMACHOSTXBA, EMACHOSRXBA,
-      RESERVED15[218], EMACPP, EMACPC, EMACCC, RESERVED16, EMACEPHYRIS,
-      EMACEPHYIM, EMACEPHYIMSC;
-};
-#undef EMAC
-#define EMAC ((struct tm4c_emac *) (uintptr_t) 0x400EC000)
-
-#undef BIT
-#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 struct mg_tcpip_if *s_ifp;                    // MIP interface
-enum {
-  EPHY_ADDR = 0,
-  EPHYBMCR = 0,
-  EPHYBMSR = 1,
-  EPHYSTS = 16
-};  // PHY constants
-
-static inline void tm4cspin(volatile uint32_t count) {
-  while (count--) (void) 0;
-}
-
-static uint32_t emac_read_phy(uint8_t addr, uint8_t reg) {
-  EMAC->EMACMIIADDR &= (0xf << 2);
-  EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6);
-  EMAC->EMACMIIADDR |= BIT(0);
-  while (EMAC->EMACMIIADDR & BIT(0)) tm4cspin(1);
-  return EMAC->EMACMIIDATA;
-}
-
-static void emac_write_phy(uint8_t addr, uint8_t reg, uint32_t val) {
-  EMAC->EMACMIIDATA = val;
-  EMAC->EMACMIIADDR &= (0xf << 2);
-  EMAC->EMACMIIADDR |= ((uint32_t) addr << 11) | ((uint32_t) reg << 6) | BIT(1);
-  EMAC->EMACMIIADDR |= BIT(0);
-  while (EMAC->EMACMIIADDR & BIT(0)) tm4cspin(1);
-}
-
-static uint32_t get_sysclk(void) {
-  struct sysctl {
-    volatile uint32_t DONTCARE0[44], RSCLKCFG, DONTCARE1[43], PLLFREQ0,
-        PLLFREQ1;
-  } *sysctl = (struct sysctl *) 0x400FE000;
-  uint32_t clk = 0, piosc = 16000000 /* 16 MHz */, mosc = 25000000 /* 25MHz */;
-  if (sysctl->RSCLKCFG & (1 << 28)) {  // USEPLL
-    uint32_t fin, vco, mdiv, n, q, psysdiv;
-    uint32_t pllsrc = (sysctl->RSCLKCFG & (0xf << 24)) >> 24;
-    if (pllsrc == 0) {
-      clk = piosc;
-    } else if (pllsrc == 3) {
-      clk = mosc;
-    } else {
-      MG_ERROR(("Unsupported clock source"));
-    }
-    q = (sysctl->PLLFREQ1 & (0x1f << 8)) >> 8;
-    n = (sysctl->PLLFREQ1 & (0x1f << 0)) >> 0;
-    fin = clk / ((q + 1) * (n + 1));
-    mdiv = (sysctl->PLLFREQ0 & (0x3ff << 0)) >>
-           0;  // mint + (mfrac / 1024); MFRAC not supported
-    psysdiv = (sysctl->RSCLKCFG & (0x3f << 0)) >> 0;
-    vco = (uint32_t) ((uint64_t) fin * mdiv);
-    return vco / (psysdiv + 1);
-  }
-  uint32_t oscsrc = (sysctl->RSCLKCFG & (0xf << 20)) >> 20;
-  if (oscsrc == 0) {
-    clk = piosc;
-  } else if (oscsrc == 3) {
-    clk = mosc;
-  } else {
-    MG_ERROR(("Unsupported clock source"));
-  }
-  uint32_t osysdiv = (sysctl->RSCLKCFG & (0xf << 16)) >> 16;
-  return clk / (osysdiv + 1);
-}
-
-//  Guess CR from SYSCLK. MDC clock is generated from SYSCLK (AHB); as per
-//  802.3, it must not exceed 2.5MHz (also 20.4.2.6) As the AHB clock can be
-//  derived from the PIOSC (internal RC), and it can go above  specs, the
-//  datasheets specify a range of frequencies and activate one of a series of
-//  dividers to keep the MDC clock safely below 2.5MHz. We guess a divider
-//  setting based on SYSCLK with a +5% drift. If the user uses a different clock
-//  from our defaults, needs to set the macros on top Valid for TM4C129x (20.7)
-//  (4.5% worst case drift)
-// The PHY receives the main oscillator (MOSC) (20.3.1)
-static int guess_mdc_cr(void) {
-  uint8_t crs[] = {2, 3, 0, 1};      // EMAC->MACMIIAR::CR values
-  uint8_t div[] = {16, 26, 42, 62};  // Respective HCLK dividers
-  uint32_t sysclk = get_sysclk();    // Guess system SYSCLK
-  int result = -1;                   // Invalid CR value
-  if (sysclk < 25000000) {
-    MG_ERROR(("SYSCLK too low"));
-  } else {
-    for (int i = 0; i < 4; i++) {
-      if (sysclk / div[i] <= 2375000UL /* 2.5MHz - 5% */) {
-        result = crs[i];
-        break;
-      }
-    }
-    if (result < 0) MG_ERROR(("SYSCLK too high"));
-  }
-  MG_DEBUG(("SYSCLK: %u, CR: %d", sysclk, result));
-  return result;
-}
-
-static bool mg_tcpip_driver_tm4c_init(struct mg_tcpip_if *ifp) {
-  struct mg_tcpip_driver_tm4c_data *d =
-      (struct mg_tcpip_driver_tm4c_data *) ifp->driver_data;
-  s_ifp = ifp;
-
-  // 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
-    // MG_DEBUG(("%d %p", i, s_rxdesc[i]));
-  }
-
-  // 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
-  }
-
-  EMAC->EMACDMABUSMOD |= BIT(0);                            // Software reset
-  while ((EMAC->EMACDMABUSMOD & BIT(0)) != 0) tm4cspin(1);  // Wait until done
-
-  // Set MDC clock divider. If user told us the value, use it. Otherwise, guess
-  int cr = (d == NULL || d->mdc_cr < 0) ? guess_mdc_cr() : d->mdc_cr;
-  EMAC->EMACMIIADDR = ((uint32_t) cr & 0xf) << 2;
-
-  // NOTE(cpq): we do not use extended descriptor bit 7, and do not use
-  // hardware checksum. Therefore, descriptor size is 4, not 8
-  // EMAC->EMACDMABUSMOD = BIT(13) | BIT(16) | BIT(22) | BIT(23) | BIT(25);
-  EMAC->EMACIM = BIT(3) | BIT(9);  // Mask timestamp & PMT IT
-  EMAC->EMACFLOWCTL = BIT(7);      // Disable zero-quanta pause
-  // EMAC->EMACFRAMEFLTR = BIT(31);   // Receive all
-  // EMAC->EMACPC defaults to internal PHY (EPHY) in MMI mode
-  emac_write_phy(EPHY_ADDR, EPHYBMCR, BIT(15));  // Reset internal PHY (EPHY)
-  emac_write_phy(EPHY_ADDR, EPHYBMCR, BIT(12));  // Set autonegotiation
-  EMAC->EMACRXDLADDR = (uint32_t) (uintptr_t) s_rxdesc;  // RX descriptors
-  EMAC->EMACTXDLADDR = (uint32_t) (uintptr_t) s_txdesc;  // TX descriptors
-  EMAC->EMACDMAIM = BIT(6) | BIT(16);                    // RIE, NIE
-  EMAC->EMACCFG = BIT(2) | BIT(3) | BIT(11) | BIT(14);   // RE, TE, Duplex, Fast
-  EMAC->EMACDMAOPMODE =
-      BIT(1) | BIT(13) | BIT(21) | BIT(25);  // SR, ST, TSF, RSF
-  EMAC->EMACADDR0H = ((uint32_t) ifp->mac[5] << 8U) | ifp->mac[4];
-  EMAC->EMACADDR0L = (uint32_t) (ifp->mac[3] << 24) |
-                     ((uint32_t) ifp->mac[2] << 16) |
-                     ((uint32_t) ifp->mac[1] << 8) | ifp->mac[0];
-  // NOTE(scaprile) There are 3 additional slots for filtering, disabled by
-  // default. This also applies to the STM32 driver (at least for F7)
-  return true;
-}
-
-static uint32_t s_txno;
-static size_t mg_tcpip_driver_tm4c_tx(const void *buf, size_t len,
-                                      struct mg_tcpip_if *ifp) {
-  if (len > sizeof(s_txbuf[s_txno])) {
-    MG_ERROR(("Frame too big, %ld", (long) len));
-    len = 0;  // fail
-  } else if ((s_txdesc[s_txno][0] & BIT(31))) {
-    MG_ERROR(("No descriptors available"));
-    // printf("D0 %lx SR %lx\n", (long) s_txdesc[0][0], (long)
-    // EMAC->EMACDMARIS);
-    len = 0;  // 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,IC
-    s_txdesc[s_txno][0] |= BIT(31);  // Set OWN bit - let DMA take over
-    if (++s_txno >= ETH_DESC_CNT) s_txno = 0;
-  }
-  EMAC->EMACDMARIS = BIT(2) | BIT(5);  // Clear any prior TU/UNF
-  EMAC->EMACTXPOLLD = 0;               // and resume
-  return len;
-  (void) ifp;
-}
-
-static bool mg_tcpip_driver_tm4c_up(struct mg_tcpip_if *ifp) {
-  uint32_t bmsr = emac_read_phy(EPHY_ADDR, EPHYBMSR);
-  bool up = (bmsr & BIT(2)) ? 1 : 0;
-  if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) {  // link state just went up
-    uint32_t sts = emac_read_phy(EPHY_ADDR, EPHYSTS);
-    uint32_t emaccfg = EMAC->EMACCFG | BIT(14) | BIT(11);  // 100M, Full-duplex
-    if (sts & BIT(1)) emaccfg &= ~BIT(14);                 // 10M
-    if ((sts & BIT(2)) == 0) emaccfg &= ~BIT(11);          // Half-duplex
-    EMAC->EMACCFG = emaccfg;  // IRQ handler does not fiddle with this register
-    MG_DEBUG(("Link is %uM %s-duplex", emaccfg & BIT(14) ? 100 : 10,
-              emaccfg & BIT(11) ? "full" : "half"));
-  }
-  return up;
-}
-
-void EMAC0_IRQHandler(void);
-static uint32_t s_rxno;
-void EMAC0_IRQHandler(void) {
-  if (EMAC->EMACDMARIS & BIT(6)) {        // Frame received, loop
-    EMAC->EMACDMARIS = BIT(16) | BIT(6);  // Clear flag
-    for (uint32_t i = 0; i < 10; i++) {   // read as they arrive but not forever
-      if (s_rxdesc[s_rxno][0] & BIT(31)) break;  // exit when done
-      if (((s_rxdesc[s_rxno][0] & (BIT(8) | BIT(9))) == (BIT(8) | BIT(9))) &&
-          !(s_rxdesc[s_rxno][0] & BIT(15))) {  // skip partial/errored frames
-        uint32_t len = ((s_rxdesc[s_rxno][0] >> 16) & (BIT(14) - 1));
-        //  printf("%lx %lu %lx %.8lx\n", s_rxno, len, s_rxdesc[s_rxno][0],
-        //  EMAC->EMACDMARIS);
-        mg_tcpip_qwrite(s_rxbuf[s_rxno], len > 4 ? len - 4 : len, s_ifp);
-      }
-      s_rxdesc[s_rxno][0] = BIT(31);
-      if (++s_rxno >= ETH_DESC_CNT) s_rxno = 0;
-    }
-  }
-  EMAC->EMACDMARIS = BIT(7);  // Clear possible RU while processing
-  EMAC->EMACRXPOLLD = 0;      // and resume RX
-}
-
-struct mg_tcpip_driver mg_tcpip_driver_tm4c = {mg_tcpip_driver_tm4c_init,
-                                               mg_tcpip_driver_tm4c_tx, NULL,
-                                               mg_tcpip_driver_tm4c_up};
-#endif
-
-#ifdef MG_ENABLE_LINES
-#line 1 "src/drivers/w5500.c"
-#endif
-
-
-#if MG_ENABLE_TCPIP
-
-enum { W5500_CR = 0, W5500_S0 = 1, W5500_TX0 = 2, W5500_RX0 = 3 };
-
-static void w5500_txn(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, bool wr,
-                      void *buf, size_t len) {
-  uint8_t *p = (uint8_t *) buf;
-  uint8_t cmd[] = {(uint8_t) (addr >> 8), (uint8_t) (addr & 255),
-                   (uint8_t) ((block << 3) | (wr ? 4 : 0))};
-  s->begin(s->spi);
-  for (size_t i = 0; i < sizeof(cmd); i++) s->txn(s->spi, cmd[i]);
-  for (size_t i = 0; i < len; i++) {
-    uint8_t r = s->txn(s->spi, p[i]);
-    if (!wr) p[i] = r;
-  }
-  s->end(s->spi);
-}
-
-// clang-format off
-static  void w5500_wn(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, void *buf, size_t len) { w5500_txn(s, block, addr, true, buf, len); }
-static  void w5500_w1(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, uint8_t val) { w5500_wn(s, block, addr, &val, 1); }
-static  void w5500_w2(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, uint16_t val) { uint8_t buf[2] = {(uint8_t) (val >> 8), (uint8_t) (val & 255)}; w5500_wn(s, block, addr, buf, sizeof(buf)); }
-static  void w5500_rn(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr, void *buf, size_t len) { w5500_txn(s, block, addr, false, buf, len); }
-static  uint8_t w5500_r1(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr) { uint8_t r = 0; w5500_rn(s, block, addr, &r, 1); return r; }
-static  uint16_t w5500_r2(struct mg_tcpip_spi *s, uint8_t block, uint16_t addr) { uint8_t buf[2] = {0, 0}; w5500_rn(s, block, addr, buf, sizeof(buf)); return (uint16_t) ((buf[0] << 8) | buf[1]); }
-// clang-format on
-
-static size_t w5500_rx(void *buf, size_t buflen, struct mg_tcpip_if *ifp) {
-  struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data;
-  uint16_t r = 0, n = 0, len = (uint16_t) buflen, n2;     // Read recv len
-  while ((n2 = w5500_r2(s, W5500_S0, 0x26)) > n) n = n2;  // Until it is stable
-  // printf("RSR: %d\n", (int) n);
-  if (n > 0) {
-    uint16_t ptr = w5500_r2(s, W5500_S0, 0x28);  // Get read pointer
-    n = w5500_r2(s, W5500_RX0, ptr);             // Read frame length
-    if (n <= len + 2 && n > 1) {
-      r = (uint16_t) (n - 2);
-      w5500_rn(s, W5500_RX0, (uint16_t) (ptr + 2), buf, r);
-    }
-    w5500_w2(s, W5500_S0, 0x28, (uint16_t) (ptr + n));  // Advance read pointer
-    w5500_w1(s, W5500_S0, 1, 0x40);                     // Sock0 CR -> RECV
-    // printf("  RX_RD: tot=%u n=%u r=%u\n", n2, n, r);
-  }
-  return r;
-}
-
-static size_t w5500_tx(const void *buf, size_t buflen, struct mg_tcpip_if *ifp) {
-  struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data;
-  uint16_t n = 0, len = (uint16_t) buflen;
-  while (n < len) n = w5500_r2(s, W5500_S0, 0x20);      // Wait for space
-  uint16_t ptr = w5500_r2(s, W5500_S0, 0x24);           // Get write pointer
-  w5500_wn(s, W5500_TX0, ptr, (void *) buf, len);       // Write data
-  w5500_w2(s, W5500_S0, 0x24, (uint16_t) (ptr + len));  // Advance write pointer
-  w5500_w1(s, W5500_S0, 1, 0x20);                       // Sock0 CR -> SEND
-  for (int i = 0; i < 40; i++) {
-    uint8_t ir = w5500_r1(s, W5500_S0, 2);  // Read S0 IR
-    if (ir == 0) continue;
-    // printf("IR %d, len=%d, free=%d, ptr %d\n", ir, (int) len, (int) n, ptr);
-    w5500_w1(s, W5500_S0, 2, ir);  // Write S0 IR: clear it!
-    if (ir & 8) len = 0;           // Timeout. Report error
-    if (ir & (16 | 8)) break;      // Stop on SEND_OK or timeout
-  }
-  return len;
-}
-
-static bool w5500_init(struct mg_tcpip_if *ifp) {
-  struct mg_tcpip_spi *s = (struct mg_tcpip_spi *) ifp->driver_data;
-  s->end(s->spi);
-  w5500_w1(s, W5500_CR, 0, 0x80);     // Reset chip: CR -> 0x80
-  w5500_w1(s, W5500_CR, 0x2e, 0);     // CR PHYCFGR -> reset
-  w5500_w1(s, W5500_CR, 0x2e, 0xf8);  // CR PHYCFGR -> set
-  // w5500_wn(s, W5500_CR, 9, s->mac, 6);      // Set source MAC
-  w5500_w1(s, W5500_S0, 0x1e, 16);          // Sock0 RX buf size
-  w5500_w1(s, W5500_S0, 0x1f, 16);          // Sock0 TX buf size
-  w5500_w1(s, W5500_S0, 0, 4);              // Sock0 MR -> MACRAW
-  w5500_w1(s, W5500_S0, 1, 1);              // Sock0 CR -> OPEN
-  return w5500_r1(s, W5500_S0, 3) == 0x42;  // Sock0 SR == MACRAW
-}
-
-static bool w5500_up(struct mg_tcpip_if *ifp) {
-  struct mg_tcpip_spi *spi = (struct mg_tcpip_spi *) ifp->driver_data;
-  uint8_t phycfgr = w5500_r1(spi, W5500_CR, 0x2e);
-  return phycfgr & 1;  // Bit 0 of PHYCFGR is LNK (0 - down, 1 - up)
-}
-
-struct mg_tcpip_driver mg_tcpip_driver_w5500 = {w5500_init, w5500_tx, w5500_rx, w5500_up};
-#endif
diff --git a/ext/mongoose.h b/ext/mongoose.h
deleted file mode 100644 (file)
index bc5f945..0000000
+++ /dev/null
@@ -1,1942 +0,0 @@
-// Copyright (c) 2004-2013 Sergey Lyubka
-// Copyright (c) 2013-2022 Cesanta Software Limited
-// All rights reserved
-//
-// This software is dual-licensed: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 2 as
-// published by the Free Software Foundation. For the terms of this
-// license, see http://www.gnu.org/licenses/
-//
-// You are free to use this software under the terms of the GNU General
-// Public License, but WITHOUT ANY WARRANTY; without even the implied
-// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-// See the GNU General Public License for more details.
-//
-// 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-only or commercial
-
-#ifndef MONGOOSE_H
-#define MONGOOSE_H
-
-#define MG_VERSION "7.11"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-
-#define MG_ARCH_CUSTOM 0       // User creates its own mongoose_custom.h
-#define MG_ARCH_UNIX 1         // Linux, BSD, Mac, ...
-#define MG_ARCH_WIN32 2        // Windows
-#define MG_ARCH_ESP32 3        // ESP32
-#define MG_ARCH_ESP8266 4      // ESP8266
-#define MG_ARCH_FREERTOS 5     // FreeRTOS
-#define MG_ARCH_AZURERTOS 6    // MS Azure RTOS
-#define MG_ARCH_ZEPHYR 7       // Zephyr RTOS
-#define MG_ARCH_NEWLIB 8       // Bare metal ARM
-#define MG_ARCH_CMSIS_RTOS1 9  // CMSIS-RTOS API v1 (Keil RTX)
-#define MG_ARCH_TIRTOS 10      // Texas Semi TI-RTOS
-#define MG_ARCH_RP2040 11      // Raspberry Pi RP2040
-#define MG_ARCH_ARMCC 12       // Keil MDK-Core with Configuration Wizard
-#define MG_ARCH_CMSIS_RTOS2 13 // CMSIS-RTOS API v2 (Keil RTX5, FreeRTOS)
-#define MG_ARCH_RTTHREAD 14    // RT-Thread RTOS
-
-#if !defined(MG_ARCH)
-#if defined(__unix__) || defined(__APPLE__)
-#define MG_ARCH MG_ARCH_UNIX
-#elif defined(_WIN32)
-#define MG_ARCH MG_ARCH_WIN32
-#elif defined(ICACHE_FLASH) || defined(ICACHE_RAM_ATTR)
-#define MG_ARCH MG_ARCH_ESP8266
-#elif defined(__ZEPHYR__)
-#define MG_ARCH MG_ARCH_ZEPHYR
-#elif defined(ESP_PLATFORM)
-#define MG_ARCH MG_ARCH_ESP32
-#elif defined(FREERTOS_IP_H)
-#define MG_ARCH MG_ARCH_FREERTOS
-#define MG_ENABLE_FREERTOS_TCP 1
-#elif defined(AZURE_RTOS_THREADX)
-#define MG_ARCH MG_ARCH_AZURERTOS
-#elif defined(PICO_TARGET_NAME)
-#define MG_ARCH MG_ARCH_RP2040
-#elif defined(__ARMCC_VERSION)
-#define MG_ARCH MG_ARCH_ARMCC
-#elif defined(__RTTHREAD__)
-#define MG_ARCH MG_ARCH_RTTHREAD
-#endif
-#endif  // !defined(MG_ARCH)
-
-// if the user did not specify an MG_ARCH, or specified a custom one, OR
-// we guessed a known IDE, pull the customized config (Configuration Wizard)
-#if !defined(MG_ARCH) || (MG_ARCH == MG_ARCH_CUSTOM) || MG_ARCH == MG_ARCH_ARMCC
-#include "mongoose_custom.h"  // keep this include
-#endif
-
-#if !defined(MG_ARCH)
-#error "MG_ARCH is not specified and we couldn't guess it. Set -D MG_ARCH=..."
-#endif
-
-// http://esr.ibiblio.org/?p=5095
-#define MG_BIG_ENDIAN (*(uint16_t *) "\0\xff" < 0x100)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-#if MG_ARCH == MG_ARCH_AZURERTOS
-
-#include <stdarg.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <time.h>
-
-#include <fx_api.h>
-#include <tx_api.h>
-
-#include <nx_api.h>
-#include <nx_bsd.h>
-#include <nx_port.h>
-#include <tx_port.h>
-
-#define PATH_MAX FX_MAXIMUM_PATH
-#define MG_DIRSEP '\\'
-
-#define socklen_t int
-#define closesocket(x) soc_close(x)
-
-#undef FOPEN_MAX
-
-#endif
-
-
-#if MG_ARCH == MG_ARCH_ESP32
-
-#include <ctype.h>
-#include <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <netdb.h>
-#include <stdarg.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <time.h>
-
-#include <esp_timer.h>
-
-#define MG_PATH_MAX 128
-
-#endif
-
-
-#if MG_ARCH == MG_ARCH_ESP8266
-
-#include <ctype.h>
-#include <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <netdb.h>
-#include <stdarg.h>
-#include <stdbool.h>
-#include <stddef.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 <esp_system.h>
-
-#define MG_PATH_MAX 128
-
-#endif
-
-
-#if MG_ARCH == MG_ARCH_FREERTOS
-
-#include <ctype.h>
-#if !defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP
-#include <errno.h>
-#endif
-#include <stdarg.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h> // rand(), strtol(), atoi()
-#include <string.h>
-#if defined(__ARMCC_VERSION)
-#define mode_t size_t
-#include <time.h>
-#else
-#include <sys/stat.h>
-#endif
-
-#include <FreeRTOS.h>
-#include <task.h>
-
-#ifndef MG_IO_SIZE
-#define MG_IO_SIZE 512
-#endif
-
-#define calloc(a, b) mg_calloc(a, b)
-#define free(a) vPortFree(a)
-#define malloc(a) pvPortMalloc(a)
-#define strdup(s) ((char *) mg_strdup(mg_str(s)).ptr)
-
-// Re-route calloc/free to the FreeRTOS's functions, don't use stdlib
-static inline void *mg_calloc(size_t cnt, size_t size) {
-  void *p = pvPortMalloc(cnt * size);
-  if (p != NULL) memset(p, 0, size * cnt);
-  return p;
-}
-
-#define mkdir(a, b) mg_mkdir(a, b)
-static inline int mg_mkdir(const char *path, mode_t mode) {
-  (void) path, (void) mode;
-  return -1;
-}
-
-#endif  // MG_ARCH == MG_ARCH_FREERTOS
-
-
-#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
-\r
-\r
-#if MG_ARCH == MG_ARCH_RTTHREAD\r
-\r
-#include <rtthread.h>\r
-#include <ctype.h>\r
-#include <errno.h>\r
-#include <fcntl.h>\r
-#include <sys/socket.h>\r
-#include <sys/select.h>\r
-#include <stdarg.h>\r
-#include <stdbool.h>\r
-#include <stdint.h>\r
-#include <stdio.h>\r
-#include <stdlib.h>\r
-#include <string.h>\r
-#include <sys/types.h>\r
-#include <time.h>\r
-\r
-#ifndef MG_IO_SIZE\r
-#define MG_IO_SIZE 1460\r
-#endif\r
-\r
-#endif // MG_ARCH == MG_ARCH_RTTHREAD\r
-
-
-#if MG_ARCH == MG_ARCH_ARMCC || MG_ARCH == MG_ARCH_CMSIS_RTOS1 || \
-    MG_ARCH == MG_ARCH_CMSIS_RTOS2
-
-#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>
-#if MG_ARCH == MG_ARCH_CMSIS_RTOS1
-#include "cmsis_os.h"  // keep this include
-// https://developer.arm.com/documentation/ka003821/latest
-extern uint32_t rt_time_get(void);
-#elif MG_ARCH == MG_ARCH_CMSIS_RTOS2
-#include "cmsis_os2.h"  // keep this include
-#endif
-
-#define strdup(s) ((char *) mg_strdup(mg_str(s)).ptr)
-
-#if defined(__ARMCC_VERSION)
-#define mode_t size_t
-#define mkdir(a, b) mg_mkdir(a, b)
-static inline int mg_mkdir(const char *path, mode_t mode) {
-  (void) path, (void) mode;
-  return -1;
-}
-#endif
-
-#if (MG_ARCH == MG_ARCH_CMSIS_RTOS1 || MG_ARCH == MG_ARCH_CMSIS_RTOS2) &&     \
-    !defined MG_ENABLE_RL && (!defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP) && \
-    (!defined(MG_ENABLE_TCPIP) || !MG_ENABLE_TCPIP)
-#define MG_ENABLE_RL 1
-#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 <serrno.h>
-#include <sys/socket.h>
-
-#include <ti/sysbios/knl/Clock.h>
-
-#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_EPOLL) && defined(__linux__)
-#define MG_ENABLE_EPOLL 1
-#elif !defined(MG_ENABLE_POLL)
-#define MG_ENABLE_POLL 1
-#endif
-
-#include <arpa/inet.h>
-#include <ctype.h>
-#include <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <limits.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <signal.h>
-#include <stdarg.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#if defined(MG_ENABLE_EPOLL) && MG_ENABLE_EPOLL
-#include <sys/epoll.h>
-#elif 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>
-#include <sys/types.h>
-#include <time.h>
-#include <unistd.h>
-
-#ifndef MG_ENABLE_DIRLIST
-#define MG_ENABLE_DIRLIST 1
-#endif
-
-#ifndef MG_PATH_MAX
-#define MG_PATH_MAX FILENAME_MAX
-#endif
-
-#endif
-
-
-#if MG_ARCH == MG_ARCH_WIN32
-
-#ifndef WIN32_LEAN_AND_MEAN
-#define WIN32_LEAN_AND_MEAN
-#endif
-
-#ifndef _CRT_SECURE_NO_WARNINGS
-#define _CRT_SECURE_NO_WARNINGS
-#endif
-
-#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS
-#define _WINSOCK_DEPRECATED_NO_WARNINGS
-#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>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <time.h>
-
-#if defined(_MSC_VER) && _MSC_VER < 1700
-#define __func__ ""
-typedef __int64 int64_t;
-typedef unsigned __int64 uint64_t;
-typedef unsigned char uint8_t;
-typedef char int8_t;
-typedef unsigned short uint16_t;
-typedef short int16_t;
-typedef unsigned int uint32_t;
-typedef int int32_t;
-typedef enum { false = 0, true = 1 } bool;
-#else
-#include <stdbool.h>
-#include <stdint.h>
-#include <ws2tcpip.h>
-#endif
-
-#include <process.h>
-#include <winerror.h>
-#include <winsock2.h>
-
-// Protect from calls like std::snprintf in app code
-// See https://github.com/cesanta/mongoose/issues/1047
-#ifndef __cplusplus
-#define snprintf _snprintf
-#define vsnprintf _vsnprintf
-#ifndef strdup  // For MSVC with _DEBUG, see #1359
-#define strdup(x) _strdup(x)
-#endif
-#endif
-
-#define MG_INVALID_SOCKET INVALID_SOCKET
-#define MG_SOCKET_TYPE SOCKET
-typedef unsigned long nfds_t;
-#if defined(_MSC_VER)
-#pragma comment(lib, "ws2_32.lib")
-#ifndef alloca
-#define alloca(a) _alloca(a)
-#endif
-#endif
-#define poll(a, b, c) WSAPoll((a), (b), (c))
-#define closesocket(x) closesocket(x)
-
-typedef int socklen_t;
-#define MG_DIRSEP '\\'
-
-#ifndef MG_PATH_MAX
-#define MG_PATH_MAX FILENAME_MAX
-#endif
-
-#ifndef SO_EXCLUSIVEADDRUSE
-#define SO_EXCLUSIVEADDRUSE ((int) (~SO_REUSEADDR))
-#endif
-
-#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? WSAGetLastError() : 0)
-
-#define MG_SOCK_PENDING(errcode)                                            \
-  (((errcode) < 0) &&                                                       \
-   (WSAGetLastError() == WSAEINTR || WSAGetLastError() == WSAEINPROGRESS || \
-    WSAGetLastError() == WSAEWOULDBLOCK))
-
-#define MG_SOCK_RESET(errcode) \
-  (((errcode) < 0) && (WSAGetLastError() == WSAECONNRESET))
-
-#define realpath(a, b) _fullpath((b), (a), MG_PATH_MAX)
-#define sleep(x) Sleep((x) *1000)
-#define mkdir(a, b) _mkdir(a)
-
-#ifndef S_ISDIR
-#define S_ISDIR(x) (((x) &_S_IFMT) == _S_IFDIR)
-#endif
-
-#ifndef MG_ENABLE_DIRLIST
-#define MG_ENABLE_DIRLIST 1
-#endif
-
-#ifndef SIGPIPE
-#define SIGPIPE 0
-#endif
-
-#endif
-
-
-#if MG_ARCH == MG_ARCH_ZEPHYR
-
-#include <zephyr/kernel.h>
-
-#include <ctype.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <zephyr/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)
-#ifndef strdup
-#define strdup(s) ((char *) mg_strdup(mg_str(s)).ptr)
-#endif
-#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
-
-
-#if defined(MG_ENABLE_FREERTOS_TCP) && MG_ENABLE_FREERTOS_TCP
-
-#include <limits.h>
-#include <list.h>
-
-#include <FreeRTOS_IP.h>
-#include <FreeRTOS_Sockets.h>
-#include <FreeRTOS_errno_TCP.h>  // contents to be moved and file removed, some day
-
-#define MG_SOCKET_TYPE Socket_t
-#define MG_INVALID_SOCKET FREERTOS_INVALID_SOCKET
-
-// 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
-#define IPPROTO_UDP FREERTOS_IPPROTO_UDP
-#define AF_INET FREERTOS_AF_INET
-#define SOCK_STREAM FREERTOS_SOCK_STREAM
-#define SOCK_DGRAM FREERTOS_SOCK_DGRAM
-#define SO_BROADCAST 0
-#define SO_ERROR 0
-#define SOL_SOCKET 0
-#define SO_REUSEADDR 0
-
-#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? (errcode) : 0)
-
-#define MG_SOCK_PENDING(errcode)                 \
-  ((errcode) == -pdFREERTOS_ERRNO_EWOULDBLOCK || \
-   (errcode) == -pdFREERTOS_ERRNO_EISCONN ||     \
-   (errcode) == -pdFREERTOS_ERRNO_EINPROGRESS || \
-   (errcode) == -pdFREERTOS_ERRNO_EAGAIN)
-
-#define MG_SOCK_RESET(errcode) ((errcode) == -pdFREERTOS_ERRNO_ENOTCONN)
-
-// actually only if optional timeout is enabled
-#define MG_SOCK_INTR(fd) (fd == NULL)
-
-#define sockaddr_in freertos_sockaddr
-#define sockaddr freertos_sockaddr
-#define accept(a, b, c) FreeRTOS_accept((a), (b), (c))
-#define connect(a, b, c) FreeRTOS_connect((a), (b), (c))
-#define bind(a, b, c) FreeRTOS_bind((a), (b), (c))
-#define listen(a, b) FreeRTOS_listen((a), (b))
-#define socket(a, b, c) FreeRTOS_socket((a), (b), (c))
-#define send(a, b, c, d) FreeRTOS_send((a), (b), (c), (d))
-#define recv(a, b, c, d) FreeRTOS_recv((a), (b), (c), (d))
-#define setsockopt(a, b, c, d, e) FreeRTOS_setsockopt((a), (b), (c), (d), (e))
-#define sendto(a, b, c, d, e, f) FreeRTOS_sendto((a), (b), (c), (d), (e), (f))
-#define recvfrom(a, b, c, d, e, f) \
-  FreeRTOS_recvfrom((a), (b), (c), (d), (e), (f))
-#define closesocket(x) FreeRTOS_closesocket(x)
-#define gethostbyname(x) FreeRTOS_gethostbyname(x)
-#define getsockname(a, b, c) mg_getsockname((a), (b), (c))
-#define getpeername(a, b, c) mg_getpeername((a), (b), (c))
-
-static inline int mg_getsockname(MG_SOCKET_TYPE fd, void *buf, socklen_t *len) {
-  (void) fd, (void) buf, (void) len;
-  return -1;
-}
-
-static inline int mg_getpeername(MG_SOCKET_TYPE fd, void *buf, socklen_t *len) {
-  (void) fd, (void) buf, (void) len;
-  return 0;
-}
-#endif
-
-
-#if defined(MG_ENABLE_LWIP) && MG_ENABLE_LWIP
-
-#if defined(__GNUC__) && !defined(__ARMCC_VERSION)
-#include <sys/stat.h>
-#endif
-
-struct timeval;
-
-#include <lwip/sockets.h>
-
-#if !LWIP_TIMEVAL_PRIVATE
-#if defined(__GNUC__) && !defined(__ARMCC_VERSION) // armclang sets both
-#include <sys/time.h>
-#else
-struct timeval {
-  time_t tv_sec;
-  long tv_usec;
-};
-#endif
-#endif
-
-#if LWIP_SOCKET != 1
-// Sockets support disabled in LWIP by default
-#error Set LWIP_SOCKET variable to 1 (in lwipopts.h)
-#endif
-#endif
-
-
-#if defined(MG_ENABLE_RL) && MG_ENABLE_RL
-#include <rl_net.h>
-
-#define closesocket(x) closesocket(x)
-
-#define TCP_NODELAY SO_KEEPALIVE
-
-#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? (errcode) : 0)
-
-#define MG_SOCK_PENDING(errcode)                                \
-  ((errcode) == BSD_EWOULDBLOCK || (errcode) == BSD_EALREADY || \
-   (errcode) == BSD_EINPROGRESS)
-
-#define MG_SOCK_RESET(errcode) \
-  ((errcode) == BSD_ECONNABORTED || (errcode) == BSD_ECONNRESET)
-
-// In blocking mode, which is enabled by default, accept() waits for a
-// connection request. In non blocking mode, you must call accept()
-// again if the error code BSD_EWOULDBLOCK is returned.
-#define MG_SOCK_INTR(fd) (fd == BSD_EWOULDBLOCK)
-
-#define socklen_t int
-#endif
-
-
-#ifndef MG_ENABLE_LOG
-#define MG_ENABLE_LOG 1
-#endif
-
-#ifndef MG_ENABLE_TCPIP
-#define MG_ENABLE_TCPIP 0  // Mongoose built-in network stack
-#endif
-
-#ifndef MG_ENABLE_LWIP
-#define MG_ENABLE_LWIP 0  // lWIP network stack
-#endif
-
-#ifndef MG_ENABLE_FREERTOS_TCP
-#define MG_ENABLE_FREERTOS_TCP 0  // Amazon FreeRTOS-TCP network stack
-#endif
-
-#ifndef MG_ENABLE_RL
-#define MG_ENABLE_RL 0  // ARM MDK network stack
-#endif
-
-#ifndef MG_ENABLE_SOCKET
-#define MG_ENABLE_SOCKET !MG_ENABLE_TCPIP
-#endif
-
-#ifndef MG_ENABLE_POLL
-#define MG_ENABLE_POLL 0
-#endif
-
-#ifndef MG_ENABLE_EPOLL
-#define MG_ENABLE_EPOLL 0
-#endif
-
-#ifndef MG_ENABLE_FATFS
-#define MG_ENABLE_FATFS 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 1
-#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
-
-#ifndef MG_ENABLE_ASSERT
-#define MG_ENABLE_ASSERT 0
-#endif
-
-#ifndef MG_IO_SIZE
-#define MG_IO_SIZE 2048  // Granularity of the send/recv IO buffer growth
-#endif
-
-#ifndef MG_MAX_RECV_SIZE
-#define MG_MAX_RECV_SIZE (3UL * 1024UL * 1024UL)  // Maximum recv IO buffer size
-#endif
-
-#ifndef MG_DATA_SIZE
-#define MG_DATA_SIZE 32  // struct mg_connection :: data size
-#endif
-
-#ifndef MG_MAX_HTTP_HEADERS
-#define MG_MAX_HTTP_HEADERS 30
-#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
-
-#ifndef MG_INVALID_SOCKET
-#define MG_INVALID_SOCKET (-1)
-#endif
-
-#ifndef MG_SOCKET_TYPE
-#define MG_SOCKET_TYPE int
-#endif
-
-#ifndef MG_SOCKET_ERRNO
-#define MG_SOCKET_ERRNO errno
-#endif
-
-#if MG_ENABLE_EPOLL
-#define MG_EPOLL_ADD(c)                                                    \
-  do {                                                                     \
-    struct epoll_event ev = {EPOLLIN | EPOLLERR | EPOLLHUP, {c}};          \
-    epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_ADD, (int) (size_t) c->fd, &ev); \
-  } while (0)
-#define MG_EPOLL_MOD(c, wr)                                                \
-  do {                                                                     \
-    struct epoll_event ev = {EPOLLIN | EPOLLERR | EPOLLHUP, {c}};          \
-    if (wr) ev.events |= EPOLLOUT;                                         \
-    epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_MOD, (int) (size_t) c->fd, &ev); \
-  } while (0)
-#else
-#define MG_EPOLL_ADD(c)
-#define MG_EPOLL_MOD(c, wr)
-#endif
-
-
-
-
-struct mg_str {
-  const char *ptr;  // Pointer to string data
-  size_t len;       // String len
-};
-
-#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)
-
-struct mg_str mg_str(const char *s);
-struct mg_str mg_str_n(const char *s, size_t n);
-int mg_lower(const char *s);
-int mg_ncasecmp(const char *s1, const char *s2, size_t len);
-int mg_casecmp(const char *s1, const char *s2);
-int mg_vcmp(const struct mg_str *s1, const char *s2);
-int mg_vcasecmp(const struct mg_str *str1, const char *str2);
-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);
-bool mg_path_is_sane(const char *path);
-
-
-
-
-// Single producer, single consumer non-blocking queue
-
-struct mg_queue {
-  char *buf;
-  size_t size;
-  volatile size_t tail;
-  volatile size_t head;
-};
-
-void mg_queue_init(struct mg_queue *, char *, size_t);        // Init queue
-size_t mg_queue_book(struct mg_queue *, char **buf, size_t);  // Reserve space
-void mg_queue_add(struct mg_queue *, size_t);                 // Add new message
-size_t mg_queue_next(struct mg_queue *, char **);  // Get oldest message
-void mg_queue_del(struct mg_queue *, size_t);      // Delete oldest message
-
-
-
-
-typedef void (*mg_pfn_t)(char, void *);                  // Output function
-typedef size_t (*mg_pm_t)(mg_pfn_t, void *, va_list *);  // %M printer
-
-size_t mg_vxprintf(void (*)(char, void *), void *, const char *fmt, va_list *);
-size_t mg_xprintf(void (*fn)(char, void *), void *, const char *fmt, ...);
-
-
-
-
-
-
-// Convenience wrappers around mg_xprintf
-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, ...);
-char *mg_vmprintf(const char *fmt, va_list *ap);
-char *mg_mprintf(const char *fmt, ...);
-size_t mg_queue_vprintf(struct mg_queue *, const char *fmt, va_list *);
-size_t mg_queue_printf(struct mg_queue *, const char *fmt, ...);
-
-// %M print helper functions
-size_t mg_print_base64(void (*out)(char, void *), void *arg, va_list *ap);
-size_t mg_print_esc(void (*out)(char, void *), void *arg, va_list *ap);
-size_t mg_print_hex(void (*out)(char, void *), void *arg, va_list *ap);
-size_t mg_print_ip(void (*out)(char, void *), void *arg, va_list *ap);
-size_t mg_print_ip_port(void (*out)(char, void *), void *arg, va_list *ap);
-size_t mg_print_ip4(void (*out)(char, void *), void *arg, va_list *ap);
-size_t mg_print_ip6(void (*out)(char, void *), void *arg, va_list *ap);
-size_t mg_print_mac(void (*out)(char, void *), void *arg, va_list *ap);
-
-// Various output functions
-void mg_pfn_iobuf(char ch, void *param);  // param: struct mg_iobuf *
-void mg_pfn_stdout(char c, void *param);  // param: ignored
-
-// A helper macro for printing JSON: mg_snprintf(buf, len, "%m", MG_ESC("hi"))
-#define MG_ESC(str) mg_print_esc, 0, (str)
-
-
-
-
-
-
-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(int log_level);
-void mg_hexdump(const void *buf, size_t len);
-void mg_log_set_fn(mg_pfn_t fn, void *param);
-
-#if MG_ENABLE_LOG
-#define MG_LOG(level, args)                                                \
-  do {                                                                     \
-    if (mg_log_prefix((level), __FILE__, __LINE__, __func__)) mg_log args; \
-  } while (0)
-#else
-#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 id;         // Timer ID
-  uint64_t period_ms;       // Timer period in milliseconds
-  uint64_t expire;          // Expiration timestamp in milliseconds
-  unsigned flags;           // Possible flags values below
-#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
-};
-
-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);
-bool mg_timer_expired(uint64_t *expiration, uint64_t period, uint64_t now);
-
-
-
-
-
-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;
-};
-
-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, ...);
-
-
-
-
-
-
-
-#if MG_ENABLE_ASSERT
-#include <assert.h>
-#elif !defined(assert)
-#define assert(x)
-#endif
-
-void mg_random(void *buf, size_t len);
-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);
-uint64_t mg_millis(void);
-
-#define mg_htons(x) mg_ntohs(x)
-#define mg_htonl(x) mg_ntohl(x)
-
-#define MG_U32(a, b, c, d)                                         \
-  (((uint32_t) ((a) &255) << 24) | ((uint32_t) ((b) &255) << 16) | \
-   ((uint32_t) ((c) &255) << 8) | (uint32_t) ((d) &255))
-
-// For printing IPv4 addresses: printf("%d.%d.%d.%d\n", MG_IPADDR_PARTS(&ip))
-#define MG_U8P(ADDR) ((uint8_t *) (ADDR))
-#define MG_IPADDR_PARTS(ADDR) \
-  MG_U8P(ADDR)[0], MG_U8P(ADDR)[1], MG_U8P(ADDR)[2], MG_U8P(ADDR)[3]
-
-struct mg_addr;
-int mg_check_ip_acl(struct mg_str acl, struct mg_addr *remote_ip);
-
-// Linked list management macros
-#define LIST_ADD_HEAD(type_, head_, elem_) \
-  do {                                     \
-    (elem_)->next = (*head_);              \
-    *(head_) = (elem_);                    \
-  } while (0)
-
-#define LIST_ADD_TAIL(type_, head_, elem_) \
-  do {                                     \
-    type_ **h = head_;                     \
-    while (*h != NULL) h = &(*h)->next;    \
-    *h = (elem_);                          \
-  } while (0)
-
-#define LIST_DELETE(type_, head_, elem_)   \
-  do {                                     \
-    type_ **h = head_;                     \
-    while (*h != (elem_)) h = &(*h)->next; \
-    *h = (elem_)->next;                    \
-  } while (0)
-
-
-
-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);
-struct mg_str mg_url_user(const char *url);
-struct mg_str mg_url_pass(const char *url);
-const char *mg_url_uri(const char *url);
-
-
-
-
-struct mg_iobuf {
-  unsigned char *buf;  // Pointer to stored data
-  size_t size;         // Total size available
-  size_t len;          // Current number of bytes
-  size_t align;        // Alignment during allocation
-};
-
-int mg_iobuf_init(struct mg_iobuf *, size_t, size_t);
-int mg_iobuf_resize(struct mg_iobuf *, size_t);
-void mg_iobuf_free(struct mg_iobuf *);
-size_t mg_iobuf_add(struct mg_iobuf *, size_t, const void *, size_t);
-size_t mg_iobuf_del(struct mg_iobuf *, size_t ofs, size_t len);
-
-int mg_base64_update(unsigned char p, char *to, int len);
-int mg_base64_final(char *to, int len);
-int mg_base64_encode(const unsigned char *p, int n, char *to);
-int mg_base64_decode(const char *src, int n, char *dst);
-
-
-
-
-typedef struct {
-  uint32_t buf[4];
-  uint32_t bits[2];
-  unsigned char in[64];
-} mg_md5_ctx;
-
-void mg_md5_init(mg_md5_ctx *c);
-void mg_md5_update(mg_md5_ctx *c, const unsigned char *data, size_t len);
-void mg_md5_final(mg_md5_ctx *c, unsigned char[16]);
-
-
-
-
-typedef struct {
-  uint32_t state[5];
-  uint32_t count[2];
-  unsigned char buffer[64];
-} mg_sha1_ctx;
-
-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 *);
-
-
-struct mg_connection;
-typedef void (*mg_event_handler_t)(struct mg_connection *, int ev,
-                                   void *ev_data, void *fn_data);
-void mg_call(struct mg_connection *c, int ev, void *ev_data);
-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        uint64_t *uptime_millis
-  MG_EV_RESOLVE,     // Host name is resolved        NULL
-  MG_EV_CONNECT,     // Connection established       NULL
-  MG_EV_ACCEPT,      // Connection accepted          NULL
-  MG_EV_TLS_HS,      // TLS handshake succeeded      NULL
-  MG_EV_READ,        // Data received from socket    long *bytes_read
-  MG_EV_WRITE,       // Data written to socket       long *bytes_written
-  MG_EV_CLOSE,       // Connection closed            NULL
-  MG_EV_HTTP_MSG,    // HTTP request/response        struct mg_http_message *
-  MG_EV_HTTP_CHUNK,  // HTTP chunk (partial msg)     struct mg_http_message *
-  MG_EV_WS_OPEN,     // Websocket handshake done     struct mg_http_message *
-  MG_EV_WS_MSG,      // Websocket msg, text or bin   struct mg_ws_message *
-  MG_EV_WS_CTL,      // Websocket control msg        struct mg_ws_message *
-  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           uint64_t *epoch_millis
-  MG_EV_USER         // Starting ID for user events
-};
-
-
-
-
-
-
-
-
-
-struct mg_dns {
-  const char *url;          // DNS server URL
-  struct mg_connection *c;  // DNS server connection
-};
-
-struct mg_addr {
-  uint8_t ip[16];  // Holds IPv4 or IPv6 address, in network byte order
-  uint16_t port;   // TCP or UDP port in network byte order
-  bool is_ip6;     // True when address is IPv6 address
-};
-
-struct mg_mgr {
-  struct mg_connection *conns;  // List of active connections
-  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
-  unsigned long timerid;        // Next timer ID
-  void *userdata;               // Arbitrary user data pointer
-  void *tls_ctx;                // TLS context shared by all TLS sessions
-  uint16_t mqtt_id;             // MQTT IDs for pub/sub
-  void *active_dns_requests;    // DNS requests in progress
-  struct mg_timer *timers;      // Active timers
-  int epoll_fd;                 // Used when MG_EPOLL_ENABLE=1
-  void *priv;                   // Used by the MIP stack
-  size_t extraconnsize;         // Used by the MIP stack
-#if MG_ENABLE_FREERTOS_TCP
-  SocketSet_t ss;  // NOTE(lsm): referenced from socket struct
-#endif
-};
-
-struct mg_connection {
-  struct mg_connection *next;  // Linkage in struct mg_mgr :: connections
-  struct mg_mgr *mgr;          // Our container
-  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
-  struct mg_iobuf send;        // Outgoing data
-  mg_event_handler_t fn;       // User-specified event handler function
-  void *fn_data;               // User-specified function parameter
-  mg_event_handler_t pfn;      // Protocol-specific handler function
-  void *pfn_data;              // Protocol-specific function parameter
-  char data[MG_DATA_SIZE];     // Arbitrary connection data
-  void *tls;                   // TLS specific data
-  unsigned is_listening : 1;   // Listening connection
-  unsigned is_client : 1;      // Outbound (client) connection
-  unsigned is_accepted : 1;    // Accepted (server) connection
-  unsigned is_resolving : 1;   // Non-blocking DNS resolution is in progress
-  unsigned is_arplooking : 1;  // Non-blocking ARP resolution is in progress
-  unsigned is_connecting : 1;  // Non-blocking connect is in progress
-  unsigned is_tls : 1;         // TLS-enabled 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_resp : 1;        // Response is still being generated
-  unsigned is_readable : 1;    // Connection is ready to read
-  unsigned is_writable : 1;    // Connection is ready to write
-};
-
-void mg_mgr_poll(struct mg_mgr *, int ms);
-void mg_mgr_init(struct mg_mgr *);
-void mg_mgr_free(struct mg_mgr *);
-
-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);
-size_t mg_printf(struct mg_connection *, const char *fmt, ...);
-size_t mg_vprintf(struct mg_connection *, const char *fmt, va_list *ap);
-bool mg_aton(struct mg_str str, struct mg_addr *addr);
-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);
-
-// Utility functions
-struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds,
-                              unsigned flags, void (*fn)(void *), void *arg);
-
-// Low-level IO primives used by TLS layer
-enum { MG_IO_ERR = -1, MG_IO_WAIT = -2, MG_IO_RESET = -3 };
-long mg_io_send(struct mg_connection *c, const void *buf, size_t len);
-long mg_io_recv(struct mg_connection *c, void *buf, size_t len);
-
-
-
-
-
-
-
-
-struct mg_http_header {
-  struct mg_str name;   // Header name
-  struct mg_str value;  // Header value
-};
-
-struct mg_http_message {
-  struct mg_str method, uri, query, proto;             // Request/response line
-  struct mg_http_header headers[MG_MAX_HTTP_HEADERS];  // Headers
-  struct mg_str body;                                  // Body
-  struct mg_str head;                                  // Request + headers
-  struct mg_str chunk;    // Chunk for chunked encoding,  or partial body
-  struct mg_str message;  // Request + headers + body
-};
-
-// Parameter for mg_http_serve_dir()
-struct mg_http_serve_opts {
-  const char *root_dir;       // Web root directory, must be non-NULL
-  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
-};
-
-// Parameter for mg_http_next_multipart
-struct mg_http_part {
-  struct mg_str name;      // Form field name
-  struct mg_str filename;  // Filename for file uploads
-  struct mg_str body;      // Part contents
-};
-
-int mg_http_parse(const char *s, size_t len, struct mg_http_message *);
-int mg_http_get_request_len(const unsigned char *buf, size_t buf_len);
-void mg_http_printf_chunk(struct mg_connection *cnn, const char *fmt, ...);
-void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len);
-void mg_http_delete_chunk(struct mg_connection *c, struct mg_http_message *hm);
-struct mg_connection *mg_http_listen(struct mg_mgr *, const char *url,
-                                     mg_event_handler_t fn, void *fn_data);
-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,
-                       const struct mg_http_serve_opts *);
-void mg_http_serve_file(struct mg_connection *, struct mg_http_message *hm,
-                        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);
-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_hello(const char *url);
-
-
-void mg_http_serve_ssi(struct mg_connection *c, const char *root,
-                       const char *fullpath);
-
-
-#define MG_TLS_NONE 0     // No TLS support
-#define MG_TLS_MBED 1     // mbedTLS
-#define MG_TLS_OPENSSL 2  // OpenSSL
-#define MG_TLS_BUILTIN 3  // Built-in
-#define MG_TLS_CUSTOM 4   // Custom implementation
-
-#ifndef MG_TLS
-#define MG_TLS MG_TLS_NONE
-#endif
-
-
-
-
-
-struct mg_tls_opts {
-  struct mg_str client_ca;
-  struct mg_str server_ca;
-  struct mg_str server_cert;
-  struct mg_str server_key;
-  struct mg_str client_cert;
-  struct mg_str client_key;
-};
-
-void mg_tls_ctx_init(struct mg_mgr *, const struct mg_tls_opts *);
-void mg_tls_ctx_free(struct mg_mgr *);
-void mg_tls_init(struct mg_connection *, struct mg_str hostname);
-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_TLS == MG_TLS_MBED
-#include <mbedtls/debug.h>
-#include <mbedtls/net_sockets.h>
-#include <mbedtls/ssl.h>
-#include <mbedtls/ssl_ticket.h>
-
-struct mg_tls_ctx {
-  mbedtls_x509_crt server_ca;     // Parsed CA certificate
-  mbedtls_x509_crt client_ca;     // Parsed CA certificate
-  mbedtls_x509_crt server_cert;   // Parsed server certificate
-  mbedtls_pk_context server_key;  // Parsed server private key context
-  mbedtls_x509_crt client_cert;   // Parsed client certificate
-  mbedtls_pk_context client_key;  // Parsed client private key context
-#ifdef MBEDTLS_SSL_SESSION_TICKETS
-  mbedtls_ssl_ticket_context ticket_ctx;  // Session tickets context
-#endif
-};
-
-struct mg_tls {
-  mbedtls_ssl_context ssl;  // SSL/TLS context
-  mbedtls_ssl_config conf;  // SSL-TLS config
-};
-#endif
-
-
-#if MG_TLS == MG_TLS_OPENSSL
-
-#include <openssl/err.h>
-#include <openssl/ssl.h>
-
-struct mg_tls_ctx {
-  X509 *server_cert;
-  EVP_PKEY *server_key;
-  STACK_OF(X509_INFO) *server_ca;
-  X509 *client_cert;
-  EVP_PKEY *client_key;
-  STACK_OF(X509_INFO) *client_ca;
-};
-
-struct mg_tls {
-  SSL_CTX *ctx;
-  SSL *ssl;
-};
-#endif
-
-
-#define WEBSOCKET_OP_CONTINUE 0
-#define WEBSOCKET_OP_TEXT 1
-#define WEBSOCKET_OP_BINARY 2
-#define WEBSOCKET_OP_CLOSE 8
-#define WEBSOCKET_OP_PING 9
-#define WEBSOCKET_OP_PONG 10
-
-
-
-struct mg_ws_message {
-  struct mg_str data;  // Websocket message data
-  uint8_t flags;       // Websocket message flags
-};
-
-struct mg_connection *mg_ws_connect(struct mg_mgr *, const char *url,
-                                    mg_event_handler_t fn, void *fn_data,
-                                    const char *fmt, ...);
-void mg_ws_upgrade(struct mg_connection *, struct mg_http_message *,
-                   const char *fmt, ...);
-size_t mg_ws_send(struct mg_connection *, const void *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_request(struct mg_connection *c);
-int64_t mg_sntp_parse(const unsigned char *buf, size_t len);
-
-
-
-
-
-#define MQTT_CMD_CONNECT 1
-#define MQTT_CMD_CONNACK 2
-#define MQTT_CMD_PUBLISH 3
-#define MQTT_CMD_PUBACK 4
-#define MQTT_CMD_PUBREC 5
-#define MQTT_CMD_PUBREL 6
-#define MQTT_CMD_PUBCOMP 7
-#define MQTT_CMD_SUBSCRIBE 8
-#define MQTT_CMD_SUBACK 9
-#define MQTT_CMD_UNSUBSCRIBE 10
-#define MQTT_CMD_UNSUBACK 11
-#define MQTT_CMD_PINGREQ 12
-#define MQTT_CMD_PINGRESP 13
-#define MQTT_CMD_DISCONNECT 14
-#define MQTT_CMD_AUTH 15
-
-#define MQTT_PROP_PAYLOAD_FORMAT_INDICATOR 0x01
-#define MQTT_PROP_MESSAGE_EXPIRY_INTERVAL 0x02
-#define MQTT_PROP_CONTENT_TYPE 0x03
-#define MQTT_PROP_RESPONSE_TOPIC 0x08
-#define MQTT_PROP_CORRELATION_DATA 0x09
-#define MQTT_PROP_SUBSCRIPTION_IDENTIFIER 0x0B
-#define MQTT_PROP_SESSION_EXPIRY_INTERVAL 0x11
-#define MQTT_PROP_ASSIGNED_CLIENT_IDENTIFIER 0x12
-#define MQTT_PROP_SERVER_KEEP_ALIVE 0x13
-#define MQTT_PROP_AUTHENTICATION_METHOD 0x15
-#define MQTT_PROP_AUTHENTICATION_DATA 0x16
-#define MQTT_PROP_REQUEST_PROBLEM_INFORMATION 0x17
-#define MQTT_PROP_WILL_DELAY_INTERVAL 0x18
-#define MQTT_PROP_REQUEST_RESPONSE_INFORMATION 0x19
-#define MQTT_PROP_RESPONSE_INFORMATION 0x1A
-#define MQTT_PROP_SERVER_REFERENCE 0x1C
-#define MQTT_PROP_REASON_STRING 0x1F
-#define MQTT_PROP_RECEIVE_MAXIMUM 0x21
-#define MQTT_PROP_TOPIC_ALIAS_MAXIMUM 0x22
-#define MQTT_PROP_TOPIC_ALIAS 0x23
-#define MQTT_PROP_MAXIMUM_QOS 0x24
-#define MQTT_PROP_RETAIN_AVAILABLE 0x25
-#define MQTT_PROP_USER_PROPERTY 0x26
-#define MQTT_PROP_MAXIMUM_PACKET_SIZE 0x27
-#define MQTT_PROP_WILDCARD_SUBSCRIPTION_AVAILABLE 0x28
-#define MQTT_PROP_SUBSCRIPTION_IDENTIFIER_AVAILABLE 0x29
-#define MQTT_PROP_SHARED_SUBSCRIPTION_AVAILABLE 0x2A
-
-enum {
-  MQTT_PROP_TYPE_BYTE,
-  MQTT_PROP_TYPE_STRING,
-  MQTT_PROP_TYPE_STRING_PAIR,
-  MQTT_PROP_TYPE_BINARY_DATA,
-  MQTT_PROP_TYPE_VARIABLE_INT,
-  MQTT_PROP_TYPE_INT,
-  MQTT_PROP_TYPE_SHORT
-};
-
-enum { MQTT_OK, MQTT_INCOMPLETE, MQTT_MALFORMED };
-
-struct mg_mqtt_prop {
-  uint8_t id;         // Enumerated at MQTT5 Reference
-  uint32_t iv;        // Integer value for 8-, 16-, 32-bit integers types
-  struct mg_str key;  // Non-NULL only for user property type
-  struct mg_str val;  // Non-NULL only for UTF-8 types and user properties
-};
-
-struct mg_mqtt_opts {
-  struct mg_str user;               // Username, can be empty
-  struct mg_str pass;               // Password, can be empty
-  struct mg_str client_id;          // Client ID
-  struct mg_str topic;              // message/subscription topic
-  struct mg_str message;            // message content
-  uint8_t qos;                      // 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 retain;                      // Retain flag
-  bool clean;                       // Clean session flag
-  struct mg_mqtt_prop *props;       // MQTT5 props array
-  size_t num_props;                 // number of props
-  struct mg_mqtt_prop *will_props;  // Valid only for CONNECT packet (MQTT5)
-  size_t num_will_props;            // Number of will props
-};
-
-struct mg_mqtt_message {
-  struct mg_str topic;  // Parsed topic for PUBLISH
-  struct mg_str data;   // Parsed message for PUBLISH
-  struct mg_str dgram;  // Whole MQTT packet, including headers
-  uint16_t id;          // For PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK, PUBLISH
-  uint8_t cmd;          // MQTT command, one of MQTT_CMD_*
-  uint8_t qos;          // Quality of service
-  uint8_t ack;          // CONNACK return code, 0 = success
-  size_t props_start;   // Offset to the start of the properties (MQTT5)
-  size_t props_size;    // Length of the properties
-};
-
-struct mg_connection *mg_mqtt_connect(struct mg_mgr *, const char *url,
-                                      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, const struct mg_mqtt_opts *opts);
-void mg_mqtt_pub(struct mg_connection *c, const struct mg_mqtt_opts *opts);
-void mg_mqtt_sub(struct mg_connection *, const struct mg_mqtt_opts *opts);
-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);
-void mg_mqtt_ping(struct mg_connection *);
-void mg_mqtt_pong(struct mg_connection *);
-void mg_mqtt_disconnect(struct mg_connection *, const struct mg_mqtt_opts *);
-size_t mg_mqtt_next_prop(struct mg_mqtt_message *, struct mg_mqtt_prop *,
-                         size_t ofs);
-
-
-
-
-
-// Mongoose sends DNS queries that contain only one question:
-// either A (IPv4) or AAAA (IPv6) address lookup.
-// Therefore, we expect zero or one answer.
-// If `resolved` is true, then `addr` contains resolved IPv4 or IPV6 address.
-struct mg_dns_message {
-  uint16_t txnid;       // Transaction ID
-  bool resolved;        // Resolve successful, addr is set
-  struct mg_addr addr;  // Resolved address
-  char name[256];       // Host name
-};
-
-struct mg_dns_header {
-  uint16_t txnid;  // Transaction ID
-  uint16_t flags;
-  uint16_t num_questions;
-  uint16_t num_answers;
-  uint16_t num_authority_prs;
-  uint16_t num_other_prs;
-};
-
-// DNS resource record
-struct mg_dns_rr {
-  uint16_t nlen;    // Name or pointer length
-  uint16_t atype;   // Address type
-  uint16_t aclass;  // Address class
-  uint16_t alen;    // Address length
-};
-
-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 *);
-
-
-
-
-
-#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(struct mg_str json, 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);
-
-bool mg_json_unescape(struct mg_str str, char *buf, size_t len);
-
-
-
-
-// JSON-RPC request descriptor
-struct mg_rpc_req {
-  struct mg_rpc **head;  // RPC handlers list head
-  struct mg_rpc *rpc;    // RPC handler being called
-  mg_pfn_t pfn;          // Response printing function
-  void *pfn_data;        // Response printing function data
-  void *req_data;        // Arbitrary request data
-  struct mg_str frame;   // Request, e.g. {"id":1,"method":"add","params":[1,2]}
-};
-
-// JSON-RPC method handler
-struct mg_rpc {
-  struct mg_rpc *next;              // Next in list
-  struct mg_str method;             // Method pattern
-  void (*fn)(struct mg_rpc_req *);  // Handler function
-  void *fn_data;                    // Handler function argument
-};
-
-void mg_rpc_add(struct mg_rpc **head, struct mg_str method_pattern,
-                void (*handler)(struct mg_rpc_req *), void *handler_data);
-void mg_rpc_del(struct mg_rpc **head, void (*handler)(struct mg_rpc_req *));
-void mg_rpc_process(struct mg_rpc_req *);
-
-// Helper functions to print result or error frame
-void mg_rpc_ok(struct mg_rpc_req *, const char *fmt, ...);
-void mg_rpc_vok(struct mg_rpc_req *, const char *fmt, va_list *ap);
-void mg_rpc_err(struct mg_rpc_req *, int code, const char *fmt, ...);
-void mg_rpc_verr(struct mg_rpc_req *, int code, const char *fmt, va_list *);
-void mg_rpc_list(struct mg_rpc_req *r);
-
-
-
-
-
-
-#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP
-struct mg_tcpip_if;  // Mongoose TCP/IP network interface
-
-struct mg_tcpip_driver {
-  bool (*init)(struct mg_tcpip_if *);                         // Init driver
-  size_t (*tx)(const void *, size_t, struct mg_tcpip_if *);   // Transmit frame
-  size_t (*rx)(void *buf, size_t len, struct mg_tcpip_if *);  // Receive frame
-  bool (*up)(struct mg_tcpip_if *);                           // Up/down status
-};
-
-// Network interface
-struct mg_tcpip_if {
-  uint8_t mac[6];                  // MAC address. Must be set to a valid MAC
-  uint32_t ip, mask, gw;           // IP address, mask, default gateway
-  struct mg_str tx;                // Output (TX) buffer
-  bool enable_dhcp_client;         // Enable DCHP client
-  bool enable_dhcp_server;         // Enable DCHP server
-  bool enable_crc32_check;         // Do a CRC check on RX frames and strip it
-  bool enable_mac_check;           // Do a MAC check on RX frames
-  struct mg_tcpip_driver *driver;  // Low level driver
-  void *driver_data;               // Driver-specific data
-  struct mg_mgr *mgr;              // Mongoose event manager
-  struct mg_queue recv_queue;      // Receive queue
-
-  // Internal state, user can use it but should not change it
-  uint8_t gwmac[6];             // Router's MAC
-  uint64_t now;                 // Current time
-  uint64_t timer_1000ms;        // 1000 ms timer: for DHCP and link state
-  uint64_t lease_expire;        // Lease expiration time, in ms
-  uint16_t eport;               // Next ephemeral port
-  volatile uint32_t ndrop;      // Number of received, but dropped frames
-  volatile uint32_t nrecv;      // Number of received frames
-  volatile uint32_t nsent;      // Number of transmitted frames
-  volatile uint32_t nerr;       // Number of driver errors
-  uint8_t state;                // Current state
-#define MG_TCPIP_STATE_DOWN 0   // Interface is down
-#define MG_TCPIP_STATE_UP 1     // Interface is up
-#define MG_TCPIP_STATE_REQ 2    // Interface is up and has requested an IP
-#define MG_TCPIP_STATE_READY 3  // Interface is up and has an IP assigned
-};
-
-void mg_tcpip_init(struct mg_mgr *, struct mg_tcpip_if *);
-void mg_tcpip_free(struct mg_tcpip_if *);
-void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp);
-
-extern struct mg_tcpip_driver mg_tcpip_driver_stm32;
-extern struct mg_tcpip_driver mg_tcpip_driver_w5500;
-extern struct mg_tcpip_driver mg_tcpip_driver_tm4c;
-extern struct mg_tcpip_driver mg_tcpip_driver_stm32h;
-extern struct mg_tcpip_driver mg_tcpip_driver_imxrt;
-extern struct mg_tcpip_driver mg_tcpip_driver_same54;
-
-// Drivers that require SPI, can use this SPI abstraction
-struct mg_tcpip_spi {
-  void *spi;                        // Opaque SPI bus descriptor
-  void (*begin)(void *);            // SPI begin: slave select low
-  void (*end)(void *);              // SPI end: slave select high
-  uint8_t (*txn)(void *, uint8_t);  // SPI transaction: write 1 byte, read reply
-};
-#endif
-
-
-struct mg_tcpip_driver_imxrt1020_data {
-  // MDC clock divider. MDC clock is derived from IPS Bus clock (ipg_clk),
-  // must not exceed 2.5MHz. Configuration for clock range 2.36~2.50 MHz
-  //    ipg_clk       MSCR       mdc_cr VALUE
-  //    -------------------------------------
-  //                                -1  <-- tell driver to guess the value
-  //    25 MHz        0x04           0
-  //    33 MHz        0x06           1
-  //    40 MHz        0x07           2
-  //    50 MHz        0x09           3
-  //    66 MHz        0x0D           4  <-- value for iMXRT1020-EVK at max freq.
-  int mdc_cr;  // Valid values: -1, 0, 1, 2, 3, 4
-};
-
-
-#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_SAME54) && MG_ENABLE_DRIVER_SAME54
-
-struct mg_tcpip_driver_same54_data {
-    int mdc_cr;
-};
-
-#endif
-
-
-struct mg_tcpip_driver_stm32_data {
-  // MDC clock divider. MDC clock is derived from HCLK, must not exceed 2.5MHz
-  //    HCLK range    DIVIDER    mdc_cr VALUE
-  //    -------------------------------------
-  //                                -1  <-- tell driver to guess the value
-  //    60-100 MHz    HCLK/42        0
-  //    100-150 MHz   HCLK/62        1
-  //    20-35 MHz     HCLK/16        2
-  //    35-60 MHz     HCLK/26        3
-  //    150-216 MHz   HCLK/102       4  <-- value for Nucleo-F* on max speed
-  //    216-310 MHz   HCLK/124       5
-  //    110, 111 Reserved
-  int mdc_cr;  // Valid values: -1, 0, 1, 2, 3, 4, 5
-};
-
-
-struct mg_tcpip_driver_stm32h_data {
-  // MDC clock divider. MDC clock is derived from HCLK, must not exceed 2.5MHz
-  //    HCLK range    DIVIDER    mdc_cr VALUE
-  //    -------------------------------------
-  //                                -1  <-- tell driver to guess the value
-  //    60-100 MHz    HCLK/42        0
-  //    100-150 MHz   HCLK/62        1
-  //    20-35 MHz     HCLK/16        2
-  //    35-60 MHz     HCLK/26        3
-  //    150-250 MHz   HCLK/102       4  <-- value for Nucleo-H* on max speed driven by HSI
-  //    250-300 MHz   HCLK/124       5  <-- value for Nucleo-H* on max speed driven by CSI
-  //    110, 111 Reserved
-  int mdc_cr;  // Valid values: -1, 0, 1, 2, 3, 4, 5
-};
-
-
-struct mg_tcpip_driver_tm4c_data {
-  // MDC clock divider. MDC clock is derived from SYSCLK, must not exceed 2.5MHz
-  //    SYSCLK range   DIVIDER   mdc_cr VALUE
-  //    -------------------------------------
-  //                                -1  <-- tell driver to guess the value
-  //    60-100 MHz    SYSCLK/42      0
-  //    100-150 MHz   SYSCLK/62      1  <-- value for EK-TM4C129* on max speed
-  //    20-35 MHz     SYSCLK/16      2
-  //    35-60 MHz     SYSCLK/26      3
-  //    0x4-0xF Reserved
-  int mdc_cr;  // Valid values: -1, 0, 1, 2, 3
-};
-
-
-#define CA_ISRG_ROOT_X2                                                \
-  "-----BEGIN CERTIFICATE-----\n"                                      \
-  "MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw\n" \
-  "CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg\n" \
-  "R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00\n" \
-  "MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT\n" \
-  "ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw\n" \
-  "EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW\n" \
-  "+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9\n" \
-  "ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T\n" \
-  "AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI\n" \
-  "zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW\n" \
-  "tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1\n" \
-  "/q4AaOeMSQ+2b1tbFfLn\n"                                             \
-  "-----END CERTIFICATE-----\n"
-
-#define CA_ISRG_ROOT_X1                                                \
-  "-----BEGIN CERTIFICATE-----\n"                                      \
-  "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" \
-  "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" \
-  "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n" \
-  "WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n" \
-  "ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n" \
-  "MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n" \
-  "h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n" \
-  "0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n" \
-  "A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n" \
-  "T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n" \
-  "B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n" \
-  "B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n" \
-  "KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n" \
-  "OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n" \
-  "jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n" \
-  "qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n" \
-  "rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n" \
-  "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n" \
-  "hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n" \
-  "ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n" \
-  "3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n" \
-  "NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n" \
-  "ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n" \
-  "TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n" \
-  "jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n" \
-  "oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n" \
-  "4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n" \
-  "mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n" \
-  "emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n" \
-  "-----END CERTIFICATE-----\n"
-
-#define CA_GOOGLE_TRUST                                                \
-  "-----BEGIN CERTIFICATE-----\n"                                      \
-  "MIIBxTCCAWugAwIBAgINAfD3nVndblD3QnNxUDAKBggqhkjOPQQDAjBEMQswCQYD\n" \
-  "VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzERMA8G\n" \
-  "A1UEAxMIR1RTIExUU1IwHhcNMTgxMTAxMDAwMDQyWhcNNDIxMTAxMDAwMDQyWjBE\n" \
-  "MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM\n" \
-  "QzERMA8GA1UEAxMIR1RTIExUU1IwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATN\n" \
-  "8YyO2u+yCQoZdwAkUNv5c3dokfULfrA6QJgFV2XMuENtQZIG5HUOS6jFn8f0ySlV\n" \
-  "eORCxqFyjDJyRn86d+Iko0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUw\n" \
-  "AwEB/zAdBgNVHQ4EFgQUPv7/zFLrvzQ+PfNA0OQlsV+4u1IwCgYIKoZIzj0EAwID\n" \
-  "SAAwRQIhAPKuf/VtBHqGw3TUwUIq7TfaExp3bH7bjCBmVXJupT9FAiBr0SmCtsuk\n" \
-  "miGgpajjf/gFigGM34F9021bCWs1MbL0SA==\n"                             \
-  "-----END CERTIFICATE-----\n"
-
-#define CA_GLOBALSIGN_EC                                               \
-  "-----BEGIN CERTIFICATE-----\n"                                      \
-  "MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk\n" \
-  "MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH\n" \
-  "bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX\n" \
-  "DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD\n" \
-  "QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu\n" \
-  "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ\n" \
-  "FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw\n" \
-  "DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F\n" \
-  "uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX\n" \
-  "kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs\n" \
-  "ewv4n4Q=\n"                                                         \
-  "-----END CERTIFICATE-----\n"
-
-#define CA_GLOBALSIGN_RSA                                              \
-  "-----BEGIN CERTIFICATE-----\n"                                      \
-  "MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG\n" \
-  "A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv\n" \
-  "b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw\n" \
-  "MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i\n" \
-  "YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT\n" \
-  "aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ\n" \
-  "jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp\n" \
-  "xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp\n" \
-  "1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG\n" \
-  "snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ\n" \
-  "U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8\n" \
-  "9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E\n" \
-  "BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B\n" \
-  "AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz\n" \
-  "yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE\n" \
-  "38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP\n" \
-  "AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad\n" \
-  "DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME\n" \
-  "HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==\n"                             \
-  "-----END CERTIFICATE-----\n"
-
-#define CA_DIGICERT                                                    \
-  "-----BEGIN CERTIFICATE-----\n"                                      \
-  "MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\n" \
-  "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" \
-  "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n" \
-  "QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\n" \
-  "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n" \
-  "b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n" \
-  "9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\n" \
-  "CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\n" \
-  "nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n" \
-  "43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\n" \
-  "T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\n" \
-  "gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\n" \
-  "BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\n" \
-  "TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\n" \
-  "DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\n" \
-  "hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n" \
-  "06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\n" \
-  "PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\n" \
-  "YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\n" \
-  "CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n"                 \
-  "-----END CERTIFICATE-----\n"
-
-#define CA_AMAZON_4                                                    \
-  "-----BEGIN CERTIFICATE-----\n"                                      \
-  "MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5\n" \
-  "MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g\n" \
-  "Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG\n" \
-  "A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg\n" \
-  "Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi\n" \
-  "9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk\n" \
-  "M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB\n" \
-  "/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB\n" \
-  "MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw\n" \
-  "CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW\n" \
-  "1KyLa2tJElMzrdfkviT8tQp21KW8EA==\n"                                 \
-  "-----END CERTIFICATE-----\n"
-
-#define CA_AMAZON_3                                                    \
-  "-----BEGIN CERTIFICATE-----\n"                                      \
-  "MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5\n" \
-  "MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g\n" \
-  "Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG\n" \
-  "A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg\n" \
-  "Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl\n" \
-  "ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j\n" \
-  "QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr\n" \
-  "ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr\n" \
-  "BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM\n" \
-  "YyRIHN8wfdVoOw==\n"                                                 \
-  "-----END CERTIFICATE-----\n"
-
-#define CA_ALL                                                     \
-  CA_ISRG_ROOT_X1 CA_ISRG_ROOT_X2 CA_GOOGLE_TRUST CA_GLOBALSIGN_EC \
-      CA_GLOBALSIGN_RSA CA_DIGICERT CA_AMAZON_4 CA_AMAZON_3
-
-#ifdef __cplusplus
-}
-#endif
-#endif  // MONGOOSE_H
diff --git a/ext/stb_ds.h b/ext/stb_ds.h
deleted file mode 100644 (file)
index e84c82d..0000000
+++ /dev/null
@@ -1,1895 +0,0 @@
-/* stb_ds.h - v0.67 - public domain data structures - Sean Barrett 2019
-
-   This is a single-header-file library that provides easy-to-use
-   dynamic arrays and hash tables for C (also works in C++).
-
-   For a gentle introduction:
-      http://nothings.org/stb_ds
-
-   To use this library, do this in *one* C or C++ file:
-      #define STB_DS_IMPLEMENTATION
-      #include "stb_ds.h"
-
-TABLE OF CONTENTS
-
-  Table of Contents
-  Compile-time options
-  License
-  Documentation
-  Notes
-  Notes - Dynamic arrays
-  Notes - Hash maps
-  Credits
-
-COMPILE-TIME OPTIONS
-
-  #define STBDS_NO_SHORT_NAMES
-
-     This flag needs to be set globally.
-
-     By default stb_ds exposes shorter function names that are not qualified
-     with the "stbds_" prefix. If these names conflict with the names in your
-     code, define this flag.
-
-  #define STBDS_SIPHASH_2_4
-
-     This flag only needs to be set in the file containing #define STB_DS_IMPLEMENTATION.
-
-     By default stb_ds.h hashes using a weaker variant of SipHash and a custom hash for
-     4- and 8-byte keys. On 64-bit platforms, you can define the above flag to force
-     stb_ds.h to use specification-compliant SipHash-2-4 for all keys. Doing so makes
-     hash table insertion about 20% slower on 4- and 8-byte keys, 5% slower on
-     64-byte keys, and 10% slower on 256-byte keys on my test computer.
-
-  #define STBDS_REALLOC(context,ptr,size) better_realloc
-  #define STBDS_FREE(context,ptr)         better_free
-
-     These defines only need to be set in the file containing #define STB_DS_IMPLEMENTATION.
-
-     By default stb_ds uses stdlib realloc() and free() for memory management. You can
-     substitute your own functions instead by defining these symbols. You must either
-     define both, or neither. Note that at the moment, 'context' will always be NULL.
-     @TODO add an array/hash initialization function that takes a memory context pointer.
-
-  #define STBDS_UNIT_TESTS
-
-     Defines a function stbds_unit_tests() that checks the functioning of the data structures.
-
-  Note that on older versions of gcc (e.g. 5.x.x) you may need to build with '-std=c++0x'
-     (or equivalentally '-std=c++11') when using anonymous structures as seen on the web
-     page or in STBDS_UNIT_TESTS.
-
-LICENSE
-
-  Placed in the public domain and also MIT licensed.
-  See end of file for detailed license information.
-
-DOCUMENTATION
-
-  Dynamic Arrays
-
-    Non-function interface:
-
-      Declare an empty dynamic array of type T
-        T* foo = NULL;
-
-      Access the i'th item of a dynamic array 'foo' of type T, T* foo:
-        foo[i]
-
-    Functions (actually macros)
-
-      arrfree:
-        void arrfree(T*);
-          Frees the array.
-
-      arrlen:
-        ptrdiff_t arrlen(T*);
-          Returns the number of elements in the array.
-
-      arrlenu:
-        size_t arrlenu(T*);
-          Returns the number of elements in the array as an unsigned type.
-
-      arrpop:
-        T arrpop(T* a)
-          Removes the final element of the array and returns it.
-
-      arrput:
-        T arrput(T* a, T b);
-          Appends the item b to the end of array a. Returns b.
-
-      arrins:
-        T arrins(T* a, int p, T b);
-          Inserts the item b into the middle of array a, into a[p],
-          moving the rest of the array over. Returns b.
-
-      arrinsn:
-        void arrinsn(T* a, int p, int n);
-          Inserts n uninitialized items into array a starting at a[p],
-          moving the rest of the array over.
-
-      arraddnptr:
-        T* arraddnptr(T* a, int n)
-          Appends n uninitialized items onto array at the end.
-          Returns a pointer to the first uninitialized item added.
-
-      arraddnindex:
-        size_t arraddnindex(T* a, int n)
-          Appends n uninitialized items onto array at the end.
-          Returns the index of the first uninitialized item added.
-
-      arrdel:
-        void arrdel(T* a, int p);
-          Deletes the element at a[p], moving the rest of the array over.
-
-      arrdeln:
-        void arrdeln(T* a, int p, int n);
-          Deletes n elements starting at a[p], moving the rest of the array over.
-
-      arrdelswap:
-        void arrdelswap(T* a, int p);
-          Deletes the element at a[p], replacing it with the element from
-          the end of the array. O(1) performance.
-
-      arrsetlen:
-        void arrsetlen(T* a, int n);
-          Changes the length of the array to n. Allocates uninitialized
-          slots at the end if necessary.
-
-      arrsetcap:
-        size_t arrsetcap(T* a, int n);
-          Sets the length of allocated storage to at least n. It will not
-          change the length of the array.
-
-      arrcap:
-        size_t arrcap(T* a);
-          Returns the number of total elements the array can contain without
-          needing to be reallocated.
-
-  Hash maps & String hash maps
-
-    Given T is a structure type: struct { TK key; TV value; }. Note that some
-    functions do not require TV value and can have other fields. For string
-    hash maps, TK must be 'char *'.
-
-    Special interface:
-
-      stbds_rand_seed:
-        void stbds_rand_seed(size_t seed);
-          For security against adversarially chosen data, you should seed the
-          library with a strong random number. Or at least seed it with time().
-
-      stbds_hash_string:
-        size_t stbds_hash_string(char *str, size_t seed);
-          Returns a hash value for a string.
-
-      stbds_hash_bytes:
-        size_t stbds_hash_bytes(void *p, size_t len, size_t seed);
-          These functions hash an arbitrary number of bytes. The function
-          uses a custom hash for 4- and 8-byte data, and a weakened version
-          of SipHash for everything else. On 64-bit platforms you can get
-          specification-compliant SipHash-2-4 on all data by defining
-          STBDS_SIPHASH_2_4, at a significant cost in speed.
-
-    Non-function interface:
-
-      Declare an empty hash map of type T
-        T* foo = NULL;
-
-      Access the i'th entry in a hash table T* foo:
-        foo[i]
-
-    Function interface (actually macros):
-
-      hmfree
-      shfree
-        void hmfree(T*);
-        void shfree(T*);
-          Frees the hashmap and sets the pointer to NULL.
-
-      hmlen
-      shlen
-        ptrdiff_t hmlen(T*)
-        ptrdiff_t shlen(T*)
-          Returns the number of elements in the hashmap.
-
-      hmlenu
-      shlenu
-        size_t hmlenu(T*)
-        size_t shlenu(T*)
-          Returns the number of elements in the hashmap.
-
-      hmgeti
-      shgeti
-      hmgeti_ts
-        ptrdiff_t hmgeti(T*, TK key)
-        ptrdiff_t shgeti(T*, char* key)
-        ptrdiff_t hmgeti_ts(T*, TK key, ptrdiff_t tempvar)
-          Returns the index in the hashmap which has the key 'key', or -1
-          if the key is not present.
-
-      hmget
-      hmget_ts
-      shget
-        TV hmget(T*, TK key)
-        TV shget(T*, char* key)
-        TV hmget_ts(T*, TK key, ptrdiff_t tempvar)
-          Returns the value corresponding to 'key' in the hashmap.
-          The structure must have a 'value' field
-
-      hmgets
-      shgets
-        T hmgets(T*, TK key)
-        T shgets(T*, char* key)
-          Returns the structure corresponding to 'key' in the hashmap.
-
-      hmgetp
-      shgetp
-      hmgetp_ts
-      hmgetp_null
-      shgetp_null
-        T* hmgetp(T*, TK key)
-        T* shgetp(T*, char* key)
-        T* hmgetp_ts(T*, TK key, ptrdiff_t tempvar)
-        T* hmgetp_null(T*, TK key)
-        T* shgetp_null(T*, char *key)
-          Returns a pointer to the structure corresponding to 'key' in
-          the hashmap. Functions ending in "_null" return NULL if the key
-          is not present in the hashmap; the others return a pointer to a
-          structure holding the default value (but not the searched-for key).
-
-      hmdefault
-      shdefault
-        TV hmdefault(T*, TV value)
-        TV shdefault(T*, TV value)
-          Sets the default value for the hashmap, the value which will be
-          returned by hmget/shget if the key is not present.
-
-      hmdefaults
-      shdefaults
-        TV hmdefaults(T*, T item)
-        TV shdefaults(T*, T item)
-          Sets the default struct for the hashmap, the contents which will be
-          returned by hmgets/shgets if the key is not present.
-
-      hmput
-      shput
-        TV hmput(T*, TK key, TV value)
-        TV shput(T*, char* key, TV value)
-          Inserts a <key,value> pair into the hashmap. If the key is already
-          present in the hashmap, updates its value.
-
-      hmputs
-      shputs
-        T hmputs(T*, T item)
-        T shputs(T*, T item)
-          Inserts a struct with T.key into the hashmap. If the struct is already
-          present in the hashmap, updates it.
-
-      hmdel
-      shdel
-        int hmdel(T*, TK key)
-        int shdel(T*, char* key)
-          If 'key' is in the hashmap, deletes its entry and returns 1.
-          Otherwise returns 0.
-
-    Function interface (actually macros) for strings only:
-
-      sh_new_strdup
-        void sh_new_strdup(T*);
-          Overwrites the existing pointer with a newly allocated
-          string hashmap which will automatically allocate and free
-          each string key using realloc/free
-
-      sh_new_arena
-        void sh_new_arena(T*);
-          Overwrites the existing pointer with a newly allocated
-          string hashmap which will automatically allocate each string
-          key to a string arena. Every string key ever used by this
-          hash table remains in the arena until the arena is freed.
-          Additionally, any key which is deleted and reinserted will
-          be allocated multiple times in the string arena.
-
-NOTES
-
-  * These data structures are realloc'd when they grow, and the macro
-    "functions" write to the provided pointer. This means: (a) the pointer
-    must be an lvalue, and (b) the pointer to the data structure is not
-    stable, and you must maintain it the same as you would a realloc'd
-    pointer. For example, if you pass a pointer to a dynamic array to a
-    function which updates it, the function must return back the new
-    pointer to the caller. This is the price of trying to do this in C.
-
-  * The following are the only functions that are thread-safe on a single data
-    structure, i.e. can be run in multiple threads simultaneously on the same
-    data structure
-        hmlen        shlen
-        hmlenu       shlenu
-        hmget_ts     shget_ts
-        hmgeti_ts    shgeti_ts
-        hmgets_ts    shgets_ts
-
-  * You iterate over the contents of a dynamic array and a hashmap in exactly
-    the same way, using arrlen/hmlen/shlen:
-
-      for (i=0; i < arrlen(foo); ++i)
-         ... foo[i] ...
-
-  * All operations except arrins/arrdel are O(1) amortized, but individual
-    operations can be slow, so these data structures may not be suitable
-    for real time use. Dynamic arrays double in capacity as needed, so
-    elements are copied an average of once. Hash tables double/halve
-    their size as needed, with appropriate hysteresis to maintain O(1)
-    performance.
-
-NOTES - DYNAMIC ARRAY
-
-  * If you know how long a dynamic array is going to be in advance, you can avoid
-    extra memory allocations by using arrsetlen to allocate it to that length in
-    advance and use foo[n] while filling it out, or arrsetcap to allocate the memory
-    for that length and use arrput/arrpush as normal.
-
-  * Unlike some other versions of the dynamic array, this version should
-    be safe to use with strict-aliasing optimizations.
-
-NOTES - HASH MAP
-
-  * For compilers other than GCC and clang (e.g. Visual Studio), for hmput/hmget/hmdel
-    and variants, the key must be an lvalue (so the macro can take the address of it).
-    Extensions are used that eliminate this requirement if you're using C99 and later
-    in GCC or clang, or if you're using C++ in GCC. But note that this can make your
-    code less portable.
-
-  * To test for presence of a key in a hashmap, just do 'hmgeti(foo,key) >= 0'.
-
-  * The iteration order of your data in the hashmap is determined solely by the
-    order of insertions and deletions. In particular, if you never delete, new
-    keys are always added at the end of the array. This will be consistent
-    across all platforms and versions of the library. However, you should not
-    attempt to serialize the internal hash table, as the hash is not consistent
-    between different platforms, and may change with future versions of the library.
-
-  * Use sh_new_arena() for string hashmaps that you never delete from. Initialize
-    with NULL if you're managing the memory for your strings, or your strings are
-    never freed (at least until the hashmap is freed). Otherwise, use sh_new_strdup().
-    @TODO: make an arena variant that garbage collects the strings with a trivial
-    copy collector into a new arena whenever the table shrinks / rebuilds. Since
-    current arena recommendation is to only use arena if it never deletes, then
-    this can just replace current arena implementation.
-
-  * If adversarial input is a serious concern and you're on a 64-bit platform,
-    enable STBDS_SIPHASH_2_4 (see the 'Compile-time options' section), and pass
-    a strong random number to stbds_rand_seed.
-
-  * The default value for the hash table is stored in foo[-1], so if you
-    use code like 'hmget(T,k)->value = 5' you can accidentally overwrite
-    the value stored by hmdefault if 'k' is not present.
-
-CREDITS
-
-  Sean Barrett -- library, idea for dynamic array API/implementation
-  Per Vognsen  -- idea for hash table API/implementation
-  Rafael Sachetto -- arrpop()
-  github:HeroicKatora -- arraddn() reworking
-
-  Bugfixes:
-    Andy Durdin
-    Shane Liesegang
-    Vinh Truong
-    Andreas Molzer
-    github:hashitaku
-    github:srdjanstipic
-    Macoy Madson
-    Andreas Vennstrom
-    Tobias Mansfield-Williams
-*/
-
-#ifdef STBDS_UNIT_TESTS
-#define _CRT_SECURE_NO_WARNINGS
-#endif
-
-#ifndef INCLUDE_STB_DS_H
-#define INCLUDE_STB_DS_H
-
-#include <stddef.h>
-#include <string.h>
-
-#ifndef STBDS_NO_SHORT_NAMES
-#define arrlen      stbds_arrlen
-#define arrlenu     stbds_arrlenu
-#define arrput      stbds_arrput
-#define arrpush     stbds_arrput
-#define arrpop      stbds_arrpop
-#define arrfree     stbds_arrfree
-#define arraddn     stbds_arraddn // deprecated, use one of the following instead:
-#define arraddnptr  stbds_arraddnptr
-#define arraddnindex stbds_arraddnindex
-#define arrsetlen   stbds_arrsetlen
-#define arrlast     stbds_arrlast
-#define arrins      stbds_arrins
-#define arrinsn     stbds_arrinsn
-#define arrdel      stbds_arrdel
-#define arrdeln     stbds_arrdeln
-#define arrdelswap  stbds_arrdelswap
-#define arrcap      stbds_arrcap
-#define arrsetcap   stbds_arrsetcap
-
-#define hmput       stbds_hmput
-#define hmputs      stbds_hmputs
-#define hmget       stbds_hmget
-#define hmget_ts    stbds_hmget_ts
-#define hmgets      stbds_hmgets
-#define hmgetp      stbds_hmgetp
-#define hmgetp_ts   stbds_hmgetp_ts
-#define hmgetp_null stbds_hmgetp_null
-#define hmgeti      stbds_hmgeti
-#define hmgeti_ts   stbds_hmgeti_ts
-#define hmdel       stbds_hmdel
-#define hmlen       stbds_hmlen
-#define hmlenu      stbds_hmlenu
-#define hmfree      stbds_hmfree
-#define hmdefault   stbds_hmdefault
-#define hmdefaults  stbds_hmdefaults
-
-#define shput       stbds_shput
-#define shputi      stbds_shputi
-#define shputs      stbds_shputs
-#define shget       stbds_shget
-#define shgeti      stbds_shgeti
-#define shgets      stbds_shgets
-#define shgetp      stbds_shgetp
-#define shgetp_null stbds_shgetp_null
-#define shdel       stbds_shdel
-#define shlen       stbds_shlen
-#define shlenu      stbds_shlenu
-#define shfree      stbds_shfree
-#define shdefault   stbds_shdefault
-#define shdefaults  stbds_shdefaults
-#define sh_new_arena  stbds_sh_new_arena
-#define sh_new_strdup stbds_sh_new_strdup
-
-#define stralloc    stbds_stralloc
-#define strreset    stbds_strreset
-#endif
-
-#if defined(STBDS_REALLOC) && !defined(STBDS_FREE) || !defined(STBDS_REALLOC) && defined(STBDS_FREE)
-#error "You must define both STBDS_REALLOC and STBDS_FREE, or neither."
-#endif
-#if !defined(STBDS_REALLOC) && !defined(STBDS_FREE)
-#include <stdlib.h>
-#define STBDS_REALLOC(c,p,s) realloc(p,s)
-#define STBDS_FREE(c,p)      free(p)
-#endif
-
-#ifdef _MSC_VER
-#define STBDS_NOTUSED(v)  (void)(v)
-#else
-#define STBDS_NOTUSED(v)  (void)sizeof(v)
-#endif
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-// for security against attackers, seed the library with a random number, at least time() but stronger is better
-extern void stbds_rand_seed(size_t seed);
-
-// these are the hash functions used internally if you want to test them or use them for other purposes
-extern size_t stbds_hash_bytes(void *p, size_t len, size_t seed);
-extern size_t stbds_hash_string(char *str, size_t seed);
-
-// this is a simple string arena allocator, initialize with e.g. 'stbds_string_arena my_arena={0}'.
-typedef struct stbds_string_arena stbds_string_arena;
-extern char * stbds_stralloc(stbds_string_arena *a, char *str);
-extern void   stbds_strreset(stbds_string_arena *a);
-
-// have to #define STBDS_UNIT_TESTS to call this
-extern void stbds_unit_tests(void);
-
-///////////////
-//
-// Everything below here is implementation details
-//
-
-extern void * stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap);
-extern void   stbds_arrfreef(void *a);
-extern void   stbds_hmfree_func(void *p, size_t elemsize);
-extern void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode);
-extern void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode);
-extern void * stbds_hmput_default(void *a, size_t elemsize);
-extern void * stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode);
-extern void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode);
-extern void * stbds_shmode_func(size_t elemsize, int mode);
-
-#ifdef __cplusplus
-}
-#endif
-
-#if defined(__GNUC__) || defined(__clang__)
-#define STBDS_HAS_TYPEOF
-#ifdef __cplusplus
-//#define STBDS_HAS_LITERAL_ARRAY  // this is currently broken for clang
-#endif
-#endif
-
-#if !defined(__cplusplus)
-#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
-#define STBDS_HAS_LITERAL_ARRAY
-#endif
-#endif
-
-// this macro takes the address of the argument, but on gcc/clang can accept rvalues
-#if defined(STBDS_HAS_LITERAL_ARRAY) && defined(STBDS_HAS_TYPEOF)
-  #if __clang__
-  #define STBDS_ADDRESSOF(typevar, value)     ((__typeof__(typevar)[1]){value}) // literal array decays to pointer to value
-  #else
-  #define STBDS_ADDRESSOF(typevar, value)     ((typeof(typevar)[1]){value}) // literal array decays to pointer to value
-  #endif
-#else
-#define STBDS_ADDRESSOF(typevar, value)     &(value)
-#endif
-
-#define STBDS_OFFSETOF(var,field)           ((char *) &(var)->field - (char *) (var))
-
-#define stbds_header(t)  ((stbds_array_header *) (t) - 1)
-#define stbds_temp(t)    stbds_header(t)->temp
-#define stbds_temp_key(t) (*(char **) stbds_header(t)->hash_table)
-
-#define stbds_arrsetcap(a,n)   (stbds_arrgrow(a,0,n))
-#define stbds_arrsetlen(a,n)   ((stbds_arrcap(a) < (size_t) (n) ? stbds_arrsetcap((a),(size_t)(n)),0 : 0), (a) ? stbds_header(a)->length = (size_t) (n) : 0)
-#define stbds_arrcap(a)        ((a) ? stbds_header(a)->capacity : 0)
-#define stbds_arrlen(a)        ((a) ? (ptrdiff_t) stbds_header(a)->length : 0)
-#define stbds_arrlenu(a)       ((a) ?             stbds_header(a)->length : 0)
-#define stbds_arrput(a,v)      (stbds_arrmaybegrow(a,1), (a)[stbds_header(a)->length++] = (v))
-#define stbds_arrpush          stbds_arrput  // synonym
-#define stbds_arrpop(a)        (stbds_header(a)->length--, (a)[stbds_header(a)->length])
-#define stbds_arraddn(a,n)     ((void)(stbds_arraddnindex(a, n)))    // deprecated, use one of the following instead:
-#define stbds_arraddnptr(a,n)  (stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), &(a)[stbds_header(a)->length-(n)]) : (a))
-#define stbds_arraddnindex(a,n)(stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), stbds_header(a)->length-(n)) : stbds_arrlen(a))
-#define stbds_arraddnoff       stbds_arraddnindex
-#define stbds_arrlast(a)       ((a)[stbds_header(a)->length-1])
-#define stbds_arrfree(a)       ((void) ((a) ? STBDS_FREE(NULL,stbds_header(a)) : (void)0), (a)=NULL)
-#define stbds_arrdel(a,i)      stbds_arrdeln(a,i,1)
-#define stbds_arrdeln(a,i,n)   (memmove(&(a)[i], &(a)[(i)+(n)], sizeof *(a) * (stbds_header(a)->length-(n)-(i))), stbds_header(a)->length -= (n))
-#define stbds_arrdelswap(a,i)  ((a)[i] = stbds_arrlast(a), stbds_header(a)->length -= 1)
-#define stbds_arrinsn(a,i,n)   (stbds_arraddn((a),(n)), memmove(&(a)[(i)+(n)], &(a)[i], sizeof *(a) * (stbds_header(a)->length-(n)-(i))))
-#define stbds_arrins(a,i,v)    (stbds_arrinsn((a),(i),1), (a)[i]=(v))
-
-#define stbds_arrmaybegrow(a,n)  ((!(a) || stbds_header(a)->length + (n) > stbds_header(a)->capacity) \
-                                  ? (stbds_arrgrow(a,n,0),0) : 0)
-
-#define stbds_arrgrow(a,b,c)   ((a) = stbds_arrgrowf_wrapper((a), sizeof *(a), (b), (c)))
-
-#define stbds_hmput(t, k, v) \
-    ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, 0),   \
-     (t)[stbds_temp((t)-1)].key = (k),    \
-     (t)[stbds_temp((t)-1)].value = (v))
-
-#define stbds_hmputs(t, s) \
-    ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), &(s).key, sizeof (s).key, STBDS_HM_BINARY), \
-     (t)[stbds_temp((t)-1)] = (s))
-
-#define stbds_hmgeti(t,k) \
-    ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_HM_BINARY), \
-      stbds_temp((t)-1))
-
-#define stbds_hmgeti_ts(t,k,temp) \
-    ((t) = stbds_hmget_key_ts_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, &(temp), STBDS_HM_BINARY), \
-      (temp))
-
-#define stbds_hmgetp(t, k) \
-    ((void) stbds_hmgeti(t,k), &(t)[stbds_temp((t)-1)])
-
-#define stbds_hmgetp_ts(t, k, temp) \
-    ((void) stbds_hmgeti_ts(t,k,temp), &(t)[temp])
-
-#define stbds_hmdel(t,k) \
-    (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_BINARY)),(t)?stbds_temp((t)-1):0)
-
-#define stbds_hmdefault(t, v) \
-    ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1].value = (v))
-
-#define stbds_hmdefaults(t, s) \
-    ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1] = (s))
-
-#define stbds_hmfree(p)        \
-    ((void) ((p) != NULL ? stbds_hmfree_func((p)-1,sizeof*(p)),0 : 0),(p)=NULL)
-
-#define stbds_hmgets(t, k)    (*stbds_hmgetp(t,k))
-#define stbds_hmget(t, k)     (stbds_hmgetp(t,k)->value)
-#define stbds_hmget_ts(t, k, temp)  (stbds_hmgetp_ts(t,k,temp)->value)
-#define stbds_hmlen(t)        ((t) ? (ptrdiff_t) stbds_header((t)-1)->length-1 : 0)
-#define stbds_hmlenu(t)       ((t) ?             stbds_header((t)-1)->length-1 : 0)
-#define stbds_hmgetp_null(t,k)  (stbds_hmgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)])
-
-#define stbds_shput(t, k, v) \
-    ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING),   \
-     (t)[stbds_temp((t)-1)].value = (v))
-
-#define stbds_shputi(t, k, v) \
-    ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING),   \
-     (t)[stbds_temp((t)-1)].value = (v), stbds_temp((t)-1))
-
-#define stbds_shputs(t, s) \
-    ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (s).key, sizeof (s).key, STBDS_HM_STRING), \
-     (t)[stbds_temp((t)-1)] = (s), \
-     (t)[stbds_temp((t)-1)].key = stbds_temp_key((t)-1)) // above line overwrites whole structure, so must rewrite key here if it was allocated internally
-
-#define stbds_pshput(t, p) \
-    ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (p)->key, sizeof (p)->key, STBDS_HM_PTR_TO_STRING), \
-     (t)[stbds_temp((t)-1)] = (p))
-
-#define stbds_shgeti(t,k) \
-     ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \
-      stbds_temp((t)-1))
-
-#define stbds_pshgeti(t,k) \
-     ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_HM_PTR_TO_STRING), \
-      stbds_temp((t)-1))
-
-#define stbds_shgetp(t, k) \
-    ((void) stbds_shgeti(t,k), &(t)[stbds_temp((t)-1)])
-
-#define stbds_pshget(t, k) \
-    ((void) stbds_pshgeti(t,k), (t)[stbds_temp((t)-1)])
-
-#define stbds_shdel(t,k) \
-    (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_STRING)),(t)?stbds_temp((t)-1):0)
-#define stbds_pshdel(t,k) \
-    (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_OFFSETOF(*(t),key), STBDS_HM_PTR_TO_STRING)),(t)?stbds_temp((t)-1):0)
-
-#define stbds_sh_new_arena(t)  \
-    ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_ARENA))
-#define stbds_sh_new_strdup(t) \
-    ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_STRDUP))
-
-#define stbds_shdefault(t, v)  stbds_hmdefault(t,v)
-#define stbds_shdefaults(t, s) stbds_hmdefaults(t,s)
-
-#define stbds_shfree       stbds_hmfree
-#define stbds_shlenu       stbds_hmlenu
-
-#define stbds_shgets(t, k) (*stbds_shgetp(t,k))
-#define stbds_shget(t, k)  (stbds_shgetp(t,k)->value)
-#define stbds_shgetp_null(t,k)  (stbds_shgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)])
-#define stbds_shlen        stbds_hmlen
-
-typedef struct
-{
-  size_t      length;
-  size_t      capacity;
-  void      * hash_table;
-  ptrdiff_t   temp;
-} stbds_array_header;
-
-typedef struct stbds_string_block
-{
-  struct stbds_string_block *next;
-  char storage[8];
-} stbds_string_block;
-
-struct stbds_string_arena
-{
-  stbds_string_block *storage;
-  size_t remaining;
-  unsigned char block;
-  unsigned char mode;  // this isn't used by the string arena itself
-};
-
-#define STBDS_HM_BINARY         0
-#define STBDS_HM_STRING         1
-
-enum
-{
-   STBDS_SH_NONE,
-   STBDS_SH_DEFAULT,
-   STBDS_SH_STRDUP,
-   STBDS_SH_ARENA
-};
-
-#ifdef __cplusplus
-// in C we use implicit assignment from these void*-returning functions to T*.
-// in C++ these templates make the same code work
-template<class T> static T * stbds_arrgrowf_wrapper(T *a, size_t elemsize, size_t addlen, size_t min_cap) {
-  return (T*)stbds_arrgrowf((void *)a, elemsize, addlen, min_cap);
-}
-template<class T> static T * stbds_hmget_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) {
-  return (T*)stbds_hmget_key((void*)a, elemsize, key, keysize, mode);
-}
-template<class T> static T * stbds_hmget_key_ts_wrapper(T *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) {
-  return (T*)stbds_hmget_key_ts((void*)a, elemsize, key, keysize, temp, mode);
-}
-template<class T> static T * stbds_hmput_default_wrapper(T *a, size_t elemsize) {
-  return (T*)stbds_hmput_default((void *)a, elemsize);
-}
-template<class T> static T * stbds_hmput_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) {
-  return (T*)stbds_hmput_key((void*)a, elemsize, key, keysize, mode);
-}
-template<class T> static T * stbds_hmdel_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode){
-  return (T*)stbds_hmdel_key((void*)a, elemsize, key, keysize, keyoffset, mode);
-}
-template<class T> static T * stbds_shmode_func_wrapper(T *, size_t elemsize, int mode) {
-  return (T*)stbds_shmode_func(elemsize, mode);
-}
-#else
-#define stbds_arrgrowf_wrapper            stbds_arrgrowf
-#define stbds_hmget_key_wrapper           stbds_hmget_key
-#define stbds_hmget_key_ts_wrapper        stbds_hmget_key_ts
-#define stbds_hmput_default_wrapper       stbds_hmput_default
-#define stbds_hmput_key_wrapper           stbds_hmput_key
-#define stbds_hmdel_key_wrapper           stbds_hmdel_key
-#define stbds_shmode_func_wrapper(t,e,m)  stbds_shmode_func(e,m)
-#endif
-
-#endif // INCLUDE_STB_DS_H
-
-
-//////////////////////////////////////////////////////////////////////////////
-//
-//   IMPLEMENTATION
-//
-
-#ifdef STB_DS_IMPLEMENTATION
-#include <assert.h>
-#include <string.h>
-
-#ifndef STBDS_ASSERT
-#define STBDS_ASSERT_WAS_UNDEFINED
-#define STBDS_ASSERT(x)   ((void) 0)
-#endif
-
-#ifdef STBDS_STATISTICS
-#define STBDS_STATS(x)   x
-size_t stbds_array_grow;
-size_t stbds_hash_grow;
-size_t stbds_hash_shrink;
-size_t stbds_hash_rebuild;
-size_t stbds_hash_probes;
-size_t stbds_hash_alloc;
-size_t stbds_rehash_probes;
-size_t stbds_rehash_items;
-#else
-#define STBDS_STATS(x)
-#endif
-
-//
-// stbds_arr implementation
-//
-
-//int *prev_allocs[65536];
-//int num_prev;
-
-void *stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap)
-{
-  stbds_array_header temp={0}; // force debugging
-  void *b;
-  size_t min_len = stbds_arrlen(a) + addlen;
-  (void) sizeof(temp);
-
-  // compute the minimum capacity needed
-  if (min_len > min_cap)
-    min_cap = min_len;
-
-  if (min_cap <= stbds_arrcap(a))
-    return a;
-
-  // increase needed capacity to guarantee O(1) amortized
-  if (min_cap < 2 * stbds_arrcap(a))
-    min_cap = 2 * stbds_arrcap(a);
-  else if (min_cap < 4)
-    min_cap = 4;
-
-  //if (num_prev < 65536) if (a) prev_allocs[num_prev++] = (int *) ((char *) a+1);
-  //if (num_prev == 2201)
-  //  num_prev = num_prev;
-  b = STBDS_REALLOC(NULL, (a) ? stbds_header(a) : 0, elemsize * min_cap + sizeof(stbds_array_header));
-  //if (num_prev < 65536) prev_allocs[num_prev++] = (int *) (char *) b;
-  b = (char *) b + sizeof(stbds_array_header);
-  if (a == NULL) {
-    stbds_header(b)->length = 0;
-    stbds_header(b)->hash_table = 0;
-    stbds_header(b)->temp = 0;
-  } else {
-    STBDS_STATS(++stbds_array_grow);
-  }
-  stbds_header(b)->capacity = min_cap;
-
-  return b;
-}
-
-void stbds_arrfreef(void *a)
-{
-  STBDS_FREE(NULL, stbds_header(a));
-}
-
-//
-// stbds_hm hash table implementation
-//
-
-#ifdef STBDS_INTERNAL_SMALL_BUCKET
-#define STBDS_BUCKET_LENGTH      4
-#else
-#define STBDS_BUCKET_LENGTH      8
-#endif
-
-#define STBDS_BUCKET_SHIFT      (STBDS_BUCKET_LENGTH == 8 ? 3 : 2)
-#define STBDS_BUCKET_MASK       (STBDS_BUCKET_LENGTH-1)
-#define STBDS_CACHE_LINE_SIZE   64
-
-#define STBDS_ALIGN_FWD(n,a)   (((n) + (a) - 1) & ~((a)-1))
-
-typedef struct
-{
-   size_t    hash [STBDS_BUCKET_LENGTH];
-   ptrdiff_t index[STBDS_BUCKET_LENGTH];
-} stbds_hash_bucket; // in 32-bit, this is one 64-byte cache line; in 64-bit, each array is one 64-byte cache line
-
-typedef struct
-{
-  char * temp_key; // this MUST be the first field of the hash table
-  size_t slot_count;
-  size_t used_count;
-  size_t used_count_threshold;
-  size_t used_count_shrink_threshold;
-  size_t tombstone_count;
-  size_t tombstone_count_threshold;
-  size_t seed;
-  size_t slot_count_log2;
-  stbds_string_arena string;
-  stbds_hash_bucket *storage; // not a separate allocation, just 64-byte aligned storage after this struct
-} stbds_hash_index;
-
-#define STBDS_INDEX_EMPTY    -1
-#define STBDS_INDEX_DELETED  -2
-#define STBDS_INDEX_IN_USE(x)  ((x) >= 0)
-
-#define STBDS_HASH_EMPTY      0
-#define STBDS_HASH_DELETED    1
-
-static size_t stbds_hash_seed=0x31415926;
-
-void stbds_rand_seed(size_t seed)
-{
-  stbds_hash_seed = seed;
-}
-
-#define stbds_load_32_or_64(var, temp, v32, v64_hi, v64_lo)                                          \
-  temp = v64_lo ^ v32, temp <<= 16, temp <<= 16, temp >>= 16, temp >>= 16, /* discard if 32-bit */   \
-  var = v64_hi, var <<= 16, var <<= 16,                                    /* discard if 32-bit */   \
-  var ^= temp ^ v32
-
-#define STBDS_SIZE_T_BITS           ((sizeof (size_t)) * 8)
-
-static size_t stbds_probe_position(size_t hash, size_t slot_count, size_t slot_log2)
-{
-  size_t pos;
-  STBDS_NOTUSED(slot_log2);
-  pos = hash & (slot_count-1);
-  #ifdef STBDS_INTERNAL_BUCKET_START
-  pos &= ~STBDS_BUCKET_MASK;
-  #endif
-  return pos;
-}
-
-static size_t stbds_log2(size_t slot_count)
-{
-  size_t n=0;
-  while (slot_count > 1) {
-    slot_count >>= 1;
-    ++n;
-  }
-  return n;
-}
-
-static stbds_hash_index *stbds_make_hash_index(size_t slot_count, stbds_hash_index *ot)
-{
-  stbds_hash_index *t;
-  t = (stbds_hash_index *) STBDS_REALLOC(NULL,0,(slot_count >> STBDS_BUCKET_SHIFT) * sizeof(stbds_hash_bucket) + sizeof(stbds_hash_index) + STBDS_CACHE_LINE_SIZE-1);
-  t->storage = (stbds_hash_bucket *) STBDS_ALIGN_FWD((size_t) (t+1), STBDS_CACHE_LINE_SIZE);
-  t->slot_count = slot_count;
-  t->slot_count_log2 = stbds_log2(slot_count);
-  t->tombstone_count = 0;
-  t->used_count = 0;
-
-  #if 0 // A1
-  t->used_count_threshold        = slot_count*12/16; // if 12/16th of table is occupied, grow
-  t->tombstone_count_threshold   = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild
-  t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink
-  #elif 1 // A2
-  //t->used_count_threshold        = slot_count*12/16; // if 12/16th of table is occupied, grow
-  //t->tombstone_count_threshold   = slot_count* 3/16; // if tombstones are 3/16th of table, rebuild
-  //t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink
-
-  // compute without overflowing
-  t->used_count_threshold        = slot_count - (slot_count>>2);
-  t->tombstone_count_threshold   = (slot_count>>3) + (slot_count>>4);
-  t->used_count_shrink_threshold = slot_count >> 2;
-
-  #elif 0 // B1
-  t->used_count_threshold        = slot_count*13/16; // if 13/16th of table is occupied, grow
-  t->tombstone_count_threshold   = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild
-  t->used_count_shrink_threshold = slot_count* 5/16; // if table is only 5/16th full, shrink
-  #else // C1
-  t->used_count_threshold        = slot_count*14/16; // if 14/16th of table is occupied, grow
-  t->tombstone_count_threshold   = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild
-  t->used_count_shrink_threshold = slot_count* 6/16; // if table is only 6/16th full, shrink
-  #endif
-  // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2
-    // Note that the larger tables have high variance as they were run fewer times
-  //     A1            A2          B1           C1
-  //    0.10ms :     0.10ms :     0.10ms :     0.11ms :      2,000 inserts creating 2K table
-  //    0.96ms :     0.95ms :     0.97ms :     1.04ms :     20,000 inserts creating 20K table
-  //   14.48ms :    14.46ms :    10.63ms :    11.00ms :    200,000 inserts creating 200K table
-  //  195.74ms :   196.35ms :   203.69ms :   214.92ms :  2,000,000 inserts creating 2M table
-  // 2193.88ms :  2209.22ms :  2285.54ms :  2437.17ms : 20,000,000 inserts creating 20M table
-  //   65.27ms :    53.77ms :    65.33ms :    65.47ms : 500,000 inserts & deletes in 2K table
-  //   72.78ms :    62.45ms :    71.95ms :    72.85ms : 500,000 inserts & deletes in 20K table
-  //   89.47ms :    77.72ms :    96.49ms :    96.75ms : 500,000 inserts & deletes in 200K table
-  //   97.58ms :    98.14ms :    97.18ms :    97.53ms : 500,000 inserts & deletes in 2M table
-  //  118.61ms :   119.62ms :   120.16ms :   118.86ms : 500,000 inserts & deletes in 20M table
-  //  192.11ms :   194.39ms :   196.38ms :   195.73ms : 500,000 inserts & deletes in 200M table
-
-  if (slot_count <= STBDS_BUCKET_LENGTH)
-    t->used_count_shrink_threshold = 0;
-  // to avoid infinite loop, we need to guarantee that at least one slot is empty and will terminate probes
-  STBDS_ASSERT(t->used_count_threshold + t->tombstone_count_threshold < t->slot_count);
-  STBDS_STATS(++stbds_hash_alloc);
-  if (ot) {
-    t->string = ot->string;
-    // reuse old seed so we can reuse old hashes so below "copy out old data" doesn't do any hashing
-    t->seed = ot->seed;
-  } else {
-    size_t a,b,temp;
-    memset(&t->string, 0, sizeof(t->string));
-    t->seed = stbds_hash_seed;
-    // LCG
-    // in 32-bit, a =          2147001325   b =  715136305
-    // in 64-bit, a = 2862933555777941757   b = 3037000493
-    stbds_load_32_or_64(a,temp, 2147001325, 0x27bb2ee6, 0x87b0b0fd);
-    stbds_load_32_or_64(b,temp,  715136305,          0, 0xb504f32d);
-    stbds_hash_seed = stbds_hash_seed  * a + b;
-  }
-
-  {
-    size_t i,j;
-    for (i=0; i < slot_count >> STBDS_BUCKET_SHIFT; ++i) {
-      stbds_hash_bucket *b = &t->storage[i];
-      for (j=0; j < STBDS_BUCKET_LENGTH; ++j)
-        b->hash[j] = STBDS_HASH_EMPTY;
-      for (j=0; j < STBDS_BUCKET_LENGTH; ++j)
-        b->index[j] = STBDS_INDEX_EMPTY;
-    }
-  }
-
-  // copy out the old data, if any
-  if (ot) {
-    size_t i,j;
-    t->used_count = ot->used_count;
-    for (i=0; i < ot->slot_count >> STBDS_BUCKET_SHIFT; ++i) {
-      stbds_hash_bucket *ob = &ot->storage[i];
-      for (j=0; j < STBDS_BUCKET_LENGTH; ++j) {
-        if (STBDS_INDEX_IN_USE(ob->index[j])) {
-          size_t hash = ob->hash[j];
-          size_t pos = stbds_probe_position(hash, t->slot_count, t->slot_count_log2);
-          size_t step = STBDS_BUCKET_LENGTH;
-          STBDS_STATS(++stbds_rehash_items);
-          for (;;) {
-            size_t limit,z;
-            stbds_hash_bucket *bucket;
-            bucket = &t->storage[pos >> STBDS_BUCKET_SHIFT];
-            STBDS_STATS(++stbds_rehash_probes);
-
-            for (z=pos & STBDS_BUCKET_MASK; z < STBDS_BUCKET_LENGTH; ++z) {
-              if (bucket->hash[z] == 0) {
-                bucket->hash[z] = hash;
-                bucket->index[z] = ob->index[j];
-                goto done;
-              }
-            }
-
-            limit = pos & STBDS_BUCKET_MASK;
-            for (z = 0; z < limit; ++z) {
-              if (bucket->hash[z] == 0) {
-                bucket->hash[z] = hash;
-                bucket->index[z] = ob->index[j];
-                goto done;
-              }
-            }
-
-            pos += step;                  // quadratic probing
-            step += STBDS_BUCKET_LENGTH;
-            pos &= (t->slot_count-1);
-          }
-        }
-       done:
-        ;
-      }
-    }
-  }
-
-  return t;
-}
-
-#define STBDS_ROTATE_LEFT(val, n)   (((val) << (n)) | ((val) >> (STBDS_SIZE_T_BITS - (n))))
-#define STBDS_ROTATE_RIGHT(val, n)  (((val) >> (n)) | ((val) << (STBDS_SIZE_T_BITS - (n))))
-
-size_t stbds_hash_string(char *str, size_t seed)
-{
-  size_t hash = seed;
-  while (*str)
-     hash = STBDS_ROTATE_LEFT(hash, 9) + (unsigned char) *str++;
-
-  // Thomas Wang 64-to-32 bit mix function, hopefully also works in 32 bits
-  hash ^= seed;
-  hash = (~hash) + (hash << 18);
-  hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,31);
-  hash = hash * 21;
-  hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,11);
-  hash += (hash << 6);
-  hash ^= STBDS_ROTATE_RIGHT(hash,22);
-  return hash+seed;
-}
-
-#ifdef STBDS_SIPHASH_2_4
-#define STBDS_SIPHASH_C_ROUNDS 2
-#define STBDS_SIPHASH_D_ROUNDS 4
-typedef int STBDS_SIPHASH_2_4_can_only_be_used_in_64_bit_builds[sizeof(size_t) == 8 ? 1 : -1];
-#endif
-
-#ifndef STBDS_SIPHASH_C_ROUNDS
-#define STBDS_SIPHASH_C_ROUNDS 1
-#endif
-#ifndef STBDS_SIPHASH_D_ROUNDS
-#define STBDS_SIPHASH_D_ROUNDS 1
-#endif
-
-#ifdef _MSC_VER
-#pragma warning(push)
-#pragma warning(disable:4127) // conditional expression is constant, for do..while(0) and sizeof()==
-#endif
-
-static size_t stbds_siphash_bytes(void *p, size_t len, size_t seed)
-{
-  unsigned char *d = (unsigned char *) p;
-  size_t i,j;
-  size_t v0,v1,v2,v3, data;
-
-  // hash that works on 32- or 64-bit registers without knowing which we have
-  // (computes different results on 32-bit and 64-bit platform)
-  // derived from siphash, but on 32-bit platforms very different as it uses 4 32-bit state not 4 64-bit
-  v0 = ((((size_t) 0x736f6d65 << 16) << 16) + 0x70736575) ^  seed;
-  v1 = ((((size_t) 0x646f7261 << 16) << 16) + 0x6e646f6d) ^ ~seed;
-  v2 = ((((size_t) 0x6c796765 << 16) << 16) + 0x6e657261) ^  seed;
-  v3 = ((((size_t) 0x74656462 << 16) << 16) + 0x79746573) ^ ~seed;
-
-  #ifdef STBDS_TEST_SIPHASH_2_4
-  // hardcoded with key material in the siphash test vectors
-  v0 ^= 0x0706050403020100ull ^  seed;
-  v1 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed;
-  v2 ^= 0x0706050403020100ull ^  seed;
-  v3 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed;
-  #endif
-
-  #define STBDS_SIPROUND() \
-    do {                   \
-      v0 += v1; v1 = STBDS_ROTATE_LEFT(v1, 13);  v1 ^= v0; v0 = STBDS_ROTATE_LEFT(v0,STBDS_SIZE_T_BITS/2); \
-      v2 += v3; v3 = STBDS_ROTATE_LEFT(v3, 16);  v3 ^= v2;                                                 \
-      v2 += v1; v1 = STBDS_ROTATE_LEFT(v1, 17);  v1 ^= v2; v2 = STBDS_ROTATE_LEFT(v2,STBDS_SIZE_T_BITS/2); \
-      v0 += v3; v3 = STBDS_ROTATE_LEFT(v3, 21);  v3 ^= v0;                                                 \
-    } while (0)
-
-  for (i=0; i+sizeof(size_t) <= len; i += sizeof(size_t), d += sizeof(size_t)) {
-    data = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24);
-    data |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // discarded if size_t == 4
-
-    v3 ^= data;
-    for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j)
-      STBDS_SIPROUND();
-    v0 ^= data;
-  }
-  data = len << (STBDS_SIZE_T_BITS-8);
-  switch (len - i) {
-    case 7: data |= ((size_t) d[6] << 24) << 24; // fall through
-    case 6: data |= ((size_t) d[5] << 20) << 20; // fall through
-    case 5: data |= ((size_t) d[4] << 16) << 16; // fall through
-    case 4: data |= (d[3] << 24); // fall through
-    case 3: data |= (d[2] << 16); // fall through
-    case 2: data |= (d[1] << 8); // fall through
-    case 1: data |= d[0]; // fall through
-    case 0: break;
-  }
-  v3 ^= data;
-  for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j)
-    STBDS_SIPROUND();
-  v0 ^= data;
-  v2 ^= 0xff;
-  for (j=0; j < STBDS_SIPHASH_D_ROUNDS; ++j)
-    STBDS_SIPROUND();
-
-#ifdef STBDS_SIPHASH_2_4
-  return v0^v1^v2^v3;
-#else
-  return v1^v2^v3; // slightly stronger since v0^v3 in above cancels out final round operation? I tweeted at the authors of SipHash about this but they didn't reply
-#endif
-}
-
-size_t stbds_hash_bytes(void *p, size_t len, size_t seed)
-{
-#ifdef STBDS_SIPHASH_2_4
-  return stbds_siphash_bytes(p,len,seed);
-#else
-  unsigned char *d = (unsigned char *) p;
-
-  if (len == 4) {
-    unsigned int hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24);
-    #if 0
-    // HASH32-A  Bob Jenkin's hash function w/o large constants
-    hash ^= seed;
-    hash -= (hash<<6);
-    hash ^= (hash>>17);
-    hash -= (hash<<9);
-    hash ^= seed;
-    hash ^= (hash<<4);
-    hash -= (hash<<3);
-    hash ^= (hash<<10);
-    hash ^= (hash>>15);
-    #elif 1
-    // HASH32-BB  Bob Jenkin's presumably-accidental version of Thomas Wang hash with rotates turned into shifts.
-    // Note that converting these back to rotates makes it run a lot slower, presumably due to collisions, so I'm
-    // not really sure what's going on.
-    hash ^= seed;
-    hash = (hash ^ 61) ^ (hash >> 16);
-    hash = hash + (hash << 3);
-    hash = hash ^ (hash >> 4);
-    hash = hash * 0x27d4eb2d;
-    hash ^= seed;
-    hash = hash ^ (hash >> 15);
-    #else  // HASH32-C   -  Murmur3
-    hash ^= seed;
-    hash *= 0xcc9e2d51;
-    hash = (hash << 17) | (hash >> 15);
-    hash *= 0x1b873593;
-    hash ^= seed;
-    hash = (hash << 19) | (hash >> 13);
-    hash = hash*5 + 0xe6546b64;
-    hash ^= hash >> 16;
-    hash *= 0x85ebca6b;
-    hash ^= seed;
-    hash ^= hash >> 13;
-    hash *= 0xc2b2ae35;
-    hash ^= hash >> 16;
-    #endif
-    // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2
-    // Note that the larger tables have high variance as they were run fewer times
-    //  HASH32-A   //  HASH32-BB  //  HASH32-C
-    //    0.10ms   //    0.10ms   //    0.10ms :      2,000 inserts creating 2K table
-    //    0.96ms   //    0.95ms   //    0.99ms :     20,000 inserts creating 20K table
-    //   14.69ms   //   14.43ms   //   14.97ms :    200,000 inserts creating 200K table
-    //  199.99ms   //  195.36ms   //  202.05ms :  2,000,000 inserts creating 2M table
-    // 2234.84ms   // 2187.74ms   // 2240.38ms : 20,000,000 inserts creating 20M table
-    //   55.68ms   //   53.72ms   //   57.31ms : 500,000 inserts & deletes in 2K table
-    //   63.43ms   //   61.99ms   //   65.73ms : 500,000 inserts & deletes in 20K table
-    //   80.04ms   //   77.96ms   //   81.83ms : 500,000 inserts & deletes in 200K table
-    //  100.42ms   //   97.40ms   //  102.39ms : 500,000 inserts & deletes in 2M table
-    //  119.71ms   //  120.59ms   //  121.63ms : 500,000 inserts & deletes in 20M table
-    //  185.28ms   //  195.15ms   //  187.74ms : 500,000 inserts & deletes in 200M table
-    //   15.58ms   //   14.79ms   //   15.52ms : 200,000 inserts creating 200K table with varying key spacing
-
-    return (((size_t) hash << 16 << 16) | hash) ^ seed;
-  } else if (len == 8 && sizeof(size_t) == 8) {
-    size_t hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24);
-    hash |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // avoid warning if size_t == 4
-    hash ^= seed;
-    hash = (~hash) + (hash << 21);
-    hash ^= STBDS_ROTATE_RIGHT(hash,24);
-    hash *= 265;
-    hash ^= STBDS_ROTATE_RIGHT(hash,14);
-    hash ^= seed;
-    hash *= 21;
-    hash ^= STBDS_ROTATE_RIGHT(hash,28);
-    hash += (hash << 31);
-    hash = (~hash) + (hash << 18);
-    return hash;
-  } else {
-    return stbds_siphash_bytes(p,len,seed);
-  }
-#endif
-}
-#ifdef _MSC_VER
-#pragma warning(pop)
-#endif
-
-
-static int stbds_is_key_equal(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode, size_t i)
-{
-  if (mode >= STBDS_HM_STRING)
-    return 0==strcmp((char *) key, * (char **) ((char *) a + elemsize*i + keyoffset));
-  else
-    return 0==memcmp(key, (char *) a + elemsize*i + keyoffset, keysize);
-}
-
-#define STBDS_HASH_TO_ARR(x,elemsize) ((char*) (x) - (elemsize))
-#define STBDS_ARR_TO_HASH(x,elemsize) ((char*) (x) + (elemsize))
-
-#define stbds_hash_table(a)  ((stbds_hash_index *) stbds_header(a)->hash_table)
-
-void stbds_hmfree_func(void *a, size_t elemsize)
-{
-  if (a == NULL) return;
-  if (stbds_hash_table(a) != NULL) {
-    if (stbds_hash_table(a)->string.mode == STBDS_SH_STRDUP) {
-      size_t i;
-      // skip 0th element, which is default
-      for (i=1; i < stbds_header(a)->length; ++i)
-        STBDS_FREE(NULL, *(char**) ((char *) a + elemsize*i));
-    }
-    stbds_strreset(&stbds_hash_table(a)->string);
-  }
-  STBDS_FREE(NULL, stbds_header(a)->hash_table);
-  STBDS_FREE(NULL, stbds_header(a));
-}
-
-static ptrdiff_t stbds_hm_find_slot(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode)
-{
-  void *raw_a = STBDS_HASH_TO_ARR(a,elemsize);
-  stbds_hash_index *table = stbds_hash_table(raw_a);
-  size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed);
-  size_t step = STBDS_BUCKET_LENGTH;
-  size_t limit,i;
-  size_t pos;
-  stbds_hash_bucket *bucket;
-
-  if (hash < 2) hash += 2; // stored hash values are forbidden from being 0, so we can detect empty slots
-
-  pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2);
-
-  for (;;) {
-    STBDS_STATS(++stbds_hash_probes);
-    bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT];
-
-    // start searching from pos to end of bucket, this should help performance on small hash tables that fit in cache
-    for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) {
-      if (bucket->hash[i] == hash) {
-        if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) {
-          return (pos & ~STBDS_BUCKET_MASK)+i;
-        }
-      } else if (bucket->hash[i] == STBDS_HASH_EMPTY) {
-        return -1;
-      }
-    }
-
-    // search from beginning of bucket to pos
-    limit = pos & STBDS_BUCKET_MASK;
-    for (i = 0; i < limit; ++i) {
-      if (bucket->hash[i] == hash) {
-        if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) {
-          return (pos & ~STBDS_BUCKET_MASK)+i;
-        }
-      } else if (bucket->hash[i] == STBDS_HASH_EMPTY) {
-        return -1;
-      }
-    }
-
-    // quadratic probing
-    pos += step;
-    step += STBDS_BUCKET_LENGTH;
-    pos &= (table->slot_count-1);
-  }
-  /* NOTREACHED */
-}
-
-void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode)
-{
-  size_t keyoffset = 0;
-  if (a == NULL) {
-    // make it non-empty so we can return a temp
-    a = stbds_arrgrowf(0, elemsize, 0, 1);
-    stbds_header(a)->length += 1;
-    memset(a, 0, elemsize);
-    *temp = STBDS_INDEX_EMPTY;
-    // adjust a to point after the default element
-    return STBDS_ARR_TO_HASH(a,elemsize);
-  } else {
-    stbds_hash_index *table;
-    void *raw_a = STBDS_HASH_TO_ARR(a,elemsize);
-    // adjust a to point to the default element
-    table = (stbds_hash_index *) stbds_header(raw_a)->hash_table;
-    if (table == 0) {
-      *temp = -1;
-    } else {
-      ptrdiff_t slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode);
-      if (slot < 0) {
-        *temp = STBDS_INDEX_EMPTY;
-      } else {
-        stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT];
-        *temp = b->index[slot & STBDS_BUCKET_MASK];
-      }
-    }
-    return a;
-  }
-}
-
-void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode)
-{
-  ptrdiff_t temp;
-  void *p = stbds_hmget_key_ts(a, elemsize, key, keysize, &temp, mode);
-  stbds_temp(STBDS_HASH_TO_ARR(p,elemsize)) = temp;
-  return p;
-}
-
-void * stbds_hmput_default(void *a, size_t elemsize)
-{
-  // three cases:
-  //   a is NULL <- allocate
-  //   a has a hash table but no entries, because of shmode <- grow
-  //   a has entries <- do nothing
-  if (a == NULL || stbds_header(STBDS_HASH_TO_ARR(a,elemsize))->length == 0) {
-    a = stbds_arrgrowf(a ? STBDS_HASH_TO_ARR(a,elemsize) : NULL, elemsize, 0, 1);
-    stbds_header(a)->length += 1;
-    memset(a, 0, elemsize);
-    a=STBDS_ARR_TO_HASH(a,elemsize);
-  }
-  return a;
-}
-
-static char *stbds_strdup(char *str);
-
-void *stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode)
-{
-  size_t keyoffset=0;
-  void *raw_a;
-  stbds_hash_index *table;
-
-  if (a == NULL) {
-    a = stbds_arrgrowf(0, elemsize, 0, 1);
-    memset(a, 0, elemsize);
-    stbds_header(a)->length += 1;
-    // adjust a to point AFTER the default element
-    a = STBDS_ARR_TO_HASH(a,elemsize);
-  }
-
-  // adjust a to point to the default element
-  raw_a = a;
-  a = STBDS_HASH_TO_ARR(a,elemsize);
-
-  table = (stbds_hash_index *) stbds_header(a)->hash_table;
-
-  if (table == NULL || table->used_count >= table->used_count_threshold) {
-    stbds_hash_index *nt;
-    size_t slot_count;
-
-    slot_count = (table == NULL) ? STBDS_BUCKET_LENGTH : table->slot_count*2;
-    nt = stbds_make_hash_index(slot_count, table);
-    if (table)
-      STBDS_FREE(NULL, table);
-    else
-      nt->string.mode = mode >= STBDS_HM_STRING ? STBDS_SH_DEFAULT : 0;
-    stbds_header(a)->hash_table = table = nt;
-    STBDS_STATS(++stbds_hash_grow);
-  }
-
-  // we iterate hash table explicitly because we want to track if we saw a tombstone
-  {
-    size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed);
-    size_t step = STBDS_BUCKET_LENGTH;
-    size_t pos;
-    ptrdiff_t tombstone = -1;
-    stbds_hash_bucket *bucket;
-
-    // stored hash values are forbidden from being 0, so we can detect empty slots to early out quickly
-    if (hash < 2) hash += 2;
-
-    pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2);
-
-    for (;;) {
-      size_t limit, i;
-      STBDS_STATS(++stbds_hash_probes);
-      bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT];
-
-      // start searching from pos to end of bucket
-      for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) {
-        if (bucket->hash[i] == hash) {
-          if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) {
-            stbds_temp(a) = bucket->index[i];
-            if (mode >= STBDS_HM_STRING)
-              stbds_temp_key(a) = * (char **) ((char *) raw_a + elemsize*bucket->index[i] + keyoffset);
-            return STBDS_ARR_TO_HASH(a,elemsize);
-          }
-        } else if (bucket->hash[i] == 0) {
-          pos = (pos & ~STBDS_BUCKET_MASK) + i;
-          goto found_empty_slot;
-        } else if (tombstone < 0) {
-          if (bucket->index[i] == STBDS_INDEX_DELETED)
-            tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i);
-        }
-      }
-
-      // search from beginning of bucket to pos
-      limit = pos & STBDS_BUCKET_MASK;
-      for (i = 0; i < limit; ++i) {
-        if (bucket->hash[i] == hash) {
-          if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) {
-            stbds_temp(a) = bucket->index[i];
-            return STBDS_ARR_TO_HASH(a,elemsize);
-          }
-        } else if (bucket->hash[i] == 0) {
-          pos = (pos & ~STBDS_BUCKET_MASK) + i;
-          goto found_empty_slot;
-        } else if (tombstone < 0) {
-          if (bucket->index[i] == STBDS_INDEX_DELETED)
-            tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i);
-        }
-      }
-
-      // quadratic probing
-      pos += step;
-      step += STBDS_BUCKET_LENGTH;
-      pos &= (table->slot_count-1);
-    }
-   found_empty_slot:
-    if (tombstone >= 0) {
-      pos = tombstone;
-      --table->tombstone_count;
-    }
-    ++table->used_count;
-
-    {
-      ptrdiff_t i = (ptrdiff_t) stbds_arrlen(a);
-      // we want to do stbds_arraddn(1), but we can't use the macros since we don't have something of the right type
-      if ((size_t) i+1 > stbds_arrcap(a))
-        *(void **) &a = stbds_arrgrowf(a, elemsize, 1, 0);
-      raw_a = STBDS_ARR_TO_HASH(a,elemsize);
-
-      STBDS_ASSERT((size_t) i+1 <= stbds_arrcap(a));
-      stbds_header(a)->length = i+1;
-      bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT];
-      bucket->hash[pos & STBDS_BUCKET_MASK] = hash;
-      bucket->index[pos & STBDS_BUCKET_MASK] = i-1;
-      stbds_temp(a) = i-1;
-
-      switch (table->string.mode) {
-         case STBDS_SH_STRDUP:  stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_strdup((char*) key); break;
-         case STBDS_SH_ARENA:   stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_stralloc(&table->string, (char*)key); break;
-         case STBDS_SH_DEFAULT: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = (char *) key; break;
-         default:                memcpy((char *) a + elemsize*i, key, keysize); break;
-      }
-    }
-    return STBDS_ARR_TO_HASH(a,elemsize);
-  }
-}
-
-void * stbds_shmode_func(size_t elemsize, int mode)
-{
-  void *a = stbds_arrgrowf(0, elemsize, 0, 1);
-  stbds_hash_index *h;
-  memset(a, 0, elemsize);
-  stbds_header(a)->length = 1;
-  stbds_header(a)->hash_table = h = (stbds_hash_index *) stbds_make_hash_index(STBDS_BUCKET_LENGTH, NULL);
-  h->string.mode = (unsigned char) mode;
-  return STBDS_ARR_TO_HASH(a,elemsize);
-}
-
-void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode)
-{
-  if (a == NULL) {
-    return 0;
-  } else {
-    stbds_hash_index *table;
-    void *raw_a = STBDS_HASH_TO_ARR(a,elemsize);
-    table = (stbds_hash_index *) stbds_header(raw_a)->hash_table;
-    stbds_temp(raw_a) = 0;
-    if (table == 0) {
-      return a;
-    } else {
-      ptrdiff_t slot;
-      slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode);
-      if (slot < 0)
-        return a;
-      else {
-        stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT];
-        int i = slot & STBDS_BUCKET_MASK;
-        ptrdiff_t old_index = b->index[i];
-        ptrdiff_t final_index = (ptrdiff_t) stbds_arrlen(raw_a)-1-1; // minus one for the raw_a vs a, and minus one for 'last'
-        STBDS_ASSERT(slot < (ptrdiff_t) table->slot_count);
-        --table->used_count;
-        ++table->tombstone_count;
-        stbds_temp(raw_a) = 1;
-        STBDS_ASSERT(table->used_count >= 0);
-        //STBDS_ASSERT(table->tombstone_count < table->slot_count/4);
-        b->hash[i] = STBDS_HASH_DELETED;
-        b->index[i] = STBDS_INDEX_DELETED;
-
-        if (mode == STBDS_HM_STRING && table->string.mode == STBDS_SH_STRDUP)
-          STBDS_FREE(NULL, *(char**) ((char *) a+elemsize*old_index));
-
-        // if indices are the same, memcpy is a no-op, but back-pointer-fixup will fail, so skip
-        if (old_index != final_index) {
-          // swap delete
-          memmove((char*) a + elemsize*old_index, (char*) a + elemsize*final_index, elemsize);
-
-          // now find the slot for the last element
-          if (mode == STBDS_HM_STRING)
-            slot = stbds_hm_find_slot(a, elemsize, *(char**) ((char *) a+elemsize*old_index + keyoffset), keysize, keyoffset, mode);
-          else
-            slot = stbds_hm_find_slot(a, elemsize,  (char* ) a+elemsize*old_index + keyoffset, keysize, keyoffset, mode);
-          STBDS_ASSERT(slot >= 0);
-          b = &table->storage[slot >> STBDS_BUCKET_SHIFT];
-          i = slot & STBDS_BUCKET_MASK;
-          STBDS_ASSERT(b->index[i] == final_index);
-          b->index[i] = old_index;
-        }
-        stbds_header(raw_a)->length -= 1;
-
-        if (table->used_count < table->used_count_shrink_threshold && table->slot_count > STBDS_BUCKET_LENGTH) {
-          stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count>>1, table);
-          STBDS_FREE(NULL, table);
-          STBDS_STATS(++stbds_hash_shrink);
-        } else if (table->tombstone_count > table->tombstone_count_threshold) {
-          stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count   , table);
-          STBDS_FREE(NULL, table);
-          STBDS_STATS(++stbds_hash_rebuild);
-        }
-
-        return a;
-      }
-    }
-  }
-  /* NOTREACHED */
-}
-
-static char *stbds_strdup(char *str)
-{
-  // to keep replaceable allocator simple, we don't want to use strdup.
-  // rolling our own also avoids problem of strdup vs _strdup
-  size_t len = strlen(str)+1;
-  char *p = (char*) STBDS_REALLOC(NULL, 0, len);
-  memmove(p, str, len);
-  return p;
-}
-
-#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MIN
-#define STBDS_STRING_ARENA_BLOCKSIZE_MIN  512u
-#endif
-#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MAX
-#define STBDS_STRING_ARENA_BLOCKSIZE_MAX  (1u<<20)
-#endif
-
-char *stbds_stralloc(stbds_string_arena *a, char *str)
-{
-  char *p;
-  size_t len = strlen(str)+1;
-  if (len > a->remaining) {
-    // compute the next blocksize
-    size_t blocksize = a->block;
-
-    // size is 512, 512, 1024, 1024, 2048, 2048, 4096, 4096, etc., so that
-    // there are log(SIZE) allocations to free when we destroy the table
-    blocksize = (size_t) (STBDS_STRING_ARENA_BLOCKSIZE_MIN) << (blocksize>>1);
-
-    // if size is under 1M, advance to next blocktype
-    if (blocksize < (size_t)(STBDS_STRING_ARENA_BLOCKSIZE_MAX))
-      ++a->block;
-
-    if (len > blocksize) {
-      // if string is larger than blocksize, then just allocate the full size.
-      // note that we still advance string_block so block size will continue
-      // increasing, so e.g. if somebody only calls this with 1000-long strings,
-      // eventually the arena will start doubling and handling those as well
-      stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + len);
-      memmove(sb->storage, str, len);
-      if (a->storage) {
-        // insert it after the first element, so that we don't waste the space there
-        sb->next = a->storage->next;
-        a->storage->next = sb;
-      } else {
-        sb->next = 0;
-        a->storage = sb;
-        a->remaining = 0; // this is redundant, but good for clarity
-      }
-      return sb->storage;
-    } else {
-      stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + blocksize);
-      sb->next = a->storage;
-      a->storage = sb;
-      a->remaining = blocksize;
-    }
-  }
-
-  STBDS_ASSERT(len <= a->remaining);
-  p = a->storage->storage + a->remaining - len;
-  a->remaining -= len;
-  memmove(p, str, len);
-  return p;
-}
-
-void stbds_strreset(stbds_string_arena *a)
-{
-  stbds_string_block *x,*y;
-  x = a->storage;
-  while (x) {
-    y = x->next;
-    STBDS_FREE(NULL, x);
-    x = y;
-  }
-  memset(a, 0, sizeof(*a));
-}
-
-#endif
-
-//////////////////////////////////////////////////////////////////////////////
-//
-//   UNIT TESTS
-//
-
-#ifdef STBDS_UNIT_TESTS
-#include <stdio.h>
-#ifdef STBDS_ASSERT_WAS_UNDEFINED
-#undef STBDS_ASSERT
-#endif
-#ifndef STBDS_ASSERT
-#define STBDS_ASSERT assert
-#include <assert.h>
-#endif
-
-typedef struct { int key,b,c,d; } stbds_struct;
-typedef struct { int key[2],b,c,d; } stbds_struct2;
-
-static char buffer[256];
-char *strkey(int n)
-{
-#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__)
-   sprintf_s(buffer, sizeof(buffer), "test_%d", n);
-#else
-   sprintf(buffer, "test_%d", n);
-#endif
-   return buffer;
-}
-
-void stbds_unit_tests(void)
-{
-#if defined(_MSC_VER) && _MSC_VER <= 1200 && defined(__cplusplus)
-  // VC6 C++ doesn't like the template<> trick on unnamed structures, so do nothing!
-  STBDS_ASSERT(0);
-#else
-  const int testsize = 100000;
-  const int testsize2 = testsize/20;
-  int *arr=NULL;
-  struct { int   key;        int value; }  *intmap  = NULL;
-  struct { char *key;        int value; }  *strmap  = NULL, s;
-  struct { stbds_struct key; int value; }  *map     = NULL;
-  stbds_struct                             *map2    = NULL;
-  stbds_struct2                            *map3    = NULL;
-  stbds_string_arena                        sa      = { 0 };
-  int key3[2] = { 1,2 };
-  ptrdiff_t temp;
-
-  int i,j;
-
-  STBDS_ASSERT(arrlen(arr)==0);
-  for (i=0; i < 20000; i += 50) {
-    for (j=0; j < i; ++j)
-      arrpush(arr,j);
-    arrfree(arr);
-  }
-
-  for (i=0; i < 4; ++i) {
-    arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4);
-    arrdel(arr,i);
-    arrfree(arr);
-    arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4);
-    arrdelswap(arr,i);
-    arrfree(arr);
-  }
-
-  for (i=0; i < 5; ++i) {
-    arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4);
-    stbds_arrins(arr,i,5);
-    STBDS_ASSERT(arr[i] == 5);
-    if (i < 4)
-      STBDS_ASSERT(arr[4] == 4);
-    arrfree(arr);
-  }
-
-  i = 1;
-  STBDS_ASSERT(hmgeti(intmap,i) == -1);
-  hmdefault(intmap, -2);
-  STBDS_ASSERT(hmgeti(intmap, i) == -1);
-  STBDS_ASSERT(hmget (intmap, i) == -2);
-  for (i=0; i < testsize; i+=2)
-    hmput(intmap, i, i*5);
-  for (i=0; i < testsize; i+=1) {
-    if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 );
-    else       STBDS_ASSERT(hmget(intmap, i) == i*5);
-    if (i & 1) STBDS_ASSERT(hmget_ts(intmap, i, temp) == -2 );
-    else       STBDS_ASSERT(hmget_ts(intmap, i, temp) == i*5);
-  }
-  for (i=0; i < testsize; i+=2)
-    hmput(intmap, i, i*3);
-  for (i=0; i < testsize; i+=1)
-    if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 );
-    else       STBDS_ASSERT(hmget(intmap, i) == i*3);
-  for (i=2; i < testsize; i+=4)
-    hmdel(intmap, i); // delete half the entries
-  for (i=0; i < testsize; i+=1)
-    if (i & 3) STBDS_ASSERT(hmget(intmap, i) == -2 );
-    else       STBDS_ASSERT(hmget(intmap, i) == i*3);
-  for (i=0; i < testsize; i+=1)
-    hmdel(intmap, i); // delete the rest of the entries
-  for (i=0; i < testsize; i+=1)
-    STBDS_ASSERT(hmget(intmap, i) == -2 );
-  hmfree(intmap);
-  for (i=0; i < testsize; i+=2)
-    hmput(intmap, i, i*3);
-  hmfree(intmap);
-
-  #if defined(__clang__) || defined(__GNUC__)
-  #ifndef __cplusplus
-  intmap = NULL;
-  hmput(intmap, 15, 7);
-  hmput(intmap, 11, 3);
-  hmput(intmap,  9, 5);
-  STBDS_ASSERT(hmget(intmap, 9) == 5);
-  STBDS_ASSERT(hmget(intmap, 11) == 3);
-  STBDS_ASSERT(hmget(intmap, 15) == 7);
-  #endif
-  #endif
-
-  for (i=0; i < testsize; ++i)
-    stralloc(&sa, strkey(i));
-  strreset(&sa);
-
-  {
-    s.key = "a", s.value = 1;
-    shputs(strmap, s);
-    STBDS_ASSERT(*strmap[0].key == 'a');
-    STBDS_ASSERT(strmap[0].key == s.key);
-    STBDS_ASSERT(strmap[0].value == s.value);
-    shfree(strmap);
-  }
-
-  {
-    s.key = "a", s.value = 1;
-    sh_new_strdup(strmap);
-    shputs(strmap, s);
-    STBDS_ASSERT(*strmap[0].key == 'a');
-    STBDS_ASSERT(strmap[0].key != s.key);
-    STBDS_ASSERT(strmap[0].value == s.value);
-    shfree(strmap);
-  }
-
-  {
-    s.key = "a", s.value = 1;
-    sh_new_arena(strmap);
-    shputs(strmap, s);
-    STBDS_ASSERT(*strmap[0].key == 'a');
-    STBDS_ASSERT(strmap[0].key != s.key);
-    STBDS_ASSERT(strmap[0].value == s.value);
-    shfree(strmap);
-  }
-
-  for (j=0; j < 2; ++j) {
-    STBDS_ASSERT(shgeti(strmap,"foo") == -1);
-    if (j == 0)
-      sh_new_strdup(strmap);
-    else
-      sh_new_arena(strmap);
-    STBDS_ASSERT(shgeti(strmap,"foo") == -1);
-    shdefault(strmap, -2);
-    STBDS_ASSERT(shgeti(strmap,"foo") == -1);
-    for (i=0; i < testsize; i+=2)
-      shput(strmap, strkey(i), i*3);
-    for (i=0; i < testsize; i+=1)
-      if (i & 1) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 );
-      else       STBDS_ASSERT(shget(strmap, strkey(i)) == i*3);
-    for (i=2; i < testsize; i+=4)
-      shdel(strmap, strkey(i)); // delete half the entries
-    for (i=0; i < testsize; i+=1)
-      if (i & 3) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 );
-      else       STBDS_ASSERT(shget(strmap, strkey(i)) == i*3);
-    for (i=0; i < testsize; i+=1)
-      shdel(strmap, strkey(i)); // delete the rest of the entries
-    for (i=0; i < testsize; i+=1)
-      STBDS_ASSERT(shget(strmap, strkey(i)) == -2 );
-    shfree(strmap);
-  }
-
-  {
-    struct { char *key; char value; } *hash = NULL;
-    char name[4] = "jen";
-    shput(hash, "bob"   , 'h');
-    shput(hash, "sally" , 'e');
-    shput(hash, "fred"  , 'l');
-    shput(hash, "jen"   , 'x');
-    shput(hash, "doug"  , 'o');
-
-    shput(hash, name    , 'l');
-    shfree(hash);
-  }
-
-  for (i=0; i < testsize; i += 2) {
-    stbds_struct s = { i,i*2,i*3,i*4 };
-    hmput(map, s, i*5);
-  }
-
-  for (i=0; i < testsize; i += 1) {
-    stbds_struct s = { i,i*2,i*3  ,i*4 };
-    stbds_struct t = { i,i*2,i*3+1,i*4 };
-    if (i & 1) STBDS_ASSERT(hmget(map, s) == 0);
-    else       STBDS_ASSERT(hmget(map, s) == i*5);
-    if (i & 1) STBDS_ASSERT(hmget_ts(map, s, temp) == 0);
-    else       STBDS_ASSERT(hmget_ts(map, s, temp) == i*5);
-    //STBDS_ASSERT(hmget(map, t.key) == 0);
-  }
-
-  for (i=0; i < testsize; i += 2) {
-    stbds_struct s = { i,i*2,i*3,i*4 };
-    hmputs(map2, s);
-  }
-  hmfree(map);
-
-  for (i=0; i < testsize; i += 1) {
-    stbds_struct s = { i,i*2,i*3,i*4 };
-    stbds_struct t = { i,i*2,i*3+1,i*4 };
-    if (i & 1) STBDS_ASSERT(hmgets(map2, s.key).d == 0);
-    else       STBDS_ASSERT(hmgets(map2, s.key).d == i*4);
-    //STBDS_ASSERT(hmgetp(map2, t.key) == 0);
-  }
-  hmfree(map2);
-
-  for (i=0; i < testsize; i += 2) {
-    stbds_struct2 s = { { i,i*2 }, i*3,i*4, i*5 };
-    hmputs(map3, s);
-  }
-  for (i=0; i < testsize; i += 1) {
-    stbds_struct2 s = { { i,i*2}, i*3, i*4, i*5 };
-    stbds_struct2 t = { { i,i*2}, i*3+1, i*4, i*5 };
-    if (i & 1) STBDS_ASSERT(hmgets(map3, s.key).d == 0);
-    else       STBDS_ASSERT(hmgets(map3, s.key).d == i*5);
-    //STBDS_ASSERT(hmgetp(map3, t.key) == 0);
-  }
-#endif
-}
-#endif
-
-
-/*
-------------------------------------------------------------------------------
-This software is available under 2 licenses -- choose whichever you prefer.
-------------------------------------------------------------------------------
-ALTERNATIVE A - MIT License
-Copyright (c) 2019 Sean Barrett
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
-so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-------------------------------------------------------------------------------
-ALTERNATIVE B - Public Domain (www.unlicense.org)
-This is free and unencumbered software released into the public domain.
-Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
-software, either in source code form or as a compiled binary, for any purpose,
-commercial or non-commercial, and by any means.
-In jurisdictions that recognize copyright laws, the author or authors of this
-software dedicate any and all copyright interest in the software to the public
-domain. We make this dedication for the benefit of the public at large and to
-the detriment of our heirs and successors. We intend this dedication to be an
-overt act of relinquishment in perpetuity of all present and future rights to
-this software under copyright law.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-------------------------------------------------------------------------------
-*/
diff --git a/src/main.c b/src/main.c
deleted file mode 100644 (file)
index 055fcca..0000000
+++ /dev/null
@@ -1,451 +0,0 @@
-#include <stdint.h>\r
-#include <mongoose.h>\r
-#define STB_DS_IMPLEMENTATION\r
-#include <stb_ds.h>\r
-\r
-\r
-#define NAME_LEN 32\r
-#define CONTENT_LEN 1024\r
-#define HTML_LEN 1024*8\r
-\r
-typedef struct Post Post;\r
-typedef struct User User;\r
-\r
-User * UsersFind(struct mg_str name);\r
-\r
-// Post / User\r
-\r
-struct Post {\r
-  time_t timestamp;\r
-  User * user;\r
-  char content[CONTENT_LEN];\r
-  uint32_t likes;\r
-  Post * comments;\r
-  //Post * parent;\r
-};\r
-\r
-struct User {\r
-  char name[NAME_LEN];\r
-  Post * posts;\r
-  User ** following;\r
-};\r
-\r
-Post\r
-PostNew() {\r
-  Post result;\r
-  memset(&result, 0, sizeof(result));\r
-  return result;\r
-}\r
-\r
-void\r
-PostWrite(Post * post, FILE * f) {\r
-  fwrite(&post->timestamp, 8, 1, f);\r
-  fwrite(post->user->name, 1, sizeof(post->user->name), f);\r
-  fwrite(post->content, 1, sizeof(post->content), f);\r
-  fwrite(&post->likes, 4, 1, f);\r
-  uint32_t numComments = arrlen(post->comments);\r
-  fwrite(&numComments, 4, 1, f);\r
-  for (int i = 0; i < numComments; i++) {\r
-    PostWrite(&post->comments[i], f);\r
-  }\r
-}\r
-\r
-void\r
-PostRead(Post * post, FILE * f) {\r
-  fread(&post->timestamp, 8, 1, f);\r
-  char name[NAME_LEN];\r
-  fread(name, 1, sizeof(name), f);\r
-  post->user = UsersFind(mg_str(name));\r
-  fread(post->content, 1, sizeof(post->content), f);\r
-  fread(&post->likes, 4, 1, f);\r
-  uint32_t numComments;\r
-  fread(&numComments, 4, 1, f);\r
-  post->comments = NULL;\r
-  for (int i = 0; i < numComments; i++) {\r
-    Post newComment = PostNew();\r
-    PostRead(&newComment, f);\r
-    arrput(post->comments, newComment);\r
-  }\r
-}\r
-\r
-User\r
-UserNew(const char * name) {\r
-  User result;\r
-  memset(&result, 0, sizeof(result));\r
-  strcpy(result.name, name);\r
-  return result;\r
-}\r
-\r
-void\r
-UserWrite(User * user, FILE * f) {\r
-  fwrite(user->name, 1, sizeof(user->name), f);\r
-\r
-  uint32_t numFollowing = arrlen(user->following);\r
-  uint32_t numPosts = arrlen(user->posts);\r
-  fwrite(&numFollowing, 4, 1, f);\r
-  fwrite(&numPosts, 4, 1, f);\r
-\r
-  for (int i = 0; i < numFollowing; i++) {\r
-    fwrite(user->following[i]->name, 1, sizeof(user->following[i]->name), f);\r
-  }\r
-\r
-  for (int i = 0; i < numPosts; i++) {\r
-    PostWrite(&user->posts[i], f);\r
-  }\r
-}\r
-\r
-void\r
-UserRead(User * user, FILE * f) {\r
-  fread(user->name, 1, sizeof(user->name), f);\r
-\r
-  uint32_t numFollowing;\r
-  uint32_t numPosts;\r
-  fread(&numFollowing, 1, 4, f);\r
-  fread(&numPosts, 1, 4, f);\r
-\r
-  user->following = NULL;\r
-  for (int i = 0; i < numFollowing; i++) {\r
-    char name[NAME_LEN];\r
-    fread(name, 1, sizeof(name), f);\r
-    arrput(user->following, UsersFind(mg_str(name)));\r
-  }\r
-\r
-  user->posts = NULL;\r
-  for (int i = 0; i < numPosts; i++) {\r
-    Post post = PostNew();\r
-    PostRead(&post, f);\r
-    arrput(user->posts, post);\r
-  }\r
-}\r
-\r
-\r
-\r
-// Users\r
-\r
-User ** g_users;\r
-static User pat, yas, tof;\r
-\r
-void\r
-UsersSetup() {\r
-  pat = UserNew("pat");\r
-  yas = UserNew("yas");\r
-  tof = UserNew("tof");\r
-\r
-  arrput(pat.following, &yas);\r
-  arrput(yas.following, &pat);\r
-  arrput(tof.following, &yas);\r
-  arrput(tof.following, &pat);\r
-\r
-  arrput(g_users, &pat);\r
-  arrput(g_users, &yas);\r
-  arrput(g_users, &tof);\r
-}\r
-\r
-User *\r
-UsersFind(struct mg_str name) {\r
-  for (int i = 0; i < arrlen(g_users); i++) {\r
-    User * user = g_users[i];\r
-    if (strncmp(user->name, name.ptr, name.len) == 0)\r
-      return user;\r
-  }\r
-  return NULL;\r
-}\r
-\r
-\r
-\r
-// HTML\r
-\r
-void\r
-http_redirect(struct mg_connection * c, struct mg_http_message * hm) {\r
-  struct mg_str *referer = mg_http_get_header(hm, "Referer");\r
-\r
-  static char redirectHeaders[128];\r
-  snprintf(redirectHeaders, 128,\r
-    "Location: %.*s\r\n",\r
-      referer->len, referer->ptr);\r
-  mg_http_reply(c, 303, redirectHeaders, "");\r
-}\r
-\r
-char *\r
-html_404()\r
-{\r
-  static char html[1024];\r
-  snprintf(html, 1024,\r
-          "<!DOCTYPE html>"\r
-          "<html>"\r
-          "<body>"\r
-          "<p><i>Eeeeeeh?</i> <b>The Website's Under Construction</b></p>"\r
-          "</body>"\r
-          "</html>");\r
-  return html;\r
-}\r
-\r
-char *\r
-html_user_not_found()\r
-{\r
-  static char html[1024];\r
-  snprintf(html, 1024,\r
-          "<!DOCTYPE html>"\r
-          "<html>"\r
-          "<body>"\r
-          "<p>User was not found :/</p>"\r
-          "</body>"\r
-          "</html>");\r
-  return html;\r
-}\r
-\r
-int\r
-html_post(Post * post, char * buffer, int bufferLen) {\r
-  int result = 0;\r
-  int printed = 0;\r
-\r
-  printed = snprintf(buffer, bufferLen,\r
-    "<div style=\"border: 2px solid black; margin: 2px; padding: 2px;\">[%s] <a href=\"/user/%s\">%s</a>: %s <br />",\r
-      ctime(&post->timestamp),\r
-      post->user->name,\r
-      post->user->name,\r
-      post->content);\r
-  buffer += printed; bufferLen -= printed; result += printed;\r
-\r
-  if (post->likes > 0) {\r
-    printed = snprintf(buffer, bufferLen,\r
-      "Likes: %d<br />", post->likes);\r
-    buffer += printed; bufferLen -= printed; result += printed;\r
-  }\r
-\r
-  printed = snprintf(buffer, bufferLen,\r
-    "<form action=\"/api/like/%s/%llu\" method=\"post\">"\r
-      "<input type=\"submit\" value=\"Like!\">"\r
-    "</form>",\r
-      post->user->name,\r
-      (size_t)post);\r
-  buffer += printed; bufferLen -= printed; result += printed;\r
-  \r
-  if (arrlen(post->comments) > 0) {\r
-    printed = snprintf(buffer, bufferLen,\r
-      "Comments:<br />");\r
-    buffer += printed; bufferLen -= printed; result += printed;\r
-\r
-    for (int i = 0; i < arrlen(post->comments); i++) {\r
-      printed = html_post(&post->comments[i], buffer, bufferLen);\r
-      buffer += printed; bufferLen -= printed; result += printed;\r
-    }\r
-  }\r
-\r
-  printed = snprintf(buffer, bufferLen,\r
-    "<form action=\"/api/comment/%s/%llu\" method=\"post\">"\r
-      "<input type=\"text\" name=\"content\" autofocus>"\r
-      "<input type=\"submit\" value=\"Comment!\">"\r
-    "</form>"\r
-    "</div>",\r
-      post->user->name,\r
-      (size_t)post);\r
-  buffer += printed; bufferLen -= printed; result += printed;\r
-\r
-  return result;\r
-}\r
-\r
-char *\r
-html_home(User * user)\r
-{\r
-  Post ** posts = NULL;\r
-  for (int i = 0; i < arrlen(user->following); i++) {\r
-    User * userF = user->following[i];\r
-    for (int j = 0; j < arrlen(userF->posts); j++) {\r
-      Post * post = &userF->posts[j];\r
-      bool inserted = false;\r
-      for (int k = 0; k < arrlen(posts); k++) {\r
-        if (post->timestamp > posts[k]->timestamp) {\r
-          arrins(posts, k, post);\r
-          inserted = true;\r
-          break;\r
-        }\r
-      }\r
-      if (! inserted)\r
-        arrput(posts, post);\r
-    }\r
-  }\r
-\r
-  static char html[HTML_LEN];\r
-  snprintf(html, HTML_LEN,\r
-          "<!DOCTYPE html>"\r
-          "<html>"\r
-          "<body>"\r
-          "<form action=\"/api/post/%s\" method=\"post\">"\r
-            "<input type=\"text\" name=\"content\" autofocus><br>"\r
-            "<input type=\"submit\" value=\"Chirp!\">"\r
-          "</form>",\r
-          user->name);\r
-  snprintf(html+strlen(html), HTML_LEN-strlen(html),\r
-          "<form action=\"/api/write/%s\" method=\"post\">"\r
-            "<input type=\"submit\" value=\"Write!\">"\r
-          "</form>"\r
-          "<form action=\"/api/read/%s\" method=\"post\">"\r
-            "<input type=\"submit\" value=\"Read!\">"\r
-          "</form>",\r
-          user->name,\r
-          user->name);\r
-  for (int i = 0; i < arrlen(posts); i++) {\r
-    html_post(posts[i], html+strlen(html), HTML_LEN-strlen(html));\r
-  }\r
-  snprintf(html+strlen(html), HTML_LEN-strlen(html),\r
-          "</body>"\r
-          "</html>");\r
-  arrfree(posts);\r
-  return html;\r
-}\r
-\r
-char *\r
-html_user(User * user)\r
-{\r
-  static char html[HTML_LEN];\r
-  snprintf(html, HTML_LEN,\r
-          "<!DOCTYPE html>"\r
-          "<html>"\r
-          "<body>"\r
-          "<form action=\"/api/post/%s\" method=\"post\">"\r
-            "<input type=\"text\" name=\"content\" autofocus><br>"\r
-            "<input type=\"submit\" value=\"Chirp!\">"\r
-          "</form>",\r
-          user->name);\r
-  snprintf(html+strlen(html), HTML_LEN-strlen(html),\r
-          "<form action=\"/api/write/%s\" method=\"post\">"\r
-            "<input type=\"submit\" value=\"Write!\">"\r
-          "</form>"\r
-          "<form action=\"/api/read/%s\" method=\"post\">"\r
-            "<input type=\"submit\" value=\"Read!\">"\r
-          "</form>",\r
-          user->name,\r
-          user->name);\r
-  for (int i = arrlen(user->posts) - 1; i >= 0; i--) {\r
-    html_post(&user->posts[i], html+strlen(html), HTML_LEN-strlen(html));\r
-  }\r
-  snprintf(html+strlen(html), HTML_LEN-strlen(html),\r
-          "</body>"\r
-          "</html>");\r
-  return html;\r
-}\r
-\r
-\r
-\r
-static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data)\r
-{\r
-  if (ev == MG_EV_HTTP_MSG)\r
-  {\r
-    struct mg_http_message *hm = (struct mg_http_message *)ev_data;\r
-    \r
-    // POST\r
-    if (mg_strcmp(hm->method, mg_str("POST")) == 0)\r
-    {\r
-      // printf("POST: %.*s  %.*s\n",\r
-      //   hm->uri.len, hm->uri.ptr,\r
-      //   hm->body.len, hm->body.ptr);\r
-      // printf("Message: %.*s\n",\r
-      //   hm->message.len, hm->message.ptr);\r
-      \r
-\r
-      // TODO: make sure this diesnt error. shhhhhh!\r
-      struct mg_str caps[10];\r
-\r
-      if (mg_match(hm->uri, mg_str("/api/post/*"), caps)) {\r
-        User * user = UsersFind(caps[0]);\r
-        \r
-        if (user != NULL) {\r
-          Post newPost;\r
-          newPost.likes = 0;\r
-          newPost.comments = NULL;\r
-          newPost.timestamp = time(NULL);\r
-          newPost.user = user;\r
-          mg_http_get_var(&hm->body, "content", newPost.content, sizeof(newPost.content)-1);\r
-          arrput(user->posts, newPost);\r
-        }\r
-      }\r
-      else if (mg_match(hm->uri, mg_str("/api/comment/*/*"), caps)) {\r
-        User * user = UsersFind(caps[0]);\r
-        \r
-        if (user != NULL) {\r
-          Post * parent = (Post *)atoll(caps[1].ptr);\r
-          if (parent != NULL) {\r
-            Post newComment;\r
-            newComment.likes = 0;\r
-            newComment.comments = NULL;\r
-            newComment.timestamp = time(NULL);\r
-            newComment.user = user;\r
-            mg_http_get_var(&hm->body, "content", newComment.content, sizeof(newComment.content)-1);\r
-            arrput(parent->comments, newComment);\r
-          }\r
-        }\r
-      }\r
-      else if (mg_match(hm->uri, mg_str("/api/like/*/*"), caps)) {\r
-        User * user = UsersFind(caps[0]);\r
-        if (user != NULL) {\r
-          Post * post = (Post *)atoll(caps[1].ptr);\r
-          if (post != NULL)\r
-            post->likes++;\r
-        }\r
-      }\r
-      else if (mg_match(hm->uri, mg_str("/api/write/*"), caps)) {\r
-        User * user = UsersFind(caps[0]);\r
-        if (user != NULL) {\r
-          char filename[128];\r
-          snprintf(filename, 128, "data/users/%s", user->name);\r
-          FILE * f = fopen(filename, "w");\r
-          UserWrite(user, f);\r
-          fclose(f);\r
-        }\r
-      }\r
-      else if (mg_match(hm->uri, mg_str("/api/read/*"), caps)) {\r
-        User * user = UsersFind(caps[0]);\r
-        if (user != NULL) {\r
-          char filename[128];\r
-          snprintf(filename, 128, "data/users/%s", user->name);\r
-          FILE * f = fopen(filename, "r");\r
-          UserRead(user, f);\r
-          fclose(f);\r
-        }\r
-      }\r
-      http_redirect(c, hm);\r
-    }\r
-    // GET\r
-    else if (mg_strcmp(hm->method, mg_str("GET")) == 0)\r
-    {\r
-      struct mg_str caps[2];\r
-\r
-      if (mg_match(hm->uri, mg_str("/home/*"), caps)) {\r
-        User * user = UsersFind(caps[0]);\r
-\r
-        if (user == NULL)\r
-          mg_http_reply(c, 200, "", html_user_not_found());\r
-        else\r
-          mg_http_reply(c, 200, "", html_home(user));\r
-      }\r
-      else if (mg_match(hm->uri, mg_str("/user/*"), caps)) {\r
-        User * user = UsersFind(caps[0]);\r
-\r
-        if (user == NULL)\r
-          mg_http_reply(c, 200, "", html_user_not_found());\r
-        else\r
-          mg_http_reply(c, 200, "", html_user(user));\r
-      }\r
-      else {\r
-        mg_http_reply(c, 404, "", html_404());\r
-      }\r
-    }\r
-    else {\r
-        mg_http_reply(c, 404, "", "Unknown");\r
-    }\r
-  }\r
-}\r
-\r
-int main(int argc, char *argv[])\r
-{\r
-  UsersSetup();\r
-\r
-  struct mg_mgr mgr;\r
-  mg_mgr_init(&mgr);\r
-  mg_http_listen(&mgr, "http://[::]:8000", fn, &mgr);\r
-  while (1)\r
-    mg_mgr_poll(&mgr, 1000);\r
-  mg_mgr_free(&mgr);\r
-  return 0;\r
-}\r