diff mbox series

[1/6] Updated mongoose to 6.16

Message ID 20191208122844.11642-1-sbabic@denx.de
State Accepted
Headers show
Series [1/6] Updated mongoose to 6.16 | expand

Commit Message

Stefano Babic Dec. 8, 2019, 12:28 p.m. UTC
Upgrade mongoose webserver (mongoose.c and mongoose.h) to 6.16 [1].

The issue found in SWUpdate with current mongoose is that the ping/pong
mechanismus in Websocket does not work when the IPC blocks due to I/O.
It is required to write just a part of the data in non blockin mode, but
the webserver requires that the handler consumes all data. New version
allows to return the bytes still available in the buffer and the server
will call the handler again.

[1] dce60c6dbb096f3b96e1a45cbfdfd55e18b38bb6.

Signed-off-by: Stefano Babic <sbabic@denx.de>
---
 mongoose/mongoose.c | 3371 ++++++++++++++++++++++++-------------------
 mongoose/mongoose.h |  662 ++++++++-
 2 files changed, 2513 insertions(+), 1520 deletions(-)
diff mbox series

Patch

diff --git a/mongoose/mongoose.c b/mongoose/mongoose.c
index d238934..5c80007 100644
--- a/mongoose/mongoose.c
+++ b/mongoose/mongoose.c
@@ -5,8 +5,6 @@ 
 /*
  * Copyright (c) 2014 Cesanta Software Limited
  * All rights reserved
- *
- * SPDX-License-Identifier: GPL-2.0
  */
 
 #ifndef CS_MONGOOSE_SRC_INTERNAL_H_
@@ -40,7 +38,9 @@ 
 /* Amalgamated: #include "mg_http.h" */
 /* Amalgamated: #include "mg_net.h" */
 
+#ifndef MG_CTL_MSG_MESSAGE_SIZE
 #define MG_CTL_MSG_MESSAGE_SIZE 8192
+#endif
 
 /* internals that need to be accessible in unit tests */
 MG_INTERNAL struct mg_connection *mg_do_connect(struct mg_connection *nc,
@@ -88,6 +88,9 @@  extern void *(*test_calloc)(size_t count, size_t size);
 #if MG_ENABLE_HTTP
 struct mg_serve_http_opts;
 
+MG_INTERNAL struct mg_http_proto_data *mg_http_create_proto_data(
+    struct mg_connection *c);
+
 /*
  * Reassemble the content of the buffer (buf, blen) which should be
  * in the HTTP chunked encoding, by collapsing data chunks to the
@@ -165,8 +168,20 @@  MG_INTERNAL int mg_sntp_parse_reply(const char *buf, int len,
 #line 1 "common/mg_mem.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_MG_MEM_H_
@@ -201,8 +216,20 @@  extern "C" {
 #line 1 "common/cs_base64.c"
 #endif
 /*
- * Copyright (c) 2014 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef EXCLUDE_COMMON
@@ -409,8 +436,20 @@  int cs_base64_decode(const unsigned char *s, int len, char *dst, int *dec_len) {
 #line 1 "common/cs_dbg.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_CS_DBG_H_
@@ -426,6 +465,10 @@  int cs_base64_decode(const unsigned char *s, int len, char *dst, int *dec_len) {
 #define CS_ENABLE_DEBUG 0
 #endif
 
+#ifndef CS_LOG_PREFIX_LEN
+#define CS_LOG_PREFIX_LEN 24
+#endif
+
 #ifndef CS_LOG_ENABLE_TS_DIFF
 #define CS_LOG_ENABLE_TS_DIFF 0
 #endif
@@ -456,51 +499,28 @@  enum cs_log_level {
 void cs_log_set_level(enum cs_log_level level);
 
 /*
- * Set log filter. NULL (a default) logs everything.
- * Otherwise, function name and file name will be tested against the given
- * pattern, and only matching messages will be printed.
- *
- * For the pattern syntax, refer to `mg_match_prefix()` in `str_util.h`.
- *
- * Example:
- * ```c
- * void foo(void) {
- *   LOG(LL_INFO, ("hello from foo"));
- * }
+ * A comma-separated set of prefix=level.
+ * prefix is matched against the log prefix exactly as printed, including line
+ * number, but partial match is ok. Check stops on first matching entry.
+ * If nothing matches, default level is used.
  *
- * void bar(void) {
- *   LOG(LL_INFO, ("hello from bar"));
- * }
+ * Examples:
+ *   main.c:=4 - everything from main C at verbose debug level.
+ *   mongoose.c=1,mjs.c=1,=4 - everything at verbose debug except mg_* and mjs_*
  *
- * void test(void) {
- *   cs_log_set_filter(NULL);
- *   foo();
- *   bar();
- *
- *   cs_log_set_filter("f*");
- *   foo();
- *   bar(); // Will NOT print anything
- *
- *   cs_log_set_filter("bar");
- *   foo(); // Will NOT print anything
- *   bar();
- * }
- * ```
  */
-void cs_log_set_filter(const char *pattern);
+void cs_log_set_file_level(const char *file_level);
 
 /*
- * Helper function which prints message prefix with the given `level`, function
- * name `func` and `filename`. If message should be printed (accordingly to the
- * current log level and filter), prints the prefix and returns 1, otherwise
- * returns 0.
+ * Helper function which prints message prefix with the given `level`.
+ * If message should be printed (according to the current log level
+ * and filter), prints the prefix and returns 1, otherwise returns 0.
  *
  * Clients should typically just use `LOG()` macro.
  */
-int cs_log_print_prefix(enum cs_log_level level, const char *func,
-                        const char *filename);
+int cs_log_print_prefix(enum cs_log_level level, const char *fname, int line);
 
-extern enum cs_log_level cs_log_threshold;
+extern enum cs_log_level cs_log_level;
 
 #if CS_ENABLE_STDIO
 
@@ -513,11 +533,9 @@  void cs_log_set_file(FILE *file);
  * Prints log to the current log file, appends "\n" in the end and flushes the
  * stream.
  */
-void cs_log_printf(const char *fmt, ...)
-#ifdef __GNUC__
-    __attribute__((format(printf, 1, 2)))
-#endif
-    ;
+void cs_log_printf(const char *fmt, ...) PRINTF_LIKE(1, 2);
+
+#if CS_ENABLE_STDIO
 
 /*
  * Format and print message `x` with the given level `l`. Example:
@@ -527,11 +545,19 @@  void cs_log_printf(const char *fmt, ...)
  * LOG(LL_DEBUG, ("my debug message: %d", 123));
  * ```
  */
-#define LOG(l, x)                                                    \
-  do {                                                               \
-    if (cs_log_print_prefix(l, __func__, __FILE__)) cs_log_printf x; \
+#define LOG(l, x)                                     \
+  do {                                                \
+    if (cs_log_print_prefix(l, __FILE__, __LINE__)) { \
+      cs_log_printf x;                                \
+    }                                                 \
   } while (0)
 
+#else
+
+#define LOG(l, x) ((void) l)
+
+#endif
+
 #ifndef CS_NDEBUG
 
 /*
@@ -561,8 +587,20 @@  void cs_log_printf(const char *fmt, ...)
 #line 1 "common/cs_dbg.c"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 /* Amalgamated: #include "common/cs_dbg.h" */
@@ -574,19 +612,17 @@  void cs_log_printf(const char *fmt, ...)
 /* Amalgamated: #include "common/cs_time.h" */
 /* Amalgamated: #include "common/str_util.h" */
 
-enum cs_log_level cs_log_threshold WEAK =
+enum cs_log_level cs_log_level WEAK =
 #if CS_ENABLE_DEBUG
     LL_VERBOSE_DEBUG;
 #else
     LL_ERROR;
 #endif
 
-static char *s_filter_pattern = NULL;
-static size_t s_filter_pattern_len;
-
-void cs_log_set_filter(const char *pattern) WEAK;
-
 #if CS_ENABLE_STDIO
+static char *s_file_level = NULL;
+
+void cs_log_set_file_level(const char *file_level) WEAK;
 
 FILE *cs_log_file WEAK = NULL;
 
@@ -596,34 +632,62 @@  double cs_log_ts WEAK;
 
 enum cs_log_level cs_log_cur_msg_level WEAK = LL_NONE;
 
-void cs_log_set_filter(const char *pattern) {
-  free(s_filter_pattern);
-  if (pattern != NULL) {
-    s_filter_pattern = strdup(pattern);
-    s_filter_pattern_len = strlen(pattern);
+void cs_log_set_file_level(const char *file_level) {
+  char *fl = s_file_level;
+  if (file_level != NULL) {
+    s_file_level = strdup(file_level);
   } else {
-    s_filter_pattern = NULL;
-    s_filter_pattern_len = 0;
+    s_file_level = NULL;
   }
+  free(fl);
 }
 
-int cs_log_print_prefix(enum cs_log_level, const char *, const char *) WEAK;
-int cs_log_print_prefix(enum cs_log_level level, const char *func,
-                        const char *filename) {
-  char prefix[21];
+int cs_log_print_prefix(enum cs_log_level level, const char *file, int ln) WEAK;
+int cs_log_print_prefix(enum cs_log_level level, const char *file, int ln) {
+  char prefix[CS_LOG_PREFIX_LEN], *q;
+  const char *p;
+  size_t fl = 0, ll = 0, pl = 0;
+
+  if (level > cs_log_level && s_file_level == NULL) return 0;
 
-  if (level > cs_log_threshold) return 0;
-  if (s_filter_pattern != NULL &&
-      mg_match_prefix(s_filter_pattern, s_filter_pattern_len, func) == 0 &&
-      mg_match_prefix(s_filter_pattern, s_filter_pattern_len, filename) == 0) {
-    return 0;
+  p = file + strlen(file);
+
+  while (p != file) {
+    const char c = *(p - 1);
+    if (c == '/' || c == '\\') break;
+    p--;
+    fl++;
+  }
+
+  ll = (ln < 10000 ? (ln < 1000 ? (ln < 100 ? (ln < 10 ? 1 : 2) : 3) : 4) : 5);
+  if (fl > (sizeof(prefix) - ll - 2)) fl = (sizeof(prefix) - ll - 2);
+
+  pl = fl + 1 + ll;
+  memcpy(prefix, p, fl);
+  q = prefix + pl;
+  memset(q, ' ', sizeof(prefix) - pl);
+  do {
+    *(--q) = '0' + (ln % 10);
+    ln /= 10;
+  } while (ln > 0);
+  *(--q) = ':';
+
+  if (s_file_level != NULL) {
+    enum cs_log_level pll = cs_log_level;
+    struct mg_str fl = mg_mk_str(s_file_level), ps = MG_MK_STR_N(prefix, pl);
+    struct mg_str k, v;
+    while ((fl = mg_next_comma_list_entry_n(fl, &k, &v)).p != NULL) {
+      bool yes = !(!mg_str_starts_with(ps, k) || v.len == 0);
+      if (!yes) continue;
+      pll = (enum cs_log_level)(*v.p - '0');
+      break;
+    }
+    if (level > pll) return 0;
   }
 
-  strncpy(prefix, func, 20);
-  prefix[20] = '\0';
   if (cs_log_file == NULL) cs_log_file = stderr;
   cs_log_cur_msg_level = level;
-  fprintf(cs_log_file, "%-20s ", prefix);
+  fwrite(prefix, 1, sizeof(prefix), cs_log_file);
 #if CS_LOG_ENABLE_TS_DIFF
   {
     double now = cs_time();
@@ -652,15 +716,15 @@  void cs_log_set_file(FILE *file) {
 
 #else
 
-void cs_log_set_filter(const char *pattern) {
-  (void) pattern;
+void cs_log_set_file_level(const char *file_level) {
+  (void) file_level;
 }
 
 #endif /* CS_ENABLE_STDIO */
 
 void cs_log_set_level(enum cs_log_level level) WEAK;
 void cs_log_set_level(enum cs_log_level level) {
-  cs_log_threshold = level;
+  cs_log_level = level;
 #if CS_LOG_ENABLE_TS_DIFF && CS_ENABLE_STDIO
   cs_log_ts = cs_time();
 #endif
@@ -669,8 +733,20 @@  void cs_log_set_level(enum cs_log_level level) {
 #line 1 "common/cs_dirent.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_CS_DIRENT_H_
@@ -711,8 +787,20 @@  struct dirent *readdir(DIR *dir);
 #line 1 "common/cs_dirent.c"
 #endif
 /*
- * Copyright (c) 2015 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef EXCLUDE_COMMON
@@ -810,8 +898,20 @@  typedef int cs_dirent_dummy;
 #line 1 "common/cs_time.c"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 /* Amalgamated: #include "common/cs_time.h" */
@@ -896,8 +996,20 @@  double cs_timegm(const struct tm *tm) {
 #line 1 "common/cs_endian.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_CS_ENDIAN_H_
@@ -1202,7 +1314,7 @@  static uint32_t blk0(union char64long16 *block, int i) {
   z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
   w = rol(w, 30);
 
-static void cs_sha1_transform(uint32_t state[5], const unsigned char buffer[64]) {
+void cs_sha1_transform(uint32_t state[5], const unsigned char buffer[64]) {
   uint32_t a, b, c, d, e;
   union char64long16 block[1];
 
@@ -1401,8 +1513,20 @@  void cs_hmac_sha1(const unsigned char *key, size_t keylen,
 #line 1 "common/mbuf.c"
 #endif
 /*
- * Copyright (c) 2014 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef EXCLUDE_COMMON
@@ -1472,10 +1596,21 @@  size_t mbuf_insert(struct mbuf *a, size_t off, const void *buf, size_t len) {
     }
     a->len += len;
   } else {
-    size_t new_size = (size_t)((a->len + len) * MBUF_SIZE_MULTIPLIER);
-    if ((p = (char *) MBUF_REALLOC(a->buf, new_size)) != NULL) {
+    size_t min_size = (a->len + len);
+    size_t new_size = (size_t)(min_size * MBUF_SIZE_MULTIPLIER);
+    if (new_size - min_size > MBUF_SIZE_MAX_HEADROOM) {
+      new_size = min_size + MBUF_SIZE_MAX_HEADROOM;
+    }
+    p = (char *) MBUF_REALLOC(a->buf, new_size);
+    if (p == NULL && new_size != min_size) {
+      new_size = min_size;
+      p = (char *) MBUF_REALLOC(a->buf, new_size);
+    }
+    if (p != NULL) {
       a->buf = p;
-      memmove(a->buf + off + len, a->buf + off, a->len - off);
+      if (off != a->len) {
+        memmove(a->buf + off + len, a->buf + off, a->len - off);
+      }
       if (buf != NULL) memcpy(a->buf + off, buf, len);
       a->len += len;
       a->size = new_size;
@@ -1492,6 +1627,22 @@  size_t mbuf_append(struct mbuf *a, const void *buf, size_t len) {
   return mbuf_insert(a, a->len, buf, len);
 }
 
+size_t mbuf_append_and_free(struct mbuf *a, void *buf, size_t len) WEAK;
+size_t mbuf_append_and_free(struct mbuf *a, void *data, size_t len) {
+  size_t ret;
+  /* Optimization: if the buffer is currently empty,
+   * take over the user-provided buffer. */
+  if (a->len == 0) {
+    if (a->buf != NULL) free(a->buf);
+    a->buf = (char *) data;
+    a->len = a->size = len;
+    return len;
+  }
+  ret = mbuf_insert(a, a->len, data, len);
+  free(data);
+  return ret;
+}
+
 void mbuf_remove(struct mbuf *mb, size_t n) WEAK;
 void mbuf_remove(struct mbuf *mb, size_t n) {
   if (n > 0 && n <= mb->len) {
@@ -1500,18 +1651,43 @@  void mbuf_remove(struct mbuf *mb, size_t n) {
   }
 }
 
+void mbuf_clear(struct mbuf *mb) WEAK;
+void mbuf_clear(struct mbuf *mb) {
+  mb->len = 0;
+}
+
+void mbuf_move(struct mbuf *from, struct mbuf *to) WEAK;
+void mbuf_move(struct mbuf *from, struct mbuf *to) {
+  memcpy(to, from, sizeof(*to));
+  memset(from, 0, sizeof(*from));
+}
+
 #endif /* EXCLUDE_COMMON */
 #ifdef MG_MODULE_LINES
 #line 1 "common/mg_str.c"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 /* Amalgamated: #include "common/mg_mem.h" */
 /* Amalgamated: #include "common/mg_str.h" */
+/* Amalgamated: #include "common/platform.h" */
 
+#include <ctype.h>
 #include <stdlib.h>
 #include <string.h>
 
@@ -1611,6 +1787,14 @@  int mg_strncmp(const struct mg_str str1, const struct mg_str str2, size_t n) {
   return mg_strcmp(s1, s2);
 }
 
+void mg_strfree(struct mg_str *s) WEAK;
+void mg_strfree(struct mg_str *s) {
+  char *sp = (char *) s->p;
+  s->p = NULL;
+  s->len = 0;
+  if (sp != NULL) free(sp);
+}
+
 const char *mg_strstr(const struct mg_str haystack,
                       const struct mg_str needle) WEAK;
 const char *mg_strstr(const struct mg_str haystack,
@@ -1624,12 +1808,43 @@  const char *mg_strstr(const struct mg_str haystack,
   }
   return NULL;
 }
+
+struct mg_str mg_strstrip(struct mg_str s) WEAK;
+struct mg_str mg_strstrip(struct mg_str s) {
+  while (s.len > 0 && isspace((int) *s.p)) {
+    s.p++;
+    s.len--;
+  }
+  while (s.len > 0 && isspace((int) *(s.p + s.len - 1))) {
+    s.len--;
+  }
+  return s;
+}
+
+int mg_str_starts_with(struct mg_str s, struct mg_str prefix) WEAK;
+int mg_str_starts_with(struct mg_str s, struct mg_str prefix) {
+  const struct mg_str sp = MG_MK_STR_N(s.p, prefix.len);
+  if (s.len < prefix.len) return 0;
+  return (mg_strcmp(sp, prefix) == 0);
+}
 #ifdef MG_MODULE_LINES
 #line 1 "common/str_util.c"
 #endif
 /*
- * Copyright (c) 2015 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef EXCLUDE_COMMON
@@ -2176,6 +2391,13 @@  size_t mg_match_prefix(const char *pattern, int pattern_len, const char *str) {
 
 #define MG_MAX_HOST_LEN 200
 
+#ifndef MG_TCP_IO_SIZE
+#define MG_TCP_IO_SIZE 1460
+#endif
+#ifndef MG_UDP_IO_SIZE
+#define MG_UDP_IO_SIZE 1460
+#endif
+
 #define MG_COPY_COMMON_CONNECTION_OPTIONS(dst, src) \
   memcpy(dst, src, sizeof(*dst));
 
@@ -2216,8 +2438,6 @@  MG_INTERNAL void mg_remove_conn(struct mg_connection *conn) {
 MG_INTERNAL void mg_call(struct mg_connection *nc,
                          mg_event_handler_t ev_handler, void *user_data, int ev,
                          void *ev_data) {
-  static int nesting_level = 0;
-  nesting_level++;
   if (ev_handler == NULL) {
     /*
      * If protocol handler is specified, call it. Otherwise, call user-specified
@@ -2226,7 +2446,7 @@  MG_INTERNAL void mg_call(struct mg_connection *nc,
     ev_handler = nc->proto_handler ? nc->proto_handler : nc->handler;
   }
   if (ev != MG_EV_POLL) {
-    DBG(("%p %s ev=%d ev_data=%p flags=%lu rmbl=%d smbl=%d", nc,
+    DBG(("%p %s ev=%d ev_data=%p flags=0x%lx rmbl=%d smbl=%d", nc,
          ev_handler == nc->handler ? "user" : "proto", ev, ev_data, nc->flags,
          (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
   }
@@ -2239,33 +2459,25 @@  MG_INTERNAL void mg_call(struct mg_connection *nc,
 #endif
   if (ev_handler != NULL) {
     unsigned long flags_before = nc->flags;
-    size_t recv_mbuf_before = nc->recv_mbuf.len, recved;
     ev_handler(nc, ev, ev_data MG_UD_ARG(user_data));
-    recved = (recv_mbuf_before - nc->recv_mbuf.len);
     /* Prevent user handler from fiddling with system flags. */
     if (ev_handler == nc->handler && nc->flags != flags_before) {
       nc->flags = (flags_before & ~_MG_CALLBACK_MODIFIABLE_FLAGS_MASK) |
                   (nc->flags & _MG_CALLBACK_MODIFIABLE_FLAGS_MASK);
     }
-    /* It's important to not double-count recved bytes, and since mg_call can be
-     * called recursively (e.g. proto_handler invokes user handler), we keep
-     * track of recursion and only report received bytes at the top level. */
-    if (nesting_level == 1 && recved > 0 && !(nc->flags & MG_F_UDP)) {
-      nc->iface->vtable->recved(nc, recved);
-    }
   }
+  if (ev != MG_EV_POLL) nc->mgr->num_calls++;
   if (ev != MG_EV_POLL) {
-    DBG(("%p after %s flags=%lu rmbl=%d smbl=%d", nc,
+    DBG(("%p after %s flags=0x%lx rmbl=%d smbl=%d", nc,
          ev_handler == nc->handler ? "user" : "proto", nc->flags,
          (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
   }
-  nesting_level--;
 #if !MG_ENABLE_CALLBACK_USERDATA
   (void) user_data;
 #endif
 }
 
-void mg_if_timer(struct mg_connection *c, double now) {
+MG_INTERNAL void mg_timer(struct mg_connection *c, double now) {
   if (c->ev_timer_time > 0 && now >= c->ev_timer_time) {
     double old_value = c->ev_timer_time;
     c->ev_timer_time = 0;
@@ -2273,13 +2485,53 @@  void mg_if_timer(struct mg_connection *c, double now) {
   }
 }
 
-void mg_if_poll(struct mg_connection *nc, time_t now) {
-  if (!(nc->flags & MG_F_SSL) || (nc->flags & MG_F_SSL_HANDSHAKE_DONE)) {
-    mg_call(nc, NULL, nc->user_data, MG_EV_POLL, &now);
+MG_INTERNAL size_t recv_avail_size(struct mg_connection *conn, size_t max) {
+  size_t avail;
+  if (conn->recv_mbuf_limit < conn->recv_mbuf.len) return 0;
+  avail = conn->recv_mbuf_limit - conn->recv_mbuf.len;
+  return avail > max ? max : avail;
+}
+
+static int mg_do_recv(struct mg_connection *nc);
+
+int mg_if_poll(struct mg_connection *nc, double now) {
+  if (nc->flags & MG_F_CLOSE_IMMEDIATELY) {
+    mg_close_conn(nc);
+    return 0;
+  } else if (nc->flags & MG_F_SEND_AND_CLOSE) {
+    if (nc->send_mbuf.len == 0) {
+      nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+      mg_close_conn(nc);
+      return 0;
+    }
+  } else if (nc->flags & MG_F_RECV_AND_CLOSE) {
+    mg_close_conn(nc);
+    return 0;
+  }
+#if MG_ENABLE_SSL
+  if ((nc->flags & (MG_F_SSL | MG_F_LISTENING | MG_F_CONNECTING)) == MG_F_SSL) {
+    /* SSL library may have data to be delivered to the app in its buffers,
+     * drain them. */
+    int recved = 0;
+    do {
+      if (nc->flags & (MG_F_WANT_READ | MG_F_WANT_WRITE)) break;
+      if (recv_avail_size(nc, MG_TCP_IO_SIZE) <= 0) break;
+      recved = mg_do_recv(nc);
+    } while (recved > 0);
+  }
+#endif /* MG_ENABLE_SSL */
+  mg_timer(nc, now);
+  {
+    time_t now_t = (time_t) now;
+    mg_call(nc, NULL, nc->user_data, MG_EV_POLL, &now_t);
   }
+  return 1;
 }
 
-static void mg_destroy_conn(struct mg_connection *conn, int destroy_if) {
+void mg_destroy_conn(struct mg_connection *conn, int destroy_if) {
+  if (conn->sock != INVALID_SOCKET) { /* Don't print timer-only conns */
+    LOG(LL_DEBUG, ("%p 0x%lx %d", conn, conn->flags, destroy_if));
+  }
   if (destroy_if) conn->iface->vtable->destroy_conn(conn);
   if (conn->proto_data != NULL && conn->proto_data_destructor != NULL) {
     conn->proto_data_destructor(conn->proto_data);
@@ -2295,12 +2547,23 @@  static void mg_destroy_conn(struct mg_connection *conn, int destroy_if) {
 }
 
 void mg_close_conn(struct mg_connection *conn) {
-  DBG(("%p %lu %d", conn, conn->flags, conn->sock));
+  /* See if there's any remaining data to deliver. Skip if user completely
+   * throttled the connection there will be no progress anyway. */
+  if (conn->sock != INVALID_SOCKET && mg_do_recv(conn) == -2) {
+    /* Receive is throttled, wait. */
+    conn->flags |= MG_F_RECV_AND_CLOSE;
+    return;
+  }
 #if MG_ENABLE_SSL
   if (conn->flags & MG_F_SSL_HANDSHAKE_DONE) {
     mg_ssl_if_conn_close_notify(conn);
   }
 #endif
+  /*
+   * Clearly mark the connection as going away (if not already).
+   * Some net_if impls (LwIP) need this for cleanly handling half-dead conns.
+   */
+  conn->flags |= MG_F_CLOSE_IMMEDIATELY;
   mg_remove_conn(conn);
   conn->iface->vtable->destroy_conn(conn);
   mg_call(conn, NULL, conn->user_data, MG_EV_CLOSE, NULL);
@@ -2332,15 +2595,6 @@  void mg_mgr_init_opt(struct mg_mgr *m, void *user_data,
   signal(SIGPIPE, SIG_IGN);
 #endif
 
-#if MG_ENABLE_SSL
-  {
-    static int init_done;
-    if (!init_done) {
-      mg_ssl_if_init();
-      init_done++;
-    }
-  }
-#endif
   {
     int i;
     if (opts.num_ifaces == 0) {
@@ -2353,7 +2607,7 @@  void mg_mgr_init_opt(struct mg_mgr *m, void *user_data,
     m->num_ifaces = opts.num_ifaces;
     m->ifaces =
         (struct mg_iface **) MG_MALLOC(sizeof(*m->ifaces) * opts.num_ifaces);
-    for (i = 0; i < mg_num_ifaces; i++) {
+    for (i = 0; i < opts.num_ifaces; i++) {
       m->ifaces[i] = mg_if_create_iface(opts.ifaces[i], m);
       m->ifaces[i]->vtable->init(m->ifaces[i]);
     }
@@ -2363,6 +2617,15 @@  void mg_mgr_init_opt(struct mg_mgr *m, void *user_data,
   }
   DBG(("=================================="));
   DBG(("init mgr=%p", m));
+#if MG_ENABLE_SSL
+  {
+    static int init_done;
+    if (!init_done) {
+      mg_ssl_if_init();
+      init_done++;
+    }
+  }
+#endif
 }
 
 void mg_mgr_free(struct mg_mgr *m) {
@@ -2381,6 +2644,7 @@  void mg_mgr_free(struct mg_mgr *m) {
 
   for (conn = m->active_connections; conn != NULL; conn = tmp_conn) {
     tmp_conn = conn->next;
+    conn->flags |= MG_F_CLOSE_IMMEDIATELY;
     mg_close_conn(conn);
   }
 
@@ -2396,19 +2660,14 @@  void mg_mgr_free(struct mg_mgr *m) {
   MG_FREE((char *) m->nameserver);
 }
 
-time_t mg_mgr_poll(struct mg_mgr *m, int timeout_ms) {
-  int i;
-  time_t now = 0; /* oh GCC, seriously ? */
-
-  if (m->num_ifaces == 0) {
-    LOG(LL_ERROR, ("cannot poll: no interfaces"));
-    return 0;
-  }
+int mg_mgr_poll(struct mg_mgr *m, int timeout_ms) {
+  int i, num_calls_before = m->num_calls;
 
   for (i = 0; i < m->num_ifaces; i++) {
-    now = m->ifaces[i]->vtable->poll(m->ifaces[i], timeout_ms);
+    m->ifaces[i]->vtable->poll(m->ifaces[i], timeout_ms);
   }
-  return now;
+
+  return (m->num_calls - num_calls_before);
 }
 
 int mg_vprintf(struct mg_connection *nc, const char *fmt, va_list ap) {
@@ -2449,8 +2708,8 @@  static int mg_resolve2(const char *host, struct in_addr *ina) {
     return 0;
   }
   for (p = servinfo; p != NULL; p = p->ai_next) {
-    memcpy(&h, &p->ai_addr, sizeof(struct sockaddr_in *));
-    memcpy(ina, &h->sin_addr, sizeof(ina));
+    memcpy(&h, &p->ai_addr, sizeof(h));
+    memcpy(ina, &h->sin_addr, sizeof(*ina));
   }
   freeaddrinfo(servinfo);
   return 1;
@@ -2605,6 +2864,36 @@  MG_INTERNAL int mg_parse_address(const char *str, union socket_address *sa,
   return port < 0xffffUL && (ch == '\0' || ch == ',' || isspace(ch)) ? len : -1;
 }
 
+#if MG_ENABLE_SSL
+MG_INTERNAL void mg_ssl_handshake(struct mg_connection *nc) {
+  int err = 0;
+  int server_side = (nc->listener != NULL);
+  enum mg_ssl_if_result res;
+  if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) return;
+  res = mg_ssl_if_handshake(nc);
+
+  if (res == MG_SSL_OK) {
+    nc->flags |= MG_F_SSL_HANDSHAKE_DONE;
+    nc->flags &= ~(MG_F_WANT_READ | MG_F_WANT_WRITE);
+    if (server_side) {
+      mg_call(nc, NULL, nc->user_data, MG_EV_ACCEPT, &nc->sa);
+    } else {
+      mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &err);
+    }
+  } else if (res == MG_SSL_WANT_READ) {
+    nc->flags |= MG_F_WANT_READ;
+  } else if (res == MG_SSL_WANT_WRITE) {
+    nc->flags |= MG_F_WANT_WRITE;
+  } else {
+    if (!server_side) {
+      err = res;
+      mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &err);
+    }
+    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+  }
+}
+#endif /* MG_ENABLE_SSL */
+
 struct mg_connection *mg_if_accept_new_conn(struct mg_connection *lc) {
   struct mg_add_sock_opts opts;
   struct mg_connection *nc;
@@ -2618,95 +2907,132 @@  struct mg_connection *mg_if_accept_new_conn(struct mg_connection *lc) {
   nc->iface = lc->iface;
   if (lc->flags & MG_F_SSL) nc->flags |= MG_F_SSL;
   mg_add_conn(nc->mgr, nc);
-  DBG(("%p %p %d %d", lc, nc, nc->sock, (int) nc->flags));
+  LOG(LL_DEBUG, ("%p %p %d %d", lc, nc, nc->sock, (int) nc->flags));
   return nc;
 }
 
 void mg_if_accept_tcp_cb(struct mg_connection *nc, union socket_address *sa,
                          size_t sa_len) {
-  (void) sa_len;
+  LOG(LL_DEBUG, ("%p %s://%s:%hu", nc, (nc->flags & MG_F_UDP ? "udp" : "tcp"),
+                 inet_ntoa(sa->sin.sin_addr), ntohs(sa->sin.sin_port)));
   nc->sa = *sa;
-  mg_call(nc, NULL, nc->user_data, MG_EV_ACCEPT, &nc->sa);
+#if MG_ENABLE_SSL
+  if (nc->listener->flags & MG_F_SSL) {
+    nc->flags |= MG_F_SSL;
+    if (mg_ssl_if_conn_accept(nc, nc->listener) == MG_SSL_OK) {
+      mg_ssl_handshake(nc);
+    } else {
+      mg_close_conn(nc);
+    }
+  } else
+#endif
+  {
+    mg_call(nc, NULL, nc->user_data, MG_EV_ACCEPT, &nc->sa);
+  }
+  (void) sa_len;
 }
 
 void mg_send(struct mg_connection *nc, const void *buf, int len) {
   nc->last_io_time = (time_t) mg_time();
-  if (nc->flags & MG_F_UDP) {
-    nc->iface->vtable->udp_send(nc, buf, len);
-  } else {
-    nc->iface->vtable->tcp_send(nc, buf, len);
-  }
+  mbuf_append(&nc->send_mbuf, buf, len);
 }
 
-void mg_if_sent_cb(struct mg_connection *nc, int num_sent) {
-  DBG(("%p %d", nc, num_sent));
-#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP
-  if (nc->mgr && nc->mgr->hexdump_file != NULL) {
-    char *buf = nc->send_mbuf.buf;
-    mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, num_sent, MG_EV_SEND);
-  }
-#endif
-  if (num_sent < 0) {
-    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
-  } else {
-    mbuf_remove(&nc->send_mbuf, num_sent);
-    mbuf_trim(&nc->send_mbuf);
+static int mg_recv_tcp(struct mg_connection *nc, char *buf, size_t len);
+static int mg_recv_udp(struct mg_connection *nc, char *buf, size_t len);
+
+static int mg_do_recv(struct mg_connection *nc) {
+  int res = 0;
+  char *buf = NULL;
+  size_t len = (nc->flags & MG_F_UDP ? MG_UDP_IO_SIZE : MG_TCP_IO_SIZE);
+  if ((nc->flags & (MG_F_CLOSE_IMMEDIATELY | MG_F_CONNECTING)) ||
+      ((nc->flags & MG_F_LISTENING) && !(nc->flags & MG_F_UDP))) {
+    return -1;
   }
-  mg_call(nc, NULL, nc->user_data, MG_EV_SEND, &num_sent);
+  do {
+    len = recv_avail_size(nc, len);
+    if (len == 0) {
+      res = -2;
+      break;
+    }
+    if (nc->recv_mbuf.size < nc->recv_mbuf.len + len) {
+      mbuf_resize(&nc->recv_mbuf, nc->recv_mbuf.len + len);
+    }
+    buf = nc->recv_mbuf.buf + nc->recv_mbuf.len;
+    len = nc->recv_mbuf.size - nc->recv_mbuf.len;
+    if (nc->flags & MG_F_UDP) {
+      res = mg_recv_udp(nc, buf, len);
+    } else {
+      res = mg_recv_tcp(nc, buf, len);
+    }
+  } while (res > 0 && !(nc->flags & (MG_F_CLOSE_IMMEDIATELY | MG_F_UDP)));
+  return res;
 }
 
-MG_INTERNAL void mg_recv_common(struct mg_connection *nc, void *buf, int len,
-                                int own) {
-  DBG(("%p %d %u", nc, len, (unsigned int) nc->recv_mbuf.len));
-
-#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP
-  if (nc->mgr && nc->mgr->hexdump_file != NULL) {
-    mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, len, MG_EV_RECV);
-  }
-#endif
+void mg_if_can_recv_cb(struct mg_connection *nc) {
+  mg_do_recv(nc);
+}
 
-  if (nc->flags & MG_F_CLOSE_IMMEDIATELY) {
-    DBG(("%p discarded %d bytes", nc, len));
-    /*
-     * This connection will not survive next poll. Do not deliver events,
-     * send data to /dev/null without acking.
-     */
-    if (own) {
-      MG_FREE(buf);
+static int mg_recv_tcp(struct mg_connection *nc, char *buf, size_t len) {
+  int n = 0;
+#if MG_ENABLE_SSL
+  if (nc->flags & MG_F_SSL) {
+    if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {
+      n = mg_ssl_if_read(nc, buf, len);
+      DBG(("%p <- %d bytes (SSL)", nc, n));
+      if (n < 0) {
+        if (n == MG_SSL_WANT_READ) {
+          nc->flags |= MG_F_WANT_READ;
+          n = 0;
+        } else {
+          nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+        }
+      } else if (n > 0) {
+        nc->flags &= ~MG_F_WANT_READ;
+      }
+    } else {
+      mg_ssl_handshake(nc);
     }
-    return;
+  } else
+#endif
+  {
+    n = nc->iface->vtable->tcp_recv(nc, buf, len);
+    DBG(("%p <- %d bytes", nc, n));
   }
-  nc->last_io_time = (time_t) mg_time();
-  if (!own) {
-    mbuf_append(&nc->recv_mbuf, buf, len);
-  } else if (nc->recv_mbuf.len == 0) {
-    /* Adopt buf as recv_mbuf's backing store. */
-    mbuf_free(&nc->recv_mbuf);
-    nc->recv_mbuf.buf = (char *) buf;
-    nc->recv_mbuf.size = nc->recv_mbuf.len = len;
-  } else {
-    mbuf_append(&nc->recv_mbuf, buf, len);
-    MG_FREE(buf);
+  if (n > 0) {
+    nc->recv_mbuf.len += n;
+    nc->last_io_time = (time_t) mg_time();
+#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP
+    if (nc->mgr && nc->mgr->hexdump_file != NULL) {
+      mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, n, MG_EV_RECV);
+    }
+#endif
+    mbuf_trim(&nc->recv_mbuf);
+    mg_call(nc, NULL, nc->user_data, MG_EV_RECV, &n);
+  } else if (n < 0) {
+    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
   }
-  mg_call(nc, NULL, nc->user_data, MG_EV_RECV, &len);
-}
-
-void mg_if_recv_tcp_cb(struct mg_connection *nc, void *buf, int len, int own) {
-  mg_recv_common(nc, buf, len, own);
+  mbuf_trim(&nc->recv_mbuf);
+  return n;
 }
 
-void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len,
-                       union socket_address *sa, size_t sa_len) {
-  assert(nc->flags & MG_F_UDP);
-  DBG(("%p %u", nc, (unsigned int) len));
+static int mg_recv_udp(struct mg_connection *nc, char *buf, size_t len) {
+  int n = 0;
+  struct mg_connection *lc = nc;
+  union socket_address sa;
+  size_t sa_len = sizeof(sa);
+  n = nc->iface->vtable->udp_recv(lc, buf, len, &sa, &sa_len);
+  if (n < 0) {
+    lc->flags |= MG_F_CLOSE_IMMEDIATELY;
+    goto out;
+  }
   if (nc->flags & MG_F_LISTENING) {
-    struct mg_connection *lc = nc;
     /*
      * Do we have an existing connection for this source?
      * This is very inefficient for long connection lists.
      */
+    lc = nc;
     for (nc = mg_next(lc->mgr, NULL); nc != NULL; nc = mg_next(lc->mgr, nc)) {
-      if (memcmp(&nc->sa.sa, &sa->sa, sa_len) == 0 && nc->listener == lc) {
+      if (memcmp(&nc->sa.sa, &sa.sa, sa_len) == 0 && nc->listener == lc) {
         break;
       }
     }
@@ -2718,7 +3044,7 @@  void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len,
       if (nc != NULL) {
         nc->sock = lc->sock;
         nc->listener = lc;
-        nc->sa = *sa;
+        nc->sa = sa;
         nc->proto_handler = lc->proto_handler;
         nc->user_data = lc->user_data;
         nc->recv_mbuf_limit = lc->recv_mbuf_limit;
@@ -2737,19 +3063,91 @@  void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len,
         nc->flags |= MG_F_SEND_AND_CLOSE;
         mg_add_conn(lc->mgr, nc);
         mg_call(nc, NULL, nc->user_data, MG_EV_ACCEPT, &nc->sa);
-      } else {
-        DBG(("OOM"));
-        /* No return here, we still need to drop on the floor */
       }
     }
   }
   if (nc != NULL) {
-    mg_recv_common(nc, buf, len, 1);
-  } else {
-    /* Drop on the floor. */
-    MG_FREE(buf);
+    DBG(("%p <- %d bytes from %s:%d", nc, n, inet_ntoa(nc->sa.sin.sin_addr),
+         ntohs(nc->sa.sin.sin_port)));
+    if (nc == lc) {
+      nc->recv_mbuf.len += n;
+    } else {
+      mbuf_append(&nc->recv_mbuf, buf, n);
+    }
+    mbuf_trim(&lc->recv_mbuf);
+    lc->last_io_time = nc->last_io_time = (time_t) mg_time();
+#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP
+    if (nc->mgr && nc->mgr->hexdump_file != NULL) {
+      mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, n, MG_EV_RECV);
+    }
+#endif
+    if (n != 0) {
+      mg_call(nc, NULL, nc->user_data, MG_EV_RECV, &n);
+    }
   }
-}
+
+out:
+  mbuf_free(&lc->recv_mbuf);
+  return n;
+}
+
+void mg_if_can_send_cb(struct mg_connection *nc) {
+  int n = 0;
+  const char *buf = nc->send_mbuf.buf;
+  size_t len = nc->send_mbuf.len;
+
+  if (nc->flags & (MG_F_CLOSE_IMMEDIATELY | MG_F_CONNECTING)) {
+    return;
+  }
+  if (!(nc->flags & MG_F_UDP)) {
+    if (nc->flags & MG_F_LISTENING) return;
+    if (len > MG_TCP_IO_SIZE) len = MG_TCP_IO_SIZE;
+  }
+#if MG_ENABLE_SSL
+  if (nc->flags & MG_F_SSL) {
+    if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {
+      if (len > 0) {
+        n = mg_ssl_if_write(nc, buf, len);
+        DBG(("%p -> %d bytes (SSL)", nc, n));
+      }
+      if (n < 0) {
+        if (n == MG_SSL_WANT_WRITE) {
+          nc->flags |= MG_F_WANT_WRITE;
+          n = 0;
+        } else {
+          nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+        }
+      } else {
+        nc->flags &= ~MG_F_WANT_WRITE;
+      }
+    } else {
+      mg_ssl_handshake(nc);
+    }
+  } else
+#endif
+      if (len > 0) {
+    if (nc->flags & MG_F_UDP) {
+      n = nc->iface->vtable->udp_send(nc, buf, len);
+    } else {
+      n = nc->iface->vtable->tcp_send(nc, buf, len);
+    }
+    DBG(("%p -> %d bytes", nc, n));
+  }
+
+#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP
+  if (n > 0 && nc->mgr && nc->mgr->hexdump_file != NULL) {
+    mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, n, MG_EV_SEND);
+  }
+#endif
+  if (n < 0) {
+    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+  } else if (n > 0) {
+    nc->last_io_time = (time_t) mg_time();
+    mbuf_remove(&nc->send_mbuf, n);
+    mbuf_trim(&nc->send_mbuf);
+  }
+  if (n != 0) mg_call(nc, NULL, nc->user_data, MG_EV_SEND, &n);
+}
 
 /*
  * Schedules an async connect for a resolved address and proto.
@@ -2760,8 +3158,8 @@  void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len,
 MG_INTERNAL struct mg_connection *mg_do_connect(struct mg_connection *nc,
                                                 int proto,
                                                 union socket_address *sa) {
-  DBG(("%p %s://%s:%hu", nc, proto == SOCK_DGRAM ? "udp" : "tcp",
-       inet_ntoa(sa->sin.sin_addr), ntohs(sa->sin.sin_port)));
+  LOG(LL_DEBUG, ("%p %s://%s:%hu", nc, proto == SOCK_DGRAM ? "udp" : "tcp",
+                 inet_ntoa(sa->sin.sin_addr), ntohs(sa->sin.sin_port)));
 
   nc->flags |= MG_F_CONNECTING;
   if (proto == SOCK_DGRAM) {
@@ -2774,12 +3172,21 @@  MG_INTERNAL struct mg_connection *mg_do_connect(struct mg_connection *nc,
 }
 
 void mg_if_connect_cb(struct mg_connection *nc, int err) {
-  DBG(("%p connect, err=%d", nc, err));
+  LOG(LL_DEBUG,
+      ("%p %s://%s:%hu -> %d", nc, (nc->flags & MG_F_UDP ? "udp" : "tcp"),
+       inet_ntoa(nc->sa.sin.sin_addr), ntohs(nc->sa.sin.sin_port), err));
   nc->flags &= ~MG_F_CONNECTING;
   if (err != 0) {
     nc->flags |= MG_F_CLOSE_IMMEDIATELY;
   }
-  mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &err);
+#if MG_ENABLE_SSL
+  if (err == 0 && (nc->flags & MG_F_SSL)) {
+    mg_ssl_handshake(nc);
+  } else
+#endif
+  {
+    mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &err);
+  }
 }
 
 #if MG_ENABLE_ASYNC_RESOLVER
@@ -2837,6 +3244,16 @@  struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *address,
   return mg_connect_opt(mgr, address, MG_CB(callback, user_data), opts);
 }
 
+void mg_ev_handler_empty(struct mg_connection *c, int ev,
+                         void *ev_data MG_UD_ARG(void *user_data)) {
+  (void) c;
+  (void) ev;
+  (void) ev_data;
+#if MG_ENABLE_CALLBACK_USERDATA
+  (void) user_data;
+#endif
+}
+
 struct mg_connection *mg_connect_opt(struct mg_mgr *mgr, const char *address,
                                      MG_CB(mg_event_handler_t callback,
                                            void *user_data),
@@ -2848,6 +3265,8 @@  struct mg_connection *mg_connect_opt(struct mg_mgr *mgr, const char *address,
 
   MG_COPY_COMMON_CONNECTION_OPTIONS(&add_sock_opts, &opts);
 
+  if (callback == NULL) callback = mg_ev_handler_empty;
+
   if ((nc = mg_create_connection(mgr, callback, add_sock_opts)) == NULL) {
     return NULL;
   }
@@ -2869,7 +3288,8 @@  struct mg_connection *mg_connect_opt(struct mg_mgr *mgr, const char *address,
 #endif
 
 #if MG_ENABLE_SSL
-  DBG(("%p %s %s,%s,%s", nc, address, (opts.ssl_cert ? opts.ssl_cert : "-"),
+  LOG(LL_DEBUG,
+      ("%p %s %s,%s,%s", nc, address, (opts.ssl_cert ? opts.ssl_cert : "-"),
        (opts.ssl_key ? opts.ssl_key : "-"),
        (opts.ssl_ca_cert ? opts.ssl_ca_cert : "-")));
 
@@ -2960,10 +3380,7 @@  struct mg_connection *mg_bind_opt(struct mg_mgr *mgr, const char *address,
   opts.user_data = user_data;
 #endif
 
-  if (callback == NULL) {
-    MG_SET_PTRPTR(opts.error_string, "handler is required");
-    return NULL;
-  }
+  if (callback == NULL) callback = mg_ev_handler_empty;
 
   MG_COPY_COMMON_CONNECTION_OPTIONS(&add_sock_opts, &opts);
 
@@ -3115,18 +3532,18 @@  double mg_set_timer(struct mg_connection *c, double timestamp) {
   DBG(("%p %p %d -> %lu", c, c->priv_2, (c->flags & MG_F_RESOLVING ? 1 : 0),
        (unsigned long) timestamp));
   if ((c->flags & MG_F_RESOLVING) && c->priv_2 != NULL) {
-    ((struct mg_connection *) c->priv_2)->ev_timer_time = timestamp;
+    mg_set_timer((struct mg_connection *) c->priv_2, timestamp);
   }
   return result;
 }
 
-static void mg_sock_set(struct mg_connection *nc, sock_t sock) {
+void mg_sock_set(struct mg_connection *nc, sock_t sock) {
   if (sock != INVALID_SOCKET) {
     nc->iface->vtable->sock_set(nc, sock);
   }
 }
 
-static void mg_if_get_conn_addr(struct mg_connection *nc, int remote,
+void mg_if_get_conn_addr(struct mg_connection *nc, int remote,
                          union socket_address *sa) {
   nc->iface->vtable->get_conn_addr(nc, remote, sa);
 }
@@ -3255,6 +3672,162 @@  struct mg_iface *mg_find_iface(struct mg_mgr *mgr,
   }
   return NULL;
 }
+
+double mg_mgr_min_timer(const struct mg_mgr *mgr) {
+  double min_timer = 0;
+  struct mg_connection *nc;
+  for (nc = mgr->active_connections; nc != NULL; nc = nc->next) {
+    if (nc->ev_timer_time <= 0) continue;
+    if (min_timer == 0 || nc->ev_timer_time < min_timer) {
+      min_timer = nc->ev_timer_time;
+    }
+  }
+  return min_timer;
+}
+#ifdef MG_MODULE_LINES
+#line 1 "mongoose/src/mg_net_if_null.c"
+#endif
+/*
+ * Copyright (c) 2018 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.cesanta.com/license>.
+ */
+
+static void mg_null_if_connect_tcp(struct mg_connection *c,
+                                   const union socket_address *sa) {
+  c->flags |= MG_F_CLOSE_IMMEDIATELY;
+  (void) sa;
+}
+
+static void mg_null_if_connect_udp(struct mg_connection *c) {
+  c->flags |= MG_F_CLOSE_IMMEDIATELY;
+}
+
+static int mg_null_if_listen_tcp(struct mg_connection *c,
+                                 union socket_address *sa) {
+  (void) c;
+  (void) sa;
+  return -1;
+}
+
+static int mg_null_if_listen_udp(struct mg_connection *c,
+                                 union socket_address *sa) {
+  (void) c;
+  (void) sa;
+  return -1;
+}
+
+static int mg_null_if_tcp_send(struct mg_connection *c, const void *buf,
+                               size_t len) {
+  (void) c;
+  (void) buf;
+  (void) len;
+  return -1;
+}
+
+static int mg_null_if_udp_send(struct mg_connection *c, const void *buf,
+                               size_t len) {
+  (void) c;
+  (void) buf;
+  (void) len;
+  return -1;
+}
+
+int mg_null_if_tcp_recv(struct mg_connection *c, void *buf, size_t len) {
+  (void) c;
+  (void) buf;
+  (void) len;
+  return -1;
+}
+
+int mg_null_if_udp_recv(struct mg_connection *c, void *buf, size_t len,
+                        union socket_address *sa, size_t *sa_len) {
+  (void) c;
+  (void) buf;
+  (void) len;
+  (void) sa;
+  (void) sa_len;
+  return -1;
+}
+
+static int mg_null_if_create_conn(struct mg_connection *c) {
+  (void) c;
+  return 1;
+}
+
+static void mg_null_if_destroy_conn(struct mg_connection *c) {
+  (void) c;
+}
+
+static void mg_null_if_sock_set(struct mg_connection *c, sock_t sock) {
+  (void) c;
+  (void) sock;
+}
+
+static void mg_null_if_init(struct mg_iface *iface) {
+  (void) iface;
+}
+
+static void mg_null_if_free(struct mg_iface *iface) {
+  (void) iface;
+}
+
+static void mg_null_if_add_conn(struct mg_connection *c) {
+  c->sock = INVALID_SOCKET;
+  c->flags |= MG_F_CLOSE_IMMEDIATELY;
+}
+
+static void mg_null_if_remove_conn(struct mg_connection *c) {
+  (void) c;
+}
+
+static time_t mg_null_if_poll(struct mg_iface *iface, int timeout_ms) {
+  struct mg_mgr *mgr = iface->mgr;
+  struct mg_connection *nc, *tmp;
+  double now = mg_time();
+  /* We basically just run timers and poll. */
+  for (nc = mgr->active_connections; nc != NULL; nc = tmp) {
+    tmp = nc->next;
+    mg_if_poll(nc, now);
+  }
+  (void) timeout_ms;
+  return (time_t) now;
+}
+
+static void mg_null_if_get_conn_addr(struct mg_connection *c, int remote,
+                                     union socket_address *sa) {
+  (void) c;
+  (void) remote;
+  (void) sa;
+}
+
+#define MG_NULL_IFACE_VTABLE                                                   \
+  {                                                                            \
+    mg_null_if_init, mg_null_if_free, mg_null_if_add_conn,                     \
+        mg_null_if_remove_conn, mg_null_if_poll, mg_null_if_listen_tcp,        \
+        mg_null_if_listen_udp, mg_null_if_connect_tcp, mg_null_if_connect_udp, \
+        mg_null_if_tcp_send, mg_null_if_udp_send, mg_null_if_tcp_recv,         \
+        mg_null_if_udp_recv, mg_null_if_create_conn, mg_null_if_destroy_conn,  \
+        mg_null_if_sock_set, mg_null_if_get_conn_addr,                         \
+  }
+
+const struct mg_iface_vtable mg_null_iface_vtable = MG_NULL_IFACE_VTABLE;
+
+#if MG_NET_IF == MG_NET_IF_NULL
+const struct mg_iface_vtable mg_default_iface_vtable = MG_NULL_IFACE_VTABLE;
+#endif /* MG_NET_IF == MG_NET_IF_NULL */
 #ifdef MG_MODULE_LINES
 #line 1 "mongoose/src/mg_net_if_socket.c"
 #endif
@@ -3269,16 +3842,10 @@  struct mg_iface *mg_find_iface(struct mg_mgr *mgr,
 /* Amalgamated: #include "mg_internal.h" */
 /* Amalgamated: #include "mg_util.h" */
 
-#define MG_TCP_RECV_BUFFER_SIZE 1024
-#define MG_UDP_RECV_BUFFER_SIZE 1500
-
 static sock_t mg_open_listening_socket(union socket_address *sa, int type,
                                        int proto);
-#if MG_ENABLE_SSL
-static void mg_ssl_begin(struct mg_connection *nc);
-#endif
 
-static void mg_set_non_blocking_mode(sock_t sock) {
+void mg_set_non_blocking_mode(sock_t sock) {
 #ifdef _WIN32
   unsigned long on = 1;
   ioctlsocket(sock, FIONBIO, &on);
@@ -3300,7 +3867,7 @@  static int mg_is_error(void) {
       ;
 }
 
-static void mg_socket_if_connect_tcp(struct mg_connection *nc,
+void mg_socket_if_connect_tcp(struct mg_connection *nc,
                               const union socket_address *sa) {
   int rc, proto = 0;
   nc->sock = socket(AF_INET, SOCK_STREAM, proto);
@@ -3317,7 +3884,7 @@  static void mg_socket_if_connect_tcp(struct mg_connection *nc,
        nc->err));
 }
 
-static void mg_socket_if_connect_udp(struct mg_connection *nc) {
+void mg_socket_if_connect_udp(struct mg_connection *nc) {
   nc->sock = socket(AF_INET, SOCK_DGRAM, 0);
   if (nc->sock == INVALID_SOCKET) {
     nc->err = mg_get_errno() ? mg_get_errno() : 1;
@@ -3334,7 +3901,7 @@  static void mg_socket_if_connect_udp(struct mg_connection *nc) {
   nc->err = 0;
 }
 
-static int mg_socket_if_listen_tcp(struct mg_connection *nc,
+int mg_socket_if_listen_tcp(struct mg_connection *nc,
                             union socket_address *sa) {
   int proto = 0;
   sock_t sock = mg_open_listening_socket(sa, SOCK_STREAM, proto);
@@ -3346,34 +3913,55 @@  static int mg_socket_if_listen_tcp(struct mg_connection *nc,
 }
 
 static int mg_socket_if_listen_udp(struct mg_connection *nc,
-                            union socket_address *sa) {
+                                   union socket_address *sa) {
   sock_t sock = mg_open_listening_socket(sa, SOCK_DGRAM, 0);
   if (sock == INVALID_SOCKET) return (mg_get_errno() ? mg_get_errno() : 1);
   mg_sock_set(nc, sock);
   return 0;
 }
 
-static void mg_socket_if_tcp_send(struct mg_connection *nc, const void *buf,
-                           size_t len) {
-  mbuf_append(&nc->send_mbuf, buf, len);
+static int mg_socket_if_tcp_send(struct mg_connection *nc, const void *buf,
+                                 size_t len) {
+  int n = (int) MG_SEND_FUNC(nc->sock, buf, len, 0);
+  if (n < 0 && !mg_is_error()) n = 0;
+  return n;
 }
 
-static void mg_socket_if_udp_send(struct mg_connection *nc, const void *buf,
-                           size_t len) {
-  mbuf_append(&nc->send_mbuf, buf, len);
+static int mg_socket_if_udp_send(struct mg_connection *nc, const void *buf,
+                                 size_t len) {
+  int n = sendto(nc->sock, buf, len, 0, &nc->sa.sa, sizeof(nc->sa.sin));
+  if (n < 0 && !mg_is_error()) n = 0;
+  return n;
 }
 
-static void mg_socket_if_recved(struct mg_connection *nc, size_t len) {
-  (void) nc;
-  (void) len;
+static int mg_socket_if_tcp_recv(struct mg_connection *nc, void *buf,
+                                 size_t len) {
+  int n = (int) MG_RECV_FUNC(nc->sock, buf, len, 0);
+  if (n == 0) {
+    /* Orderly shutdown of the socket, try flushing output. */
+    nc->flags |= MG_F_SEND_AND_CLOSE;
+  } else if (n < 0 && !mg_is_error()) {
+    n = 0;
+  }
+  return n;
 }
 
-static int mg_socket_if_create_conn(struct mg_connection *nc) {
+static int mg_socket_if_udp_recv(struct mg_connection *nc, void *buf,
+                                 size_t len, union socket_address *sa,
+                                 size_t *sa_len) {
+  socklen_t sa_len_st = *sa_len;
+  int n = recvfrom(nc->sock, buf, len, 0, &sa->sa, &sa_len_st);
+  *sa_len = sa_len_st;
+  if (n < 0 && !mg_is_error()) n = 0;
+  return n;
+}
+
+int mg_socket_if_create_conn(struct mg_connection *nc) {
   (void) nc;
   return 1;
 }
 
-static void mg_socket_if_destroy_conn(struct mg_connection *nc) {
+void mg_socket_if_destroy_conn(struct mg_connection *nc) {
   if (nc->sock == INVALID_SOCKET) return;
   if (!(nc->flags & MG_F_UDP)) {
     closesocket(nc->sock);
@@ -3391,7 +3979,9 @@  static int mg_accept_conn(struct mg_connection *lc) {
   /* NOTE(lsm): on Windows, sock is always > FD_SETSIZE */
   sock_t sock = accept(lc->sock, &sa.sa, &sa_len);
   if (sock == INVALID_SOCKET) {
-    if (mg_is_error()) DBG(("%p: failed to accept: %d", lc, mg_get_errno()));
+    if (mg_is_error()) {
+      DBG(("%p: failed to accept: %d", lc, mg_get_errno()));
+    }
     return 0;
   }
   nc = mg_if_accept_new_conn(lc);
@@ -3402,14 +3992,7 @@  static int mg_accept_conn(struct mg_connection *lc) {
   DBG(("%p conn from %s:%d", nc, inet_ntoa(sa.sin.sin_addr),
        ntohs(sa.sin.sin_port)));
   mg_sock_set(nc, sock);
-#if MG_ENABLE_SSL
-  if (lc->flags & MG_F_SSL) {
-    if (mg_ssl_if_conn_accept(nc, lc) != MG_SSL_OK) mg_close_conn(nc);
-  } else
-#endif
-  {
-    mg_if_accept_tcp_cb(nc, &sa, sa_len);
-  }
+  mg_if_accept_tcp_cb(nc, &sa, sa_len);
   return 1;
 }
 
@@ -3460,178 +4043,21 @@  static sock_t mg_open_listening_socket(union socket_address *sa, int type,
   return sock;
 }
 
-static void mg_write_to_socket(struct mg_connection *nc) {
-  struct mbuf *io = &nc->send_mbuf;
-  int n = 0;
-
-#if MG_LWIP
-  /* With LWIP we don't know if the socket is ready */
-  if (io->len == 0) return;
-#endif
-
-  assert(io->len > 0);
-
-  if (nc->flags & MG_F_UDP) {
-    n =
-        sendto(nc->sock, io->buf, io->len, 0, &nc->sa.sa, sizeof(nc->sa.sin));
-    DBG(("%p %d %d %d %s:%hu", nc, nc->sock, n, mg_get_errno(),
-         inet_ntoa(nc->sa.sin.sin_addr), ntohs(nc->sa.sin.sin_port)));
-    mg_if_sent_cb(nc, n);
-    return;
-  }
-
-#if MG_ENABLE_SSL
-  if (nc->flags & MG_F_SSL) {
-    if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {
-      n = mg_ssl_if_write(nc, io->buf, io->len);
-      DBG(("%p %d bytes -> %d (SSL)", nc, n, nc->sock));
-      if (n < 0) {
-        if (n != MG_SSL_WANT_READ && n != MG_SSL_WANT_WRITE) {
-          nc->flags |= MG_F_CLOSE_IMMEDIATELY;
-        }
-        return;
-      } else {
-        /* Successful SSL operation, clear off SSL wait flags */
-        nc->flags &= ~(MG_F_WANT_READ | MG_F_WANT_WRITE);
-      }
-    } else {
-      mg_ssl_begin(nc);
-      return;
-    }
-  } else
-#endif
-  {
-    n = (int) MG_SEND_FUNC(nc->sock, io->buf, io->len, 0);
-    DBG(("%p %d bytes -> %d", nc, n, nc->sock));
-  }
-
-  mg_if_sent_cb(nc, n);
-}
-
-MG_INTERNAL size_t recv_avail_size(struct mg_connection *conn, size_t max) {
-  size_t avail;
-  if (conn->recv_mbuf_limit < conn->recv_mbuf.len) return 0;
-  avail = conn->recv_mbuf_limit - conn->recv_mbuf.len;
-  return avail > max ? max : avail;
-}
-
-static void mg_handle_tcp_read(struct mg_connection *conn) {
-  int n = 0;
-  char *buf = (char *) MG_MALLOC(MG_TCP_RECV_BUFFER_SIZE);
-
-  if (buf == NULL) {
-    DBG(("OOM"));
-    return;
-  }
-
-#if MG_ENABLE_SSL
-  if (conn->flags & MG_F_SSL) {
-    if (conn->flags & MG_F_SSL_HANDSHAKE_DONE) {
-      /* SSL library may have more bytes ready to read than we ask to read.
-       * Therefore, read in a loop until we read everything. Without the loop,
-       * we skip to the next select() cycle which can just timeout. */
-      while ((n = mg_ssl_if_read(conn, buf, MG_TCP_RECV_BUFFER_SIZE)) > 0) {
-        DBG(("%p %d bytes <- %d (SSL)", conn, n, conn->sock));
-        mg_if_recv_tcp_cb(conn, buf, n, 1 /* own */);
-        buf = NULL;
-        if (conn->flags & MG_F_CLOSE_IMMEDIATELY) break;
-        /* buf has been freed, we need a new one. */
-        buf = (char *) MG_MALLOC(MG_TCP_RECV_BUFFER_SIZE);
-        if (buf == NULL) break;
-      }
-      MG_FREE(buf);
-      if (n < 0 && n != MG_SSL_WANT_READ) conn->flags |= MG_F_CLOSE_IMMEDIATELY;
-    } else {
-      MG_FREE(buf);
-      mg_ssl_begin(conn);
-      return;
-    }
-  } else
-#endif
-  {
-    n = (int) MG_RECV_FUNC(conn->sock, buf,
-                           recv_avail_size(conn, MG_TCP_RECV_BUFFER_SIZE), 0);
-    DBG(("%p %d bytes (PLAIN) <- %d", conn, n, conn->sock));
-    if (n > 0) {
-      mg_if_recv_tcp_cb(conn, buf, n, 1 /* own */);
-    } else {
-      MG_FREE(buf);
-    }
-    if (n == 0) {
-      /* Orderly shutdown of the socket, try flushing output. */
-      conn->flags |= MG_F_SEND_AND_CLOSE;
-    } else if (n < 0 && mg_is_error()) {
-      conn->flags |= MG_F_CLOSE_IMMEDIATELY;
-    }
-  }
-}
-
-static int mg_recvfrom(struct mg_connection *nc, union socket_address *sa,
-                       socklen_t *sa_len, char **buf) {
-  int n;
-  *buf = (char *) MG_MALLOC(MG_UDP_RECV_BUFFER_SIZE);
-  if (*buf == NULL) {
-    DBG(("Out of memory"));
-    return -ENOMEM;
-  }
-  n = recvfrom(nc->sock, *buf, MG_UDP_RECV_BUFFER_SIZE, 0, &sa->sa, sa_len);
-  if (n <= 0) {
-    DBG(("%p recvfrom: %s", nc, strerror(mg_get_errno())));
-    MG_FREE(*buf);
-  }
-  return n;
-}
-
-static void mg_handle_udp_read(struct mg_connection *nc) {
-  char *buf = NULL;
-  union socket_address sa;
-  socklen_t sa_len = sizeof(sa);
-  int n = mg_recvfrom(nc, &sa, &sa_len, &buf);
-  DBG(("%p %d bytes from %s:%d", nc, n, inet_ntoa(nc->sa.sin.sin_addr),
-       ntohs(nc->sa.sin.sin_port)));
-  mg_if_recv_udp_cb(nc, buf, n, &sa, sa_len);
-}
-
-#if MG_ENABLE_SSL
-static void mg_ssl_begin(struct mg_connection *nc) {
-  int server_side = (nc->listener != NULL);
-  enum mg_ssl_if_result res = mg_ssl_if_handshake(nc);
-  DBG(("%p %d res %d", nc, server_side, res));
-
-  if (res == MG_SSL_OK) {
-    nc->flags |= MG_F_SSL_HANDSHAKE_DONE;
-    nc->flags &= ~(MG_F_WANT_READ | MG_F_WANT_WRITE);
-
-    if (server_side) {
-      union socket_address sa;
-      socklen_t sa_len = sizeof(sa);
-      (void) getpeername(nc->sock, &sa.sa, &sa_len);
-      mg_if_accept_tcp_cb(nc, &sa, sa_len);
-    } else {
-      mg_if_connect_cb(nc, 0);
-    }
-  } else if (res != MG_SSL_WANT_READ && res != MG_SSL_WANT_WRITE) {
-    if (!server_side) {
-      mg_if_connect_cb(nc, res);
-    }
-    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
-  }
-}
-#endif /* MG_ENABLE_SSL */
-
 #define _MG_F_FD_CAN_READ 1
 #define _MG_F_FD_CAN_WRITE 1 << 1
 #define _MG_F_FD_ERROR 1 << 2
 
-static void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {
+void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {
   int worth_logging =
       fd_flags != 0 || (nc->flags & (MG_F_WANT_READ | MG_F_WANT_WRITE));
   if (worth_logging) {
-    DBG(("%p fd=%d fd_flags=%d nc_flags=%lu rmbl=%d smbl=%d", nc, nc->sock,
+    DBG(("%p fd=%d fd_flags=%d nc_flags=0x%lx rmbl=%d smbl=%d", nc, nc->sock,
          fd_flags, nc->flags, (int) nc->recv_mbuf.len,
          (int) nc->send_mbuf.len));
   }
 
+  if (!mg_if_poll(nc, now)) return;
+
   if (nc->flags & MG_F_CONNECTING) {
     if (fd_flags != 0) {
       int err = 0;
@@ -3652,15 +4078,7 @@  static void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double no
        */
       err = nc->err;
 #endif
-#if MG_ENABLE_SSL
-      if ((nc->flags & MG_F_SSL) && err == 0) {
-        mg_ssl_begin(nc);
-      } else {
-        mg_if_connect_cb(nc, err);
-      }
-#else
       mg_if_connect_cb(nc, err);
-#endif
     } else if (nc->err != 0) {
       mg_if_connect_cb(nc, nc->err);
     }
@@ -3668,7 +4086,7 @@  static void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double no
 
   if (fd_flags & _MG_F_FD_CAN_READ) {
     if (nc->flags & MG_F_UDP) {
-      mg_handle_udp_read(nc);
+      mg_if_can_recv_cb(nc);
     } else {
       if (nc->flags & MG_F_LISTENING) {
         /*
@@ -3678,22 +4096,16 @@  static void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double no
          */
         mg_accept_conn(nc);
       } else {
-        mg_handle_tcp_read(nc);
+        mg_if_can_recv_cb(nc);
       }
     }
   }
 
-  if (!(nc->flags & MG_F_CLOSE_IMMEDIATELY)) {
-    if ((fd_flags & _MG_F_FD_CAN_WRITE) && nc->send_mbuf.len > 0) {
-      mg_write_to_socket(nc);
-    }
-    mg_if_poll(nc, (time_t) now);
-    mg_if_timer(nc, now);
-  }
+  if (fd_flags & _MG_F_FD_CAN_WRITE) mg_if_can_send_cb(nc);
 
   if (worth_logging) {
-    DBG(("%p after fd=%d nc_flags=%lu rmbl=%d smbl=%d", nc, nc->sock, nc->flags,
-         (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
+    DBG(("%p after fd=%d nc_flags=0x%lx rmbl=%d smbl=%d", nc, nc->sock,
+         nc->flags, (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
   }
 }
 
@@ -3716,14 +4128,14 @@  static void mg_mgr_handle_ctl_sock(struct mg_mgr *mgr) {
 #endif
 
 /* Associate a socket to a connection. */
-static void mg_socket_if_sock_set(struct mg_connection *nc, sock_t sock) {
+void mg_socket_if_sock_set(struct mg_connection *nc, sock_t sock) {
   mg_set_non_blocking_mode(sock);
   mg_set_close_on_exec(sock);
   nc->sock = sock;
   DBG(("%p %d", nc, sock));
 }
 
-static void mg_socket_if_init(struct mg_iface *iface) {
+void mg_socket_if_init(struct mg_iface *iface) {
   (void) iface;
   DBG(("%p using select()", iface->mgr));
 #if MG_ENABLE_BROADCAST
@@ -3731,19 +4143,19 @@  static void mg_socket_if_init(struct mg_iface *iface) {
 #endif
 }
 
-static void mg_socket_if_free(struct mg_iface *iface) {
+void mg_socket_if_free(struct mg_iface *iface) {
   (void) iface;
 }
 
-static void mg_socket_if_add_conn(struct mg_connection *nc) {
+void mg_socket_if_add_conn(struct mg_connection *nc) {
   (void) nc;
 }
 
-static void mg_socket_if_remove_conn(struct mg_connection *nc) {
+void mg_socket_if_remove_conn(struct mg_connection *nc) {
   (void) nc;
 }
 
-static void mg_add_to_set(sock_t sock, fd_set *set, sock_t *max_fd) {
+void mg_add_to_set(sock_t sock, fd_set *set, sock_t *max_fd) {
   if (sock != INVALID_SOCKET
 #ifdef __unix__
       && sock < (sock_t) FD_SETSIZE
@@ -3756,7 +4168,7 @@  static void mg_add_to_set(sock_t sock, fd_set *set, sock_t *max_fd) {
   }
 }
 
-static time_t mg_socket_if_poll(struct mg_iface *iface, int timeout_ms) {
+time_t mg_socket_if_poll(struct mg_iface *iface, int timeout_ms) {
   struct mg_mgr *mgr = iface->mgr;
   double now = mg_time();
   double min_timer;
@@ -3807,8 +4219,7 @@  static time_t mg_socket_if_poll(struct mg_iface *iface, int timeout_ms) {
       }
 #endif
 
-      if (!(nc->flags & MG_F_WANT_WRITE) &&
-          nc->recv_mbuf.len < nc->recv_mbuf_limit &&
+      if (nc->recv_mbuf.len < nc->recv_mbuf_limit &&
           (!(nc->flags & MG_F_UDP) || nc->listener == NULL)) {
         mg_add_to_set(nc->sock, &read_set, &max_fd);
       }
@@ -3879,14 +4290,6 @@  static time_t mg_socket_if_poll(struct mg_iface *iface, int timeout_ms) {
     mg_mgr_handle_conn(nc, fd_flags, now);
   }
 
-  for (nc = mgr->active_connections; nc != NULL; nc = tmp) {
-    tmp = nc->next;
-    if ((nc->flags & MG_F_CLOSE_IMMEDIATELY) ||
-        (nc->send_mbuf.len == 0 && (nc->flags & MG_F_SEND_AND_CLOSE))) {
-      mg_close_conn(nc);
-    }
-  }
-
   return (time_t) now;
 }
 
@@ -3912,7 +4315,7 @@  mg_socketpair_accept(sock_t sock, union socket_address *sa, socklen_t sa_len) {
 }
 
 int mg_socketpair(sock_t sp[2], int sock_type) {
-  union socket_address sa;
+  union socket_address sa, sa2;
   sock_t sock;
   socklen_t len = sizeof(sa.sin);
   int ret = 0;
@@ -3921,18 +4324,20 @@  int mg_socketpair(sock_t sp[2], int sock_type) {
 
   (void) memset(&sa, 0, sizeof(sa));
   sa.sin.sin_family = AF_INET;
-  sa.sin.sin_port = htons(0);
   sa.sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */
+  sa2 = sa;
 
   if ((sock = socket(AF_INET, sock_type, 0)) == INVALID_SOCKET) {
   } else if (bind(sock, &sa.sa, len) != 0) {
   } else if (sock_type == SOCK_STREAM && listen(sock, 1) != 0) {
   } else if (getsockname(sock, &sa.sa, &len) != 0) {
   } else if ((sp[0] = socket(AF_INET, sock_type, 0)) == INVALID_SOCKET) {
-  } else if (connect(sp[0], &sa.sa, len) != 0) {
+  } else if (sock_type == SOCK_STREAM && connect(sp[0], &sa.sa, len) != 0) {
   } else if (sock_type == SOCK_DGRAM &&
-             (getsockname(sp[0], &sa.sa, &len) != 0 ||
-              connect(sock, &sa.sa, len) != 0)) {
+             (bind(sp[0], &sa2.sa, len) != 0 ||
+              getsockname(sp[0], &sa2.sa, &len) != 0 ||
+              connect(sp[0], &sa.sa, len) != 0 ||
+              connect(sock, &sa2.sa, len) != 0)) {
   } else if ((sp[1] = (sock_type == SOCK_DGRAM ? sock : mg_socketpair_accept(
                                                             sock, &sa, len))) ==
              INVALID_SOCKET) {
@@ -3970,7 +4375,7 @@  void mg_sock_to_str(sock_t sock, char *buf, size_t len, int flags) {
   mg_sock_addr_to_str(&sa, buf, len, flags);
 }
 
-static void mg_socket_if_get_conn_addr(struct mg_connection *nc, int remote,
+void mg_socket_if_get_conn_addr(struct mg_connection *nc, int remote,
                                 union socket_address *sa) {
   if ((nc->flags & MG_F_UDP) && remote) {
     memcpy(sa, &nc->sa, sizeof(*sa));
@@ -3993,7 +4398,8 @@  static void mg_socket_if_get_conn_addr(struct mg_connection *nc, int remote,
     mg_socket_if_connect_udp,                                           \
     mg_socket_if_tcp_send,                                              \
     mg_socket_if_udp_send,                                              \
-    mg_socket_if_recved,                                                \
+    mg_socket_if_tcp_recv,                                              \
+    mg_socket_if_udp_recv,                                              \
     mg_socket_if_create_conn,                                           \
     mg_socket_if_destroy_conn,                                          \
     mg_socket_if_sock_set,                                              \
@@ -4021,18 +4427,35 @@  struct socksdata {
   char *proxy_addr;        /* HOST:PORT of the socks5 proxy server */
   struct mg_connection *s; /* Respective connection to the server */
   struct mg_connection *c; /* Connection to the client */
-  struct mbuf tmp;         /* Temporary buffer for sent data */
 };
 
 static void socks_if_disband(struct socksdata *d) {
   LOG(LL_DEBUG, ("disbanding proxy %p %p", d->c, d->s));
-  if (d->c) d->c->flags |= MG_F_SEND_AND_CLOSE;
-  if (d->s) d->s->flags |= MG_F_SEND_AND_CLOSE;
-  d->c = d->s = NULL;
+  if (d->c) {
+    d->c->flags |= MG_F_SEND_AND_CLOSE;
+    d->c->user_data = NULL;
+    d->c = NULL;
+  }
+  if (d->s) {
+    d->s->flags |= MG_F_SEND_AND_CLOSE;
+    d->s->user_data = NULL;
+    d->s = NULL;
+  }
+}
+
+static void socks_if_relay(struct mg_connection *s) {
+  struct socksdata *d = (struct socksdata *) s->user_data;
+  if (d == NULL || d->c == NULL || !(s->flags & MG_SOCKS_CONNECT_DONE) ||
+      d->s == NULL) {
+    return;
+  }
+  if (s->recv_mbuf.len > 0) mg_if_can_recv_cb(d->c);
+  if (d->c->send_mbuf.len > 0 && s->send_mbuf.len == 0) mg_if_can_send_cb(d->c);
 }
 
 static void socks_if_handler(struct mg_connection *c, int ev, void *ev_data) {
   struct socksdata *d = (struct socksdata *) c->user_data;
+  if (d == NULL) return;
   if (ev == MG_EV_CONNECT) {
     int res = *(int *) ev_data;
     if (res == 0) {
@@ -4065,6 +4488,7 @@  static void socks_if_handler(struct mg_connection *c, int ev, void *ev_data) {
       memcpy(buf + 4, &d->c->sa.sin.sin_addr, 4);
       memcpy(buf + 8, &d->c->sa.sin.sin_port, 2);
       mg_send(c, buf, sizeof(buf));
+      LOG(LL_DEBUG, ("%p Sent connect request", c));
     }
     /* Process connect request */
     if ((c->flags & MG_SOCKS_HANDSHAKE_DONE) &&
@@ -4077,17 +4501,12 @@  static void socks_if_handler(struct mg_connection *c, int ev, void *ev_data) {
       }
       mbuf_remove(&c->recv_mbuf, 10);
       c->flags |= MG_SOCKS_CONNECT_DONE;
-      /* Connected. Move sent data from client, if any, to server */
-      if (d->s && d->c) {
-        mbuf_append(&d->s->send_mbuf, d->tmp.buf, d->tmp.len);
-        mbuf_free(&d->tmp);
-      }
-    }
-    /* All flags are set, we're in relay mode */
-    if ((c->flags & MG_SOCKS_CONNECT_DONE) && d->c && d->s) {
-      mbuf_append(&d->c->recv_mbuf, d->s->recv_mbuf.buf, d->s->recv_mbuf.len);
-      mbuf_remove(&d->s->recv_mbuf, d->s->recv_mbuf.len);
+      LOG(LL_DEBUG, ("%p Connect done %p", c, d->c));
+      mg_if_connect_cb(d->c, 0);
     }
+    socks_if_relay(c);
+  } else if (ev == MG_EV_SEND || ev == MG_EV_POLL) {
+    socks_if_relay(c);
   }
 }
 
@@ -4097,7 +4516,7 @@  static void mg_socks_if_connect_tcp(struct mg_connection *c,
   d->c = c;
   d->s = mg_connect(c->mgr, d->proxy_addr, socks_if_handler);
   d->s->user_data = d;
-  LOG(LL_DEBUG, ("%p %s", c, d->proxy_addr));
+  LOG(LL_DEBUG, ("%p %s %p %p", c, d->proxy_addr, d, d->s));
   (void) sa;
 }
 
@@ -4119,29 +4538,44 @@  static int mg_socks_if_listen_udp(struct mg_connection *c,
   return -1;
 }
 
-static void mg_socks_if_tcp_send(struct mg_connection *c, const void *buf,
-                                 size_t len) {
+static int mg_socks_if_tcp_send(struct mg_connection *c, const void *buf,
+                                size_t len) {
+  int res;
   struct socksdata *d = (struct socksdata *) c->iface->data;
-  LOG(LL_DEBUG, ("%p -> %p %d %d", c, buf, (int) len, (int) c->send_mbuf.len));
-  if (d && d->s && d->s->flags & MG_SOCKS_CONNECT_DONE) {
-    mbuf_append(&d->s->send_mbuf, d->tmp.buf, d->tmp.len);
-    mbuf_append(&d->s->send_mbuf, buf, len);
-    mbuf_free(&d->tmp);
-  } else {
-    mbuf_append(&d->tmp, buf, len);
-  }
+  if (d->s == NULL) return -1;
+  res = (int) mbuf_append(&d->s->send_mbuf, buf, len);
+  DBG(("%p -> %d -> %p", c, res, d->s));
+  return res;
 }
 
-static void mg_socks_if_udp_send(struct mg_connection *c, const void *buf,
-                                 size_t len) {
+static int mg_socks_if_udp_send(struct mg_connection *c, const void *buf,
+                                size_t len) {
   (void) c;
   (void) buf;
   (void) len;
+  return -1;
+}
+
+int mg_socks_if_tcp_recv(struct mg_connection *c, void *buf, size_t len) {
+  struct socksdata *d = (struct socksdata *) c->iface->data;
+  if (d->s == NULL) return -1;
+  if (len > d->s->recv_mbuf.len) len = d->s->recv_mbuf.len;
+  if (len > 0) {
+    memcpy(buf, d->s->recv_mbuf.buf, len);
+    mbuf_remove(&d->s->recv_mbuf, len);
+  }
+  DBG(("%p <- %d <- %p", c, (int) len, d->s));
+  return len;
 }
 
-static void mg_socks_if_recved(struct mg_connection *c, size_t len) {
+int mg_socks_if_udp_recv(struct mg_connection *c, void *buf, size_t len,
+                         union socket_address *sa, size_t *sa_len) {
   (void) c;
+  (void) buf;
   (void) len;
+  (void) sa;
+  (void) sa_len;
+  return -1;
 }
 
 static int mg_socks_if_create_conn(struct mg_connection *c) {
@@ -4170,7 +4604,6 @@  static void mg_socks_if_free(struct mg_iface *iface) {
   LOG(LL_DEBUG, ("%p", iface));
   if (d != NULL) {
     socks_if_disband(d);
-    mbuf_free(&d->tmp);
     MG_FREE(d->proxy_addr);
     MG_FREE(d);
     iface->data = NULL;
@@ -4201,14 +4634,15 @@  static void mg_socks_if_get_conn_addr(struct mg_connection *c, int remote,
 }
 
 const struct mg_iface_vtable mg_socks_iface_vtable = {
-    mg_socks_if_init,        mg_socks_if_free,
-    mg_socks_if_add_conn,    mg_socks_if_remove_conn,
-    mg_socks_if_poll,        mg_socks_if_listen_tcp,
-    mg_socks_if_listen_udp,  mg_socks_if_connect_tcp,
-    mg_socks_if_connect_udp, mg_socks_if_tcp_send,
-    mg_socks_if_udp_send,    mg_socks_if_recved,
-    mg_socks_if_create_conn, mg_socks_if_destroy_conn,
-    mg_socks_if_sock_set,    mg_socks_if_get_conn_addr,
+    mg_socks_if_init,          mg_socks_if_free,
+    mg_socks_if_add_conn,      mg_socks_if_remove_conn,
+    mg_socks_if_poll,          mg_socks_if_listen_tcp,
+    mg_socks_if_listen_udp,    mg_socks_if_connect_tcp,
+    mg_socks_if_connect_udp,   mg_socks_if_tcp_send,
+    mg_socks_if_udp_send,      mg_socks_if_tcp_recv,
+    mg_socks_if_udp_recv,      mg_socks_if_create_conn,
+    mg_socks_if_destroy_conn,  mg_socks_if_sock_set,
+    mg_socks_if_get_conn_addr,
 };
 
 struct mg_iface *mg_socks_mk_iface(struct mg_mgr *mgr, const char *proxy_addr) {
@@ -4234,6 +4668,9 @@  struct mg_iface *mg_socks_mk_iface(struct mg_mgr *mgr, const char *proxy_addr) {
 #endif
 
 #include <openssl/ssl.h>
+#ifndef KR_VERSION
+#include <openssl/tls1.h>
+#endif
 
 struct mg_ssl_if_ctx {
   SSL *ssl;
@@ -4242,7 +4679,7 @@  struct mg_ssl_if_ctx {
   size_t identity_len;
 };
 
-void mg_ssl_if_init(void) {
+void mg_ssl_if_init() {
   SSL_library_init();
 }
 
@@ -4318,14 +4755,6 @@  enum mg_ssl_if_result mg_ssl_if_conn_init(
     return MG_SSL_ERROR;
   }
 
-  if (params->server_name != NULL) {
-#ifdef KR_VERSION
-    SSL_CTX_kr_set_verify_name(ctx->ssl_ctx, params->server_name);
-#else
-/* TODO(rojer): Implement server name verification on OpenSSL. */
-#endif
-  }
-
   if (mg_set_cipher_list(ctx->ssl_ctx, params->cipher_suites) != MG_SSL_OK) {
     MG_SET_PTRPTR(err_msg, "Invalid cipher suite list");
     return MG_SSL_ERROR;
@@ -4344,6 +4773,14 @@  enum mg_ssl_if_result mg_ssl_if_conn_init(
     return MG_SSL_ERROR;
   }
 
+  if (params->server_name != NULL) {
+#ifdef KR_VERSION
+    SSL_CTX_kr_set_verify_name(ctx->ssl_ctx, params->server_name);
+#else
+    SSL_set_tlsext_host_name(ctx->ssl, params->server_name);
+#endif
+  }
+
   nc->flags |= MG_F_SSL;
 
   return MG_SSL_OK;
@@ -4531,7 +4968,7 @@  static enum mg_ssl_if_result mg_set_cipher_list(SSL_CTX *ctx, const char *cl) {
               : MG_SSL_ERROR);
 }
 
-#ifndef KR_VERSION
+#if !defined(KR_VERSION) && !defined(LIBRESSL_VERSION_NUMBER)
 static unsigned int mg_ssl_if_ossl_psk_cb(SSL *ssl, const char *hint,
                                           char *identity,
                                           unsigned int max_identity_len,
@@ -4597,10 +5034,10 @@  static enum mg_ssl_if_result mg_ssl_if_ossl_set_psk(struct mg_ssl_if_ctx *ctx,
   (void) ctx;
   (void) identity;
   (void) key_str;
-  /* Krypton does not support PSK. */
+  /* Krypton / LibreSSL does not support PSK. */
   return MG_SSL_ERROR;
 }
-#endif /* defined(KR_VERSION) */
+#endif /* !defined(KR_VERSION) && !defined(LIBRESSL_VERSION_NUMBER) */
 
 const char *mg_set_ssl(struct mg_connection *nc, const char *cert,
                        const char *ca_cert) {
@@ -4628,9 +5065,12 @@  const char *mg_set_ssl(struct mg_connection *nc, const char *cert,
 
 #include <mbedtls/debug.h>
 #include <mbedtls/ecp.h>
+#include <mbedtls/net.h>
 #include <mbedtls/platform.h>
 #include <mbedtls/ssl.h>
+#include <mbedtls/ssl_internal.h>
 #include <mbedtls/x509_crt.h>
+#include <mbedtls/version.h>
 
 static void mg_ssl_mbed_log(void *ctx, int level, const char *file, int line,
                             const char *str) {
@@ -4640,6 +5080,8 @@  static void mg_ssl_mbed_log(void *ctx, int level, const char *file, int line,
       cs_level = LL_ERROR;
       break;
     case 2:
+      cs_level = LL_INFO;
+      break;
     case 3:
       cs_level = LL_DEBUG;
       break;
@@ -4648,8 +5090,11 @@  static void mg_ssl_mbed_log(void *ctx, int level, const char *file, int line,
   }
   /* mbedTLS passes strings with \n at the end, strip it. */
   LOG(cs_level, ("%p %.*s", ctx, (int) (strlen(str) - 1), str));
+  (void) ctx;
+  (void) str;
   (void) file;
   (void) line;
+  (void) cs_level;
 }
 
 struct mg_ssl_if_ctx {
@@ -4659,12 +5104,14 @@  struct mg_ssl_if_ctx {
   mbedtls_pk_context *key;
   mbedtls_x509_crt *ca_cert;
   struct mbuf cipher_suites;
+  size_t saved_len;
 };
 
 /* Must be provided by the platform. ctx is struct mg_connection. */
 extern int mg_ssl_if_mbed_random(void *ctx, unsigned char *buf, size_t len);
 
-void mg_ssl_if_init(void) {
+void mg_ssl_if_init() {
+  LOG(LL_INFO, ("%s", MBEDTLS_VERSION_STRING_FULL));
 }
 
 enum mg_ssl_if_result mg_ssl_if_conn_accept(struct mg_connection *nc,
@@ -4688,9 +5135,11 @@  static enum mg_ssl_if_result mg_use_ca_cert(struct mg_ssl_if_ctx *ctx,
                                             const char *cert);
 static enum mg_ssl_if_result mg_set_cipher_list(struct mg_ssl_if_ctx *ctx,
                                                 const char *ciphers);
+#ifdef MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED
 static enum mg_ssl_if_result mg_ssl_if_mbed_set_psk(struct mg_ssl_if_ctx *ctx,
                                                     const char *identity,
                                                     const char *key);
+#endif
 
 enum mg_ssl_if_result mg_ssl_if_conn_init(
     struct mg_connection *nc, const struct mg_ssl_if_conn_params *params,
@@ -4739,11 +5188,13 @@  enum mg_ssl_if_result mg_ssl_if_conn_init(
     return MG_SSL_ERROR;
   }
 
+#ifdef MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED
   if (mg_ssl_if_mbed_set_psk(ctx, params->psk_identity, params->psk_key) !=
       MG_SSL_OK) {
     MG_SET_PTRPTR(err_msg, "Invalid PSK settings");
     return MG_SSL_ERROR;
   }
+#endif
 
   if (!(nc->flags & MG_F_LISTENING)) {
     ctx->ssl = (mbedtls_ssl_context *) MG_CALLOC(1, sizeof(*ctx->ssl));
@@ -4781,40 +5232,41 @@  enum mg_ssl_if_result mg_ssl_if_conn_init(
   return MG_SSL_OK;
 }
 
-#if MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL
-int ssl_socket_send(void *ctx, const unsigned char *buf, size_t len);
-int ssl_socket_recv(void *ctx, unsigned char *buf, size_t len);
-#else
-static int ssl_socket_send(void *ctx, const unsigned char *buf, size_t len) {
+static int mg_ssl_if_mbed_send(void *ctx, const unsigned char *buf,
+                               size_t len) {
   struct mg_connection *nc = (struct mg_connection *) ctx;
-  int n = (int) MG_SEND_FUNC(nc->sock, buf, len, 0);
-  LOG(LL_DEBUG, ("%p %d -> %d", nc, (int) len, n));
-  if (n >= 0) return n;
-  n = mg_get_errno();
-  return ((n == EAGAIN || n == EINPROGRESS) ? MBEDTLS_ERR_SSL_WANT_WRITE : -1);
+  int n = nc->iface->vtable->tcp_send(nc, buf, len);
+  if (n > 0) return n;
+  if (n == 0) return MBEDTLS_ERR_SSL_WANT_WRITE;
+  return MBEDTLS_ERR_NET_SEND_FAILED;
 }
 
-static int ssl_socket_recv(void *ctx, unsigned char *buf, size_t len) {
+static int mg_ssl_if_mbed_recv(void *ctx, unsigned char *buf, size_t len) {
   struct mg_connection *nc = (struct mg_connection *) ctx;
-  int n = (int) MG_RECV_FUNC(nc->sock, buf, len, 0);
-  LOG(LL_DEBUG, ("%p %d <- %d", nc, (int) len, n));
-  if (n >= 0) return n;
-  n = mg_get_errno();
-  return ((n == EAGAIN || n == EINPROGRESS) ? MBEDTLS_ERR_SSL_WANT_READ : -1);
+  int n = nc->iface->vtable->tcp_recv(nc, buf, len);
+  if (n > 0) return n;
+  if (n == 0) return MBEDTLS_ERR_SSL_WANT_READ;
+  return MBEDTLS_ERR_NET_RECV_FAILED;
 }
-#endif
 
 static enum mg_ssl_if_result mg_ssl_if_mbed_err(struct mg_connection *nc,
                                                 int ret) {
-  if (ret == MBEDTLS_ERR_SSL_WANT_READ) return MG_SSL_WANT_READ;
-  if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) return MG_SSL_WANT_WRITE;
-  if (ret !=
-      MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { /* CLOSE_NOTIFY = Normal shutdown */
-    LOG(LL_ERROR, ("%p SSL error: %d", nc, ret));
+  enum mg_ssl_if_result res = MG_SSL_OK;
+  if (ret == MBEDTLS_ERR_SSL_WANT_READ) {
+    res = MG_SSL_WANT_READ;
+  } else if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
+    res = MG_SSL_WANT_WRITE;
+  } else if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
+    LOG(LL_DEBUG, ("%p TLS connection closed by peer", nc));
+    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+    res = MG_SSL_OK;
+  } else {
+    LOG(LL_ERROR, ("%p mbedTLS error: -0x%04x", nc, -ret));
+    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+    res = MG_SSL_ERROR;
   }
   nc->err = ret;
-  nc->flags |= MG_F_CLOSE_IMMEDIATELY;
-  return MG_SSL_ERROR;
+  return res;
 }
 
 static void mg_ssl_if_mbed_free_certs_and_keys(struct mg_ssl_if_ctx *ctx) {
@@ -4829,9 +5281,9 @@  static void mg_ssl_if_mbed_free_certs_and_keys(struct mg_ssl_if_ctx *ctx) {
   if (ctx->ca_cert != NULL) {
     mbedtls_ssl_conf_ca_chain(ctx->conf, NULL, NULL);
 #ifdef MBEDTLS_X509_CA_CHAIN_ON_DISK
-    if (ctx->ca_cert->ca_chain_file != NULL) {
-      MG_FREE((void *) ctx->ca_cert->ca_chain_file);
-      ctx->ca_cert->ca_chain_file = NULL;
+    if (ctx->conf->ca_chain_file != NULL) {
+      MG_FREE((void *) ctx->conf->ca_chain_file);
+      ctx->conf->ca_chain_file = NULL;
     }
 #endif
     mbedtls_x509_crt_free(ctx->ca_cert);
@@ -4845,7 +5297,8 @@  enum mg_ssl_if_result mg_ssl_if_handshake(struct mg_connection *nc) {
   int err;
   /* If bio is not yet set, do it now. */
   if (ctx->ssl->p_bio == NULL) {
-    mbedtls_ssl_set_bio(ctx->ssl, nc, ssl_socket_send, ssl_socket_recv, NULL);
+    mbedtls_ssl_set_bio(ctx->ssl, nc, mg_ssl_if_mbed_send, mg_ssl_if_mbed_recv,
+                        NULL);
   }
   err = mbedtls_ssl_handshake(ctx->ssl);
   if (err != 0) return mg_ssl_if_mbed_err(nc, err);
@@ -4871,20 +5324,35 @@  enum mg_ssl_if_result mg_ssl_if_handshake(struct mg_connection *nc) {
   return MG_SSL_OK;
 }
 
-int mg_ssl_if_read(struct mg_connection *nc, void *buf, size_t buf_size) {
+int mg_ssl_if_read(struct mg_connection *nc, void *buf, size_t len) {
   struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
-  int n = mbedtls_ssl_read(ctx->ssl, (unsigned char *) buf, buf_size);
-  DBG(("%p %d -> %d", nc, (int) buf_size, n));
+  int n = mbedtls_ssl_read(ctx->ssl, (unsigned char *) buf, len);
+  DBG(("%p %d -> %d", nc, (int) len, n));
   if (n < 0) return mg_ssl_if_mbed_err(nc, n);
   if (n == 0) nc->flags |= MG_F_CLOSE_IMMEDIATELY;
   return n;
 }
 
-int mg_ssl_if_write(struct mg_connection *nc, const void *data, size_t len) {
+int mg_ssl_if_write(struct mg_connection *nc, const void *buf, size_t len) {
   struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
-  int n = mbedtls_ssl_write(ctx->ssl, (const unsigned char *) data, len);
-  DBG(("%p %d -> %d", nc, (int) len, n));
-  if (n < 0) return mg_ssl_if_mbed_err(nc, n);
+  /* Per mbedTLS docs, if write returns WANT_READ or WANT_WRITE, the operation
+   * should be retried with the same data and length.
+   * Here we assume that the data being pushed will remain the same but the
+   * amount may grow between calls so we save the length that was used and
+   * retry. The assumption being that the data itself won't change and won't
+   * be removed. */
+  size_t l = len;
+  if (ctx->saved_len > 0 && ctx->saved_len < l) l = ctx->saved_len;
+  int n = mbedtls_ssl_write(ctx->ssl, (const unsigned char *) buf, l);
+  DBG(("%p %d,%d,%d -> %d", nc, (int) len, (int) ctx->saved_len, (int) l, n));
+  if (n < 0) {
+    if (n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE) {
+      ctx->saved_len = len;
+    }
+    return mg_ssl_if_mbed_err(nc, n);
+  } else if (n > 0) {
+    ctx->saved_len = 0;
+  }
   return n;
 }
 
@@ -4922,15 +5390,13 @@  static enum mg_ssl_if_result mg_use_ca_cert(struct mg_ssl_if_ctx *ctx,
   mbedtls_x509_crt_init(ctx->ca_cert);
 #ifdef MBEDTLS_X509_CA_CHAIN_ON_DISK
   ca_cert = strdup(ca_cert);
-  if (mbedtls_x509_crt_set_ca_chain_file(ctx->ca_cert, ca_cert) != 0) {
-    return MG_SSL_ERROR;
-  }
+  mbedtls_ssl_conf_ca_chain_file(ctx->conf, ca_cert, NULL);
 #else
   if (mbedtls_x509_crt_parse_file(ctx->ca_cert, ca_cert) != 0) {
     return MG_SSL_ERROR;
   }
-#endif
   mbedtls_ssl_conf_ca_chain(ctx->conf, ctx->ca_cert, NULL);
+#endif
   mbedtls_ssl_conf_authmode(ctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
   return MG_SSL_OK;
 }
@@ -5037,6 +5503,7 @@  static enum mg_ssl_if_result mg_set_cipher_list(struct mg_ssl_if_ctx *ctx,
   return MG_SSL_OK;
 }
 
+#ifdef MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED
 static enum mg_ssl_if_result mg_ssl_if_mbed_set_psk(struct mg_ssl_if_ctx *ctx,
                                                     const char *identity,
                                                     const char *key_str) {
@@ -5073,6 +5540,7 @@  static enum mg_ssl_if_result mg_ssl_if_mbed_set_psk(struct mg_ssl_if_ctx *ctx,
   }
   return MG_SSL_OK;
 }
+#endif
 
 const char *mg_set_ssl(struct mg_connection *nc, const char *cert,
                        const char *ca_cert) {
@@ -5493,7 +5961,6 @@  enum mg_http_multipart_stream_state {
   MPS_BEGIN,
   MPS_WAITING_FOR_BOUNDARY,
   MPS_WAITING_FOR_CHUNK,
-  MPS_GOT_CHUNK,
   MPS_GOT_BOUNDARY,
   MPS_FINALIZE,
   MPS_FINISHED
@@ -5505,9 +5972,9 @@  struct mg_http_multipart_stream {
   const char *var_name;
   const char *file_name;
   void *user_data;
-  int prev_io_len;
   enum mg_http_multipart_stream_state state;
   int processing_part;
+  int data_avail;
 };
 
 struct mg_reverse_proxy_data {
@@ -5544,20 +6011,29 @@  struct mg_http_proto_data {
   size_t rcvd; /* How many bytes we have received. */
 };
 
-static void mg_http_conn_destructor(void *proto_data);
+static void mg_http_proto_data_destructor(void *proto_data);
+
 struct mg_connection *mg_connect_http_base(
     struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),
     struct mg_connect_opts opts, const char *scheme1, const char *scheme2,
     const char *scheme_ssl1, const char *scheme_ssl2, const char *url,
     struct mg_str *path, struct mg_str *user_info, struct mg_str *host);
 
-static struct mg_http_proto_data *mg_http_get_proto_data(
+MG_INTERNAL struct mg_http_proto_data *mg_http_create_proto_data(
     struct mg_connection *c) {
-  if (c->proto_data == NULL) {
-    c->proto_data = MG_CALLOC(1, sizeof(struct mg_http_proto_data));
-    c->proto_data_destructor = mg_http_conn_destructor;
-  }
+  /* If we have proto data from previous connection, flush it. */
+  if (c->proto_data != NULL) {
+    void *pd = c->proto_data;
+    c->proto_data = NULL;
+    mg_http_proto_data_destructor(pd);
+  }
+  c->proto_data = MG_CALLOC(1, sizeof(struct mg_http_proto_data));
+  c->proto_data_destructor = mg_http_proto_data_destructor;
+  return (struct mg_http_proto_data *) c->proto_data;
+}
 
+static struct mg_http_proto_data *mg_http_get_proto_data(
+    struct mg_connection *c) {
   return (struct mg_http_proto_data *) c->proto_data;
 }
 
@@ -5613,7 +6089,7 @@  static void mg_http_free_reverse_proxy_data(struct mg_reverse_proxy_data *rpd) {
   }
 }
 
-static void mg_http_conn_destructor(void *proto_data) {
+static void mg_http_proto_data_destructor(void *proto_data) {
   struct mg_http_proto_data *pd = (struct mg_http_proto_data *) proto_data;
 #if MG_ENABLE_FILESYSTEM
   mg_http_free_proto_data_file(&pd->file);
@@ -5813,7 +6289,7 @@  int mg_parse_http(const char *s, int n, struct http_message *hm, int is_req) {
     }
   } else {
     s = mg_skip(s, end, " ", &hm->proto);
-    if (end - s < 4 || s[3] != ' ') return -1;
+    if (end - s < 4 || s[0] < '0' || s[0] > '9' || s[3] != ' ') return -1;
     hm->resp_code = atoi(s);
     if (hm->resp_code < 100 || hm->resp_code >= 600) return -1;
     s += 4;
@@ -5863,7 +6339,7 @@  struct mg_str *mg_get_http_header(struct http_message *hm, const char *name) {
 static void mg_http_transfer_file_data(struct mg_connection *nc) {
   struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
   char buf[MG_MAX_HTTP_SEND_MBUF];
-  size_t to_read = 0, left = (size_t)(pd->file.cl - pd->file.sent);
+  size_t n = 0, to_read = 0, left = (size_t)(pd->file.cl - pd->file.sent);
 
   if (pd->file.type == DATA_FILE) {
     struct mbuf *io = &nc->send_mbuf;
@@ -5876,7 +6352,7 @@  static void mg_http_transfer_file_data(struct mg_connection *nc) {
       to_read = left;
     }
     if (to_read > 0) {
-      size_t n = mg_fread(buf, 1, to_read, pd->file.fp);
+      n = mg_fread(buf, 1, to_read, pd->file.fp);
       if (n > 0) {
         mg_send(nc, buf, n);
         pd->file.sent += n;
@@ -5886,7 +6362,8 @@  static void mg_http_transfer_file_data(struct mg_connection *nc) {
       /* Rate-limited */
     }
     if (pd->file.sent >= pd->file.cl) {
-      LOG(LL_DEBUG, ("%p done, %d bytes", nc, (int) pd->file.sent));
+      LOG(LL_DEBUG, ("%p done, %d bytes, ka %d", nc, (int) pd->file.sent,
+                     pd->file.keepalive));
       if (!pd->file.keepalive) nc->flags |= MG_F_SEND_AND_CLOSE;
       mg_http_free_proto_data_file(&pd->file);
     }
@@ -5932,6 +6409,10 @@  static size_t mg_http_parse_chunk(char *buf, size_t len, char **chunk_data,
     n *= 16;
     n += (s[i] >= '0' && s[i] <= '9') ? s[i] - '0' : tolower(s[i]) - 'a' + 10;
     i++;
+    if (i > 6) {
+      /* Chunk size is unreasonable. */
+      return 0;
+    }
   }
 
   /* Skip new line */
@@ -6011,19 +6492,19 @@  MG_INTERNAL size_t mg_handle_chunked(struct mg_connection *nc,
   return body_len;
 }
 
-static struct mg_http_endpoint *mg_http_get_endpoint_handler(struct mg_connection *nc,
+struct mg_http_endpoint *mg_http_get_endpoint_handler(struct mg_connection *nc,
                                                       struct mg_str *uri_path) {
   struct mg_http_proto_data *pd;
   struct mg_http_endpoint *ret = NULL;
   int matched, matched_max = 0;
   struct mg_http_endpoint *ep;
 
-  if (nc == NULL) {
-    return NULL;
-  }
+  if (nc == NULL) return NULL;
 
   pd = mg_http_get_proto_data(nc);
 
+  if (pd == NULL) return NULL;
+
   ep = pd->endpoints;
   while (ep != NULL) {
     if ((matched = mg_match_prefix_n(ep->uri_pattern, *uri_path)) > 0) {
@@ -6071,7 +6552,7 @@  static void mg_http_handler2(struct mg_connection *nc, int ev,
                              void *ev_data MG_UD_ARG(void *user_data),
                              struct http_message *hm) __attribute__((noinline));
 
-static void mg_http_handler(struct mg_connection *nc, int ev,
+void mg_http_handler(struct mg_connection *nc, int ev,
                      void *ev_data MG_UD_ARG(void *user_data)) {
   struct http_message hm;
   mg_http_handler2(nc, ev, ev_data MG_UD_ARG(user_data), &hm);
@@ -6081,7 +6562,7 @@  static void mg_http_handler2(struct mg_connection *nc, int ev,
                              void *ev_data MG_UD_ARG(void *user_data),
                              struct http_message *hm) {
 #else  /* !__XTENSA__ */
-static void mg_http_handler(struct mg_connection *nc, int ev,
+void mg_http_handler(struct mg_connection *nc, int ev,
                      void *ev_data MG_UD_ARG(void *user_data)) {
   struct http_message shm, *hm = &shm;
 #endif /* __XTENSA__ */
@@ -6095,13 +6576,13 @@  static void mg_http_handler(struct mg_connection *nc, int ev,
   if (ev == MG_EV_CLOSE) {
 #if MG_ENABLE_HTTP_CGI
     /* Close associated CGI forwarder connection */
-    if (pd->cgi.cgi_nc != NULL) {
+    if (pd != NULL && pd->cgi.cgi_nc != NULL) {
       pd->cgi.cgi_nc->user_data = NULL;
       pd->cgi.cgi_nc->flags |= MG_F_CLOSE_IMMEDIATELY;
     }
 #endif
 #if MG_ENABLE_HTTP_STREAMING_MULTIPART
-    if (pd->mp_stream.boundary != NULL) {
+    if (pd != NULL && pd->mp_stream.boundary != NULL) {
       /*
        * Multipart message is in progress, but connection is closed.
        * Finish part and request with an error flag.
@@ -6123,39 +6604,55 @@  static void mg_http_handler(struct mg_connection *nc, int ev,
         if (io->len > 0 &&
             (req_len = mg_parse_http(io->buf, io->len, hm, is_req)) > 0) {
       /*
-      * For HTTP messages without Content-Length, always send HTTP message
-      * before MG_EV_CLOSE message.
-      */
+       * For HTTP messages without Content-Length, always send HTTP message
+       * before MG_EV_CLOSE message.
+       */
       int ev2 = is_req ? MG_EV_HTTP_REQUEST : MG_EV_HTTP_REPLY;
       hm->message.len = io->len;
       hm->body.len = io->buf + io->len - hm->body.p;
       deliver_chunk(nc, hm, req_len);
       mg_http_call_endpoint_handler(nc, ev2, hm);
     }
-    pd->rcvd = 0;
+    if (pd != NULL && pd->endpoint_handler != NULL &&
+        pd->endpoint_handler != nc->handler) {
+      mg_call(nc, pd->endpoint_handler, nc->user_data, ev, NULL);
+    }
   }
 
 #if MG_ENABLE_FILESYSTEM
-  if (pd->file.fp != NULL) {
+  if (pd != NULL && pd->file.fp != NULL) {
     mg_http_transfer_file_data(nc);
   }
 #endif
 
   mg_call(nc, nc->handler, nc->user_data, ev, ev_data);
 
-  if (ev == MG_EV_RECV) {
-    struct mg_str *s;
-    pd->rcvd += *(int *) ev_data;
-
 #if MG_ENABLE_HTTP_STREAMING_MULTIPART
-    if (pd->mp_stream.boundary != NULL) {
+  if (pd != NULL && pd->mp_stream.boundary != NULL &&
+      (ev == MG_EV_RECV || ev == MG_EV_POLL)) {
+    if (ev == MG_EV_RECV) {
+      pd->rcvd += *(int *) ev_data;
+      mg_http_multipart_continue(nc);
+    } else if (pd->mp_stream.data_avail) {
+      /* Try re-delivering the data. */
       mg_http_multipart_continue(nc);
-      return;
     }
+    return;
+  }
 #endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */
 
+  if (ev == MG_EV_RECV) {
+    struct mg_str *s;
+
+  again:
     req_len = mg_parse_http(io->buf, io->len, hm, is_req);
 
+    if (req_len > 0) {
+      /* New request - new proto data */
+      pd = mg_http_create_proto_data(nc);
+      pd->rcvd = io->len;
+    }
+
     if (req_len > 0 &&
         (s = mg_get_http_header(hm, "Transfer-Encoding")) != NULL &&
         mg_vcasecmp(s, "chunked") == 0) {
@@ -6180,16 +6677,23 @@  static void mg_http_handler(struct mg_connection *nc, int ev,
       /* Do nothing, request is not yet fully buffered */
     }
 #if MG_ENABLE_HTTP_WEBSOCKET
-    else if (nc->listener == NULL &&
-             mg_get_http_header(hm, "Sec-WebSocket-Accept")) {
+    else if (nc->listener == NULL && (nc->flags & MG_F_IS_WEBSOCKET)) {
       /* We're websocket client, got handshake response from server. */
-      /* TODO(lsm): check the validity of accept Sec-WebSocket-Accept */
-      mbuf_remove(io, req_len);
-      nc->proto_handler = mg_ws_handler;
-      nc->flags |= MG_F_IS_WEBSOCKET;
-      mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_DONE,
-              NULL);
-      mg_ws_handler(nc, MG_EV_RECV, ev_data MG_UD_ARG(user_data));
+      DBG(("%p WebSocket upgrade code %d", nc, hm->resp_code));
+      if (hm->resp_code == 101 &&
+          mg_get_http_header(hm, "Sec-WebSocket-Accept")) {
+        /* TODO(lsm): check the validity of accept Sec-WebSocket-Accept */
+        mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_DONE,
+                hm);
+        mbuf_remove(io, req_len);
+        nc->proto_handler = mg_ws_handler;
+        mg_ws_handler(nc, MG_EV_RECV, ev_data MG_UD_ARG(user_data));
+      } else {
+        mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_DONE,
+                hm);
+        nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+        mbuf_remove(io, req_len);
+      }
     } else if (nc->listener != NULL &&
                (vec = mg_get_http_header(hm, "Sec-WebSocket-Key")) != NULL) {
       struct mg_http_endpoint *ep;
@@ -6220,7 +6724,7 @@  static void mg_http_handler(struct mg_connection *nc, int ev,
           mg_ws_handshake(nc, vec, hm);
         }
         mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_DONE,
-                NULL);
+                hm);
         mg_ws_handler(nc, MG_EV_RECV, ev_data MG_UD_ARG(user_data));
       }
     }
@@ -6237,6 +6741,7 @@  static void mg_http_handler(struct mg_connection *nc, int ev,
       }
     } else {
       /* We did receive all HTTP body. */
+      int request_done = 1;
       int trigger_ev = nc->listener ? MG_EV_HTTP_REQUEST : MG_EV_HTTP_REPLY;
       char addr[32];
       mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr),
@@ -6247,7 +6752,19 @@  static void mg_http_handler(struct mg_connection *nc, int ev,
       /* Whole HTTP message is fully buffered, call event handler */
       mg_http_call_endpoint_handler(nc, trigger_ev, hm);
       mbuf_remove(io, hm->message.len);
-      pd->rcvd = 0;
+      pd->rcvd -= hm->message.len;
+#if MG_ENABLE_FILESYSTEM
+      /* We don't have a generic mechanism of communicating that we are done
+       * responding to a request (should probably add one). But if we are
+       * serving
+       * a file, we are definitely not done. */
+      if (pd->file.fp != NULL) request_done = 0;
+#endif
+#if MG_ENABLE_HTTP_CGI
+      /* If this is a CGI request, we are not done either. */
+      if (pd->cgi.cgi_nc != NULL) request_done = 0;
+#endif
+      if (request_done && io->len > 0) goto again;
     }
   }
 }
@@ -6323,8 +6840,9 @@  exit_mp:
 
 #define CONTENT_DISPOSITION "Content-Disposition: "
 
-static void mg_http_multipart_call_handler(struct mg_connection *c, int ev,
-                                           const char *data, size_t data_len) {
+static size_t mg_http_multipart_call_handler(struct mg_connection *c, int ev,
+                                             const char *data,
+                                             size_t data_len) {
   struct mg_http_multipart_part mp;
   struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
   memset(&mp, 0, sizeof(mp));
@@ -6334,21 +6852,11 @@  static void mg_http_multipart_call_handler(struct mg_connection *c, int ev,
   mp.user_data = pd->mp_stream.user_data;
   mp.data.p = data;
   mp.data.len = data_len;
+  mp.num_data_consumed = data_len;
   mg_call(c, pd->endpoint_handler, c->user_data, ev, &mp);
   pd->mp_stream.user_data = mp.user_data;
-}
-
-static int mg_http_multipart_got_chunk(struct mg_connection *c) {
-  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
-  struct mbuf *io = &c->recv_mbuf;
-
-  mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA, io->buf,
-                                 pd->mp_stream.prev_io_len);
-  mbuf_remove(io, pd->mp_stream.prev_io_len);
-  pd->mp_stream.prev_io_len = 0;
-  pd->mp_stream.state = MPS_WAITING_FOR_CHUNK;
-
-  return 0;
+  pd->mp_stream.data_avail = (mp.num_data_consumed != data_len);
+  return mp.num_data_consumed;
 }
 
 static int mg_http_multipart_finalize(struct mg_connection *c) {
@@ -6485,21 +6993,26 @@  static int mg_http_multipart_continue_wait_for_chunk(struct mg_connection *c) {
   }
 
   boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len);
-  if (boundary == NULL && pd->mp_stream.prev_io_len == 0) {
-    pd->mp_stream.prev_io_len = io->len;
+  if (boundary == NULL) {
+    int data_len = (io->len - (pd->mp_stream.boundary_len + 6));
+    if (data_len > 0) {
+      size_t consumed = mg_http_multipart_call_handler(
+          c, MG_EV_HTTP_PART_DATA, io->buf, (size_t) data_len);
+      mbuf_remove(io, consumed);
+    }
     return 0;
-  } else if (boundary == NULL &&
-             (int) io->len >
-                 pd->mp_stream.prev_io_len + pd->mp_stream.boundary_len + 4) {
-    pd->mp_stream.state = MPS_GOT_CHUNK;
-    return 1;
   } else if (boundary != NULL) {
-    int data_size = (boundary - io->buf - 4);
-    mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA, io->buf, data_size);
-    mbuf_remove(io, (boundary - io->buf));
-    pd->mp_stream.prev_io_len = 0;
-    pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY;
-    return 1;
+    size_t data_len = ((size_t)(boundary - io->buf) - 4);
+    size_t consumed = mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA,
+                                                     io->buf, data_len);
+    mbuf_remove(io, consumed);
+    if (consumed == data_len) {
+      mbuf_remove(io, 4);
+      pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY;
+      return 1;
+    } else {
+      return 0;
+    }
   } else {
     return 0;
   }
@@ -6531,12 +7044,6 @@  static void mg_http_multipart_continue(struct mg_connection *c) {
         }
         break;
       }
-      case MPS_GOT_CHUNK: {
-        if (mg_http_multipart_got_chunk(c) == 0) {
-          return;
-        }
-        break;
-      }
       case MPS_FINALIZE: {
         if (mg_http_multipart_finalize(c) == 0) {
           return;
@@ -6544,7 +7051,6 @@  static void mg_http_multipart_continue(struct mg_connection *c) {
         break;
       }
       case MPS_FINISHED: {
-        mbuf_remove(&c->recv_mbuf, c->recv_mbuf.len);
         return;
       }
     }
@@ -6563,7 +7069,7 @@  void mg_set_protocol_http_websocket(struct mg_connection *nc) {
   nc->proto_handler = mg_http_handler;
 }
 
-static const char *mg_status_message(int status_code) {
+const char *mg_status_message(int status_code) {
   switch (status_code) {
     case 206:
       return "Partial Content";
@@ -6694,10 +7200,13 @@  static const char *mg_status_message(int status_code) {
   }
 }
 
-static void mg_send_response_line_s(struct mg_connection *nc, int status_code,
+void mg_send_response_line_s(struct mg_connection *nc, int status_code,
                              const struct mg_str extra_headers) {
-  mg_printf(nc, "HTTP/1.1 %d %s\r\nServer: %s\r\n", status_code,
-            mg_status_message(status_code), mg_version_header);
+  mg_printf(nc, "HTTP/1.1 %d %s\r\n", status_code,
+            mg_status_message(status_code));
+#ifndef MG_HIDE_SERVER_INFO
+  mg_printf(nc, "Server: %s\r\n", mg_version_header);
+#endif
   if (extra_headers.len > 0) {
     mg_printf(nc, "%.*s\r\n", (int) extra_headers.len, extra_headers.p);
   }
@@ -7029,7 +7538,7 @@  void mg_printf_html_escape(struct mg_connection *nc, const char *fmt, ...) {
 static void mg_http_parse_header_internal(struct mg_str *hdr,
                                           const char *var_name,
                                           struct altbuf *ab) {
-  int ch = ' ', ch1 = ',', n = strlen(var_name);
+  int ch = ' ', ch1 = ',', ch2 = ';', n = strlen(var_name);
   const char *p, *end = hdr ? hdr->p + hdr->len : NULL, *s = NULL;
 
   /* Find where variable starts */
@@ -7042,10 +7551,10 @@  static void mg_http_parse_header_internal(struct mg_str *hdr,
   if (s != NULL && &s[n + 1] < end) {
     s += n + 1;
     if (*s == '"' || *s == '\'') {
-      ch = ch1 = *s++;
+      ch = ch1 = ch2 = *s++;
     }
     p = s;
-    while (p < end && p[0] != ch && p[0] != ch1) {
+    while (p < end && p[0] != ch && p[0] != ch1 && p[0] != ch2) {
       if (ch != ' ' && p[0] == '\\' && p[1] == ch) p++;
       altbuf_append(ab, *p++);
     }
@@ -7158,7 +7667,7 @@  extern void mg_hash_md5_v(size_t num_msgs, const uint8_t *msgs[],
                           const size_t *msg_lens, uint8_t *digest);
 #endif
 
-static void cs_md5(char buf[33], ...) {
+void cs_md5(char buf[33], ...) {
   unsigned char hash[16];
   const uint8_t *msgs[20], *p;
   size_t msg_lens[20];
@@ -7281,7 +7790,7 @@  int mg_check_digest_auth(struct mg_str method, struct mg_str uri,
                          struct mg_str nc, struct mg_str nonce,
                          struct mg_str auth_domain, FILE *fp) {
   char buf[128], f_user[sizeof(buf)], f_ha1[sizeof(buf)], f_domain[sizeof(buf)];
-  char expected_response[33];
+  char exp_resp[33];
 
   /*
    * Read passwords file line by line. If should have htdigest format,
@@ -7295,11 +7804,10 @@  int mg_check_digest_auth(struct mg_str method, struct mg_str uri,
       /* Username and domain matched, check the password */
       mg_mkmd5resp(method.p, method.len, uri.p, uri.len, f_ha1, strlen(f_ha1),
                    nonce.p, nonce.len, nc.p, nc.len, cnonce.p, cnonce.len,
-                   qop.p, qop.len, expected_response);
-      LOG(LL_DEBUG,
-          ("%.*s %s %.*s %s", (int) username.len, username.p, f_domain,
-           (int) response.len, response.p, expected_response));
-      return mg_ncasecmp(response.p, expected_response, response.len) == 0;
+                   qop.p, qop.len, exp_resp);
+      LOG(LL_DEBUG, ("%.*s %s %.*s %s", (int) username.len, username.p,
+                     f_domain, (int) response.len, response.p, exp_resp));
+      return mg_ncasecmp(response.p, exp_resp, strlen(exp_resp)) == 0;
     }
   }
 
@@ -7409,7 +7917,7 @@  static void mg_scan_directory(struct mg_connection *nc, const char *dir,
                               const struct mg_serve_http_opts *opts,
                               void (*func)(struct mg_connection *, const char *,
                                            cs_stat_t *)) {
-  char path[MG_MAX_PATH];
+  char path[MG_MAX_PATH + 1];
   cs_stat_t st;
   struct dirent *dp;
   DIR *dirp;
@@ -7894,9 +8402,11 @@  void mg_http_send_digest_auth_request(struct mg_connection *c,
             domain, (unsigned long) mg_time());
 }
 
-static void mg_http_send_options(struct mg_connection *nc) {
+static void mg_http_send_options(struct mg_connection *nc,
+                                 struct mg_serve_http_opts *opts) {
+  mg_send_response_line(nc, 200, opts->extra_headers);
   mg_printf(nc, "%s",
-            "HTTP/1.1 200 OK\r\nAllow: GET, POST, HEAD, CONNECT, OPTIONS"
+            "Allow: GET, POST, HEAD, CONNECT, OPTIONS"
 #if MG_ENABLE_HTTP_WEBDAV
             ", MKCOL, PUT, DELETE, PROPFIND, MOVE\r\nDAV: 1,2"
 #endif
@@ -8003,7 +8513,7 @@  MG_INTERNAL void mg_send_http_file(struct mg_connection *nc, char *path,
 #endif
 #endif /* MG_ENABLE_HTTP_WEBDAV */
   } else if (!mg_vcmp(&hm->method, "OPTIONS")) {
-    mg_http_send_options(nc);
+    mg_http_send_options(nc, opts);
   } else if (is_directory && index_file == NULL) {
 #if MG_ENABLE_DIRECTORY_LISTING
     if (strcmp(opts->enable_directory_listing, "yes") == 0) {
@@ -8095,8 +8605,7 @@  void mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data,
     case MG_EV_HTTP_PART_BEGIN: {
       struct mg_http_multipart_part *mp =
           (struct mg_http_multipart_part *) ev_data;
-      struct file_upload_state *fus =
-          (struct file_upload_state *) MG_CALLOC(1, sizeof(*fus));
+      struct file_upload_state *fus;
       struct mg_str lfn = local_name_fn(nc, mg_mk_str(mp->file_name));
       mp->user_data = NULL;
       if (lfn.p == NULL || lfn.len == 0) {
@@ -8110,13 +8619,18 @@  void mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data,
         nc->flags |= MG_F_SEND_AND_CLOSE;
         return;
       }
+      fus = (struct file_upload_state *) MG_CALLOC(1, sizeof(*fus));
+      if (fus == NULL) {
+        nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+        return;
+      }
       fus->lfn = (char *) MG_MALLOC(lfn.len + 1);
       memcpy(fus->lfn, lfn.p, lfn.len);
       fus->lfn[lfn.len] = '\0';
       if (lfn.p != mp->file_name) MG_FREE((char *) lfn.p);
       LOG(LL_DEBUG,
           ("%p Receiving file %s -> %s", nc, mp->file_name, fus->lfn));
-      fus->fp = mg_fopen(fus->lfn, "w");
+      fus->fp = mg_fopen(fus->lfn, "wb");
       if (fus->fp == NULL) {
         mg_printf(nc,
                   "HTTP/1.1 500 Internal Server Error\r\n"
@@ -8181,12 +8695,6 @@  void mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data,
       if (mp->status >= 0 && fus->fp != NULL) {
         LOG(LL_DEBUG, ("%p Uploaded %s (%s), %d bytes", nc, mp->file_name,
                        fus->lfn, (int) fus->num_recd));
-        mg_printf(nc,
-                  "HTTP/1.1 200 OK\r\n"
-                  "Content-Type: text/plain\r\n"
-                  "Connection: close\r\n\r\n"
-                  "Ok, %s - %d bytes.\r\n",
-                  mp->file_name, (int) fus->num_recd);
       } else {
         LOG(LL_ERROR, ("Failed to store %s (%s)", mp->file_name, fus->lfn));
         /*
@@ -8198,6 +8706,15 @@  void mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data,
       MG_FREE(fus->lfn);
       MG_FREE(fus);
       mp->user_data = NULL;
+      /* Don't close the connection yet, there may be more files to come. */
+      break;
+    }
+    case MG_EV_HTTP_MULTIPART_REQUEST_END: {
+      mg_printf(nc,
+                "HTTP/1.1 200 OK\r\n"
+                "Content-Type: text/plain\r\n"
+                "Connection: close\r\n\r\n"
+                "Ok.\r\n");
       nc->flags |= MG_F_SEND_AND_CLOSE;
       break;
     }
@@ -8387,6 +8904,7 @@  void mg_register_http_endpoint_opt(struct mg_connection *nc,
   if (new_ep == NULL) return;
 
   pd = mg_http_get_proto_data(nc);
+  if (pd == NULL) pd = mg_http_create_proto_data(nc);
   new_ep->uri_pattern = mg_strdup(mg_mk_str(uri_path));
   if (opts.auth_domain != NULL && opts.auth_file != NULL) {
     new_ep->auth_domain = strdup(opts.auth_domain);
@@ -8925,7 +9443,6 @@  MG_INTERNAL void mg_handle_cgi(struct mg_connection *nc, const char *prog,
 
   if (mg_start_process(opts->cgi_interpreter, prog, blk.buf, blk.vars, dir,
                        fds[1]) != 0) {
-    size_t n = nc->recv_mbuf.len - (hm->message.len - hm->body.len);
     struct mg_connection *cgi_nc =
         mg_add_sock(nc->mgr, fds[0], mg_cgi_ev_handler MG_UD_ARG(nc));
     struct mg_http_proto_data *cgi_pd = mg_http_get_proto_data(nc);
@@ -8935,8 +9452,8 @@  MG_INTERNAL void mg_handle_cgi(struct mg_connection *nc, const char *prog,
 #endif
     nc->flags |= MG_F_HTTP_CGI_PARSE_HEADERS;
     /* Push POST data to the CGI */
-    if (n > 0 && n < nc->recv_mbuf.len) {
-      mg_send(cgi_pd->cgi.cgi_nc, hm->body.p, n);
+    if (hm->body.len > 0) {
+      mg_send(cgi_pd->cgi.cgi_nc, hm->body.p, hm->body.len);
     }
     mbuf_remove(&nc->recv_mbuf, nc->recv_mbuf.len);
   } else {
@@ -9910,6 +10427,8 @@  void mg_send_websocket_handshake3v(struct mg_connection *nc,
   }
   mg_printf(nc, "\r\n");
 
+  nc->flags |= MG_F_IS_WEBSOCKET;
+
   mbuf_free(&auth);
 }
 
@@ -10096,7 +10615,7 @@  int mg_sock_addr_to_str(const union socket_address *sa, char *buf, size_t len,
       goto cleanup;
     }
 #else
-    if (inet_ntop(AF_INET, (void *) &sa->sin.sin_addr, buf, len - 1) == NULL) {
+    if (inet_ntop(AF_INET, (void *) &sa->sin.sin_addr, buf, len) == NULL) {
       goto cleanup;
     }
 #endif
@@ -10265,17 +10784,21 @@  void mg_basic_auth_header(const struct mg_str user, const struct mg_str pass,
   mbuf_append(buf, header_suffix, strlen(header_suffix));
 }
 
-struct mg_str mg_url_encode(const struct mg_str src) {
-  static const char *dont_escape = "._-$,;~()/";
-  static const char *hex = "0123456789abcdef";
+struct mg_str mg_url_encode_opt(const struct mg_str src,
+                                const struct mg_str safe, unsigned int flags) {
+  const char *hex =
+      (flags & MG_URL_ENCODE_F_UPPERCASE_HEX ? "0123456789ABCDEF"
+                                             : "0123456789abcdef");
   size_t i = 0;
   struct mbuf mb;
   mbuf_init(&mb, src.len);
 
   for (i = 0; i < src.len; i++) {
     const unsigned char c = *((const unsigned char *) src.p + i);
-    if (isalnum(c) || strchr(dont_escape, c) != NULL) {
+    if (isalnum(c) || mg_strchr(safe, c) != NULL) {
       mbuf_append(&mb, &c, 1);
+    } else if (c == ' ' && (flags & MG_URL_ENCODE_F_SPACE_AS_PLUS)) {
+      mbuf_append(&mb, "+", 1);
     } else {
       mbuf_append(&mb, "%", 1);
       mbuf_append(&mb, &hex[c >> 4], 1);
@@ -10286,6 +10809,10 @@  struct mg_str mg_url_encode(const struct mg_str src) {
   mbuf_trim(&mb);
   return mg_mk_str_n(mb.buf, mb.len - 1);
 }
+
+struct mg_str mg_url_encode(const struct mg_str src) {
+  return mg_url_encode_opt(src, mg_mk_str("._-$,;~()/"), 0);
+}
 #ifdef MG_MODULE_LINES
 #line 1 "mongoose/src/mg_mqtt.c"
 #endif
@@ -10315,7 +10842,7 @@  static const char *scanto(const char *p, struct mg_str *s) {
 MG_INTERNAL int parse_mqtt(struct mbuf *io, struct mg_mqtt_message *mm) {
   uint8_t header;
   size_t len = 0, len_len = 0;
-  const char *p, *end;
+  const char *p, *end, *eop = &io->buf[io->len];
   unsigned char lc = 0;
   int cmd;
 
@@ -10326,7 +10853,7 @@  MG_INTERNAL int parse_mqtt(struct mbuf *io, struct mg_mqtt_message *mm) {
   /* decode mqtt variable length */
   len = len_len = 0;
   p = io->buf + 1;
-  while ((size_t)(p - io->buf) < io->len) {
+  while (p < eop) {
     lc = *((const unsigned char *) p++);
     len += (lc & 0x7f) << 7 * len_len;
     len_len++;
@@ -10335,9 +10862,7 @@  MG_INTERNAL int parse_mqtt(struct mbuf *io, struct mg_mqtt_message *mm) {
   }
 
   end = p + len;
-  if (lc & 0x80 || len > (io->len - (p - io->buf))) {
-    return MG_MQTT_ERROR_INCOMPLETE_MSG;
-  }
+  if (lc & 0x80 || end > eop) return MG_MQTT_ERROR_INCOMPLETE_MSG;
 
   mm->cmd = cmd;
   mm->qos = MG_MQTT_GET_QOS(header);
@@ -10391,7 +10916,9 @@  MG_INTERNAL int parse_mqtt(struct mbuf *io, struct mg_mqtt_message *mm) {
     case MG_MQTT_CMD_PUBREL:
     case MG_MQTT_CMD_PUBCOMP:
     case MG_MQTT_CMD_SUBACK:
+      if (end - p < 2) return MG_MQTT_ERROR_MALFORMED_MSG;
       mm->message_id = getu16(p);
+      p += 2;
       break;
     case MG_MQTT_CMD_PUBLISH: {
       p = scanto(p, &mm->topic);
@@ -10487,26 +11014,44 @@  static void mg_mqtt_proto_data_destructor(void *proto_data) {
   MG_FREE(proto_data);
 }
 
+static struct mg_str mg_mqtt_next_topic_component(struct mg_str *topic) {
+  struct mg_str res = *topic;
+  const char *c = mg_strchr(*topic, '/');
+  if (c != NULL) {
+    res.len = (c - topic->p);
+    topic->len -= (res.len + 1);
+    topic->p += (res.len + 1);
+  } else {
+    topic->len = 0;
+  }
+  return res;
+}
+
+/* Refernce: https://mosquitto.org/man/mqtt-7.html */
 int mg_mqtt_match_topic_expression(struct mg_str exp, struct mg_str topic) {
-  /* TODO(mkm): implement real matching */
-  if (memchr(exp.p, '#', exp.len)) {
-    /* exp `foo/#` will become `foo/` */
-    exp.len -= 1;
-    /*
-     * topic should be longer than the expression: e.g. topic `foo/bar` does
-     * match `foo/#`, but neither `foo` nor `foo/` do.
-     */
-    if (topic.len <= exp.len) {
+  struct mg_str ec, tc;
+  if (exp.len == 0) return 0;
+  while (1) {
+    ec = mg_mqtt_next_topic_component(&exp);
+    tc = mg_mqtt_next_topic_component(&topic);
+    if (ec.len == 0) {
+      if (tc.len != 0) return 0;
+      if (exp.len == 0) break;
+      continue;
+    }
+    if (mg_vcmp(&ec, "+") == 0) {
+      if (tc.len == 0 && topic.len == 0) return 0;
+      continue;
+    }
+    if (mg_vcmp(&ec, "#") == 0) {
+      /* Must be the last component in the expression or it's invalid. */
+      return (exp.len == 0);
+    }
+    if (mg_strcmp(ec, tc) != 0) {
       return 0;
     }
-
-    /* Truncate topic so that it'll pass the next length check */
-    topic.len = exp.len;
   }
-  if (topic.len != exp.len) {
-    return 0;
-  }
-  return strncmp(topic.p, exp.p, exp.len) == 0;
+  return (tc.len == 0 && topic.len == 0);
 }
 
 int mg_mqtt_vmatch_topic_expression(const char *exp, struct mg_str topic) {
@@ -10519,18 +11064,13 @@  void mg_set_protocol_mqtt(struct mg_connection *nc) {
   nc->proto_data_destructor = mg_mqtt_proto_data_destructor;
 }
 
-static void mg_mqtt_prepend_header(struct mg_connection *nc, uint8_t cmd,
-                                   uint8_t flags, size_t len) {
+static void mg_send_mqtt_header(struct mg_connection *nc, uint8_t cmd,
+                                uint8_t flags, size_t len) {
   struct mg_mqtt_proto_data *pd = (struct mg_mqtt_proto_data *) nc->proto_data;
-  size_t off = nc->send_mbuf.len - len;
-  uint8_t header = cmd << 4 | (uint8_t) flags;
-
   uint8_t buf[1 + sizeof(size_t)];
   uint8_t *vlen = &buf[1];
 
-  assert(nc->send_mbuf.len >= len);
-
-  buf[0] = header;
+  buf[0] = (cmd << 4) | flags;
 
   /* mqtt variable length encoding */
   do {
@@ -10540,7 +11080,7 @@  static void mg_mqtt_prepend_header(struct mg_connection *nc, uint8_t cmd,
     vlen++;
   } while (len > 0);
 
-  mbuf_insert(&nc->send_mbuf, off, buf, vlen - buf);
+  mg_send(nc, buf, vlen - buf);
   pd->last_control_time = mg_time();
 }
 
@@ -10551,11 +11091,16 @@  void mg_send_mqtt_handshake(struct mg_connection *nc, const char *client_id) {
 
 void mg_send_mqtt_handshake_opt(struct mg_connection *nc, const char *client_id,
                                 struct mg_send_mqtt_handshake_opts opts) {
-  uint16_t hlen, nlen, rem_len = 0;
   struct mg_mqtt_proto_data *pd = (struct mg_mqtt_proto_data *) nc->proto_data;
+  uint16_t id_len = 0, wt_len = 0, wm_len = 0, user_len = 0, pw_len = 0;
+  uint16_t netbytes;
+  size_t total_len;
 
-  mg_send(nc, "\00\04MQTT\04", 7);
-  rem_len += 7;
+  if (client_id != NULL) {
+    id_len = strlen(client_id);
+  }
+
+  total_len = 7 + 1 + 2 + 2 + id_len;
 
   if (opts.user_name != NULL) {
     opts.flags |= MG_MQTT_HAS_USER_NAME;
@@ -10564,56 +11109,58 @@  void mg_send_mqtt_handshake_opt(struct mg_connection *nc, const char *client_id,
     opts.flags |= MG_MQTT_HAS_PASSWORD;
   }
   if (opts.will_topic != NULL && opts.will_message != NULL) {
+    wt_len = strlen(opts.will_topic);
+    wm_len = strlen(opts.will_message);
     opts.flags |= MG_MQTT_HAS_WILL;
   }
   if (opts.keep_alive == 0) {
     opts.keep_alive = 60;
   }
 
+  if (opts.flags & MG_MQTT_HAS_WILL) {
+    total_len += 2 + wt_len + 2 + wm_len;
+  }
+  if (opts.flags & MG_MQTT_HAS_USER_NAME) {
+    user_len = strlen(opts.user_name);
+    total_len += 2 + user_len;
+  }
+  if (opts.flags & MG_MQTT_HAS_PASSWORD) {
+    pw_len = strlen(opts.password);
+    total_len += 2 + pw_len;
+  }
+
+  mg_send_mqtt_header(nc, MG_MQTT_CMD_CONNECT, 0, total_len);
+  mg_send(nc, "\00\04MQTT\04", 7);
   mg_send(nc, &opts.flags, 1);
-  rem_len += 1;
 
-  nlen = htons(opts.keep_alive);
-  mg_send(nc, &nlen, 2);
-  rem_len += 2;
+  netbytes = htons(opts.keep_alive);
+  mg_send(nc, &netbytes, 2);
 
-  hlen = strlen(client_id);
-  nlen = htons((uint16_t) hlen);
-  mg_send(nc, &nlen, 2);
-  mg_send(nc, client_id, hlen);
-  rem_len += 2 + hlen;
+  netbytes = htons(id_len);
+  mg_send(nc, &netbytes, 2);
+  mg_send(nc, client_id, id_len);
 
   if (opts.flags & MG_MQTT_HAS_WILL) {
-    hlen = strlen(opts.will_topic);
-    nlen = htons((uint16_t) hlen);
-    mg_send(nc, &nlen, 2);
-    mg_send(nc, opts.will_topic, hlen);
-    rem_len += 2 + hlen;
+    netbytes = htons(wt_len);
+    mg_send(nc, &netbytes, 2);
+    mg_send(nc, opts.will_topic, wt_len);
 
-    hlen = strlen(opts.will_message);
-    nlen = htons((uint16_t) hlen);
-    mg_send(nc, &nlen, 2);
-    mg_send(nc, opts.will_message, hlen);
-    rem_len += 2 + hlen;
+    netbytes = htons(wm_len);
+    mg_send(nc, &netbytes, 2);
+    mg_send(nc, opts.will_message, wm_len);
   }
 
   if (opts.flags & MG_MQTT_HAS_USER_NAME) {
-    hlen = strlen(opts.user_name);
-    nlen = htons((uint16_t) hlen);
-    mg_send(nc, &nlen, 2);
-    mg_send(nc, opts.user_name, hlen);
-    rem_len += 2 + hlen;
+    netbytes = htons(user_len);
+    mg_send(nc, &netbytes, 2);
+    mg_send(nc, opts.user_name, user_len);
   }
   if (opts.flags & MG_MQTT_HAS_PASSWORD) {
-    hlen = strlen(opts.password);
-    nlen = htons((uint16_t) hlen);
-    mg_send(nc, &nlen, 2);
-    mg_send(nc, opts.password, hlen);
-    rem_len += 2 + hlen;
+    netbytes = htons(pw_len);
+    mg_send(nc, &netbytes, 2);
+    mg_send(nc, opts.password, pw_len);
   }
 
-  mg_mqtt_prepend_header(nc, MG_MQTT_CMD_CONNECT, 0, rem_len);
-
   if (pd != NULL) {
     pd->keep_alive = opts.keep_alive;
   }
@@ -10622,40 +11169,52 @@  void mg_send_mqtt_handshake_opt(struct mg_connection *nc, const char *client_id,
 void mg_mqtt_publish(struct mg_connection *nc, const char *topic,
                      uint16_t message_id, int flags, const void *data,
                      size_t len) {
-  size_t old_len = nc->send_mbuf.len;
+  uint16_t netbytes;
+  uint16_t topic_len = strlen(topic);
+
+  size_t total_len = 2 + topic_len + len;
+  if (MG_MQTT_GET_QOS(flags) > 0) {
+    total_len += 2;
+  }
 
-  uint16_t topic_len = htons((uint16_t) strlen(topic));
-  uint16_t message_id_net = htons(message_id);
+  mg_send_mqtt_header(nc, MG_MQTT_CMD_PUBLISH, flags, total_len);
+
+  netbytes = htons(topic_len);
+  mg_send(nc, &netbytes, 2);
+  mg_send(nc, topic, topic_len);
 
-  mg_send(nc, &topic_len, 2);
-  mg_send(nc, topic, strlen(topic));
   if (MG_MQTT_GET_QOS(flags) > 0) {
-    mg_send(nc, &message_id_net, 2);
+    netbytes = htons(message_id);
+    mg_send(nc, &netbytes, 2);
   }
-  mg_send(nc, data, len);
 
-  mg_mqtt_prepend_header(nc, MG_MQTT_CMD_PUBLISH, flags,
-                         nc->send_mbuf.len - old_len);
+  mg_send(nc, data, len);
 }
 
 void mg_mqtt_subscribe(struct mg_connection *nc,
                        const struct mg_mqtt_topic_expression *topics,
                        size_t topics_len, uint16_t message_id) {
-  size_t old_len = nc->send_mbuf.len;
-
-  uint16_t message_id_n = htons(message_id);
+  uint16_t netbytes;
   size_t i;
+  uint16_t topic_len;
+  size_t total_len = 2;
 
-  mg_send(nc, (char *) &message_id_n, 2);
   for (i = 0; i < topics_len; i++) {
-    uint16_t topic_len_n = htons((uint16_t) strlen(topics[i].topic));
-    mg_send(nc, &topic_len_n, 2);
-    mg_send(nc, topics[i].topic, strlen(topics[i].topic));
-    mg_send(nc, &topics[i].qos, 1);
+    total_len += 2 + strlen(topics[i].topic) + 1;
   }
 
-  mg_mqtt_prepend_header(nc, MG_MQTT_CMD_SUBSCRIBE, MG_MQTT_QOS(1),
-                         nc->send_mbuf.len - old_len);
+  mg_send_mqtt_header(nc, MG_MQTT_CMD_SUBSCRIBE, MG_MQTT_QOS(1), total_len);
+
+  netbytes = htons(message_id);
+  mg_send(nc, (char *) &netbytes, 2);
+
+  for (i = 0; i < topics_len; i++) {
+    topic_len = strlen(topics[i].topic);
+    netbytes = htons(topic_len);
+    mg_send(nc, &netbytes, 2);
+    mg_send(nc, topics[i].topic, topic_len);
+    mg_send(nc, &topics[i].qos, 1);
+  }
 }
 
 int mg_mqtt_next_subscribe_topic(struct mg_mqtt_message *msg,
@@ -10675,27 +11234,33 @@  int mg_mqtt_next_subscribe_topic(struct mg_mqtt_message *msg,
 
 void mg_mqtt_unsubscribe(struct mg_connection *nc, char **topics,
                          size_t topics_len, uint16_t message_id) {
-  size_t old_len = nc->send_mbuf.len;
-
-  uint16_t message_id_n = htons(message_id);
+  uint16_t netbytes;
   size_t i;
+  uint16_t topic_len;
+  size_t total_len = 2;
 
-  mg_send(nc, (char *) &message_id_n, 2);
   for (i = 0; i < topics_len; i++) {
-    uint16_t topic_len_n = htons((uint16_t) strlen(topics[i]));
-    mg_send(nc, &topic_len_n, 2);
-    mg_send(nc, topics[i], strlen(topics[i]));
+    total_len += 2 + strlen(topics[i]);
   }
 
-  mg_mqtt_prepend_header(nc, MG_MQTT_CMD_UNSUBSCRIBE, MG_MQTT_QOS(1),
-                         nc->send_mbuf.len - old_len);
+  mg_send_mqtt_header(nc, MG_MQTT_CMD_UNSUBSCRIBE, MG_MQTT_QOS(1), total_len);
+
+  netbytes = htons(message_id);
+  mg_send(nc, (char *) &netbytes, 2);
+
+  for (i = 0; i < topics_len; i++) {
+    topic_len = strlen(topics[i]);
+    netbytes = htons(topic_len);
+    mg_send(nc, &netbytes, 2);
+    mg_send(nc, topics[i], topic_len);
+  }
 }
 
 void mg_mqtt_connack(struct mg_connection *nc, uint8_t return_code) {
   uint8_t unused = 0;
+  mg_send_mqtt_header(nc, MG_MQTT_CMD_CONNACK, 0, 2);
   mg_send(nc, &unused, 1);
   mg_send(nc, &return_code, 1);
-  mg_mqtt_prepend_header(nc, MG_MQTT_CMD_CONNACK, 0, 2);
 }
 
 /*
@@ -10705,10 +11270,13 @@  void mg_mqtt_connack(struct mg_connection *nc, uint8_t return_code) {
  */
 static void mg_send_mqtt_short_command(struct mg_connection *nc, uint8_t cmd,
                                        uint16_t message_id) {
-  uint16_t message_id_net = htons(message_id);
+  uint16_t netbytes;
   uint8_t flags = (cmd == MG_MQTT_CMD_PUBREL ? 2 : 0);
-  mg_send(nc, &message_id_net, 2);
-  mg_mqtt_prepend_header(nc, cmd, flags, 2 /* len */);
+
+  mg_send_mqtt_header(nc, cmd, flags, 2 /* len */);
+
+  netbytes = htons(message_id);
+  mg_send(nc, &netbytes, 2);
 }
 
 void mg_mqtt_puback(struct mg_connection *nc, uint16_t message_id) {
@@ -10730,12 +11298,16 @@  void mg_mqtt_pubcomp(struct mg_connection *nc, uint16_t message_id) {
 void mg_mqtt_suback(struct mg_connection *nc, uint8_t *qoss, size_t qoss_len,
                     uint16_t message_id) {
   size_t i;
-  uint16_t message_id_net = htons(message_id);
-  mg_send(nc, &message_id_net, 2);
+  uint16_t netbytes;
+
+  mg_send_mqtt_header(nc, MG_MQTT_CMD_SUBACK, MG_MQTT_QOS(1), 2 + qoss_len);
+
+  netbytes = htons(message_id);
+  mg_send(nc, &netbytes, 2);
+
   for (i = 0; i < qoss_len; i++) {
     mg_send(nc, &qoss[i], 1);
   }
-  mg_mqtt_prepend_header(nc, MG_MQTT_CMD_SUBACK, MG_MQTT_QOS(1), 2 + qoss_len);
 }
 
 void mg_mqtt_unsuback(struct mg_connection *nc, uint16_t message_id) {
@@ -10743,15 +11315,15 @@  void mg_mqtt_unsuback(struct mg_connection *nc, uint16_t message_id) {
 }
 
 void mg_mqtt_ping(struct mg_connection *nc) {
-  mg_mqtt_prepend_header(nc, MG_MQTT_CMD_PINGREQ, 0, 0);
+  mg_send_mqtt_header(nc, MG_MQTT_CMD_PINGREQ, 0, 0);
 }
 
 void mg_mqtt_pong(struct mg_connection *nc) {
-  mg_mqtt_prepend_header(nc, MG_MQTT_CMD_PINGRESP, 0, 0);
+  mg_send_mqtt_header(nc, MG_MQTT_CMD_PINGRESP, 0, 0);
 }
 
 void mg_mqtt_disconnect(struct mg_connection *nc) {
-  mg_mqtt_prepend_header(nc, MG_MQTT_CMD_DISCONNECT, 0, 0);
+  mg_send_mqtt_header(nc, MG_MQTT_CMD_DISCONNECT, 0, 0);
 }
 
 #endif /* MG_ENABLE_MQTT */
@@ -11559,12 +12131,13 @@  static void mg_resolve_async_eh(struct mg_connection *nc, int ev,
   time_t now = (time_t) mg_time();
   struct mg_resolve_async_request *req;
   struct mg_dns_message *msg;
-  int first = 0;
 #if !MG_ENABLE_CALLBACK_USERDATA
   void *user_data = nc->user_data;
 #endif
 
-  if (ev != MG_EV_POLL) DBG(("ev=%d user_data=%p", ev, user_data));
+  if (ev != MG_EV_POLL) {
+    DBG(("ev=%d user_data=%p", ev, user_data));
+  }
 
   req = (struct mg_resolve_async_request *) user_data;
 
@@ -11573,17 +12146,16 @@  static void mg_resolve_async_eh(struct mg_connection *nc, int ev,
   }
 
   switch (ev) {
-    case MG_EV_CONNECT:
-      /* don't depend on timer not being at epoch for sending out first req */
-      first = 1;
-    /* fallthrough */
     case MG_EV_POLL:
       if (req->retries > req->max_retries) {
         req->err = MG_RESOLVE_EXCEEDED_RETRY_COUNT;
         nc->flags |= MG_F_CLOSE_IMMEDIATELY;
         break;
       }
-      if (first || now - req->last_time >= req->timeout) {
+      if (nc->flags & MG_F_CONNECTING) break;
+    /* fallthrough */
+    case MG_EV_CONNECT:
+      if (req->retries == 0 || now - req->last_time >= req->timeout) {
         mg_send_dns_query(nc, req->name, req->query);
         req->last_time = now;
         req->retries++;
@@ -12472,7 +13044,7 @@  static void mg_sntp_handler(struct mg_connection *c, int ev,
   }
 }
 
-static int mg_set_protocol_sntp(struct mg_connection *c) {
+int mg_set_protocol_sntp(struct mg_connection *c) {
   if ((c->flags & MG_F_UDP) == 0) {
     return -1;
   }
@@ -12758,8 +13330,20 @@  void mg_set_protocol_socks(struct mg_connection *c) {
 #line 1 "common/platforms/cc3200/cc3200_libc.c"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #if CS_PLATFORM == CS_P_CC3200
@@ -12864,8 +13448,20 @@  int _isatty(int fd) {
 #line 1 "common/platforms/msp432/msp432_libc.c"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #if CS_PLATFORM == CS_P_MSP432
@@ -12885,8 +13481,20 @@  int gettimeofday(struct timeval *tp, void *tzp) {
 #line 1 "common/platforms/nrf5/nrf5_libc.c"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #if (CS_PLATFORM == CS_P_NRF51 || CS_PLATFORM == CS_P_NRF52) && \
@@ -12902,8 +13510,20 @@  int gettimeofday(struct timeval *tp, void *tzp) {
 #line 1 "common/platforms/simplelink/sl_fs_slfs.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_PLATFORMS_SIMPLELINK_SL_FS_SLFS_H_
@@ -12930,7 +13550,9 @@  off_t fs_slfs_lseek(int fd, off_t offset, int whence);
 int fs_slfs_unlink(const char *filename);
 int fs_slfs_rename(const char *from, const char *to);
 
-void fs_slfs_set_new_file_size(const char *name, size_t size);
+void fs_slfs_set_file_size(const char *name, size_t size);
+void fs_slfs_set_file_flags(const char *name, uint32_t flags, uint32_t *token);
+void fs_slfs_unset_file_flags(const char *name);
 
 #endif /* defined(MG_FS_SLFS) */
 
@@ -12939,8 +13561,20 @@  void fs_slfs_set_new_file_size(const char *name, size_t size);
 #line 1 "common/platforms/simplelink/sl_fs_slfs.c"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 /* Standard libc interface to TI SimpleLink FS. */
@@ -12959,14 +13593,14 @@  void fs_slfs_set_new_file_size(const char *name, size_t size);
 /* Amalgamated: #include "common/mg_mem.h" */
 
 #if SL_MAJOR_VERSION_NUM < 2
-int slfs_open(const unsigned char *fname, uint32_t flags) {
+int slfs_open(const unsigned char *fname, uint32_t flags, uint32_t *token) {
   _i32 fh;
-  _i32 r = sl_FsOpen(fname, flags, NULL /* token */, &fh);
+  _i32 r = sl_FsOpen(fname, flags, (unsigned long *) token, &fh);
   return (r < 0 ? r : fh);
 }
 #else /* SL_MAJOR_VERSION_NUM >= 2 */
-int slfs_open(const unsigned char *fname, uint32_t flags) {
-  return sl_FsOpen(fname, flags, NULL /* token */);
+int slfs_open(const unsigned char *fname, uint32_t flags, uint32_t *token) {
+  return sl_FsOpen(fname, flags, (unsigned long *) token);
 }
 #endif
 
@@ -12982,9 +13616,11 @@  const char *drop_dir(const char *fname, bool *is_slfs);
 #define FS_SLFS_MAX_FILE_SIZE (64 * 1024)
 #endif
 
-struct sl_file_size_hint {
+struct sl_file_open_info {
   char *name;
   size_t size;
+  uint32_t flags;
+  uint32_t *token;
 };
 
 struct sl_fd_info {
@@ -12994,7 +13630,10 @@  struct sl_fd_info {
 };
 
 static struct sl_fd_info s_sl_fds[MAX_OPEN_SLFS_FILES];
-static struct sl_file_size_hint s_sl_file_size_hints[MAX_OPEN_SLFS_FILES];
+static struct sl_file_open_info s_sl_file_open_infos[MAX_OPEN_SLFS_FILES];
+
+static struct sl_file_open_info *fs_slfs_find_foi(const char *name,
+                                                  bool create);
 
 static int sl_fs_to_errno(_i32 r) {
   DBG(("SL error: %d", (int) r));
@@ -13035,7 +13674,13 @@  int fs_slfs_open(const char *pathname, int flags, mode_t mode) {
   _u32 am = 0;
   fi->size = (size_t) -1;
   int rw = (flags & 3);
-  size_t new_size = FS_SLFS_MAX_FILE_SIZE;
+  size_t new_size = 0;
+  struct sl_file_open_info *foi =
+      fs_slfs_find_foi(pathname, false /* create */);
+  if (foi != NULL) {
+    LOG(LL_DEBUG, ("FOI for %s: %d 0x%x %p", pathname, (int) foi->size,
+                   (unsigned int) foi->flags, foi->token));
+  }
   if (rw == O_RDONLY) {
     SlFsFileInfo_t sl_fi;
     _i32 r = sl_FsGetInfo((const _u8 *) pathname, 0, &sl_fi);
@@ -13050,24 +13695,27 @@  int fs_slfs_open(const char *pathname, int flags, mode_t mode) {
       return set_errno(ENOTSUP);
     }
     if (flags & O_CREAT) {
-      size_t i;
-      for (i = 0; i < MAX_OPEN_SLFS_FILES; i++) {
-        if (s_sl_file_size_hints[i].name != NULL &&
-            strcmp(s_sl_file_size_hints[i].name, pathname) == 0) {
-          new_size = s_sl_file_size_hints[i].size;
-          MG_FREE(s_sl_file_size_hints[i].name);
-          s_sl_file_size_hints[i].name = NULL;
-          break;
-        }
+      if (foi->size > 0) {
+        new_size = foi->size;
+      } else {
+        new_size = FS_SLFS_MAX_FILE_SIZE;
       }
       am = FS_MODE_OPEN_CREATE(new_size, 0);
     } else {
       am = SL_FS_WRITE;
     }
+#if SL_MAJOR_VERSION_NUM >= 2
+    am |= SL_FS_OVERWRITE;
+#endif
+  }
+  uint32_t *token = NULL;
+  if (foi != NULL) {
+    am |= foi->flags;
+    token = foi->token;
   }
-  fi->fh = slfs_open((_u8 *) pathname, am);
-  LOG(LL_DEBUG, ("sl_FsOpen(%s, 0x%x) sz %u = %d", pathname, (int) am,
-                 (unsigned int) new_size, (int) fi->fh));
+  fi->fh = slfs_open((_u8 *) pathname, am, token);
+  LOG(LL_DEBUG, ("sl_FsOpen(%s, 0x%x, %p) sz %u = %d", pathname, (int) am,
+                 token, (unsigned int) new_size, (int) fi->fh));
   int r;
   if (fi->fh >= 0) {
     fi->pos = 0;
@@ -13171,16 +13819,46 @@  int fs_slfs_rename(const char *from, const char *to) {
   return set_errno(ENOTSUP);
 }
 
-void fs_slfs_set_new_file_size(const char *name, size_t size) {
-  int i;
+static struct sl_file_open_info *fs_slfs_find_foi(const char *name,
+                                                  bool create) {
+  int i = 0;
   for (i = 0; i < MAX_OPEN_SLFS_FILES; i++) {
-    if (s_sl_file_size_hints[i].name == NULL) {
-      DBG(("File size hint: %s %d", name, (int) size));
-      s_sl_file_size_hints[i].name = strdup(name);
-      s_sl_file_size_hints[i].size = size;
+    if (s_sl_file_open_infos[i].name != NULL &&
+        strcmp(drop_dir(s_sl_file_open_infos[i].name, NULL), name) == 0) {
       break;
     }
   }
+  if (i != MAX_OPEN_SLFS_FILES) return &s_sl_file_open_infos[i];
+  if (!create) return NULL;
+  for (i = 0; i < MAX_OPEN_SLFS_FILES; i++) {
+    if (s_sl_file_open_infos[i].name == NULL) break;
+  }
+  if (i == MAX_OPEN_SLFS_FILES) {
+    i = 0; /* Evict a random slot. */
+  }
+  if (s_sl_file_open_infos[i].name != NULL) {
+    free(s_sl_file_open_infos[i].name);
+  }
+  s_sl_file_open_infos[i].name = strdup(name);
+  return &s_sl_file_open_infos[i];
+}
+
+void fs_slfs_set_file_size(const char *name, size_t size) {
+  struct sl_file_open_info *foi = fs_slfs_find_foi(name, true /* create */);
+  foi->size = size;
+}
+
+void fs_slfs_set_file_flags(const char *name, uint32_t flags, uint32_t *token) {
+  struct sl_file_open_info *foi = fs_slfs_find_foi(name, true /* create */);
+  foi->flags = flags;
+  foi->token = token;
+}
+
+void fs_slfs_unset_file_flags(const char *name) {
+  struct sl_file_open_info *foi = fs_slfs_find_foi(name, false /* create */);
+  if (foi == NULL) return;
+  free(foi->name);
+  memset(foi, 0, sizeof(*foi));
 }
 
 #endif /* defined(MG_FS_SLFS) || defined(CC3200_FS_SLFS) */
@@ -13188,8 +13866,20 @@  void fs_slfs_set_new_file_size(const char *name, size_t size) {
 #line 1 "common/platforms/simplelink/sl_fs.c"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #if MG_NET_IF == MG_NET_IF_SIMPLELINK && \
@@ -13602,8 +14292,20 @@  int sl_fs_init(void) {
 #line 1 "common/platforms/simplelink/sl_socket.c"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #if MG_NET_IF == MG_NET_IF_SIMPLELINK
@@ -13708,8 +14410,20 @@  void mg_run_in_task(void (*cb)(struct mg_mgr *mgr, void *arg), void *cb_arg) {
 #line 1 "common/platforms/simplelink/sl_net_if.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_PLATFORMS_SIMPLELINK_SL_NET_IF_H_
@@ -13736,8 +14450,20 @@  extern const struct mg_iface_vtable mg_simplelink_iface_vtable;
 #line 1 "common/platforms/simplelink/sl_net_if.c"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 /* Amalgamated: #include "common/platforms/simplelink/sl_net_if.h" */
@@ -13768,17 +14494,19 @@  static int mg_is_error(int n) {
   return (n < 0 && n != SL_ERROR_BSD_EALREADY && n != SL_ERROR_BSD_EAGAIN);
 }
 
-void mg_sl_if_connect_tcp(struct mg_connection *nc,
-                          const union socket_address *sa) {
+static void mg_sl_if_connect_tcp(struct mg_connection *nc,
+                                 const union socket_address *sa) {
   int proto = 0;
+#if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_SIMPLELINK
   if (nc->flags & MG_F_SSL) proto = SL_SEC_SOCKET;
+#endif
   sock_t sock = sl_Socket(AF_INET, SOCK_STREAM, proto);
   if (sock < 0) {
     nc->err = sock;
     goto out;
   }
   mg_sock_set(nc, sock);
-#if MG_ENABLE_SSL
+#if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_SIMPLELINK
   nc->err = sl_set_ssl_opts(sock, nc);
   if (nc->err != 0) goto out;
 #endif
@@ -13788,7 +14516,7 @@  out:
        ntohs(sa->sin.sin_port), nc->sock, proto, nc->err));
 }
 
-void mg_sl_if_connect_udp(struct mg_connection *nc) {
+static void mg_sl_if_connect_udp(struct mg_connection *nc) {
   sock_t sock = sl_Socket(AF_INET, SOCK_DGRAM, 0);
   if (sock < 0) {
     nc->err = sock;
@@ -13798,7 +14526,8 @@  void mg_sl_if_connect_udp(struct mg_connection *nc) {
   nc->err = 0;
 }
 
-int mg_sl_if_listen_tcp(struct mg_connection *nc, union socket_address *sa) {
+static int mg_sl_if_listen_tcp(struct mg_connection *nc,
+                               union socket_address *sa) {
   int proto = 0;
   if (nc->flags & MG_F_SSL) proto = SL_SEC_SOCKET;
   sock_t sock = mg_open_listening_socket(nc, sa, SOCK_STREAM, proto);
@@ -13807,27 +14536,50 @@  int mg_sl_if_listen_tcp(struct mg_connection *nc, union socket_address *sa) {
   return 0;
 }
 
-int mg_sl_if_listen_udp(struct mg_connection *nc, union socket_address *sa) {
+static int mg_sl_if_listen_udp(struct mg_connection *nc,
+                               union socket_address *sa) {
   sock_t sock = mg_open_listening_socket(nc, sa, SOCK_DGRAM, 0);
   if (sock == INVALID_SOCKET) return (errno ? errno : 1);
   mg_sock_set(nc, sock);
   return 0;
 }
 
-void mg_sl_if_tcp_send(struct mg_connection *nc, const void *buf, size_t len) {
-  mbuf_append(&nc->send_mbuf, buf, len);
+static int mg_sl_if_tcp_send(struct mg_connection *nc, const void *buf,
+                             size_t len) {
+  int n = (int) sl_Send(nc->sock, buf, len, 0);
+  if (n < 0 && !mg_is_error(n)) n = 0;
+  return n;
 }
 
-void mg_sl_if_udp_send(struct mg_connection *nc, const void *buf, size_t len) {
-  mbuf_append(&nc->send_mbuf, buf, len);
+static int mg_sl_if_udp_send(struct mg_connection *nc, const void *buf,
+                             size_t len) {
+  int n = sl_SendTo(nc->sock, buf, len, 0, &nc->sa.sa, sizeof(nc->sa.sin));
+  if (n < 0 && !mg_is_error(n)) n = 0;
+  return n;
 }
 
-void mg_sl_if_recved(struct mg_connection *nc, size_t len) {
-  (void) nc;
-  (void) len;
+static int mg_sl_if_tcp_recv(struct mg_connection *nc, void *buf, size_t len) {
+  int n = sl_Recv(nc->sock, buf, len, 0);
+  if (n == 0) {
+    /* Orderly shutdown of the socket, try flushing output. */
+    nc->flags |= MG_F_SEND_AND_CLOSE;
+  } else if (n < 0 && !mg_is_error(n)) {
+    n = 0;
+  }
+  return n;
+}
+
+static int mg_sl_if_udp_recv(struct mg_connection *nc, void *buf, size_t len,
+                             union socket_address *sa, size_t *sa_len) {
+  SlSocklen_t sa_len_t = *sa_len;
+  int n = sl_RecvFrom(nc->sock, buf, MG_UDP_RECV_BUFFER_SIZE, 0,
+                      (SlSockAddr_t *) sa, &sa_len_t);
+  *sa_len = sa_len_t;
+  if (n < 0 && !mg_is_error(n)) n = 0;
+  return n;
 }
 
-int mg_sl_if_create_conn(struct mg_connection *nc) {
+static int mg_sl_if_create_conn(struct mg_connection *nc) {
   (void) nc;
   return 1;
 }
@@ -13858,7 +14610,6 @@  static int mg_accept_conn(struct mg_connection *lc) {
   DBG(("%p conn from %s:%d", nc, inet_ntoa(sa.sin.sin_addr),
        ntohs(sa.sin.sin_port)));
   mg_sock_set(nc, sock);
-  if (nc->flags & MG_F_SSL) nc->flags |= MG_F_SSL_HANDSHAKE_DONE;
   mg_if_accept_tcp_cb(nc, &sa, sa_len);
   return 1;
 }
@@ -13872,11 +14623,11 @@  static sock_t mg_open_listening_socket(struct mg_connection *nc,
       (sa->sa.sa_family == AF_INET) ? sizeof(sa->sin) : sizeof(sa->sin6);
   sock_t sock = sl_Socket(sa->sa.sa_family, type, proto);
   if (sock < 0) return sock;
+#if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_SIMPLELINK
+  if ((r = sl_set_ssl_opts(sock, nc)) < 0) goto clean;
+#endif
   if ((r = sl_Bind(sock, &sa->sa, sa_len)) < 0) goto clean;
   if (type != SOCK_DGRAM) {
-#if MG_ENABLE_SSL
-    if ((r = sl_set_ssl_opts(sock, nc)) < 0) goto clean;
-#endif
     if ((r = sl_Listen(sock, SOMAXCONN)) < 0) goto clean;
   }
   mg_set_non_blocking_mode(sock);
@@ -13888,86 +14639,18 @@  clean:
   return sock;
 }
 
-static void mg_write_to_socket(struct mg_connection *nc) {
-  struct mbuf *io = &nc->send_mbuf;
-  int n = 0;
-
-  if (nc->flags & MG_F_UDP) {
-    n = sl_SendTo(nc->sock, io->buf, io->len, 0, &nc->sa.sa,
-                  sizeof(nc->sa.sin));
-    DBG(("%p %d %d %d %s:%hu", nc, nc->sock, n, errno,
-         inet_ntoa(nc->sa.sin.sin_addr), ntohs(nc->sa.sin.sin_port)));
-  } else {
-    n = (int) sl_Send(nc->sock, io->buf, io->len, 0);
-    DBG(("%p %d bytes -> %d", nc, n, nc->sock));
-  }
-
-  if (n > 0) {
-    mg_if_sent_cb(nc, n);
-  } else if (n < 0 && mg_is_error(n)) {
-    /* Something went wrong, drop the connection. */
-    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
-  }
-}
-
-MG_INTERNAL size_t recv_avail_size(struct mg_connection *conn, size_t max) {
-  size_t avail;
-  if (conn->recv_mbuf_limit < conn->recv_mbuf.len) return 0;
-  avail = conn->recv_mbuf_limit - conn->recv_mbuf.len;
-  return avail > max ? max : avail;
-}
-
-static void mg_handle_tcp_read(struct mg_connection *conn) {
-  int n = 0;
-  char *buf = (char *) MG_MALLOC(MG_TCP_RECV_BUFFER_SIZE);
-
-  if (buf == NULL) {
-    DBG(("OOM"));
-    return;
-  }
-
-  n = (int) sl_Recv(conn->sock, buf,
-                    recv_avail_size(conn, MG_TCP_RECV_BUFFER_SIZE), 0);
-  DBG(("%p %d bytes <- %d", conn, n, conn->sock));
-  if (n > 0) {
-    mg_if_recv_tcp_cb(conn, buf, n, 1 /* own */);
-  } else {
-    MG_FREE(buf);
-  }
-  if (n == 0) {
-    /* Orderly shutdown of the socket, try flushing output. */
-    conn->flags |= MG_F_SEND_AND_CLOSE;
-  } else if (mg_is_error(n)) {
-    conn->flags |= MG_F_CLOSE_IMMEDIATELY;
-  }
-}
-
-static void mg_handle_udp_read(struct mg_connection *nc) {
-  char *buf = (char *) MG_MALLOC(MG_UDP_RECV_BUFFER_SIZE);
-  if (buf == NULL) return;
-  union socket_address sa;
-  socklen_t sa_len = sizeof(sa);
-  int n = sl_RecvFrom(nc->sock, buf, MG_UDP_RECV_BUFFER_SIZE, 0,
-                      (SlSockAddr_t *) &sa, &sa_len);
-  DBG(("%p %d bytes from %s:%d", nc, n, inet_ntoa(nc->sa.sin.sin_addr),
-       ntohs(nc->sa.sin.sin_port)));
-  if (n > 0) {
-    mg_if_recv_udp_cb(nc, buf, n, &sa, sa_len);
-  } else {
-    MG_FREE(buf);
-  }
-}
-
 #define _MG_F_FD_CAN_READ 1
 #define _MG_F_FD_CAN_WRITE 1 << 1
 #define _MG_F_FD_ERROR 1 << 2
 
-static void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {
-  DBG(("%p fd=%d fd_flags=%d nc_flags=%lu rmbl=%d smbl=%d", nc, nc->sock,
+void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {
+  DBG(("%p fd=%d fd_flags=%d nc_flags=0x%lx rmbl=%d smbl=%d", nc, nc->sock,
        fd_flags, nc->flags, (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
 
+  if (!mg_if_poll(nc, now)) return;
+
   if (nc->flags & MG_F_CONNECTING) {
-    if (nc->flags & MG_F_UDP || nc->err != SL_ERROR_BSD_EALREADY) {
+    if ((nc->flags & MG_F_UDP) || nc->err != SL_ERROR_BSD_EALREADY) {
       mg_if_connect_cb(nc, nc->err);
     } else {
       /* In SimpleLink, to get status of non-blocking connect() we need to wait
@@ -13989,9 +14672,6 @@  static void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double no
             ) {
           nc->err = 0;
         }
-        if (nc->flags & MG_F_SSL && nc->err == 0) {
-          nc->flags |= MG_F_SSL_HANDSHAKE_DONE;
-        }
         mg_if_connect_cb(nc, nc->err);
       }
     }
@@ -14001,28 +14681,21 @@  static void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double no
 
   if (fd_flags & _MG_F_FD_CAN_READ) {
     if (nc->flags & MG_F_UDP) {
-      mg_handle_udp_read(nc);
+      mg_if_can_recv_cb(nc);
     } else {
       if (nc->flags & MG_F_LISTENING) {
         mg_accept_conn(nc);
       } else {
-        mg_handle_tcp_read(nc);
+        mg_if_can_recv_cb(nc);
       }
     }
   }
 
-  if (!(nc->flags & MG_F_CLOSE_IMMEDIATELY)) {
-    if ((fd_flags & _MG_F_FD_CAN_WRITE) && nc->send_mbuf.len > 0) {
-      mg_write_to_socket(nc);
-    }
-
-    if (!(fd_flags & (_MG_F_FD_CAN_READ | _MG_F_FD_CAN_WRITE))) {
-      mg_if_poll(nc, now);
-    }
-    mg_if_timer(nc, now);
+  if (fd_flags & _MG_F_FD_CAN_WRITE) {
+    mg_if_can_send_cb(nc);
   }
 
-  DBG(("%p after fd=%d nc_flags=%lu rmbl=%d smbl=%d", nc, nc->sock, nc->flags,
+  DBG(("%p after fd=%d nc_flags=0x%lx rmbl=%d smbl=%d", nc, nc->sock, nc->flags,
        (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
 }
 
@@ -14143,14 +14816,6 @@  time_t mg_sl_if_poll(struct mg_iface *iface, int timeout_ms) {
     mg_mgr_handle_conn(nc, fd_flags, now);
   }
 
-  for (nc = mgr->active_connections; nc != NULL; nc = tmp) {
-    tmp = nc->next;
-    if ((nc->flags & MG_F_CLOSE_IMMEDIATELY) ||
-        (nc->send_mbuf.len == 0 && (nc->flags & MG_F_SEND_AND_CLOSE))) {
-      mg_close_conn(nc);
-    }
-  }
-
   return now;
 }
 
@@ -14201,7 +14866,8 @@  void sl_restart_cb(struct mg_mgr *mgr) {
     mg_sl_if_connect_udp,                                               \
     mg_sl_if_tcp_send,                                                  \
     mg_sl_if_udp_send,                                                  \
-    mg_sl_if_recved,                                                    \
+    mg_sl_if_tcp_recv,                                                  \
+    mg_sl_if_udp_recv,                                                  \
     mg_sl_if_create_conn,                                               \
     mg_sl_if_destroy_conn,                                              \
     mg_sl_if_sock_set,                                                  \
@@ -14219,8 +14885,20 @@  const struct mg_iface_vtable mg_default_iface_vtable = MG_SL_IFACE_VTABLE;
 #line 1 "common/platforms/simplelink/sl_ssl_if.c"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_SIMPLELINK
@@ -14241,7 +14919,7 @@  struct mg_ssl_if_ctx {
   char *ssl_server_name;
 };
 
-void mg_ssl_if_init(void) {
+void mg_ssl_if_init() {
 }
 
 enum mg_ssl_if_result mg_ssl_if_conn_init(
@@ -14274,6 +14952,31 @@  enum mg_ssl_if_result mg_ssl_if_conn_init(
   return MG_SSL_OK;
 }
 
+enum mg_ssl_if_result mg_ssl_if_conn_accept(struct mg_connection *nc,
+                                            struct mg_connection *lc) {
+  /* SimpleLink does everything for us, nothing for us to do. */
+  (void) nc;
+  (void) lc;
+  return MG_SSL_OK;
+}
+
+enum mg_ssl_if_result mg_ssl_if_handshake(struct mg_connection *nc) {
+  /* SimpleLink has already performed the handshake, nothing to do. */
+  return MG_SSL_OK;
+}
+
+int mg_ssl_if_read(struct mg_connection *nc, void *buf, size_t len) {
+  /* SimpelLink handles TLS, so this is just a pass-through. */
+  int n = nc->iface->vtable->tcp_recv(nc, buf, len);
+  if (n == 0) nc->flags |= MG_F_WANT_READ;
+  return n;
+}
+
+int mg_ssl_if_write(struct mg_connection *nc, const void *buf, size_t len) {
+  /* SimpelLink handles TLS, so this is just a pass-through. */
+  return nc->iface->vtable->tcp_send(nc, buf, len);
+}
+
 void mg_ssl_if_conn_close_notify(struct mg_connection *nc) {
   /* Nothing to do */
   (void) nc;
@@ -14298,9 +15001,9 @@  bool pem_to_der(const char *pem_file, const char *der_file) {
   pf = fopen(pem_file, "r");
   if (pf == NULL) goto clean;
   remove(der_file);
-  fs_slfs_set_new_file_size(der_file + MG_SSL_IF_SIMPLELINK_SLFS_PREFIX_LEN,
-                            2048);
+  fs_slfs_set_file_size(der_file + MG_SSL_IF_SIMPLELINK_SLFS_PREFIX_LEN, 2048);
   df = fopen(der_file, "w");
+  fs_slfs_unset_file_flags(der_file + MG_SSL_IF_SIMPLELINK_SLFS_PREFIX_LEN);
   if (df == NULL) goto clean;
   while (1) {
     char pem_buf[70];
@@ -14374,56 +15077,59 @@  int sl_set_ssl_opts(int sock, struct mg_connection *nc) {
   const struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;
   DBG(("%p ssl ctx: %p", nc, ctx));
 
-  if (ctx != NULL) {
-    DBG(("%p %s,%s,%s,%s", nc, (ctx->ssl_cert ? ctx->ssl_cert : "-"),
-         (ctx->ssl_key ? ctx->ssl_cert : "-"),
-         (ctx->ssl_ca_cert ? ctx->ssl_ca_cert : "-"),
-         (ctx->ssl_server_name ? ctx->ssl_server_name : "-")));
-    if (ctx->ssl_cert != NULL && ctx->ssl_key != NULL) {
-      char *ssl_cert = sl_pem2der(ctx->ssl_cert);
-      char *ssl_key = sl_pem2der(ctx->ssl_key);
-      if (ssl_cert != NULL && ssl_key != NULL) {
-        err = sl_SetSockOpt(sock, SL_SOL_SOCKET,
-                            SL_SO_SECURE_FILES_CERTIFICATE_FILE_NAME, ssl_cert,
-                            strlen(ssl_cert));
-        LOG(LL_INFO, ("CERTIFICATE_FILE_NAME %s -> %d", ssl_cert, err));
+  if (ctx == NULL) return 0;
+  DBG(("%p %s,%s,%s,%s", nc, (ctx->ssl_cert ? ctx->ssl_cert : "-"),
+       (ctx->ssl_key ? ctx->ssl_cert : "-"),
+       (ctx->ssl_ca_cert ? ctx->ssl_ca_cert : "-"),
+       (ctx->ssl_server_name ? ctx->ssl_server_name : "-")));
+  if (ctx->ssl_cert != NULL && ctx->ssl_key != NULL) {
+    char *ssl_cert = sl_pem2der(ctx->ssl_cert), *ssl_key = NULL;
+    if (ssl_cert != NULL) {
+      err = sl_SetSockOpt(sock, SL_SOL_SOCKET,
+                          SL_SO_SECURE_FILES_CERTIFICATE_FILE_NAME, ssl_cert,
+                          strlen(ssl_cert));
+      MG_FREE(ssl_cert);
+      LOG(LL_DEBUG, ("CERTIFICATE_FILE_NAME %s -> %d", ssl_cert, err));
+      ssl_key = sl_pem2der(ctx->ssl_key);
+      if (ssl_key != NULL) {
         err = sl_SetSockOpt(sock, SL_SOL_SOCKET,
                             SL_SO_SECURE_FILES_PRIVATE_KEY_FILE_NAME, ssl_key,
                             strlen(ssl_key));
-        LOG(LL_INFO, ("PRIVATE_KEY_FILE_NAME %s -> %d", ssl_key, err));
+        MG_FREE(ssl_key);
+        LOG(LL_DEBUG, ("PRIVATE_KEY_FILE_NAME %s -> %d", ssl_key, err));
       } else {
         err = -1;
       }
-      MG_FREE(ssl_cert);
-      MG_FREE(ssl_key);
-      if (err != 0) return err;
-    }
-    if (ctx->ssl_ca_cert != NULL) {
-      if (ctx->ssl_ca_cert[0] != '\0') {
-        char *ssl_ca_cert = sl_pem2der(ctx->ssl_ca_cert);
-        if (ssl_ca_cert != NULL) {
-          err = sl_SetSockOpt(sock, SL_SOL_SOCKET,
-                              SL_SO_SECURE_FILES_CA_FILE_NAME, ssl_ca_cert,
-                              strlen(ssl_ca_cert));
-          LOG(LL_INFO, ("CA_FILE_NAME %s -> %d", ssl_ca_cert, err));
-        } else {
-          err = -1;
-        }
-        MG_FREE(ssl_ca_cert);
-        if (err != 0) return err;
+    } else {
+      err = -1;
+    }
+    if (err != 0) return err;
+  }
+  if (ctx->ssl_ca_cert != NULL) {
+    if (ctx->ssl_ca_cert[0] != '\0') {
+      char *ssl_ca_cert = sl_pem2der(ctx->ssl_ca_cert);
+      if (ssl_ca_cert != NULL) {
+        err =
+            sl_SetSockOpt(sock, SL_SOL_SOCKET, SL_SO_SECURE_FILES_CA_FILE_NAME,
+                          ssl_ca_cert, strlen(ssl_ca_cert));
+        LOG(LL_DEBUG, ("CA_FILE_NAME %s -> %d", ssl_ca_cert, err));
+      } else {
+        err = -1;
       }
+      MG_FREE(ssl_ca_cert);
+      if (err != 0) return err;
     }
-    if (ctx->ssl_server_name != NULL) {
-      err = sl_SetSockOpt(sock, SL_SOL_SOCKET,
-                          SL_SO_SECURE_DOMAIN_NAME_VERIFICATION,
-                          ctx->ssl_server_name, strlen(ctx->ssl_server_name));
-      DBG(("DOMAIN_NAME_VERIFICATION %s -> %d", ctx->ssl_server_name, err));
-      /* Domain name verificationw as added in a NWP service pack, older
-       * versions return SL_ERROR_BSD_ENOPROTOOPT. There isn't much we can do
-       * about it,
-       * so we ignore the error. */
-      if (err != 0 && err != SL_ERROR_BSD_ENOPROTOOPT) return err;
-    }
+  }
+  if (ctx->ssl_server_name != NULL) {
+    err = sl_SetSockOpt(sock, SL_SOL_SOCKET,
+                        SL_SO_SECURE_DOMAIN_NAME_VERIFICATION,
+                        ctx->ssl_server_name, strlen(ctx->ssl_server_name));
+    DBG(("DOMAIN_NAME_VERIFICATION %s -> %d", ctx->ssl_server_name, err));
+    /* Domain name verificationw as added in a NWP service pack, older
+     * versions return SL_ERROR_BSD_ENOPROTOOPT. There isn't much we can do
+     * about it,
+     * so we ignore the error. */
+    if (err != 0 && err != SL_ERROR_BSD_ENOPROTOOPT) return err;
   }
   return 0;
 }
@@ -14433,8 +15139,20 @@  int sl_set_ssl_opts(int sock, struct mg_connection *nc) {
 #line 1 "common/platforms/lwip/mg_lwip_net_if.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_PLATFORMS_LWIP_MG_NET_IF_LWIP_H_
@@ -14464,9 +15182,9 @@  struct mg_lwip_conn_state {
   /* Last SSL write size, for retries. */
   int last_ssl_write_size;
   /* Whether MG_SIG_RECV is already pending for this connection */
-  int recv_pending : 1;
+  int recv_pending;
   /* Whether the connection is about to close, just `rx_chain` needs to drain */
-  int draining_rx_chain : 1;
+  int draining_rx_chain;
 };
 
 enum mg_sig_type {
@@ -14489,8 +15207,20 @@  void mg_lwip_mgr_schedule_poll(struct mg_mgr *mgr);
 #line 1 "common/platforms/lwip/mg_lwip_net_if.c"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #if MG_ENABLE_NET_IF_LWIP_LOW_LEVEL
@@ -14502,7 +15232,8 @@  void mg_lwip_mgr_schedule_poll(struct mg_mgr *mgr);
 #include <lwip/tcp.h>
 #include <lwip/tcpip.h>
 #if ((LWIP_VERSION_MAJOR << 8) | LWIP_VERSION_MINOR) >= 0x0105
-#include <lwip/priv/tcp_priv.h> /* For tcp_seg */
+#include <lwip/priv/tcp_priv.h>   /* For tcp_seg */
+#include <lwip/priv/tcpip_priv.h> /* For tcpip_api_call */
 #else
 #include <lwip/tcp_impl.h>
 #endif
@@ -14542,14 +15273,36 @@  void mg_lwip_mgr_schedule_poll(struct mg_mgr *mgr);
 #define SET_ADDR(dst, src) (dst)->sin.sin_addr.s_addr = ip_2_ip4(src)->addr
 #endif
 
-#if NO_SYS
-#define tcpip_callback(fn, arg) (fn)(arg)
-typedef void (*tcpip_callback_fn)(void *arg);
+#if !NO_SYS
+#if LWIP_TCPIP_CORE_LOCKING
+/* With locking tcpip_api_call is just a function call wrapped in lock/unlock,
+ * so we can get away with just casting. */
+void mg_lwip_netif_run_on_tcpip(void (*fn)(void *), void *arg) {
+  tcpip_api_call((tcpip_api_call_fn) fn, (struct tcpip_api_call_data *) arg);
+}
+#else
+static sys_sem_t s_tcpip_call_lock_sem = NULL;
+static sys_sem_t s_tcpip_call_sync_sem = NULL;
+struct mg_lwip_netif_tcpip_call_ctx {
+  void (*fn)(void *);
+  void *arg;
+};
+static void xxx_tcpip(void *arg) {
+  struct mg_lwip_netif_tcpip_call_ctx *ctx =
+      (struct mg_lwip_netif_tcpip_call_ctx *) arg;
+  ctx->fn(ctx->arg);
+  sys_sem_signal(&s_tcpip_call_sync_sem);
+}
+void mg_lwip_netif_run_on_tcpip(void (*fn)(void *), void *arg) {
+  struct mg_lwip_netif_tcpip_call_ctx ctx = {.fn = fn, .arg = arg};
+  sys_arch_sem_wait(&s_tcpip_call_lock_sem, 0);
+  tcpip_send_msg_wait_sem(xxx_tcpip, &ctx, &s_tcpip_call_sync_sem);
+  sys_sem_signal(&s_tcpip_call_lock_sem);
+}
+#endif
+#else
+#define mg_lwip_netif_run_on_tcpip(fn, arg) (fn)(arg)
 #endif
-
-void mg_lwip_ssl_do_hs(struct mg_connection *nc);
-void mg_lwip_ssl_send(struct mg_connection *nc);
-void mg_lwip_ssl_recv(struct mg_connection *nc);
 
 void mg_lwip_if_init(struct mg_iface *iface);
 void mg_lwip_if_free(struct mg_iface *iface);
@@ -14557,7 +15310,8 @@  void mg_lwip_if_add_conn(struct mg_connection *nc);
 void mg_lwip_if_remove_conn(struct mg_connection *nc);
 time_t mg_lwip_if_poll(struct mg_iface *iface, int timeout_ms);
 
-#if defined(RTOS_SDK) || defined(ESP_PLATFORM)
+// If compiling for Mongoose OS.
+#ifdef MGOS
 extern void mgos_lock();
 extern void mgos_unlock();
 #else
@@ -14608,7 +15362,7 @@  static err_t mg_lwip_tcp_conn_cb(void *arg, struct tcp_pcb *tpcb, err_t err) {
 static void mg_lwip_tcp_error_cb(void *arg, err_t err) {
   struct mg_connection *nc = (struct mg_connection *) arg;
   DBG(("%p conn error %d", nc, err));
-  if (nc == NULL) return;
+  if (nc == NULL || (nc->flags & MG_F_CLOSE_IMMEDIATELY)) return;
   struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
   cs->pcb.tcp = NULL; /* Has already been deallocated */
   if (nc->flags & MG_F_CONNECTING) {
@@ -14622,10 +15376,12 @@  static void mg_lwip_tcp_error_cb(void *arg, err_t err) {
 static err_t mg_lwip_tcp_recv_cb(void *arg, struct tcp_pcb *tpcb,
                                  struct pbuf *p, err_t err) {
   struct mg_connection *nc = (struct mg_connection *) arg;
-  DBG(("%p %p %u %d", nc, tpcb, (p != NULL ? p->tot_len : 0), err));
+  struct mg_lwip_conn_state *cs =
+      (nc ? (struct mg_lwip_conn_state *) nc->sock : NULL);
+  DBG(("%p %p %p %p %u %d", nc, cs, tpcb, p, (p != NULL ? p->tot_len : 0),
+       err));
   if (p == NULL) {
     if (nc != NULL && !(nc->flags & MG_F_CLOSE_IMMEDIATELY)) {
-      struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
       if (cs->rx_chain != NULL) {
         /*
          * rx_chain still contains non-consumed data, don't close the
@@ -14643,7 +15399,6 @@  static err_t mg_lwip_tcp_recv_cb(void *arg, struct tcp_pcb *tpcb,
     tcp_abort(tpcb);
     return ERR_ARG;
   }
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
   /*
    * If we get a chain of more than one segment at once, we need to bump
    * refcount on the subsequent bufs to make them independent.
@@ -14658,7 +15413,7 @@  static err_t mg_lwip_tcp_recv_cb(void *arg, struct tcp_pcb *tpcb,
   } else if (pbuf_clen(cs->rx_chain) >= 4) {
     /* ESP SDK has a limited pool of 5 pbufs. We must not hog them all or RX
      * will be completely blocked. We already have at least 4 in the chain,
-     * this one is, so we have to make a copy and release this one. */
+     * this one is the last, so we have to make a copy and release this one. */
     struct pbuf *np = pbuf_alloc(PBUF_RAW, p->tot_len, PBUF_RAM);
     if (np != NULL) {
       pbuf_copy(np, p);
@@ -14666,58 +15421,10 @@  static err_t mg_lwip_tcp_recv_cb(void *arg, struct tcp_pcb *tpcb,
       p = np;
     }
   }
-  mgos_unlock();
   mg_lwip_recv_common(nc, p);
-  return ERR_OK;
-}
-
-static void mg_lwip_consume_rx_chain_tcp(struct mg_connection *nc) {
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  if (cs->rx_chain == NULL) return;
-#if MG_ENABLE_SSL
-  if (nc->flags & MG_F_SSL) {
-    if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {
-      mg_lwip_ssl_recv(nc);
-    } else {
-      mg_lwip_ssl_do_hs(nc);
-    }
-    return;
-  }
-#endif
-  mgos_lock();
-  while (cs->rx_chain != NULL && nc->recv_mbuf.len < nc->recv_mbuf_limit) {
-    struct pbuf *seg = cs->rx_chain;
-
-    size_t seg_len = (seg->len - cs->rx_offset);
-    size_t buf_avail = (nc->recv_mbuf_limit - nc->recv_mbuf.len);
-    size_t len = MIN(seg_len, buf_avail);
-
-    char *data = (char *) MG_MALLOC(len);
-    if (data == NULL) {
-      mgos_unlock();
-      DBG(("OOM"));
-      return;
-    }
-    pbuf_copy_partial(seg, data, len, cs->rx_offset);
-    cs->rx_offset += len;
-    if (cs->rx_offset == cs->rx_chain->len) {
-      cs->rx_chain = pbuf_dechain(cs->rx_chain);
-      pbuf_free(seg);
-      cs->rx_offset = 0;
-    }
-    mgos_unlock();
-    mg_if_recv_tcp_cb(nc, data, len, 1 /* own */);
-    mgos_lock();
-  }
-  mgos_unlock();
-}
-
-static void mg_lwip_handle_recv_tcp(struct mg_connection *nc) {
-  mg_lwip_consume_rx_chain_tcp(nc);
-
-  if (nc->send_mbuf.len > 0) {
-    mg_lwip_mgr_schedule_poll(nc->mgr);
-  }
+  mgos_unlock();
+  (void) err;
+  return ERR_OK;
 }
 
 static err_t mg_lwip_tcp_sent_cb(void *arg, struct tcp_pcb *tpcb,
@@ -14729,6 +15436,10 @@  static err_t mg_lwip_tcp_sent_cb(void *arg, struct tcp_pcb *tpcb,
       nc->send_mbuf.len == 0 && tpcb->unsent == NULL && tpcb->unacked == NULL) {
     mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
   }
+  if (nc->send_mbuf.len > 0 || (nc->flags & MG_F_WANT_WRITE)) {
+    mg_lwip_mgr_schedule_poll(nc->mgr);
+  }
+  (void) num_sent;
   return ERR_OK;
 }
 
@@ -14769,7 +15480,7 @@  static void mg_lwip_if_connect_tcp_tcpip(void *arg) {
 void mg_lwip_if_connect_tcp(struct mg_connection *nc,
                             const union socket_address *sa) {
   struct mg_lwip_if_connect_tcp_ctx ctx = {.nc = nc, .sa = sa};
-  tcpip_callback(mg_lwip_if_connect_tcp_tcpip, &ctx);
+  mg_lwip_netif_run_on_tcpip(mg_lwip_if_connect_tcp_tcpip, &ctx);
 }
 
 /*
@@ -14803,13 +15514,14 @@  static void mg_lwip_udp_recv_cb(void *arg, struct udp_pcb *pcb, struct pbuf *p,
   /* Logic in the recv handler requires that there be exactly one data pbuf. */
   p = pbuf_coalesce(p, PBUF_RAW);
   pbuf_chain(sap, p);
+  mgos_lock();
   mg_lwip_recv_common(nc, sap);
+  mgos_unlock();
   (void) pcb;
 }
 
 static void mg_lwip_recv_common(struct mg_connection *nc, struct pbuf *p) {
   struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  mgos_lock();
   if (cs->rx_chain == NULL) {
     cs->rx_chain = p;
   } else {
@@ -14819,32 +15531,30 @@  static void mg_lwip_recv_common(struct mg_connection *nc, struct pbuf *p) {
     cs->recv_pending = 1;
     mg_lwip_post_signal(MG_SIG_RECV, nc);
   }
-  mgos_unlock();
 }
 
-static void mg_lwip_handle_recv_udp(struct mg_connection *nc) {
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+static int mg_lwip_if_udp_recv(struct mg_connection *nc, void *buf, size_t len,
+                               union socket_address *sa, size_t *sa_len) {
   /*
    * For UDP, RX chain consists of interleaved address and packet bufs:
    * Address pbuf followed by exactly one data pbuf (recv_cb took care of that).
    */
-  while (cs->rx_chain != NULL) {
-    struct pbuf *sap = cs->rx_chain;
-    struct pbuf *p = sap->next;
-    cs->rx_chain = pbuf_dechain(p);
-    size_t data_len = p->len;
-    char *data = (char *) MG_MALLOC(data_len);
-    if (data != NULL) {
-      pbuf_copy_partial(p, data, data_len, 0);
-      pbuf_free(p);
-      mg_if_recv_udp_cb(nc, data, data_len,
-                        (union socket_address *) sap->payload, sap->len);
-      pbuf_free(sap);
-    } else {
-      pbuf_free(p);
-      pbuf_free(sap);
-    }
+  int res = 0;
+  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+  if (nc->sock == INVALID_SOCKET) return -1;
+  mgos_lock();
+  if (cs->rx_chain != NULL) {
+    struct pbuf *ap = cs->rx_chain;
+    struct pbuf *dp = ap->next;
+    cs->rx_chain = pbuf_dechain(dp);
+    res = MIN(dp->len, len);
+    pbuf_copy_partial(dp, buf, res, 0);
+    pbuf_free(dp);
+    pbuf_copy_partial(ap, sa, MIN(*sa_len, ap->len), 0);
+    pbuf_free(ap);
   }
+  mgos_unlock();
+  return res;
 }
 
 static void mg_lwip_if_connect_udp_tcpip(void *arg) {
@@ -14863,14 +15573,7 @@  static void mg_lwip_if_connect_udp_tcpip(void *arg) {
 }
 
 void mg_lwip_if_connect_udp(struct mg_connection *nc) {
-  tcpip_callback(mg_lwip_if_connect_udp_tcpip, nc);
-}
-
-void mg_lwip_accept_conn(struct mg_connection *nc, struct tcp_pcb *tpcb) {
-  union socket_address sa;
-  SET_ADDR(&sa, &tpcb->remote_ip);
-  sa.sin.sin_port = htons(tpcb->remote_port);
-  mg_if_accept_tcp_cb(nc, &sa, sizeof(sa.sin));
+  mg_lwip_netif_run_on_tcpip(mg_lwip_if_connect_udp_tcpip, nc);
 }
 
 static void tcp_close_tcpip(void *arg) {
@@ -14880,17 +15583,11 @@  static void tcp_close_tcpip(void *arg) {
 void mg_lwip_handle_accept(struct mg_connection *nc) {
   struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
   if (cs->pcb.tcp == NULL) return;
-#if MG_ENABLE_SSL
-  if (cs->lc->flags & MG_F_SSL) {
-    if (mg_ssl_if_conn_accept(nc, cs->lc) != MG_SSL_OK) {
-      LOG(LL_ERROR, ("SSL error"));
-      tcpip_callback(tcp_close_tcpip, cs->pcb.tcp);
-    }
-  } else
-#endif
-  {
-    mg_lwip_accept_conn(nc, cs->pcb.tcp);
-  }
+  union socket_address sa;
+  struct tcp_pcb *tpcb = cs->pcb.tcp;
+  SET_ADDR(&sa, &tpcb->remote_ip);
+  sa.sin.sin_port = htons(tpcb->remote_port);
+  mg_if_accept_tcp_cb(nc, &sa, sizeof(sa.sin));
 }
 
 static err_t mg_lwip_accept_cb(void *arg, struct tcp_pcb *newtpcb, err_t err) {
@@ -14962,7 +15659,7 @@  static void mg_lwip_if_listen_tcp_tcpip(void *arg) {
 
 int mg_lwip_if_listen_tcp(struct mg_connection *nc, union socket_address *sa) {
   struct mg_lwip_if_listen_ctx ctx = {.nc = nc, .sa = sa};
-  tcpip_callback(mg_lwip_if_listen_tcp_tcpip, &ctx);
+  mg_lwip_netif_run_on_tcpip(mg_lwip_if_listen_tcp_tcpip, &ctx);
   return ctx.ret;
 }
 
@@ -14988,7 +15685,7 @@  static void mg_lwip_if_listen_udp_tcpip(void *arg) {
 
 int mg_lwip_if_listen_udp(struct mg_connection *nc, union socket_address *sa) {
   struct mg_lwip_if_listen_ctx ctx = {.nc = nc, .sa = sa};
-  tcpip_callback(mg_lwip_if_listen_udp_tcpip, &ctx);
+  mg_lwip_netif_run_on_tcpip(mg_lwip_if_listen_udp_tcpip, &ctx);
   return ctx.ret;
 }
 
@@ -15013,7 +15710,7 @@  static void mg_lwip_tcp_write_tcpip(void *arg) {
   if (len == 0) {
     DBG(("%p no buf avail %u %u %p %p", tpcb, tpcb->snd_buf, tpcb->snd_queuelen,
          tpcb->unsent, tpcb->unacked));
-    tcpip_callback(tcp_output_tcpip, tpcb);
+    mg_lwip_netif_run_on_tcpip(tcp_output_tcpip, tpcb);
     ctx->ret = 0;
     return;
   }
@@ -15045,17 +15742,18 @@  static void mg_lwip_tcp_write_tcpip(void *arg) {
     return;
   }
   ctx->ret = len;
+  (void) unsent;
+  (void) unacked;
 }
 
-static int mg_lwip_tcp_write(struct mg_connection *nc, const void *data,
-                             uint16_t len) {
-  struct mg_lwip_tcp_write_ctx ctx = {.nc = nc, .data = data, .len = len};
+int mg_lwip_if_tcp_send(struct mg_connection *nc, const void *buf, size_t len) {
+  struct mg_lwip_tcp_write_ctx ctx = {.nc = nc, .data = buf, .len = len};
   struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
+  if (nc->sock == INVALID_SOCKET) return -1;
   struct tcp_pcb *tpcb = cs->pcb.tcp;
-  if (tpcb == NULL) {
-    return -1;
-  }
-  tcpip_callback(mg_lwip_tcp_write_tcpip, &ctx);
+  if (tpcb == NULL) return -1;
+  if (tpcb->snd_buf <= 0) return 0;
+  mg_lwip_netif_run_on_tcpip(mg_lwip_tcp_write_tcpip, &ctx);
   return ctx.ret;
 }
 
@@ -15072,18 +15770,10 @@  static void udp_sendto_tcpip(void *arg) {
   ctx->ret = udp_sendto(ctx->upcb, ctx->p, ctx->ip, ctx->port);
 }
 
-static int mg_lwip_udp_send(struct mg_connection *nc, const void *data,
-                            uint16_t len) {
+static int mg_lwip_if_udp_send(struct mg_connection *nc, const void *data,
+                               size_t len) {
   struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  if (cs->pcb.udp == NULL) {
-    /*
-     * In case of UDP, this usually means, what
-     * async DNS resolve is still in progress and connection
-     * is not ready yet
-     */
-    DBG(("%p socket is not connected", nc));
-    return -1;
-  }
+  if (nc->sock == INVALID_SOCKET || cs->pcb.udp == NULL) return -1;
   struct udp_pcb *upcb = cs->pcb.udp;
   struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM);
 #if defined(LWIP_IPV4) && LWIP_IPV4 && defined(LWIP_IPV6) && LWIP_IPV6
@@ -15092,46 +15782,32 @@  static int mg_lwip_udp_send(struct mg_connection *nc, const void *data,
   ip_addr_t ip = {.addr = nc->sa.sin.sin_addr.s_addr};
 #endif
   u16_t port = ntohs(nc->sa.sin.sin_port);
-  if (p == NULL) {
-    DBG(("OOM"));
-    return 0;
-  }
+  if (p == NULL) return 0;
   memcpy(p->payload, data, len);
   struct udp_sendto_ctx ctx = {.upcb = upcb, .p = p, .ip = &ip, .port = port};
-  tcpip_callback(udp_sendto_tcpip, &ctx);
+  mg_lwip_netif_run_on_tcpip(udp_sendto_tcpip, &ctx);
   cs->err = ctx.ret;
   pbuf_free(p);
-  return (cs->err == ERR_OK ? len : -1);
+  return (cs->err == ERR_OK ? (int) len : -2);
 }
 
-static void mg_lwip_send_more(struct mg_connection *nc) {
-  int num_sent = 0;
-  if (nc->sock == INVALID_SOCKET) return;
-  if (nc->flags & MG_F_UDP) {
-    num_sent = mg_lwip_udp_send(nc, nc->send_mbuf.buf, nc->send_mbuf.len);
-    DBG(("%p mg_lwip_udp_send %u = %d", nc, nc->send_mbuf.len, num_sent));
-  } else {
-    num_sent = mg_lwip_tcp_write(nc, nc->send_mbuf.buf, nc->send_mbuf.len);
-    DBG(("%p mg_lwip_tcp_write %u = %d", nc, nc->send_mbuf.len, num_sent));
-  }
-  if (num_sent == 0) return;
-  if (num_sent > 0) {
-    mg_if_sent_cb(nc, num_sent);
-  } else {
-    mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
+static int mg_lwip_if_can_send(struct mg_connection *nc,
+                               struct mg_lwip_conn_state *cs) {
+  int can_send = 0;
+  if (nc->send_mbuf.len > 0 || (nc->flags & MG_F_WANT_WRITE)) {
+    /* We have stuff to send, but can we? */
+    if (nc->flags & MG_F_UDP) {
+      /* UDP is always ready for sending. */
+      can_send = (cs->pcb.udp != NULL);
+    } else {
+      can_send = (cs->pcb.tcp != NULL && cs->pcb.tcp->snd_buf > 0);
+/* See comment above. */
+#if CS_PLATFORM == CS_P_ESP8266
+      if (cs->pcb.tcp->unacked != NULL) can_send = 0;
+#endif
+    }
   }
-}
-
-void mg_lwip_if_tcp_send(struct mg_connection *nc, const void *buf,
-                         size_t len) {
-  mbuf_append(&nc->send_mbuf, buf, len);
-  mg_lwip_mgr_schedule_poll(nc->mgr);
-}
-
-void mg_lwip_if_udp_send(struct mg_connection *nc, const void *buf,
-                         size_t len) {
-  mbuf_append(&nc->send_mbuf, buf, len);
-  mg_lwip_mgr_schedule_poll(nc->mgr);
+  return can_send;
 }
 
 struct tcp_recved_ctx {
@@ -15141,30 +15817,38 @@  struct tcp_recved_ctx {
 
 void tcp_recved_tcpip(void *arg) {
   struct tcp_recved_ctx *ctx = (struct tcp_recved_ctx *) arg;
-  tcp_recved(ctx->tpcb, ctx->len);
+  if (ctx->tpcb != NULL) tcp_recved(ctx->tpcb, ctx->len);
 }
 
-void mg_lwip_if_recved(struct mg_connection *nc, size_t len) {
-  if (nc->flags & MG_F_UDP) return;
+static int mg_lwip_if_tcp_recv(struct mg_connection *nc, void *buf,
+                               size_t len) {
+  int res = 0;
+  char *bufp = buf;
   struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  if (nc->sock == INVALID_SOCKET || cs->pcb.tcp == NULL) {
-    DBG(("%p invalid socket", nc));
-    return;
+  if (nc->sock == INVALID_SOCKET) return -1;
+  mgos_lock();
+  while (cs->rx_chain != NULL && len > 0) {
+    struct pbuf *seg = cs->rx_chain;
+    size_t seg_len = (seg->len - cs->rx_offset);
+    size_t copy_len = MIN(len, seg_len);
+
+    pbuf_copy_partial(seg, bufp, copy_len, cs->rx_offset);
+    len -= copy_len;
+    res += copy_len;
+    bufp += copy_len;
+    cs->rx_offset += copy_len;
+    if (cs->rx_offset == cs->rx_chain->len) {
+      cs->rx_chain = pbuf_dechain(cs->rx_chain);
+      pbuf_free(seg);
+      cs->rx_offset = 0;
+    }
   }
-  DBG(("%p %p %u %u", nc, cs->pcb.tcp, len,
-       (cs->rx_chain ? cs->rx_chain->tot_len : 0)));
-  struct tcp_recved_ctx ctx = {.tpcb = cs->pcb.tcp, .len = len};
-#if MG_ENABLE_SSL
-  if (!(nc->flags & MG_F_SSL)) {
-    tcpip_callback(tcp_recved_tcpip, &ctx);
-  } else {
-    /* Currently SSL acknowledges data immediately.
-     * TODO(rojer): Find a way to propagate mg_lwip_if_recved. */
+  mgos_unlock();
+  if (res > 0) {
+    struct tcp_recved_ctx ctx = {.tpcb = cs->pcb.tcp, .len = res};
+    mg_lwip_netif_run_on_tcpip(tcp_recved_tcpip, &ctx);
   }
-#else
-  tcpip_callback(tcp_recved_tcpip, &ctx);
-#endif
-  mbuf_trim(&nc->recv_mbuf);
+  return res;
 }
 
 int mg_lwip_if_create_conn(struct mg_connection *nc) {
@@ -15189,7 +15873,7 @@  void mg_lwip_if_destroy_conn(struct mg_connection *nc) {
       tcp_arg(tpcb, NULL);
       DBG(("%p tcp_close %p", nc, tpcb));
       tcp_arg(tpcb, NULL);
-      tcpip_callback(tcp_close_tcpip, tpcb);
+      mg_lwip_netif_run_on_tcpip(tcp_close_tcpip, tpcb);
     }
     while (cs->rx_chain != NULL) {
       struct pbuf *seg = cs->rx_chain;
@@ -15203,7 +15887,7 @@  void mg_lwip_if_destroy_conn(struct mg_connection *nc) {
     struct udp_pcb *upcb = cs->pcb.udp;
     if (upcb != NULL) {
       DBG(("%p udp_remove %p", nc, upcb));
-      tcpip_callback(udp_remove_tcpip, upcb);
+      mg_lwip_netif_run_on_tcpip(udp_remove_tcpip, upcb);
     }
     memset(cs, 0, sizeof(*cs));
     MG_FREE(cs);
@@ -15253,7 +15937,8 @@  void mg_lwip_if_sock_set(struct mg_connection *nc, sock_t sock) {
     mg_lwip_if_connect_udp,                                           \
     mg_lwip_if_tcp_send,                                              \
     mg_lwip_if_udp_send,                                              \
-    mg_lwip_if_recved,                                                \
+    mg_lwip_if_tcp_recv,                                              \
+    mg_lwip_if_udp_recv,                                              \
     mg_lwip_if_create_conn,                                           \
     mg_lwip_if_destroy_conn,                                          \
     mg_lwip_if_sock_set,                                              \
@@ -15271,8 +15956,20 @@  const struct mg_iface_vtable mg_default_iface_vtable = MG_LWIP_IFACE_VTABLE;
 #line 1 "common/platforms/lwip/mg_lwip_ev_mgr.c"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #if MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL
@@ -15313,38 +16010,27 @@  void mg_ev_mgr_lwip_process_signals(struct mg_mgr *mgr) {
       (struct mg_ev_mgr_lwip_data *) mgr->ifaces[MG_MAIN_IFACE]->data;
   while (md->sig_queue_len > 0) {
     mgos_lock();
-    int sig = md->sig_queue[md->start_index].sig;
-    struct mg_connection *nc = md->sig_queue[md->start_index].nc;
+    int i = md->start_index;
+    int sig = md->sig_queue[i].sig;
+    struct mg_connection *nc = md->sig_queue[i].nc;
     struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-    md->start_index = (md->start_index + 1) % MG_SIG_QUEUE_LEN;
+    md->start_index = (i + 1) % MG_SIG_QUEUE_LEN;
     md->sig_queue_len--;
     mgos_unlock();
     if (nc->iface == NULL || nc->mgr == NULL) continue;
     switch (sig) {
       case MG_SIG_CONNECT_RESULT: {
-#if MG_ENABLE_SSL
-        if (cs->err == 0 && (nc->flags & MG_F_SSL) &&
-            !(nc->flags & MG_F_SSL_HANDSHAKE_DONE)) {
-          mg_lwip_ssl_do_hs(nc);
-        } else
-#endif
-        {
-          mg_if_connect_cb(nc, cs->err);
-        }
+        mg_if_connect_cb(nc, cs->err);
         break;
       }
       case MG_SIG_CLOSE_CONN: {
-        nc->flags |= MG_F_SEND_AND_CLOSE;
         mg_close_conn(nc);
         break;
       }
       case MG_SIG_RECV: {
         cs->recv_pending = 0;
-        if (nc->flags & MG_F_UDP) {
-          mg_lwip_handle_recv_udp(nc);
-        } else {
-          mg_lwip_handle_recv_tcp(nc);
-        }
+        mg_if_can_recv_cb(nc);
+        mbuf_trim(&nc->recv_mbuf);
         break;
       }
       case MG_SIG_TOMBSTONE: {
@@ -15359,8 +16045,13 @@  void mg_ev_mgr_lwip_process_signals(struct mg_mgr *mgr) {
 }
 
 void mg_lwip_if_init(struct mg_iface *iface) {
-  LOG(LL_INFO, ("%p Mongoose init", iface));
+  LOG(LL_INFO, ("Mongoose %s, LwIP %u.%u.%u", MG_VERSION, LWIP_VERSION_MAJOR,
+                LWIP_VERSION_MINOR, LWIP_VERSION_REVISION));
   iface->data = MG_CALLOC(1, sizeof(struct mg_ev_mgr_lwip_data));
+#if !NO_SYS && !LWIP_TCPIP_CORE_LOCKING
+  sys_sem_new(&s_tcpip_call_lock_sem, 1);
+  sys_sem_new(&s_tcpip_call_sync_sem, 0);
+#endif
 }
 
 void mg_lwip_if_free(struct mg_iface *iface) {
@@ -15398,46 +16089,11 @@  time_t mg_lwip_if_poll(struct mg_iface *iface, int timeout_ms) {
     struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
     tmp = nc->next;
     n++;
-    if ((nc->flags & MG_F_CLOSE_IMMEDIATELY) ||
-        ((nc->flags & MG_F_SEND_AND_CLOSE) && (nc->flags & MG_F_UDP) &&
-         (nc->send_mbuf.len == 0))) {
-      mg_close_conn(nc);
-      continue;
-    }
-    mg_if_poll(nc, now);
-    mg_if_timer(nc, now);
-#if MG_ENABLE_SSL
-    if ((nc->flags & MG_F_SSL) && cs != NULL && cs->pcb.tcp != NULL &&
-        cs->pcb.tcp->state == ESTABLISHED) {
-      if (((nc->flags & MG_F_WANT_WRITE) ||
-           ((nc->send_mbuf.len > 0) &&
-            (nc->flags & MG_F_SSL_HANDSHAKE_DONE))) &&
-          cs->pcb.tcp->snd_buf > 0) {
-        /* Can write more. */
-        if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {
-          if (!(nc->flags & MG_F_CONNECTING)) mg_lwip_ssl_send(nc);
-        } else {
-          mg_lwip_ssl_do_hs(nc);
-        }
-      }
-      if (cs->rx_chain != NULL || (nc->flags & MG_F_WANT_READ)) {
-        if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {
-          if (!(nc->flags & MG_F_CONNECTING)) mg_lwip_ssl_recv(nc);
-        } else {
-          mg_lwip_ssl_do_hs(nc);
-        }
-      }
-    } else
-#endif /* MG_ENABLE_SSL */
-    {
-      if (nc->send_mbuf.len > 0 && !(nc->flags & MG_F_CONNECTING)) {
-        mg_lwip_send_more(nc);
-      }
-    }
+    if (!mg_if_poll(nc, now)) continue;
     if (nc->sock != INVALID_SOCKET &&
         !(nc->flags & (MG_F_UDP | MG_F_LISTENING)) && cs->pcb.tcp != NULL &&
         cs->pcb.tcp->unsent != NULL) {
-      tcpip_callback(tcp_output_tcpip, cs->pcb.tcp);
+      mg_lwip_netif_run_on_tcpip(tcp_output_tcpip, cs->pcb.tcp);
     }
     if (nc->ev_timer_time > 0) {
       if (num_timers == 0 || nc->ev_timer_time < min_timer) {
@@ -15447,14 +16103,17 @@  time_t mg_lwip_if_poll(struct mg_iface *iface, int timeout_ms) {
     }
 
     if (nc->sock != INVALID_SOCKET) {
-      /* Try to consume data from cs->rx_chain */
-      mg_lwip_consume_rx_chain_tcp(nc);
-
-      /*
-       * If the connection is about to close, and rx_chain is finally empty,
-       * send the MG_SIG_CLOSE_CONN signal
-       */
-      if (cs->draining_rx_chain && cs->rx_chain == NULL) {
+      if (mg_lwip_if_can_send(nc, cs)) {
+        mg_if_can_send_cb(nc);
+        mbuf_trim(&nc->send_mbuf);
+      }
+      if (cs->rx_chain != NULL) {
+        mg_if_can_recv_cb(nc);
+      } else if (cs->draining_rx_chain) {
+        /*
+         * If the connection is about to close, and rx_chain is finally empty,
+         * send the MG_SIG_CLOSE_CONN signal
+         */
         mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
       }
     }
@@ -15468,257 +16127,25 @@  time_t mg_lwip_if_poll(struct mg_iface *iface, int timeout_ms) {
   return now;
 }
 
-uint32_t mg_lwip_get_poll_delay_ms(struct mg_mgr *mgr) {
-  struct mg_connection *nc;
-  double now;
-  double min_timer = 0;
-  int num_timers = 0;
-  mg_ev_mgr_lwip_process_signals(mgr);
-  for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {
-    struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-    if (nc->ev_timer_time > 0) {
-      if (num_timers == 0 || nc->ev_timer_time < min_timer) {
-        min_timer = nc->ev_timer_time;
-      }
-      num_timers++;
-    }
-    if (nc->send_mbuf.len > 0
-#if MG_ENABLE_SSL
-        || (nc->flags & MG_F_WANT_WRITE)
-#endif
-            ) {
-      int can_send = 0;
-      /* We have stuff to send, but can we? */
-      if (nc->flags & MG_F_UDP) {
-        /* UDP is always ready for sending. */
-        can_send = (cs->pcb.udp != NULL);
-      } else {
-        can_send = (cs->pcb.tcp != NULL && cs->pcb.tcp->snd_buf > 0);
-      }
-      /* We want and can send, request a poll immediately. */
-      if (can_send) return 0;
-    }
-  }
-  uint32_t timeout_ms = ~0;
-  now = mg_time();
-  if (num_timers > 0) {
-    /* If we have a timer that is past due, do a poll ASAP. */
-    if (min_timer < now) return 0;
-    double timer_timeout_ms = (min_timer - now) * 1000 + 1 /* rounding */;
-    if (timer_timeout_ms < timeout_ms) {
-      timeout_ms = timer_timeout_ms;
-    }
-  }
-  return timeout_ms;
-}
-
 #endif /* MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL */
 #ifdef MG_MODULE_LINES
-#line 1 "common/platforms/lwip/mg_lwip_ssl_if.c"
-#endif
-/*
- * Copyright (c) 2014-2016 Cesanta Software Limited
- * All rights reserved
- */
-
-#if MG_ENABLE_SSL && MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL
-
-/* Amalgamated: #include "common/mg_mem.h" */
-/* Amalgamated: #include "common/cs_dbg.h" */
-
-#include <lwip/pbuf.h>
-#include <lwip/tcp.h>
-
-#ifndef MG_LWIP_SSL_IO_SIZE
-#define MG_LWIP_SSL_IO_SIZE 1024
-#endif
-
-#ifndef MIN
-#define MIN(a, b) ((a) < (b) ? (a) : (b))
-#endif
-
-void mg_lwip_ssl_do_hs(struct mg_connection *nc) {
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  int server_side = (nc->listener != NULL);
-  enum mg_ssl_if_result res;
-  if (nc->flags & MG_F_CLOSE_IMMEDIATELY) return;
-  res = mg_ssl_if_handshake(nc);
-  DBG(("%p %lu %d %d", nc, nc->flags, server_side, res));
-  if (res != MG_SSL_OK) {
-    if (res == MG_SSL_WANT_WRITE) {
-      nc->flags |= MG_F_WANT_WRITE;
-      cs->err = 0;
-    } else if (res == MG_SSL_WANT_READ) {
-      /*
-       * Nothing to do in particular, we are callback-driven.
-       * What we definitely do not need anymore is SSL reading (nothing left).
-       */
-      nc->flags &= ~MG_F_WANT_READ;
-      cs->err = 0;
-    } else {
-      cs->err = res;
-      if (server_side) {
-        mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
-      } else {
-        mg_lwip_post_signal(MG_SIG_CONNECT_RESULT, nc);
-      }
-    }
-  } else {
-    cs->err = 0;
-    nc->flags &= ~MG_F_WANT_WRITE;
-    /*
-     * Handshake is done. Schedule a read immediately to consume app data
-     * which may already be waiting.
-     */
-    nc->flags |= (MG_F_SSL_HANDSHAKE_DONE | MG_F_WANT_READ);
-    if (server_side) {
-      mg_lwip_accept_conn(nc, cs->pcb.tcp);
-    } else {
-      mg_lwip_post_signal(MG_SIG_CONNECT_RESULT, nc);
-    }
-  }
-}
-
-void mg_lwip_ssl_send(struct mg_connection *nc) {
-  if (nc->sock == INVALID_SOCKET) {
-    DBG(("%p invalid socket", nc));
-    return;
-  }
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  /* It's ok if the buffer is empty. Return value of 0 may also be valid. */
-  int len = cs->last_ssl_write_size;
-  if (len == 0) {
-    len = MIN(MG_LWIP_SSL_IO_SIZE, nc->send_mbuf.len);
-  }
-  int ret = mg_ssl_if_write(nc, nc->send_mbuf.buf, len);
-  DBG(("%p SSL_write %u = %d", nc, len, ret));
-  if (ret > 0) {
-    mg_if_sent_cb(nc, ret);
-    cs->last_ssl_write_size = 0;
-  } else if (ret < 0) {
-    /* This is tricky. We must remember the exact data we were sending to retry
-     * exactly the same send next time. */
-    cs->last_ssl_write_size = len;
-  }
-  if (ret == len) {
-    nc->flags &= ~MG_F_WANT_WRITE;
-  } else if (ret == MG_SSL_WANT_WRITE) {
-    nc->flags |= MG_F_WANT_WRITE;
-  } else {
-    mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
-  }
-}
-
-void mg_lwip_ssl_recv(struct mg_connection *nc) {
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  /* Don't deliver data before connect callback */
-  if (nc->flags & MG_F_CONNECTING) return;
-  while (nc->recv_mbuf.len < nc->recv_mbuf_limit) {
-    char *buf = (char *) MG_MALLOC(MG_LWIP_SSL_IO_SIZE);
-    if (buf == NULL) return;
-    int ret = mg_ssl_if_read(nc, buf, MG_LWIP_SSL_IO_SIZE);
-    DBG(("%p %p SSL_read %u = %d", nc, cs->rx_chain, MG_LWIP_SSL_IO_SIZE, ret));
-    if (ret <= 0) {
-      MG_FREE(buf);
-      if (ret == MG_SSL_WANT_WRITE) {
-        nc->flags |= MG_F_WANT_WRITE;
-        return;
-      } else if (ret == MG_SSL_WANT_READ) {
-        /*
-         * Nothing to do in particular, we are callback-driven.
-         * What we definitely do not need anymore is SSL reading (nothing left).
-         */
-        nc->flags &= ~MG_F_WANT_READ;
-        cs->err = 0;
-        return;
-      } else {
-        mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);
-        return;
-      }
-    } else {
-      mg_if_recv_tcp_cb(nc, buf, ret, 1 /* own */);
-    }
-  }
-}
-
-#ifdef KR_VERSION
-
-ssize_t kr_send(int fd, const void *buf, size_t len) {
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) fd;
-  int ret = mg_lwip_tcp_write(cs->nc, buf, len);
-  DBG(("%p mg_lwip_tcp_write %u = %d", cs->nc, len, ret));
-  if (ret == 0) ret = KR_IO_WOULDBLOCK;
-  return ret;
-}
-
-ssize_t kr_recv(int fd, void *buf, size_t len) {
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) fd;
-  struct pbuf *seg = cs->rx_chain;
-  if (seg == NULL) {
-    DBG(("%u - nothing to read", len));
-    return KR_IO_WOULDBLOCK;
-  }
-  size_t seg_len = (seg->len - cs->rx_offset);
-  DBG(("%u %u %u %u", len, cs->rx_chain->len, seg_len, cs->rx_chain->tot_len));
-  len = MIN(len, seg_len);
-  pbuf_copy_partial(seg, buf, len, cs->rx_offset);
-  cs->rx_offset += len;
-  tcp_recved(cs->pcb.tcp, len);
-  if (cs->rx_offset == cs->rx_chain->len) {
-    cs->rx_chain = pbuf_dechain(cs->rx_chain);
-    pbuf_free(seg);
-    cs->rx_offset = 0;
-  }
-  return len;
-}
-
-#elif MG_SSL_IF == MG_SSL_IF_MBEDTLS
-
-int ssl_socket_send(void *ctx, const unsigned char *buf, size_t len) {
-  struct mg_connection *nc = (struct mg_connection *) ctx;
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  int ret = mg_lwip_tcp_write(cs->nc, buf, len);
-  if (ret == 0) ret = MBEDTLS_ERR_SSL_WANT_WRITE;
-  LOG(LL_DEBUG, ("%p %d -> %d", nc, len, ret));
-  return ret;
-}
-
-int ssl_socket_recv(void *ctx, unsigned char *buf, size_t len) {
-  struct mg_connection *nc = (struct mg_connection *) ctx;
-  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;
-  struct pbuf *seg = cs->rx_chain;
-  if (seg == NULL) {
-    DBG(("%u - nothing to read", len));
-    return MBEDTLS_ERR_SSL_WANT_READ;
-  }
-  size_t seg_len = (seg->len - cs->rx_offset);
-  DBG(("%u %u %u %u", len, cs->rx_chain->len, seg_len, cs->rx_chain->tot_len));
-  mgos_lock();
-  len = MIN(len, seg_len);
-  pbuf_copy_partial(seg, buf, len, cs->rx_offset);
-  cs->rx_offset += len;
-  /* TCP PCB may be NULL if connection has already been closed
-   * but we still have data to deliver to SSL. */
-  if (cs->pcb.tcp != NULL) tcp_recved(cs->pcb.tcp, len);
-  if (cs->rx_offset == cs->rx_chain->len) {
-    cs->rx_chain = pbuf_dechain(cs->rx_chain);
-    pbuf_free(seg);
-    cs->rx_offset = 0;
-  }
-  mgos_unlock();
-  LOG(LL_DEBUG, ("%p <- %d", nc, (int) len));
-  return len;
-}
-
-#endif
-
-#endif /* MG_ENABLE_SSL && MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL */
-#ifdef MG_MODULE_LINES
 #line 1 "common/platforms/wince/wince_libc.c"
 #endif
 /*
- * Copyright (c) 2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifdef WINCE
@@ -15794,8 +16221,20 @@  static void mg_gmt_time_string(char *buf, size_t buf_len, time_t *t) {
 #line 1 "common/platforms/pic32/pic32_net_if.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_PLATFORMS_PIC32_NET_IF_H_
@@ -15822,8 +16261,20 @@  extern const struct mg_iface_vtable mg_pic32_iface_vtable;
 #line 1 "common/platforms/pic32/pic32_net_if.c"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #if MG_ENABLE_NET_IF_PIC32
@@ -16117,8 +16568,20 @@  const struct mg_iface_vtable mg_default_iface_vtable = MG_PIC32_IFACE_VTABLE;
 #line 1 "common/platforms/windows/windows_direct.c"
 #endif
 /*
- * Copyright (c) 2017 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifdef _WIN32
diff --git a/mongoose/mongoose.h b/mongoose/mongoose.h
index 310871d..c59fbb6 100644
--- a/mongoose/mongoose.h
+++ b/mongoose/mongoose.h
@@ -18,14 +18,12 @@ 
  *
  * Alternatively, you can license this software under a commercial
  * license, as set out in <https://www.cesanta.com/license>.
- *
- * SPDX-License-Identifier: GPL-2.0
  */
 
 #ifndef CS_MONGOOSE_SRC_COMMON_H_
 #define CS_MONGOOSE_SRC_COMMON_H_
 
-#define MG_VERSION "6.11"
+#define MG_VERSION "6.16"
 
 /* Local tweaks, applied before any of Mongoose's own headers. */
 #ifdef MG_LOCALS
@@ -60,8 +58,9 @@ 
 #define CS_P_NRF51 12
 #define CS_P_NRF52 10
 #define CS_P_PIC32 11
+#define CS_P_RS14100 18
 #define CS_P_STM32 16
-/* Next id: 18 */
+/* Next id: 19 */
 
 /* If not specified explicitly, we guess platform by defines. */
 #ifndef CS_PLATFORM
@@ -93,6 +92,8 @@ 
 #elif defined(TARGET_IS_TM4C129_RA0) || defined(TARGET_IS_TM4C129_RA1) || \
     defined(TARGET_IS_TM4C129_RA2)
 #define CS_PLATFORM CS_P_TM4C129
+#elif defined(RS14100)
+#define CS_PLATFORM CS_P_RS14100
 #elif defined(STM32)
 #define CS_PLATFORM CS_P_STM32
 #endif
@@ -107,6 +108,7 @@ 
 #define MG_NET_IF_SIMPLELINK 2
 #define MG_NET_IF_LWIP_LOW_LEVEL 3
 #define MG_NET_IF_PIC32 4
+#define MG_NET_IF_NULL 5
 
 #define MG_SSL_IF_OPENSSL 1
 #define MG_SSL_IF_MBEDTLS 2
@@ -126,12 +128,26 @@ 
 /* Amalgamated: #include "common/platforms/platform_nxp_lpc.h" */
 /* Amalgamated: #include "common/platforms/platform_nxp_kinetis.h" */
 /* Amalgamated: #include "common/platforms/platform_pic32.h" */
+/* Amalgamated: #include "common/platforms/platform_rs14100.h" */
 /* Amalgamated: #include "common/platforms/platform_stm32.h" */
+#if CS_PLATFORM == CS_P_CUSTOM
+#include <platform_custom.h>
+#endif
 
 /* Common stuff */
 
+#if !defined(PRINTF_LIKE)
+#if defined(__GNUC__) || defined(__clang__) || defined(__TI_COMPILER_VERSION__)
+#define PRINTF_LIKE(f, a) __attribute__((format(printf, f, a)))
+#else
+#define PRINTF_LIKE(f, a)
+#endif
+#endif
+
 #if !defined(WEAK)
-#if (defined(__GNUC__) || defined(__TI_COMPILER_VERSION__)) && !defined(_WIN32)
+#if (defined(__GNUC__) || defined(__clang__) || \
+     defined(__TI_COMPILER_VERSION__)) &&       \
+    !defined(_WIN32)
 #define WEAK __attribute__((weak))
 #else
 #define WEAK
@@ -495,8 +511,20 @@  typedef struct stat cs_stat_t;
 #line 1 "common/platforms/platform_esp32.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_PLATFORMS_PLATFORM_ESP32_H_
@@ -540,8 +568,20 @@  typedef struct stat cs_stat_t;
 #line 1 "common/platforms/platform_esp8266.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_PLATFORMS_PLATFORM_ESP8266_H_
@@ -571,10 +611,6 @@  typedef struct stat cs_stat_t;
 #define __cdecl
 #define _FILE_OFFSET_BITS 32
 
-#if !defined(RTOS_SDK) && !defined(__cplusplus)
-#define fileno(x) -1
-#endif
-
 #define MG_LWIP 1
 
 /* struct timeval is defined in sys/time.h. */
@@ -605,8 +641,20 @@  typedef struct stat cs_stat_t;
 #line 1 "common/platforms/platform_cc3100.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_PLATFORMS_PLATFORM_CC3100_H_
@@ -654,8 +702,20 @@  int inet_pton(int af, const char *src, void *dst);
 #line 1 "common/platforms/platform_cc3200.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_PLATFORMS_PLATFORM_CC3200_H_
@@ -776,11 +836,147 @@  int stat(const char *pathname, struct stat *st);
 #endif /* CS_PLATFORM == CS_P_CC3200 */
 #endif /* CS_COMMON_PLATFORMS_PLATFORM_CC3200_H_ */
 #ifdef MG_MODULE_LINES
+#line 1 "common/platforms/platform_cc3220.h"
+#endif
+/*
+ * Copyright (c) 2014-2018 Cesanta Software Limited
+ * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CS_COMMON_PLATFORMS_PLATFORM_CC3220_H_
+#define CS_COMMON_PLATFORMS_PLATFORM_CC3220_H_
+#if CS_PLATFORM == CS_P_CC3220
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <time.h>
+
+#ifndef __TI_COMPILER_VERSION__
+#include <fcntl.h>
+#include <sys/time.h>
+#endif
+
+#define MG_NET_IF MG_NET_IF_SIMPLELINK
+#ifndef MG_SSL_IF
+#define MG_SSL_IF MG_SSL_IF_SIMPLELINK
+#endif
+
+/* Only SPIFFS supports directories, SLFS does not. */
+#if defined(CC3220_FS_SPIFFS) && !defined(MG_ENABLE_DIRECTORY_LISTING)
+#define MG_ENABLE_DIRECTORY_LISTING 1
+#endif
+
+/* Amalgamated: #include "common/platforms/simplelink/cs_simplelink.h" */
+
+typedef int sock_t;
+#define INVALID_SOCKET (-1)
+#define SIZE_T_FMT "u"
+typedef struct stat cs_stat_t;
+#define DIRSEP '/'
+#define to64(x) strtoll(x, NULL, 10)
+#define INT64_FMT PRId64
+#define INT64_X_FMT PRIx64
+#define __cdecl
+
+#define fileno(x) -1
+
+/* Some functions we implement for Mongoose. */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef __TI_COMPILER_VERSION__
+struct SlTimeval_t;
+#define timeval SlTimeval_t
+int gettimeofday(struct timeval *t, void *tz);
+int settimeofday(const struct timeval *tv, const void *tz);
+
+int asprintf(char **strp, const char *fmt, ...);
+
+#endif
+
+/* TI's libc does not have stat & friends, add them. */
+#ifdef __TI_COMPILER_VERSION__
+
+#include <file.h>
+
+typedef unsigned int mode_t;
+typedef size_t _off_t;
+typedef long ssize_t;
+
+struct stat {
+  int st_ino;
+  mode_t st_mode;
+  int st_nlink;
+  time_t st_mtime;
+  off_t st_size;
+};
+
+int _stat(const char *pathname, struct stat *st);
+int stat(const char *pathname, struct stat *st);
+
+#define __S_IFMT 0170000
+
+#define __S_IFDIR 0040000
+#define __S_IFCHR 0020000
+#define __S_IFREG 0100000
+
+#define __S_ISTYPE(mode, mask) (((mode) &__S_IFMT) == (mask))
+
+#define S_IFDIR __S_IFDIR
+#define S_IFCHR __S_IFCHR
+#define S_IFREG __S_IFREG
+#define S_ISDIR(mode) __S_ISTYPE((mode), __S_IFDIR)
+#define S_ISREG(mode) __S_ISTYPE((mode), __S_IFREG)
+
+#endif /* __TI_COMPILER_VERSION__ */
+
+#ifndef CS_ENABLE_STDIO
+#define CS_ENABLE_STDIO 1
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CS_PLATFORM == CS_P_CC3220 */
+#endif /* CS_COMMON_PLATFORMS_PLATFORM_CC3200_H_ */
+#ifdef MG_MODULE_LINES
 #line 1 "common/platforms/platform_msp432.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_PLATFORMS_PLATFORM_MSP432_H_
@@ -887,8 +1083,20 @@  int _stat(const char *pathname, struct stat *st);
 #line 1 "common/platforms/platform_tm4c129.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_PLATFORMS_PLATFORM_TM4C129_H_
@@ -947,8 +1155,20 @@  typedef struct stat cs_stat_t;
 #line 1 "common/platforms/platform_mbed.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_PLATFORMS_PLATFORM_MBED_H_
@@ -1031,8 +1251,20 @@  in_addr_t inet_addr(const char *cp);
 #line 1 "common/platforms/platform_nrf51.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 #ifndef CS_COMMON_PLATFORMS_PLATFORM_NRF51_H_
 #define CS_COMMON_PLATFORMS_PLATFORM_NRF51_H_
@@ -1076,8 +1308,20 @@  int gettimeofday(struct timeval *tp, void *tzp);
 #line 1 "common/platforms/platform_nrf52.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 #ifndef CS_COMMON_PLATFORMS_PLATFORM_NRF52_H_
 #define CS_COMMON_PLATFORMS_PLATFORM_NRF52_H_
@@ -1124,8 +1368,20 @@  int gettimeofday(struct timeval *tp, void *tzp);
 #line 1 "common/platforms/simplelink/cs_simplelink.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_PLATFORMS_SIMPLELINK_CS_SIMPLELINK_H_
@@ -1282,7 +1538,7 @@  int sl_set_ssl_opts(int sock, struct mg_connection *nc);
 
 #endif /* SL_MAJOR_VERSION_NUM < 2 */
 
-int slfs_open(const unsigned char *fname, uint32_t flags);
+int slfs_open(const unsigned char *fname, uint32_t flags, uint32_t *token);
 
 #endif /* MG_NET_IF == MG_NET_IF_SIMPLELINK */
 
@@ -1495,8 +1751,20 @@  const char *strerror();
 #line 1 "common/platforms/platform_nxp_lpc.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_PLATFORMS_PLATFORM_NXP_LPC_H_
@@ -1551,8 +1819,20 @@  typedef struct stat cs_stat_t;
 #line 1 "common/platforms/platform_nxp_kinetis.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_PLATFORMS_PLATFORM_NXP_KINETIS_H_
@@ -1585,8 +1865,20 @@  typedef struct stat cs_stat_t;
 #line 1 "common/platforms/platform_pic32.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_PLATFORMS_PLATFORM_PIC32_H_
@@ -1622,11 +1914,87 @@  char *inet_ntoa(struct in_addr in);
 
 #endif /* CS_COMMON_PLATFORMS_PLATFORM_PIC32_H_ */
 #ifdef MG_MODULE_LINES
+#line 1 "common/platforms/platform_rs14100.h"
+#endif
+/*
+ * Copyright (c) 2014-2019 Cesanta Software Limited
+ * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CS_COMMON_PLATFORMS_PLATFORM_RS14100_H_
+#define CS_COMMON_PLATFORMS_PLATFORM_RS14100_H_
+#if CS_PLATFORM == CS_P_RS14100
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#ifdef MGOS_HAVE_VFS_COMMON
+#include <mgos_vfs.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define to64(x) strtoll(x, NULL, 10)
+#define INT64_FMT "lld"
+#define SIZE_T_FMT "u"
+typedef struct stat cs_stat_t;
+#define DIRSEP '/'
+
+#ifndef CS_ENABLE_STDIO
+#define CS_ENABLE_STDIO 1
+#endif
+
+#ifndef MG_ENABLE_FILESYSTEM
+#define MG_ENABLE_FILESYSTEM 1
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CS_PLATFORM == CS_P_RS14100 */
+#endif /* CS_COMMON_PLATFORMS_PLATFORM_RS14100_H_ */
+#ifdef MG_MODULE_LINES
 #line 1 "common/platforms/platform_stm32.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_PLATFORMS_PLATFORM_STM32_H_
@@ -1648,7 +2016,7 @@  char *inet_ntoa(struct in_addr in);
 #include <stm32_sdk_hal.h>
 
 #define to64(x) strtoll(x, NULL, 10)
-#define INT64_FMT PRId64
+#define INT64_FMT "lld"
 #define SIZE_T_FMT "u"
 typedef struct stat cs_stat_t;
 #define DIRSEP '/'
@@ -1667,8 +2035,20 @@  typedef struct stat cs_stat_t;
 #line 1 "common/platforms/lwip/mg_lwip.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_PLATFORMS_LWIP_MG_LWIP_H_
@@ -1722,7 +2102,6 @@  typedef int sock_t;
 #if MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL
 struct mg_mgr;
 struct mg_connection;
-uint32_t mg_lwip_get_poll_delay_ms(struct mg_mgr *mgr);
 void mg_lwip_set_keepalive_params(struct mg_connection *nc, int idle,
                                   int interval, int count);
 #endif
@@ -1739,8 +2118,20 @@  void mg_lwip_set_keepalive_params(struct mg_connection *nc, int idle,
 #line 1 "common/cs_md5.h"
 #endif
 /*
- * Copyright (c) 2014 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_MD5_H_
@@ -1775,8 +2166,20 @@  void cs_md5_final(unsigned char *md, cs_md5_ctx *c);
 #line 1 "common/cs_sha1.h"
 #endif
 /*
- * Copyright (c) 2014 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_SHA1_H_
@@ -1817,8 +2220,20 @@  void cs_hmac_sha1(const unsigned char *key, size_t key_len,
 #line 1 "common/cs_time.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_CS_TIME_H_
@@ -1850,8 +2265,20 @@  double cs_timegm(const struct tm *tm);
 #line 1 "common/mg_str.h"
 #endif
 /*
- * Copyright (c) 2014-2016 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_MG_STR_H_
@@ -1859,8 +2286,6 @@  double cs_timegm(const struct tm *tm);
 
 #include <stddef.h>
 
-/* Amalgamated: #include "common/platform.h" */
-
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -1885,6 +2310,8 @@  struct mg_str mg_mk_str_n(const char *s, size_t len);
 /* Macro for initializing mg_str. */
 #define MG_MK_STR(str_literal) \
   { str_literal, sizeof(str_literal) - 1 }
+#define MG_MK_STR_N(str_literal, len) \
+  { str_literal, len }
 #define MG_NULL_STR \
   { NULL, 0 }
 
@@ -1924,11 +2351,22 @@  int mg_strcmp(const struct mg_str str1, const struct mg_str str2);
  */
 int mg_strncmp(const struct mg_str str1, const struct mg_str str2, size_t n);
 
+/*
+ * Free the string (assuming it was heap allocated).
+ */
+void mg_strfree(struct mg_str *s);
+
 /*
  * Finds the first occurrence of a substring `needle` in the `haystack`.
  */
 const char *mg_strstr(const struct mg_str haystack, const struct mg_str needle);
 
+/* Strip whitespace at the start and the end of s */
+struct mg_str mg_strstrip(struct mg_str s);
+
+/* Returns 1 if s starts with the given prefix. */
+int mg_str_starts_with(struct mg_str s, struct mg_str prefix);
+
 #ifdef __cplusplus
 }
 #endif
@@ -1938,13 +2376,23 @@  const char *mg_strstr(const struct mg_str haystack, const struct mg_str needle);
 #line 1 "common/mbuf.h"
 #endif
 /*
- * Copyright (c) 2015 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 /*
- * === Memory Buffers
- *
  * Mbufs are mutable/growing memory buffers, like C++ strings.
  * Mbuf can append data to the end of a buffer or insert data into arbitrary
  * position in the middle of a buffer. The buffer grows automatically when
@@ -1965,6 +2413,14 @@  extern "C" {
 #define MBUF_SIZE_MULTIPLIER 1.5
 #endif
 
+#ifndef MBUF_SIZE_MAX_HEADROOM
+#ifdef BUFSIZ
+#define MBUF_SIZE_MAX_HEADROOM BUFSIZ
+#else
+#define MBUF_SIZE_MAX_HEADROOM 1024
+#endif
+#endif
+
 /* Memory buffer descriptor */
 struct mbuf {
   char *buf;   /* Buffer pointer */
@@ -1988,6 +2444,14 @@  void mbuf_free(struct mbuf *);
  */
 size_t mbuf_append(struct mbuf *, const void *data, size_t data_size);
 
+/*
+ * Appends data to the Mbuf and frees it (data must be heap-allocated).
+ *
+ * Returns the number of bytes appended or 0 if out of memory.
+ * data is freed irrespective of return value.
+ */
+size_t mbuf_append_and_free(struct mbuf *, void *data, size_t data_size);
+
 /*
  * Inserts data at a specified offset in the Mbuf.
  *
@@ -2008,6 +2472,12 @@  void mbuf_remove(struct mbuf *, size_t data_size);
  */
 void mbuf_resize(struct mbuf *, size_t new_size);
 
+/* Moves the state from one mbuf to the other. */
+void mbuf_move(struct mbuf *from, struct mbuf *to);
+
+/* Removes all the data from mbuf (if any). */
+void mbuf_clear(struct mbuf *);
+
 /* Shrinks an Mbuf by resizing its `size` to `len`. */
 void mbuf_trim(struct mbuf *);
 
@@ -2020,8 +2490,20 @@  void mbuf_trim(struct mbuf *);
 #line 1 "common/cs_base64.h"
 #endif
 /*
- * Copyright (c) 2014 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_CS_BASE64_H_
@@ -2056,6 +2538,14 @@  void cs_base64_finish(struct cs_base64_ctx *ctx);
 
 void cs_base64_encode(const unsigned char *src, int src_len, char *dst);
 void cs_fprint_base64(FILE *f, const unsigned char *src, int src_len);
+
+/*
+ * Decodes a base64 string `s` length `len` into `dst`.
+ * `dst` must have enough space to hold the result.
+ * `*dec_len` will contain the resulting length of the string in `dst`
+ * while return value will return number of processed bytes in `src`.
+ * Return value == len indicates successful processing of all the data.
+ */
 int cs_base64_decode(const unsigned char *s, int len, char *dst, int *dec_len);
 
 #ifdef __cplusplus
@@ -2069,8 +2559,20 @@  int cs_base64_decode(const unsigned char *s, int len, char *dst, int *dec_len);
 #line 1 "common/str_util.h"
 #endif
 /*
- * Copyright (c) 2015 Cesanta Software Limited
+ * Copyright (c) 2014-2018 Cesanta Software Limited
  * All rights reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the ""License"");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an ""AS IS"" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
  */
 
 #ifndef CS_COMMON_STR_UTIL_H_
@@ -2094,7 +2596,11 @@  int cs_base64_decode(const unsigned char *s, int len, char *dst, int *dec_len);
  * Expands to a string representation of its argument: e.g.
  * `CS_STRINGIFY_LIT(5) expands to "5"`
  */
+#if !defined(_MSC_VER) || _MSC_VER >= 1900
+#define CS_STRINGIFY_LIT(...) #__VA_ARGS__
+#else
 #define CS_STRINGIFY_LIT(x) #x
+#endif
 
 /*
  * Expands to a string representation of its argument, which is allowed
@@ -2119,7 +2625,8 @@  size_t c_strnlen(const char *s, size_t maxlen);
 /*
  * Equivalent of standard `snprintf()`.
  */
-int c_snprintf(char *buf, size_t buf_size, const char *format, ...);
+int c_snprintf(char *buf, size_t buf_size, const char *format, ...)
+    PRINTF_LIKE(3, 4);
 
 /*
  * Equivalent of standard `vsnprintf()`.
@@ -2187,7 +2694,8 @@  int mg_casecmp(const char *s1, const char *s2);
  *
  * The purpose of this is to avoid malloc-ing if generated strings are small.
  */
-int mg_asprintf(char **buf, size_t size, const char *fmt, ...);
+int mg_asprintf(char **buf, size_t size, const char *fmt, ...)
+    PRINTF_LIKE(3, 4);
 
 /* Same as mg_asprintf, but takes varargs list. */
 int mg_avprintf(char **buf, size_t size, const char *fmt, va_list ap);
@@ -2210,6 +2718,8 @@  const char *mg_next_comma_list_entry(const char *list, struct mg_str *val,
 
 /*
  * Like `mg_next_comma_list_entry()`, but takes `list` as `struct mg_str`.
+ * NB: Test return value's .p, not .len. On last itreation that yields result
+ * .len will be 0 but .p will not. When finished, .p will be NULL.
  */
 struct mg_str mg_next_comma_list_entry_n(struct mg_str list, struct mg_str *val,
                                          struct mg_str *eq_val);
@@ -2684,7 +3194,7 @@  struct {								\
 	if (LIST_NEXT((elm), field) != NULL &&				\
 	    LIST_NEXT((elm), field)->field.le_prev !=			\
 	     &((elm)->field.le_next))					\
-		panic("Bad link elm %p next->prev != elm", (elm));	\
+	     	panic("Bad link elm %p next->prev != elm", (elm));	\
 } while (0)
 
 #define	QMD_LIST_CHECK_PREV(elm, field) do {				\
@@ -2827,7 +3337,7 @@  struct {								\
 
 #define	QMD_TAILQ_CHECK_TAIL(head, field) do {				\
 	if (*(head)->tqh_last != NULL)					\
-		panic("Bad tailq NEXT(%p->tqh_last) != NULL", (head)); 	\
+	    	panic("Bad tailq NEXT(%p->tqh_last) != NULL", (head)); 	\
 } while (0)
 
 #define	QMD_TAILQ_CHECK_NEXT(elm, field) do {				\
@@ -3239,10 +3749,12 @@  struct mg_iface_vtable {
   void (*connect_udp)(struct mg_connection *nc);
 
   /* Send functions for TCP and UDP. Sent data is copied before return. */
-  void (*tcp_send)(struct mg_connection *nc, const void *buf, size_t len);
-  void (*udp_send)(struct mg_connection *nc, const void *buf, size_t len);
+  int (*tcp_send)(struct mg_connection *nc, const void *buf, size_t len);
+  int (*udp_send)(struct mg_connection *nc, const void *buf, size_t len);
 
-  void (*recved)(struct mg_connection *nc, size_t len);
+  int (*tcp_recv)(struct mg_connection *nc, void *buf, size_t len);
+  int (*udp_recv)(struct mg_connection *nc, void *buf, size_t len,
+                  union socket_address *sa, size_t *sa_len);
 
   /* Perform interface-related connection initialization. Return 1 on ok. */
   int (*create_conn)(struct mg_connection *nc);
@@ -3283,19 +3795,15 @@  void mg_if_accept_tcp_cb(struct mg_connection *nc, union socket_address *sa,
 
 /* Callback invoked by connect methods. err = 0 -> ok, != 0 -> error. */
 void mg_if_connect_cb(struct mg_connection *nc, int err);
-/* Callback that reports that data has been put on the wire. */
-void mg_if_sent_cb(struct mg_connection *nc, int num_sent);
 /*
- * Receive callback.
- * if `own` is true, buf must be heap-allocated and ownership is transferred
- * to the core.
- * Core will acknowledge consumption by calling iface::recved.
+ * Callback that tells the core that data can be received.
+ * Core will use tcp/udp_recv to retrieve the data.
  */
-void mg_if_recv_tcp_cb(struct mg_connection *nc, void *buf, int len, int own);
+void mg_if_can_recv_cb(struct mg_connection *nc);
+void mg_if_can_send_cb(struct mg_connection *nc);
 /*
  * Receive callback.
  * buf must be heap-allocated and ownership is transferred to the core.
- * Core will acknowledge consumption by calling iface::recved.
  */
 void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len,
                        union socket_address *sa, size_t sa_len);
@@ -3303,10 +3811,13 @@  void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len,
 /* void mg_if_close_conn(struct mg_connection *nc); */
 
 /* Deliver a POLL event to the connection. */
-void mg_if_poll(struct mg_connection *nc, time_t now);
+int mg_if_poll(struct mg_connection *nc, double now);
 
-/* Deliver a TIMER event to the connection. */
-void mg_if_timer(struct mg_connection *c, double now);
+/*
+ * Return minimal timer value amoung connections in the manager.
+ * Returns 0 if there aren't any timers.
+ */
+double mg_mgr_min_timer(const struct mg_mgr *mgr);
 
 #ifdef __cplusplus
 }
@@ -3333,7 +3844,7 @@  extern "C" {
 struct mg_ssl_if_ctx;
 struct mg_connection;
 
-void mg_ssl_if_init(void);
+void mg_ssl_if_init();
 
 enum mg_ssl_if_result {
   MG_SSL_OK = 0,
@@ -3465,6 +3976,7 @@  struct mg_mgr {
 #endif
   void *user_data; /* User data */
   int num_ifaces;
+  int num_calls;
   struct mg_iface **ifaces; /* network interfaces */
   const char *nameserver;   /* DNS server to use */
 };
@@ -3515,6 +4027,7 @@  struct mg_connection {
 #define MG_F_WANT_READ (1 << 6)          /* SSL specific */
 #define MG_F_WANT_WRITE (1 << 7)         /* SSL specific */
 #define MG_F_IS_WEBSOCKET (1 << 8)       /* Websocket specific */
+#define MG_F_RECV_AND_CLOSE (1 << 9) /* Drain rx and close the connection. */
 
 /* Flags that are settable by user */
 #define MG_F_SEND_AND_CLOSE (1 << 10)      /* Push remaining data and close  */
@@ -3577,17 +4090,17 @@  void mg_mgr_init_opt(struct mg_mgr *mgr, void *user_data,
  *
  * Closes and deallocates all active connections.
  */
-void mg_mgr_free(struct mg_mgr *);
+void mg_mgr_free(struct mg_mgr *mgr);
 
 /*
  * This function performs the actual IO and must be called in a loop
- * (an event loop). It returns the current timestamp.
+ * (an event loop). It returns number of user events generated (except POLLs).
  * `milli` is the maximum number of milliseconds to sleep.
  * `mg_mgr_poll()` checks all connections for IO readiness. If at least one
  * of the connections is IO-ready, `mg_mgr_poll()` triggers the respective
  * event handlers and returns.
  */
-time_t mg_mgr_poll(struct mg_mgr *, int milli);
+int mg_mgr_poll(struct mg_mgr *mgr, int milli);
 
 #if MG_ENABLE_BROADCAST
 /*
@@ -3600,7 +4113,7 @@  time_t mg_mgr_poll(struct mg_mgr *, int milli);
  * `func` callback function will be called by the IO thread for each
  * connection. When called, the event will be `MG_EV_POLL`, and a message will
  * be passed as the `ev_data` pointer. Maximum message size is capped
- * by `MG_CTL_MSG_MESSAGE_SIZE` which is set to 8192 bytes.
+ * by `MG_CTL_MSG_MESSAGE_SIZE` which is set to 8192 bytes by default.
  */
 void mg_broadcast(struct mg_mgr *mgr, mg_event_handler_t cb, void *data,
                   size_t len);
@@ -4236,10 +4749,17 @@  void mg_basic_auth_header(const struct mg_str user, const struct mg_str pass,
 
 /*
  * URL-escape the specified string.
- * All non-printable characters are escaped, plus `._-$,;~()/`.
+ * All characters acept letters, numbers and characters listed in
+ * `safe` are escaped. If `hex_upper`is true, `A-F` are used for hex digits.
  * Input need not be NUL-terminated, but the returned string is.
  * Returned string is heap-allocated and must be free()'d.
  */
+#define MG_URL_ENCODE_F_SPACE_AS_PLUS (1 << 0)
+#define MG_URL_ENCODE_F_UPPERCASE_HEX (1 << 1)
+struct mg_str mg_url_encode_opt(const struct mg_str src,
+                                const struct mg_str safe, unsigned int flags);
+
+/* Same as `mg_url_encode_opt(src, "._-$,;~()/", 0)`. */
 struct mg_str mg_url_encode(const struct mg_str src);
 
 #ifdef __cplusplus
@@ -4331,6 +4851,14 @@  struct mg_http_multipart_part {
   struct mg_str data;
   int status; /* <0 on error */
   void *user_data;
+  /*
+   * User handler can indicate how much of the data was consumed
+   * by setting this variable. By default, it is assumed that all
+   * data has been consumed by the handler.
+   * If not all data was consumed, user's handler will be invoked again later
+   * with the remainder.
+   */
+  size_t num_data_consumed;
 };
 
 /* SSI call context */
@@ -4349,7 +4877,7 @@  struct mg_ssi_call_ctx {
 
 #if MG_ENABLE_HTTP_WEBSOCKET
 #define MG_EV_WEBSOCKET_HANDSHAKE_REQUEST 111 /* struct http_message * */
-#define MG_EV_WEBSOCKET_HANDSHAKE_DONE 112    /* NULL */
+#define MG_EV_WEBSOCKET_HANDSHAKE_DONE 112    /* struct http_message * */
 #define MG_EV_WEBSOCKET_FRAME 113             /* struct websocket_message * */
 #define MG_EV_WEBSOCKET_CONTROL_FRAME 114     /* struct websocket_message * */
 #endif
@@ -4388,7 +4916,9 @@  struct mg_ssi_call_ctx {
  * - MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: server has received the WebSocket
  *   handshake request. `ev_data` contains parsed HTTP request.
  * - MG_EV_WEBSOCKET_HANDSHAKE_DONE: server has completed the WebSocket
- *   handshake. `ev_data` is `NULL`.
+ *   handshake. `ev_data` is a `struct http_message` containing the
+ *   client's request (server mode) or server's response (client).
+ *   In client mode handler can examine `resp_code`, which should be 101.
  * - MG_EV_WEBSOCKET_FRAME: new WebSocket frame has arrived. `ev_data` is
  *   `struct websocket_message *`
  *
@@ -4682,9 +5212,9 @@  int mg_http_parse_header2(struct mg_str *hdr, const char *var_name, char **buf,
 int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf,
                          size_t buf_size)
 #ifdef __GNUC__
-    __attribute__((deprecated));
+    __attribute__((deprecated))
 #endif
-;
+    ;
 
 /*
  * Gets and parses the Authorization: Basic header